@tyvm/knowhow 0.0.105 → 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.
- package/CONFIG.md +8 -5
- package/package.json +3 -2
- package/scripts/check-model-pricing.ts +509 -0
- package/scripts/compare-openrouter-coverage.ts +576 -0
- package/src/agents/base/base.ts +127 -2
- package/src/agents/tools/execCommand.ts +4 -0
- package/src/agents/tools/executeScript/definition.ts +1 -1
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +3 -43
- package/src/agents/tools/writeFile.ts +1 -1
- package/src/auth/browserLogin.ts +9 -4
- package/src/chat/modules/RemoteSyncModule.ts +3 -0
- package/src/cli.ts +31 -1
- package/src/clients/cerebras.ts +10 -0
- package/src/clients/contextLimits.ts +7 -2
- package/src/clients/copilot.ts +23 -0
- package/src/clients/deepseek.ts +16 -0
- package/src/clients/fireworks.ts +15 -0
- package/src/clients/gemini.ts +45 -2
- package/src/clients/github.ts +16 -0
- package/src/clients/groq.ts +15 -0
- package/src/clients/http.ts +190 -6
- package/src/clients/index.ts +116 -4
- package/src/clients/llama.ts +16 -0
- package/src/clients/mistral.ts +16 -0
- package/src/clients/nvidia.ts +16 -0
- package/src/clients/openai.ts +41 -11
- package/src/clients/openrouter.ts +17 -0
- package/src/clients/pricing/anthropic.ts +105 -78
- package/src/clients/pricing/cerebras.ts +11 -0
- package/src/clients/pricing/copilot.ts +60 -0
- package/src/clients/pricing/deepseek.ts +15 -0
- package/src/clients/pricing/fireworks.ts +32 -0
- package/src/clients/pricing/github.ts +69 -0
- package/src/clients/pricing/google.ts +245 -206
- package/src/clients/pricing/groq.ts +56 -0
- package/src/clients/pricing/index.ts +42 -5
- package/src/clients/pricing/llama.ts +18 -0
- package/src/clients/pricing/mistral.ts +34 -0
- package/src/clients/pricing/models.ts +7 -236
- package/src/clients/pricing/nvidia.ts +102 -0
- package/src/clients/pricing/openai.ts +347 -171
- package/src/clients/pricing/openrouter.ts +36 -0
- package/src/clients/pricing/types.ts +83 -2
- package/src/clients/pricing/xai.ts +121 -65
- package/src/clients/types.ts +4 -0
- package/src/clients/xai.ts +150 -0
- package/src/fileSync.ts +8 -2
- package/src/login.ts +11 -3
- package/src/services/AgentSyncFs.ts +36 -12
- package/src/services/KnowhowClient.ts +11 -0
- package/src/services/LazyToolsService.ts +6 -0
- package/src/services/S3.ts +0 -7
- package/src/services/modules/index.ts +11 -2
- package/src/types.ts +56 -279
- package/src/worker.ts +174 -0
- package/tests/clients/pricing.test.ts +37 -0
- package/tests/manual/clients/completions.json +838 -226
- package/tests/manual/clients/completions.test.ts +46 -31
- package/ts_build/package.json +3 -2
- package/ts_build/src/agents/base/base.d.ts +17 -1
- package/ts_build/src/agents/base/base.js +82 -1
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.js +3 -0
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.js +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +3 -38
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/visionTool.d.ts +1 -1
- package/ts_build/src/agents/tools/writeFile.js +1 -1
- package/ts_build/src/agents/tools/writeFile.js.map +1 -1
- package/ts_build/src/ai.d.ts +1 -1
- package/ts_build/src/auth/browserLogin.d.ts +2 -1
- package/ts_build/src/auth/browserLogin.js +10 -3
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.js +1 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/cli.js +19 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -82
- package/ts_build/src/clients/cerebras.d.ts +4 -0
- package/ts_build/src/clients/cerebras.js +14 -0
- package/ts_build/src/clients/cerebras.js.map +1 -0
- package/ts_build/src/clients/contextLimits.js +7 -2
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/copilot.d.ts +4 -0
- package/ts_build/src/clients/copilot.js +15 -0
- package/ts_build/src/clients/copilot.js.map +1 -0
- package/ts_build/src/clients/deepseek.d.ts +4 -0
- package/ts_build/src/clients/deepseek.js +15 -0
- package/ts_build/src/clients/deepseek.js.map +1 -0
- package/ts_build/src/clients/fireworks.d.ts +4 -0
- package/ts_build/src/clients/fireworks.js +15 -0
- package/ts_build/src/clients/fireworks.js.map +1 -0
- package/ts_build/src/clients/gemini.d.ts +1 -0
- package/ts_build/src/clients/gemini.js +28 -1
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/github.d.ts +4 -0
- package/ts_build/src/clients/github.js +15 -0
- package/ts_build/src/clients/github.js.map +1 -0
- package/ts_build/src/clients/groq.d.ts +4 -0
- package/ts_build/src/clients/groq.js +15 -0
- package/ts_build/src/clients/groq.js.map +1 -0
- package/ts_build/src/clients/http.d.ts +22 -1
- package/ts_build/src/clients/http.js +132 -7
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +14 -0
- package/ts_build/src/clients/index.js +94 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/llama.d.ts +4 -0
- package/ts_build/src/clients/llama.js +15 -0
- package/ts_build/src/clients/llama.js.map +1 -0
- package/ts_build/src/clients/mistral.d.ts +4 -0
- package/ts_build/src/clients/mistral.js +15 -0
- package/ts_build/src/clients/mistral.js.map +1 -0
- package/ts_build/src/clients/nvidia.d.ts +4 -0
- package/ts_build/src/clients/nvidia.js +15 -0
- package/ts_build/src/clients/nvidia.js.map +1 -0
- package/ts_build/src/clients/openai.d.ts +4 -206
- package/ts_build/src/clients/openai.js +27 -9
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/openrouter.d.ts +4 -0
- package/ts_build/src/clients/openrouter.js +15 -0
- package/ts_build/src/clients/openrouter.js.map +1 -0
- package/ts_build/src/clients/pricing/anthropic.d.ts +26 -78
- package/ts_build/src/clients/pricing/anthropic.js +75 -78
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/cerebras.d.ts +4 -0
- package/ts_build/src/clients/pricing/cerebras.js +11 -0
- package/ts_build/src/clients/pricing/cerebras.js.map +1 -0
- package/ts_build/src/clients/pricing/copilot.d.ts +5 -0
- package/ts_build/src/clients/pricing/copilot.js +35 -0
- package/ts_build/src/clients/pricing/copilot.js.map +1 -0
- package/ts_build/src/clients/pricing/deepseek.d.ts +5 -0
- package/ts_build/src/clients/pricing/deepseek.js +10 -0
- package/ts_build/src/clients/pricing/deepseek.js.map +1 -0
- package/ts_build/src/clients/pricing/fireworks.d.ts +5 -0
- package/ts_build/src/clients/pricing/fireworks.js +21 -0
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -0
- package/ts_build/src/clients/pricing/github.d.ts +4 -0
- package/ts_build/src/clients/pricing/github.js +58 -0
- package/ts_build/src/clients/pricing/github.js.map +1 -0
- package/ts_build/src/clients/pricing/google.d.ts +59 -6
- package/ts_build/src/clients/pricing/google.js +214 -167
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/groq.d.ts +5 -0
- package/ts_build/src/clients/pricing/groq.js +41 -0
- package/ts_build/src/clients/pricing/groq.js.map +1 -0
- package/ts_build/src/clients/pricing/index.d.ts +16 -5
- package/ts_build/src/clients/pricing/index.js +62 -7
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/llama.d.ts +4 -0
- package/ts_build/src/clients/pricing/llama.js +14 -0
- package/ts_build/src/clients/pricing/llama.js.map +1 -0
- package/ts_build/src/clients/pricing/mistral.d.ts +5 -0
- package/ts_build/src/clients/pricing/mistral.js +23 -0
- package/ts_build/src/clients/pricing/mistral.js.map +1 -0
- package/ts_build/src/clients/pricing/models.d.ts +5 -4
- package/ts_build/src/clients/pricing/models.js +8 -162
- package/ts_build/src/clients/pricing/models.js.map +1 -1
- package/ts_build/src/clients/pricing/nvidia.d.ts +8 -0
- package/ts_build/src/clients/pricing/nvidia.js +96 -0
- package/ts_build/src/clients/pricing/nvidia.js.map +1 -0
- package/ts_build/src/clients/pricing/openai.d.ts +86 -197
- package/ts_build/src/clients/pricing/openai.js +294 -168
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/openrouter.d.ts +4 -0
- package/ts_build/src/clients/pricing/openrouter.js +29 -0
- package/ts_build/src/clients/pricing/openrouter.js.map +1 -0
- package/ts_build/src/clients/pricing/types.d.ts +27 -2
- package/ts_build/src/clients/pricing/types.js +46 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +37 -57
- package/ts_build/src/clients/pricing/xai.js +92 -59
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +1 -0
- package/ts_build/src/clients/xai.d.ts +2 -62
- package/ts_build/src/clients/xai.js +121 -0
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/fileSync.js +7 -2
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/login.js +8 -2
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.js +1 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +1 -0
- package/ts_build/src/services/KnowhowClient.js +7 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.d.ts +1 -0
- package/ts_build/src/services/LazyToolsService.js +3 -0
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/S3.js +0 -7
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/modules/index.js +41 -1
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +163 -124
- package/ts_build/src/types.js +33 -213
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +4 -0
- package/ts_build/src/worker.js +140 -0
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/clients/pricing.test.js +21 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/manual/clients/completions.test.js +27 -24
- package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
package/src/clients/http.ts
CHANGED
|
@@ -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
|
-
|
|
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 ||
|
|
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;
|
package/src/clients/index.ts
CHANGED
|
@@ -43,6 +43,16 @@ import type {
|
|
|
43
43
|
ModelType,
|
|
44
44
|
ModelCatalogEntry,
|
|
45
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";
|
|
46
56
|
export {
|
|
47
57
|
OpenAiTextPricing,
|
|
48
58
|
AnthropicTextPricing,
|
|
@@ -75,6 +85,18 @@ const BUILT_IN_PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
|
|
|
75
85
|
anthropic: { clientClass: GenericAnthropicClient },
|
|
76
86
|
google: { clientClass: GenericGeminiClient },
|
|
77
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 },
|
|
78
100
|
knowhow: {
|
|
79
101
|
createClient: (entry: ModelProvider) => {
|
|
80
102
|
const jwt = loadKnowhowJwt();
|
|
@@ -94,7 +116,17 @@ const DEFAULT_PROVIDERS: ModelProvider[] = [
|
|
|
94
116
|
{ provider: "anthropic", envKey: "ANTHROPIC_API_KEY" },
|
|
95
117
|
{ provider: "google", envKey: "GEMINI_API_KEY" },
|
|
96
118
|
{ provider: "xai", envKey: "XAI_API_KEY" },
|
|
119
|
+
{ provider: "cerebras", envKey: "CEREBRAS_API_KEY" },
|
|
97
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" },
|
|
98
130
|
];
|
|
99
131
|
|
|
100
132
|
export class AIClient {
|
|
@@ -153,19 +185,45 @@ export class AIClient {
|
|
|
153
185
|
// envKey-based auth: env var must be present
|
|
154
186
|
const envValue = process.env[effectiveEnvKey];
|
|
155
187
|
if (!envValue) return null;
|
|
156
|
-
|
|
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;
|
|
157
199
|
}
|
|
158
200
|
|
|
159
201
|
// No envKey, no url — instantiate with no arg (client uses its own defaults)
|
|
160
|
-
|
|
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;
|
|
161
213
|
}
|
|
162
214
|
|
|
163
215
|
// 3. HTTP provider — requires url, no clientClass in registry
|
|
164
216
|
if (entry.url) {
|
|
165
|
-
const client = new HttpClient(entry.url,
|
|
217
|
+
const client = new HttpClient(entry.url, {
|
|
218
|
+
headers: entry.headers,
|
|
219
|
+
timeout: entry.timeout,
|
|
220
|
+
extra_body: entry.extra_body,
|
|
221
|
+
});
|
|
166
222
|
if (entry.jwtFile) {
|
|
167
223
|
client.loadJwtFile(entry.jwtFile);
|
|
168
224
|
}
|
|
225
|
+
// For custom HTTP providers, use entry.pricing if available
|
|
226
|
+
if (entry.pricing) client.setPrices(entry.pricing);
|
|
169
227
|
return client;
|
|
170
228
|
}
|
|
171
229
|
|
|
@@ -492,6 +550,52 @@ export class AIClient {
|
|
|
492
550
|
return undefined;
|
|
493
551
|
}
|
|
494
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
|
+
|
|
495
599
|
// detects these formats:
|
|
496
600
|
// "openai", "gpt-5"
|
|
497
601
|
// "knowhow", "openai/gpt-5"
|
|
@@ -822,7 +926,6 @@ export class AIClient {
|
|
|
822
926
|
id,
|
|
823
927
|
provider,
|
|
824
928
|
type,
|
|
825
|
-
displayName: id,
|
|
826
929
|
pricing: p,
|
|
827
930
|
});
|
|
828
931
|
}
|
|
@@ -871,3 +974,12 @@ export * from "./gemini";
|
|
|
871
974
|
export * from "./contextLimits";
|
|
872
975
|
export * from "./xai";
|
|
873
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
|
+
}
|
package/src/clients/openai.ts
CHANGED
|
@@ -34,12 +34,14 @@ import {
|
|
|
34
34
|
EmbeddingModels,
|
|
35
35
|
Models,
|
|
36
36
|
OpenAiReasoningModels,
|
|
37
|
+
OpenAiChatModels,
|
|
37
38
|
OpenAiResponsesOnlyModels,
|
|
38
39
|
OpenAiImageModels,
|
|
39
40
|
OpenAiVideoModels,
|
|
40
41
|
OpenAiTTSModels,
|
|
41
42
|
OpenAiTranscriptionModels,
|
|
42
|
-
|
|
43
|
+
OpenAiEmbeddingModelsList,
|
|
44
|
+
OpenAiRealtimeModels,
|
|
43
45
|
} from "../types";
|
|
44
46
|
import { ModelModality } from "./types";
|
|
45
47
|
|
|
@@ -64,7 +66,11 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
64
66
|
reasoningEffort(
|
|
65
67
|
messages: CompletionOptions["messages"]
|
|
66
68
|
): "low" | "medium" | "high" {
|
|
67
|
-
|
|
69
|
+
return this.detectReasoningEffort(messages);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
detectReasoningEffort(messages: CompletionOptions["messages"]): "low" | "medium" | "high" {
|
|
73
|
+
const effortMap: Record<string, "low" | "medium" | "high"> = {
|
|
68
74
|
ultrathink: "high",
|
|
69
75
|
"think hard": "high",
|
|
70
76
|
"reason hard": "high",
|
|
@@ -96,6 +102,30 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
96
102
|
return "medium"; // Default to medium if no specific effort is mentioned
|
|
97
103
|
}
|
|
98
104
|
|
|
105
|
+
resolveReasoningEffort(options: CompletionOptions): "low" | "medium" | "high" {
|
|
106
|
+
return options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolves the reasoning effort for a specific model, clamping to the model's
|
|
111
|
+
* supported levels if `reasoningLevels` is set in its pricing entry.
|
|
112
|
+
* If the requested level is not supported, picks the lowest supported level.
|
|
113
|
+
*/
|
|
114
|
+
resolveReasoningEffortForModel(options: CompletionOptions): string {
|
|
115
|
+
const requested = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
|
|
116
|
+
const pricing = OpenAiTextPricing[options.model];
|
|
117
|
+
const supportedLevels = pricing?.reasoningLevels;
|
|
118
|
+
if (!supportedLevels || supportedLevels.length === 0) {
|
|
119
|
+
return requested;
|
|
120
|
+
}
|
|
121
|
+
// If the requested level is supported, use it
|
|
122
|
+
if (supportedLevels.includes(requested)) {
|
|
123
|
+
return requested;
|
|
124
|
+
}
|
|
125
|
+
// Otherwise use the first (lowest) supported level
|
|
126
|
+
return supportedLevels[0];
|
|
127
|
+
}
|
|
128
|
+
|
|
99
129
|
async createChatCompletion(
|
|
100
130
|
options: CompletionOptions
|
|
101
131
|
): Promise<CompletionResponse> {
|
|
@@ -122,8 +152,8 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
122
152
|
max_tokens: options.max_tokens,
|
|
123
153
|
...(OpenAiReasoningModels.includes(options.model) && {
|
|
124
154
|
max_tokens: undefined,
|
|
125
|
-
max_completion_tokens: Math.max(options.max_tokens,
|
|
126
|
-
reasoning_effort: this.
|
|
155
|
+
max_completion_tokens: Math.max(options.max_tokens ?? 0, 16_000),
|
|
156
|
+
reasoning_effort: this.resolveReasoningEffort(options),
|
|
127
157
|
}),
|
|
128
158
|
|
|
129
159
|
...(options.tools && {
|
|
@@ -254,7 +284,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
254
284
|
// Don't limit max_output_tokens for Responses API - codex truncates tool call arguments when limited
|
|
255
285
|
...(OpenAiReasoningModels.includes(options.model) && {
|
|
256
286
|
max_output_tokens: Math.max(options.max_tokens || 0, 16000),
|
|
257
|
-
reasoning: { effort: this.
|
|
287
|
+
reasoning: { effort: this.resolveReasoningEffortForModel(options) },
|
|
258
288
|
}),
|
|
259
289
|
...(tools?.length && {
|
|
260
290
|
tools,
|
|
@@ -349,14 +379,14 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
349
379
|
("prompt_tokens_details" in usage &&
|
|
350
380
|
usage.prompt_tokens_details?.cached_tokens) ||
|
|
351
381
|
0;
|
|
352
|
-
const cachedInputCost = (cachedInputTokens * pricing.cached_input) / 1e6;
|
|
382
|
+
const cachedInputCost = (cachedInputTokens * (pricing.cached_input ?? 0)) / 1e6;
|
|
353
383
|
|
|
354
384
|
const inputTokens = usage.prompt_tokens;
|
|
355
|
-
const inputCost = ((inputTokens -
|
|
385
|
+
const inputCost = ((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
|
|
356
386
|
|
|
357
387
|
const outputTokens =
|
|
358
388
|
("completion_tokens" in usage && usage?.completion_tokens) || 0;
|
|
359
|
-
const outputCost = (outputTokens * pricing.output) / 1e6;
|
|
389
|
+
const outputCost = (outputTokens * (pricing.output ?? 0)) / 1e6;
|
|
360
390
|
|
|
361
391
|
const total = cachedInputCost + inputCost + outputCost;
|
|
362
392
|
return total;
|
|
@@ -365,8 +395,8 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
365
395
|
async getModels(modality?: ModelModality): Promise<{ id: string }[]> {
|
|
366
396
|
if (modality) {
|
|
367
397
|
const map: Partial<Record<ModelModality, string[]>> = {
|
|
368
|
-
completion:
|
|
369
|
-
embedding:
|
|
398
|
+
completion: [...new Set([...OpenAiChatModels, ...OpenAiResponsesOnlyModels])],
|
|
399
|
+
embedding: OpenAiEmbeddingModelsList,
|
|
370
400
|
image: OpenAiImageModels,
|
|
371
401
|
audio: [...OpenAiTTSModels, ...OpenAiTranscriptionModels],
|
|
372
402
|
transcription: OpenAiTranscriptionModels,
|
|
@@ -406,7 +436,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
406
436
|
}
|
|
407
437
|
|
|
408
438
|
const response = await this.client.audio.transcriptions.create({
|
|
409
|
-
file
|
|
439
|
+
file,
|
|
410
440
|
model: options.model || "whisper-1",
|
|
411
441
|
language: options.language,
|
|
412
442
|
prompt: options.prompt,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HttpClient } from "./http";
|
|
2
|
+
import { OpenRouterTextPricing } from "./pricing/openrouter";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OpenRouter client — OpenAI-compatible API aggregator
|
|
6
|
+
* https://openrouter.ai/docs
|
|
7
|
+
* 39+ free models; append `:free` suffix to a model id for the free variant.
|
|
8
|
+
* One API key gives access to models from many providers.
|
|
9
|
+
* Set env var OPENROUTER_API_KEY to enable.
|
|
10
|
+
*/
|
|
11
|
+
export class GenericOpenRouterClient extends HttpClient {
|
|
12
|
+
constructor(apiKey = process.env.OPENROUTER_API_KEY) {
|
|
13
|
+
super("https://openrouter.ai/api");
|
|
14
|
+
if (apiKey) this.setJwt(apiKey);
|
|
15
|
+
this.setPrices(OpenRouterTextPricing);
|
|
16
|
+
}
|
|
17
|
+
}
|