@tyvm/knowhow 0.0.104 → 0.0.106

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 (233) hide show
  1. package/CONFIG.md +8 -5
  2. package/package.json +3 -2
  3. package/scripts/check-model-pricing.ts +509 -0
  4. package/scripts/compare-openrouter-coverage.ts +576 -0
  5. package/src/agents/base/base.ts +127 -2
  6. package/src/agents/tools/execCommand.ts +4 -0
  7. package/src/agents/tools/executeScript/definition.ts +1 -1
  8. package/src/agents/tools/index.ts +0 -1
  9. package/src/agents/tools/list.ts +3 -43
  10. package/src/agents/tools/writeFile.ts +1 -1
  11. package/src/auth/browserLogin.ts +9 -4
  12. package/src/chat/modules/RemoteSyncModule.ts +3 -0
  13. package/src/cli.ts +31 -1
  14. package/src/clients/cerebras.ts +10 -0
  15. package/src/clients/contextLimits.ts +7 -2
  16. package/src/clients/copilot.ts +23 -0
  17. package/src/clients/deepseek.ts +16 -0
  18. package/src/clients/fireworks.ts +15 -0
  19. package/src/clients/gemini.ts +45 -2
  20. package/src/clients/github.ts +16 -0
  21. package/src/clients/groq.ts +15 -0
  22. package/src/clients/http.ts +190 -6
  23. package/src/clients/index.ts +215 -9
  24. package/src/clients/llama.ts +16 -0
  25. package/src/clients/mistral.ts +16 -0
  26. package/src/clients/nvidia.ts +16 -0
  27. package/src/clients/openai.ts +41 -11
  28. package/src/clients/openrouter.ts +17 -0
  29. package/src/clients/pricing/anthropic.ts +105 -78
  30. package/src/clients/pricing/cerebras.ts +11 -0
  31. package/src/clients/pricing/copilot.ts +60 -0
  32. package/src/clients/pricing/deepseek.ts +15 -0
  33. package/src/clients/pricing/fireworks.ts +32 -0
  34. package/src/clients/pricing/github.ts +69 -0
  35. package/src/clients/pricing/google.ts +245 -206
  36. package/src/clients/pricing/groq.ts +56 -0
  37. package/src/clients/pricing/index.ts +43 -6
  38. package/src/clients/pricing/llama.ts +18 -0
  39. package/src/clients/pricing/mistral.ts +34 -0
  40. package/src/clients/pricing/models.ts +23 -0
  41. package/src/clients/pricing/nvidia.ts +102 -0
  42. package/src/clients/pricing/openai.ts +347 -171
  43. package/src/clients/pricing/openrouter.ts +36 -0
  44. package/src/clients/pricing/types.ts +110 -0
  45. package/src/clients/pricing/xai.ts +123 -66
  46. package/src/clients/types.ts +4 -0
  47. package/src/clients/xai.ts +152 -2
  48. package/src/fileSync.ts +8 -2
  49. package/src/login.ts +11 -3
  50. package/src/services/AgentSyncFs.ts +36 -12
  51. package/src/services/KnowhowClient.ts +11 -0
  52. package/src/services/LazyToolsService.ts +6 -0
  53. package/src/services/S3.ts +0 -7
  54. package/src/services/SyncedAgentWatcher.ts +13 -298
  55. package/src/services/index.ts +1 -0
  56. package/src/services/modules/index.ts +11 -2
  57. package/src/services/watchers/FsSyncer.ts +155 -0
  58. package/src/services/watchers/RemoteSyncer.ts +153 -0
  59. package/src/services/watchers/index.ts +2 -0
  60. package/src/types.ts +56 -279
  61. package/src/worker.ts +174 -0
  62. package/tests/clients/pricing.test.ts +37 -0
  63. package/tests/manual/clients/completions.json +838 -226
  64. package/tests/manual/clients/completions.test.ts +46 -31
  65. package/ts_build/package.json +3 -2
  66. package/ts_build/src/agents/base/base.d.ts +17 -1
  67. package/ts_build/src/agents/base/base.js +82 -1
  68. package/ts_build/src/agents/base/base.js.map +1 -1
  69. package/ts_build/src/agents/tools/execCommand.js +3 -0
  70. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  71. package/ts_build/src/agents/tools/executeScript/definition.js +1 -1
  72. package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -1
  73. package/ts_build/src/agents/tools/index.d.ts +0 -1
  74. package/ts_build/src/agents/tools/index.js +0 -1
  75. package/ts_build/src/agents/tools/index.js.map +1 -1
  76. package/ts_build/src/agents/tools/list.js +3 -38
  77. package/ts_build/src/agents/tools/list.js.map +1 -1
  78. package/ts_build/src/agents/tools/visionTool.d.ts +1 -1
  79. package/ts_build/src/agents/tools/writeFile.js +1 -1
  80. package/ts_build/src/agents/tools/writeFile.js.map +1 -1
  81. package/ts_build/src/ai.d.ts +1 -1
  82. package/ts_build/src/auth/browserLogin.d.ts +2 -1
  83. package/ts_build/src/auth/browserLogin.js +10 -3
  84. package/ts_build/src/auth/browserLogin.js.map +1 -1
  85. package/ts_build/src/chat/modules/RemoteSyncModule.js +1 -0
  86. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  87. package/ts_build/src/cli.js +19 -0
  88. package/ts_build/src/cli.js.map +1 -1
  89. package/ts_build/src/clients/anthropic.d.ts +1 -82
  90. package/ts_build/src/clients/cerebras.d.ts +4 -0
  91. package/ts_build/src/clients/cerebras.js +14 -0
  92. package/ts_build/src/clients/cerebras.js.map +1 -0
  93. package/ts_build/src/clients/contextLimits.js +7 -2
  94. package/ts_build/src/clients/contextLimits.js.map +1 -1
  95. package/ts_build/src/clients/copilot.d.ts +4 -0
  96. package/ts_build/src/clients/copilot.js +15 -0
  97. package/ts_build/src/clients/copilot.js.map +1 -0
  98. package/ts_build/src/clients/deepseek.d.ts +4 -0
  99. package/ts_build/src/clients/deepseek.js +15 -0
  100. package/ts_build/src/clients/deepseek.js.map +1 -0
  101. package/ts_build/src/clients/fireworks.d.ts +4 -0
  102. package/ts_build/src/clients/fireworks.js +15 -0
  103. package/ts_build/src/clients/fireworks.js.map +1 -0
  104. package/ts_build/src/clients/gemini.d.ts +1 -0
  105. package/ts_build/src/clients/gemini.js +28 -1
  106. package/ts_build/src/clients/gemini.js.map +1 -1
  107. package/ts_build/src/clients/github.d.ts +4 -0
  108. package/ts_build/src/clients/github.js +15 -0
  109. package/ts_build/src/clients/github.js.map +1 -0
  110. package/ts_build/src/clients/groq.d.ts +4 -0
  111. package/ts_build/src/clients/groq.js +15 -0
  112. package/ts_build/src/clients/groq.js.map +1 -0
  113. package/ts_build/src/clients/http.d.ts +22 -1
  114. package/ts_build/src/clients/http.js +132 -7
  115. package/ts_build/src/clients/http.js.map +1 -1
  116. package/ts_build/src/clients/index.d.ts +22 -0
  117. package/ts_build/src/clients/index.js +150 -5
  118. package/ts_build/src/clients/index.js.map +1 -1
  119. package/ts_build/src/clients/llama.d.ts +4 -0
  120. package/ts_build/src/clients/llama.js +15 -0
  121. package/ts_build/src/clients/llama.js.map +1 -0
  122. package/ts_build/src/clients/mistral.d.ts +4 -0
  123. package/ts_build/src/clients/mistral.js +15 -0
  124. package/ts_build/src/clients/mistral.js.map +1 -0
  125. package/ts_build/src/clients/nvidia.d.ts +4 -0
  126. package/ts_build/src/clients/nvidia.js +15 -0
  127. package/ts_build/src/clients/nvidia.js.map +1 -0
  128. package/ts_build/src/clients/openai.d.ts +4 -206
  129. package/ts_build/src/clients/openai.js +27 -9
  130. package/ts_build/src/clients/openai.js.map +1 -1
  131. package/ts_build/src/clients/openrouter.d.ts +4 -0
  132. package/ts_build/src/clients/openrouter.js +15 -0
  133. package/ts_build/src/clients/openrouter.js.map +1 -0
  134. package/ts_build/src/clients/pricing/anthropic.d.ts +26 -78
  135. package/ts_build/src/clients/pricing/anthropic.js +75 -78
  136. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  137. package/ts_build/src/clients/pricing/cerebras.d.ts +4 -0
  138. package/ts_build/src/clients/pricing/cerebras.js +11 -0
  139. package/ts_build/src/clients/pricing/cerebras.js.map +1 -0
  140. package/ts_build/src/clients/pricing/copilot.d.ts +5 -0
  141. package/ts_build/src/clients/pricing/copilot.js +35 -0
  142. package/ts_build/src/clients/pricing/copilot.js.map +1 -0
  143. package/ts_build/src/clients/pricing/deepseek.d.ts +5 -0
  144. package/ts_build/src/clients/pricing/deepseek.js +10 -0
  145. package/ts_build/src/clients/pricing/deepseek.js.map +1 -0
  146. package/ts_build/src/clients/pricing/fireworks.d.ts +5 -0
  147. package/ts_build/src/clients/pricing/fireworks.js +21 -0
  148. package/ts_build/src/clients/pricing/fireworks.js.map +1 -0
  149. package/ts_build/src/clients/pricing/github.d.ts +4 -0
  150. package/ts_build/src/clients/pricing/github.js +58 -0
  151. package/ts_build/src/clients/pricing/github.js.map +1 -0
  152. package/ts_build/src/clients/pricing/google.d.ts +59 -6
  153. package/ts_build/src/clients/pricing/google.js +214 -167
  154. package/ts_build/src/clients/pricing/google.js.map +1 -1
  155. package/ts_build/src/clients/pricing/groq.d.ts +5 -0
  156. package/ts_build/src/clients/pricing/groq.js +41 -0
  157. package/ts_build/src/clients/pricing/groq.js.map +1 -0
  158. package/ts_build/src/clients/pricing/index.d.ts +17 -6
  159. package/ts_build/src/clients/pricing/index.js +65 -10
  160. package/ts_build/src/clients/pricing/index.js.map +1 -1
  161. package/ts_build/src/clients/pricing/llama.d.ts +4 -0
  162. package/ts_build/src/clients/pricing/llama.js +14 -0
  163. package/ts_build/src/clients/pricing/llama.js.map +1 -0
  164. package/ts_build/src/clients/pricing/mistral.d.ts +5 -0
  165. package/ts_build/src/clients/pricing/mistral.js +23 -0
  166. package/ts_build/src/clients/pricing/mistral.js.map +1 -0
  167. package/ts_build/src/clients/pricing/models.d.ts +9 -0
  168. package/ts_build/src/clients/pricing/models.js +19 -0
  169. package/ts_build/src/clients/pricing/models.js.map +1 -0
  170. package/ts_build/src/clients/pricing/nvidia.d.ts +8 -0
  171. package/ts_build/src/clients/pricing/nvidia.js +96 -0
  172. package/ts_build/src/clients/pricing/nvidia.js.map +1 -0
  173. package/ts_build/src/clients/pricing/openai.d.ts +86 -197
  174. package/ts_build/src/clients/pricing/openai.js +294 -168
  175. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  176. package/ts_build/src/clients/pricing/openrouter.d.ts +4 -0
  177. package/ts_build/src/clients/pricing/openrouter.js +29 -0
  178. package/ts_build/src/clients/pricing/openrouter.js.map +1 -0
  179. package/ts_build/src/clients/pricing/types.d.ts +46 -0
  180. package/ts_build/src/clients/pricing/types.js +49 -0
  181. package/ts_build/src/clients/pricing/types.js.map +1 -0
  182. package/ts_build/src/clients/pricing/xai.d.ts +39 -64
  183. package/ts_build/src/clients/pricing/xai.js +93 -60
  184. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  185. package/ts_build/src/clients/types.d.ts +1 -0
  186. package/ts_build/src/clients/xai.d.ts +2 -58
  187. package/ts_build/src/clients/xai.js +123 -2
  188. package/ts_build/src/clients/xai.js.map +1 -1
  189. package/ts_build/src/fileSync.js +7 -2
  190. package/ts_build/src/fileSync.js.map +1 -1
  191. package/ts_build/src/login.js +8 -2
  192. package/ts_build/src/login.js.map +1 -1
  193. package/ts_build/src/services/AgentSyncFs.js +1 -0
  194. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  195. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  196. package/ts_build/src/services/KnowhowClient.js +7 -0
  197. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  198. package/ts_build/src/services/LazyToolsService.d.ts +1 -0
  199. package/ts_build/src/services/LazyToolsService.js +3 -0
  200. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  201. package/ts_build/src/services/S3.js +0 -7
  202. package/ts_build/src/services/S3.js.map +1 -1
  203. package/ts_build/src/services/SyncedAgentWatcher.d.ts +0 -51
  204. package/ts_build/src/services/SyncedAgentWatcher.js +1 -282
  205. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  206. package/ts_build/src/services/index.d.ts +1 -0
  207. package/ts_build/src/services/index.js +1 -0
  208. package/ts_build/src/services/index.js.map +1 -1
  209. package/ts_build/src/services/modules/index.js +41 -1
  210. package/ts_build/src/services/modules/index.js.map +1 -1
  211. package/ts_build/src/services/watchers/FsSyncer.d.ts +27 -0
  212. package/ts_build/src/services/watchers/FsSyncer.js +135 -0
  213. package/ts_build/src/services/watchers/FsSyncer.js.map +1 -0
  214. package/ts_build/src/services/watchers/RemoteSyncer.d.ts +28 -0
  215. package/ts_build/src/services/watchers/RemoteSyncer.js +126 -0
  216. package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -0
  217. package/ts_build/src/services/watchers/index.d.ts +2 -0
  218. package/ts_build/src/services/watchers/index.js +19 -0
  219. package/ts_build/src/services/watchers/index.js.map +1 -0
  220. package/ts_build/src/types.d.ts +163 -124
  221. package/ts_build/src/types.js +33 -213
  222. package/ts_build/src/types.js.map +1 -1
  223. package/ts_build/src/worker.d.ts +4 -0
  224. package/ts_build/src/worker.js +140 -0
  225. package/ts_build/src/worker.js.map +1 -1
  226. package/ts_build/tests/clients/pricing.test.js +21 -0
  227. package/ts_build/tests/clients/pricing.test.js.map +1 -1
  228. package/ts_build/tests/manual/clients/completions.test.js +27 -24
  229. package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
  230. package/src/clients/pricing/catalog.ts +0 -287
  231. package/ts_build/src/clients/pricing/catalog.d.ts +0 -28
  232. package/ts_build/src/clients/pricing/catalog.js +0 -179
  233. package/ts_build/src/clients/pricing/catalog.js.map +0 -1
@@ -6,11 +6,29 @@ import {
6
6
  EmbeddingOptions,
7
7
  EmbeddingResponse,
8
8
  } from "./types";
9
+ import { ModelPricing } from "./pricing/types";
9
10
  import fs from "fs";
10
11
  import path from "path";
11
12
 
13
+ export interface HttpClientOptions {
14
+ headers?: Record<string, string>;
15
+ timeout?: number;
16
+ extra_body?: Record<string, any>;
17
+ }
18
+
12
19
  export class HttpClient implements GenericClient {
13
- constructor(private baseUrl: string, private headers = {}) {}
20
+ /** Timeout in milliseconds for HTTP requests. Default: 30000 (30s). Use 0 to disable. */
21
+ private timeout: number;
22
+ private headers: Record<string, string>;
23
+ private extra_body: Record<string, any>;
24
+ /** Optional pricing table: model id → per-million-token prices */
25
+ private pricingMap: Record<string, ModelPricing> = {};
26
+
27
+ constructor(private baseUrl: string, options: HttpClientOptions = {}) {
28
+ this.headers = options.headers ?? {};
29
+ this.timeout = options.timeout ?? 30000;
30
+ this.extra_body = options.extra_body ?? {};
31
+ }
14
32
 
15
33
  private async withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
16
34
  let lastError: any;
@@ -64,6 +82,52 @@ export class HttpClient implements GenericClient {
64
82
  this.setJwt(key);
65
83
  }
66
84
 
85
+ /**
86
+ * Supply a pricing map so that createChatCompletion / createEmbedding can
87
+ * calculate a local usd_cost from usage tokens when the provider does not
88
+ * return a cost field itself.
89
+ */
90
+ setPrices(pricingMap: Record<string, ModelPricing>) {
91
+ this.pricingMap = pricingMap;
92
+ }
93
+
94
+ /**
95
+ * Calculate USD cost for a completion/embedding call from token usage.
96
+ * Returns undefined if no pricing entry exists for the model.
97
+ */
98
+ calculateCost(
99
+ model: string,
100
+ usage: { prompt_tokens?: number; completion_tokens?: number; prompt_tokens_details?: { cached_tokens?: number } } | undefined
101
+ ): number | undefined {
102
+ if (!usage) return undefined;
103
+ const pricing = this.pricingMap[model];
104
+ if (!pricing) return undefined;
105
+
106
+ const cachedInputTokens =
107
+ usage.prompt_tokens_details?.cached_tokens ?? 0;
108
+ const inputTokens = usage.prompt_tokens ?? 0;
109
+ const outputTokens = usage.completion_tokens ?? 0;
110
+
111
+ const cachedInputCost = (cachedInputTokens * (pricing.cache_hit ?? pricing.cached_input ?? 0)) / 1e6;
112
+ const inputCost = ((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
113
+ const outputCost = (outputTokens * (pricing.output ?? 0)) / 1e6;
114
+
115
+ return cachedInputCost + inputCost + outputCost;
116
+ }
117
+
118
+ /**
119
+ * Apply extra options (timeout, headers, extra_body) after construction.
120
+ * Used by AIClient.resolveClient to honour per-provider config overrides
121
+ * even when the client is created via a known clientClass (e.g. nvidia, groq).
122
+ */
123
+ setOptions(options: Omit<HttpClientOptions, "headers"> & { headers?: Record<string, string> }) {
124
+ if (options.timeout !== undefined) this.timeout = options.timeout;
125
+ if (options.extra_body !== undefined) this.extra_body = options.extra_body;
126
+ if (options.headers) {
127
+ this.headers = { ...this.headers, ...options.headers };
128
+ }
129
+ }
130
+
67
131
  loadJwtFile(filePath: string) {
68
132
  try {
69
133
  const jwtFile = path.join(process.cwd(), filePath);
@@ -85,7 +149,8 @@ export class HttpClient implements GenericClient {
85
149
  ...options,
86
150
  model: options.model,
87
151
  messages: options.messages,
88
- max_tokens: options.max_tokens || 3000,
152
+ max_tokens: options.max_tokens || 4000,
153
+ ...this.extra_body,
89
154
 
90
155
  ...(options.tools && {
91
156
  tools: options.tools,
@@ -96,7 +161,7 @@ export class HttpClient implements GenericClient {
96
161
  const response = await http.post(
97
162
  `${this.baseUrl}/v1/chat/completions`,
98
163
  body,
99
- { headers: this.headers as Record<string, string> }
164
+ { headers: this.headers as Record<string, string>, timeout: this.timeout }
100
165
  );
101
166
 
102
167
  const data = response.data;
@@ -116,7 +181,125 @@ export class HttpClient implements GenericClient {
116
181
  })),
117
182
  model: data.model,
118
183
  usage: data.usage,
119
- usd_cost: data.usd_cost,
184
+ usd_cost: data.usd_cost ?? this.calculateCost(options.model, data.usage),
185
+ };
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Creates a completion using the Responses API (/v1/responses).
191
+ * Compatible with providers that implement the OpenAI Responses API spec
192
+ * (e.g. xAI at https://api.x.ai/v1/responses).
193
+ */
194
+ async createResponse(
195
+ options: CompletionOptions,
196
+ store = false
197
+ ): Promise<CompletionResponse> {
198
+ return this.withRetry(async () => {
199
+ // Extract system messages as instructions
200
+ const systemMessages = options.messages.filter((m) => m.role === "system");
201
+ const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
202
+ const instructions = systemMessages
203
+ .map((m) => (typeof m.content === "string" ? m.content : ""))
204
+ .join("\n")
205
+ .trim() || undefined;
206
+
207
+ // Convert messages to Responses API input format
208
+ const input: any[] = nonSystemMessages.map((msg) => {
209
+ if (msg.role === "tool") {
210
+ return {
211
+ type: "function_call_output",
212
+ call_id: msg.tool_call_id,
213
+ output: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
214
+ };
215
+ }
216
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
217
+ return (msg.tool_calls as any[]).map((tc: any) => ({
218
+ type: "function_call",
219
+ id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
220
+ call_id: tc.id,
221
+ name: tc.function.name,
222
+ arguments: tc.function.arguments,
223
+ }));
224
+ }
225
+ return {
226
+ role: msg.role,
227
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
228
+ };
229
+ }).flat();
230
+
231
+ const tools = options.tools?.map((tool) => ({
232
+ type: "function" as const,
233
+ name: tool.function.name,
234
+ description: tool.function.description,
235
+ parameters: tool.function.parameters as Record<string, unknown>,
236
+ strict: false,
237
+ }));
238
+
239
+ const body = {
240
+ model: options.model,
241
+ input,
242
+ ...(instructions && { instructions }),
243
+ ...(options.max_tokens && { max_output_tokens: options.max_tokens }),
244
+ ...(tools?.length && { tools, tool_choice: "auto" }),
245
+ store,
246
+ ...this.extra_body,
247
+ };
248
+
249
+ const response = await http.post(
250
+ `${this.baseUrl}/v1/responses`,
251
+ body,
252
+ { headers: this.headers as Record<string, string>, timeout: this.timeout }
253
+ );
254
+
255
+ const data = response.data;
256
+
257
+ if (data.error) {
258
+ throw new Error(JSON.stringify(data.error, null, 2));
259
+ }
260
+
261
+ // Map usage from Responses API format to Chat Completions format
262
+ const usage = data.usage
263
+ ? {
264
+ prompt_tokens: data.usage.input_tokens,
265
+ completion_tokens: data.usage.output_tokens,
266
+ total_tokens: data.usage.input_tokens + data.usage.output_tokens,
267
+ }
268
+ : undefined;
269
+
270
+ // Collect text content and tool calls from output items
271
+ let textContent: string | null = null;
272
+ const toolCalls: any[] = [];
273
+
274
+ for (const item of data.output ?? []) {
275
+ if (item.type === "message") {
276
+ for (const part of item.content ?? []) {
277
+ if (part.type === "output_text") {
278
+ textContent = (textContent ?? "") + part.text;
279
+ }
280
+ }
281
+ } else if (item.type === "function_call") {
282
+ toolCalls.push({
283
+ id: item.call_id,
284
+ type: "function",
285
+ function: { name: item.name, arguments: item.arguments },
286
+ });
287
+ }
288
+ }
289
+
290
+ return {
291
+ choices: [
292
+ {
293
+ message: {
294
+ role: "assistant",
295
+ content: textContent,
296
+ ...(toolCalls.length > 0 && { tool_calls: toolCalls }),
297
+ },
298
+ },
299
+ ],
300
+ model: data.model ?? options.model,
301
+ usage,
302
+ usd_cost: data.usd_cost ?? this.calculateCost(options.model, usage),
120
303
  };
121
304
  });
122
305
  }
@@ -129,7 +312,7 @@ export class HttpClient implements GenericClient {
129
312
  model: options.model,
130
313
  input: options.input,
131
314
  },
132
- { headers: this.headers as Record<string, string> }
315
+ { headers: this.headers as Record<string, string>, timeout: this.timeout }
133
316
  );
134
317
 
135
318
  const data = response.data;
@@ -143,7 +326,7 @@ export class HttpClient implements GenericClient {
143
326
  data: data.data,
144
327
  model: options.model,
145
328
  usage: data.usage,
146
- usd_cost: data.usd_cost,
329
+ usd_cost: data.usd_cost ?? this.calculateCost(options.model, data.usage),
147
330
  };
148
331
  });
149
332
  }
@@ -152,6 +335,7 @@ export class HttpClient implements GenericClient {
152
335
  return this.withRetry(async () => {
153
336
  const response = await http.get(`${this.baseUrl}/v1/models?type=${type}`, {
154
337
  headers: this.headers as Record<string, string>,
338
+ timeout: this.timeout,
155
339
  });
156
340
 
157
341
  const data = response.data?.data;
@@ -30,6 +30,42 @@ import { ModelProvider } from "../types";
30
30
  import { getConfig } from "../config";
31
31
  import { loadKnowhowJwt, KNOWHOW_API_URL } from "../services/KnowhowClient";
32
32
  import { ContextLimits } from "./contextLimits";
33
+ import { OpenAiTextPricing } from "./pricing/openai";
34
+ import { AnthropicTextPricing } from "./pricing/anthropic";
35
+ import { GeminiPricing } from "./pricing/google";
36
+ import {
37
+ XaiTextPricing,
38
+ XaiImagePricing,
39
+ XaiVideoPricing,
40
+ } from "./pricing/xai";
41
+ import type {
42
+ ModelPricing,
43
+ ModelType,
44
+ ModelCatalogEntry,
45
+ } from "./pricing/types";
46
+ import { GenericCerebrasClient } from "./cerebras";
47
+ import { GenericGroqClient } from "./groq";
48
+ import { GenericGitHubModelsClient } from "./github";
49
+ import { GenericNvidiaClient } from "./nvidia";
50
+ import { GenericOpenRouterClient } from "./openrouter";
51
+ import { GenericDeepSeekClient } from "./deepseek";
52
+ import { GenericMistralClient } from "./mistral";
53
+ import { GitHubCopilotClient } from "./copilot";
54
+ import { GenericLlamaClient } from "./llama";
55
+ import { GenericFireworksClient } from "./fireworks";
56
+ export {
57
+ OpenAiTextPricing,
58
+ AnthropicTextPricing,
59
+ GeminiPricing,
60
+ XaiTextPricing,
61
+ XaiImagePricing,
62
+ XaiVideoPricing,
63
+ };
64
+ export type {
65
+ ModelPricing,
66
+ ModelType,
67
+ ModelCatalogEntry,
68
+ } from "./pricing/types";
33
69
 
34
70
  // ---------------------------------------------------------------------------
35
71
  // Built-in provider registry
@@ -49,6 +85,18 @@ const BUILT_IN_PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
49
85
  anthropic: { clientClass: GenericAnthropicClient },
50
86
  google: { clientClass: GenericGeminiClient },
51
87
  xai: { clientClass: GenericXAIClient },
88
+ cerebras: {
89
+ clientClass: GenericCerebrasClient,
90
+ },
91
+ groq: { clientClass: GenericGroqClient },
92
+ github: { clientClass: GenericGitHubModelsClient },
93
+ nvidia: { clientClass: GenericNvidiaClient },
94
+ openrouter: { clientClass: GenericOpenRouterClient },
95
+ deepseek: { clientClass: GenericDeepSeekClient },
96
+ mistral: { clientClass: GenericMistralClient },
97
+ "github-copilot": { clientClass: GitHubCopilotClient },
98
+ llama: { clientClass: GenericLlamaClient },
99
+ fireworks: { clientClass: GenericFireworksClient },
52
100
  knowhow: {
53
101
  createClient: (entry: ModelProvider) => {
54
102
  const jwt = loadKnowhowJwt();
@@ -68,7 +116,17 @@ const DEFAULT_PROVIDERS: ModelProvider[] = [
68
116
  { provider: "anthropic", envKey: "ANTHROPIC_API_KEY" },
69
117
  { provider: "google", envKey: "GEMINI_API_KEY" },
70
118
  { provider: "xai", envKey: "XAI_API_KEY" },
119
+ { provider: "cerebras", envKey: "CEREBRAS_API_KEY" },
71
120
  { provider: "knowhow" },
121
+ { provider: "groq", envKey: "GROQ_API_KEY" },
122
+ { provider: "github", envKey: "GITHUB_TOKEN" },
123
+ { provider: "nvidia", envKey: "NVIDIA_API_KEY" },
124
+ { provider: "openrouter", envKey: "OPENROUTER_API_KEY" },
125
+ { provider: "deepseek", envKey: "DEEPSEEK_API_KEY" },
126
+ { provider: "mistral", envKey: "MISTRAL_API_KEY" },
127
+ { provider: "github-copilot", envKey: "GITHUB_COPILOT_TOKEN" },
128
+ { provider: "llama", envKey: "LLAMA_API_KEY" },
129
+ { provider: "fireworks", envKey: "FIREWORKS_API_KEY" },
72
130
  ];
73
131
 
74
132
  export class AIClient {
@@ -127,19 +185,45 @@ export class AIClient {
127
185
  // envKey-based auth: env var must be present
128
186
  const envValue = process.env[effectiveEnvKey];
129
187
  if (!envValue) return null;
130
- return new reg.clientClass(envValue);
188
+ const client = new reg.clientClass(envValue);
189
+ // Apply any extra options (timeout, headers, extra_body) from config
190
+ if (client instanceof HttpClient) {
191
+ client.setOptions({
192
+ timeout: entry.timeout,
193
+ headers: entry.headers,
194
+ extra_body: entry.extra_body,
195
+ });
196
+ if (entry.pricing) client.setPrices(entry.pricing);
197
+ }
198
+ return client;
131
199
  }
132
200
 
133
201
  // No envKey, no url — instantiate with no arg (client uses its own defaults)
134
- return new reg.clientClass();
202
+ const client = new reg.clientClass();
203
+ // Apply any extra options (timeout, headers, extra_body) from config
204
+ if (client instanceof HttpClient) {
205
+ client.setOptions({
206
+ timeout: entry.timeout,
207
+ headers: entry.headers,
208
+ extra_body: entry.extra_body,
209
+ });
210
+ if (entry.pricing) client.setPrices(entry.pricing);
211
+ }
212
+ return client;
135
213
  }
136
214
 
137
215
  // 3. HTTP provider — requires url, no clientClass in registry
138
216
  if (entry.url) {
139
- const client = new HttpClient(entry.url, entry.headers);
217
+ const client = new HttpClient(entry.url, {
218
+ headers: entry.headers,
219
+ timeout: entry.timeout,
220
+ extra_body: entry.extra_body,
221
+ });
140
222
  if (entry.jwtFile) {
141
223
  client.loadJwtFile(entry.jwtFile);
142
224
  }
225
+ // For custom HTTP providers, use entry.pricing if available
226
+ if (entry.pricing) client.setPrices(entry.pricing);
143
227
  return client;
144
228
  }
145
229
 
@@ -219,7 +303,9 @@ export class AIClient {
219
303
 
220
304
  if (!client) {
221
305
  if (entry.provider === "knowhow") {
222
- console.warn(`⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`);
306
+ console.warn(
307
+ `⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`
308
+ );
223
309
  }
224
310
  continue;
225
311
  }
@@ -464,6 +550,52 @@ export class AIClient {
464
550
  return undefined;
465
551
  }
466
552
 
553
+ /**
554
+ * Normalize a model ID for fuzzy matching:
555
+ * - lowercase
556
+ * - replace dots with dashes (e.g. "claude-opus-4.7" → "claude-opus-4-7")
557
+ * - strip variant suffixes like ":thinking", ":free"
558
+ * - strip trailing date suffixes like "-20250514"
559
+ * - strip trailing "-beta", "-preview", "-latest"
560
+ */
561
+ private static normalizeModelId(id: string): string {
562
+ return id
563
+ .toLowerCase()
564
+ .replace(/\./g, "-")
565
+ .replace(/:[^:]+$/, "")
566
+ .replace(/-\d{8}$/, "")
567
+ .replace(/-(beta|preview|latest|exp|rc\d*)$/i, "");
568
+ }
569
+
570
+ /**
571
+ * Fuzzy model lookup: given a model name (possibly without date suffix,
572
+ * with dots instead of dashes, etc.), find the best matching registered model.
573
+ *
574
+ * Example: "claude-3.7-sonnet" matches "claude-3-7-sonnet-20250219"
575
+ * "gpt-4.1" matches "gpt-4.1" exactly
576
+ *
577
+ * @param modelQuery - the model name to search for (can be partial/normalized)
578
+ * @param provider - optional provider to restrict search to
579
+ */
580
+ findModelFuzzy(modelQuery: string, provider?: string): { provider: string; model: string } | undefined {
581
+ const queryNorm = AIClient.normalizeModelId(modelQuery);
582
+ const providers = provider
583
+ ? [provider]
584
+ : Object.keys(this.clientModels);
585
+
586
+ for (const p of providers) {
587
+ const models = (this.clientModels[p] as string[]) ?? [];
588
+ for (const m of models) {
589
+ const mNorm = AIClient.normalizeModelId(m);
590
+ // Exact normalized match, OR our model is a dated variant of the query
591
+ if (mNorm === queryNorm || mNorm.startsWith(queryNorm + "-")) {
592
+ return { provider: p, model: m };
593
+ }
594
+ }
595
+ }
596
+ return undefined;
597
+ }
598
+
467
599
  // detects these formats:
468
600
  // "openai", "gpt-5"
469
601
  // "knowhow", "openai/gpt-5"
@@ -499,17 +631,22 @@ export class AIClient {
499
631
  }
500
632
 
501
633
  const allModels = this.listAllModels();
502
- const hasKnowhowModels =
503
- allModels["knowhow"] && allModels["knowhow"].length > 0;
634
+ const hasKnowhowModels = allModels.knowhow && allModels.knowhow.length > 0;
504
635
  const knowhowIsConfigured = Object.keys(allModels).includes("knowhow");
505
636
 
506
- console.warn(`⚠️ Unable to find model '${model}' for provider '${provider}'.`);
507
- console.warn(` Available providers: ${Object.keys(allModels).join(", ") || "(none)"}`);
637
+ console.warn(
638
+ `⚠️ Unable to find model '${model}' for provider '${provider}'.`
639
+ );
640
+ console.warn(
641
+ ` Available providers: ${Object.keys(allModels).join(", ") || "(none)"}`
642
+ );
508
643
 
509
644
  if (!hasKnowhowModels && !knowhowIsConfigured) {
510
645
  console.warn(` Tip: Run 'knowhow login' to enable Knowhow models.`);
511
646
  } else if (!hasKnowhowModels) {
512
- console.warn(` Tip: The Knowhow provider returned no models. Try running 'knowhow login' to re-authenticate.`);
647
+ console.warn(
648
+ ` Tip: The Knowhow provider returned no models. Try running 'knowhow login' to re-authenticate.`
649
+ );
513
650
  }
514
651
 
515
652
  return { provider, model };
@@ -763,6 +900,66 @@ export class AIClient {
763
900
  if (contextLimit === undefined) return undefined;
764
901
  return { contextLimit, threshold: contextLimit };
765
902
  }
903
+
904
+ /**
905
+ * Returns pricing information for all known models, derived from the
906
+ * provider pricing maps.
907
+ *
908
+ * @param modelId Optional model id filter (without provider prefix).
909
+ * If omitted, all models across all providers are returned.
910
+ */
911
+ getPrices(modelId?: string): ModelCatalogEntry[] {
912
+ const results: ModelCatalogEntry[] = [];
913
+
914
+ const addModels = (
915
+ models: Record<string, string[]>,
916
+ type: ModelType,
917
+ pricingMap: Record<string, ModelPricing>
918
+ ) => {
919
+ for (const [provider, ids] of Object.entries(models)) {
920
+ for (const id of ids) {
921
+ if (modelId && id !== modelId) continue;
922
+ if (!pricingMap[id]) continue;
923
+
924
+ const p = pricingMap[id];
925
+ results.push({
926
+ id,
927
+ provider,
928
+ type,
929
+ pricing: p,
930
+ });
931
+ }
932
+ }
933
+ };
934
+
935
+ // Build a combined pricing map across all providers
936
+ const allTextPricing: Record<string, ModelPricing> = {
937
+ ...OpenAiTextPricing,
938
+ ...AnthropicTextPricing,
939
+ ...GeminiPricing,
940
+ ...XaiTextPricing,
941
+ };
942
+ const allImagePricing: Record<string, ModelPricing> = {
943
+ ...XaiImagePricing,
944
+ };
945
+ const allVideoPricing: Record<string, ModelPricing> = {
946
+ ...XaiVideoPricing,
947
+ };
948
+
949
+ addModels(this.completionModels, "completion", allTextPricing);
950
+ addModels(this.embeddingModels, "embedding", allTextPricing);
951
+ addModels(this.imageModels, "image", {
952
+ ...allTextPricing,
953
+ ...allImagePricing,
954
+ });
955
+ addModels(this.audioModels, "audio", allTextPricing);
956
+ addModels(this.videoModels, "video", {
957
+ ...allTextPricing,
958
+ ...allVideoPricing,
959
+ });
960
+
961
+ return results;
962
+ }
766
963
  }
767
964
 
768
965
  export const Clients = new AIClient();
@@ -777,3 +974,12 @@ export * from "./gemini";
777
974
  export * from "./contextLimits";
778
975
  export * from "./xai";
779
976
  export * from "./knowhowMcp";
977
+ export * from "./groq";
978
+ export * from "./github";
979
+ export * from "./nvidia";
980
+ export * from "./openrouter";
981
+ export * from "./deepseek";
982
+ export * from "./mistral";
983
+ export * from "./llama";
984
+ export * from "./copilot";
985
+ export * from "./fireworks";
@@ -0,0 +1,16 @@
1
+ import { HttpClient } from "./http";
2
+ import { LlamaTextPricing } from "./pricing/llama";
3
+
4
+ /**
5
+ * Meta Llama API client — OpenAI-compatible API
6
+ * https://llama.developer.meta.com/docs/
7
+ * Direct from Meta: free Llama 3.x, Llama 4, and Cerebras/Groq-hosted variants.
8
+ * Set env var LLAMA_API_KEY to enable.
9
+ */
10
+ export class GenericLlamaClient extends HttpClient {
11
+ constructor(apiKey = process.env.LLAMA_API_KEY) {
12
+ super("https://api.llama.com/compat");
13
+ if (apiKey) this.setJwt(apiKey);
14
+ this.setPrices(LlamaTextPricing);
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ import { HttpClient } from "./http";
2
+ import { MistralTextPricing } from "./pricing/mistral";
3
+
4
+ /**
5
+ * Mistral AI client — OpenAI-compatible API
6
+ * https://docs.mistral.ai/api/
7
+ * Top European AI lab with Mistral Large, Codestral, and free Devstral coding model.
8
+ * Set env var MISTRAL_API_KEY to enable.
9
+ */
10
+ export class GenericMistralClient extends HttpClient {
11
+ constructor(apiKey = process.env.MISTRAL_API_KEY) {
12
+ super("https://api.mistral.ai");
13
+ if (apiKey) this.setJwt(apiKey);
14
+ this.setPrices(MistralTextPricing);
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ import { HttpClient } from "./http";
2
+ import { NvidiaTextPricing } from "./pricing/nvidia";
3
+
4
+ /**
5
+ * NVIDIA NIM client — OpenAI-compatible API
6
+ * https://build.nvidia.com/explore/discover
7
+ * 76+ free models including Llama, Mistral, Phi, Flux image generation.
8
+ * Set env var NVIDIA_API_KEY to enable.
9
+ */
10
+ export class GenericNvidiaClient extends HttpClient {
11
+ constructor(apiKey = process.env.NVIDIA_API_KEY) {
12
+ super("https://integrate.api.nvidia.com");
13
+ if (apiKey) this.setJwt(apiKey);
14
+ this.setPrices(NvidiaTextPricing);
15
+ }
16
+ }