@imenam/unified-llm-client 1.4.1 → 2.0.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.
Files changed (35) hide show
  1. package/README.md +124 -24
  2. package/dist/common/types.d.ts +1 -0
  3. package/dist/common/types.d.ts.map +1 -1
  4. package/dist/core/LLMAdapter.d.ts +2 -1
  5. package/dist/core/LLMAdapter.d.ts.map +1 -1
  6. package/dist/core/LLMAdapter.js +9 -9
  7. package/dist/core/UnifiedLLMClient.d.ts +10 -2
  8. package/dist/core/UnifiedLLMClient.d.ts.map +1 -1
  9. package/dist/core/UnifiedLLMClient.js +49 -13
  10. package/dist/examples/api-key-resolver-example.d.ts +2 -0
  11. package/dist/examples/api-key-resolver-example.d.ts.map +1 -0
  12. package/dist/examples/api-key-resolver-example.js +65 -0
  13. package/dist/examples/example.js +6 -7
  14. package/dist/examples/proxy-api-key-resolver-example.d.ts +2 -0
  15. package/dist/examples/proxy-api-key-resolver-example.d.ts.map +1 -0
  16. package/dist/examples/proxy-api-key-resolver-example.js +75 -0
  17. package/dist/examples/proxy-example.d.ts +2 -0
  18. package/dist/examples/proxy-example.d.ts.map +1 -0
  19. package/dist/examples/proxy-example.js +120 -0
  20. package/dist/examples/proxy-server-start.d.ts +2 -0
  21. package/dist/examples/proxy-server-start.d.ts.map +1 -0
  22. package/dist/examples/proxy-server-start.js +30 -0
  23. package/dist/index.d.ts +5 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/proxy/ProxyPromptAdapter.d.ts +17 -0
  27. package/dist/proxy/ProxyPromptAdapter.d.ts.map +1 -0
  28. package/dist/proxy/ProxyPromptAdapter.js +238 -0
  29. package/dist/proxy/ProxyServer.d.ts +13 -0
  30. package/dist/proxy/ProxyServer.d.ts.map +1 -0
  31. package/dist/proxy/ProxyServer.js +243 -0
  32. package/dist/proxy/types.d.ts +32 -0
  33. package/dist/proxy/types.d.ts.map +1 -0
  34. package/dist/proxy/types.js +2 -0
  35. package/package.json +6 -2
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __rest = (this && this.__rest) || function (s, e) {
45
+ var t = {};
46
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
47
+ t[p] = s[p];
48
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
49
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
50
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
51
+ t[p[i]] = s[p[i]];
52
+ }
53
+ return t;
54
+ };
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.ProxyServer = void 0;
57
+ const http = __importStar(require("http"));
58
+ const os = __importStar(require("os"));
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const mime = __importStar(require("mime-types"));
62
+ const UnifiedLLMClient_1 = require("../core/UnifiedLLMClient");
63
+ class ProxyServer {
64
+ constructor(options) {
65
+ this.options = options;
66
+ this.server = http.createServer(this.handleRequest.bind(this));
67
+ }
68
+ start() {
69
+ return new Promise((resolve) => {
70
+ this.server.listen(this.options.port, () => {
71
+ console.log(`[ProxyServer] Listening on port ${this.options.port}`);
72
+ resolve();
73
+ });
74
+ });
75
+ }
76
+ stop() {
77
+ return new Promise((resolve, reject) => {
78
+ this.server.close((err) => {
79
+ if (err)
80
+ reject(err);
81
+ else
82
+ resolve();
83
+ });
84
+ });
85
+ }
86
+ handleRequest(req, res) {
87
+ if (req.method !== "POST" || req.url !== "/api/chat") {
88
+ res.writeHead(404, { "Content-Type": "application/json" });
89
+ res.end(JSON.stringify({ error: "Not found" }));
90
+ return;
91
+ }
92
+ if (this.options.apiKey) {
93
+ const providedKey = req.headers["x-api-key"];
94
+ if (providedKey !== this.options.apiKey) {
95
+ res.writeHead(401, { "Content-Type": "application/json" });
96
+ res.end(JSON.stringify({ error: "Unauthorized" }));
97
+ return;
98
+ }
99
+ }
100
+ let body = "";
101
+ req.on("data", (chunk) => {
102
+ body += chunk.toString();
103
+ });
104
+ req.on("end", () => {
105
+ this.processChat(body, res).catch((err) => {
106
+ console.error("[ProxyServer] Unhandled error:", err);
107
+ if (!res.headersSent) {
108
+ res.writeHead(500, { "Content-Type": "application/json" });
109
+ res.end(JSON.stringify({ error: err.message }));
110
+ }
111
+ });
112
+ });
113
+ req.on("error", (err) => {
114
+ console.error("[ProxyServer] Request error:", err);
115
+ });
116
+ }
117
+ processChat(body, res) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ let request;
120
+ try {
121
+ request = JSON.parse(body);
122
+ }
123
+ catch (_a) {
124
+ res.writeHead(400, { "Content-Type": "application/json" });
125
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
126
+ return;
127
+ }
128
+ let resolvedApiKey;
129
+ if (this.options.apiKeyResolver) {
130
+ if (request.apiKeyContext === undefined) {
131
+ res.writeHead(400, { "Content-Type": "application/json" });
132
+ res.end(JSON.stringify({ error: "apiKeyResolver is defined but no apiKeyContext was provided" }));
133
+ return;
134
+ }
135
+ const result = this.options.apiKeyResolver(request.apiKeyContext);
136
+ if (result === false) {
137
+ res.writeHead(403, { "Content-Type": "application/json" });
138
+ res.end(JSON.stringify({ error: "apiKeyResolver returned false" }));
139
+ return;
140
+ }
141
+ resolvedApiKey = result;
142
+ }
143
+ const tempFiles = [];
144
+ try {
145
+ const messages = yield this.decodeAttachedFiles(request.messages, tempFiles);
146
+ const client = new UnifiedLLMClient_1.UnifiedLLMClient(request.model, resolvedApiKey ? { apiKey: resolvedApiKey } : undefined);
147
+ if (request.stream) {
148
+ res.writeHead(200, {
149
+ "Content-Type": "text/event-stream",
150
+ "Cache-Control": "no-cache",
151
+ "Connection": "keep-alive",
152
+ });
153
+ try {
154
+ yield client.chat({
155
+ message: messages,
156
+ tools: request.tools,
157
+ toolChoice: request.toolChoice,
158
+ stream: true,
159
+ config: request.config,
160
+ onChunk: (chunk) => {
161
+ const { rawChunk: _rawChunk } = chunk, safeChunk = __rest(chunk, ["rawChunk"]);
162
+ this.writeSSE(res, { type: "chunk", data: safeChunk });
163
+ },
164
+ onFinalResponse: (finalResponse) => {
165
+ const { raw: _raw } = finalResponse, safeResponse = __rest(finalResponse, ["raw"]);
166
+ this.writeSSE(res, { type: "final", data: safeResponse });
167
+ },
168
+ });
169
+ }
170
+ catch (err) {
171
+ this.writeSSE(res, { type: "error", data: { message: err.message } });
172
+ }
173
+ finally {
174
+ res.end();
175
+ }
176
+ }
177
+ else {
178
+ try {
179
+ const response = yield client.chat({
180
+ message: messages,
181
+ tools: request.tools,
182
+ toolChoice: request.toolChoice,
183
+ stream: false,
184
+ config: request.config,
185
+ });
186
+ const { raw: _raw } = response, safeResponse = __rest(response, ["raw"]);
187
+ res.writeHead(200, { "Content-Type": "application/json" });
188
+ res.end(JSON.stringify(Object.assign(Object.assign({}, safeResponse), { raw: null })));
189
+ }
190
+ catch (err) {
191
+ res.writeHead(500, { "Content-Type": "application/json" });
192
+ res.end(JSON.stringify({ error: err.message, statusCode: 500 }));
193
+ }
194
+ }
195
+ }
196
+ finally {
197
+ for (const tmpFile of tempFiles) {
198
+ try {
199
+ fs.unlinkSync(tmpFile);
200
+ }
201
+ catch (_b) {
202
+ /* ignore cleanup errors */
203
+ }
204
+ }
205
+ }
206
+ });
207
+ }
208
+ decodeAttachedFiles(messages, tempFiles) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ return messages.map((message) => {
211
+ if (!message.attachedFile)
212
+ return message;
213
+ const files = Array.isArray(message.attachedFile)
214
+ ? message.attachedFile
215
+ : [message.attachedFile];
216
+ const decodedFiles = files.map((file) => {
217
+ if (typeof file !== "string")
218
+ return file;
219
+ if (file.startsWith("http://") || file.startsWith("https://"))
220
+ return file;
221
+ if (file.startsWith("data:")) {
222
+ const match = file.match(/^data:([^;]+);base64,(.+)$/);
223
+ if (!match)
224
+ return file;
225
+ const mimeType = match[1];
226
+ const base64Data = match[2];
227
+ const ext = mime.extension(mimeType) || "bin";
228
+ const tmpPath = path.join(os.tmpdir(), `proxy-upload-${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}`);
229
+ fs.writeFileSync(tmpPath, Buffer.from(base64Data, "base64"));
230
+ tempFiles.push(tmpPath);
231
+ return tmpPath;
232
+ }
233
+ return file;
234
+ });
235
+ return Object.assign(Object.assign({}, message), { attachedFile: decodedFiles.length === 1 ? decodedFiles[0] : decodedFiles });
236
+ });
237
+ });
238
+ }
239
+ writeSSE(res, event) {
240
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
241
+ }
242
+ }
243
+ exports.ProxyServer = ProxyServer;
@@ -0,0 +1,32 @@
1
+ import { ApiKeyResolver, ChatMessage, ToolDefinition, ChatResponse, Model } from "../common/types";
2
+ import { StreamedChatChunk } from "../core/LLMAdapter";
3
+ export interface ProxyServerOptions {
4
+ port: number;
5
+ apiKey?: string;
6
+ apiKeyResolver?: ApiKeyResolver;
7
+ }
8
+ export interface ProxyChatRequest {
9
+ model: Model;
10
+ messages: ChatMessage[];
11
+ tools?: Omit<ToolDefinition, "executor">[];
12
+ toolChoice?: string;
13
+ stream?: boolean;
14
+ config?: {
15
+ thinkingBudget?: number;
16
+ metadata?: Record<string, any>;
17
+ };
18
+ apiKeyContext?: unknown;
19
+ }
20
+ export type ProxySSEEvent = {
21
+ type: "chunk";
22
+ data: StreamedChatChunk;
23
+ } | {
24
+ type: "final";
25
+ data: Omit<ChatResponse, "raw">;
26
+ } | {
27
+ type: "error";
28
+ data: {
29
+ message: string;
30
+ };
31
+ };
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/proxy/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,EAAE,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE;QACP,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAChC,CAAC;IACF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imenam/unified-llm-client",
3
- "version": "1.4.1",
3
+ "version": "2.0.0",
4
4
  "description": "A unified interface for multiple LLM providers (OpenAI, Gemini, Anthropic, Qwen) with tool-calling, streaming, and audio support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,11 @@
20
20
  "example:audio": "node dist/examples/audio-example.js",
21
21
  "example:qwen": "node dist/examples/qwen-vl-example.js",
22
22
  "example:anthropic": "node dist/examples/anthropic-example.js",
23
- "example:abort": "node dist/examples/chat-abort-example.js"
23
+ "example:abort": "node dist/examples/chat-abort-example.js",
24
+ "example:proxy": "node dist/examples/proxy-example.js",
25
+ "example:proxy-api-key-resolver": "node dist/examples/proxy-api-key-resolver-example.js",
26
+ "example:api-key-resolver": "node dist/examples/api-key-resolver-example.js",
27
+ "proxy:start": "node dist/examples/proxy-server-start.js"
24
28
  },
25
29
  "keywords": [
26
30
  "llm",