@imenam/unified-llm-client 2.0.0 → 2.0.2
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 +30 -4
- package/dist/adapters/anthropic/AnthropicLogAdapter.d.ts.map +1 -1
- package/dist/adapters/anthropic/AnthropicLogAdapter.js +1 -5
- package/dist/adapters/gemini/GeminiLogAdapter.d.ts.map +1 -1
- package/dist/adapters/gemini/GeminiLogAdapter.js +1 -5
- package/dist/adapters/openai/OpenAiLogAdapter.d.ts.map +1 -1
- package/dist/adapters/openai/OpenAiLogAdapter.js +1 -5
- package/dist/adapters/qwen/QwenLogAdapter.d.ts.map +1 -1
- package/dist/adapters/qwen/QwenLogAdapter.js +1 -5
- package/dist/common/types.d.ts +3 -1
- package/dist/common/types.d.ts.map +1 -1
- package/dist/core/UnifiedLLMClient.d.ts.map +1 -1
- package/dist/core/UnifiedLLMClient.js +37 -28
- package/dist/examples/api-key-resolver-example.js +2 -2
- package/dist/examples/example-error-logging.d.ts +2 -0
- package/dist/examples/example-error-logging.d.ts.map +1 -0
- package/dist/examples/example-error-logging.js +62 -0
- package/dist/examples/proxy-api-key-resolver-example.js +2 -2
- package/dist/proxy/ProxyServer.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -207,6 +207,32 @@ const client = new UnifiedLLMClient(MODELS.GEMINI_FLASH_2_5, {
|
|
|
207
207
|
const response = await client.chat({ message: "Hello!" });
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
The proxy server also supports an `apiKeyResolver` for per-request dynamic key resolution (e.g. multi-tenant scenarios):
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { ProxyServer } from "@imenam/unified-llm-client";
|
|
214
|
+
import type { ApiKeyResolver } from "@imenam/unified-llm-client";
|
|
215
|
+
|
|
216
|
+
const resolver: ApiKeyResolver<{ userId: string }> = async (ctx) =>
|
|
217
|
+
(await userKeyStore.get(ctx.userId)) ?? false;
|
|
218
|
+
|
|
219
|
+
const server = new ProxyServer({ port: 3000, apiKey: "my-secret", apiKeyResolver: resolver });
|
|
220
|
+
await server.start();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Client passes the context — server resolves the key
|
|
225
|
+
const client = new UnifiedLLMClient(MODELS.GPT_4O_MINI, {
|
|
226
|
+
proxyUrl: "http://localhost:3000",
|
|
227
|
+
proxyApiKey: "my-secret",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const response = await client.chat({
|
|
231
|
+
message: "Hello!",
|
|
232
|
+
apiKeyContext: { userId: "user-123" },
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
210
236
|
## API Key Resolver
|
|
211
237
|
|
|
212
238
|
Dynamically resolve which API key to use for each request based on a custom context (e.g. per-user key, request metadata):
|
|
@@ -215,8 +241,8 @@ Dynamically resolve which API key to use for each request based on a custom cont
|
|
|
215
241
|
import { UnifiedLLMClient, MODELS } from "@imenam/unified-llm-client";
|
|
216
242
|
import type { ApiKeyResolver } from "@imenam/unified-llm-client";
|
|
217
243
|
|
|
218
|
-
const resolver: ApiKeyResolver<{ userId: string }> = (ctx) => {
|
|
219
|
-
const key = userKeyStore.get(ctx.userId);
|
|
244
|
+
const resolver: ApiKeyResolver<{ userId: string }> = async (ctx) => {
|
|
245
|
+
const key = await userKeyStore.get(ctx.userId);
|
|
220
246
|
return key ?? false; // return false to deny access
|
|
221
247
|
};
|
|
222
248
|
|
|
@@ -239,7 +265,7 @@ UnifiedLLMClient.setDefaultApiKeyResolver((ctx) => {
|
|
|
239
265
|
```
|
|
240
266
|
|
|
241
267
|
> The resolver is called on every `chat()` call. Returning `false` throws an access-denied error.
|
|
242
|
-
> The `apiKeyResolver` option is
|
|
268
|
+
> The client-side `apiKeyResolver` option is for direct mode only. In proxy mode, the resolver is defined on the `ProxyServer` — the client passes `apiKeyContext` and the server resolves the key.
|
|
243
269
|
|
|
244
270
|
## Cancellation
|
|
245
271
|
|
|
@@ -294,7 +320,7 @@ UnifiedLLMClient.setDefaultLogger((log) => {
|
|
|
294
320
|
| `options.proxyUrl` | `string` | Proxy server URL — activates proxy mode |
|
|
295
321
|
| `options.proxyApiKey` | `string` | Key sent in the `x-api-key` header (proxy mode) |
|
|
296
322
|
| `options.apiKey` | `string` | Static API key passed directly to the provider (overrides env var) |
|
|
297
|
-
| `options.apiKeyResolver` | `ApiKeyResolver` | Dynamic per-request key resolver (
|
|
323
|
+
| `options.apiKeyResolver` | `ApiKeyResolver` | Dynamic per-request key resolver (direct mode only — in proxy mode, set this on `ProxyServer`) |
|
|
298
324
|
|
|
299
325
|
### `client.chat(options)`
|
|
300
326
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnthropicLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/anthropic/AnthropicLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,mBAAoB,YAAW,UAAU;IACpD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"AnthropicLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/anthropic/AnthropicLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,mBAAoB,YAAW,UAAU;IACpD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;CAqCxC"}
|
|
@@ -17,11 +17,7 @@ class AnthropicLogAdapter {
|
|
|
17
17
|
provider: provider,
|
|
18
18
|
model,
|
|
19
19
|
prompt: messages,
|
|
20
|
-
output: {
|
|
21
|
-
content: response.content,
|
|
22
|
-
toolCalls: response.toolCalls,
|
|
23
|
-
finishReason: response.finishReason,
|
|
24
|
-
},
|
|
20
|
+
output: Object.assign({ content: response.content, toolCalls: response.toolCalls, finishReason: response.finishReason }, (response.error ? { error: response.error } : {})),
|
|
25
21
|
usage: {
|
|
26
22
|
promptTokens: inputTokens,
|
|
27
23
|
completionTokens: outputTokens,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GeminiLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/gemini/GeminiLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAI5D,qBAAa,gBAAiB,YAAW,UAAU;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"GeminiLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/gemini/GeminiLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAI5D,qBAAa,gBAAiB,YAAW,UAAU;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;CAuCxC"}
|
|
@@ -14,11 +14,7 @@ class GeminiLogAdapter {
|
|
|
14
14
|
provider: provider,
|
|
15
15
|
model: model,
|
|
16
16
|
prompt: messages,
|
|
17
|
-
output: {
|
|
18
|
-
content: response.content,
|
|
19
|
-
toolCalls: response.toolCalls,
|
|
20
|
-
finishReason: response.finishReason,
|
|
21
|
-
},
|
|
17
|
+
output: Object.assign({ content: response.content, toolCalls: response.toolCalls, finishReason: response.finishReason }, (response.error ? { error: response.error } : {})),
|
|
22
18
|
usage: {
|
|
23
19
|
promptTokens: (usage === null || usage === void 0 ? void 0 : usage.promptTokens) || 0,
|
|
24
20
|
completionTokens: (usage === null || usage === void 0 ? void 0 : usage.completionTokens) || 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenAiLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/openai/OpenAiLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,gBAAiB,YAAW,UAAU;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"OpenAiLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/openai/OpenAiLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,gBAAiB,YAAW,UAAU;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;CAgCxC"}
|
|
@@ -13,11 +13,7 @@ class OpenAiLogAdapter {
|
|
|
13
13
|
provider: provider,
|
|
14
14
|
model: model,
|
|
15
15
|
prompt: messages,
|
|
16
|
-
output: {
|
|
17
|
-
content: response.content,
|
|
18
|
-
toolCalls: response.toolCalls,
|
|
19
|
-
finishReason: response.finishReason,
|
|
20
|
-
},
|
|
16
|
+
output: Object.assign({ content: response.content, toolCalls: response.toolCalls, finishReason: response.finishReason }, (response.error ? { error: response.error } : {})),
|
|
21
17
|
usage: {
|
|
22
18
|
promptTokens: (usage === null || usage === void 0 ? void 0 : usage.promptTokens) || 0,
|
|
23
19
|
completionTokens: (usage === null || usage === void 0 ? void 0 : usage.completionTokens) || 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QwenLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/qwen/QwenLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,cAAe,YAAW,UAAU;IAC/C,YAAY,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE;IAapC,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"QwenLogAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/qwen/QwenLogAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAY,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,cAAe,YAAW,UAAU;IAC/C,YAAY,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE;IAapC,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;CAgCxC"}
|
|
@@ -25,11 +25,7 @@ class QwenLogAdapter {
|
|
|
25
25
|
provider: provider,
|
|
26
26
|
model: model,
|
|
27
27
|
prompt: this.removeImages(messages),
|
|
28
|
-
output: {
|
|
29
|
-
content: response.content,
|
|
30
|
-
toolCalls: response.toolCalls,
|
|
31
|
-
finishReason: response.finishReason,
|
|
32
|
-
},
|
|
28
|
+
output: Object.assign({ content: response.content, toolCalls: response.toolCalls, finishReason: response.finishReason }, (response.error ? { error: response.error } : {})),
|
|
33
29
|
usage: {
|
|
34
30
|
promptTokens: (usage === null || usage === void 0 ? void 0 : usage.promptTokens) || ((_a = raw === null || raw === void 0 ? void 0 : raw.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) || 0,
|
|
35
31
|
completionTokens: (usage === null || usage === void 0 ? void 0 : usage.completionTokens) || ((_b = raw === null || raw === void 0 ? void 0 : raw.usage) === null || _b === void 0 ? void 0 : _b.completion_tokens) || 0,
|
package/dist/common/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type Provider = "openai" | "gemini" | "qwen" | "anthropic";
|
|
2
|
-
export type ApiKeyResolver<T = unknown> = (context: T) => string | false;
|
|
2
|
+
export type ApiKeyResolver<T = unknown> = (context: T) => Promise<string | false> | string | false;
|
|
3
3
|
export type ChatMessageRole = "user" | "assistant" | "system" | "tool" | "function";
|
|
4
4
|
export interface ChatMessage {
|
|
5
5
|
tool_call_id?: string;
|
|
@@ -37,6 +37,7 @@ export interface ChatResponse {
|
|
|
37
37
|
finishReason?: string;
|
|
38
38
|
requestId?: string;
|
|
39
39
|
llmLogId?: number;
|
|
40
|
+
error?: string;
|
|
40
41
|
lastToolArguments?: ToolCall["arguments"];
|
|
41
42
|
}
|
|
42
43
|
export type Model = {
|
|
@@ -78,6 +79,7 @@ export interface UnifiedLog {
|
|
|
78
79
|
content: string | null;
|
|
79
80
|
toolCalls?: ToolCall[];
|
|
80
81
|
finishReason?: string;
|
|
82
|
+
error?: string;
|
|
81
83
|
};
|
|
82
84
|
usage: UnifiedUsage;
|
|
83
85
|
latencyMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAElE,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,MAAM,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UnifiedLLMClient.d.ts","sourceRoot":"","sources":["../../src/core/UnifiedLLMClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAY,cAAc,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAiB,oBAAoB,EAAqE,iBAAiB,EAAgB,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG3L,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAExC,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACrD,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,EAAE,CAAC;IAC9C,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,YAAY,KAAK,IAAI,CAAC;IACxD,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAA;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,UAAU,CAAC,CAAoC;IACvD,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAC,CAA0C;IACzD,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IAEtC,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAIvE,MAAM,CAAC,wBAAwB,CAAC,QAAQ,EAAE,cAAc;gBAI5C,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE;QAClC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,cAAc,CAAC;QAChC,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IA0BD,OAAO,CAAC,2BAA2B;IAM7B,IAAI,CAAC,EACT,OAAO,EACP,KAAK,EACL,UAAmB,EACnB,OAAO,EACP,eAAe,EACf,KAAK,EACL,MAAc,EACd,MAAM,EACN,MAAM,EACN,aAAa,EACd,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"UnifiedLLMClient.d.ts","sourceRoot":"","sources":["../../src/core/UnifiedLLMClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,7 +64,7 @@ class UnifiedLLMClient {
|
|
|
64
64
|
if (apiKeyContext === undefined) {
|
|
65
65
|
throw new Error("[ApiKeyResolver] A resolver is defined but no apiKeyContext was provided.");
|
|
66
66
|
}
|
|
67
|
-
const resolvedKey = effectiveResolver(apiKeyContext);
|
|
67
|
+
const resolvedKey = yield effectiveResolver(apiKeyContext);
|
|
68
68
|
if (resolvedKey === false)
|
|
69
69
|
throw new Error("[ApiKeyResolver] The resolver returned false — access denied.");
|
|
70
70
|
const resolved = LLMAdapter_1.PROVIDERS_CONFIG[this.model.provider](resolvedKey);
|
|
@@ -79,35 +79,44 @@ class UnifiedLLMClient {
|
|
|
79
79
|
this.checkNotImplementedFeatures(m);
|
|
80
80
|
const messages = yield Promise.all(messageArray.map(m => promptAdapter.formatMessage(m)));
|
|
81
81
|
let finalResponse;
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let aggregatedContent = "";
|
|
87
|
-
finalResponse = yield promptAdapter.streamFromProvider({
|
|
88
|
-
messages,
|
|
89
|
-
tools,
|
|
90
|
-
toolChoice,
|
|
91
|
-
model: this.model.modelName,
|
|
92
|
-
config: effectiveConfig,
|
|
93
|
-
onChunk: (chunk) => {
|
|
94
|
-
aggregatedContent += chunk.contentChunk || "";
|
|
95
|
-
if (onChunk) {
|
|
96
|
-
onChunk(chunk);
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
onFinalResponse: (resp) => {
|
|
100
|
-
resp.content = aggregatedContent;
|
|
101
|
-
if (onFinalResponse) {
|
|
102
|
-
onFinalResponse(resp);
|
|
103
|
-
}
|
|
82
|
+
try {
|
|
83
|
+
if (stream) {
|
|
84
|
+
if (!promptAdapter.streamFromProvider) {
|
|
85
|
+
throw new Error("The current adapter does not support streaming.");
|
|
104
86
|
}
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
let aggregatedContent = "";
|
|
88
|
+
finalResponse = yield promptAdapter.streamFromProvider({
|
|
89
|
+
messages,
|
|
90
|
+
tools,
|
|
91
|
+
toolChoice,
|
|
92
|
+
model: this.model.modelName,
|
|
93
|
+
config: effectiveConfig,
|
|
94
|
+
onChunk: (chunk) => {
|
|
95
|
+
aggregatedContent += chunk.contentChunk || "";
|
|
96
|
+
if (onChunk) {
|
|
97
|
+
onChunk(chunk);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
onFinalResponse: (resp) => {
|
|
101
|
+
resp.content = aggregatedContent;
|
|
102
|
+
if (onFinalResponse) {
|
|
103
|
+
onFinalResponse(resp);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
finalResponse.content = aggregatedContent;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const providerResponse = yield promptAdapter.sendToProvider(messages, tools, toolChoice, undefined, this.model.modelName, effectiveConfig);
|
|
111
|
+
finalResponse = outputAdapter.parse(providerResponse);
|
|
112
|
+
}
|
|
107
113
|
}
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
114
|
+
catch (error) {
|
|
115
|
+
const latencyMs = Date.now() - startTime;
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
117
|
+
const errorResponse = { content: null, raw: null, error: errorMessage };
|
|
118
|
+
yield this.handleLogging(messageArray, errorResponse, latencyMs, effectiveConfig === null || effectiveConfig === void 0 ? void 0 : effectiveConfig.metadata, onLog);
|
|
119
|
+
throw error;
|
|
111
120
|
}
|
|
112
121
|
const latencyMs = Date.now() - startTime;
|
|
113
122
|
yield this.handleLogging(messageArray, finalResponse, latencyMs, effectiveConfig === null || effectiveConfig === void 0 ? void 0 : effectiveConfig.metadata, onLog);
|
|
@@ -22,7 +22,7 @@ const __1 = require("..");
|
|
|
22
22
|
* Exemple de resolver : renvoie la clé pour un tenant connu, sinon `false` (refus).
|
|
23
23
|
* En production, vous chargeriez la clé depuis une base, un vault, etc.
|
|
24
24
|
*/
|
|
25
|
-
const demoResolver = (ctx) => {
|
|
25
|
+
const demoResolver = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
26
|
const { tenantId } = ctx;
|
|
27
27
|
const byTenant = {
|
|
28
28
|
// Un seul slot de démo : même clé env que la config Gemini par défaut.
|
|
@@ -32,7 +32,7 @@ const demoResolver = (ctx) => {
|
|
|
32
32
|
if (!key)
|
|
33
33
|
return false;
|
|
34
34
|
return key;
|
|
35
|
-
};
|
|
35
|
+
});
|
|
36
36
|
function main() {
|
|
37
37
|
return __awaiter(this, void 0, void 0, function* () {
|
|
38
38
|
if (!process.env.GOOGLE_API_KEY) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example-error-logging.d.ts","sourceRoot":"","sources":["../../src/examples/example-error-logging.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const __1 = require("..");
|
|
13
|
+
const logger = (log) => {
|
|
14
|
+
var _a;
|
|
15
|
+
const status = log.output.error ? "ERROR" : "OK";
|
|
16
|
+
console.log(`\n[${status}] ${log.provider} / ${log.model} — ${log.latencyMs}ms`);
|
|
17
|
+
if (log.output.error) {
|
|
18
|
+
console.log("***** error captured in logger *****");
|
|
19
|
+
console.log(` error : ${log.output.error}`);
|
|
20
|
+
console.log(` prompt : ${JSON.stringify(log.prompt.map(m => m.content))}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log("***** content captured in logger *****");
|
|
24
|
+
console.log(` content : ${(_a = log.output.content) === null || _a === void 0 ? void 0 : _a.slice(0, 80)}...`);
|
|
25
|
+
console.log(` tokens : ${log.usage.totalTokens}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function run() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
// --- Cas 1 : clé API invalide → erreur provider loggée ---
|
|
31
|
+
console.log("=== Test 1 : clé API invalide ===");
|
|
32
|
+
const badKeyClient = new __1.UnifiedLLMClient(__1.MODELS.GEMINI_FLASH_2_5, {
|
|
33
|
+
apiKey: "INVALID_KEY",
|
|
34
|
+
logger,
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
yield badKeyClient.chat({ message: "Bonjour !" });
|
|
38
|
+
}
|
|
39
|
+
catch (_a) {
|
|
40
|
+
console.log(" (exception capturée par l'appelant — comportement attendu)");
|
|
41
|
+
}
|
|
42
|
+
// --- Cas 2 : appel réussi pour montrer que le logger fonctionne normalement ---
|
|
43
|
+
console.log("\n=== Test 2 : appel réussi ===");
|
|
44
|
+
const client = new __1.UnifiedLLMClient(__1.MODELS.GEMINI_FLASH_2_5, { logger });
|
|
45
|
+
yield client.chat({ message: "Dis-moi bonjour en une phrase." });
|
|
46
|
+
// --- Cas 3 : erreur loggée via onLog (logger ad-hoc par appel) ---
|
|
47
|
+
console.log("\n=== Test 3 : clé invalide avec onLog ===");
|
|
48
|
+
const badKeyClient2 = new __1.UnifiedLLMClient(__1.MODELS.GEMINI_FLASH_2_5, {
|
|
49
|
+
apiKey: "ANOTHER_BAD_KEY",
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
yield badKeyClient2.chat({
|
|
53
|
+
message: "Test onLog error",
|
|
54
|
+
onLog: logger,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (_b) {
|
|
58
|
+
console.log(" (exception capturée par l'appelant — comportement attendu)");
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
run().catch(console.error);
|
|
@@ -29,13 +29,13 @@ function main() {
|
|
|
29
29
|
const server = new __1.ProxyServer({
|
|
30
30
|
port: PROXY_PORT,
|
|
31
31
|
apiKey: PROXY_API_KEY,
|
|
32
|
-
apiKeyResolver: (ctx) => {
|
|
32
|
+
apiKeyResolver: (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
33
33
|
const { tenantId } = ctx;
|
|
34
34
|
if (tenantId === "demo") {
|
|
35
35
|
return process.env.GOOGLE_API_KEY;
|
|
36
36
|
}
|
|
37
37
|
return false;
|
|
38
|
-
},
|
|
38
|
+
}),
|
|
39
39
|
});
|
|
40
40
|
yield server.start();
|
|
41
41
|
console.log(`[example] Proxy listening on http://localhost:${PROXY_PORT}\n`);
|
|
@@ -132,7 +132,7 @@ class ProxyServer {
|
|
|
132
132
|
res.end(JSON.stringify({ error: "apiKeyResolver is defined but no apiKeyContext was provided" }));
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
|
-
const result = this.options.apiKeyResolver(request.apiKeyContext);
|
|
135
|
+
const result = yield this.options.apiKeyResolver(request.apiKeyContext);
|
|
136
136
|
if (result === false) {
|
|
137
137
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
138
138
|
res.end(JSON.stringify({ error: "apiKeyResolver returned false" }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imenam/unified-llm-client",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
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",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"example:proxy": "node dist/examples/proxy-example.js",
|
|
25
25
|
"example:proxy-api-key-resolver": "node dist/examples/proxy-api-key-resolver-example.js",
|
|
26
26
|
"example:api-key-resolver": "node dist/examples/api-key-resolver-example.js",
|
|
27
|
-
"proxy:start": "node dist/examples/proxy-server-start.js"
|
|
27
|
+
"proxy:start": "node dist/examples/proxy-server-start.js",
|
|
28
|
+
"example:error-logging": "node dist/examples/example-error-logging.js"
|
|
28
29
|
},
|
|
29
30
|
"keywords": [
|
|
30
31
|
"llm",
|