@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.
- 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 +215 -9
- 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 +43 -6
- package/src/clients/pricing/llama.ts +18 -0
- package/src/clients/pricing/mistral.ts +34 -0
- package/src/clients/pricing/models.ts +23 -0
- 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 +110 -0
- package/src/clients/pricing/xai.ts +123 -66
- package/src/clients/types.ts +4 -0
- package/src/clients/xai.ts +152 -2
- 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/SyncedAgentWatcher.ts +13 -298
- package/src/services/index.ts +1 -0
- package/src/services/modules/index.ts +11 -2
- package/src/services/watchers/FsSyncer.ts +155 -0
- package/src/services/watchers/RemoteSyncer.ts +153 -0
- package/src/services/watchers/index.ts +2 -0
- 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 +22 -0
- package/ts_build/src/clients/index.js +150 -5
- 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 +17 -6
- package/ts_build/src/clients/pricing/index.js +65 -10
- 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 +9 -0
- package/ts_build/src/clients/pricing/models.js +19 -0
- package/ts_build/src/clients/pricing/models.js.map +1 -0
- 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 +46 -0
- package/ts_build/src/clients/pricing/types.js +49 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -0
- package/ts_build/src/clients/pricing/xai.d.ts +39 -64
- package/ts_build/src/clients/pricing/xai.js +93 -60
- 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 -58
- package/ts_build/src/clients/xai.js +123 -2
- 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/SyncedAgentWatcher.d.ts +0 -51
- package/ts_build/src/services/SyncedAgentWatcher.js +1 -282
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
- package/ts_build/src/services/index.d.ts +1 -0
- package/ts_build/src/services/index.js +1 -0
- package/ts_build/src/services/index.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/services/watchers/FsSyncer.d.ts +27 -0
- package/ts_build/src/services/watchers/FsSyncer.js +135 -0
- package/ts_build/src/services/watchers/FsSyncer.js.map +1 -0
- package/ts_build/src/services/watchers/RemoteSyncer.d.ts +28 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js +126 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -0
- package/ts_build/src/services/watchers/index.d.ts +2 -0
- package/ts_build/src/services/watchers/index.js +19 -0
- package/ts_build/src/services/watchers/index.js.map +1 -0
- 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/pricing/catalog.ts +0 -287
- package/ts_build/src/clients/pricing/catalog.d.ts +0 -28
- package/ts_build/src/clients/pricing/catalog.js +0 -179
- package/ts_build/src/clients/pricing/catalog.js.map +0 -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
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
507
|
-
|
|
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(
|
|
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
|
+
}
|