@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import OpenAIClient, { ClientOptions } from 'openai';
2
- import { LogLevel, GenerateOptions, IModel, Message, ToolDefinitionForLLM, ProviderResponse, StreamChunk } from '@nebulaos/core';
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, IModel, Message, ToolDefinitionForLLM, ProviderResponse, StreamChunk } from '@nebulaos/core';
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 messagesPreview = messages.map((m) => ({
78
- role: m.role,
79
- content: typeof m.content === "string" ? m.content.length > 500 ? m.content.slice(0, 500) + "..." : m.content : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content)
80
- }));
81
- const toolsPreview = tools?.map((t) => ({
82
- name: t.function.name,
83
- description: t.function.description?.slice(0, 200)
84
- }));
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: "llm",
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 messagesPreview = messages.map((m) => ({
180
- role: m.role,
181
- content: typeof m.content === "string" ? m.content.length > 500 ? m.content.slice(0, 500) + "..." : m.content : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content)
182
- }));
183
- const toolsPreview = tools?.map((t) => ({
184
- name: t.function.name,
185
- description: t.function.description?.slice(0, 200)
186
- }));
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: "llm",
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: tc.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: tc.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 messagesPreview = messages.map((m) => ({
47
- role: m.role,
48
- content: typeof m.content === "string" ? m.content.length > 500 ? m.content.slice(0, 500) + "..." : m.content : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content)
49
- }));
50
- const toolsPreview = tools?.map((t) => ({
51
- name: t.function.name,
52
- description: t.function.description?.slice(0, 200)
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: "llm",
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 messagesPreview = messages.map((m) => ({
149
- role: m.role,
150
- content: typeof m.content === "string" ? m.content.length > 500 ? m.content.slice(0, 500) + "..." : m.content : Array.isArray(m.content) ? `[${m.content.length} parts]` : String(m.content)
151
- }));
152
- const toolsPreview = tools?.map((t) => ({
153
- name: t.function.name,
154
- description: t.function.description?.slice(0, 200)
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: "llm",
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: tc.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: tc.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,
@@ -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.2",
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.4"
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
- "vitest": "^1.0.0"
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": "vitest",
44
+ "test": "jest",
42
45
  "typecheck": "tsc --noEmit"
43
46
  }
44
47
  }