@nebulaos/llm-gateway 0.1.0

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/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @nebulaos/llm-gateway
2
+
3
+ NebulaOS LLM Gateway provider for the NebulaOS SDK. Provides OpenAI-compatible chat completions through pre-configured routes with automatic fallback, cost tracking, and access control.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @nebulaos/llm-gateway
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { LLMGateway } from "@nebulaos/llm-gateway";
15
+ import { Agent } from "@nebulaos/core";
16
+
17
+ const agent = new Agent({
18
+ name: "assistente",
19
+ model: new LLMGateway({
20
+ apiKey: "your-nebula-api-key",
21
+ baseUrl: "https://your-nebula-instance.com",
22
+ model: "assistente", // Route alias configured in NebulaOS
23
+ logLevel: "debug",
24
+ }),
25
+ instructions: "You are a helpful assistant.",
26
+ });
27
+
28
+ // Use the agent
29
+ const response = await agent.generate("Hello, how can I help you?");
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ | Option | Type | Required | Description |
35
+ |--------|------|----------|-------------|
36
+ | `apiKey` | `string` | Yes | API key from NebulaOS |
37
+ | `baseUrl` | `string` | No | NebulaOS instance URL (defaults to localhost:4100) |
38
+ | `model` | `string` | Yes | Route alias configured in NebulaOS |
39
+ | `logLevel` | `string` | No | Logger verbosity ("debug", "info", "warn", "error", "none") |
40
+
41
+ ## Routes
42
+
43
+ Routes are configured in the NebulaOS UI and provide:
44
+
45
+ - **Automatic fallback**: If primary model fails, automatically tries backup models
46
+ - **Cost tracking**: All usage is automatically tracked and billed
47
+ - **Access control**: API keys can be restricted to specific routes
48
+ - **Rate limiting**: Configurable limits per route and user
49
+
50
+ ## Supported Features
51
+
52
+ - ✅ Text chat completions
53
+ - ✅ Streaming responses
54
+ - ✅ Tool/function calling
55
+ - ✅ JSON mode
56
+ - ✅ Multimodal (images)
57
+ - ✅ Automatic cost tracking
58
+ - ✅ Route-based access control
59
+
60
+ ## Error Handling
61
+
62
+ The provider will throw errors for:
63
+ - Invalid API keys
64
+ - Routes not found or inactive
65
+ - Access denied to routes
66
+ - Network issues with NebulaOS
67
+
68
+ All errors include appropriate HTTP status codes and messages.
@@ -0,0 +1,50 @@
1
+ import OpenAIClient, { ClientOptions } from 'openai';
2
+ import { LogLevel, IModel, Message, ToolDefinitionForLLM, GenerateOptions, ProviderResponse, StreamChunk } from '@nebulaos/core';
3
+
4
+ /**
5
+ * LLM Gateway Provider Configuration
6
+ */
7
+ interface LLMGatewayConfig {
8
+ /** API Key from NebulaOS */
9
+ apiKey: string;
10
+ /** Base URL of the NebulaOS LLM Gateway */
11
+ baseUrl?: string;
12
+ /** Route alias (e.g., "assistente", "code-review") */
13
+ model: string;
14
+ /** Logger verbosity for gateway calls */
15
+ logLevel?: LogLevel;
16
+ /** Optional OpenAI client options */
17
+ clientOptions?: ClientOptions;
18
+ }
19
+ /**
20
+ * NebulaOS LLM Gateway Provider
21
+ *
22
+ * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.
23
+ * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,
24
+ * and access control.
25
+ */
26
+ declare class LLMGateway implements IModel {
27
+ providerName: string;
28
+ modelName: string;
29
+ private client;
30
+ private baseUrl;
31
+ private logger;
32
+ capabilities: {
33
+ readonly inputFiles: {
34
+ readonly mimeTypes: readonly ["image/*"];
35
+ readonly sources: readonly ["url", "base64"];
36
+ };
37
+ };
38
+ constructor(config: LLMGatewayConfig);
39
+ generate(messages: Message[], tools?: ToolDefinitionForLLM[], options?: GenerateOptions): Promise<ProviderResponse>;
40
+ generateStream(messages: Message[], tools?: ToolDefinitionForLLM[], options?: GenerateOptions): AsyncGenerator<StreamChunk>;
41
+ private extractExtraOptions;
42
+ private buildGatewayHeaders;
43
+ protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[];
44
+ private convertTools;
45
+ private mapUsage;
46
+ private mapFinishReason;
47
+ private convertContentPart;
48
+ }
49
+
50
+ export { LLMGateway, type LLMGatewayConfig };
@@ -0,0 +1,50 @@
1
+ import OpenAIClient, { ClientOptions } from 'openai';
2
+ import { LogLevel, IModel, Message, ToolDefinitionForLLM, GenerateOptions, ProviderResponse, StreamChunk } from '@nebulaos/core';
3
+
4
+ /**
5
+ * LLM Gateway Provider Configuration
6
+ */
7
+ interface LLMGatewayConfig {
8
+ /** API Key from NebulaOS */
9
+ apiKey: string;
10
+ /** Base URL of the NebulaOS LLM Gateway */
11
+ baseUrl?: string;
12
+ /** Route alias (e.g., "assistente", "code-review") */
13
+ model: string;
14
+ /** Logger verbosity for gateway calls */
15
+ logLevel?: LogLevel;
16
+ /** Optional OpenAI client options */
17
+ clientOptions?: ClientOptions;
18
+ }
19
+ /**
20
+ * NebulaOS LLM Gateway Provider
21
+ *
22
+ * Provides access to NebulaOS LLM Gateway routes through an OpenAI-compatible interface.
23
+ * Routes are pre-configured in NebulaOS and provide automatic fallback, cost tracking,
24
+ * and access control.
25
+ */
26
+ declare class LLMGateway implements IModel {
27
+ providerName: string;
28
+ modelName: string;
29
+ private client;
30
+ private baseUrl;
31
+ private logger;
32
+ capabilities: {
33
+ readonly inputFiles: {
34
+ readonly mimeTypes: readonly ["image/*"];
35
+ readonly sources: readonly ["url", "base64"];
36
+ };
37
+ };
38
+ constructor(config: LLMGatewayConfig);
39
+ generate(messages: Message[], tools?: ToolDefinitionForLLM[], options?: GenerateOptions): Promise<ProviderResponse>;
40
+ generateStream(messages: Message[], tools?: ToolDefinitionForLLM[], options?: GenerateOptions): AsyncGenerator<StreamChunk>;
41
+ private extractExtraOptions;
42
+ private buildGatewayHeaders;
43
+ protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[];
44
+ private convertTools;
45
+ private mapUsage;
46
+ private mapFinishReason;
47
+ private convertContentPart;
48
+ }
49
+
50
+ export { LLMGateway, type LLMGatewayConfig };
package/dist/index.js ADDED
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ LLMGateway: () => LLMGateway
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_openai = __toESM(require("openai"));
37
+ var import_node_crypto = require("crypto");
38
+ var import_core = require("@nebulaos/core");
39
+ var LLMGateway = class {
40
+ providerName = "llm-gateway";
41
+ modelName;
42
+ client;
43
+ baseUrl;
44
+ logger;
45
+ capabilities = {
46
+ inputFiles: {
47
+ mimeTypes: ["image/*"],
48
+ sources: ["url", "base64"]
49
+ }
50
+ };
51
+ constructor(config) {
52
+ this.modelName = config.model;
53
+ this.baseUrl = config.baseUrl || "http://localhost:4100";
54
+ const baseURL = this.baseUrl.endsWith("/v1") ? this.baseUrl : `${this.baseUrl}/v1`;
55
+ this.logger = new import_core.ConsoleLogger(config.logLevel || "info", "nebulaos/llm-gateway");
56
+ this.client = new import_openai.default({
57
+ apiKey: config.apiKey,
58
+ baseURL,
59
+ ...config.clientOptions
60
+ });
61
+ }
62
+ async generate(messages, tools, options) {
63
+ const model = `route:${this.modelName}`;
64
+ const headers = this.buildGatewayHeaders();
65
+ this.logger.debug("LLM Gateway request", {
66
+ model,
67
+ baseUrl: this.baseUrl,
68
+ stream: false,
69
+ messageCount: messages.length,
70
+ toolCount: tools?.length ?? 0
71
+ });
72
+ try {
73
+ const response = await this.client.chat.completions.create(
74
+ {
75
+ model,
76
+ messages: this.convertMessages(messages),
77
+ tools: this.convertTools(tools),
78
+ response_format: options?.responseFormat?.type === "json" ? options.responseFormat.schema ? {
79
+ type: "json_schema",
80
+ json_schema: { name: "response", schema: options.responseFormat.schema }
81
+ } : { type: "json_object" } : void 0,
82
+ ...this.extractExtraOptions(options)
83
+ },
84
+ { headers }
85
+ );
86
+ this.logger.debug("LLM Gateway response", {
87
+ model,
88
+ finishReason: response.choices?.[0]?.finish_reason,
89
+ hasUsage: Boolean(response.usage)
90
+ });
91
+ const choice = response.choices[0];
92
+ const message = choice.message;
93
+ return {
94
+ content: message.content || "",
95
+ toolCalls: message.tool_calls?.map((tc) => ({
96
+ id: tc.id,
97
+ type: "function",
98
+ function: {
99
+ name: tc.function.name,
100
+ arguments: tc.function.arguments
101
+ }
102
+ })),
103
+ finishReason: this.mapFinishReason(choice.finish_reason),
104
+ usage: this.mapUsage(response.usage)
105
+ };
106
+ } catch (error) {
107
+ this.logger.error("LLM Gateway request failed", error, void 0, void 0);
108
+ throw error;
109
+ }
110
+ }
111
+ async *generateStream(messages, tools, options) {
112
+ const model = `route:${this.modelName}`;
113
+ const headers = this.buildGatewayHeaders();
114
+ this.logger.debug("LLM Gateway stream request", {
115
+ model,
116
+ baseUrl: this.baseUrl,
117
+ stream: true,
118
+ messageCount: messages.length,
119
+ toolCount: tools?.length ?? 0
120
+ });
121
+ let stream;
122
+ try {
123
+ stream = await this.client.chat.completions.create(
124
+ {
125
+ model,
126
+ messages: this.convertMessages(messages),
127
+ tools: this.convertTools(tools),
128
+ stream: true,
129
+ stream_options: { include_usage: true },
130
+ response_format: options?.responseFormat?.type === "json" ? options.responseFormat.schema ? {
131
+ type: "json_schema",
132
+ json_schema: { name: "response", schema: options.responseFormat.schema }
133
+ } : { type: "json_object" } : void 0,
134
+ ...this.extractExtraOptions(options)
135
+ },
136
+ { headers }
137
+ );
138
+ } catch (error) {
139
+ this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
140
+ throw error;
141
+ }
142
+ try {
143
+ for await (const chunk of stream) {
144
+ if (chunk.usage) {
145
+ yield {
146
+ type: "finish",
147
+ reason: "stop",
148
+ usage: this.mapUsage(chunk.usage)
149
+ };
150
+ }
151
+ const choice = chunk.choices?.[0];
152
+ if (!choice) continue;
153
+ if (choice.finish_reason) {
154
+ yield {
155
+ type: "finish",
156
+ reason: this.mapFinishReason(choice.finish_reason)
157
+ };
158
+ }
159
+ const delta = choice.delta;
160
+ if (!delta) continue;
161
+ if (delta.content) {
162
+ yield { type: "content_delta", delta: delta.content };
163
+ }
164
+ if (delta.tool_calls) {
165
+ for (const tc of delta.tool_calls) {
166
+ if (tc.id && tc.function?.name) {
167
+ yield {
168
+ type: "tool_call_start",
169
+ index: tc.index,
170
+ id: tc.id,
171
+ name: tc.function.name
172
+ };
173
+ }
174
+ if (tc.function?.arguments) {
175
+ yield {
176
+ type: "tool_call_delta",
177
+ index: tc.index,
178
+ args: tc.function.arguments
179
+ };
180
+ }
181
+ }
182
+ }
183
+ }
184
+ } catch (error) {
185
+ this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
186
+ throw error;
187
+ }
188
+ }
189
+ // ==========================================================================
190
+ // Helpers (copied from OpenAI provider)
191
+ // ==========================================================================
192
+ extractExtraOptions(options) {
193
+ if (!options) return {};
194
+ const { responseFormat, ...rest } = options;
195
+ return rest;
196
+ }
197
+ buildGatewayHeaders() {
198
+ const headers = {
199
+ "x-request-id": (0, import_node_crypto.randomUUID)()
200
+ };
201
+ const exec = import_core.ExecutionContext.getOrUndefined();
202
+ if (exec?.executionId) {
203
+ headers["x-execution-id"] = exec.executionId;
204
+ }
205
+ const ctx = import_core.Tracing.getContext();
206
+ if (ctx) {
207
+ headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;
208
+ } else {
209
+ const traceId = (0, import_node_crypto.randomBytes)(16).toString("hex");
210
+ const spanId = (0, import_node_crypto.randomBytes)(8).toString("hex");
211
+ headers.traceparent = `00-${traceId}-${spanId}-01`;
212
+ }
213
+ return headers;
214
+ }
215
+ convertMessages(messages) {
216
+ const allowedToolCallIds = /* @__PURE__ */ new Set();
217
+ return messages.flatMap((m) => {
218
+ if (m.role === "tool") {
219
+ if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {
220
+ return [];
221
+ }
222
+ return {
223
+ role: "tool",
224
+ tool_call_id: m.tool_call_id,
225
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
226
+ // Tool output usually string
227
+ };
228
+ }
229
+ if (m.role === "assistant") {
230
+ let assistantContent = null;
231
+ if (typeof m.content === "string") {
232
+ assistantContent = m.content;
233
+ }
234
+ if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {
235
+ assistantContent = "";
236
+ }
237
+ if (m.tool_calls) {
238
+ m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));
239
+ }
240
+ return {
241
+ role: "assistant",
242
+ content: assistantContent,
243
+ tool_calls: m.tool_calls?.map((tc) => ({
244
+ id: tc.id,
245
+ type: "function",
246
+ function: {
247
+ name: tc.function.name,
248
+ arguments: tc.function.arguments
249
+ }
250
+ }))
251
+ };
252
+ }
253
+ const content = Array.isArray(
254
+ m.content
255
+ ) ? m.content.map((part) => this.convertContentPart(part)) : m.content || "";
256
+ return {
257
+ role: m.role,
258
+ content,
259
+ name: m.name
260
+ };
261
+ });
262
+ }
263
+ convertTools(tools) {
264
+ if (!tools || tools.length === 0) return void 0;
265
+ return tools.map((t) => ({
266
+ type: "function",
267
+ function: {
268
+ name: t.function.name,
269
+ description: t.function.description,
270
+ parameters: t.function.parameters
271
+ }
272
+ }));
273
+ }
274
+ mapUsage(usage) {
275
+ if (!usage) return void 0;
276
+ return {
277
+ promptTokens: usage.prompt_tokens,
278
+ completionTokens: usage.completion_tokens,
279
+ totalTokens: usage.total_tokens,
280
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens
281
+ };
282
+ }
283
+ mapFinishReason(reason) {
284
+ switch (reason) {
285
+ case "stop":
286
+ return "stop";
287
+ case "length":
288
+ return "length";
289
+ case "tool_calls":
290
+ return "tool_calls";
291
+ case "content_filter":
292
+ return "content_filter";
293
+ default:
294
+ return void 0;
295
+ }
296
+ }
297
+ convertContentPart(part) {
298
+ if (part.type === "text") return { type: "text", text: part.text };
299
+ if (part.type === "file") {
300
+ const { mimeType, source } = part.file;
301
+ if (!mimeType.startsWith("image/")) {
302
+ throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);
303
+ }
304
+ const url = source.type === "url" ? source.url : `data:${mimeType};base64,${source.base64}`;
305
+ return { type: "image_url", image_url: { url } };
306
+ }
307
+ if (part.type === "image_url") {
308
+ return { type: "image_url", image_url: { url: part.image_url.url } };
309
+ }
310
+ const _exhaustive = part;
311
+ throw new Error(`Unsupported content type: ${_exhaustive.type}`);
312
+ }
313
+ };
314
+ // Annotate the CommonJS export names for ESM import in node:
315
+ 0 && (module.exports = {
316
+ LLMGateway
317
+ });
318
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\n\n/**\n * 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\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\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 }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const model = `route:${this.modelName}`;\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 const response = await this.client.chat.completions.create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n options?.responseFormat?.type === \"json\"\n ? options.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: options.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(options),\n },\n { headers },\n );\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\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: this.mapFinishReason(choice.finish_reason),\n usage: this.mapUsage(response.usage),\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n throw error;\n }\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const model = `route:${this.modelName}`;\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 options?.responseFormat?.type === \"json\"\n ? options.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: options.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(options),\n },\n { headers },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n throw error;\n }\n\n try {\n for await (const chunk of stream) {\n if (chunk.usage) {\n yield {\n type: \"finish\",\n reason: \"stop\",\n usage: this.mapUsage(chunk.usage),\n };\n }\n\n const choice = chunk.choices?.[0];\n if (!choice) continue;\n\n if (choice.finish_reason) {\n yield {\n type: \"finish\",\n reason: this.mapFinishReason(choice.finish_reason),\n };\n }\n\n const delta = choice.delta;\n if (!delta) continue;\n\n if (delta.content) {\n yield { type: \"content_delta\", delta: delta.content };\n }\n\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n if (tc.id && tc.function?.name) {\n yield {\n type: \"tool_call_start\",\n index: tc.index,\n id: tc.id,\n name: tc.function.name,\n };\n }\n\n if (tc.function?.arguments) {\n yield {\n type: \"tool_call_delta\",\n index: tc.index,\n args: tc.function.arguments,\n };\n }\n }\n }\n }\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n throw error;\n }\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-request-id\": randomUUID(),\n };\n\n const exec = ExecutionContext.getOrUndefined();\n if (exec?.executionId) {\n headers[\"x-execution-id\"] = exec.executionId;\n }\n\n const ctx = Tracing.getContext();\n if (ctx) {\n // IDs já estão em formato W3C (hex)\n headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;\n } else {\n // Still emit a root trace context so the gateway can correlate requests by traceId.\n const traceId = randomBytes(16).toString(\"hex\");\n const spanId = randomBytes(8).toString(\"hex\");\n headers.traceparent = `00-${traceId}-${spanId}-01`;\n }\n\n return headers;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { mimeType, source } = part.file;\n if (!mimeType.startsWith(\"image/\")) {\n throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);\n }\n\n const url = source.type === \"url\" ? source.url : `data:${mimeType};base64,${source.base64}`;\n\n return { type: \"image_url\", image_url: { url } };\n }\n\n if (part.type === \"image_url\") {\n return { type: \"image_url\", image_url: { url: part.image_url.url } };\n }\n\n // Exhaustive check - should never reach here with proper ContentPart\n const _exhaustive: never = part;\n throw new Error(`Unsupported content type: ${(_exhaustive as any).type}`);\n }\n}\n"],"mappings":"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","names":["OpenAIClient"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,287 @@
1
+ // src/index.ts
2
+ import OpenAIClient from "openai";
3
+ import { randomBytes, randomUUID } from "crypto";
4
+ import {
5
+ ConsoleLogger,
6
+ ExecutionContext,
7
+ Tracing
8
+ } from "@nebulaos/core";
9
+ var LLMGateway = class {
10
+ providerName = "llm-gateway";
11
+ modelName;
12
+ client;
13
+ baseUrl;
14
+ logger;
15
+ capabilities = {
16
+ inputFiles: {
17
+ mimeTypes: ["image/*"],
18
+ sources: ["url", "base64"]
19
+ }
20
+ };
21
+ constructor(config) {
22
+ this.modelName = config.model;
23
+ this.baseUrl = config.baseUrl || "http://localhost:4100";
24
+ const baseURL = this.baseUrl.endsWith("/v1") ? this.baseUrl : `${this.baseUrl}/v1`;
25
+ this.logger = new ConsoleLogger(config.logLevel || "info", "nebulaos/llm-gateway");
26
+ this.client = new OpenAIClient({
27
+ apiKey: config.apiKey,
28
+ baseURL,
29
+ ...config.clientOptions
30
+ });
31
+ }
32
+ async generate(messages, tools, options) {
33
+ const model = `route:${this.modelName}`;
34
+ const headers = this.buildGatewayHeaders();
35
+ this.logger.debug("LLM Gateway request", {
36
+ model,
37
+ baseUrl: this.baseUrl,
38
+ stream: false,
39
+ messageCount: messages.length,
40
+ toolCount: tools?.length ?? 0
41
+ });
42
+ try {
43
+ const response = await this.client.chat.completions.create(
44
+ {
45
+ model,
46
+ messages: this.convertMessages(messages),
47
+ tools: this.convertTools(tools),
48
+ response_format: options?.responseFormat?.type === "json" ? options.responseFormat.schema ? {
49
+ type: "json_schema",
50
+ json_schema: { name: "response", schema: options.responseFormat.schema }
51
+ } : { type: "json_object" } : void 0,
52
+ ...this.extractExtraOptions(options)
53
+ },
54
+ { headers }
55
+ );
56
+ this.logger.debug("LLM Gateway response", {
57
+ model,
58
+ finishReason: response.choices?.[0]?.finish_reason,
59
+ hasUsage: Boolean(response.usage)
60
+ });
61
+ const choice = response.choices[0];
62
+ const message = choice.message;
63
+ return {
64
+ content: message.content || "",
65
+ toolCalls: message.tool_calls?.map((tc) => ({
66
+ id: tc.id,
67
+ type: "function",
68
+ function: {
69
+ name: tc.function.name,
70
+ arguments: tc.function.arguments
71
+ }
72
+ })),
73
+ finishReason: this.mapFinishReason(choice.finish_reason),
74
+ usage: this.mapUsage(response.usage)
75
+ };
76
+ } catch (error) {
77
+ this.logger.error("LLM Gateway request failed", error, void 0, void 0);
78
+ throw error;
79
+ }
80
+ }
81
+ async *generateStream(messages, tools, options) {
82
+ const model = `route:${this.modelName}`;
83
+ const headers = this.buildGatewayHeaders();
84
+ this.logger.debug("LLM Gateway stream request", {
85
+ model,
86
+ baseUrl: this.baseUrl,
87
+ stream: true,
88
+ messageCount: messages.length,
89
+ toolCount: tools?.length ?? 0
90
+ });
91
+ let stream;
92
+ try {
93
+ stream = await this.client.chat.completions.create(
94
+ {
95
+ model,
96
+ messages: this.convertMessages(messages),
97
+ tools: this.convertTools(tools),
98
+ stream: true,
99
+ stream_options: { include_usage: true },
100
+ response_format: options?.responseFormat?.type === "json" ? options.responseFormat.schema ? {
101
+ type: "json_schema",
102
+ json_schema: { name: "response", schema: options.responseFormat.schema }
103
+ } : { type: "json_object" } : void 0,
104
+ ...this.extractExtraOptions(options)
105
+ },
106
+ { headers }
107
+ );
108
+ } catch (error) {
109
+ this.logger.error("LLM Gateway stream request failed", error, void 0, void 0);
110
+ throw error;
111
+ }
112
+ try {
113
+ for await (const chunk of stream) {
114
+ if (chunk.usage) {
115
+ yield {
116
+ type: "finish",
117
+ reason: "stop",
118
+ usage: this.mapUsage(chunk.usage)
119
+ };
120
+ }
121
+ const choice = chunk.choices?.[0];
122
+ if (!choice) continue;
123
+ if (choice.finish_reason) {
124
+ yield {
125
+ type: "finish",
126
+ reason: this.mapFinishReason(choice.finish_reason)
127
+ };
128
+ }
129
+ const delta = choice.delta;
130
+ if (!delta) continue;
131
+ if (delta.content) {
132
+ yield { type: "content_delta", delta: delta.content };
133
+ }
134
+ if (delta.tool_calls) {
135
+ for (const tc of delta.tool_calls) {
136
+ if (tc.id && tc.function?.name) {
137
+ yield {
138
+ type: "tool_call_start",
139
+ index: tc.index,
140
+ id: tc.id,
141
+ name: tc.function.name
142
+ };
143
+ }
144
+ if (tc.function?.arguments) {
145
+ yield {
146
+ type: "tool_call_delta",
147
+ index: tc.index,
148
+ args: tc.function.arguments
149
+ };
150
+ }
151
+ }
152
+ }
153
+ }
154
+ } catch (error) {
155
+ this.logger.error("LLM Gateway stream failed", error, void 0, void 0);
156
+ throw error;
157
+ }
158
+ }
159
+ // ==========================================================================
160
+ // Helpers (copied from OpenAI provider)
161
+ // ==========================================================================
162
+ extractExtraOptions(options) {
163
+ if (!options) return {};
164
+ const { responseFormat, ...rest } = options;
165
+ return rest;
166
+ }
167
+ buildGatewayHeaders() {
168
+ const headers = {
169
+ "x-request-id": randomUUID()
170
+ };
171
+ const exec = ExecutionContext.getOrUndefined();
172
+ if (exec?.executionId) {
173
+ headers["x-execution-id"] = exec.executionId;
174
+ }
175
+ const ctx = Tracing.getContext();
176
+ if (ctx) {
177
+ headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;
178
+ } else {
179
+ const traceId = randomBytes(16).toString("hex");
180
+ const spanId = randomBytes(8).toString("hex");
181
+ headers.traceparent = `00-${traceId}-${spanId}-01`;
182
+ }
183
+ return headers;
184
+ }
185
+ convertMessages(messages) {
186
+ const allowedToolCallIds = /* @__PURE__ */ new Set();
187
+ return messages.flatMap((m) => {
188
+ if (m.role === "tool") {
189
+ if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {
190
+ return [];
191
+ }
192
+ return {
193
+ role: "tool",
194
+ tool_call_id: m.tool_call_id,
195
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
196
+ // Tool output usually string
197
+ };
198
+ }
199
+ if (m.role === "assistant") {
200
+ let assistantContent = null;
201
+ if (typeof m.content === "string") {
202
+ assistantContent = m.content;
203
+ }
204
+ if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {
205
+ assistantContent = "";
206
+ }
207
+ if (m.tool_calls) {
208
+ m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));
209
+ }
210
+ return {
211
+ role: "assistant",
212
+ content: assistantContent,
213
+ tool_calls: m.tool_calls?.map((tc) => ({
214
+ id: tc.id,
215
+ type: "function",
216
+ function: {
217
+ name: tc.function.name,
218
+ arguments: tc.function.arguments
219
+ }
220
+ }))
221
+ };
222
+ }
223
+ const content = Array.isArray(
224
+ m.content
225
+ ) ? m.content.map((part) => this.convertContentPart(part)) : m.content || "";
226
+ return {
227
+ role: m.role,
228
+ content,
229
+ name: m.name
230
+ };
231
+ });
232
+ }
233
+ convertTools(tools) {
234
+ if (!tools || tools.length === 0) return void 0;
235
+ return tools.map((t) => ({
236
+ type: "function",
237
+ function: {
238
+ name: t.function.name,
239
+ description: t.function.description,
240
+ parameters: t.function.parameters
241
+ }
242
+ }));
243
+ }
244
+ mapUsage(usage) {
245
+ if (!usage) return void 0;
246
+ return {
247
+ promptTokens: usage.prompt_tokens,
248
+ completionTokens: usage.completion_tokens,
249
+ totalTokens: usage.total_tokens,
250
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens
251
+ };
252
+ }
253
+ mapFinishReason(reason) {
254
+ switch (reason) {
255
+ case "stop":
256
+ return "stop";
257
+ case "length":
258
+ return "length";
259
+ case "tool_calls":
260
+ return "tool_calls";
261
+ case "content_filter":
262
+ return "content_filter";
263
+ default:
264
+ return void 0;
265
+ }
266
+ }
267
+ convertContentPart(part) {
268
+ if (part.type === "text") return { type: "text", text: part.text };
269
+ if (part.type === "file") {
270
+ const { mimeType, source } = part.file;
271
+ if (!mimeType.startsWith("image/")) {
272
+ throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);
273
+ }
274
+ const url = source.type === "url" ? source.url : `data:${mimeType};base64,${source.base64}`;
275
+ return { type: "image_url", image_url: { url } };
276
+ }
277
+ if (part.type === "image_url") {
278
+ return { type: "image_url", image_url: { url: part.image_url.url } };
279
+ }
280
+ const _exhaustive = part;
281
+ throw new Error(`Unsupported content type: ${_exhaustive.type}`);
282
+ }
283
+ };
284
+ export {
285
+ LLMGateway
286
+ };
287
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import OpenAIClient, { ClientOptions } from \"openai\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport {\n IModel,\n Message,\n ProviderResponse,\n StreamChunk,\n ToolDefinitionForLLM,\n GenerateOptions,\n TokenUsage,\n ContentPart,\n ConsoleLogger,\n type LogLevel,\n ExecutionContext,\n Tracing,\n} from \"@nebulaos/core\";\n\n/**\n * 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\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\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 }\n\n async generate(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): Promise<ProviderResponse> {\n const model = `route:${this.modelName}`;\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 const response = await this.client.chat.completions.create(\n {\n model,\n messages: this.convertMessages(messages),\n tools: this.convertTools(tools),\n response_format:\n options?.responseFormat?.type === \"json\"\n ? options.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: options.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(options),\n },\n { headers },\n );\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\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: this.mapFinishReason(choice.finish_reason),\n usage: this.mapUsage(response.usage),\n };\n } catch (error) {\n this.logger.error(\"LLM Gateway request failed\", error, undefined, undefined);\n throw error;\n }\n }\n\n async *generateStream(\n messages: Message[],\n tools?: ToolDefinitionForLLM[],\n options?: GenerateOptions,\n ): AsyncGenerator<StreamChunk> {\n const model = `route:${this.modelName}`;\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 options?.responseFormat?.type === \"json\"\n ? options.responseFormat.schema\n ? {\n type: \"json_schema\",\n json_schema: { name: \"response\", schema: options.responseFormat.schema as any },\n }\n : { type: \"json_object\" }\n : undefined,\n ...this.extractExtraOptions(options),\n },\n { headers },\n );\n } catch (error) {\n this.logger.error(\"LLM Gateway stream request failed\", error, undefined, undefined);\n throw error;\n }\n\n try {\n for await (const chunk of stream) {\n if (chunk.usage) {\n yield {\n type: \"finish\",\n reason: \"stop\",\n usage: this.mapUsage(chunk.usage),\n };\n }\n\n const choice = chunk.choices?.[0];\n if (!choice) continue;\n\n if (choice.finish_reason) {\n yield {\n type: \"finish\",\n reason: this.mapFinishReason(choice.finish_reason),\n };\n }\n\n const delta = choice.delta;\n if (!delta) continue;\n\n if (delta.content) {\n yield { type: \"content_delta\", delta: delta.content };\n }\n\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n if (tc.id && tc.function?.name) {\n yield {\n type: \"tool_call_start\",\n index: tc.index,\n id: tc.id,\n name: tc.function.name,\n };\n }\n\n if (tc.function?.arguments) {\n yield {\n type: \"tool_call_delta\",\n index: tc.index,\n args: tc.function.arguments,\n };\n }\n }\n }\n }\n } catch (error) {\n this.logger.error(\"LLM Gateway stream failed\", error, undefined, undefined);\n throw error;\n }\n }\n\n // ==========================================================================\n // Helpers (copied from OpenAI provider)\n // ==========================================================================\n\n private extractExtraOptions(options?: GenerateOptions): Record<string, any> {\n if (!options) return {};\n const { responseFormat, ...rest } = options;\n return rest;\n }\n\n private buildGatewayHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"x-request-id\": randomUUID(),\n };\n\n const exec = ExecutionContext.getOrUndefined();\n if (exec?.executionId) {\n headers[\"x-execution-id\"] = exec.executionId;\n }\n\n const ctx = Tracing.getContext();\n if (ctx) {\n // IDs já estão em formato W3C (hex)\n headers.traceparent = `00-${ctx.traceId}-${ctx.spanId}-01`;\n } else {\n // Still emit a root trace context so the gateway can correlate requests by traceId.\n const traceId = randomBytes(16).toString(\"hex\");\n const spanId = randomBytes(8).toString(\"hex\");\n headers.traceparent = `00-${traceId}-${spanId}-01`;\n }\n\n return headers;\n }\n\n protected convertMessages(messages: Message[]): OpenAIClient.Chat.ChatCompletionMessageParam[] {\n // Ensure tools have a preceding assistant message with tool_calls\n const allowedToolCallIds = new Set<string>();\n\n return messages.flatMap((m) => {\n if (m.role === \"tool\") {\n // Skip orphan tool messages (no preceding tool_calls)\n if (!m.tool_call_id || !allowedToolCallIds.has(m.tool_call_id)) {\n return [];\n }\n\n return {\n role: \"tool\",\n tool_call_id: m.tool_call_id!,\n content: typeof m.content === \"string\" ? m.content : JSON.stringify(m.content), // Tool output usually string\n };\n }\n\n if (m.role === \"assistant\") {\n // OpenAI rules:\n // - content is required (string | null)\n // - if tool_calls is present, content can be null\n // - if tool_calls is NOT present, content must be string (cannot be null/empty if strict, but usually empty string is fine)\n\n let assistantContent: string | null = null;\n\n if (typeof m.content === \"string\") {\n assistantContent = m.content;\n }\n\n // If content is null/empty AND no tool_calls, force empty string to avoid API error\n if (!assistantContent && (!m.tool_calls || m.tool_calls.length === 0)) {\n assistantContent = \"\";\n }\n\n if (m.tool_calls) {\n m.tool_calls.forEach((tc) => allowedToolCallIds.add(tc.id));\n }\n\n return {\n role: \"assistant\",\n content: assistantContent,\n tool_calls: m.tool_calls?.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n })),\n };\n }\n\n // User / System with potential multimodal content\n const content: OpenAIClient.Chat.ChatCompletionContentPart[] | string = Array.isArray(\n m.content,\n )\n ? m.content.map((part) => this.convertContentPart(part))\n : m.content || \"\";\n\n return {\n role: m.role as \"system\" | \"user\",\n content,\n name: m.name,\n } as any;\n });\n }\n\n private convertTools(\n tools?: ToolDefinitionForLLM[],\n ): OpenAIClient.Chat.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n private mapUsage(usage?: OpenAIClient.CompletionUsage): TokenUsage | undefined {\n if (!usage) return undefined;\n return {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n reasoningTokens: (usage as any).completion_tokens_details?.reasoning_tokens,\n };\n }\n\n private mapFinishReason(reason: string | null): ProviderResponse[\"finishReason\"] {\n switch (reason) {\n case \"stop\":\n return \"stop\";\n case \"length\":\n return \"length\";\n case \"tool_calls\":\n return \"tool_calls\";\n case \"content_filter\":\n return \"content_filter\";\n default:\n return undefined;\n }\n }\n\n private convertContentPart(part: ContentPart): OpenAIClient.Chat.ChatCompletionContentPart {\n if (part.type === \"text\") return { type: \"text\", text: part.text };\n\n if (part.type === \"file\") {\n const { mimeType, source } = part.file;\n if (!mimeType.startsWith(\"image/\")) {\n throw new Error(`LLM Gateway: file mimeType '${mimeType}' is not supported yet`);\n }\n\n const url = source.type === \"url\" ? source.url : `data:${mimeType};base64,${source.base64}`;\n\n return { type: \"image_url\", image_url: { url } };\n }\n\n if (part.type === \"image_url\") {\n return { type: \"image_url\", image_url: { url: part.image_url.url } };\n }\n\n // Exhaustive check - should never reach here with proper ContentPart\n const _exhaustive: never = part;\n throw new Error(`Unsupported content type: ${(_exhaustive as any).type}`);\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@nebulaos/llm-gateway",
3
+ "version": "0.1.0",
4
+ "description": "NebulaOS LLM Gateway provider - OpenAI-compatible chat completions",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": false,
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/Psyco-AI-Tech/nebulaos.git",
22
+ "directory": "packages/providers/llm-gateway"
23
+ },
24
+ "license": "MIT",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "openai": "^4.52.7",
30
+ "@nebulaos/core": "0.1.1"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.0.0",
34
+ "tsup": "^8.0.0",
35
+ "typescript": "^5.0.0",
36
+ "vitest": "^1.0.0"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "test": "vitest",
42
+ "typecheck": "tsc --noEmit"
43
+ }
44
+ }