@nebulaos/llm-gateway 0.2.0 → 0.2.1
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 +2 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -82,9 +82,7 @@ var LLMGateway = class {
|
|
|
82
82
|
messagesCount: messages.length,
|
|
83
83
|
toolsCount: tools?.length ?? 0,
|
|
84
84
|
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
85
|
-
responseFormat
|
|
86
|
-
messages,
|
|
87
|
-
tools
|
|
85
|
+
responseFormat
|
|
88
86
|
};
|
|
89
87
|
return import_core.Tracing.withSpan(
|
|
90
88
|
{
|
|
@@ -180,9 +178,7 @@ var LLMGateway = class {
|
|
|
180
178
|
messagesCount: messages.length,
|
|
181
179
|
toolsCount: tools?.length ?? 0,
|
|
182
180
|
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
183
|
-
responseFormat
|
|
184
|
-
messages,
|
|
185
|
-
tools
|
|
181
|
+
responseFormat
|
|
186
182
|
};
|
|
187
183
|
const llmSpan = import_core.Tracing.startSpan({
|
|
188
184
|
kind: import_types.SpanType.llm_wrapper,
|
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\";\nimport { SpanType, type LLMSpanStartData, type LLMSpanEndData, type LLMResponseChoice } from \"@nebulaos/types\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n return Tracing.withSpan(\n {\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n // Typed span end data for LLM calls\n const endData: LLMSpanEndData = {\n usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finishReason ?? \"stop\",\n toolCallsCount: message.tool_calls?.length ?? 0,\n choices: this.sanitizeChoices(response.choices),\n model: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : undefined,\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n // Start span manually for streaming (async generators can't use withSpan directly).\n // Tracing.startSpan is synchronous (no await) under ADR-0002.\n const llmSpan = Tracing.startSpan({\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n });\n\n // Streaming + span context: we run the full HTTP + iteration loop inside a\n // `Tracing.runWithSpan(llmSpan, ...)` scope — that is the only way to keep\n // the span's TracingContext active across every `await` inside the stream\n // (outgoing fetch, SSE chunk iteration, nested calls that might be added\n // later by wrappers). Because `runWithSpan` takes `() => Promise<T>` and\n // our public API is an async generator, we bridge the two with a classic\n // producer/consumer queue. The producer task runs fully inside the span\n // context; the consumer (this async generator) just drains the queue and\n // yields chunks as they arrive — preserving real-time streaming semantics\n // while still guaranteeing that anything happening under `runWithSpan`\n // sees the correct `Tracing.getContext()`.\n type QueueItem =\n | { kind: \"chunk\"; value: StreamChunk }\n | { kind: \"done\" }\n | { kind: \"error\"; error: unknown };\n\n const queue: QueueItem[] = [];\n let pendingResolve: ((item: QueueItem) => void) | null = null;\n\n // Abort controller propagates early-exit from the consumer to the OpenAI\n // HTTP request and to the SSE iteration inside the producer. If the caller\n // breaks out of the generator (timeout, disconnect, or just wants the\n // first chunk), we cancel the upstream LLM call instead of silently\n // draining the whole response.\n const abortController = new AbortController();\n let consumerAborted = false;\n\n const push = (item: QueueItem): void => {\n if (pendingResolve) {\n const resolve = pendingResolve;\n pendingResolve = null;\n resolve(item);\n } else {\n queue.push(item);\n }\n };\n\n const pull = (): Promise<QueueItem> => {\n if (queue.length > 0) return Promise.resolve(queue.shift() as QueueItem);\n return new Promise<QueueItem>((resolve) => {\n pendingResolve = resolve;\n });\n };\n\n // Fire the producer. We intentionally do NOT await it here — the consumer\n // below drains the queue and observes completion via the \"done\" / \"error\"\n // sentinels. Any rejection is captured and turned into a queue item so the\n // async generator surface stays clean.\n const producer = Tracing.runWithSpan(llmSpan, async () => {\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, signal: abortController.signal },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n throw this.handleError(error);\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 // Fast-path exit when the consumer aborted — avoid reading the rest\n // of the upstream response. OpenAI's stream iterator checks the\n // signal too, but we double-check here in case a chunk landed in\n // the internal buffer right before the abort.\n if (abortController.signal.aborted) break;\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n push({\n kind: \"chunk\",\n value: { type: \"finish\", reason: \"stop\", 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 push({\n kind: \"chunk\",\n value: { type: \"finish\", 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 push({ kind: \"chunk\", value: { 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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_start\",\n index: idx,\n id: tc.id,\n name: tc.function.name,\n },\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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_delta\",\n index: idx,\n args: tc.function.arguments,\n },\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 const endData: LLMSpanEndData = {\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finalFinishReason ?? \"stop\",\n toolCallsCount,\n outputPreview,\n choices: this.sanitizeChoices(choices),\n };\n\n await llmSpan.end({ status: \"success\", data: endData });\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n throw this.handleError(error);\n }\n }).then(\n () => push({ kind: \"done\" }),\n (error) => push({ kind: \"error\", error }),\n );\n\n // Consumer: drain the queue until the producer signals done or error.\n let completedNormally = false;\n try {\n while (true) {\n const item = await pull();\n if (item.kind === \"chunk\") {\n yield item.value;\n } else if (item.kind === \"done\") {\n completedNormally = true;\n return;\n } else {\n // On error, make sure the span is closed before re-throwing. The\n // producer only closes the span on the success path — errors bubble\n // up here so we own the failure-close.\n completedNormally = true;\n const gatewayError =\n item.error instanceof LLMGatewayError ? item.error : this.handleError(item.error);\n\n if (!llmSpan.isEnded) {\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n await llmSpan.end({ status: \"error\", data: errorEndData });\n }\n\n throw gatewayError;\n }\n }\n } finally {\n // If the consumer exits without seeing `done`/`error` (break, throw,\n // `return` from the caller's `for await`), abort the upstream LLM call\n // so we stop burning tokens and memory. The producer will bail out\n // (either from the OpenAI client rejecting or from the signal check\n // inside the SSE loop), close the span as `cancelled`, and resolve the\n // `producer` Promise — so awaiting it below is still safe.\n if (!completedNormally) {\n consumerAborted = true;\n abortController.abort();\n if (!llmSpan.isEnded) {\n try {\n await llmSpan.end({ status: \"cancelled\" });\n } catch {\n // span end errors are non-fatal here\n }\n }\n }\n\n // Make sure we don't leave the producer dangling. `producer` is a\n // Promise<void> that always resolves (errors are captured into the\n // queue), so awaiting is safe. Under normal completion it's already\n // done; under abort it resolves shortly after the signal fires.\n try {\n await producer;\n } catch {\n // Producer rejections are already routed through the queue; an\n // abort-triggered rejection landing here is expected and ignorable.\n }\n void consumerAborted;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n // Check if error message suggests permission/token issues\n const lowerMsg = originalMessage.toLowerCase();\n const isPermissionRelated =\n lowerMsg.includes(\"unauthorized\") ||\n lowerMsg.includes(\"permission\") ||\n lowerMsg.includes(\"access denied\") ||\n lowerMsg.includes(\"not allowed\") ||\n lowerMsg.includes(\"forbidden\") ||\n lowerMsg.includes(\"invalid api key\") ||\n lowerMsg.includes(\"api key\");\n\n const permissionHint = isPermissionRelated\n ? `This error may indicate a token/permission issue. `\n : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. ` +\n `Common cause: using a route without the corresponding token permission. `;\n\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `${permissionHint}` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n /**\n * Builds the outbound headers for a call to the NebulaOS LLM Gateway.\n *\n * Under ADR-0002, correlation with the NebulaOS backend is carried on\n * domain-scoped `x-nebula-*` headers that APMs of the host process do not\n * touch. The standard W3C `traceparent` is still emitted (same trace-id /\n * span-id) for compatibility with caches, proxies, and log correlation —\n * but the backend treats `x-nebula-traceparent` as the authoritative source.\n * If a host APM rewrites `traceparent` on egress, NebulaOS correlation is\n * unaffected.\n *\n * Legacy headers (`x-request-id`, `x-execution-id`, `x-resource-name`) are\n * no longer emitted; `nebulaos-cloud` accepts both sets temporarily via a\n * Phase 1B fallback, but new SDK releases emit only the `x-nebula-*` set\n * plus the compat `traceparent`.\n */\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-nebula-request-id\": randomUUID(),\n };\n\n const ctx = Tracing.getContext();\n\n // Trace context: prefer the active NebulaOS context; fall back to a fresh\n // root context so downstream backend logs still have a traceId to group by.\n const traceId = ctx?.traceId ?? randomBytes(16).toString(\"hex\");\n const spanId = ctx?.spanId ?? randomBytes(8).toString(\"hex\");\n const traceparent = `00-${traceId}-${spanId}-01`;\n headers[\"x-nebula-traceparent\"] = traceparent;\n headers.traceparent = traceparent; // W3C compat, non-authoritative\n\n // Execution id: Tracing context first, then the legacy ExecutionContext ALS\n // as fallback for call-sites that still seed it directly (pre-Tracing flows).\n const executionId = ctx?.executionId ?? ExecutionContext.getOrUndefined()?.executionId;\n if (executionId) {\n headers[\"x-nebula-execution-id\"] = executionId;\n }\n\n if (ctx?.resourceName) {\n headers[\"x-nebula-resource-name\"] = ctx.resourceName;\n }\n if (ctx?.resourceType) {\n headers[\"x-nebula-resource-type\"] = ctx.resourceType;\n }\n if (ctx?.workspaceId) {\n headers[\"x-nebula-workspace-id\"] = ctx.workspaceId;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n // Check if this is a UnifiedToolMessage (has toolOutputs)\n const toolOutputs = (m as any).toolOutputs as Array<{\n toolCallId: string;\n status: 'pending' | 'success' | 'error';\n content: string | null;\n error?: string;\n }> | undefined;\n\n if (toolOutputs && toolOutputs.length > 0) {\n // Expand UnifiedToolMessage into assistant + tool messages\n const result: OpenAIClient.Chat.ChatCompletionMessageParam[] = [];\n\n // 1. Assistant message with tool_calls\n result.push({\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n\n // 2. Tool messages for each completed toolOutput\n for (const output of toolOutputs) {\n if (output.status === 'pending') {\n // Skip pending outputs - tool hasn't finished yet\n continue;\n }\n\n result.push({\n role: \"tool\",\n tool_call_id: output.toolCallId,\n content: output.status === 'error'\n ? JSON.stringify({ error: output.error || 'Unknown error' })\n : (output.content || ''),\n });\n }\n\n return result;\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { data, mediaType, filename } = part;\n\n const isImage = mediaType.startsWith(\"image/\");\n const isPdf = mediaType === \"application/pdf\";\n const isText = mediaType.startsWith(\"text/\");\n\n if (!isImage && !isPdf && !isText) {\n throw new Error(`LLM Gateway: file mediaType '${mediaType}' is not supported yet`);\n }\n\n // Resolve data URI/URL\n let url: string;\n if (data instanceof Uint8Array) {\n const base64 = Buffer.from(data).toString(\"base64\");\n url = `data:${mediaType};base64,${base64}`;\n } else if (typeof data === \"string\") {\n if (data.startsWith(\"data:\") || data.includes(\"://\")) {\n url = data;\n } else {\n url = `data:${mediaType};base64,${data}`;\n }\n } else {\n throw new Error(`LLM Gateway: unsupported file data type`);\n }\n\n if (isImage) {\n return { type: \"image_url\", image_url: { url } };\n }\n\n // PDF and text files use the OpenAI-compatible `file` content part\n return {\n type: \"file\",\n file: {\n file_data: url,\n filename: filename ?? (isPdf ? \"document.pdf\" : \"document.txt\"),\n },\n };\n }\n\n throw new Error(`Unsupported content type: ${(part as any).type}`);\n }\n\n /**\n * Sanitize choices for observability storage.\n *\n * Removes provider-specific fields that are not useful for tracing/debugging\n * and that bloat the attribute size (causing truncation). Specifically:\n * - Gemini's thought_signature (huge base64 strings, 2000+ chars each)\n * - thinking_blocks (already captured via reasoningTokens)\n */\n private sanitizeChoices(choices: unknown[] | undefined): LLMResponseChoice[] {\n if (!choices) return [];\n\n return choices.map((choice) => {\n if (!choice || typeof choice !== \"object\") return choice as LLMResponseChoice;\n\n const sanitized = { ...choice } as Record<string, unknown>;\n\n // Remove provider_specific_fields.thought_signature (Gemini thinking signatures)\n if (sanitized.message && typeof sanitized.message === \"object\") {\n const message = { ...sanitized.message } as Record<string, unknown>;\n\n // Remove thinking_blocks (captured via reasoningTokens in usage)\n if (\"thinking_blocks\" in message) {\n delete message.thinking_blocks;\n }\n\n // Remove provider_specific_fields (contains thought_signatures)\n if (\"provider_specific_fields\" in message) {\n delete message.provider_specific_fields;\n }\n\n // Sanitize tool_calls: remove thought_signature from each tool call\n if (message.tool_calls && Array.isArray(message.tool_calls)) {\n message.tool_calls = message.tool_calls.map((tc: Record<string, unknown>) => {\n if (!tc || typeof tc !== \"object\") return tc;\n const sanitizedTc = { ...tc };\n // Remove provider_specific_fields from tool call (contains thought_signature)\n if (\"provider_specific_fields\" in sanitizedTc) {\n delete sanitizedTc.provider_specific_fields;\n }\n return sanitizedTc;\n });\n }\n\n sanitized.message = message;\n }\n\n return sanitized as LLMResponseChoice;\n });\n }\n}\n"],"mappings":"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","names":["OpenAIClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\nimport { SpanType, type LLMSpanStartData, type LLMSpanEndData, type LLMResponseChoice } from \"@nebulaos/types\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\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 };\n\n return Tracing.withSpan(\n {\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n // Typed span end data for LLM calls\n const endData: LLMSpanEndData = {\n usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finishReason ?? \"stop\",\n toolCallsCount: message.tool_calls?.length ?? 0,\n choices: this.sanitizeChoices(response.choices),\n model: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : undefined,\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\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 };\n\n // Start span manually for streaming (async generators can't use withSpan directly).\n // Tracing.startSpan is synchronous (no await) under ADR-0002.\n const llmSpan = Tracing.startSpan({\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n });\n\n // Streaming + span context: we run the full HTTP + iteration loop inside a\n // `Tracing.runWithSpan(llmSpan, ...)` scope — that is the only way to keep\n // the span's TracingContext active across every `await` inside the stream\n // (outgoing fetch, SSE chunk iteration, nested calls that might be added\n // later by wrappers). Because `runWithSpan` takes `() => Promise<T>` and\n // our public API is an async generator, we bridge the two with a classic\n // producer/consumer queue. The producer task runs fully inside the span\n // context; the consumer (this async generator) just drains the queue and\n // yields chunks as they arrive — preserving real-time streaming semantics\n // while still guaranteeing that anything happening under `runWithSpan`\n // sees the correct `Tracing.getContext()`.\n type QueueItem =\n | { kind: \"chunk\"; value: StreamChunk }\n | { kind: \"done\" }\n | { kind: \"error\"; error: unknown };\n\n const queue: QueueItem[] = [];\n let pendingResolve: ((item: QueueItem) => void) | null = null;\n\n // Abort controller propagates early-exit from the consumer to the OpenAI\n // HTTP request and to the SSE iteration inside the producer. If the caller\n // breaks out of the generator (timeout, disconnect, or just wants the\n // first chunk), we cancel the upstream LLM call instead of silently\n // draining the whole response.\n const abortController = new AbortController();\n let consumerAborted = false;\n\n const push = (item: QueueItem): void => {\n if (pendingResolve) {\n const resolve = pendingResolve;\n pendingResolve = null;\n resolve(item);\n } else {\n queue.push(item);\n }\n };\n\n const pull = (): Promise<QueueItem> => {\n if (queue.length > 0) return Promise.resolve(queue.shift() as QueueItem);\n return new Promise<QueueItem>((resolve) => {\n pendingResolve = resolve;\n });\n };\n\n // Fire the producer. We intentionally do NOT await it here — the consumer\n // below drains the queue and observes completion via the \"done\" / \"error\"\n // sentinels. Any rejection is captured and turned into a queue item so the\n // async generator surface stays clean.\n const producer = Tracing.runWithSpan(llmSpan, async () => {\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, signal: abortController.signal },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n throw this.handleError(error);\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 // Fast-path exit when the consumer aborted — avoid reading the rest\n // of the upstream response. OpenAI's stream iterator checks the\n // signal too, but we double-check here in case a chunk landed in\n // the internal buffer right before the abort.\n if (abortController.signal.aborted) break;\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n push({\n kind: \"chunk\",\n value: { type: \"finish\", reason: \"stop\", 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 push({\n kind: \"chunk\",\n value: { type: \"finish\", 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 push({ kind: \"chunk\", value: { 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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_start\",\n index: idx,\n id: tc.id,\n name: tc.function.name,\n },\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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_delta\",\n index: idx,\n args: tc.function.arguments,\n },\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 const endData: LLMSpanEndData = {\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finalFinishReason ?? \"stop\",\n toolCallsCount,\n outputPreview,\n choices: this.sanitizeChoices(choices),\n };\n\n await llmSpan.end({ status: \"success\", data: endData });\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n throw this.handleError(error);\n }\n }).then(\n () => push({ kind: \"done\" }),\n (error) => push({ kind: \"error\", error }),\n );\n\n // Consumer: drain the queue until the producer signals done or error.\n let completedNormally = false;\n try {\n while (true) {\n const item = await pull();\n if (item.kind === \"chunk\") {\n yield item.value;\n } else if (item.kind === \"done\") {\n completedNormally = true;\n return;\n } else {\n // On error, make sure the span is closed before re-throwing. The\n // producer only closes the span on the success path — errors bubble\n // up here so we own the failure-close.\n completedNormally = true;\n const gatewayError =\n item.error instanceof LLMGatewayError ? item.error : this.handleError(item.error);\n\n if (!llmSpan.isEnded) {\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n await llmSpan.end({ status: \"error\", data: errorEndData });\n }\n\n throw gatewayError;\n }\n }\n } finally {\n // If the consumer exits without seeing `done`/`error` (break, throw,\n // `return` from the caller's `for await`), abort the upstream LLM call\n // so we stop burning tokens and memory. The producer will bail out\n // (either from the OpenAI client rejecting or from the signal check\n // inside the SSE loop), close the span as `cancelled`, and resolve the\n // `producer` Promise — so awaiting it below is still safe.\n if (!completedNormally) {\n consumerAborted = true;\n abortController.abort();\n if (!llmSpan.isEnded) {\n try {\n await llmSpan.end({ status: \"cancelled\" });\n } catch {\n // span end errors are non-fatal here\n }\n }\n }\n\n // Make sure we don't leave the producer dangling. `producer` is a\n // Promise<void> that always resolves (errors are captured into the\n // queue), so awaiting is safe. Under normal completion it's already\n // done; under abort it resolves shortly after the signal fires.\n try {\n await producer;\n } catch {\n // Producer rejections are already routed through the queue; an\n // abort-triggered rejection landing here is expected and ignorable.\n }\n void consumerAborted;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n // Check if error message suggests permission/token issues\n const lowerMsg = originalMessage.toLowerCase();\n const isPermissionRelated =\n lowerMsg.includes(\"unauthorized\") ||\n lowerMsg.includes(\"permission\") ||\n lowerMsg.includes(\"access denied\") ||\n lowerMsg.includes(\"not allowed\") ||\n lowerMsg.includes(\"forbidden\") ||\n lowerMsg.includes(\"invalid api key\") ||\n lowerMsg.includes(\"api key\");\n\n const permissionHint = isPermissionRelated\n ? `This error may indicate a token/permission issue. `\n : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. ` +\n `Common cause: using a route without the corresponding token permission. `;\n\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `${permissionHint}` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n /**\n * Builds the outbound headers for a call to the NebulaOS LLM Gateway.\n *\n * Under ADR-0002, correlation with the NebulaOS backend is carried on\n * domain-scoped `x-nebula-*` headers that APMs of the host process do not\n * touch. The standard W3C `traceparent` is still emitted (same trace-id /\n * span-id) for compatibility with caches, proxies, and log correlation —\n * but the backend treats `x-nebula-traceparent` as the authoritative source.\n * If a host APM rewrites `traceparent` on egress, NebulaOS correlation is\n * unaffected.\n *\n * Legacy headers (`x-request-id`, `x-execution-id`, `x-resource-name`) are\n * no longer emitted; `nebulaos-cloud` accepts both sets temporarily via a\n * Phase 1B fallback, but new SDK releases emit only the `x-nebula-*` set\n * plus the compat `traceparent`.\n */\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-nebula-request-id\": randomUUID(),\n };\n\n const ctx = Tracing.getContext();\n\n // Trace context: prefer the active NebulaOS context; fall back to a fresh\n // root context so downstream backend logs still have a traceId to group by.\n const traceId = ctx?.traceId ?? randomBytes(16).toString(\"hex\");\n const spanId = ctx?.spanId ?? randomBytes(8).toString(\"hex\");\n const traceparent = `00-${traceId}-${spanId}-01`;\n headers[\"x-nebula-traceparent\"] = traceparent;\n headers.traceparent = traceparent; // W3C compat, non-authoritative\n\n // Execution id: Tracing context first, then the legacy ExecutionContext ALS\n // as fallback for call-sites that still seed it directly (pre-Tracing flows).\n const executionId = ctx?.executionId ?? ExecutionContext.getOrUndefined()?.executionId;\n if (executionId) {\n headers[\"x-nebula-execution-id\"] = executionId;\n }\n\n if (ctx?.resourceName) {\n headers[\"x-nebula-resource-name\"] = ctx.resourceName;\n }\n if (ctx?.resourceType) {\n headers[\"x-nebula-resource-type\"] = ctx.resourceType;\n }\n if (ctx?.workspaceId) {\n headers[\"x-nebula-workspace-id\"] = ctx.workspaceId;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n // Check if this is a UnifiedToolMessage (has toolOutputs)\n const toolOutputs = (m as any).toolOutputs as Array<{\n toolCallId: string;\n status: 'pending' | 'success' | 'error';\n content: string | null;\n error?: string;\n }> | undefined;\n\n if (toolOutputs && toolOutputs.length > 0) {\n // Expand UnifiedToolMessage into assistant + tool messages\n const result: OpenAIClient.Chat.ChatCompletionMessageParam[] = [];\n\n // 1. Assistant message with tool_calls\n result.push({\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n\n // 2. Tool messages for each completed toolOutput\n for (const output of toolOutputs) {\n if (output.status === 'pending') {\n // Skip pending outputs - tool hasn't finished yet\n continue;\n }\n\n result.push({\n role: \"tool\",\n tool_call_id: output.toolCallId,\n content: output.status === 'error'\n ? JSON.stringify({ error: output.error || 'Unknown error' })\n : (output.content || ''),\n });\n }\n\n return result;\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { data, mediaType, filename } = part;\n\n const isImage = mediaType.startsWith(\"image/\");\n const isPdf = mediaType === \"application/pdf\";\n const isText = mediaType.startsWith(\"text/\");\n\n if (!isImage && !isPdf && !isText) {\n throw new Error(`LLM Gateway: file mediaType '${mediaType}' is not supported yet`);\n }\n\n // Resolve data URI/URL\n let url: string;\n if (data instanceof Uint8Array) {\n const base64 = Buffer.from(data).toString(\"base64\");\n url = `data:${mediaType};base64,${base64}`;\n } else if (typeof data === \"string\") {\n if (data.startsWith(\"data:\") || data.includes(\"://\")) {\n url = data;\n } else {\n url = `data:${mediaType};base64,${data}`;\n }\n } else {\n throw new Error(`LLM Gateway: unsupported file data type`);\n }\n\n if (isImage) {\n return { type: \"image_url\", image_url: { url } };\n }\n\n // PDF and text files use the OpenAI-compatible `file` content part\n return {\n type: \"file\",\n file: {\n file_data: url,\n filename: filename ?? (isPdf ? \"document.pdf\" : \"document.txt\"),\n },\n };\n }\n\n throw new Error(`Unsupported content type: ${(part as any).type}`);\n }\n\n /**\n * Sanitize choices for observability storage.\n *\n * Removes provider-specific fields that are not useful for tracing/debugging\n * and that bloat the attribute size (causing truncation). Specifically:\n * - Gemini's thought_signature (huge base64 strings, 2000+ chars each)\n * - thinking_blocks (already captured via reasoningTokens)\n */\n private sanitizeChoices(choices: unknown[] | undefined): LLMResponseChoice[] {\n if (!choices) return [];\n\n return choices.map((choice) => {\n if (!choice || typeof choice !== \"object\") return choice as LLMResponseChoice;\n\n const sanitized = { ...choice } as Record<string, unknown>;\n\n // Remove provider_specific_fields.thought_signature (Gemini thinking signatures)\n if (sanitized.message && typeof sanitized.message === \"object\") {\n const message = { ...sanitized.message } as Record<string, unknown>;\n\n // Remove thinking_blocks (captured via reasoningTokens in usage)\n if (\"thinking_blocks\" in message) {\n delete message.thinking_blocks;\n }\n\n // Remove provider_specific_fields (contains thought_signatures)\n if (\"provider_specific_fields\" in message) {\n delete message.provider_specific_fields;\n }\n\n // Sanitize tool_calls: remove thought_signature from each tool call\n if (message.tool_calls && Array.isArray(message.tool_calls)) {\n message.tool_calls = message.tool_calls.map((tc: Record<string, unknown>) => {\n if (!tc || typeof tc !== \"object\") return tc;\n const sanitizedTc = { ...tc };\n // Remove provider_specific_fields from tool call (contains thought_signature)\n if (\"provider_specific_fields\" in sanitizedTc) {\n delete sanitizedTc.provider_specific_fields;\n }\n return sanitizedTc;\n });\n }\n\n sanitized.message = message;\n }\n\n return sanitized as LLMResponseChoice;\n });\n }\n}\n"],"mappings":"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","names":["OpenAIClient"]}
|
package/dist/index.mjs
CHANGED
|
@@ -51,9 +51,7 @@ var LLMGateway = class {
|
|
|
51
51
|
messagesCount: messages.length,
|
|
52
52
|
toolsCount: tools?.length ?? 0,
|
|
53
53
|
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
54
|
-
responseFormat
|
|
55
|
-
messages,
|
|
56
|
-
tools
|
|
54
|
+
responseFormat
|
|
57
55
|
};
|
|
58
56
|
return Tracing.withSpan(
|
|
59
57
|
{
|
|
@@ -149,9 +147,7 @@ var LLMGateway = class {
|
|
|
149
147
|
messagesCount: messages.length,
|
|
150
148
|
toolsCount: tools?.length ?? 0,
|
|
151
149
|
llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : void 0,
|
|
152
|
-
responseFormat
|
|
153
|
-
messages,
|
|
154
|
-
tools
|
|
150
|
+
responseFormat
|
|
155
151
|
};
|
|
156
152
|
const llmSpan = Tracing.startSpan({
|
|
157
153
|
kind: SpanType.llm_wrapper,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\nimport { SpanType, type LLMSpanStartData, type LLMSpanEndData, type LLMResponseChoice } from \"@nebulaos/types\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n return Tracing.withSpan(\n {\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n // Typed span end data for LLM calls\n const endData: LLMSpanEndData = {\n usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finishReason ?? \"stop\",\n toolCallsCount: message.tool_calls?.length ?? 0,\n choices: this.sanitizeChoices(response.choices),\n model: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : undefined,\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\n // Typed span start data for LLM calls - full messages for enterprise observability\n const startData: LLMSpanStartData = {\n provider: this.providerName,\n model: this.modelName,\n messagesCount: messages.length,\n toolsCount: tools?.length ?? 0,\n llmConfig: Object.keys(llmConfig).length > 0 ? llmConfig : undefined,\n responseFormat,\n messages,\n tools,\n };\n\n // Start span manually for streaming (async generators can't use withSpan directly).\n // Tracing.startSpan is synchronous (no await) under ADR-0002.\n const llmSpan = Tracing.startSpan({\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n });\n\n // Streaming + span context: we run the full HTTP + iteration loop inside a\n // `Tracing.runWithSpan(llmSpan, ...)` scope — that is the only way to keep\n // the span's TracingContext active across every `await` inside the stream\n // (outgoing fetch, SSE chunk iteration, nested calls that might be added\n // later by wrappers). Because `runWithSpan` takes `() => Promise<T>` and\n // our public API is an async generator, we bridge the two with a classic\n // producer/consumer queue. The producer task runs fully inside the span\n // context; the consumer (this async generator) just drains the queue and\n // yields chunks as they arrive — preserving real-time streaming semantics\n // while still guaranteeing that anything happening under `runWithSpan`\n // sees the correct `Tracing.getContext()`.\n type QueueItem =\n | { kind: \"chunk\"; value: StreamChunk }\n | { kind: \"done\" }\n | { kind: \"error\"; error: unknown };\n\n const queue: QueueItem[] = [];\n let pendingResolve: ((item: QueueItem) => void) | null = null;\n\n // Abort controller propagates early-exit from the consumer to the OpenAI\n // HTTP request and to the SSE iteration inside the producer. If the caller\n // breaks out of the generator (timeout, disconnect, or just wants the\n // first chunk), we cancel the upstream LLM call instead of silently\n // draining the whole response.\n const abortController = new AbortController();\n let consumerAborted = false;\n\n const push = (item: QueueItem): void => {\n if (pendingResolve) {\n const resolve = pendingResolve;\n pendingResolve = null;\n resolve(item);\n } else {\n queue.push(item);\n }\n };\n\n const pull = (): Promise<QueueItem> => {\n if (queue.length > 0) return Promise.resolve(queue.shift() as QueueItem);\n return new Promise<QueueItem>((resolve) => {\n pendingResolve = resolve;\n });\n };\n\n // Fire the producer. We intentionally do NOT await it here — the consumer\n // below drains the queue and observes completion via the \"done\" / \"error\"\n // sentinels. Any rejection is captured and turned into a queue item so the\n // async generator surface stays clean.\n const producer = Tracing.runWithSpan(llmSpan, async () => {\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, signal: abortController.signal },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n throw this.handleError(error);\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 // Fast-path exit when the consumer aborted — avoid reading the rest\n // of the upstream response. OpenAI's stream iterator checks the\n // signal too, but we double-check here in case a chunk landed in\n // the internal buffer right before the abort.\n if (abortController.signal.aborted) break;\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n push({\n kind: \"chunk\",\n value: { type: \"finish\", reason: \"stop\", 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 push({\n kind: \"chunk\",\n value: { type: \"finish\", 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 push({ kind: \"chunk\", value: { 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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_start\",\n index: idx,\n id: tc.id,\n name: tc.function.name,\n },\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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_delta\",\n index: idx,\n args: tc.function.arguments,\n },\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 const endData: LLMSpanEndData = {\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finalFinishReason ?? \"stop\",\n toolCallsCount,\n outputPreview,\n choices: this.sanitizeChoices(choices),\n };\n\n await llmSpan.end({ status: \"success\", data: endData });\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n throw this.handleError(error);\n }\n }).then(\n () => push({ kind: \"done\" }),\n (error) => push({ kind: \"error\", error }),\n );\n\n // Consumer: drain the queue until the producer signals done or error.\n let completedNormally = false;\n try {\n while (true) {\n const item = await pull();\n if (item.kind === \"chunk\") {\n yield item.value;\n } else if (item.kind === \"done\") {\n completedNormally = true;\n return;\n } else {\n // On error, make sure the span is closed before re-throwing. The\n // producer only closes the span on the success path — errors bubble\n // up here so we own the failure-close.\n completedNormally = true;\n const gatewayError =\n item.error instanceof LLMGatewayError ? item.error : this.handleError(item.error);\n\n if (!llmSpan.isEnded) {\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n await llmSpan.end({ status: \"error\", data: errorEndData });\n }\n\n throw gatewayError;\n }\n }\n } finally {\n // If the consumer exits without seeing `done`/`error` (break, throw,\n // `return` from the caller's `for await`), abort the upstream LLM call\n // so we stop burning tokens and memory. The producer will bail out\n // (either from the OpenAI client rejecting or from the signal check\n // inside the SSE loop), close the span as `cancelled`, and resolve the\n // `producer` Promise — so awaiting it below is still safe.\n if (!completedNormally) {\n consumerAborted = true;\n abortController.abort();\n if (!llmSpan.isEnded) {\n try {\n await llmSpan.end({ status: \"cancelled\" });\n } catch {\n // span end errors are non-fatal here\n }\n }\n }\n\n // Make sure we don't leave the producer dangling. `producer` is a\n // Promise<void> that always resolves (errors are captured into the\n // queue), so awaiting is safe. Under normal completion it's already\n // done; under abort it resolves shortly after the signal fires.\n try {\n await producer;\n } catch {\n // Producer rejections are already routed through the queue; an\n // abort-triggered rejection landing here is expected and ignorable.\n }\n void consumerAborted;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n // Check if error message suggests permission/token issues\n const lowerMsg = originalMessage.toLowerCase();\n const isPermissionRelated =\n lowerMsg.includes(\"unauthorized\") ||\n lowerMsg.includes(\"permission\") ||\n lowerMsg.includes(\"access denied\") ||\n lowerMsg.includes(\"not allowed\") ||\n lowerMsg.includes(\"forbidden\") ||\n lowerMsg.includes(\"invalid api key\") ||\n lowerMsg.includes(\"api key\");\n\n const permissionHint = isPermissionRelated\n ? `This error may indicate a token/permission issue. `\n : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. ` +\n `Common cause: using a route without the corresponding token permission. `;\n\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `${permissionHint}` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n /**\n * Builds the outbound headers for a call to the NebulaOS LLM Gateway.\n *\n * Under ADR-0002, correlation with the NebulaOS backend is carried on\n * domain-scoped `x-nebula-*` headers that APMs of the host process do not\n * touch. The standard W3C `traceparent` is still emitted (same trace-id /\n * span-id) for compatibility with caches, proxies, and log correlation —\n * but the backend treats `x-nebula-traceparent` as the authoritative source.\n * If a host APM rewrites `traceparent` on egress, NebulaOS correlation is\n * unaffected.\n *\n * Legacy headers (`x-request-id`, `x-execution-id`, `x-resource-name`) are\n * no longer emitted; `nebulaos-cloud` accepts both sets temporarily via a\n * Phase 1B fallback, but new SDK releases emit only the `x-nebula-*` set\n * plus the compat `traceparent`.\n */\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-nebula-request-id\": randomUUID(),\n };\n\n const ctx = Tracing.getContext();\n\n // Trace context: prefer the active NebulaOS context; fall back to a fresh\n // root context so downstream backend logs still have a traceId to group by.\n const traceId = ctx?.traceId ?? randomBytes(16).toString(\"hex\");\n const spanId = ctx?.spanId ?? randomBytes(8).toString(\"hex\");\n const traceparent = `00-${traceId}-${spanId}-01`;\n headers[\"x-nebula-traceparent\"] = traceparent;\n headers.traceparent = traceparent; // W3C compat, non-authoritative\n\n // Execution id: Tracing context first, then the legacy ExecutionContext ALS\n // as fallback for call-sites that still seed it directly (pre-Tracing flows).\n const executionId = ctx?.executionId ?? ExecutionContext.getOrUndefined()?.executionId;\n if (executionId) {\n headers[\"x-nebula-execution-id\"] = executionId;\n }\n\n if (ctx?.resourceName) {\n headers[\"x-nebula-resource-name\"] = ctx.resourceName;\n }\n if (ctx?.resourceType) {\n headers[\"x-nebula-resource-type\"] = ctx.resourceType;\n }\n if (ctx?.workspaceId) {\n headers[\"x-nebula-workspace-id\"] = ctx.workspaceId;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n // Check if this is a UnifiedToolMessage (has toolOutputs)\n const toolOutputs = (m as any).toolOutputs as Array<{\n toolCallId: string;\n status: 'pending' | 'success' | 'error';\n content: string | null;\n error?: string;\n }> | undefined;\n\n if (toolOutputs && toolOutputs.length > 0) {\n // Expand UnifiedToolMessage into assistant + tool messages\n const result: OpenAIClient.Chat.ChatCompletionMessageParam[] = [];\n\n // 1. Assistant message with tool_calls\n result.push({\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n\n // 2. Tool messages for each completed toolOutput\n for (const output of toolOutputs) {\n if (output.status === 'pending') {\n // Skip pending outputs - tool hasn't finished yet\n continue;\n }\n\n result.push({\n role: \"tool\",\n tool_call_id: output.toolCallId,\n content: output.status === 'error'\n ? JSON.stringify({ error: output.error || 'Unknown error' })\n : (output.content || ''),\n });\n }\n\n return result;\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { data, mediaType, filename } = part;\n\n const isImage = mediaType.startsWith(\"image/\");\n const isPdf = mediaType === \"application/pdf\";\n const isText = mediaType.startsWith(\"text/\");\n\n if (!isImage && !isPdf && !isText) {\n throw new Error(`LLM Gateway: file mediaType '${mediaType}' is not supported yet`);\n }\n\n // Resolve data URI/URL\n let url: string;\n if (data instanceof Uint8Array) {\n const base64 = Buffer.from(data).toString(\"base64\");\n url = `data:${mediaType};base64,${base64}`;\n } else if (typeof data === \"string\") {\n if (data.startsWith(\"data:\") || data.includes(\"://\")) {\n url = data;\n } else {\n url = `data:${mediaType};base64,${data}`;\n }\n } else {\n throw new Error(`LLM Gateway: unsupported file data type`);\n }\n\n if (isImage) {\n return { type: \"image_url\", image_url: { url } };\n }\n\n // PDF and text files use the OpenAI-compatible `file` content part\n return {\n type: \"file\",\n file: {\n file_data: url,\n filename: filename ?? (isPdf ? \"document.pdf\" : \"document.txt\"),\n },\n };\n }\n\n throw new Error(`Unsupported content type: ${(part as any).type}`);\n }\n\n /**\n * Sanitize choices for observability storage.\n *\n * Removes provider-specific fields that are not useful for tracing/debugging\n * and that bloat the attribute size (causing truncation). Specifically:\n * - Gemini's thought_signature (huge base64 strings, 2000+ chars each)\n * - thinking_blocks (already captured via reasoningTokens)\n */\n private sanitizeChoices(choices: unknown[] | undefined): LLMResponseChoice[] {\n if (!choices) return [];\n\n return choices.map((choice) => {\n if (!choice || typeof choice !== \"object\") return choice as LLMResponseChoice;\n\n const sanitized = { ...choice } as Record<string, unknown>;\n\n // Remove provider_specific_fields.thought_signature (Gemini thinking signatures)\n if (sanitized.message && typeof sanitized.message === \"object\") {\n const message = { ...sanitized.message } as Record<string, unknown>;\n\n // Remove thinking_blocks (captured via reasoningTokens in usage)\n if (\"thinking_blocks\" in message) {\n delete message.thinking_blocks;\n }\n\n // Remove provider_specific_fields (contains thought_signatures)\n if (\"provider_specific_fields\" in message) {\n delete message.provider_specific_fields;\n }\n\n // Sanitize tool_calls: remove thought_signature from each tool call\n if (message.tool_calls && Array.isArray(message.tool_calls)) {\n message.tool_calls = message.tool_calls.map((tc: Record<string, unknown>) => {\n if (!tc || typeof tc !== \"object\") return tc;\n const sanitizedTc = { ...tc };\n // Remove provider_specific_fields from tool call (contains thought_signature)\n if (\"provider_specific_fields\" in sanitizedTc) {\n delete sanitizedTc.provider_specific_fields;\n }\n return sanitizedTc;\n });\n }\n\n sanitized.message = message;\n }\n\n return sanitized as LLMResponseChoice;\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { APIError, ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\nimport { SpanType, type LLMSpanStartData, type LLMSpanEndData, type LLMResponseChoice } from \"@nebulaos/types\";\n\n/**\n * Custom error class for LLM Gateway errors.\n * Provides clear, actionable error messages for developers.\n */\nexport class LLMGatewayError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status?: number,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"LLMGatewayError\";\n }\n}\n\n/**\n * LLM Gateway Provider Configuration\n */\nexport interface LLMGatewayConfig {\n /** API Key from NebulaOS */\n apiKey: string;\n /** Base URL of the NebulaOS LLM Gateway */\n baseUrl?: string;\n /** Route alias (e.g., \"assistente\", \"code-review\") */\n model: string;\n /** Logger verbosity for gateway calls */\n logLevel?: LogLevel;\n /** Optional OpenAI client options */\n clientOptions?: ClientOptions;\n /**\n * Default model options passed to every generate() call.\n * Supports provider-specific params like reasoning_effort, temperature, topK, etc.\n * These can be overridden by options passed directly to generate().\n */\n options?: Omit<GenerateOptions, \"responseFormat\">;\n}\n\n/**\n * NebulaOS LLM Gateway Provider\n *\n * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.\n * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,\n * and access control.\n */\nexport class LLMGateway implements IModel {\n providerName = \"llm-gateway\";\n modelName: string;\n private client: OpenAIClient;\n private baseUrl: string;\n private logger: ConsoleLogger;\n private options?: Omit<GenerateOptions, \"responseFormat\">;\n\n capabilities = {\n inputFiles: {\n mimeTypes: [\"image/*\"],\n sources: [\"url\", \"base64\"] as const,\n },\n } as const;\n\n constructor(config: LLMGatewayConfig) {\n this.modelName = config.model;\n\n this.baseUrl = config.baseUrl || \"http://localhost:4100\";\n const baseURL = this.baseUrl.endsWith(\"/v1\") ? this.baseUrl : `${this.baseUrl}/v1`;\n this.logger = new ConsoleLogger(config.logLevel || \"info\", \"nebulaos/llm-gateway\");\n\n this.client = new OpenAIClient({\n apiKey: config.apiKey,\n baseURL,\n ...config.clientOptions,\n });\n this.options = config.options;\n }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\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 };\n\n return Tracing.withSpan(\n {\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n },\n async (llmSpan) => {\n const headers = this.buildGatewayHeaders();\n this.logger.debug(\"LLM Gateway request\", {\n model,\n baseUrl: this.baseUrl,\n stream: false,\n messageCount: messages.length,\n toolCount: tools?.length ?? 0,\n });\n\n try {\n // Use .withResponse() to access HTTP headers for backend enrichment data\n const { data: response, response: httpResponse } = await this.client.chat.completions\n .create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n mergedOptions?.responseFormat?.type === \"json\"\n ? mergedOptions.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: mergedOptions.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(mergedOptions),\n },\n { headers },\n )\n .withResponse();\n\n this.logger.debug(\"LLM Gateway response\", {\n model,\n finishReason: response.choices?.[0]?.finish_reason,\n hasUsage: Boolean(response.usage),\n });\n\n const choice = response.choices[0];\n const message = choice.message;\n const usage = this.mapUsage(response.usage);\n const finishReason = this.mapFinishReason(choice.finish_reason);\n\n // Read enrichment headers from backend (model actual, cost, usage, fallback)\n const enrichment = this.extractEnrichmentFromHeaders(httpResponse.headers);\n\n // Typed span end data for LLM calls\n const endData: LLMSpanEndData = {\n usage: enrichment.usage ?? usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finishReason ?? \"stop\",\n toolCallsCount: message.tool_calls?.length ?? 0,\n choices: this.sanitizeChoices(response.choices),\n model: enrichment.modelActual,\n fallbackUsed: enrichment.fallbackUsed,\n cost: enrichment.cost ? parseFloat(enrichment.cost.amountUsd) : undefined,\n };\n\n await llmSpan.end({\n status: \"success\",\n data: endData,\n });\n\n return {\n content: message.content || \"\",\n toolCalls: message.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n finishReason,\n usage: enrichment.usage ?? usage,\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n const gatewayError = this.handleError(error);\n\n // Typed span end data for error case\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n\n await llmSpan.end({\n status: \"error\",\n data: errorEndData,\n });\n\n throw gatewayError;\n }\n },\n );\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const mergedOptions = { ...this.options, ...options };\n const model = this.modelName;\n\n // Extract LLM config params (temperature, thinkingLevel, topP, maxTokens, etc)\n const { responseFormat, ...llmConfig } = mergedOptions ?? {};\n\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 };\n\n // Start span manually for streaming (async generators can't use withSpan directly).\n // Tracing.startSpan is synchronous (no await) under ADR-0002.\n const llmSpan = Tracing.startSpan({\n kind: SpanType.llm_wrapper,\n name: `llm:${this.modelName}`,\n data: startData,\n });\n\n // Streaming + span context: we run the full HTTP + iteration loop inside a\n // `Tracing.runWithSpan(llmSpan, ...)` scope — that is the only way to keep\n // the span's TracingContext active across every `await` inside the stream\n // (outgoing fetch, SSE chunk iteration, nested calls that might be added\n // later by wrappers). Because `runWithSpan` takes `() => Promise<T>` and\n // our public API is an async generator, we bridge the two with a classic\n // producer/consumer queue. The producer task runs fully inside the span\n // context; the consumer (this async generator) just drains the queue and\n // yields chunks as they arrive — preserving real-time streaming semantics\n // while still guaranteeing that anything happening under `runWithSpan`\n // sees the correct `Tracing.getContext()`.\n type QueueItem =\n | { kind: \"chunk\"; value: StreamChunk }\n | { kind: \"done\" }\n | { kind: \"error\"; error: unknown };\n\n const queue: QueueItem[] = [];\n let pendingResolve: ((item: QueueItem) => void) | null = null;\n\n // Abort controller propagates early-exit from the consumer to the OpenAI\n // HTTP request and to the SSE iteration inside the producer. If the caller\n // breaks out of the generator (timeout, disconnect, or just wants the\n // first chunk), we cancel the upstream LLM call instead of silently\n // draining the whole response.\n const abortController = new AbortController();\n let consumerAborted = false;\n\n const push = (item: QueueItem): void => {\n if (pendingResolve) {\n const resolve = pendingResolve;\n pendingResolve = null;\n resolve(item);\n } else {\n queue.push(item);\n }\n };\n\n const pull = (): Promise<QueueItem> => {\n if (queue.length > 0) return Promise.resolve(queue.shift() as QueueItem);\n return new Promise<QueueItem>((resolve) => {\n pendingResolve = resolve;\n });\n };\n\n // Fire the producer. We intentionally do NOT await it here — the consumer\n // below drains the queue and observes completion via the \"done\" / \"error\"\n // sentinels. Any rejection is captured and turned into a queue item so the\n // async generator surface stays clean.\n const producer = Tracing.runWithSpan(llmSpan, async () => {\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, signal: abortController.signal },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n throw this.handleError(error);\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 // Fast-path exit when the consumer aborted — avoid reading the rest\n // of the upstream response. OpenAI's stream iterator checks the\n // signal too, but we double-check here in case a chunk landed in\n // the internal buffer right before the abort.\n if (abortController.signal.aborted) break;\n if (chunk.usage) {\n finalUsage = this.mapUsage(chunk.usage);\n push({\n kind: \"chunk\",\n value: { type: \"finish\", reason: \"stop\", 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 push({\n kind: \"chunk\",\n value: { type: \"finish\", 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 push({ kind: \"chunk\", value: { 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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_start\",\n index: idx,\n id: tc.id,\n name: tc.function.name,\n },\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 push({\n kind: \"chunk\",\n value: {\n type: \"tool_call_delta\",\n index: idx,\n args: tc.function.arguments,\n },\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 const endData: LLMSpanEndData = {\n usage: finalUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n finishReason: finalFinishReason ?? \"stop\",\n toolCallsCount,\n outputPreview,\n choices: this.sanitizeChoices(choices),\n };\n\n await llmSpan.end({ status: \"success\", data: endData });\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n throw this.handleError(error);\n }\n }).then(\n () => push({ kind: \"done\" }),\n (error) => push({ kind: \"error\", error }),\n );\n\n // Consumer: drain the queue until the producer signals done or error.\n let completedNormally = false;\n try {\n while (true) {\n const item = await pull();\n if (item.kind === \"chunk\") {\n yield item.value;\n } else if (item.kind === \"done\") {\n completedNormally = true;\n return;\n } else {\n // On error, make sure the span is closed before re-throwing. The\n // producer only closes the span on the success path — errors bubble\n // up here so we own the failure-close.\n completedNormally = true;\n const gatewayError =\n item.error instanceof LLMGatewayError ? item.error : this.handleError(item.error);\n\n if (!llmSpan.isEnded) {\n const errorEndData: LLMSpanEndData = {\n error: {\n message: gatewayError.message,\n code: gatewayError.code,\n status: gatewayError.status,\n },\n };\n await llmSpan.end({ status: \"error\", data: errorEndData });\n }\n\n throw gatewayError;\n }\n }\n } finally {\n // If the consumer exits without seeing `done`/`error` (break, throw,\n // `return` from the caller's `for await`), abort the upstream LLM call\n // so we stop burning tokens and memory. The producer will bail out\n // (either from the OpenAI client rejecting or from the signal check\n // inside the SSE loop), close the span as `cancelled`, and resolve the\n // `producer` Promise — so awaiting it below is still safe.\n if (!completedNormally) {\n consumerAborted = true;\n abortController.abort();\n if (!llmSpan.isEnded) {\n try {\n await llmSpan.end({ status: \"cancelled\" });\n } catch {\n // span end errors are non-fatal here\n }\n }\n }\n\n // Make sure we don't leave the producer dangling. `producer` is a\n // Promise<void> that always resolves (errors are captured into the\n // queue), so awaiting is safe. Under normal completion it's already\n // done; under abort it resolves shortly after the signal fires.\n try {\n await producer;\n } catch {\n // Producer rejections are already routed through the queue; an\n // abort-triggered rejection landing here is expected and ignorable.\n }\n void consumerAborted;\n }\n }\n\n // ==========================================================================\n // Error Handling\n // ==========================================================================\n\n /**\n * Transforms raw errors into actionable LLMGatewayError with clear messages.\n * This ensures developers get specific guidance on how to resolve issues.\n *\n * Differentiates between:\n * - Gateway errors: LLM Gateway API key issues (check NEBULAOS_API_KEY env var)\n * - Provider errors: LLM provider API key issues (check route config in dashboard)\n */\n private handleError(error: unknown): LLMGatewayError {\n // Handle OpenAI SDK APIError (includes status, message, code)\n if (error instanceof APIError) {\n const status = error.status;\n const originalMessage = error.message;\n\n // Check X-Error-Source header to differentiate gateway vs provider errors\n const errorSource = this.extractErrorSource(error);\n\n // Authentication errors (401)\n if (status === 401) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway authentication failed: Your LLM Gateway API key is invalid or expired. ` +\n `Please verify your NEBULAOS_API_KEY environment variable or check your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_AUTH_ERROR\",\n status,\n error,\n );\n } else {\n // Provider error (default for 401 without explicit gateway source)\n return new LLMGatewayError(\n `LLM Provider authentication failed: The API key configured for your LLM provider (OpenAI, Azure, etc.) is invalid or expired. ` +\n `Please verify the provider API key in your route configuration in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_AUTH_ERROR\",\n status,\n error,\n );\n }\n }\n\n // Permission denied (403)\n if (status === 403) {\n if (errorSource === \"gateway\") {\n return new LLMGatewayError(\n `LLM Gateway access denied: Your LLM Gateway API key does not have permission to access this route. ` +\n `Please verify the route is allowed for your LLM Gateway API key in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"GATEWAY_FORBIDDEN\",\n status,\n error,\n );\n } else {\n return new LLMGatewayError(\n `LLM Provider access denied: The provider API key does not have permission for this operation. ` +\n `Please verify the provider API key permissions in the NebulaOS dashboard. ` +\n `Original error: ${originalMessage}`,\n \"PROVIDER_FORBIDDEN\",\n status,\n error,\n );\n }\n }\n\n // Rate limit (429)\n if (status === 429) {\n return new LLMGatewayError(\n `LLM Gateway rate limit exceeded: Too many requests to the LLM provider. ` +\n `Please wait before retrying or check your rate limit configuration. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_RATE_LIMIT\",\n status,\n error,\n );\n }\n\n // Bad request (400)\n if (status === 400) {\n // Check if error message suggests permission/token issues\n const lowerMsg = originalMessage.toLowerCase();\n const isPermissionRelated =\n lowerMsg.includes(\"unauthorized\") ||\n lowerMsg.includes(\"permission\") ||\n lowerMsg.includes(\"access denied\") ||\n lowerMsg.includes(\"not allowed\") ||\n lowerMsg.includes(\"forbidden\") ||\n lowerMsg.includes(\"invalid api key\") ||\n lowerMsg.includes(\"api key\");\n\n const permissionHint = isPermissionRelated\n ? `This error may indicate a token/permission issue. `\n : `If this error persists, verify that the LLM Gateway API key has access to the route '${this.modelName}'. ` +\n `Common cause: using a route without the corresponding token permission. `;\n\n return new LLMGatewayError(\n `LLM Gateway request error: Invalid request parameters. ` +\n `Please check your request configuration (model, messages, tools). ` +\n `${permissionHint}` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_BAD_REQUEST\",\n status,\n error,\n );\n }\n\n // Not found (404)\n if (status === 404) {\n return new LLMGatewayError(\n `LLM Gateway route not found: The specified model or route does not exist. ` +\n `Please verify the route alias '${this.modelName}' is correct and provisioned. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_NOT_FOUND\",\n status,\n error,\n );\n }\n\n // Timeout (408, 504)\n if (status === 408 || status === 504) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The request took too long to complete. ` +\n `This may be due to high load or a complex request. Please try again. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_TIMEOUT\",\n status,\n error,\n );\n }\n\n // Server errors (5xx)\n if (status && status >= 500) {\n return new LLMGatewayError(\n `LLM Gateway server error: The LLM provider returned an error (${status}). ` +\n `This is typically a temporary issue. Please try again later. ` +\n `Original error: ${originalMessage}`,\n \"LLM_GATEWAY_SERVER_ERROR\",\n status,\n error,\n );\n }\n\n // Other API errors\n return new LLMGatewayError(\n `LLM Gateway error (${status}): ${originalMessage}`,\n \"LLM_GATEWAY_ERROR\",\n status,\n error,\n );\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n\n // Connection errors\n if (msg.includes(\"econnrefused\") || msg.includes(\"enotfound\") || msg.includes(\"network\")) {\n return new LLMGatewayError(\n `LLM Gateway connection failed: Unable to connect to the LLM Gateway at ${this.baseUrl}. ` +\n `Please verify the gateway is running and accessible. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_CONNECTION_ERROR\",\n undefined,\n error,\n );\n }\n\n // Timeout errors\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\") || msg.includes(\"etimedout\")) {\n return new LLMGatewayError(\n `LLM Gateway timeout: The connection timed out. ` +\n `Please check network connectivity and try again. ` +\n `Original error: ${error.message}`,\n \"LLM_GATEWAY_TIMEOUT\",\n undefined,\n error,\n );\n }\n\n // Generic Error - preserve original message with context\n return new LLMGatewayError(\n `LLM Gateway error: ${error.message}`,\n \"LLM_GATEWAY_ERROR\",\n undefined,\n error,\n );\n }\n\n // Unknown error type\n return new LLMGatewayError(\n `LLM Gateway error: An unexpected error occurred. Details: ${String(error)}`,\n \"LLM_GATEWAY_UNKNOWN_ERROR\",\n undefined,\n error,\n );\n }\n\n /**\n * Extracts the error source from an APIError.\n * The backend sets X-Error-Source header or includes source in the error body\n * to differentiate between gateway errors (LLM Gateway API key) and provider errors.\n *\n * @returns \"gateway\" if the error is from LLM Gateway authentication,\n * \"provider\" if the error is from the upstream LLM provider,\n * undefined if the source cannot be determined.\n */\n private extractErrorSource(error: APIError): \"gateway\" | \"provider\" | undefined {\n // Try to get source from response headers\n const headers = error.headers;\n if (headers) {\n const errorSource = headers[\"x-error-source\"] || headers[\"X-Error-Source\"];\n if (errorSource === \"gateway\" || errorSource === \"provider\") {\n return errorSource;\n }\n }\n\n // Try to get source from error body\n // The backend may include { error: { source: \"gateway\" | \"provider\", ... } }\n const errorBody = error.error as Record<string, unknown> | undefined;\n if (errorBody && typeof errorBody === \"object\") {\n // Check for nested error object (OpenAI style)\n const nestedError = errorBody.error as Record<string, unknown> | undefined;\n if (nestedError && typeof nestedError === \"object\" && nestedError.source) {\n const source = nestedError.source;\n if (source === \"gateway\" || source === \"provider\") {\n return source;\n }\n }\n // Check for direct source field\n if (errorBody.source === \"gateway\" || errorBody.source === \"provider\") {\n return errorBody.source;\n }\n }\n\n // Check error message for gateway-specific patterns\n const msg = error.message.toLowerCase();\n if (msg.includes(\"llm gateway api key\") || msg.includes(\"llm gateway\")) {\n return \"gateway\";\n }\n\n return undefined;\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n /**\n * Builds the outbound headers for a call to the NebulaOS LLM Gateway.\n *\n * Under ADR-0002, correlation with the NebulaOS backend is carried on\n * domain-scoped `x-nebula-*` headers that APMs of the host process do not\n * touch. The standard W3C `traceparent` is still emitted (same trace-id /\n * span-id) for compatibility with caches, proxies, and log correlation —\n * but the backend treats `x-nebula-traceparent` as the authoritative source.\n * If a host APM rewrites `traceparent` on egress, NebulaOS correlation is\n * unaffected.\n *\n * Legacy headers (`x-request-id`, `x-execution-id`, `x-resource-name`) are\n * no longer emitted; `nebulaos-cloud` accepts both sets temporarily via a\n * Phase 1B fallback, but new SDK releases emit only the `x-nebula-*` set\n * plus the compat `traceparent`.\n */\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-nebula-request-id\": randomUUID(),\n };\n\n const ctx = Tracing.getContext();\n\n // Trace context: prefer the active NebulaOS context; fall back to a fresh\n // root context so downstream backend logs still have a traceId to group by.\n const traceId = ctx?.traceId ?? randomBytes(16).toString(\"hex\");\n const spanId = ctx?.spanId ?? randomBytes(8).toString(\"hex\");\n const traceparent = `00-${traceId}-${spanId}-01`;\n headers[\"x-nebula-traceparent\"] = traceparent;\n headers.traceparent = traceparent; // W3C compat, non-authoritative\n\n // Execution id: Tracing context first, then the legacy ExecutionContext ALS\n // as fallback for call-sites that still seed it directly (pre-Tracing flows).\n const executionId = ctx?.executionId ?? ExecutionContext.getOrUndefined()?.executionId;\n if (executionId) {\n headers[\"x-nebula-execution-id\"] = executionId;\n }\n\n if (ctx?.resourceName) {\n headers[\"x-nebula-resource-name\"] = ctx.resourceName;\n }\n if (ctx?.resourceType) {\n headers[\"x-nebula-resource-type\"] = ctx.resourceType;\n }\n if (ctx?.workspaceId) {\n headers[\"x-nebula-workspace-id\"] = ctx.workspaceId;\n }\n\n return headers;\n }\n\n /**\n * Extracts enrichment data from backend HTTP headers.\n * Backend returns this data so SDK can enrich its own span (avoiding duplicate spans).\n */\n private extractEnrichmentFromHeaders(headers: Headers): {\n modelActual?: string;\n fallbackUsed?: boolean;\n usage?: TokenUsage;\n cost?: { amountUsd: string; available: boolean };\n } {\n const result: ReturnType<typeof this.extractEnrichmentFromHeaders> = {};\n\n const modelActual = headers.get(\"x-llm-model-actual\");\n if (modelActual) {\n result.modelActual = modelActual;\n }\n\n const fallbackUsed = headers.get(\"x-llm-fallback-used\");\n if (fallbackUsed) {\n result.fallbackUsed = fallbackUsed === \"true\";\n }\n\n const usageRaw = headers.get(\"x-llm-usage\");\n if (usageRaw) {\n try {\n const usage = JSON.parse(usageRaw);\n result.usage = {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,\n // Preserve any additional token fields from provider\n ...usage,\n };\n } catch {\n this.logger.warn(\"Failed to parse x-llm-usage header\", { usageRaw });\n }\n }\n\n const cost = headers.get(\"x-llm-cost\");\n const costAvailable = headers.get(\"x-llm-cost-available\");\n if (cost) {\n result.cost = {\n amountUsd: cost,\n available: costAvailable === \"true\",\n };\n }\n\n return result;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n // Check if this is a UnifiedToolMessage (has toolOutputs)\n const toolOutputs = (m as any).toolOutputs as Array<{\n toolCallId: string;\n status: 'pending' | 'success' | 'error';\n content: string | null;\n error?: string;\n }> | undefined;\n\n if (toolOutputs && toolOutputs.length > 0) {\n // Expand UnifiedToolMessage into assistant + tool messages\n const result: OpenAIClient.Chat.ChatCompletionMessageParam[] = [];\n\n // 1. Assistant message with tool_calls\n result.push({\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n });\n\n // 2. Tool messages for each completed toolOutput\n for (const output of toolOutputs) {\n if (output.status === 'pending') {\n // Skip pending outputs - tool hasn't finished yet\n continue;\n }\n\n result.push({\n role: \"tool\",\n tool_call_id: output.toolCallId,\n content: output.status === 'error'\n ? JSON.stringify({ error: output.error || 'Unknown error' })\n : (output.content || ''),\n });\n }\n\n return result;\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { data, mediaType, filename } = part;\n\n const isImage = mediaType.startsWith(\"image/\");\n const isPdf = mediaType === \"application/pdf\";\n const isText = mediaType.startsWith(\"text/\");\n\n if (!isImage && !isPdf && !isText) {\n throw new Error(`LLM Gateway: file mediaType '${mediaType}' is not supported yet`);\n }\n\n // Resolve data URI/URL\n let url: string;\n if (data instanceof Uint8Array) {\n const base64 = Buffer.from(data).toString(\"base64\");\n url = `data:${mediaType};base64,${base64}`;\n } else if (typeof data === \"string\") {\n if (data.startsWith(\"data:\") || data.includes(\"://\")) {\n url = data;\n } else {\n url = `data:${mediaType};base64,${data}`;\n }\n } else {\n throw new Error(`LLM Gateway: unsupported file data type`);\n }\n\n if (isImage) {\n return { type: \"image_url\", image_url: { url } };\n }\n\n // PDF and text files use the OpenAI-compatible `file` content part\n return {\n type: \"file\",\n file: {\n file_data: url,\n filename: filename ?? (isPdf ? \"document.pdf\" : \"document.txt\"),\n },\n };\n }\n\n throw new Error(`Unsupported content type: ${(part as any).type}`);\n }\n\n /**\n * Sanitize choices for observability storage.\n *\n * Removes provider-specific fields that are not useful for tracing/debugging\n * and that bloat the attribute size (causing truncation). Specifically:\n * - Gemini's thought_signature (huge base64 strings, 2000+ chars each)\n * - thinking_blocks (already captured via reasoningTokens)\n */\n private sanitizeChoices(choices: unknown[] | undefined): LLMResponseChoice[] {\n if (!choices) return [];\n\n return choices.map((choice) => {\n if (!choice || typeof choice !== \"object\") return choice as LLMResponseChoice;\n\n const sanitized = { ...choice } as Record<string, unknown>;\n\n // Remove provider_specific_fields.thought_signature (Gemini thinking signatures)\n if (sanitized.message && typeof sanitized.message === \"object\") {\n const message = { ...sanitized.message } as Record<string, unknown>;\n\n // Remove thinking_blocks (captured via reasoningTokens in usage)\n if (\"thinking_blocks\" in message) {\n delete message.thinking_blocks;\n }\n\n // Remove provider_specific_fields (contains thought_signatures)\n if (\"provider_specific_fields\" in message) {\n delete message.provider_specific_fields;\n }\n\n // Sanitize tool_calls: remove thought_signature from each tool call\n if (message.tool_calls && Array.isArray(message.tool_calls)) {\n message.tool_calls = message.tool_calls.map((tc: Record<string, unknown>) => {\n if (!tc || typeof tc !== \"object\") return tc;\n const sanitizedTc = { ...tc };\n // Remove provider_specific_fields from tool call (contains thought_signature)\n if (\"provider_specific_fields\" in sanitizedTc) {\n delete sanitizedTc.provider_specific_fields;\n }\n return sanitizedTc;\n });\n }\n\n sanitized.message = message;\n }\n\n return sanitized as LLMResponseChoice;\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulaos/llm-gateway",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "NebulaOS LLM Gateway provider - OpenAI-compatible chat completions",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"jest": "^30.0.0",
|
|
40
40
|
"ts-jest": "^29.0.0",
|
|
41
41
|
"@types/jest": "^30.0.0",
|
|
42
|
-
"@nebulaos/
|
|
43
|
-
"@nebulaos/
|
|
42
|
+
"@nebulaos/types": "0.2.0",
|
|
43
|
+
"@nebulaos/core": "0.4.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup",
|