@nebulaos/llm-gateway 0.1.2 → 0.1.5
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/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +161 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +161 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAIClient, { ClientOptions } from 'openai';
|
|
2
|
-
import { LogLevel, GenerateOptions,
|
|
2
|
+
import { IModel, LogLevel, GenerateOptions, Message, ToolDefinitionForLLM, ProviderResponse, StreamChunk } from '@nebulaos/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Custom error class for LLM Gateway errors.
|
|
@@ -86,6 +86,15 @@ declare class LLMGateway implements IModel {
|
|
|
86
86
|
private mapUsage;
|
|
87
87
|
private mapFinishReason;
|
|
88
88
|
private convertContentPart;
|
|
89
|
+
/**
|
|
90
|
+
* Sanitize choices for observability storage.
|
|
91
|
+
*
|
|
92
|
+
* Removes provider-specific fields that are not useful for tracing/debugging
|
|
93
|
+
* and that bloat the attribute size (causing truncation). Specifically:
|
|
94
|
+
* - Gemini's thought_signature (huge base64 strings, 2000+ chars each)
|
|
95
|
+
* - thinking_blocks (already captured via reasoningTokens)
|
|
96
|
+
*/
|
|
97
|
+
private sanitizeChoices;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
export { LLMGateway, type LLMGatewayConfig, LLMGatewayError };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAIClient, { ClientOptions } from 'openai';
|
|
2
|
-
import { LogLevel, GenerateOptions,
|
|
2
|
+
import { IModel, LogLevel, GenerateOptions, Message, ToolDefinitionForLLM, ProviderResponse, StreamChunk } from '@nebulaos/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Custom error class for LLM Gateway errors.
|
|
@@ -86,6 +86,15 @@ declare class LLMGateway implements IModel {
|
|
|
86
86
|
private mapUsage;
|
|
87
87
|
private mapFinishReason;
|
|
88
88
|
private convertContentPart;
|
|
89
|
+
/**
|
|
90
|
+
* Sanitize choices for observability storage.
|
|
91
|
+
*
|
|
92
|
+
* Removes provider-specific fields that are not useful for tracing/debugging
|
|
93
|
+
* and that bloat the attribute size (causing truncation). Specifically:
|
|
94
|
+
* - Gemini's thought_signature (huge base64 strings, 2000+ chars each)
|
|
95
|
+
* - thinking_blocks (already captured via reasoningTokens)
|
|
96
|
+
*/
|
|
97
|
+
private sanitizeChoices;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
export { LLMGateway, type LLMGatewayConfig, LLMGatewayError };
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
37
37
|
var import_openai = __toESM(require("openai"));
|
|
38
38
|
var import_node_crypto = require("crypto");
|
|
39
39
|
var import_core = require("@nebulaos/core");
|
|
40
|
+
var import_types = require("@nebulaos/types");
|
|
40
41
|
var LLMGatewayError = class extends Error {
|
|
41
42
|
constructor(message, code, status, cause) {
|
|
42
43
|
super(message);
|
|
@@ -74,27 +75,22 @@ var LLMGateway = class {
|
|
|
74
75
|
async generate(messages, tools, options) {
|
|
75
76
|
const mergedOptions = { ...this.options, ...options };
|
|
76
77
|
const model = `route:${this.modelName}`;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const { responseFormat, ...llmConfig } = mergedOptions ?? {};
|
|
79
|
+
const startData = {
|
|
80
|
+
provider: this.providerName,
|
|
81
|
+
model: this.modelName,
|
|
82
|
+
messagesCount: messages.length,
|
|
83
|
+
toolsCount: tools?.length ?? 0,
|
|
84
|
+
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
85
|
+
responseFormat,
|
|
86
|
+
messages,
|
|
87
|
+
tools
|
|
88
|
+
};
|
|
85
89
|
return import_core.Tracing.withSpan(
|
|
86
90
|
{
|
|
87
|
-
kind:
|
|
91
|
+
kind: import_types.SpanType.llm,
|
|
88
92
|
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
|
-
}
|
|
93
|
+
data: startData
|
|
98
94
|
},
|
|
99
95
|
async (llmSpan) => {
|
|
100
96
|
const headers = this.buildGatewayHeaders();
|
|
@@ -129,18 +125,18 @@ var LLMGateway = class {
|
|
|
129
125
|
const usage = this.mapUsage(response.usage);
|
|
130
126
|
const finishReason = this.mapFinishReason(choice.finish_reason);
|
|
131
127
|
const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);
|
|
128
|
+
const endData = {
|
|
129
|
+
usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
130
|
+
finishReason: finishReason ?? "stop",
|
|
131
|
+
toolCallsCount: message.tool_calls?.length ?? 0,
|
|
132
|
+
choices: this.sanitizeChoices(response.choices),
|
|
133
|
+
model: enrichment.modelActual,
|
|
134
|
+
fallbackUsed: enrichment.fallbackUsed,
|
|
135
|
+
cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : void 0
|
|
136
|
+
};
|
|
132
137
|
await llmSpan.end({
|
|
133
138
|
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
|
-
}
|
|
139
|
+
data: endData
|
|
144
140
|
});
|
|
145
141
|
return {
|
|
146
142
|
content: message.content || "",
|
|
@@ -158,15 +154,16 @@ var LLMGateway = class {
|
|
|
158
154
|
} catch (error) {
|
|
159
155
|
this.logger.error("LLM Gateway request failed", error, void 0, void 0);
|
|
160
156
|
const gatewayError = this.handleError(error);
|
|
157
|
+
const errorEndData = {
|
|
158
|
+
error: {
|
|
159
|
+
message: gatewayError.message,
|
|
160
|
+
code: gatewayError.code,
|
|
161
|
+
status: gatewayError.status
|
|
162
|
+
}
|
|
163
|
+
};
|
|
161
164
|
await llmSpan.end({
|
|
162
165
|
status: "error",
|
|
163
|
-
data:
|
|
164
|
-
error: {
|
|
165
|
-
message: gatewayError.message,
|
|
166
|
-
code: gatewayError.code,
|
|
167
|
-
status: gatewayError.status
|
|
168
|
-
}
|
|
169
|
-
}
|
|
166
|
+
data: errorEndData
|
|
170
167
|
});
|
|
171
168
|
throw gatewayError;
|
|
172
169
|
}
|
|
@@ -176,26 +173,21 @@ var LLMGateway = class {
|
|
|
176
173
|
async *generateStream(messages, tools, options) {
|
|
177
174
|
const mergedOptions = { ...this.options, ...options };
|
|
178
175
|
const model = `route:${this.modelName}`;
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
176
|
+
const { responseFormat, ...llmConfig } = mergedOptions ?? {};
|
|
177
|
+
const startData = {
|
|
178
|
+
provider: this.providerName,
|
|
179
|
+
model: this.modelName,
|
|
180
|
+
messagesCount: messages.length,
|
|
181
|
+
toolsCount: tools?.length ?? 0,
|
|
182
|
+
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
183
|
+
responseFormat,
|
|
184
|
+
messages,
|
|
185
|
+
tools
|
|
186
|
+
};
|
|
187
187
|
const llmSpan = await import_core.Tracing.startSpan({
|
|
188
|
-
kind:
|
|
188
|
+
kind: import_types.SpanType.llm,
|
|
189
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
|
-
}
|
|
190
|
+
data: startData
|
|
199
191
|
});
|
|
200
192
|
const headers = this.buildGatewayHeaders();
|
|
201
193
|
this.logger.debug("LLM Gateway stream request", {
|
|
@@ -226,15 +218,16 @@ var LLMGateway = class {
|
|
|
226
218
|
this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
|
|
227
219
|
const gatewayError = this.handleError(error);
|
|
228
220
|
if (llmSpan) {
|
|
221
|
+
const errorEndData = {
|
|
222
|
+
error: {
|
|
223
|
+
message: gatewayError.message,
|
|
224
|
+
code: gatewayError.code,
|
|
225
|
+
status: gatewayError.status
|
|
226
|
+
}
|
|
227
|
+
};
|
|
229
228
|
await llmSpan.end({
|
|
230
229
|
status: "error",
|
|
231
|
-
data:
|
|
232
|
-
error: {
|
|
233
|
-
message: gatewayError.message,
|
|
234
|
-
code: gatewayError.code,
|
|
235
|
-
status: gatewayError.status
|
|
236
|
-
}
|
|
237
|
-
}
|
|
230
|
+
data: errorEndData
|
|
238
231
|
});
|
|
239
232
|
}
|
|
240
233
|
throw gatewayError;
|
|
@@ -243,6 +236,8 @@ var LLMGateway = class {
|
|
|
243
236
|
let finalFinishReason;
|
|
244
237
|
let toolCallsCount = 0;
|
|
245
238
|
let outputPreview = "";
|
|
239
|
+
let finalContent = "";
|
|
240
|
+
const toolCallsAccumulator = /* @__PURE__ */ new Map();
|
|
246
241
|
try {
|
|
247
242
|
for await (const chunk of stream) {
|
|
248
243
|
if (chunk.usage) {
|
|
@@ -265,6 +260,7 @@ var LLMGateway = class {
|
|
|
265
260
|
const delta = choice.delta;
|
|
266
261
|
if (!delta) continue;
|
|
267
262
|
if (delta.content) {
|
|
263
|
+
finalContent += delta.content;
|
|
268
264
|
if (outputPreview.length < 200) {
|
|
269
265
|
outputPreview += delta.content.slice(0, 200 - outputPreview.length);
|
|
270
266
|
}
|
|
@@ -272,49 +268,72 @@ var LLMGateway = class {
|
|
|
272
268
|
}
|
|
273
269
|
if (delta.tool_calls) {
|
|
274
270
|
for (const tc of delta.tool_calls) {
|
|
271
|
+
const idx = tc.index;
|
|
275
272
|
if (tc.id && tc.function?.name) {
|
|
276
273
|
toolCallsCount++;
|
|
274
|
+
toolCallsAccumulator.set(idx, { id: tc.id, name: tc.function.name, arguments: "" });
|
|
277
275
|
yield {
|
|
278
276
|
type: "tool_call_start",
|
|
279
|
-
index:
|
|
277
|
+
index: idx,
|
|
280
278
|
id: tc.id,
|
|
281
279
|
name: tc.function.name
|
|
282
280
|
};
|
|
283
281
|
}
|
|
284
282
|
if (tc.function?.arguments) {
|
|
283
|
+
const existing = toolCallsAccumulator.get(idx);
|
|
284
|
+
if (existing) {
|
|
285
|
+
existing.arguments += tc.function.arguments;
|
|
286
|
+
}
|
|
285
287
|
yield {
|
|
286
288
|
type: "tool_call_delta",
|
|
287
|
-
index:
|
|
289
|
+
index: idx,
|
|
288
290
|
args: tc.function.arguments
|
|
289
291
|
};
|
|
290
292
|
}
|
|
291
293
|
}
|
|
292
294
|
}
|
|
293
295
|
}
|
|
296
|
+
const toolCalls = Array.from(toolCallsAccumulator.values()).map((tc) => ({
|
|
297
|
+
id: tc.id,
|
|
298
|
+
type: "function",
|
|
299
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
300
|
+
}));
|
|
301
|
+
const choices = [{
|
|
302
|
+
index: 0,
|
|
303
|
+
message: {
|
|
304
|
+
role: "assistant",
|
|
305
|
+
content: finalContent || null,
|
|
306
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
|
307
|
+
},
|
|
308
|
+
finish_reason: finalFinishReason
|
|
309
|
+
}];
|
|
294
310
|
if (llmSpan) {
|
|
311
|
+
const endData = {
|
|
312
|
+
usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
313
|
+
finishReason: finalFinishReason ?? "stop",
|
|
314
|
+
toolCallsCount,
|
|
315
|
+
outputPreview,
|
|
316
|
+
choices: this.sanitizeChoices(choices)
|
|
317
|
+
};
|
|
295
318
|
await llmSpan.end({
|
|
296
319
|
status: "success",
|
|
297
|
-
data:
|
|
298
|
-
usage: finalUsage,
|
|
299
|
-
finishReason: finalFinishReason,
|
|
300
|
-
toolCallsCount,
|
|
301
|
-
outputPreview
|
|
302
|
-
}
|
|
320
|
+
data: endData
|
|
303
321
|
});
|
|
304
322
|
}
|
|
305
323
|
} catch (error) {
|
|
306
324
|
this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
|
|
307
325
|
const gatewayError = this.handleError(error);
|
|
308
326
|
if (llmSpan) {
|
|
327
|
+
const errorEndData = {
|
|
328
|
+
error: {
|
|
329
|
+
message: gatewayError.message,
|
|
330
|
+
code: gatewayError.code,
|
|
331
|
+
status: gatewayError.status
|
|
332
|
+
}
|
|
333
|
+
};
|
|
309
334
|
await llmSpan.end({
|
|
310
335
|
status: "error",
|
|
311
|
-
data:
|
|
312
|
-
error: {
|
|
313
|
-
message: gatewayError.message,
|
|
314
|
-
code: gatewayError.code,
|
|
315
|
-
status: gatewayError.status
|
|
316
|
-
}
|
|
317
|
-
}
|
|
336
|
+
data: errorEndData
|
|
318
337
|
});
|
|
319
338
|
}
|
|
320
339
|
throw gatewayError;
|
|
@@ -379,8 +398,11 @@ var LLMGateway = class {
|
|
|
379
398
|
);
|
|
380
399
|
}
|
|
381
400
|
if (status === 400) {
|
|
401
|
+
const lowerMsg = originalMessage.toLowerCase();
|
|
402
|
+
const isPermissionRelated = lowerMsg.includes("unauthorized") || lowerMsg.includes("permission") || lowerMsg.includes("access denied") || lowerMsg.includes("not allowed") || lowerMsg.includes("forbidden") || lowerMsg.includes("invalid api key") || lowerMsg.includes("api key");
|
|
403
|
+
const permissionHint = isPermissionRelated ? `This error may indicate a token/permission issue. ` : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. Common cause: using a route without the corresponding token permission. `;
|
|
382
404
|
return new LLMGatewayError(
|
|
383
|
-
`LLM Gateway request error: Invalid request parameters. Please check your request configuration (model, messages, tools). Original error: ${originalMessage}`,
|
|
405
|
+
`LLM Gateway request error: Invalid request parameters. Please check your request configuration (model, messages, tools). ${permissionHint}Original error: ${originalMessage}`,
|
|
384
406
|
"LLM_GATEWAY_BAD_REQUEST",
|
|
385
407
|
status,
|
|
386
408
|
error
|
|
@@ -576,6 +598,33 @@ var LLMGateway = class {
|
|
|
576
598
|
if (m.tool_calls) {
|
|
577
599
|
m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));
|
|
578
600
|
}
|
|
601
|
+
const toolOutputs = m.toolOutputs;
|
|
602
|
+
if (toolOutputs && toolOutputs.length > 0) {
|
|
603
|
+
const result = [];
|
|
604
|
+
result.push({
|
|
605
|
+
role: "assistant",
|
|
606
|
+
content: assistantContent,
|
|
607
|
+
tool_calls: m.tool_calls?.map((tc) => ({
|
|
608
|
+
id: tc.id,
|
|
609
|
+
type: "function",
|
|
610
|
+
function: {
|
|
611
|
+
name: tc.function.name,
|
|
612
|
+
arguments: tc.function.arguments
|
|
613
|
+
}
|
|
614
|
+
}))
|
|
615
|
+
});
|
|
616
|
+
for (const output of toolOutputs) {
|
|
617
|
+
if (output.status === "pending") {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
result.push({
|
|
621
|
+
role: "tool",
|
|
622
|
+
tool_call_id: output.toolCallId,
|
|
623
|
+
content: output.status === "error" ? JSON.stringify({ error: output.error || "Unknown error" }) : output.content || ""
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
579
628
|
return {
|
|
580
629
|
role: "assistant",
|
|
581
630
|
content: assistantContent,
|
|
@@ -649,6 +698,42 @@ var LLMGateway = class {
|
|
|
649
698
|
const _exhaustive = part;
|
|
650
699
|
throw new Error(`Unsupported content type: ${_exhaustive.type}`);
|
|
651
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Sanitize choices for observability storage.
|
|
703
|
+
*
|
|
704
|
+
* Removes provider-specific fields that are not useful for tracing/debugging
|
|
705
|
+
* and that bloat the attribute size (causing truncation). Specifically:
|
|
706
|
+
* - Gemini's thought_signature (huge base64 strings, 2000+ chars each)
|
|
707
|
+
* - thinking_blocks (already captured via reasoningTokens)
|
|
708
|
+
*/
|
|
709
|
+
sanitizeChoices(choices) {
|
|
710
|
+
if (!choices) return [];
|
|
711
|
+
return choices.map((choice) => {
|
|
712
|
+
if (!choice || typeof choice !== "object") return choice;
|
|
713
|
+
const sanitized = { ...choice };
|
|
714
|
+
if (sanitized.message && typeof sanitized.message === "object") {
|
|
715
|
+
const message = { ...sanitized.message };
|
|
716
|
+
if ("thinking_blocks" in message) {
|
|
717
|
+
delete message.thinking_blocks;
|
|
718
|
+
}
|
|
719
|
+
if ("provider_specific_fields" in message) {
|
|
720
|
+
delete message.provider_specific_fields;
|
|
721
|
+
}
|
|
722
|
+
if (message.tool_calls && Array.isArray(message.tool_calls)) {
|
|
723
|
+
message.tool_calls = message.tool_calls.map((tc) => {
|
|
724
|
+
if (!tc || typeof tc !== "object") return tc;
|
|
725
|
+
const sanitizedTc = { ...tc };
|
|
726
|
+
if ("provider_specific_fields" in sanitizedTc) {
|
|
727
|
+
delete sanitizedTc.provider_specific_fields;
|
|
728
|
+
}
|
|
729
|
+
return sanitizedTc;
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
sanitized.message = message;
|
|
733
|
+
}
|
|
734
|
+
return sanitized;
|
|
735
|
+
});
|
|
736
|
+
}
|
|
652
737
|
};
|
|
653
738
|
// Annotate the CommonJS export names for ESM import in node:
|
|
654
739
|
0 && (module.exports = {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Prepare context data for tracing (useful for debugging, especially on errors)\n const messagesPreview = messages.map((m) => ({\n role: m.role,\n content: typeof m.content === \"string\"\n ? m.content.length > 500 ? m.content.slice(0, 500) + \"...\" : m.content\n : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content),\n }));\n const toolsPreview = tools?.map((t) => ({\n name: t.function.name,\n description: t.function.description?.slice(0, 200),\n }));\n\n return Tracing.withSpan(\n {\n kind: \"llm\",\n name: `llm:${this.modelName}`,\n data: {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n responseFormat: mergedOptions?.responseFormat,\n messages: messagesPreview,\n tools: toolsPreview,\n },\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n await llmSpan.end({\n status: \"success\",\n data: {\n usage: enrichment.usage ?? usage,\n finishReason,\n toolCallsCount: message.tool_calls?.length ?? 0,\n outputPreview: message.content?.slice(0, 200),\n // Enrichment from backend gateway\n modelActual: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost,\n },\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n await llmSpan.end({\n status: \"error\",\n data: {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n },\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Prepare context data for tracing (useful for debugging, especially on errors)\n const messagesPreview = messages.map((m) => ({\n role: m.role,\n content: typeof m.content === \"string\"\n ? m.content.length > 500 ? m.content.slice(0, 500) + \"...\" : m.content\n : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content),\n }));\n const toolsPreview = tools?.map((t) => ({\n name: t.function.name,\n description: t.function.description?.slice(0, 200),\n }));\n\n // Start span manually for streaming (async generators can't use withSpan directly)\n const llmSpan = await Tracing.startSpan({\n kind: \"llm\",\n name: `llm:${this.modelName}`,\n data: {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n responseFormat: mergedOptions?.responseFormat,\n messages: messagesPreview,\n tools: toolsPreview,\n },\n });\n\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway stream request\", {\n model,\n baseUrl: this.baseUrl,\n stream: true,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n let stream;\n try {\n stream = await this.client.chat.completions.create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n stream: true,\n stream_options: { include_usage: true },\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n await llmSpan.end({\n status: \"error\",\n data: {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n },\n });\n }\n\n throw gatewayError;\n }\n\n let finalUsage: TokenUsage | undefined;\n let finalFinishReason: ProviderResponse[\"finishReason\"];\n let toolCallsCount = 0;\n let outputPreview = \"\";\n\n try {\n for await (const chunk of stream) {\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n yield {\n type: \"finish\",\n reason: \"stop\",\n usage: finalUsage,\n };\n }\n\n const choice = chunk.choices?.[0];\n if (!choice) continue;\n\n if (choice.finish_reason) {\n finalFinishReason = this.mapFinishReason(choice.finish_reason);\n yield {\n type: \"finish\",\n reason: finalFinishReason,\n };\n }\n\n const delta = choice.delta;\n if (!delta) continue;\n\n if (delta.content) {\n if (outputPreview.length < 200) {\n outputPreview += delta.content.slice(0, 200 - outputPreview.length);\n }\n yield { type: \"content_delta\", delta: delta.content };\n }\n\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n if (tc.id && tc.function?.name) {\n toolCallsCount++;\n yield {\n type: \"tool_call_start\",\n index: tc.index,\n id: tc.id,\n name: tc.function.name,\n };\n }\n\n if (tc.function?.arguments) {\n yield {\n type: \"tool_call_delta\",\n index: tc.index,\n args: tc.function.arguments,\n };\n }\n }\n }\n }\n\n // End span with success\n if (llmSpan) {\n await llmSpan.end({\n status: \"success\",\n data: {\n usage: finalUsage,\n finishReason: finalFinishReason,\n toolCallsCount,\n outputPreview,\n },\n });\n }\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n await llmSpan.end({\n status: \"error\",\n data: {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n },\n });\n }\n\n throw gatewayError;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-request-id\": randomUUID(),\n };\n\n const exec = ExecutionContext.getOrUndefined();\n if (exec?.executionId) {\n headers[\"x-execution-id\"] = exec.executionId;\n }\n\n const ctx = Tracing.getContext();\n if (ctx) {\n // IDs já estão em formato W3C (hex)\n headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;\n } else {\n // Still emit a root trace context so the gateway can correlate requests by traceId.\n const traceId = randomBytes(16).toString(\"hex\");\n const spanId = randomBytes(8).toString(\"hex\");\n headers.traceparent = `00-${traceId}-${spanId}-01`;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { mimeType, source } = part.file;\n if (!mimeType.startsWith(\"image/\")) {\n throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);\n }\n\n const url = source.type === \"url\" ? source.url : `data:${mimeType};base64,${source.base64}`;\n\n return { type: \"image_url\", image_url: { url } };\n }\n\n if (part.type === \"image_url\") {\n return { type: \"image_url\", image_url: { url: part.image_url.url } };\n }\n\n // Exhaustive check - should never reach here with proper ContentPart\n const _exhaustive: never = part;\n throw new Error(`Unsupported content type: ${(_exhaustive as any).type}`);\n }\n}\n"],"mappings":"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","names":["OpenAIClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\nimport { SpanType, type LLMSpanStartData, type LLMSpanEndData, type LLMResponseChoice } from \"@nebulaos/types\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n return Tracing.withSpan(\n {\n kind: SpanType.llm,\n name: `llm:${this.modelName}`,\n data: startData,\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n // Typed span end data for LLM calls\n const endData: LLMSpanEndData = {\n usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finishReason ?? \"stop\",\n toolCallsCount: message.tool_calls?.length ?? 0,\n choices: this.sanitizeChoices(response.choices),\n model: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : undefined,\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n // Start span manually for streaming (async generators can't use withSpan directly)\n const llmSpan = await Tracing.startSpan({\n kind: SpanType.llm,\n name: `llm:${this.modelName}`,\n data: startData,\n });\n\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway stream request\", {\n model,\n baseUrl: this.baseUrl,\n stream: true,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n let stream;\n try {\n stream = await this.client.chat.completions.create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n stream: true,\n stream_options: { include_usage: true },\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n }\n\n throw gatewayError;\n }\n\n let finalUsage: TokenUsage | undefined;\n let finalFinishReason: ProviderResponse[\"finishReason\"];\n let toolCallsCount = 0;\n let outputPreview = \"\";\n let finalContent = \"\";\n const toolCallsAccumulator: Map<number, { id: string; name: string; arguments: string }> = new Map();\n\n try {\n for await (const chunk of stream) {\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n yield {\n type: \"finish\",\n reason: \"stop\",\n usage: finalUsage,\n };\n }\n\n const choice = chunk.choices?.[0];\n if (!choice) continue;\n\n if (choice.finish_reason) {\n finalFinishReason = this.mapFinishReason(choice.finish_reason);\n yield {\n type: \"finish\",\n reason: finalFinishReason,\n };\n }\n\n const delta = choice.delta;\n if (!delta) continue;\n\n if (delta.content) {\n finalContent += delta.content;\n if (outputPreview.length < 200) {\n outputPreview += delta.content.slice(0, 200 - outputPreview.length);\n }\n yield { type: \"content_delta\", delta: delta.content };\n }\n\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index;\n if (tc.id && tc.function?.name) {\n toolCallsCount++;\n toolCallsAccumulator.set(idx, { id: tc.id, name: tc.function.name, arguments: \"\" });\n yield {\n type: \"tool_call_start\",\n index: idx,\n id: tc.id,\n name: tc.function.name,\n };\n }\n\n if (tc.function?.arguments) {\n const existing = toolCallsAccumulator.get(idx);\n if (existing) {\n existing.arguments += tc.function.arguments;\n }\n yield {\n type: \"tool_call_delta\",\n index: idx,\n args: tc.function.arguments,\n };\n }\n }\n }\n }\n\n // Build choices for observability\n const toolCalls = Array.from(toolCallsAccumulator.values()).map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n }));\n\n const choices = [{\n index: 0,\n message: {\n role: \"assistant\" as const,\n content: finalContent || null,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n },\n finish_reason: finalFinishReason,\n }];\n\n // Typed span end data for success case\n if (llmSpan) {\n const endData: LLMSpanEndData = {\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finalFinishReason ?? \"stop\",\n toolCallsCount,\n outputPreview,\n choices: this.sanitizeChoices(choices),\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n }\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n }\n\n throw gatewayError;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n // Check if error message suggests permission/token issues\n const lowerMsg = originalMessage.toLowerCase();\n const isPermissionRelated =\n lowerMsg.includes(\"unauthorized\") ||\n lowerMsg.includes(\"permission\") ||\n lowerMsg.includes(\"access denied\") ||\n lowerMsg.includes(\"not allowed\") ||\n lowerMsg.includes(\"forbidden\") ||\n lowerMsg.includes(\"invalid api key\") ||\n lowerMsg.includes(\"api key\");\n\n const permissionHint = isPermissionRelated\n ? `This error may indicate a token/permission issue. `\n : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. ` +\n `Common cause: using a route without the corresponding token permission. `;\n\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `${permissionHint}` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-request-id\": randomUUID(),\n };\n\n const exec = ExecutionContext.getOrUndefined();\n if (exec?.executionId) {\n headers[\"x-execution-id\"] = exec.executionId;\n }\n\n const ctx = Tracing.getContext();\n if (ctx) {\n // IDs já estão em formato W3C (hex)\n headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;\n } else {\n // Still emit a root trace context so the gateway can correlate requests by traceId.\n const traceId = randomBytes(16).toString(\"hex\");\n const spanId = randomBytes(8).toString(\"hex\");\n headers.traceparent = `00-${traceId}-${spanId}-01`;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n // Check if this is a UnifiedToolMessage (has toolOutputs)\n const toolOutputs = (m as any).toolOutputs as Array<{\n toolCallId: string;\n status: 'pending' | 'success' | 'error';\n content: string | null;\n error?: string;\n }> | undefined;\n\n if (toolOutputs && toolOutputs.length > 0) {\n // Expand UnifiedToolMessage into assistant + tool messages\n const result: OpenAIClient.Chat.ChatCompletionMessageParam[] = [];\n\n // 1. Assistant message with tool_calls\n result.push({\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n\n // 2. Tool messages for each completed toolOutput\n for (const output of toolOutputs) {\n if (output.status === 'pending') {\n // Skip pending outputs - tool hasn't finished yet\n continue;\n }\n\n result.push({\n role: \"tool\",\n tool_call_id: output.toolCallId,\n content: output.status === 'error'\n ? JSON.stringify({ error: output.error || 'Unknown error' })\n : (output.content || ''),\n });\n }\n\n return result;\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { mimeType, source } = part.file;\n if (!mimeType.startsWith(\"image/\")) {\n throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);\n }\n\n const url = source.type === \"url\" ? source.url : `data:${mimeType};base64,${source.base64}`;\n\n return { type: \"image_url\", image_url: { url } };\n }\n\n if (part.type === \"image_url\") {\n return { type: \"image_url\", image_url: { url: part.image_url.url } };\n }\n\n // Exhaustive check - should never reach here with proper ContentPart\n const _exhaustive: never = part;\n throw new Error(`Unsupported content type: ${(_exhaustive as any).type}`);\n }\n\n /**\n * Sanitize choices for observability storage.\n *\n * Removes provider-specific fields that are not useful for tracing/debugging\n * and that bloat the attribute size (causing truncation). Specifically:\n * - Gemini's thought_signature (huge base64 strings, 2000+ chars each)\n * - thinking_blocks (already captured via reasoningTokens)\n */\n private sanitizeChoices(choices: unknown[] | undefined): LLMResponseChoice[] {\n if (!choices) return [];\n\n return choices.map((choice) => {\n if (!choice || typeof choice !== \"object\") return choice as LLMResponseChoice;\n\n const sanitized = { ...choice } as Record<string, unknown>;\n\n // Remove provider_specific_fields.thought_signature (Gemini thinking signatures)\n if (sanitized.message && typeof sanitized.message === \"object\") {\n const message = { ...sanitized.message } as Record<string, unknown>;\n\n // Remove thinking_blocks (captured via reasoningTokens in usage)\n if (\"thinking_blocks\" in message) {\n delete message.thinking_blocks;\n }\n\n // Remove provider_specific_fields (contains thought_signatures)\n if (\"provider_specific_fields\" in message) {\n delete message.provider_specific_fields;\n }\n\n // Sanitize tool_calls: remove thought_signature from each tool call\n if (message.tool_calls && Array.isArray(message.tool_calls)) {\n message.tool_calls = message.tool_calls.map((tc: Record<string, unknown>) => {\n if (!tc || typeof tc !== \"object\") return tc;\n const sanitizedTc = { ...tc };\n // Remove provider_specific_fields from tool call (contains thought_signature)\n if (\"provider_specific_fields\" in sanitizedTc) {\n delete sanitizedTc.provider_specific_fields;\n }\n return sanitizedTc;\n });\n }\n\n sanitized.message = message;\n }\n\n return sanitized as LLMResponseChoice;\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAsD;AACtD,yBAAwC;AACxC,kBAaO;AACP,mBAA6F;AAMtF,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACE,SACgB,MACA,QACA,OAChB;AACA,UAAM,OAAO;AAJG;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AA+BO,IAAM,aAAN,MAAmC;AAAA,EACxC,eAAe;AAAA,EACf;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,eAAe;AAAA,IACb,YAAY;AAAA,MACV,WAAW,CAAC,SAAS;AAAA,MACrB,SAAS,CAAC,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,YAAY,QAA0B;AACpC,SAAK,YAAY,OAAO;AAExB,SAAK,UAAU,OAAO,WAAW;AACjC,UAAM,UAAU,KAAK,QAAQ,SAAS,KAAK,IAAI,KAAK,UAAU,GAAG,KAAK,OAAO;AAC7E,SAAK,SAAS,IAAI,0BAAc,OAAO,YAAY,QAAQ,sBAAsB;AAEjF,SAAK,SAAS,IAAI,cAAAA,QAAa;AAAA,MAC7B,QAAQ,OAAO;AAAA,MACf;AAAA,MACA,GAAG,OAAO;AAAA,IACZ,CAAC;AACD,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA,EAEA,MAAM,SACJ,UACA,OACA,SAC2B;AAC3B,UAAM,gBAAgB,EAAE,GAAG,KAAK,SAAS,GAAG,QAAQ;AACpD,UAAM,QAAQ,SAAS,KAAK,SAAS;AAGrC,UAAM,EAAE,gBAAgB,GAAG,UAAU,IAAI,iBAAiB,CAAC;AAG3D,UAAM,YAA8B;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,eAAe,SAAS;AAAA,MACxB,YAAY,OAAO,UAAU;AAAA,MAC7B,WAAW,OAAO,KAAK,SAAS,EAAE,SAAS,IAAI,YAAY;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO,oBAAQ;AAAA,MACb;AAAA,QACE,MAAM,sBAAS;AAAA,QACf,MAAM,OAAO,KAAK,SAAS;AAAA,QAC3B,MAAM;AAAA,MACR;AAAA,MACA,OAAO,YAAY;AACjB,cAAM,UAAU,KAAK,oBAAoB;AACzC,aAAK,OAAO,MAAM,uBAAuB;AAAA,UACvC;AAAA,UACA,SAAS,KAAK;AAAA,UACd,QAAQ;AAAA,UACR,cAAc,SAAS;AAAA,UACvB,WAAW,OAAO,UAAU;AAAA,QAC9B,CAAC;AAED,YAAI;AAEF,gBAAM,EAAE,MAAM,UAAU,UAAU,aAAa,IAAI,MAAM,KAAK,OAAO,KAAK,YACvE;AAAA,YACC;AAAA,cACE;AAAA,cACA,UAAU,KAAK,gBAAgB,QAAQ;AAAA,cACvC,OAAO,KAAK,aAAa,KAAK;AAAA,cAC9B,iBACE,eAAe,gBAAgB,SAAS,SACpC,cAAc,eAAe,SAC3B;AAAA,gBACE,MAAM;AAAA,gBACN,aAAa,EAAE,MAAM,YAAY,QAAQ,cAAc,eAAe,OAAc;AAAA,cACtF,IACA,EAAE,MAAM,cAAc,IACxB;AAAA,cACN,GAAG,KAAK,oBAAoB,aAAa;AAAA,YAC3C;AAAA,YACA,EAAE,QAAQ;AAAA,UACZ,EACC,aAAa;AAEhB,eAAK,OAAO,MAAM,wBAAwB;AAAA,YACxC;AAAA,YACA,cAAc,SAAS,UAAU,CAAC,GAAG;AAAA,YACrC,UAAU,QAAQ,SAAS,KAAK;AAAA,UAClC,CAAC;AAED,gBAAM,SAAS,SAAS,QAAQ,CAAC;AACjC,gBAAM,UAAU,OAAO;AACvB,gBAAM,QAAQ,KAAK,SAAS,SAAS,KAAK;AAC1C,gBAAM,eAAe,KAAK,gBAAgB,OAAO,aAAa;AAG9D,gBAAM,aAAa,KAAK,6BAA6B,aAAa,OAAO;AAGzE,gBAAM,UAA0B;AAAA,YAC9B,OAAO,WAAW,SAAS,SAAS,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAE;AAAA,YAC3F,cAAc,gBAAgB;AAAA,YAC9B,gBAAgB,QAAQ,YAAY,UAAU;AAAA,YAC9C,SAAS,KAAK,gBAAgB,SAAS,OAAO;AAAA,YAC9C,OAAO,WAAW;AAAA,YAClB,cAAc,WAAW;AAAA,YACzB,MAAM,WAAW,OAAO,WAAW,WAAW,KAAK,SAAS,IAAI;AAAA,UAClE;AAEA,gBAAM,QAAQ,IAAI;AAAA,YAChB,QAAQ;AAAA,YACR,MAAM;AAAA,UACR,CAAC;AAED,iBAAO;AAAA,YACL,SAAS,QAAQ,WAAW;AAAA,YAC5B,WAAW,QAAQ,YAAY,IAAI,CAAC,QAAQ;AAAA,cAC1C,IAAI,GAAG;AAAA,cACP,MAAM;AAAA,cACN,UAAU;AAAA,gBACR,MAAM,GAAG,SAAS;AAAA,gBAClB,WAAW,GAAG,SAAS;AAAA,cACzB;AAAA,YACF,EAAE;AAAA,YACF;AAAA,YACA,OAAO,WAAW,SAAS;AAAA,UAC7B;AAAA,QACF,SAAS,OAAO;AACd,eAAK,OAAO,MAAM,8BAA8B,OAAO,QAAW,MAAS;AAC3E,gBAAM,eAAe,KAAK,YAAY,KAAK;AAG3C,gBAAM,eAA+B;AAAA,YACnC,OAAO;AAAA,cACL,SAAS,aAAa;AAAA,cACtB,MAAM,aAAa;AAAA,cACnB,QAAQ,aAAa;AAAA,YACvB;AAAA,UACF;AAEA,gBAAM,QAAQ,IAAI;AAAA,YAChB,QAAQ;AAAA,YACR,MAAM;AAAA,UACR,CAAC;AAED,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,eACL,UACA,OACA,SAC6B;AAC7B,UAAM,gBAAgB,EAAE,GAAG,KAAK,SAAS,GAAG,QAAQ;AACpD,UAAM,QAAQ,SAAS,KAAK,SAAS;AAGrC,UAAM,EAAE,gBAAgB,GAAG,UAAU,IAAI,iBAAiB,CAAC;AAG3D,UAAM,YAA8B;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,eAAe,SAAS;AAAA,MACxB,YAAY,OAAO,UAAU;AAAA,MAC7B,WAAW,OAAO,KAAK,SAAS,EAAE,SAAS,IAAI,YAAY;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,UAAM,UAAU,MAAM,oBAAQ,UAAU;AAAA,MACtC,MAAM,sBAAS;AAAA,MACf,MAAM,OAAO,KAAK,SAAS;AAAA,MAC3B,MAAM;AAAA,IACR,CAAC;AAED,UAAM,UAAU,KAAK,oBAAoB;AACzC,SAAK,OAAO,MAAM,8BAA8B;AAAA,MAC9C;AAAA,MACA,SAAS,KAAK;AAAA,MACd,QAAQ;AAAA,MACR,cAAc,SAAS;AAAA,MACvB,WAAW,OAAO,UAAU;AAAA,IAC9B,CAAC;AAED,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,QAC1C;AAAA,UACE;AAAA,UACA,UAAU,KAAK,gBAAgB,QAAQ;AAAA,UACvC,OAAO,KAAK,aAAa,KAAK;AAAA,UAC9B,QAAQ;AAAA,UACR,gBAAgB,EAAE,eAAe,KAAK;AAAA,UACtC,iBACE,eAAe,gBAAgB,SAAS,SACpC,cAAc,eAAe,SAC3B;AAAA,YACE,MAAM;AAAA,YACN,aAAa,EAAE,MAAM,YAAY,QAAQ,cAAc,eAAe,OAAc;AAAA,UACtF,IACA,EAAE,MAAM,cAAc,IACxB;AAAA,UACN,GAAG,KAAK,oBAAoB,aAAa;AAAA,QAC3C;AAAA,QACA,EAAE,QAAQ;AAAA,MACZ;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,qCAAqC,OAAO,QAAW,MAAS;AAClF,YAAM,eAAe,KAAK,YAAY,KAAK;AAE3C,UAAI,SAAS;AAEX,cAAM,eAA+B;AAAA,UACnC,OAAO;AAAA,YACL,SAAS,aAAa;AAAA,YACtB,MAAM,aAAa;AAAA,YACnB,QAAQ,aAAa;AAAA,UACvB;AAAA,QACF;AAEA,cAAM,QAAQ,IAAI;AAAA,UAChB,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,YAAM;AAAA,IACR;AAEA,QAAI;AACJ,QAAI;AACJ,QAAI,iBAAiB;AACrB,QAAI,gBAAgB;AACpB,QAAI,eAAe;AACnB,UAAM,uBAAqF,oBAAI,IAAI;AAEnG,QAAI;AACF,uBAAiB,SAAS,QAAQ;AAChC,YAAI,MAAM,OAAO;AACf,uBAAa,KAAK,SAAS,MAAM,KAAK;AACtC,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO;AAAA,UACT;AAAA,QACF;AAEA,cAAM,SAAS,MAAM,UAAU,CAAC;AAChC,YAAI,CAAC,OAAQ;AAEb,YAAI,OAAO,eAAe;AACxB,8BAAoB,KAAK,gBAAgB,OAAO,aAAa;AAC7D,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,QAAQ;AAAA,UACV;AAAA,QACF;AAEA,cAAM,QAAQ,OAAO;AACrB,YAAI,CAAC,MAAO;AAEZ,YAAI,MAAM,SAAS;AACjB,0BAAgB,MAAM;AACtB,cAAI,cAAc,SAAS,KAAK;AAC9B,6BAAiB,MAAM,QAAQ,MAAM,GAAG,MAAM,cAAc,MAAM;AAAA,UACpE;AACA,gBAAM,EAAE,MAAM,iBAAiB,OAAO,MAAM,QAAQ;AAAA,QACtD;AAEA,YAAI,MAAM,YAAY;AACpB,qBAAW,MAAM,MAAM,YAAY;AACjC,kBAAM,MAAM,GAAG;AACf,gBAAI,GAAG,MAAM,GAAG,UAAU,MAAM;AAC9B;AACA,mCAAqB,IAAI,KAAK,EAAE,IAAI,GAAG,IAAI,MAAM,GAAG,SAAS,MAAM,WAAW,GAAG,CAAC;AAClF,oBAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,IAAI,GAAG;AAAA,gBACP,MAAM,GAAG,SAAS;AAAA,cACpB;AAAA,YACF;AAEA,gBAAI,GAAG,UAAU,WAAW;AAC1B,oBAAM,WAAW,qBAAqB,IAAI,GAAG;AAC7C,kBAAI,UAAU;AACZ,yBAAS,aAAa,GAAG,SAAS;AAAA,cACpC;AACA,oBAAM;AAAA,gBACJ,MAAM;AAAA,gBACN,OAAO;AAAA,gBACP,MAAM,GAAG,SAAS;AAAA,cACpB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,YAAM,YAAY,MAAM,KAAK,qBAAqB,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ;AAAA,QACvE,IAAI,GAAG;AAAA,QACP,MAAM;AAAA,QACN,UAAU,EAAE,MAAM,GAAG,MAAM,WAAW,GAAG,UAAU;AAAA,MACrD,EAAE;AAEF,YAAM,UAAU,CAAC;AAAA,QACf,OAAO;AAAA,QACP,SAAS;AAAA,UACP,MAAM;AAAA,UACN,SAAS,gBAAgB;AAAA,UACzB,YAAY,UAAU,SAAS,IAAI,YAAY;AAAA,QACjD;AAAA,QACA,eAAe;AAAA,MACjB,CAAC;AAGD,UAAI,SAAS;AACX,cAAM,UAA0B;AAAA,UAC9B,OAAO,cAAc,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAE;AAAA,UAC5E,cAAc,qBAAqB;AAAA,UACnC;AAAA,UACA;AAAA,UACA,SAAS,KAAK,gBAAgB,OAAO;AAAA,QACvC;AAEA,cAAM,QAAQ,IAAI;AAAA,UAChB,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,6BAA6B,OAAO,QAAW,MAAS;AAC1E,YAAM,eAAe,KAAK,YAAY,KAAK;AAE3C,UAAI,SAAS;AAEX,cAAM,eAA+B;AAAA,UACnC,OAAO;AAAA,YACL,SAAS,aAAa;AAAA,YACtB,MAAM,aAAa;AAAA,YACnB,QAAQ,aAAa;AAAA,UACvB;AAAA,QACF;AAEA,cAAM,QAAQ,IAAI;AAAA,UAChB,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,YAAY,OAAiC;AAEnD,QAAI,iBAAiB,wBAAU;AAC7B,YAAM,SAAS,MAAM;AACrB,YAAM,kBAAkB,MAAM;AAG9B,YAAM,cAAc,KAAK,mBAAmB,KAAK;AAGjD,UAAI,WAAW,KAAK;AAClB,YAAI,gBAAgB,WAAW;AAC7B,iBAAO,IAAI;AAAA,YACT,4NAEqB,eAAe;AAAA,YACpC;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF,OAAO;AAEL,iBAAO,IAAI;AAAA,YACT,2OAEqB,eAAe;AAAA,YACpC;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,WAAW,KAAK;AAClB,YAAI,gBAAgB,WAAW;AAC7B,iBAAO,IAAI;AAAA,YACT,iNAEqB,eAAe;AAAA,YACpC;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF,OAAO;AACL,iBAAO,IAAI;AAAA,YACT,2LAEqB,eAAe;AAAA,YACpC;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,WAAW,KAAK;AAClB,eAAO,IAAI;AAAA,UACT,+JAEqB,eAAe;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,UAAI,WAAW,KAAK;AAElB,cAAM,WAAW,gBAAgB,YAAY;AAC7C,cAAM,sBACJ,SAAS,SAAS,cAAc,KAChC,SAAS,SAAS,YAAY,KAC9B,SAAS,SAAS,eAAe,KACjC,SAAS,SAAS,aAAa,KAC/B,SAAS,SAAS,WAAW,KAC7B,SAAS,SAAS,iBAAiB,KACnC,SAAS,SAAS,SAAS;AAE7B,cAAM,iBAAiB,sBACnB,uDACA,wFAAwF,KAAK,SAAS;AAG1G,eAAO,IAAI;AAAA,UACT,4HAEK,cAAc,mBACE,eAAe;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,UAAI,WAAW,KAAK;AAClB,eAAO,IAAI;AAAA,UACT,4GACoC,KAAK,SAAS,iDAC7B,eAAe;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,UAAI,WAAW,OAAO,WAAW,KAAK;AACpC,eAAO,IAAI;AAAA,UACT,oJAEqB,eAAe;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,UAAI,UAAU,UAAU,KAAK;AAC3B,eAAO,IAAI;AAAA,UACT,iEAAiE,MAAM,mFAElD,eAAe;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,aAAO,IAAI;AAAA,QACT,sBAAsB,MAAM,MAAM,eAAe;AAAA,QACjD;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,QAAI,iBAAiB,OAAO;AAC1B,YAAM,MAAM,MAAM,QAAQ,YAAY;AAGtC,UAAI,IAAI,SAAS,cAAc,KAAK,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,SAAS,GAAG;AACxF,eAAO,IAAI;AAAA,UACT,0EAA0E,KAAK,OAAO,0EAEjE,MAAM,OAAO;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,UAAI,IAAI,SAAS,SAAS,KAAK,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,WAAW,GAAG;AACrF,eAAO,IAAI;AAAA,UACT,mHAEqB,MAAM,OAAO;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,aAAO,IAAI;AAAA,QACT,sBAAsB,MAAM,OAAO;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,WAAO,IAAI;AAAA,MACT,6DAA6D,OAAO,KAAK,CAAC;AAAA,MAC1E;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,mBAAmB,OAAqD;AAE9E,UAAM,UAAU,MAAM;AACtB,QAAI,SAAS;AACX,YAAM,cAAc,QAAQ,gBAAgB,KAAK,QAAQ,gBAAgB;AACzE,UAAI,gBAAgB,aAAa,gBAAgB,YAAY;AAC3D,eAAO;AAAA,MACT;AAAA,IACF;AAIA,UAAM,YAAY,MAAM;AACxB,QAAI,aAAa,OAAO,cAAc,UAAU;AAE9C,YAAM,cAAc,UAAU;AAC9B,UAAI,eAAe,OAAO,gBAAgB,YAAY,YAAY,QAAQ;AACxE,cAAM,SAAS,YAAY;AAC3B,YAAI,WAAW,aAAa,WAAW,YAAY;AACjD,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,UAAI,UAAU,WAAW,aAAa,UAAU,WAAW,YAAY;AACrE,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAGA,UAAM,MAAM,MAAM,QAAQ,YAAY;AACtC,QAAI,IAAI,SAAS,qBAAqB,KAAK,IAAI,SAAS,aAAa,GAAG;AACtE,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB,SAAgD;AAC1E,QAAI,CAAC,QAAS,QAAO,CAAC;AACtB,UAAM,EAAE,gBAAgB,GAAG,KAAK,IAAI;AACpC,WAAO;AAAA,EACT;AAAA,EAEQ,sBAA8C;AACpD,UAAM,UAAkC;AAAA,MACtC,oBAAgB,+BAAW;AAAA,IAC7B;AAEA,UAAM,OAAO,6BAAiB,eAAe;AAC7C,QAAI,MAAM,aAAa;AACrB,cAAQ,gBAAgB,IAAI,KAAK;AAAA,IACnC;AAEA,UAAM,MAAM,oBAAQ,WAAW;AAC/B,QAAI,KAAK;AAEP,cAAQ,cAAc,MAAM,IAAI,OAAO,IAAI,IAAI,MAAM;AAAA,IACvD,OAAO;AAEL,YAAM,cAAU,gCAAY,EAAE,EAAE,SAAS,KAAK;AAC9C,YAAM,aAAS,gCAAY,CAAC,EAAE,SAAS,KAAK;AAC5C,cAAQ,cAAc,MAAM,OAAO,IAAI,MAAM;AAAA,IAC/C;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,6BAA6B,SAKnC;AACA,UAAM,SAA+D,CAAC;AAEtE,UAAM,cAAc,QAAQ,IAAI,oBAAoB;AACpD,QAAI,aAAa;AACf,aAAO,cAAc;AAAA,IACvB;AAEA,UAAM,eAAe,QAAQ,IAAI,qBAAqB;AACtD,QAAI,cAAc;AAChB,aAAO,eAAe,iBAAiB;AAAA,IACzC;AAEA,UAAM,WAAW,QAAQ,IAAI,aAAa;AAC1C,QAAI,UAAU;AACZ,UAAI;AACF,cAAM,QAAQ,KAAK,MAAM,QAAQ;AACjC,eAAO,QAAQ;AAAA,UACb,cAAc,MAAM;AAAA,UACpB,kBAAkB,MAAM;AAAA,UACxB,aAAa,MAAM;AAAA,UACnB,iBAAiB,MAAM,2BAA2B;AAAA;AAAA,UAElD,GAAG;AAAA,QACL;AAAA,MACF,QAAQ;AACN,aAAK,OAAO,KAAK,sCAAsC,EAAE,SAAS,CAAC;AAAA,MACrE;AAAA,IACF;AAEA,UAAM,OAAO,QAAQ,IAAI,YAAY;AACrC,UAAM,gBAAgB,QAAQ,IAAI,sBAAsB;AACxD,QAAI,MAAM;AACR,aAAO,OAAO;AAAA,QACZ,WAAW;AAAA,QACX,WAAW,kBAAkB;AAAA,MAC/B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEU,gBAAgB,UAAqE;AAE7F,UAAM,qBAAqB,oBAAI,IAAY;AAE3C,WAAO,SAAS,QAAQ,CAAC,MAAM;AAC7B,UAAI,EAAE,SAAS,QAAQ;AAErB,YAAI,CAAC,EAAE,gBAAgB,CAAC,mBAAmB,IAAI,EAAE,YAAY,GAAG;AAC9D,iBAAO,CAAC;AAAA,QACV;AAEA,eAAO;AAAA,UACL,MAAM;AAAA,UACN,cAAc,EAAE;AAAA,UAChB,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,KAAK,UAAU,EAAE,OAAO;AAAA;AAAA,QAC/E;AAAA,MACF;AAEA,UAAI,EAAE,SAAS,aAAa;AAM1B,YAAI,mBAAkC;AAEtC,YAAI,OAAO,EAAE,YAAY,UAAU;AACjC,6BAAmB,EAAE;AAAA,QACvB;AAGA,YAAI,CAAC,qBAAqB,CAAC,EAAE,cAAc,EAAE,WAAW,WAAW,IAAI;AACrE,6BAAmB;AAAA,QACrB;AAEA,YAAI,EAAE,YAAY;AAChB,YAAE,WAAW,QAAQ,CAAC,OAAO,mBAAmB,IAAI,GAAG,EAAE,CAAC;AAAA,QAC5D;AAGA,cAAM,cAAe,EAAU;AAO/B,YAAI,eAAe,YAAY,SAAS,GAAG;AAEzC,gBAAM,SAAyD,CAAC;AAGhE,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,SAAS;AAAA,YACT,YAAY,EAAE,YAAY,IAAI,CAAC,QAAQ;AAAA,cACrC,IAAI,GAAG;AAAA,cACP,MAAM;AAAA,cACN,UAAU;AAAA,gBACR,MAAM,GAAG,SAAS;AAAA,gBAClB,WAAW,GAAG,SAAS;AAAA,cACzB;AAAA,YACF,EAAE;AAAA,UACJ,CAAC;AAGD,qBAAW,UAAU,aAAa;AAChC,gBAAI,OAAO,WAAW,WAAW;AAE/B;AAAA,YACF;AAEA,mBAAO,KAAK;AAAA,cACV,MAAM;AAAA,cACN,cAAc,OAAO;AAAA,cACrB,SAAS,OAAO,WAAW,UACvB,KAAK,UAAU,EAAE,OAAO,OAAO,SAAS,gBAAgB,CAAC,IACxD,OAAO,WAAW;AAAA,YACzB,CAAC;AAAA,UACH;AAEA,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,UACT,YAAY,EAAE,YAAY,IAAI,CAAC,QAAQ;AAAA,YACrC,IAAI,GAAG;AAAA,YACP,MAAM;AAAA,YACN,UAAU;AAAA,cACR,MAAM,GAAG,SAAS;AAAA,cAClB,WAAW,GAAG,SAAS;AAAA,YACzB;AAAA,UACF,EAAE;AAAA,QACJ;AAAA,MACF;AAGA,YAAM,UAAkE,MAAM;AAAA,QAC5E,EAAE;AAAA,MACJ,IACI,EAAE,QAAQ,IAAI,CAAC,SAAS,KAAK,mBAAmB,IAAI,CAAC,IACrD,EAAE,WAAW;AAEjB,aAAO;AAAA,QACL,MAAM,EAAE;AAAA,QACR;AAAA,QACA,MAAM,EAAE;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,aACN,OACoD;AACpD,QAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,WAAO,MAAM,IAAI,CAAC,OAAO;AAAA,MACvB,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM,EAAE,SAAS;AAAA,QACjB,aAAa,EAAE,SAAS;AAAA,QACxB,YAAY,EAAE,SAAS;AAAA,MACzB;AAAA,IACF,EAAE;AAAA,EACJ;AAAA,EAEQ,SAAS,OAA8D;AAC7E,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO;AAAA,MACL,cAAc,MAAM;AAAA,MACpB,kBAAkB,MAAM;AAAA,MACxB,aAAa,MAAM;AAAA,MACnB,iBAAkB,MAAc,2BAA2B;AAAA,IAC7D;AAAA,EACF;AAAA,EAEQ,gBAAgB,QAAyD;AAC/E,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAgE;AACzF,QAAI,KAAK,SAAS,OAAQ,QAAO,EAAE,MAAM,QAAQ,MAAM,KAAK,KAAK;AAEjE,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,EAAE,UAAU,OAAO,IAAI,KAAK;AAClC,UAAI,CAAC,SAAS,WAAW,QAAQ,GAAG;AAClC,cAAM,IAAI,MAAM,+BAA+B,QAAQ,wBAAwB;AAAA,MACjF;AAEA,YAAM,MAAM,OAAO,SAAS,QAAQ,OAAO,MAAM,QAAQ,QAAQ,WAAW,OAAO,MAAM;AAEzF,aAAO,EAAE,MAAM,aAAa,WAAW,EAAE,IAAI,EAAE;AAAA,IACjD;AAEA,QAAI,KAAK,SAAS,aAAa;AAC7B,aAAO,EAAE,MAAM,aAAa,WAAW,EAAE,KAAK,KAAK,UAAU,IAAI,EAAE;AAAA,IACrE;AAGA,UAAM,cAAqB;AAC3B,UAAM,IAAI,MAAM,6BAA8B,YAAoB,IAAI,EAAE;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,gBAAgB,SAAqD;AAC3E,QAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,WAAO,QAAQ,IAAI,CAAC,WAAW;AAC7B,UAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAElD,YAAM,YAAY,EAAE,GAAG,OAAO;AAG9B,UAAI,UAAU,WAAW,OAAO,UAAU,YAAY,UAAU;AAC9D,cAAM,UAAU,EAAE,GAAG,UAAU,QAAQ;AAGvC,YAAI,qBAAqB,SAAS;AAChC,iBAAO,QAAQ;AAAA,QACjB;AAGA,YAAI,8BAA8B,SAAS;AACzC,iBAAO,QAAQ;AAAA,QACjB;AAGA,YAAI,QAAQ,cAAc,MAAM,QAAQ,QAAQ,UAAU,GAAG;AAC3D,kBAAQ,aAAa,QAAQ,WAAW,IAAI,CAAC,OAAgC;AAC3E,gBAAI,CAAC,MAAM,OAAO,OAAO,SAAU,QAAO;AAC1C,kBAAM,cAAc,EAAE,GAAG,GAAG;AAE5B,gBAAI,8BAA8B,aAAa;AAC7C,qBAAO,YAAY;AAAA,YACrB;AACA,mBAAO;AAAA,UACT,CAAC;AAAA,QACH;AAEA,kBAAU,UAAU;AAAA,MACtB;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;","names":["OpenAIClient"]}
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ExecutionContext,
|
|
7
7
|
Tracing
|
|
8
8
|
} from "@nebulaos/core";
|
|
9
|
+
import { SpanType } from "@nebulaos/types";
|
|
9
10
|
var LLMGatewayError = class extends Error {
|
|
10
11
|
constructor(message, code, status, cause) {
|
|
11
12
|
super(message);
|
|
@@ -43,27 +44,22 @@ var LLMGateway = class {
|
|
|
43
44
|
async generate(messages, tools, options) {
|
|
44
45
|
const mergedOptions = { ...this.options, ...options };
|
|
45
46
|
const model = `route:${this.modelName}`;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
const { responseFormat, ...llmConfig } = mergedOptions ?? {};
|
|
48
|
+
const startData = {
|
|
49
|
+
provider: this.providerName,
|
|
50
|
+
model: this.modelName,
|
|
51
|
+
messagesCount: messages.length,
|
|
52
|
+
toolsCount: tools?.length ?? 0,
|
|
53
|
+
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
54
|
+
responseFormat,
|
|
55
|
+
messages,
|
|
56
|
+
tools
|
|
57
|
+
};
|
|
54
58
|
return Tracing.withSpan(
|
|
55
59
|
{
|
|
56
|
-
kind:
|
|
60
|
+
kind: SpanType.llm,
|
|
57
61
|
name: `llm:${this.modelName}`,
|
|
58
|
-
data:
|
|
59
|
-
provider: this.providerName,
|
|
60
|
-
model: this.modelName,
|
|
61
|
-
messagesCount: messages.length,
|
|
62
|
-
toolsCount: tools?.length ?? 0,
|
|
63
|
-
responseFormat: mergedOptions?.responseFormat,
|
|
64
|
-
messages: messagesPreview,
|
|
65
|
-
tools: toolsPreview
|
|
66
|
-
}
|
|
62
|
+
data: startData
|
|
67
63
|
},
|
|
68
64
|
async (llmSpan) => {
|
|
69
65
|
const headers = this.buildGatewayHeaders();
|
|
@@ -98,18 +94,18 @@ var LLMGateway = class {
|
|
|
98
94
|
const usage = this.mapUsage(response.usage);
|
|
99
95
|
const finishReason = this.mapFinishReason(choice.finish_reason);
|
|
100
96
|
const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);
|
|
97
|
+
const endData = {
|
|
98
|
+
usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
99
|
+
finishReason: finishReason ?? "stop",
|
|
100
|
+
toolCallsCount: message.tool_calls?.length ?? 0,
|
|
101
|
+
choices: this.sanitizeChoices(response.choices),
|
|
102
|
+
model: enrichment.modelActual,
|
|
103
|
+
fallbackUsed: enrichment.fallbackUsed,
|
|
104
|
+
cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : void 0
|
|
105
|
+
};
|
|
101
106
|
await llmSpan.end({
|
|
102
107
|
status: "success",
|
|
103
|
-
data:
|
|
104
|
-
usage: enrichment.usage ?? usage,
|
|
105
|
-
finishReason,
|
|
106
|
-
toolCallsCount: message.tool_calls?.length ?? 0,
|
|
107
|
-
outputPreview: message.content?.slice(0, 200),
|
|
108
|
-
// Enrichment from backend gateway
|
|
109
|
-
modelActual: enrichment.modelActual,
|
|
110
|
-
fallbackUsed: enrichment.fallbackUsed,
|
|
111
|
-
cost: enrichment.cost
|
|
112
|
-
}
|
|
108
|
+
data: endData
|
|
113
109
|
});
|
|
114
110
|
return {
|
|
115
111
|
content: message.content || "",
|
|
@@ -127,15 +123,16 @@ var LLMGateway = class {
|
|
|
127
123
|
} catch (error) {
|
|
128
124
|
this.logger.error("LLM Gateway request failed", error, void 0, void 0);
|
|
129
125
|
const gatewayError = this.handleError(error);
|
|
126
|
+
const errorEndData = {
|
|
127
|
+
error: {
|
|
128
|
+
message: gatewayError.message,
|
|
129
|
+
code: gatewayError.code,
|
|
130
|
+
status: gatewayError.status
|
|
131
|
+
}
|
|
132
|
+
};
|
|
130
133
|
await llmSpan.end({
|
|
131
134
|
status: "error",
|
|
132
|
-
data:
|
|
133
|
-
error: {
|
|
134
|
-
message: gatewayError.message,
|
|
135
|
-
code: gatewayError.code,
|
|
136
|
-
status: gatewayError.status
|
|
137
|
-
}
|
|
138
|
-
}
|
|
135
|
+
data: errorEndData
|
|
139
136
|
});
|
|
140
137
|
throw gatewayError;
|
|
141
138
|
}
|
|
@@ -145,26 +142,21 @@ var LLMGateway = class {
|
|
|
145
142
|
async *generateStream(messages, tools, options) {
|
|
146
143
|
const mergedOptions = { ...this.options, ...options };
|
|
147
144
|
const model = `route:${this.modelName}`;
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
const { responseFormat, ...llmConfig } = mergedOptions ?? {};
|
|
146
|
+
const startData = {
|
|
147
|
+
provider: this.providerName,
|
|
148
|
+
model: this.modelName,
|
|
149
|
+
messagesCount: messages.length,
|
|
150
|
+
toolsCount: tools?.length ?? 0,
|
|
151
|
+
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
152
|
+
responseFormat,
|
|
153
|
+
messages,
|
|
154
|
+
tools
|
|
155
|
+
};
|
|
156
156
|
const llmSpan = await Tracing.startSpan({
|
|
157
|
-
kind:
|
|
157
|
+
kind: SpanType.llm,
|
|
158
158
|
name: `llm:${this.modelName}`,
|
|
159
|
-
data:
|
|
160
|
-
provider: this.providerName,
|
|
161
|
-
model: this.modelName,
|
|
162
|
-
messagesCount: messages.length,
|
|
163
|
-
toolsCount: tools?.length ?? 0,
|
|
164
|
-
responseFormat: mergedOptions?.responseFormat,
|
|
165
|
-
messages: messagesPreview,
|
|
166
|
-
tools: toolsPreview
|
|
167
|
-
}
|
|
159
|
+
data: startData
|
|
168
160
|
});
|
|
169
161
|
const headers = this.buildGatewayHeaders();
|
|
170
162
|
this.logger.debug("LLM Gateway stream request", {
|
|
@@ -195,15 +187,16 @@ var LLMGateway = class {
|
|
|
195
187
|
this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
|
|
196
188
|
const gatewayError = this.handleError(error);
|
|
197
189
|
if (llmSpan) {
|
|
190
|
+
const errorEndData = {
|
|
191
|
+
error: {
|
|
192
|
+
message: gatewayError.message,
|
|
193
|
+
code: gatewayError.code,
|
|
194
|
+
status: gatewayError.status
|
|
195
|
+
}
|
|
196
|
+
};
|
|
198
197
|
await llmSpan.end({
|
|
199
198
|
status: "error",
|
|
200
|
-
data:
|
|
201
|
-
error: {
|
|
202
|
-
message: gatewayError.message,
|
|
203
|
-
code: gatewayError.code,
|
|
204
|
-
status: gatewayError.status
|
|
205
|
-
}
|
|
206
|
-
}
|
|
199
|
+
data: errorEndData
|
|
207
200
|
});
|
|
208
201
|
}
|
|
209
202
|
throw gatewayError;
|
|
@@ -212,6 +205,8 @@ var LLMGateway = class {
|
|
|
212
205
|
let finalFinishReason;
|
|
213
206
|
let toolCallsCount = 0;
|
|
214
207
|
let outputPreview = "";
|
|
208
|
+
let finalContent = "";
|
|
209
|
+
const toolCallsAccumulator = /* @__PURE__ */ new Map();
|
|
215
210
|
try {
|
|
216
211
|
for await (const chunk of stream) {
|
|
217
212
|
if (chunk.usage) {
|
|
@@ -234,6 +229,7 @@ var LLMGateway = class {
|
|
|
234
229
|
const delta = choice.delta;
|
|
235
230
|
if (!delta) continue;
|
|
236
231
|
if (delta.content) {
|
|
232
|
+
finalContent += delta.content;
|
|
237
233
|
if (outputPreview.length < 200) {
|
|
238
234
|
outputPreview += delta.content.slice(0, 200 - outputPreview.length);
|
|
239
235
|
}
|
|
@@ -241,49 +237,72 @@ var LLMGateway = class {
|
|
|
241
237
|
}
|
|
242
238
|
if (delta.tool_calls) {
|
|
243
239
|
for (const tc of delta.tool_calls) {
|
|
240
|
+
const idx = tc.index;
|
|
244
241
|
if (tc.id && tc.function?.name) {
|
|
245
242
|
toolCallsCount++;
|
|
243
|
+
toolCallsAccumulator.set(idx, { id: tc.id, name: tc.function.name, arguments: "" });
|
|
246
244
|
yield {
|
|
247
245
|
type: "tool_call_start",
|
|
248
|
-
index:
|
|
246
|
+
index: idx,
|
|
249
247
|
id: tc.id,
|
|
250
248
|
name: tc.function.name
|
|
251
249
|
};
|
|
252
250
|
}
|
|
253
251
|
if (tc.function?.arguments) {
|
|
252
|
+
const existing = toolCallsAccumulator.get(idx);
|
|
253
|
+
if (existing) {
|
|
254
|
+
existing.arguments += tc.function.arguments;
|
|
255
|
+
}
|
|
254
256
|
yield {
|
|
255
257
|
type: "tool_call_delta",
|
|
256
|
-
index:
|
|
258
|
+
index: idx,
|
|
257
259
|
args: tc.function.arguments
|
|
258
260
|
};
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
}
|
|
265
|
+
const toolCalls = Array.from(toolCallsAccumulator.values()).map((tc) => ({
|
|
266
|
+
id: tc.id,
|
|
267
|
+
type: "function",
|
|
268
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
269
|
+
}));
|
|
270
|
+
const choices = [{
|
|
271
|
+
index: 0,
|
|
272
|
+
message: {
|
|
273
|
+
role: "assistant",
|
|
274
|
+
content: finalContent || null,
|
|
275
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
|
276
|
+
},
|
|
277
|
+
finish_reason: finalFinishReason
|
|
278
|
+
}];
|
|
263
279
|
if (llmSpan) {
|
|
280
|
+
const endData = {
|
|
281
|
+
usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
282
|
+
finishReason: finalFinishReason ?? "stop",
|
|
283
|
+
toolCallsCount,
|
|
284
|
+
outputPreview,
|
|
285
|
+
choices: this.sanitizeChoices(choices)
|
|
286
|
+
};
|
|
264
287
|
await llmSpan.end({
|
|
265
288
|
status: "success",
|
|
266
|
-
data:
|
|
267
|
-
usage: finalUsage,
|
|
268
|
-
finishReason: finalFinishReason,
|
|
269
|
-
toolCallsCount,
|
|
270
|
-
outputPreview
|
|
271
|
-
}
|
|
289
|
+
data: endData
|
|
272
290
|
});
|
|
273
291
|
}
|
|
274
292
|
} catch (error) {
|
|
275
293
|
this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
|
|
276
294
|
const gatewayError = this.handleError(error);
|
|
277
295
|
if (llmSpan) {
|
|
296
|
+
const errorEndData = {
|
|
297
|
+
error: {
|
|
298
|
+
message: gatewayError.message,
|
|
299
|
+
code: gatewayError.code,
|
|
300
|
+
status: gatewayError.status
|
|
301
|
+
}
|
|
302
|
+
};
|
|
278
303
|
await llmSpan.end({
|
|
279
304
|
status: "error",
|
|
280
|
-
data:
|
|
281
|
-
error: {
|
|
282
|
-
message: gatewayError.message,
|
|
283
|
-
code: gatewayError.code,
|
|
284
|
-
status: gatewayError.status
|
|
285
|
-
}
|
|
286
|
-
}
|
|
305
|
+
data: errorEndData
|
|
287
306
|
});
|
|
288
307
|
}
|
|
289
308
|
throw gatewayError;
|
|
@@ -348,8 +367,11 @@ var LLMGateway = class {
|
|
|
348
367
|
);
|
|
349
368
|
}
|
|
350
369
|
if (status === 400) {
|
|
370
|
+
const lowerMsg = originalMessage.toLowerCase();
|
|
371
|
+
const isPermissionRelated = lowerMsg.includes("unauthorized") || lowerMsg.includes("permission") || lowerMsg.includes("access denied") || lowerMsg.includes("not allowed") || lowerMsg.includes("forbidden") || lowerMsg.includes("invalid api key") || lowerMsg.includes("api key");
|
|
372
|
+
const permissionHint = isPermissionRelated ? `This error may indicate a token/permission issue. ` : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. Common cause: using a route without the corresponding token permission. `;
|
|
351
373
|
return new LLMGatewayError(
|
|
352
|
-
`LLM Gateway request error: Invalid request parameters. Please check your request configuration (model, messages, tools). Original error: ${originalMessage}`,
|
|
374
|
+
`LLM Gateway request error: Invalid request parameters. Please check your request configuration (model, messages, tools). ${permissionHint}Original error: ${originalMessage}`,
|
|
353
375
|
"LLM_GATEWAY_BAD_REQUEST",
|
|
354
376
|
status,
|
|
355
377
|
error
|
|
@@ -545,6 +567,33 @@ var LLMGateway = class {
|
|
|
545
567
|
if (m.tool_calls) {
|
|
546
568
|
m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));
|
|
547
569
|
}
|
|
570
|
+
const toolOutputs = m.toolOutputs;
|
|
571
|
+
if (toolOutputs && toolOutputs.length > 0) {
|
|
572
|
+
const result = [];
|
|
573
|
+
result.push({
|
|
574
|
+
role: "assistant",
|
|
575
|
+
content: assistantContent,
|
|
576
|
+
tool_calls: m.tool_calls?.map((tc) => ({
|
|
577
|
+
id: tc.id,
|
|
578
|
+
type: "function",
|
|
579
|
+
function: {
|
|
580
|
+
name: tc.function.name,
|
|
581
|
+
arguments: tc.function.arguments
|
|
582
|
+
}
|
|
583
|
+
}))
|
|
584
|
+
});
|
|
585
|
+
for (const output of toolOutputs) {
|
|
586
|
+
if (output.status === "pending") {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
result.push({
|
|
590
|
+
role: "tool",
|
|
591
|
+
tool_call_id: output.toolCallId,
|
|
592
|
+
content: output.status === "error" ? JSON.stringify({ error: output.error || "Unknown error" }) : output.content || ""
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
return result;
|
|
596
|
+
}
|
|
548
597
|
return {
|
|
549
598
|
role: "assistant",
|
|
550
599
|
content: assistantContent,
|
|
@@ -618,6 +667,42 @@ var LLMGateway = class {
|
|
|
618
667
|
const _exhaustive = part;
|
|
619
668
|
throw new Error(`Unsupported content type: ${_exhaustive.type}`);
|
|
620
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* Sanitize choices for observability storage.
|
|
672
|
+
*
|
|
673
|
+
* Removes provider-specific fields that are not useful for tracing/debugging
|
|
674
|
+
* and that bloat the attribute size (causing truncation). Specifically:
|
|
675
|
+
* - Gemini's thought_signature (huge base64 strings, 2000+ chars each)
|
|
676
|
+
* - thinking_blocks (already captured via reasoningTokens)
|
|
677
|
+
*/
|
|
678
|
+
sanitizeChoices(choices) {
|
|
679
|
+
if (!choices) return [];
|
|
680
|
+
return choices.map((choice) => {
|
|
681
|
+
if (!choice || typeof choice !== "object") return choice;
|
|
682
|
+
const sanitized = { ...choice };
|
|
683
|
+
if (sanitized.message && typeof sanitized.message === "object") {
|
|
684
|
+
const message = { ...sanitized.message };
|
|
685
|
+
if ("thinking_blocks" in message) {
|
|
686
|
+
delete message.thinking_blocks;
|
|
687
|
+
}
|
|
688
|
+
if ("provider_specific_fields" in message) {
|
|
689
|
+
delete message.provider_specific_fields;
|
|
690
|
+
}
|
|
691
|
+
if (message.tool_calls && Array.isArray(message.tool_calls)) {
|
|
692
|
+
message.tool_calls = message.tool_calls.map((tc) => {
|
|
693
|
+
if (!tc || typeof tc !== "object") return tc;
|
|
694
|
+
const sanitizedTc = { ...tc };
|
|
695
|
+
if ("provider_specific_fields" in sanitizedTc) {
|
|
696
|
+
delete sanitizedTc.provider_specific_fields;
|
|
697
|
+
}
|
|
698
|
+
return sanitizedTc;
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
sanitized.message = message;
|
|
702
|
+
}
|
|
703
|
+
return sanitized;
|
|
704
|
+
});
|
|
705
|
+
}
|
|
621
706
|
};
|
|
622
707
|
export {
|
|
623
708
|
LLMGateway,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Prepare context data for tracing (useful for debugging, especially on errors)\n const messagesPreview = messages.map((m) => ({\n role: m.role,\n content: typeof m.content === \"string\"\n ? m.content.length > 500 ? m.content.slice(0, 500) + \"...\" : m.content\n : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content),\n }));\n const toolsPreview = tools?.map((t) => ({\n name: t.function.name,\n description: t.function.description?.slice(0, 200),\n }));\n\n return Tracing.withSpan(\n {\n kind: \"llm\",\n name: `llm:${this.modelName}`,\n data: {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n responseFormat: mergedOptions?.responseFormat,\n messages: messagesPreview,\n tools: toolsPreview,\n },\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n await llmSpan.end({\n status: \"success\",\n data: {\n usage: enrichment.usage ?? usage,\n finishReason,\n toolCallsCount: message.tool_calls?.length ?? 0,\n outputPreview: message.content?.slice(0, 200),\n // Enrichment from backend gateway\n modelActual: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost,\n },\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n await llmSpan.end({\n status: \"error\",\n data: {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n },\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Prepare context data for tracing (useful for debugging, especially on errors)\n const messagesPreview = messages.map((m) => ({\n role: m.role,\n content: typeof m.content === \"string\"\n ? m.content.length > 500 ? m.content.slice(0, 500) + \"...\" : m.content\n : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content),\n }));\n const toolsPreview = tools?.map((t) => ({\n name: t.function.name,\n description: t.function.description?.slice(0, 200),\n }));\n\n // Start span manually for streaming (async generators can't use withSpan directly)\n const llmSpan = await Tracing.startSpan({\n kind: \"llm\",\n name: `llm:${this.modelName}`,\n data: {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n responseFormat: mergedOptions?.responseFormat,\n messages: messagesPreview,\n tools: toolsPreview,\n },\n });\n\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway stream request\", {\n model,\n baseUrl: this.baseUrl,\n stream: true,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n let stream;\n try {\n stream = await this.client.chat.completions.create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n stream: true,\n stream_options: { include_usage: true },\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n await llmSpan.end({\n status: \"error\",\n data: {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n },\n });\n }\n\n throw gatewayError;\n }\n\n let finalUsage: TokenUsage | undefined;\n let finalFinishReason: ProviderResponse[\"finishReason\"];\n let toolCallsCount = 0;\n let outputPreview = \"\";\n\n try {\n for await (const chunk of stream) {\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n yield {\n type: \"finish\",\n reason: \"stop\",\n usage: finalUsage,\n };\n }\n\n const choice = chunk.choices?.[0];\n if (!choice) continue;\n\n if (choice.finish_reason) {\n finalFinishReason = this.mapFinishReason(choice.finish_reason);\n yield {\n type: \"finish\",\n reason: finalFinishReason,\n };\n }\n\n const delta = choice.delta;\n if (!delta) continue;\n\n if (delta.content) {\n if (outputPreview.length < 200) {\n outputPreview += delta.content.slice(0, 200 - outputPreview.length);\n }\n yield { type: \"content_delta\", delta: delta.content };\n }\n\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n if (tc.id && tc.function?.name) {\n toolCallsCount++;\n yield {\n type: \"tool_call_start\",\n index: tc.index,\n id: tc.id,\n name: tc.function.name,\n };\n }\n\n if (tc.function?.arguments) {\n yield {\n type: \"tool_call_delta\",\n index: tc.index,\n args: tc.function.arguments,\n };\n }\n }\n }\n }\n\n // End span with success\n if (llmSpan) {\n await llmSpan.end({\n status: \"success\",\n data: {\n usage: finalUsage,\n finishReason: finalFinishReason,\n toolCallsCount,\n outputPreview,\n },\n });\n }\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n await llmSpan.end({\n status: \"error\",\n data: {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n },\n });\n }\n\n throw gatewayError;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-request-id\": randomUUID(),\n };\n\n const exec = ExecutionContext.getOrUndefined();\n if (exec?.executionId) {\n headers[\"x-execution-id\"] = exec.executionId;\n }\n\n const ctx = Tracing.getContext();\n if (ctx) {\n // IDs já estão em formato W3C (hex)\n headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;\n } else {\n // Still emit a root trace context so the gateway can correlate requests by traceId.\n const traceId = randomBytes(16).toString(\"hex\");\n const spanId = randomBytes(8).toString(\"hex\");\n headers.traceparent = `00-${traceId}-${spanId}-01`;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { mimeType, source } = part.file;\n if (!mimeType.startsWith(\"image/\")) {\n throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);\n }\n\n const url = source.type === \"url\" ? source.url : `data:${mimeType};base64,${source.base64}`;\n\n return { type: \"image_url\", image_url: { url } };\n }\n\n if (part.type === \"image_url\") {\n return { type: \"image_url\", image_url: { url: part.image_url.url } };\n }\n\n // Exhaustive check - should never reach here with proper ContentPart\n const _exhaustive: never = part;\n throw new Error(`Unsupported content type: ${(_exhaustive as any).type}`);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\nimport { SpanType, type LLMSpanStartData, type LLMSpanEndData, type LLMResponseChoice } from \"@nebulaos/types\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n return Tracing.withSpan(\n {\n kind: SpanType.llm,\n name: `llm:${this.modelName}`,\n data: startData,\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n // Typed span end data for LLM calls\n const endData: LLMSpanEndData = {\n usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finishReason ?? \"stop\",\n toolCallsCount: message.tool_calls?.length ?? 0,\n choices: this.sanitizeChoices(response.choices),\n model: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : undefined,\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = `route:${this.modelName}`;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n // Start span manually for streaming (async generators can't use withSpan directly)\n const llmSpan = await Tracing.startSpan({\n kind: SpanType.llm,\n name: `llm:${this.modelName}`,\n data: startData,\n });\n\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway stream request\", {\n model,\n baseUrl: this.baseUrl,\n stream: true,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n let stream;\n try {\n stream = await this.client.chat.completions.create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n stream: true,\n stream_options: { include_usage: true },\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n }\n\n throw gatewayError;\n }\n\n let finalUsage: TokenUsage | undefined;\n let finalFinishReason: ProviderResponse[\"finishReason\"];\n let toolCallsCount = 0;\n let outputPreview = \"\";\n let finalContent = \"\";\n const toolCallsAccumulator: Map<number, { id: string; name: string; arguments: string }> = new Map();\n\n try {\n for await (const chunk of stream) {\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n yield {\n type: \"finish\",\n reason: \"stop\",\n usage: finalUsage,\n };\n }\n\n const choice = chunk.choices?.[0];\n if (!choice) continue;\n\n if (choice.finish_reason) {\n finalFinishReason = this.mapFinishReason(choice.finish_reason);\n yield {\n type: \"finish\",\n reason: finalFinishReason,\n };\n }\n\n const delta = choice.delta;\n if (!delta) continue;\n\n if (delta.content) {\n finalContent += delta.content;\n if (outputPreview.length < 200) {\n outputPreview += delta.content.slice(0, 200 - outputPreview.length);\n }\n yield { type: \"content_delta\", delta: delta.content };\n }\n\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index;\n if (tc.id && tc.function?.name) {\n toolCallsCount++;\n toolCallsAccumulator.set(idx, { id: tc.id, name: tc.function.name, arguments: \"\" });\n yield {\n type: \"tool_call_start\",\n index: idx,\n id: tc.id,\n name: tc.function.name,\n };\n }\n\n if (tc.function?.arguments) {\n const existing = toolCallsAccumulator.get(idx);\n if (existing) {\n existing.arguments += tc.function.arguments;\n }\n yield {\n type: \"tool_call_delta\",\n index: idx,\n args: tc.function.arguments,\n };\n }\n }\n }\n }\n\n // Build choices for observability\n const toolCalls = Array.from(toolCallsAccumulator.values()).map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n }));\n\n const choices = [{\n index: 0,\n message: {\n role: \"assistant\" as const,\n content: finalContent || null,\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n },\n finish_reason: finalFinishReason,\n }];\n\n // Typed span end data for success case\n if (llmSpan) {\n const endData: LLMSpanEndData = {\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finalFinishReason ?? \"stop\",\n toolCallsCount,\n outputPreview,\n choices: this.sanitizeChoices(choices),\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n }\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n if (llmSpan) {\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n }\n\n throw gatewayError;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n // Check if error message suggests permission/token issues\n const lowerMsg = originalMessage.toLowerCase();\n const isPermissionRelated =\n lowerMsg.includes(\"unauthorized\") ||\n lowerMsg.includes(\"permission\") ||\n lowerMsg.includes(\"access denied\") ||\n lowerMsg.includes(\"not allowed\") ||\n lowerMsg.includes(\"forbidden\") ||\n lowerMsg.includes(\"invalid api key\") ||\n lowerMsg.includes(\"api key\");\n\n const permissionHint = isPermissionRelated\n ? `This error may indicate a token/permission issue. `\n : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. ` +\n `Common cause: using a route without the corresponding token permission. `;\n\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `${permissionHint}` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-request-id\": randomUUID(),\n };\n\n const exec = ExecutionContext.getOrUndefined();\n if (exec?.executionId) {\n headers[\"x-execution-id\"] = exec.executionId;\n }\n\n const ctx = Tracing.getContext();\n if (ctx) {\n // IDs já estão em formato W3C (hex)\n headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;\n } else {\n // Still emit a root trace context so the gateway can correlate requests by traceId.\n const traceId = randomBytes(16).toString(\"hex\");\n const spanId = randomBytes(8).toString(\"hex\");\n headers.traceparent = `00-${traceId}-${spanId}-01`;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n // Check if this is a UnifiedToolMessage (has toolOutputs)\n const toolOutputs = (m as any).toolOutputs as Array<{\n toolCallId: string;\n status: 'pending' | 'success' | 'error';\n content: string | null;\n error?: string;\n }> | undefined;\n\n if (toolOutputs && toolOutputs.length > 0) {\n // Expand UnifiedToolMessage into assistant + tool messages\n const result: OpenAIClient.Chat.ChatCompletionMessageParam[] = [];\n\n // 1. Assistant message with tool_calls\n result.push({\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n\n // 2. Tool messages for each completed toolOutput\n for (const output of toolOutputs) {\n if (output.status === 'pending') {\n // Skip pending outputs - tool hasn't finished yet\n continue;\n }\n\n result.push({\n role: \"tool\",\n tool_call_id: output.toolCallId,\n content: output.status === 'error'\n ? JSON.stringify({ error: output.error || 'Unknown error' })\n : (output.content || ''),\n });\n }\n\n return result;\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { mimeType, source } = part.file;\n if (!mimeType.startsWith(\"image/\")) {\n throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);\n }\n\n const url = source.type === \"url\" ? source.url : `data:${mimeType};base64,${source.base64}`;\n\n return { type: \"image_url\", image_url: { url } };\n }\n\n if (part.type === \"image_url\") {\n return { type: \"image_url\", image_url: { url: part.image_url.url } };\n }\n\n // Exhaustive check - should never reach here with proper ContentPart\n const _exhaustive: never = part;\n throw new Error(`Unsupported content type: ${(_exhaustive as any).type}`);\n }\n\n /**\n * Sanitize choices for observability storage.\n *\n * Removes provider-specific fields that are not useful for tracing/debugging\n * and that bloat the attribute size (causing truncation). Specifically:\n * - Gemini's thought_signature (huge base64 strings, 2000+ chars each)\n * - thinking_blocks (already captured via reasoningTokens)\n */\n private sanitizeChoices(choices: unknown[] | undefined): LLMResponseChoice[] {\n if (!choices) return [];\n\n return choices.map((choice) => {\n if (!choice || typeof choice !== \"object\") return choice as LLMResponseChoice;\n\n const sanitized = { ...choice } as Record<string, unknown>;\n\n // Remove provider_specific_fields.thought_signature (Gemini thinking signatures)\n if (sanitized.message && typeof sanitized.message === \"object\") {\n const message = { ...sanitized.message } as Record<string, unknown>;\n\n // Remove thinking_blocks (captured via reasoningTokens in usage)\n if (\"thinking_blocks\" in message) {\n delete message.thinking_blocks;\n }\n\n // Remove provider_specific_fields (contains thought_signatures)\n if (\"provider_specific_fields\" in message) {\n delete message.provider_specific_fields;\n }\n\n // Sanitize tool_calls: remove thought_signature from each tool call\n if (message.tool_calls && Array.isArray(message.tool_calls)) {\n message.tool_calls = message.tool_calls.map((tc: Record<string, unknown>) => {\n if (!tc || typeof tc !== \"object\") return tc;\n const sanitizedTc = { ...tc };\n // Remove provider_specific_fields from tool call (contains thought_signature)\n if (\"provider_specific_fields\" in sanitizedTc) {\n delete sanitizedTc.provider_specific_fields;\n }\n return sanitizedTc;\n });\n }\n\n sanitized.message = message;\n }\n\n return sanitized as LLMResponseChoice;\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulaos/llm-gateway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "NebulaOS LLM Gateway provider - OpenAI-compatible chat completions",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -27,18 +27,21 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"openai": "^4.52.7",
|
|
30
|
-
"@nebulaos/core": "^0.2.
|
|
30
|
+
"@nebulaos/core": "^0.2.7",
|
|
31
|
+
"@nebulaos/types": "^0.1.5"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@types/node": "^20.0.0",
|
|
34
35
|
"tsup": "^8.0.0",
|
|
35
36
|
"typescript": "^5.0.0",
|
|
36
|
-
"
|
|
37
|
+
"jest": "^30.0.0",
|
|
38
|
+
"ts-jest": "^29.0.0",
|
|
39
|
+
"@types/jest": "^30.0.0"
|
|
37
40
|
},
|
|
38
41
|
"scripts": {
|
|
39
42
|
"build": "tsup",
|
|
40
43
|
"dev": "tsup --watch",
|
|
41
|
-
"test": "
|
|
44
|
+
"test": "jest",
|
|
42
45
|
"typecheck": "tsc --noEmit"
|
|
43
46
|
}
|
|
44
47
|
}
|