@nebulaos/llm-gateway 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +54 -4
- package/dist/index.d.mts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +394 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +393 -54
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -30,18 +30,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
LLMGateway: () => LLMGateway
|
|
33
|
+
LLMGateway: () => LLMGateway,
|
|
34
|
+
LLMGatewayError: () => LLMGatewayError
|
|
34
35
|
});
|
|
35
36
|
module.exports = __toCommonJS(index_exports);
|
|
36
37
|
var import_openai = __toESM(require("openai"));
|
|
37
38
|
var import_node_crypto = require("crypto");
|
|
38
39
|
var import_core = require("@nebulaos/core");
|
|
40
|
+
var LLMGatewayError = class extends Error {
|
|
41
|
+
constructor(message, code, status, cause) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.status = status;
|
|
45
|
+
this.cause = cause;
|
|
46
|
+
this.name = "LLMGatewayError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
39
49
|
var LLMGateway = class {
|
|
40
50
|
providerName = "llm-gateway";
|
|
41
51
|
modelName;
|
|
42
52
|
client;
|
|
43
53
|
baseUrl;
|
|
44
54
|
logger;
|
|
55
|
+
options;
|
|
45
56
|
capabilities = {
|
|
46
57
|
inputFiles: {
|
|
47
58
|
mimeTypes: ["image/*"],
|
|
@@ -58,58 +69,134 @@ var LLMGateway = class {
|
|
|
58
69
|
baseURL,
|
|
59
70
|
...config.clientOptions
|
|
60
71
|
});
|
|
72
|
+
this.options = config.options;
|
|
61
73
|
}
|
|
62
74
|
async generate(messages, tools, options) {
|
|
75
|
+
const mergedOptions = { ...this.options, ...options };
|
|
63
76
|
const model = `route:${this.modelName}`;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
const messagesPreview = messages.map((m) => ({
|
|
78
|
+
role: m.role,
|
|
79
|
+
content: typeof m.content === "string" ? m.content.length > 500 ? m.content.slice(0, 500) + "..." : m.content : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content)
|
|
80
|
+
}));
|
|
81
|
+
const toolsPreview = tools?.map((t) => ({
|
|
82
|
+
name: t.function.name,
|
|
83
|
+
description: t.function.description?.slice(0, 200)
|
|
84
|
+
}));
|
|
85
|
+
return import_core.Tracing.withSpan(
|
|
86
|
+
{
|
|
87
|
+
kind: "llm",
|
|
88
|
+
name: `llm:${this.modelName}`,
|
|
89
|
+
data: {
|
|
90
|
+
provider: this.providerName,
|
|
91
|
+
model: this.modelName,
|
|
92
|
+
messagesCount: messages.length,
|
|
93
|
+
toolsCount: tools?.length ?? 0,
|
|
94
|
+
responseFormat: mergedOptions?.responseFormat,
|
|
95
|
+
messages: messagesPreview,
|
|
96
|
+
tools: toolsPreview
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
async (llmSpan) => {
|
|
100
|
+
const headers = this.buildGatewayHeaders();
|
|
101
|
+
this.logger.debug("LLM Gateway request", {
|
|
75
102
|
model,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
baseUrl: this.baseUrl,
|
|
104
|
+
stream: false,
|
|
105
|
+
messageCount: messages.length,
|
|
106
|
+
toolCount: tools?.length ?? 0
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
const { data: response, response: httpResponse } = await this.client.chat.completions.create(
|
|
110
|
+
{
|
|
111
|
+
model,
|
|
112
|
+
messages: this.convertMessages(messages),
|
|
113
|
+
tools: this.convertTools(tools),
|
|
114
|
+
response_format: mergedOptions?.responseFormat?.type === "json" ? mergedOptions.responseFormat.schema ? {
|
|
115
|
+
type: "json_schema",
|
|
116
|
+
json_schema: { name: "response", schema: mergedOptions.responseFormat.schema }
|
|
117
|
+
} : { type: "json_object" } : void 0,
|
|
118
|
+
...this.extractExtraOptions(mergedOptions)
|
|
119
|
+
},
|
|
120
|
+
{ headers }
|
|
121
|
+
).withResponse();
|
|
122
|
+
this.logger.debug("LLM Gateway response", {
|
|
123
|
+
model,
|
|
124
|
+
finishReason: response.choices?.[0]?.finish_reason,
|
|
125
|
+
hasUsage: Boolean(response.usage)
|
|
126
|
+
});
|
|
127
|
+
const choice = response.choices[0];
|
|
128
|
+
const message = choice.message;
|
|
129
|
+
const usage = this.mapUsage(response.usage);
|
|
130
|
+
const finishReason = this.mapFinishReason(choice.finish_reason);
|
|
131
|
+
const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);
|
|
132
|
+
await llmSpan.end({
|
|
133
|
+
status: "success",
|
|
134
|
+
data: {
|
|
135
|
+
usage: enrichment.usage ?? usage,
|
|
136
|
+
finishReason,
|
|
137
|
+
toolCallsCount: message.tool_calls?.length ?? 0,
|
|
138
|
+
outputPreview: message.content?.slice(0, 200),
|
|
139
|
+
// Enrichment from backend gateway
|
|
140
|
+
modelActual: enrichment.modelActual,
|
|
141
|
+
fallbackUsed: enrichment.fallbackUsed,
|
|
142
|
+
cost: enrichment.cost
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
content: message.content || "",
|
|
147
|
+
toolCalls: message.tool_calls?.map((tc) => ({
|
|
148
|
+
id: tc.id,
|
|
149
|
+
type: "function",
|
|
150
|
+
function: {
|
|
151
|
+
name: tc.function.name,
|
|
152
|
+
arguments: tc.function.arguments
|
|
153
|
+
}
|
|
154
|
+
})),
|
|
155
|
+
finishReason,
|
|
156
|
+
usage: enrichment.usage ?? usage
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
this.logger.error("LLM Gateway request failed", error, void 0, void 0);
|
|
160
|
+
const gatewayError = this.handleError(error);
|
|
161
|
+
await llmSpan.end({
|
|
162
|
+
status: "error",
|
|
163
|
+
data: {
|
|
164
|
+
error: {
|
|
165
|
+
message: gatewayError.message,
|
|
166
|
+
code: gatewayError.code,
|
|
167
|
+
status: gatewayError.status
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
throw gatewayError;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
);
|
|
110
175
|
}
|
|
111
176
|
async *generateStream(messages, tools, options) {
|
|
177
|
+
const mergedOptions = { ...this.options, ...options };
|
|
112
178
|
const model = `route:${this.modelName}`;
|
|
179
|
+
const messagesPreview = messages.map((m) => ({
|
|
180
|
+
role: m.role,
|
|
181
|
+
content: typeof m.content === "string" ? m.content.length > 500 ? m.content.slice(0, 500) + "..." : m.content : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content)
|
|
182
|
+
}));
|
|
183
|
+
const toolsPreview = tools?.map((t) => ({
|
|
184
|
+
name: t.function.name,
|
|
185
|
+
description: t.function.description?.slice(0, 200)
|
|
186
|
+
}));
|
|
187
|
+
const llmSpan = await import_core.Tracing.startSpan({
|
|
188
|
+
kind: "llm",
|
|
189
|
+
name: `llm:${this.modelName}`,
|
|
190
|
+
data: {
|
|
191
|
+
provider: this.providerName,
|
|
192
|
+
model: this.modelName,
|
|
193
|
+
messagesCount: messages.length,
|
|
194
|
+
toolsCount: tools?.length ?? 0,
|
|
195
|
+
responseFormat: mergedOptions?.responseFormat,
|
|
196
|
+
messages: messagesPreview,
|
|
197
|
+
tools: toolsPreview
|
|
198
|
+
}
|
|
199
|
+
});
|
|
113
200
|
const headers = this.buildGatewayHeaders();
|
|
114
201
|
this.logger.debug("LLM Gateway stream request", {
|
|
115
202
|
model,
|
|
@@ -127,43 +214,66 @@ var LLMGateway = class {
|
|
|
127
214
|
tools: this.convertTools(tools),
|
|
128
215
|
stream: true,
|
|
129
216
|
stream_options: { include_usage: true },
|
|
130
|
-
response_format:
|
|
217
|
+
response_format: mergedOptions?.responseFormat?.type === "json" ? mergedOptions.responseFormat.schema ? {
|
|
131
218
|
type: "json_schema",
|
|
132
|
-
json_schema: { name: "response", schema:
|
|
219
|
+
json_schema: { name: "response", schema: mergedOptions.responseFormat.schema }
|
|
133
220
|
} : { type: "json_object" } : void 0,
|
|
134
|
-
...this.extractExtraOptions(
|
|
221
|
+
...this.extractExtraOptions(mergedOptions)
|
|
135
222
|
},
|
|
136
223
|
{ headers }
|
|
137
224
|
);
|
|
138
225
|
} catch (error) {
|
|
139
226
|
this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
|
|
140
|
-
|
|
227
|
+
const gatewayError = this.handleError(error);
|
|
228
|
+
if (llmSpan) {
|
|
229
|
+
await llmSpan.end({
|
|
230
|
+
status: "error",
|
|
231
|
+
data: {
|
|
232
|
+
error: {
|
|
233
|
+
message: gatewayError.message,
|
|
234
|
+
code: gatewayError.code,
|
|
235
|
+
status: gatewayError.status
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
throw gatewayError;
|
|
141
241
|
}
|
|
242
|
+
let finalUsage;
|
|
243
|
+
let finalFinishReason;
|
|
244
|
+
let toolCallsCount = 0;
|
|
245
|
+
let outputPreview = "";
|
|
142
246
|
try {
|
|
143
247
|
for await (const chunk of stream) {
|
|
144
248
|
if (chunk.usage) {
|
|
249
|
+
finalUsage = this.mapUsage(chunk.usage);
|
|
145
250
|
yield {
|
|
146
251
|
type: "finish",
|
|
147
252
|
reason: "stop",
|
|
148
|
-
usage:
|
|
253
|
+
usage: finalUsage
|
|
149
254
|
};
|
|
150
255
|
}
|
|
151
256
|
const choice = chunk.choices?.[0];
|
|
152
257
|
if (!choice) continue;
|
|
153
258
|
if (choice.finish_reason) {
|
|
259
|
+
finalFinishReason = this.mapFinishReason(choice.finish_reason);
|
|
154
260
|
yield {
|
|
155
261
|
type: "finish",
|
|
156
|
-
reason:
|
|
262
|
+
reason: finalFinishReason
|
|
157
263
|
};
|
|
158
264
|
}
|
|
159
265
|
const delta = choice.delta;
|
|
160
266
|
if (!delta) continue;
|
|
161
267
|
if (delta.content) {
|
|
268
|
+
if (outputPreview.length < 200) {
|
|
269
|
+
outputPreview += delta.content.slice(0, 200 - outputPreview.length);
|
|
270
|
+
}
|
|
162
271
|
yield { type: "content_delta", delta: delta.content };
|
|
163
272
|
}
|
|
164
273
|
if (delta.tool_calls) {
|
|
165
274
|
for (const tc of delta.tool_calls) {
|
|
166
275
|
if (tc.id && tc.function?.name) {
|
|
276
|
+
toolCallsCount++;
|
|
167
277
|
yield {
|
|
168
278
|
type: "tool_call_start",
|
|
169
279
|
index: tc.index,
|
|
@@ -181,10 +291,199 @@ var LLMGateway = class {
|
|
|
181
291
|
}
|
|
182
292
|
}
|
|
183
293
|
}
|
|
294
|
+
if (llmSpan) {
|
|
295
|
+
await llmSpan.end({
|
|
296
|
+
status: "success",
|
|
297
|
+
data: {
|
|
298
|
+
usage: finalUsage,
|
|
299
|
+
finishReason: finalFinishReason,
|
|
300
|
+
toolCallsCount,
|
|
301
|
+
outputPreview
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
184
305
|
} catch (error) {
|
|
185
306
|
this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
|
|
186
|
-
|
|
307
|
+
const gatewayError = this.handleError(error);
|
|
308
|
+
if (llmSpan) {
|
|
309
|
+
await llmSpan.end({
|
|
310
|
+
status: "error",
|
|
311
|
+
data: {
|
|
312
|
+
error: {
|
|
313
|
+
message: gatewayError.message,
|
|
314
|
+
code: gatewayError.code,
|
|
315
|
+
status: gatewayError.status
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
throw gatewayError;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// ==========================================================================
|
|
324
|
+
// Error Handling
|
|
325
|
+
// ==========================================================================
|
|
326
|
+
/**
|
|
327
|
+
* Transforms raw errors into actionable LLMGatewayError with clear messages.
|
|
328
|
+
* This ensures developers get specific guidance on how to resolve issues.
|
|
329
|
+
*
|
|
330
|
+
* Differentiates between:
|
|
331
|
+
* - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)
|
|
332
|
+
* - Provider errors: LLM provider API key issues (check route config in dashboard)
|
|
333
|
+
*/
|
|
334
|
+
handleError(error) {
|
|
335
|
+
if (error instanceof import_openai.APIError) {
|
|
336
|
+
const status = error.status;
|
|
337
|
+
const originalMessage = error.message;
|
|
338
|
+
const errorSource = this.extractErrorSource(error);
|
|
339
|
+
if (status === 401) {
|
|
340
|
+
if (errorSource === "gateway") {
|
|
341
|
+
return new LLMGatewayError(
|
|
342
|
+
`LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. Original error: ${originalMessage}`,
|
|
343
|
+
"GATEWAY_AUTH_ERROR",
|
|
344
|
+
status,
|
|
345
|
+
error
|
|
346
|
+
);
|
|
347
|
+
} else {
|
|
348
|
+
return new LLMGatewayError(
|
|
349
|
+
`LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. Please verify the provider API key in your route configuration in the NebulaOS dashboard. Original error: ${originalMessage}`,
|
|
350
|
+
"PROVIDER_AUTH_ERROR",
|
|
351
|
+
status,
|
|
352
|
+
error
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (status === 403) {
|
|
357
|
+
if (errorSource === "gateway") {
|
|
358
|
+
return new LLMGatewayError(
|
|
359
|
+
`LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. Original error: ${originalMessage}`,
|
|
360
|
+
"GATEWAY_FORBIDDEN",
|
|
361
|
+
status,
|
|
362
|
+
error
|
|
363
|
+
);
|
|
364
|
+
} else {
|
|
365
|
+
return new LLMGatewayError(
|
|
366
|
+
`LLM Provider access denied: The provider API key does not have permission for this operation. Please verify the provider API key permissions in the NebulaOS dashboard. Original error: ${originalMessage}`,
|
|
367
|
+
"PROVIDER_FORBIDDEN",
|
|
368
|
+
status,
|
|
369
|
+
error
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (status === 429) {
|
|
374
|
+
return new LLMGatewayError(
|
|
375
|
+
`LLM Gateway rate limit exceeded: Too many requests to the LLM provider. Please wait before retrying or check your rate limit configuration. Original error: ${originalMessage}`,
|
|
376
|
+
"LLM_GATEWAY_RATE_LIMIT",
|
|
377
|
+
status,
|
|
378
|
+
error
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (status === 400) {
|
|
382
|
+
return new LLMGatewayError(
|
|
383
|
+
`LLM Gateway request error: Invalid request parameters. Please check your request configuration (model, messages, tools). Original error: ${originalMessage}`,
|
|
384
|
+
"LLM_GATEWAY_BAD_REQUEST",
|
|
385
|
+
status,
|
|
386
|
+
error
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
if (status === 404) {
|
|
390
|
+
return new LLMGatewayError(
|
|
391
|
+
`LLM Gateway route not found: The specified model or route does not exist. Please verify the route alias '${this.modelName}' is correct and provisioned. Original error: ${originalMessage}`,
|
|
392
|
+
"LLM_GATEWAY_NOT_FOUND",
|
|
393
|
+
status,
|
|
394
|
+
error
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (status === 408 || status === 504) {
|
|
398
|
+
return new LLMGatewayError(
|
|
399
|
+
`LLM Gateway timeout: The request took too long to complete. This may be due to high load or a complex request. Please try again. Original error: ${originalMessage}`,
|
|
400
|
+
"LLM_GATEWAY_TIMEOUT",
|
|
401
|
+
status,
|
|
402
|
+
error
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (status && status >= 500) {
|
|
406
|
+
return new LLMGatewayError(
|
|
407
|
+
`LLM Gateway server error: The LLM provider returned an error (${status}). This is typically a temporary issue. Please try again later. Original error: ${originalMessage}`,
|
|
408
|
+
"LLM_GATEWAY_SERVER_ERROR",
|
|
409
|
+
status,
|
|
410
|
+
error
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return new LLMGatewayError(
|
|
414
|
+
`LLM Gateway error (${status}): ${originalMessage}`,
|
|
415
|
+
"LLM_GATEWAY_ERROR",
|
|
416
|
+
status,
|
|
417
|
+
error
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (error instanceof Error) {
|
|
421
|
+
const msg = error.message.toLowerCase();
|
|
422
|
+
if (msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("network")) {
|
|
423
|
+
return new LLMGatewayError(
|
|
424
|
+
`LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. Please verify the gateway is running and accessible. Original error: ${error.message}`,
|
|
425
|
+
"LLM_GATEWAY_CONNECTION_ERROR",
|
|
426
|
+
void 0,
|
|
427
|
+
error
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("etimedout")) {
|
|
431
|
+
return new LLMGatewayError(
|
|
432
|
+
`LLM Gateway timeout: The connection timed out. Please check network connectivity and try again. Original error: ${error.message}`,
|
|
433
|
+
"LLM_GATEWAY_TIMEOUT",
|
|
434
|
+
void 0,
|
|
435
|
+
error
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return new LLMGatewayError(
|
|
439
|
+
`LLM Gateway error: ${error.message}`,
|
|
440
|
+
"LLM_GATEWAY_ERROR",
|
|
441
|
+
void 0,
|
|
442
|
+
error
|
|
443
|
+
);
|
|
187
444
|
}
|
|
445
|
+
return new LLMGatewayError(
|
|
446
|
+
`LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,
|
|
447
|
+
"LLM_GATEWAY_UNKNOWN_ERROR",
|
|
448
|
+
void 0,
|
|
449
|
+
error
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Extracts the error source from an APIError.
|
|
454
|
+
* The backend sets X-Error-Source header or includes source in the error body
|
|
455
|
+
* to differentiate between gateway errors (LLM Gateway API key) and provider errors.
|
|
456
|
+
*
|
|
457
|
+
* @returns "gateway" if the error is from LLM Gateway authentication,
|
|
458
|
+
* "provider" if the error is from the upstream LLM provider,
|
|
459
|
+
* undefined if the source cannot be determined.
|
|
460
|
+
*/
|
|
461
|
+
extractErrorSource(error) {
|
|
462
|
+
const headers = error.headers;
|
|
463
|
+
if (headers) {
|
|
464
|
+
const errorSource = headers["x-error-source"] || headers["X-Error-Source"];
|
|
465
|
+
if (errorSource === "gateway" || errorSource === "provider") {
|
|
466
|
+
return errorSource;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const errorBody = error.error;
|
|
470
|
+
if (errorBody && typeof errorBody === "object") {
|
|
471
|
+
const nestedError = errorBody.error;
|
|
472
|
+
if (nestedError && typeof nestedError === "object" && nestedError.source) {
|
|
473
|
+
const source = nestedError.source;
|
|
474
|
+
if (source === "gateway" || source === "provider") {
|
|
475
|
+
return source;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (errorBody.source === "gateway" || errorBody.source === "provider") {
|
|
479
|
+
return errorBody.source;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const msg = error.message.toLowerCase();
|
|
483
|
+
if (msg.includes("llm gateway api key") || msg.includes("llm gateway")) {
|
|
484
|
+
return "gateway";
|
|
485
|
+
}
|
|
486
|
+
return void 0;
|
|
188
487
|
}
|
|
189
488
|
// ==========================================================================
|
|
190
489
|
// Helpers (copied from OpenAI provider)
|
|
@@ -212,6 +511,46 @@ var LLMGateway = class {
|
|
|
212
511
|
}
|
|
213
512
|
return headers;
|
|
214
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Extracts enrichment data from backend HTTP headers.
|
|
516
|
+
* Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).
|
|
517
|
+
*/
|
|
518
|
+
extractEnrichmentFromHeaders(headers) {
|
|
519
|
+
const result = {};
|
|
520
|
+
const modelActual = headers.get("x-llm-model-actual");
|
|
521
|
+
if (modelActual) {
|
|
522
|
+
result.modelActual = modelActual;
|
|
523
|
+
}
|
|
524
|
+
const fallbackUsed = headers.get("x-llm-fallback-used");
|
|
525
|
+
if (fallbackUsed) {
|
|
526
|
+
result.fallbackUsed = fallbackUsed === "true";
|
|
527
|
+
}
|
|
528
|
+
const usageRaw = headers.get("x-llm-usage");
|
|
529
|
+
if (usageRaw) {
|
|
530
|
+
try {
|
|
531
|
+
const usage = JSON.parse(usageRaw);
|
|
532
|
+
result.usage = {
|
|
533
|
+
promptTokens: usage.prompt_tokens,
|
|
534
|
+
completionTokens: usage.completion_tokens,
|
|
535
|
+
totalTokens: usage.total_tokens,
|
|
536
|
+
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
|
|
537
|
+
// Preserve any additional token fields from provider
|
|
538
|
+
...usage
|
|
539
|
+
};
|
|
540
|
+
} catch {
|
|
541
|
+
this.logger.warn("Failed to parse x-llm-usage header", { usageRaw });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const cost = headers.get("x-llm-cost");
|
|
545
|
+
const costAvailable = headers.get("x-llm-cost-available");
|
|
546
|
+
if (cost) {
|
|
547
|
+
result.cost = {
|
|
548
|
+
amountUsd: cost,
|
|
549
|
+
available: costAvailable === "true"
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
215
554
|
convertMessages(messages) {
|
|
216
555
|
const allowedToolCallIds = /* @__PURE__ */ new Set();
|
|
217
556
|
return messages.flatMap((m) => {
|
|
@@ -313,6 +652,7 @@ var LLMGateway = class {
|
|
|
313
652
|
};
|
|
314
653
|
// Annotate the CommonJS export names for ESM import in node:
|
|
315
654
|
0 && (module.exports = {
|
|
316
|
-
LLMGateway
|
|
655
|
+
LLMGateway,
|
|
656
|
+
LLMGatewayError
|
|
317
657
|
});
|
|
318
658
|
//# sourceMappingURL=index.js.map
|