@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.
Files changed (69) hide show
  1. package/package.json +1 -1
  2. package/src/agents/base/base.ts +15 -5
  3. package/src/chat/modules/AgentModule.ts +3 -3
  4. package/src/chat/modules/RemoteSyncModule.ts +45 -17
  5. package/src/clients/anthropic.ts +156 -79
  6. package/src/clients/attribution.ts +53 -0
  7. package/src/clients/http.ts +211 -84
  8. package/src/clients/index.ts +21 -9
  9. package/src/clients/openai.ts +441 -220
  10. package/src/clients/openrouter.ts +10 -3
  11. package/src/clients/pricing/openai.ts +15 -1
  12. package/src/clients/pricing/types.ts +15 -0
  13. package/src/clients/responseMetadata.ts +221 -0
  14. package/src/clients/types.ts +49 -4
  15. package/tests/unit/clients/anthropic.responseMetadata.test.ts +81 -0
  16. package/tests/unit/clients/attribution.test.ts +185 -0
  17. package/tests/unit/clients/http.responseMetadata.test.ts +86 -0
  18. package/tests/unit/clients/openai.responseMetadata.test.ts +90 -0
  19. package/tests/unit/clients/responseMetadata.test.ts +123 -0
  20. package/ts_build/package.json +1 -1
  21. package/ts_build/src/agents/base/base.js +14 -4
  22. package/ts_build/src/agents/base/base.js.map +1 -1
  23. package/ts_build/src/chat/modules/AgentModule.js +3 -3
  24. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  25. package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +1 -0
  26. package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -7
  27. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  28. package/ts_build/src/clients/anthropic.d.ts +1 -0
  29. package/ts_build/src/clients/anthropic.js +83 -38
  30. package/ts_build/src/clients/anthropic.js.map +1 -1
  31. package/ts_build/src/clients/attribution.d.ts +2 -0
  32. package/ts_build/src/clients/attribution.js +32 -0
  33. package/ts_build/src/clients/attribution.js.map +1 -0
  34. package/ts_build/src/clients/http.js +100 -20
  35. package/ts_build/src/clients/http.js.map +1 -1
  36. package/ts_build/src/clients/index.d.ts +2 -1
  37. package/ts_build/src/clients/index.js +6 -2
  38. package/ts_build/src/clients/index.js.map +1 -1
  39. package/ts_build/src/clients/openai.d.ts +2 -1
  40. package/ts_build/src/clients/openai.js +225 -99
  41. package/ts_build/src/clients/openai.js.map +1 -1
  42. package/ts_build/src/clients/openrouter.d.ts +2 -2
  43. package/ts_build/src/clients/openrouter.js +6 -2
  44. package/ts_build/src/clients/openrouter.js.map +1 -1
  45. package/ts_build/src/clients/pricing/openai.js +15 -1
  46. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  47. package/ts_build/src/clients/pricing/types.d.ts +6 -0
  48. package/ts_build/src/clients/pricing/types.js.map +1 -1
  49. package/ts_build/src/clients/responseMetadata.d.ts +15 -0
  50. package/ts_build/src/clients/responseMetadata.js +141 -0
  51. package/ts_build/src/clients/responseMetadata.js.map +1 -0
  52. package/ts_build/src/clients/types.d.ts +18 -1
  53. package/ts_build/src/clients/types.js +2 -1
  54. package/ts_build/src/clients/types.js.map +1 -1
  55. package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.d.ts +1 -0
  56. package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js +77 -0
  57. package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js.map +1 -0
  58. package/ts_build/tests/unit/clients/attribution.test.d.ts +1 -0
  59. package/ts_build/tests/unit/clients/attribution.test.js +153 -0
  60. package/ts_build/tests/unit/clients/attribution.test.js.map +1 -0
  61. package/ts_build/tests/unit/clients/http.responseMetadata.test.d.ts +1 -0
  62. package/ts_build/tests/unit/clients/http.responseMetadata.test.js +77 -0
  63. package/ts_build/tests/unit/clients/http.responseMetadata.test.js.map +1 -0
  64. package/ts_build/tests/unit/clients/openai.responseMetadata.test.d.ts +1 -0
  65. package/ts_build/tests/unit/clients/openai.responseMetadata.test.js +86 -0
  66. package/ts_build/tests/unit/clients/openai.responseMetadata.test.js.map +1 -0
  67. package/ts_build/tests/unit/clients/responseMetadata.test.d.ts +1 -0
  68. package/ts_build/tests/unit/clients/responseMetadata.test.js +96 -0
  69. package/ts_build/tests/unit/clients/responseMetadata.test.js.map +1 -0
@@ -1,4 +1,10 @@
1
- import http, { HTTP_UNAUTHORIZED_HANDLER, RefreshableHeaders } from "../utils/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>)["Authorization"];
60
+ const authHeader = (this.headers as Record<string, string>)[
61
+ "Authorization"
62
+ ];
55
63
  if (!authHeader) return false;
56
- const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
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(Buffer.from(parts[1], "base64url").toString("utf8"));
61
- return typeof payload.exp === "number" && payload.exp * 1000 - Date.now() < 5 * 60 * 1000;
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(model?: string): ModelPricing | Record<string, ModelPricing> | undefined {
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: { prompt_tokens?: number; completion_tokens?: number; prompt_tokens_details?: { cached_tokens?: number } } | undefined
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 = (cachedInputTokens * (pricing.cache_hit ?? pricing.cached_input ?? 0)) / 1e6;
205
- const inputCost = ((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
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(options: Omit<HttpClientOptions, "headers"> & { headers?: Record<string, string> }) {
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.assign(this.headers, options.headers);
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
- ...options,
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
- const response = await http.post(
257
- `${this.baseUrl}/v1/chat/completions`,
258
- body,
259
- { headers: this.headers as Record<string, string>, timeout: this.timeout }
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: data.usd_cost ?? this.calculateCost(options.model, data.usage),
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
- ...options,
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
- "Accept": "text/event-stream",
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((m) => m.role === "system");
380
- const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
381
- const instructions = systemMessages
382
- .map((m) => (typeof m.content === "string" ? m.content : ""))
383
- .join("\n")
384
- .trim() || undefined;
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) + "\n\n[TRUNCATED: output exceeded 10MB API limit]"
469
+ ? s.slice(0, MAX_TOOL_OUTPUT) +
470
+ "\n\n[TRUNCATED: output exceeded 10MB API limit]"
392
471
  : s;
393
- const input: any[] = nonSystemMessages.map((msg) => {
394
- if (msg.role === "tool") {
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
- type: "function_call_output",
397
- call_id: msg.tool_call_id,
398
- output: truncateToolOutput(typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)),
514
+ role: msg.role,
515
+ content:
516
+ typeof msg.content === "string"
517
+ ? msg.content
518
+ : JSON.stringify(msg.content),
399
519
  };
400
- }
401
- if (msg.role === "assistant" && msg.tool_calls?.length) {
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
- const response = await http.post(
465
- `${this.baseUrl}/v1/responses`,
466
- body,
467
- { headers: this.headers as Record<string, string>, timeout: this.timeout }
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
- const response = await http.post(
551
- `${this.baseUrl}/v1/embeddings`,
552
- {
553
- model: options.model,
554
- input: options.input,
555
- },
556
- { headers: this.headers as Record<string, string>, timeout: this.timeout }
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: data.usd_cost ?? this.calculateCost(options.model, data.usage),
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(`${this.baseUrl}/v1/models?type=${type}`, {
579
- headers: this.headers as Record<string, string>,
580
- timeout: this.timeout,
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
 
@@ -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 up to two optional string args (e.g. apiKey or url, jwt) */
86
- clientClass?: new (arg1?: string, arg2?: string) => GenericClient;
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 (!config.orgId || (!keyPairExists(config.cliIdentityPath) && !keyPairExists())) return null;
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")) console.warn(
324
- `⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`
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(`⚠️ Model '${model}' not in local registry for provider '${provider}', attempting anyway.`);
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(models: Record<string, string[]>): Record<string, string[]> {
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 ? this._filterByPricing(this.clientModels) : this.clientModels;
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();