@tyvm/knowhow 0.0.156 → 0.0.157
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/package.json +1 -1
- package/src/agents/base/base.ts +15 -5
- package/src/chat/modules/AgentModule.ts +3 -3
- package/src/chat/modules/RemoteSyncModule.ts +45 -17
- package/src/clients/anthropic.ts +156 -79
- package/src/clients/attribution.ts +53 -0
- package/src/clients/http.ts +211 -84
- package/src/clients/index.ts +21 -9
- package/src/clients/openai.ts +441 -220
- package/src/clients/openrouter.ts +10 -3
- package/src/clients/pricing/openai.ts +15 -1
- package/src/clients/pricing/types.ts +15 -0
- package/src/clients/responseMetadata.ts +221 -0
- package/src/clients/types.ts +49 -4
- package/tests/unit/clients/anthropic.responseMetadata.test.ts +81 -0
- package/tests/unit/clients/attribution.test.ts +185 -0
- package/tests/unit/clients/http.responseMetadata.test.ts +86 -0
- package/tests/unit/clients/openai.responseMetadata.test.ts +90 -0
- package/tests/unit/clients/responseMetadata.test.ts +123 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.js +14 -4
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +3 -3
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +1 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -7
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -0
- package/ts_build/src/clients/anthropic.js +83 -38
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/attribution.d.ts +2 -0
- package/ts_build/src/clients/attribution.js +32 -0
- package/ts_build/src/clients/attribution.js.map +1 -0
- package/ts_build/src/clients/http.js +100 -20
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +2 -1
- package/ts_build/src/clients/index.js +6 -2
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +2 -1
- package/ts_build/src/clients/openai.js +225 -99
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/openrouter.d.ts +2 -2
- package/ts_build/src/clients/openrouter.js +6 -2
- package/ts_build/src/clients/openrouter.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +15 -1
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/types.d.ts +6 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -1
- package/ts_build/src/clients/responseMetadata.d.ts +15 -0
- package/ts_build/src/clients/responseMetadata.js +141 -0
- package/ts_build/src/clients/responseMetadata.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +18 -1
- package/ts_build/src/clients/types.js +2 -1
- package/ts_build/src/clients/types.js.map +1 -1
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js +77 -0
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/attribution.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/attribution.test.js +153 -0
- package/ts_build/tests/unit/clients/attribution.test.js.map +1 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.js +77 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.js +86 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.js +96 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.js.map +1 -0
package/src/clients/http.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import http, {
|
|
1
|
+
import http, {
|
|
2
|
+
HTTP_UNAUTHORIZED_HANDLER,
|
|
3
|
+
HttpError,
|
|
4
|
+
RefreshableHeaders,
|
|
5
|
+
} from "../utils/http";
|
|
6
|
+
import { emitResponseMetadata } from "./responseMetadata";
|
|
7
|
+
|
|
2
8
|
import {
|
|
3
9
|
GenericClient,
|
|
4
10
|
CompletionOptions,
|
|
@@ -51,14 +57,23 @@ export class HttpClient implements GenericClient {
|
|
|
51
57
|
* The default implementation decodes the JWT `exp` claim (< 5 min window).
|
|
52
58
|
*/
|
|
53
59
|
needsRefresh(): boolean {
|
|
54
|
-
const authHeader = (this.headers as Record<string, string>)[
|
|
60
|
+
const authHeader = (this.headers as Record<string, string>)[
|
|
61
|
+
"Authorization"
|
|
62
|
+
];
|
|
55
63
|
if (!authHeader) return false;
|
|
56
|
-
const token = authHeader.startsWith("Bearer ")
|
|
64
|
+
const token = authHeader.startsWith("Bearer ")
|
|
65
|
+
? authHeader.slice(7)
|
|
66
|
+
: authHeader;
|
|
57
67
|
try {
|
|
58
68
|
const parts = token.split(".");
|
|
59
69
|
if (parts.length !== 3) return false;
|
|
60
|
-
const payload = JSON.parse(
|
|
61
|
-
|
|
70
|
+
const payload = JSON.parse(
|
|
71
|
+
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
72
|
+
);
|
|
73
|
+
return (
|
|
74
|
+
typeof payload.exp === "number" &&
|
|
75
|
+
payload.exp * 1000 - Date.now() < 5 * 60 * 1000
|
|
76
|
+
);
|
|
62
77
|
} catch {
|
|
63
78
|
return false;
|
|
64
79
|
}
|
|
@@ -81,7 +96,7 @@ export class HttpClient implements GenericClient {
|
|
|
81
96
|
Object.defineProperty(this.headers, HTTP_UNAUTHORIZED_HANDLER, {
|
|
82
97
|
enumerable: false,
|
|
83
98
|
configurable: true,
|
|
84
|
-
get: () => this.refreshToken ? () => this.refreshToken!() : undefined,
|
|
99
|
+
get: () => (this.refreshToken ? () => this.refreshToken!() : undefined),
|
|
85
100
|
});
|
|
86
101
|
this.timeout = options.timeout ?? 30000;
|
|
87
102
|
this.extra_body = options.extra_body ?? {};
|
|
@@ -177,7 +192,9 @@ export class HttpClient implements GenericClient {
|
|
|
177
192
|
* Returns the pricing entry for a specific model, or all pricing entries if no model is given.
|
|
178
193
|
* Returns undefined for a specific model if no pricing is known.
|
|
179
194
|
*/
|
|
180
|
-
getPricing(
|
|
195
|
+
getPricing(
|
|
196
|
+
model?: string
|
|
197
|
+
): ModelPricing | Record<string, ModelPricing> | undefined {
|
|
181
198
|
if (model !== undefined) {
|
|
182
199
|
return this.pricingMap[model];
|
|
183
200
|
}
|
|
@@ -190,19 +207,27 @@ export class HttpClient implements GenericClient {
|
|
|
190
207
|
*/
|
|
191
208
|
calculateCost(
|
|
192
209
|
model: string,
|
|
193
|
-
usage:
|
|
210
|
+
usage:
|
|
211
|
+
| {
|
|
212
|
+
prompt_tokens?: number;
|
|
213
|
+
completion_tokens?: number;
|
|
214
|
+
prompt_tokens_details?: { cached_tokens?: number };
|
|
215
|
+
}
|
|
216
|
+
| undefined
|
|
194
217
|
): number | undefined {
|
|
195
218
|
if (!usage) return undefined;
|
|
196
219
|
const pricing = this.pricingMap[model];
|
|
197
220
|
if (!pricing) return undefined;
|
|
198
221
|
|
|
199
|
-
const cachedInputTokens =
|
|
200
|
-
usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
222
|
+
const cachedInputTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
201
223
|
const inputTokens = usage.prompt_tokens ?? 0;
|
|
202
224
|
const outputTokens = usage.completion_tokens ?? 0;
|
|
203
225
|
|
|
204
|
-
const cachedInputCost =
|
|
205
|
-
|
|
226
|
+
const cachedInputCost =
|
|
227
|
+
(cachedInputTokens * (pricing.cache_hit ?? pricing.cached_input ?? 0)) /
|
|
228
|
+
1e6;
|
|
229
|
+
const inputCost =
|
|
230
|
+
((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
|
|
206
231
|
const outputCost = (outputTokens * (pricing.output ?? 0)) / 1e6;
|
|
207
232
|
|
|
208
233
|
return cachedInputCost + inputCost + outputCost;
|
|
@@ -213,12 +238,21 @@ export class HttpClient implements GenericClient {
|
|
|
213
238
|
* Used by AIClient.resolveClient to honour per-provider config overrides
|
|
214
239
|
* even when the client is created via a known clientClass (e.g. nvidia, groq).
|
|
215
240
|
*/
|
|
216
|
-
setOptions(
|
|
241
|
+
setOptions(
|
|
242
|
+
options: Omit<HttpClientOptions, "headers"> & {
|
|
243
|
+
headers?: Record<string, string>;
|
|
244
|
+
}
|
|
245
|
+
) {
|
|
217
246
|
if (options.timeout !== undefined) this.timeout = options.timeout;
|
|
218
247
|
if (options.extra_body !== undefined) this.extra_body = options.extra_body;
|
|
219
248
|
if (options.headers) {
|
|
220
249
|
// Mutate in-place to preserve the HTTP_UNAUTHORIZED_HANDLER symbol property.
|
|
221
|
-
Object.
|
|
250
|
+
for (const [name, value] of Object.entries(options.headers)) {
|
|
251
|
+
const existingName = Object.keys(this.headers).find(
|
|
252
|
+
(candidate) => candidate.toLowerCase() === name.toLowerCase()
|
|
253
|
+
);
|
|
254
|
+
this.headers[existingName ?? name] = value;
|
|
255
|
+
}
|
|
222
256
|
}
|
|
223
257
|
}
|
|
224
258
|
|
|
@@ -240,8 +274,16 @@ export class HttpClient implements GenericClient {
|
|
|
240
274
|
): Promise<CompletionResponse> {
|
|
241
275
|
await this.refreshIfNeeded();
|
|
242
276
|
return this.withRetry(async () => {
|
|
277
|
+
const {
|
|
278
|
+
signal,
|
|
279
|
+
timeout,
|
|
280
|
+
maxRetries,
|
|
281
|
+
backoffMs,
|
|
282
|
+
onResponseMetadata,
|
|
283
|
+
...requestOptions
|
|
284
|
+
} = options;
|
|
243
285
|
const body = {
|
|
244
|
-
...
|
|
286
|
+
...requestOptions,
|
|
245
287
|
model: options.model,
|
|
246
288
|
messages: options.messages,
|
|
247
289
|
max_tokens: options.max_tokens || 4000,
|
|
@@ -253,11 +295,29 @@ export class HttpClient implements GenericClient {
|
|
|
253
295
|
}),
|
|
254
296
|
};
|
|
255
297
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
298
|
+
let response;
|
|
299
|
+
try {
|
|
300
|
+
response = await http.post(
|
|
301
|
+
`${this.baseUrl}/v1/chat/completions`,
|
|
302
|
+
body,
|
|
303
|
+
{
|
|
304
|
+
headers: this.headers as Record<string, string>,
|
|
305
|
+
timeout: this.timeout,
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error instanceof HttpError) {
|
|
310
|
+
emitResponseMetadata(options, {
|
|
311
|
+
statusCode: error.status,
|
|
312
|
+
headers: error.response.headers,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
emitResponseMetadata(options, {
|
|
318
|
+
statusCode: response.status,
|
|
319
|
+
headers: response.headers,
|
|
320
|
+
});
|
|
261
321
|
|
|
262
322
|
const data = response.data;
|
|
263
323
|
|
|
@@ -276,7 +336,8 @@ export class HttpClient implements GenericClient {
|
|
|
276
336
|
})),
|
|
277
337
|
model: data.model,
|
|
278
338
|
usage: data.usage,
|
|
279
|
-
usd_cost:
|
|
339
|
+
usd_cost:
|
|
340
|
+
data.usd_cost ?? this.calculateCost(options.model, data.usage),
|
|
280
341
|
};
|
|
281
342
|
});
|
|
282
343
|
}
|
|
@@ -289,8 +350,16 @@ export class HttpClient implements GenericClient {
|
|
|
289
350
|
options: CompletionOptions
|
|
290
351
|
): AsyncGenerator<StreamChunk> {
|
|
291
352
|
await this.refreshIfNeeded();
|
|
353
|
+
const {
|
|
354
|
+
signal,
|
|
355
|
+
timeout,
|
|
356
|
+
maxRetries,
|
|
357
|
+
backoffMs,
|
|
358
|
+
onResponseMetadata,
|
|
359
|
+
...requestOptions
|
|
360
|
+
} = options;
|
|
292
361
|
const body = {
|
|
293
|
-
...
|
|
362
|
+
...requestOptions,
|
|
294
363
|
model: options.model,
|
|
295
364
|
messages: options.messages,
|
|
296
365
|
max_tokens: options.max_tokens || 4000,
|
|
@@ -308,11 +377,15 @@ export class HttpClient implements GenericClient {
|
|
|
308
377
|
headers: {
|
|
309
378
|
...(this.headers as Record<string, string>),
|
|
310
379
|
"Content-Type": "application/json",
|
|
311
|
-
|
|
380
|
+
Accept: "text/event-stream",
|
|
312
381
|
},
|
|
313
382
|
body: JSON.stringify(body),
|
|
314
383
|
signal: options.signal,
|
|
315
384
|
});
|
|
385
|
+
emitResponseMetadata(options, {
|
|
386
|
+
statusCode: response.status,
|
|
387
|
+
headers: response.headers,
|
|
388
|
+
});
|
|
316
389
|
|
|
317
390
|
if (!response.ok) {
|
|
318
391
|
const text = await response.text().catch(() => "");
|
|
@@ -376,58 +449,76 @@ export class HttpClient implements GenericClient {
|
|
|
376
449
|
await this.refreshIfNeeded();
|
|
377
450
|
return this.withRetry(async () => {
|
|
378
451
|
// Extract system messages as instructions
|
|
379
|
-
const systemMessages = options.messages.filter(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
.
|
|
384
|
-
|
|
452
|
+
const systemMessages = options.messages.filter(
|
|
453
|
+
(m) => m.role === "system"
|
|
454
|
+
);
|
|
455
|
+
const nonSystemMessages = options.messages.filter(
|
|
456
|
+
(m) => m.role !== "system"
|
|
457
|
+
);
|
|
458
|
+
const instructions =
|
|
459
|
+
systemMessages
|
|
460
|
+
.map((m) => (typeof m.content === "string" ? m.content : ""))
|
|
461
|
+
.join("\n")
|
|
462
|
+
.trim() || undefined;
|
|
385
463
|
|
|
386
464
|
// Convert messages to Responses API input format
|
|
387
465
|
// Responses API hard limit: 10,485,760 chars per function_call_output.output
|
|
388
466
|
const MAX_TOOL_OUTPUT = 10_485_000;
|
|
389
467
|
const truncateToolOutput = (s: string) =>
|
|
390
468
|
s.length > MAX_TOOL_OUTPUT
|
|
391
|
-
? s.slice(0, MAX_TOOL_OUTPUT) +
|
|
469
|
+
? s.slice(0, MAX_TOOL_OUTPUT) +
|
|
470
|
+
"\n\n[TRUNCATED: output exceeded 10MB API limit]"
|
|
392
471
|
: s;
|
|
393
|
-
const input: any[] = nonSystemMessages
|
|
394
|
-
|
|
472
|
+
const input: any[] = nonSystemMessages
|
|
473
|
+
.map((msg) => {
|
|
474
|
+
if (msg.role === "tool") {
|
|
475
|
+
return {
|
|
476
|
+
type: "function_call_output",
|
|
477
|
+
call_id: msg.tool_call_id,
|
|
478
|
+
output: truncateToolOutput(
|
|
479
|
+
typeof msg.content === "string"
|
|
480
|
+
? msg.content
|
|
481
|
+
: JSON.stringify(msg.content)
|
|
482
|
+
),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (msg.role === "assistant" && msg.tool_calls?.length) {
|
|
486
|
+
// Re-inject any captured reasoning items (encrypted_content) BEFORE
|
|
487
|
+
// the function_call items so the model preserves its chain-of-thought
|
|
488
|
+
// across tool-use turns instead of re-deriving it each turn.
|
|
489
|
+
const reasoningItems = this.getReasoningItems(msg);
|
|
490
|
+
return [
|
|
491
|
+
...reasoningItems,
|
|
492
|
+
...(msg.tool_calls as any[]).map((tc: any) => ({
|
|
493
|
+
type: "function_call",
|
|
494
|
+
id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
|
|
495
|
+
call_id: tc.id,
|
|
496
|
+
name: tc.function.name,
|
|
497
|
+
arguments: tc.function.arguments,
|
|
498
|
+
})),
|
|
499
|
+
];
|
|
500
|
+
}
|
|
501
|
+
if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
|
|
502
|
+
return [
|
|
503
|
+
...this.getReasoningItems(msg),
|
|
504
|
+
{
|
|
505
|
+
role: msg.role,
|
|
506
|
+
content:
|
|
507
|
+
typeof msg.content === "string"
|
|
508
|
+
? msg.content
|
|
509
|
+
: JSON.stringify(msg.content),
|
|
510
|
+
},
|
|
511
|
+
];
|
|
512
|
+
}
|
|
395
513
|
return {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
514
|
+
role: msg.role,
|
|
515
|
+
content:
|
|
516
|
+
typeof msg.content === "string"
|
|
517
|
+
? msg.content
|
|
518
|
+
: JSON.stringify(msg.content),
|
|
399
519
|
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Re-inject any captured reasoning items (encrypted_content) BEFORE
|
|
403
|
-
// the function_call items so the model preserves its chain-of-thought
|
|
404
|
-
// across tool-use turns instead of re-deriving it each turn.
|
|
405
|
-
const reasoningItems = this.getReasoningItems(msg);
|
|
406
|
-
return [
|
|
407
|
-
...reasoningItems,
|
|
408
|
-
...(msg.tool_calls as any[]).map((tc: any) => ({
|
|
409
|
-
type: "function_call",
|
|
410
|
-
id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
|
|
411
|
-
call_id: tc.id,
|
|
412
|
-
name: tc.function.name,
|
|
413
|
-
arguments: tc.function.arguments,
|
|
414
|
-
})),
|
|
415
|
-
];
|
|
416
|
-
}
|
|
417
|
-
if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
|
|
418
|
-
return [
|
|
419
|
-
...this.getReasoningItems(msg),
|
|
420
|
-
{
|
|
421
|
-
role: msg.role,
|
|
422
|
-
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
423
|
-
},
|
|
424
|
-
];
|
|
425
|
-
}
|
|
426
|
-
return {
|
|
427
|
-
role: msg.role,
|
|
428
|
-
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
429
|
-
};
|
|
430
|
-
}).flat();
|
|
520
|
+
})
|
|
521
|
+
.flat();
|
|
431
522
|
|
|
432
523
|
const tools = options.tools?.map((tool) => ({
|
|
433
524
|
type: "function" as const,
|
|
@@ -461,11 +552,25 @@ export class HttpClient implements GenericClient {
|
|
|
461
552
|
...this.extra_body,
|
|
462
553
|
};
|
|
463
554
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
body,
|
|
467
|
-
|
|
468
|
-
|
|
555
|
+
let response;
|
|
556
|
+
try {
|
|
557
|
+
response = await http.post(`${this.baseUrl}/v1/responses`, body, {
|
|
558
|
+
headers: this.headers as Record<string, string>,
|
|
559
|
+
timeout: this.timeout,
|
|
560
|
+
});
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error instanceof HttpError) {
|
|
563
|
+
emitResponseMetadata(options, {
|
|
564
|
+
statusCode: error.status,
|
|
565
|
+
headers: error.response.headers,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
emitResponseMetadata(options, {
|
|
571
|
+
statusCode: response.status,
|
|
572
|
+
headers: response.headers,
|
|
573
|
+
});
|
|
469
574
|
|
|
470
575
|
const data = response.data;
|
|
471
576
|
|
|
@@ -547,14 +652,32 @@ export class HttpClient implements GenericClient {
|
|
|
547
652
|
async createEmbedding(options: EmbeddingOptions): Promise<EmbeddingResponse> {
|
|
548
653
|
await this.refreshIfNeeded();
|
|
549
654
|
return this.withRetry(async () => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
655
|
+
let response;
|
|
656
|
+
try {
|
|
657
|
+
response = await http.post(
|
|
658
|
+
`${this.baseUrl}/v1/embeddings`,
|
|
659
|
+
{
|
|
660
|
+
model: options.model,
|
|
661
|
+
input: options.input,
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
headers: this.headers as Record<string, string>,
|
|
665
|
+
timeout: this.timeout,
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (error instanceof HttpError) {
|
|
670
|
+
emitResponseMetadata(options, {
|
|
671
|
+
statusCode: error.status,
|
|
672
|
+
headers: error.response.headers,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
throw error;
|
|
676
|
+
}
|
|
677
|
+
emitResponseMetadata(options, {
|
|
678
|
+
statusCode: response.status,
|
|
679
|
+
headers: response.headers,
|
|
680
|
+
});
|
|
558
681
|
|
|
559
682
|
const data = response.data;
|
|
560
683
|
|
|
@@ -567,7 +690,8 @@ export class HttpClient implements GenericClient {
|
|
|
567
690
|
data: data.data,
|
|
568
691
|
model: options.model,
|
|
569
692
|
usage: data.usage,
|
|
570
|
-
usd_cost:
|
|
693
|
+
usd_cost:
|
|
694
|
+
data.usd_cost ?? this.calculateCost(options.model, data.usage),
|
|
571
695
|
};
|
|
572
696
|
});
|
|
573
697
|
}
|
|
@@ -575,10 +699,13 @@ export class HttpClient implements GenericClient {
|
|
|
575
699
|
async getModels(type = "all") {
|
|
576
700
|
await this.refreshIfNeeded();
|
|
577
701
|
return this.withRetry(async () => {
|
|
578
|
-
const response = await http.get(
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
702
|
+
const response = await http.get(
|
|
703
|
+
`${this.baseUrl}/v1/models?type=${type}`,
|
|
704
|
+
{
|
|
705
|
+
headers: this.headers as Record<string, string>,
|
|
706
|
+
timeout: this.timeout,
|
|
707
|
+
}
|
|
708
|
+
);
|
|
582
709
|
|
|
583
710
|
const data = response.data?.data;
|
|
584
711
|
|
package/src/clients/index.ts
CHANGED
|
@@ -69,6 +69,7 @@ export {
|
|
|
69
69
|
XaiImagePricing,
|
|
70
70
|
XaiVideoPricing,
|
|
71
71
|
};
|
|
72
|
+
export * from "./responseMetadata";
|
|
72
73
|
export type {
|
|
73
74
|
ModelPricing,
|
|
74
75
|
ModelType,
|
|
@@ -82,8 +83,8 @@ export type {
|
|
|
82
83
|
// ---------------------------------------------------------------------------
|
|
83
84
|
|
|
84
85
|
type ProviderRegistryEntry = {
|
|
85
|
-
/** Constructor that accepts
|
|
86
|
-
clientClass?: new (arg1?: string
|
|
86
|
+
/** Constructor that accepts the optional API key passed by the registry. */
|
|
87
|
+
clientClass?: new (arg1?: string) => GenericClient;
|
|
87
88
|
/** Custom factory — takes precedence over clientClass */
|
|
88
89
|
createClient?: (entry: ModelProvider) => GenericClient | null;
|
|
89
90
|
};
|
|
@@ -111,7 +112,11 @@ const BUILT_IN_PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
|
|
|
111
112
|
createClient: (entry: ModelProvider) => {
|
|
112
113
|
if (!loadKnowhowJwt()) {
|
|
113
114
|
const config = getConfigSync();
|
|
114
|
-
if (
|
|
115
|
+
if (
|
|
116
|
+
!config.orgId ||
|
|
117
|
+
(!keyPairExists(config.cliIdentityPath) && !keyPairExists())
|
|
118
|
+
)
|
|
119
|
+
return null;
|
|
115
120
|
}
|
|
116
121
|
return new KnowhowGenericClient(KNOWHOW_API_URL);
|
|
117
122
|
},
|
|
@@ -320,9 +325,10 @@ export class AIClient {
|
|
|
320
325
|
if (entry.provider === "knowhow") {
|
|
321
326
|
if (process.env.KNOWHOW_CLI) {
|
|
322
327
|
const warnNeeds = (process.env.KNOWHOW_WARN_NEEDS ?? "").split(",");
|
|
323
|
-
if (warnNeeds.includes("models"))
|
|
324
|
-
|
|
325
|
-
|
|
328
|
+
if (warnNeeds.includes("models"))
|
|
329
|
+
console.warn(
|
|
330
|
+
`⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`
|
|
331
|
+
);
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
continue;
|
|
@@ -417,7 +423,9 @@ export class AIClient {
|
|
|
417
423
|
// Model not in local registry — pass it through anyway so the provider
|
|
418
424
|
// API can accept or reject it directly (e.g. newly-released models that
|
|
419
425
|
// haven't been fetched into our local model list yet).
|
|
420
|
-
console.warn(
|
|
426
|
+
console.warn(
|
|
427
|
+
`⚠️ Model '${model}' not in local registry for provider '${provider}', attempting anyway.`
|
|
428
|
+
);
|
|
421
429
|
}
|
|
422
430
|
|
|
423
431
|
return { client: this.clients[provider], provider, model };
|
|
@@ -914,7 +922,9 @@ export class AIClient {
|
|
|
914
922
|
* For HttpClient-based providers with getPricing(), only priced models are kept.
|
|
915
923
|
* For other providers (no getPricing()), all models pass through unchanged.
|
|
916
924
|
*/
|
|
917
|
-
private _filterByPricing(
|
|
925
|
+
private _filterByPricing(
|
|
926
|
+
models: Record<string, string[]>
|
|
927
|
+
): Record<string, string[]> {
|
|
918
928
|
const result: Record<string, string[]> = {};
|
|
919
929
|
for (const [provider, ids] of Object.entries(models)) {
|
|
920
930
|
const client = this.clients[provider];
|
|
@@ -1009,7 +1019,9 @@ export class AIClient {
|
|
|
1009
1019
|
}
|
|
1010
1020
|
|
|
1011
1021
|
listAllModelsWithProvider(options?: { pricing?: boolean }) {
|
|
1012
|
-
const models = options?.pricing
|
|
1022
|
+
const models = options?.pricing
|
|
1023
|
+
? this._filterByPricing(this.clientModels)
|
|
1024
|
+
: this.clientModels;
|
|
1013
1025
|
return Object.entries(models)
|
|
1014
1026
|
.map(([provider, ids]) => ids.map((m) => ({ id: `${provider}/${m}` })))
|
|
1015
1027
|
.flat();
|