@nebulaos/llm-gateway 0.1.2 → 0.1.3

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.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);
@@ -82,19 +83,22 @@ var LLMGateway = class {
82
83
  name: t.function.name,
83
84
  description: t.function.description?.slice(0, 200)
84
85
  }));
86
+ const { responseFormat, ...llmConfig } = mergedOptions ?? {};
87
+ const startData = {
88
+ provider: this.providerName,
89
+ model: this.modelName,
90
+ messagesCount: messages.length,
91
+ toolsCount: tools?.length ?? 0,
92
+ llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
93
+ responseFormat,
94
+ messages: messagesPreview,
95
+ tools: toolsPreview
96
+ };
85
97
  return import_core.Tracing.withSpan(
86
98
  {
87
- kind: "llm",
99
+ kind: import_types.SpanType.llm,
88
100
  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
- }
101
+ data: startData
98
102
  },
99
103
  async (llmSpan) => {
100
104
  const headers = this.buildGatewayHeaders();
@@ -129,18 +133,18 @@ var LLMGateway = class {
129
133
  const usage = this.mapUsage(response.usage);
130
134
  const finishReason = this.mapFinishReason(choice.finish_reason);
131
135
  const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);
136
+ const endData = {
137
+ usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
138
+ finishReason: finishReason ?? "stop",
139
+ toolCallsCount: message.tool_calls?.length ?? 0,
140
+ choices: response.choices,
141
+ model: enrichment.modelActual,
142
+ fallbackUsed: enrichment.fallbackUsed,
143
+ cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : void 0
144
+ };
132
145
  await llmSpan.end({
133
146
  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
- }
147
+ data: endData
144
148
  });
145
149
  return {
146
150
  content: message.content || "",
@@ -158,15 +162,16 @@ var LLMGateway = class {
158
162
  } catch (error) {
159
163
  this.logger.error("LLM Gateway request failed", error, void 0, void 0);
160
164
  const gatewayError = this.handleError(error);
165
+ const errorEndData = {
166
+ error: {
167
+ message: gatewayError.message,
168
+ code: gatewayError.code,
169
+ status: gatewayError.status
170
+ }
171
+ };
161
172
  await llmSpan.end({
162
173
  status: "error",
163
- data: {
164
- error: {
165
- message: gatewayError.message,
166
- code: gatewayError.code,
167
- status: gatewayError.status
168
- }
169
- }
174
+ data: errorEndData
170
175
  });
171
176
  throw gatewayError;
172
177
  }
@@ -184,18 +189,21 @@ var LLMGateway = class {
184
189
  name: t.function.name,
185
190
  description: t.function.description?.slice(0, 200)
186
191
  }));
192
+ const { responseFormat, ...llmConfig } = mergedOptions ?? {};
193
+ const startData = {
194
+ provider: this.providerName,
195
+ model: this.modelName,
196
+ messagesCount: messages.length,
197
+ toolsCount: tools?.length ?? 0,
198
+ llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
199
+ responseFormat,
200
+ messages: messagesPreview,
201
+ tools: toolsPreview
202
+ };
187
203
  const llmSpan = await import_core.Tracing.startSpan({
188
- kind: "llm",
204
+ kind: import_types.SpanType.llm,
189
205
  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
- }
206
+ data: startData
199
207
  });
200
208
  const headers = this.buildGatewayHeaders();
201
209
  this.logger.debug("LLM Gateway stream request", {
@@ -226,15 +234,16 @@ var LLMGateway = class {
226
234
  this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
227
235
  const gatewayError = this.handleError(error);
228
236
  if (llmSpan) {
237
+ const errorEndData = {
238
+ error: {
239
+ message: gatewayError.message,
240
+ code: gatewayError.code,
241
+ status: gatewayError.status
242
+ }
243
+ };
229
244
  await llmSpan.end({
230
245
  status: "error",
231
- data: {
232
- error: {
233
- message: gatewayError.message,
234
- code: gatewayError.code,
235
- status: gatewayError.status
236
- }
237
- }
246
+ data: errorEndData
238
247
  });
239
248
  }
240
249
  throw gatewayError;
@@ -243,6 +252,8 @@ var LLMGateway = class {
243
252
  let finalFinishReason;
244
253
  let toolCallsCount = 0;
245
254
  let outputPreview = "";
255
+ let finalContent = "";
256
+ const toolCallsAccumulator = /* @__PURE__ */ new Map();
246
257
  try {
247
258
  for await (const chunk of stream) {
248
259
  if (chunk.usage) {
@@ -265,6 +276,7 @@ var LLMGateway = class {
265
276
  const delta = choice.delta;
266
277
  if (!delta) continue;
267
278
  if (delta.content) {
279
+ finalContent += delta.content;
268
280
  if (outputPreview.length < 200) {
269
281
  outputPreview += delta.content.slice(0, 200 - outputPreview.length);
270
282
  }
@@ -272,49 +284,72 @@ var LLMGateway = class {
272
284
  }
273
285
  if (delta.tool_calls) {
274
286
  for (const tc of delta.tool_calls) {
287
+ const idx = tc.index;
275
288
  if (tc.id && tc.function?.name) {
276
289
  toolCallsCount++;
290
+ toolCallsAccumulator.set(idx, { id: tc.id, name: tc.function.name, arguments: "" });
277
291
  yield {
278
292
  type: "tool_call_start",
279
- index: tc.index,
293
+ index: idx,
280
294
  id: tc.id,
281
295
  name: tc.function.name
282
296
  };
283
297
  }
284
298
  if (tc.function?.arguments) {
299
+ const existing = toolCallsAccumulator.get(idx);
300
+ if (existing) {
301
+ existing.arguments += tc.function.arguments;
302
+ }
285
303
  yield {
286
304
  type: "tool_call_delta",
287
- index: tc.index,
305
+ index: idx,
288
306
  args: tc.function.arguments
289
307
  };
290
308
  }
291
309
  }
292
310
  }
293
311
  }
312
+ const toolCalls = Array.from(toolCallsAccumulator.values()).map((tc) => ({
313
+ id: tc.id,
314
+ type: "function",
315
+ function: { name: tc.name, arguments: tc.arguments }
316
+ }));
317
+ const choices = [{
318
+ index: 0,
319
+ message: {
320
+ role: "assistant",
321
+ content: finalContent || null,
322
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
323
+ },
324
+ finish_reason: finalFinishReason
325
+ }];
294
326
  if (llmSpan) {
327
+ const endData = {
328
+ usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
329
+ finishReason: finalFinishReason ?? "stop",
330
+ toolCallsCount,
331
+ outputPreview,
332
+ choices
333
+ };
295
334
  await llmSpan.end({
296
335
  status: "success",
297
- data: {
298
- usage: finalUsage,
299
- finishReason: finalFinishReason,
300
- toolCallsCount,
301
- outputPreview
302
- }
336
+ data: endData
303
337
  });
304
338
  }
305
339
  } catch (error) {
306
340
  this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
307
341
  const gatewayError = this.handleError(error);
308
342
  if (llmSpan) {
343
+ const errorEndData = {
344
+ error: {
345
+ message: gatewayError.message,
346
+ code: gatewayError.code,
347
+ status: gatewayError.status
348
+ }
349
+ };
309
350
  await llmSpan.end({
310
351
  status: "error",
311
- data: {
312
- error: {
313
- message: gatewayError.message,
314
- code: gatewayError.code,
315
- status: gatewayError.status
316
- }
317
- }
352
+ data: errorEndData
318
353
  });
319
354
  }
320
355
  throw gatewayError;
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 } 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 // 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 // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls\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: messagesPreview,\n tools: toolsPreview,\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: 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 // 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 // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls\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: messagesPreview,\n tools: toolsPreview,\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,\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 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"]}
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);
@@ -51,19 +52,22 @@ var LLMGateway = class {
51
52
  name: t.function.name,
52
53
  description: t.function.description?.slice(0, 200)
53
54
  }));
55
+ const { responseFormat, ...llmConfig } = mergedOptions ?? {};
56
+ const startData = {
57
+ provider: this.providerName,
58
+ model: this.modelName,
59
+ messagesCount: messages.length,
60
+ toolsCount: tools?.length ?? 0,
61
+ llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
62
+ responseFormat,
63
+ messages: messagesPreview,
64
+ tools: toolsPreview
65
+ };
54
66
  return Tracing.withSpan(
55
67
  {
56
- kind: "llm",
68
+ kind: SpanType.llm,
57
69
  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
- }
70
+ data: startData
67
71
  },
68
72
  async (llmSpan) => {
69
73
  const headers = this.buildGatewayHeaders();
@@ -98,18 +102,18 @@ var LLMGateway = class {
98
102
  const usage = this.mapUsage(response.usage);
99
103
  const finishReason = this.mapFinishReason(choice.finish_reason);
100
104
  const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);
105
+ const endData = {
106
+ usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
107
+ finishReason: finishReason ?? "stop",
108
+ toolCallsCount: message.tool_calls?.length ?? 0,
109
+ choices: response.choices,
110
+ model: enrichment.modelActual,
111
+ fallbackUsed: enrichment.fallbackUsed,
112
+ cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : void 0
113
+ };
101
114
  await llmSpan.end({
102
115
  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
- }
116
+ data: endData
113
117
  });
114
118
  return {
115
119
  content: message.content || "",
@@ -127,15 +131,16 @@ var LLMGateway = class {
127
131
  } catch (error) {
128
132
  this.logger.error("LLM Gateway request failed", error, void 0, void 0);
129
133
  const gatewayError = this.handleError(error);
134
+ const errorEndData = {
135
+ error: {
136
+ message: gatewayError.message,
137
+ code: gatewayError.code,
138
+ status: gatewayError.status
139
+ }
140
+ };
130
141
  await llmSpan.end({
131
142
  status: "error",
132
- data: {
133
- error: {
134
- message: gatewayError.message,
135
- code: gatewayError.code,
136
- status: gatewayError.status
137
- }
138
- }
143
+ data: errorEndData
139
144
  });
140
145
  throw gatewayError;
141
146
  }
@@ -153,18 +158,21 @@ var LLMGateway = class {
153
158
  name: t.function.name,
154
159
  description: t.function.description?.slice(0, 200)
155
160
  }));
161
+ const { responseFormat, ...llmConfig } = mergedOptions ?? {};
162
+ const startData = {
163
+ provider: this.providerName,
164
+ model: this.modelName,
165
+ messagesCount: messages.length,
166
+ toolsCount: tools?.length ?? 0,
167
+ llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
168
+ responseFormat,
169
+ messages: messagesPreview,
170
+ tools: toolsPreview
171
+ };
156
172
  const llmSpan = await Tracing.startSpan({
157
- kind: "llm",
173
+ kind: SpanType.llm,
158
174
  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
- }
175
+ data: startData
168
176
  });
169
177
  const headers = this.buildGatewayHeaders();
170
178
  this.logger.debug("LLM Gateway stream request", {
@@ -195,15 +203,16 @@ var LLMGateway = class {
195
203
  this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
196
204
  const gatewayError = this.handleError(error);
197
205
  if (llmSpan) {
206
+ const errorEndData = {
207
+ error: {
208
+ message: gatewayError.message,
209
+ code: gatewayError.code,
210
+ status: gatewayError.status
211
+ }
212
+ };
198
213
  await llmSpan.end({
199
214
  status: "error",
200
- data: {
201
- error: {
202
- message: gatewayError.message,
203
- code: gatewayError.code,
204
- status: gatewayError.status
205
- }
206
- }
215
+ data: errorEndData
207
216
  });
208
217
  }
209
218
  throw gatewayError;
@@ -212,6 +221,8 @@ var LLMGateway = class {
212
221
  let finalFinishReason;
213
222
  let toolCallsCount = 0;
214
223
  let outputPreview = "";
224
+ let finalContent = "";
225
+ const toolCallsAccumulator = /* @__PURE__ */ new Map();
215
226
  try {
216
227
  for await (const chunk of stream) {
217
228
  if (chunk.usage) {
@@ -234,6 +245,7 @@ var LLMGateway = class {
234
245
  const delta = choice.delta;
235
246
  if (!delta) continue;
236
247
  if (delta.content) {
248
+ finalContent += delta.content;
237
249
  if (outputPreview.length < 200) {
238
250
  outputPreview += delta.content.slice(0, 200 - outputPreview.length);
239
251
  }
@@ -241,49 +253,72 @@ var LLMGateway = class {
241
253
  }
242
254
  if (delta.tool_calls) {
243
255
  for (const tc of delta.tool_calls) {
256
+ const idx = tc.index;
244
257
  if (tc.id && tc.function?.name) {
245
258
  toolCallsCount++;
259
+ toolCallsAccumulator.set(idx, { id: tc.id, name: tc.function.name, arguments: "" });
246
260
  yield {
247
261
  type: "tool_call_start",
248
- index: tc.index,
262
+ index: idx,
249
263
  id: tc.id,
250
264
  name: tc.function.name
251
265
  };
252
266
  }
253
267
  if (tc.function?.arguments) {
268
+ const existing = toolCallsAccumulator.get(idx);
269
+ if (existing) {
270
+ existing.arguments += tc.function.arguments;
271
+ }
254
272
  yield {
255
273
  type: "tool_call_delta",
256
- index: tc.index,
274
+ index: idx,
257
275
  args: tc.function.arguments
258
276
  };
259
277
  }
260
278
  }
261
279
  }
262
280
  }
281
+ const toolCalls = Array.from(toolCallsAccumulator.values()).map((tc) => ({
282
+ id: tc.id,
283
+ type: "function",
284
+ function: { name: tc.name, arguments: tc.arguments }
285
+ }));
286
+ const choices = [{
287
+ index: 0,
288
+ message: {
289
+ role: "assistant",
290
+ content: finalContent || null,
291
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
292
+ },
293
+ finish_reason: finalFinishReason
294
+ }];
263
295
  if (llmSpan) {
296
+ const endData = {
297
+ usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
298
+ finishReason: finalFinishReason ?? "stop",
299
+ toolCallsCount,
300
+ outputPreview,
301
+ choices
302
+ };
264
303
  await llmSpan.end({
265
304
  status: "success",
266
- data: {
267
- usage: finalUsage,
268
- finishReason: finalFinishReason,
269
- toolCallsCount,
270
- outputPreview
271
- }
305
+ data: endData
272
306
  });
273
307
  }
274
308
  } catch (error) {
275
309
  this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
276
310
  const gatewayError = this.handleError(error);
277
311
  if (llmSpan) {
312
+ const errorEndData = {
313
+ error: {
314
+ message: gatewayError.message,
315
+ code: gatewayError.code,
316
+ status: gatewayError.status
317
+ }
318
+ };
278
319
  await llmSpan.end({
279
320
  status: "error",
280
- data: {
281
- error: {
282
- message: gatewayError.message,
283
- code: gatewayError.code,
284
- status: gatewayError.status
285
- }
286
- }
321
+ data: errorEndData
287
322
  });
288
323
  }
289
324
  throw gatewayError;
@@ -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 } 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 // 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 // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls\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: messagesPreview,\n tools: toolsPreview,\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: 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 // 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 // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls\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: messagesPreview,\n tools: toolsPreview,\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,\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 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":[]}
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.3",
4
4
  "description": "NebulaOS LLM Gateway provider - OpenAI-compatible chat completions",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -27,7 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "openai": "^4.52.7",
30
- "@nebulaos/core": "^0.2.4"
30
+ "@nebulaos/core": "^0.2.5",
31
+ "@nebulaos/types": "^0.1.4"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@types/node": "^20.0.0",