agent-accelerator 0.1.0 → 0.2.0
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/README.md +83 -112
- package/{SYSTEM_PROMPT_AGENT.md → examples/prompts/SYSTEM_PROMPT_AGENT.md} +1 -1
- package/package.json +14 -9
- package/src/agent/agent.ts +191 -71
- package/src/agent/context.ts +1 -0
- package/src/agent/delegation.ts +61 -12
- package/src/agent/loop.ts +71 -48
- package/src/data/README.md +6 -6
- package/src/index.ts +130 -45
- package/src/models/catalog-cache.ts +60 -9
- package/src/models/catalog.ts +53 -7
- package/src/providers/google.ts +926 -0
- package/src/providers/openai-compat.ts +1147 -0
- package/src/providers/openai.ts +959 -0
- package/src/providers/openrouter-responses.ts +949 -0
- package/src/providers/openrouter.ts +1037 -0
- package/src/{ai-sdk → providers}/registry.ts +43 -55
- package/src/providers.ts +490 -0
- package/src/streaming/sse-parser.ts +6 -4
- package/src/tools/executor.ts +19 -6
- package/src/tools/schema.ts +21 -11
- package/src/types/agent.ts +8 -1
- package/src/types/core.ts +1 -1
- package/src/types/message.ts +5 -0
- package/src/types/model.ts +3 -7
- package/src/types/provider-payloads.ts +2 -84
- package/src/types/tool.ts +6 -0
- package/src/update-models.ts +56 -0
- package/src/utils/cache.ts +1 -1
- package/src/utils/documents.ts +517 -0
- package/src/utils/env.ts +0 -7
- package/src/{ai-sdk → utils}/errors.ts +61 -2
- package/src/utils/headers.ts +10 -20
- package/src/utils/media.ts +5 -2
- package/src/utils/retry.ts +89 -0
- package/src/utils/serialization.ts +15 -0
- package/src/ai-sdk/converters.ts +0 -342
- package/src/ai-sdk/executor.ts +0 -454
- package/src/ai-sdk/index.ts +0 -55
- package/src/ai-sdk/model-provider.ts +0 -303
- package/src/ai-sdk/options.ts +0 -306
- package/src/ai-sdk/provider.ts +0 -415
- package/src/tokens/counter.ts +0 -136
- /package/{SYSTEM_PROMPT.md → examples/prompts/SYSTEM_PROMPT.md} +0 -0
- /package/{SYSTEM_PROMPT_TOOLS.md → examples/prompts/SYSTEM_PROMPT_TOOLS.md} +0 -0
package/src/ai-sdk/provider.ts
DELETED
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
import { createGoogleGenerativeAI, type GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
|
2
|
-
import { createOpenAI, type OpenAIProvider as VercelOpenAIProvider } from "@ai-sdk/openai";
|
|
3
|
-
import { createOpenAICompatible, type OpenAICompatibleProvider as VercelOpenAICompatibleProvider } from "@ai-sdk/openai-compatible";
|
|
4
|
-
import type { LanguageModelV4 } from "@ai-sdk/provider";
|
|
5
|
-
import { getApiKey, getEnv } from "../utils/env.ts";
|
|
6
|
-
|
|
7
|
-
export interface AiSdkModelOptions {
|
|
8
|
-
apiKey?: string;
|
|
9
|
-
baseUrl?: string;
|
|
10
|
-
headers?: Record<string, string>;
|
|
11
|
-
fetch?: typeof fetch;
|
|
12
|
-
env?: Record<string, string>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type AnyAiSdkProvider =
|
|
16
|
-
| GoogleGenerativeAIProvider
|
|
17
|
-
| VercelOpenAIProvider
|
|
18
|
-
| VercelOpenAICompatibleProvider;
|
|
19
|
-
|
|
20
|
-
export const streamThoughtSignatures = new Map<string, string>();
|
|
21
|
-
|
|
22
|
-
function isBrowserFetchRuntime(): boolean {
|
|
23
|
-
try {
|
|
24
|
-
return typeof (globalThis as any).window !== "undefined" && typeof (globalThis as any).window.document !== "undefined";
|
|
25
|
-
} catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function requestUrlOf(input: any): string {
|
|
31
|
-
try {
|
|
32
|
-
if (typeof input === "string") return input;
|
|
33
|
-
if (input instanceof URL) return input.href;
|
|
34
|
-
if (input && typeof input.url === "string") return input.url;
|
|
35
|
-
} catch {}
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function createAiSdkFetch(customFetch?: typeof fetch): typeof fetch {
|
|
40
|
-
const fetchFn = async (input: any, init?: any) => {
|
|
41
|
-
const baseFetch = customFetch || globalThis.fetch;
|
|
42
|
-
if (init && init.headers) {
|
|
43
|
-
const h: any = init.headers;
|
|
44
|
-
if (h instanceof Headers) {
|
|
45
|
-
h.delete("x-multimodal-user-content");
|
|
46
|
-
h.delete("x-thought-signature-map");
|
|
47
|
-
h.delete("x-cached-content-id");
|
|
48
|
-
if (isBrowserFetchRuntime()) {
|
|
49
|
-
for (const k of ["user-agent", "x-session-id", "x-client-request-id", "session_id", "x-opencode-session", "x-opencode-client", "x-goog-api-client"]) {
|
|
50
|
-
try { h.delete(k); } catch {}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
} else if (typeof h === "object" && !Array.isArray(h)) {
|
|
54
|
-
if (h.authorization && !h.Authorization) {
|
|
55
|
-
const token = h.authorization;
|
|
56
|
-
delete h.authorization;
|
|
57
|
-
h.Authorization = token;
|
|
58
|
-
Object.defineProperty(h, "authorization", {
|
|
59
|
-
get() {
|
|
60
|
-
return this.Authorization;
|
|
61
|
-
},
|
|
62
|
-
set(v) {
|
|
63
|
-
this.Authorization = v;
|
|
64
|
-
},
|
|
65
|
-
enumerable: false,
|
|
66
|
-
configurable: true,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Drop legacy multimodal header (Vercel serializes image/file parts natively)
|
|
71
|
-
if ((h as any)["x-multimodal-user-content"]) delete (h as any)["x-multimodal-user-content"];
|
|
72
|
-
|
|
73
|
-
// Browser: drop SDK-internal / session headers that force a CORS
|
|
74
|
-
// preflight the provider never allow-lists (surfaces as bare
|
|
75
|
-
// `TypeError: Failed to fetch`). Session affinity still flows via
|
|
76
|
-
// providerOptions.promptCacheKey, never via headers.
|
|
77
|
-
if (isBrowserFetchRuntime()) {
|
|
78
|
-
for (const k of ["User-Agent", "user-agent", "x-session-id", "x-client-request-id", "session_id", "x-opencode-session", "x-opencode-client", "x-goog-api-client"]) {
|
|
79
|
-
try { delete (h as any)[k]; } catch {}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Replay Google thought_signature on OpenAI-compat Turn 2
|
|
84
|
-
const sigMapHeader = h["x-thought-signature-map"];
|
|
85
|
-
if (sigMapHeader) {
|
|
86
|
-
delete h["x-thought-signature-map"];
|
|
87
|
-
if (init.body && typeof init.body === "string") {
|
|
88
|
-
try {
|
|
89
|
-
const sigMap = JSON.parse(sigMapHeader);
|
|
90
|
-
const bodyObj = JSON.parse(init.body);
|
|
91
|
-
if (Array.isArray(bodyObj.messages)) {
|
|
92
|
-
for (const msg of bodyObj.messages) {
|
|
93
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
|
|
94
|
-
for (const tc of msg.tool_calls) {
|
|
95
|
-
const sig = sigMap[tc.id] || sigMap["default"];
|
|
96
|
-
if (sig) {
|
|
97
|
-
tc.extra_content = { google: { thought_signature: sig } };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
init.body = JSON.stringify(bodyObj);
|
|
103
|
-
}
|
|
104
|
-
} catch {}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Add extra_body cached_content if specified
|
|
109
|
-
const cacheId = h["x-cached-content-id"];
|
|
110
|
-
if (cacheId) {
|
|
111
|
-
delete h["x-cached-content-id"];
|
|
112
|
-
if (init.body && typeof init.body === "string") {
|
|
113
|
-
try {
|
|
114
|
-
const bodyObj = JSON.parse(init.body);
|
|
115
|
-
bodyObj.extra_body = {
|
|
116
|
-
...(bodyObj.extra_body || {}),
|
|
117
|
-
google: { cached_content: cacheId },
|
|
118
|
-
};
|
|
119
|
-
init.body = JSON.stringify(bodyObj);
|
|
120
|
-
} catch {}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let res: Response;
|
|
127
|
-
try {
|
|
128
|
-
res = await baseFetch(input, init);
|
|
129
|
-
} catch (e: any) {
|
|
130
|
-
const msg = e?.message ?? String(e);
|
|
131
|
-
if (e?.name === "AbortError" || msg.includes("aborted") || msg.includes("abort")) throw e;
|
|
132
|
-
if (msg.includes("Failed to fetch") || msg.includes("NetworkError") || msg.includes("Load failed") || e instanceof TypeError) {
|
|
133
|
-
const url = requestUrlOf(input);
|
|
134
|
-
const host = (() => { try { return new URL(url).host; } catch { return ""; } })();
|
|
135
|
-
throw new Error(
|
|
136
|
-
`Network request failed (browser could not reach ${host || url || "provider"}). ` +
|
|
137
|
-
`URL: ${url || "(unknown)"}. ` +
|
|
138
|
-
`Usual browser causes: CORS preflight blocked (custom header not allow-listed), ad-blocker, offline, wrong Base URL, or page not on http://localhost. ` +
|
|
139
|
-
`Check F12 Network tab for the failed OPTIONS/POST request. Original: ${msg}`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
throw e;
|
|
143
|
-
}
|
|
144
|
-
const contentType = res.headers.get("content-type") || "";
|
|
145
|
-
|
|
146
|
-
// 1. JSON normalization: ensure choices[i].index exists (strict Zod schema tolerance)
|
|
147
|
-
if (contentType.includes("application/json")) {
|
|
148
|
-
try {
|
|
149
|
-
const text = await res.text();
|
|
150
|
-
const data = JSON.parse(text);
|
|
151
|
-
let mutated = false;
|
|
152
|
-
if (data && Array.isArray(data.choices)) {
|
|
153
|
-
for (let i = 0; i < data.choices.length; i++) {
|
|
154
|
-
if (data.choices[i] && data.choices[i].index === undefined) {
|
|
155
|
-
data.choices[i].index = i;
|
|
156
|
-
mutated = true;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (mutated) {
|
|
161
|
-
return new Response(JSON.stringify(data), {
|
|
162
|
-
status: res.status,
|
|
163
|
-
statusText: res.statusText,
|
|
164
|
-
headers: res.headers,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
return new Response(text, {
|
|
168
|
-
status: res.status,
|
|
169
|
-
statusText: res.statusText,
|
|
170
|
-
headers: res.headers,
|
|
171
|
-
});
|
|
172
|
-
} catch {
|
|
173
|
-
return res;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 2. SSE stream normalization: ensure choices[i].index, finish_reason, and extract thought signatures
|
|
178
|
-
if (contentType.includes("text/event-stream") && res.body) {
|
|
179
|
-
let hasSeenFinishReason = false;
|
|
180
|
-
const transformStream = new TransformStream({
|
|
181
|
-
transform(chunk, controller) {
|
|
182
|
-
const text = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
|
|
183
|
-
const lines = text.split("\n");
|
|
184
|
-
const modifiedLines: string[] = [];
|
|
185
|
-
|
|
186
|
-
for (const line of lines) {
|
|
187
|
-
if (line.startsWith("data: ")) {
|
|
188
|
-
const dataPayload = line.slice(6).trim();
|
|
189
|
-
if (dataPayload === "[DONE]") {
|
|
190
|
-
if (!hasSeenFinishReason) {
|
|
191
|
-
hasSeenFinishReason = true;
|
|
192
|
-
modifiedLines.push(`data: ${JSON.stringify({
|
|
193
|
-
id: "finish-synth",
|
|
194
|
-
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
|
195
|
-
})}`);
|
|
196
|
-
}
|
|
197
|
-
modifiedLines.push(line);
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
const json = JSON.parse(dataPayload);
|
|
203
|
-
let lineMutated = false;
|
|
204
|
-
if (json && Array.isArray(json.choices)) {
|
|
205
|
-
for (let i = 0; i < json.choices.length; i++) {
|
|
206
|
-
const c = json.choices[i];
|
|
207
|
-
if (c) {
|
|
208
|
-
if (c.index === undefined) {
|
|
209
|
-
c.index = i;
|
|
210
|
-
lineMutated = true;
|
|
211
|
-
}
|
|
212
|
-
if (c.finish_reason) {
|
|
213
|
-
hasSeenFinishReason = true;
|
|
214
|
-
} else if (json.finish_reason) {
|
|
215
|
-
c.finish_reason = json.finish_reason;
|
|
216
|
-
hasSeenFinishReason = true;
|
|
217
|
-
lineMutated = true;
|
|
218
|
-
}
|
|
219
|
-
if (c.delta?.tool_calls && Array.isArray(c.delta.tool_calls)) {
|
|
220
|
-
for (let j = 0; j < c.delta.tool_calls.length; j++) {
|
|
221
|
-
const tc = c.delta.tool_calls[j];
|
|
222
|
-
if (tc) {
|
|
223
|
-
if (tc.index === undefined) {
|
|
224
|
-
tc.index = j;
|
|
225
|
-
lineMutated = true;
|
|
226
|
-
}
|
|
227
|
-
const sig = tc.extra_content?.google?.thought_signature;
|
|
228
|
-
if (sig) {
|
|
229
|
-
if (tc.id) streamThoughtSignatures.set(tc.id, sig);
|
|
230
|
-
streamThoughtSignatures.set("latest", sig);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (lineMutated) {
|
|
238
|
-
modifiedLines.push(`data: ${JSON.stringify(json)}`);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
} catch {}
|
|
243
|
-
}
|
|
244
|
-
modifiedLines.push(line);
|
|
245
|
-
}
|
|
246
|
-
controller.enqueue(new TextEncoder().encode(modifiedLines.join("\n")));
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
return new Response(res.body.pipeThrough(transformStream), {
|
|
251
|
-
status: res.status,
|
|
252
|
-
statusText: res.statusText,
|
|
253
|
-
headers: res.headers,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return res;
|
|
258
|
-
};
|
|
259
|
-
return fetchFn as typeof fetch;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Creates or retrieves a Vercel AI SDK provider instance for any given provider ID.
|
|
264
|
-
*/
|
|
265
|
-
export function getAiSdkProvider(
|
|
266
|
-
providerId: string,
|
|
267
|
-
options?: AiSdkModelOptions
|
|
268
|
-
): AnyAiSdkProvider {
|
|
269
|
-
const normId = providerId.toLowerCase().trim().replace(/\/.*$/, "");
|
|
270
|
-
const effectiveFetch = createAiSdkFetch(options?.fetch);
|
|
271
|
-
|
|
272
|
-
if (normId === "google") {
|
|
273
|
-
const apiKey = options?.apiKey || getApiKey("google", undefined, options?.env);
|
|
274
|
-
const baseURL = options?.baseUrl || "https://generativelanguage.googleapis.com/v1beta";
|
|
275
|
-
return createGoogleGenerativeAI({
|
|
276
|
-
apiKey,
|
|
277
|
-
baseURL,
|
|
278
|
-
headers: options?.headers,
|
|
279
|
-
fetch: effectiveFetch,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (normId === "openai") {
|
|
284
|
-
const apiKey =
|
|
285
|
-
options?.apiKey ||
|
|
286
|
-
options?.env?.["OPENAI_BASE_API_KEY"] ||
|
|
287
|
-
options?.env?.["OPENAI_API_KEY"] ||
|
|
288
|
-
getEnv("OPENAI_BASE_API_KEY") ||
|
|
289
|
-
getEnv("OPENAI_API_KEY") ||
|
|
290
|
-
getApiKey("openai", undefined, options?.env);
|
|
291
|
-
|
|
292
|
-
const baseURL =
|
|
293
|
-
options?.baseUrl ||
|
|
294
|
-
options?.env?.["OPENAI_BASE_URL"] ||
|
|
295
|
-
getEnv("OPENAI_BASE_URL") ||
|
|
296
|
-
getEnv("OPENAI_API_BASE") ||
|
|
297
|
-
"https://api.openai.com/v1";
|
|
298
|
-
|
|
299
|
-
if (baseURL && !baseURL.includes("api.openai.com")) {
|
|
300
|
-
return createOpenAICompatible({
|
|
301
|
-
name: "openai",
|
|
302
|
-
baseURL,
|
|
303
|
-
apiKey: apiKey || "dummy-key",
|
|
304
|
-
headers: options?.headers,
|
|
305
|
-
fetch: effectiveFetch,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return createOpenAI({
|
|
310
|
-
apiKey: apiKey || "dummy-key",
|
|
311
|
-
baseURL,
|
|
312
|
-
headers: options?.headers,
|
|
313
|
-
fetch: effectiveFetch,
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (normId === "opencode") {
|
|
318
|
-
const apiKey = options?.apiKey || getApiKey("opencode", undefined, options?.env);
|
|
319
|
-
const baseURL =
|
|
320
|
-
options?.baseUrl ||
|
|
321
|
-
options?.env?.["OPENCODE_BASE_URL"] ||
|
|
322
|
-
getEnv("OPENCODE_BASE_URL") ||
|
|
323
|
-
"https://opencode.ai/zen/v1";
|
|
324
|
-
|
|
325
|
-
return createOpenAICompatible({
|
|
326
|
-
name: "opencode",
|
|
327
|
-
baseURL,
|
|
328
|
-
apiKey: apiKey || "dummy-key",
|
|
329
|
-
headers: options?.headers,
|
|
330
|
-
fetch: effectiveFetch,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (normId === "openrouter") {
|
|
335
|
-
const apiKey = options?.apiKey || getApiKey("openrouter", undefined, options?.env);
|
|
336
|
-
const baseURL =
|
|
337
|
-
options?.baseUrl ||
|
|
338
|
-
options?.env?.["OPENROUTER_BASE_URL"] ||
|
|
339
|
-
getEnv("OPENROUTER_BASE_URL") ||
|
|
340
|
-
"https://openrouter.ai/api/v1";
|
|
341
|
-
|
|
342
|
-
return createOpenAICompatible({
|
|
343
|
-
name: "openrouter",
|
|
344
|
-
baseURL,
|
|
345
|
-
apiKey: apiKey || "dummy-key",
|
|
346
|
-
headers: options?.headers,
|
|
347
|
-
fetch: effectiveFetch,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Custom / OpenAI-compatible provider (groq, cerebras, together, fireworks, ollama, etc.)
|
|
352
|
-
const envPrefix = normId.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
353
|
-
const apiKey =
|
|
354
|
-
options?.apiKey ||
|
|
355
|
-
options?.env?.[`${envPrefix}_API_KEY`] ||
|
|
356
|
-
options?.env?.[`${envPrefix}_BASE_API_KEY`] ||
|
|
357
|
-
options?.env?.["OPENAI_BASE_API_KEY"] ||
|
|
358
|
-
options?.env?.["OPENAI_API_KEY"] ||
|
|
359
|
-
getEnv(`${envPrefix}_API_KEY`) ||
|
|
360
|
-
getEnv(`${envPrefix}_BASE_API_KEY`) ||
|
|
361
|
-
getEnv("OPENAI_BASE_API_KEY") ||
|
|
362
|
-
getEnv("OPENAI_API_KEY") ||
|
|
363
|
-
getApiKey(normId, undefined, options?.env) ||
|
|
364
|
-
"dummy-key";
|
|
365
|
-
|
|
366
|
-
const baseURL =
|
|
367
|
-
options?.baseUrl ||
|
|
368
|
-
options?.env?.[`${envPrefix}_BASE_URL`] ||
|
|
369
|
-
options?.env?.[`${envPrefix}_BASEURL`] ||
|
|
370
|
-
options?.env?.[`${envPrefix}_API_BASE`] ||
|
|
371
|
-
getEnv(`${envPrefix}_BASE_URL`) ||
|
|
372
|
-
getEnv(`${envPrefix}_BASEURL`) ||
|
|
373
|
-
getEnv(`${envPrefix}_API_BASE`) ||
|
|
374
|
-
getEnv("OPENAI_BASE_URL") ||
|
|
375
|
-
getEnv("OPENAI_API_BASE") ||
|
|
376
|
-
"https://api.openai.com/v1";
|
|
377
|
-
|
|
378
|
-
return createOpenAICompatible({
|
|
379
|
-
name: normId,
|
|
380
|
-
baseURL,
|
|
381
|
-
apiKey,
|
|
382
|
-
headers: options?.headers,
|
|
383
|
-
fetch: effectiveFetch,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Returns a Vercel AI SDK LanguageModel instance for any provider and model.
|
|
389
|
-
*/
|
|
390
|
-
export function getAiSdkModel(
|
|
391
|
-
providerId: string,
|
|
392
|
-
modelId: string,
|
|
393
|
-
options?: AiSdkModelOptions
|
|
394
|
-
): LanguageModelV4 {
|
|
395
|
-
const cleanId = modelId.replace(new RegExp(`^(${providerId}|google|openai|opencode|openrouter|models?)/`, "i"), "");
|
|
396
|
-
const provider = getAiSdkProvider(providerId, options);
|
|
397
|
-
|
|
398
|
-
if ("chat" in provider && typeof (provider as any).chat === "function") {
|
|
399
|
-
return (provider as any).chat(cleanId);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (typeof provider === "function") {
|
|
403
|
-
return (provider as any)(cleanId);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if ("chatModel" in provider && typeof (provider as any).chatModel === "function") {
|
|
407
|
-
return (provider as any).chatModel(cleanId);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if ("languageModel" in provider && typeof (provider as any).languageModel === "function") {
|
|
411
|
-
return (provider as any).languageModel(cleanId);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
throw new Error(`Unsupported Vercel AI SDK provider instance for ${providerId}`);
|
|
415
|
-
}
|
package/src/tokens/counter.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import type { Message, ContentPart } from "../types/message.ts";
|
|
2
|
-
import type { ProviderContext } from "../types/message.ts";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* High-speed token estimation utility.
|
|
6
|
-
* For English text, average is ~4 characters per token or ~0.75 words per token.
|
|
7
|
-
* Code and symbols average ~2-3 characters per token.
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* Estimates token count from text without contacting a provider tokenizer.
|
|
11
|
-
*
|
|
12
|
-
* @example `const tokens = estimateTokensFromText(prompt);`
|
|
13
|
-
*/
|
|
14
|
-
export function estimateTokensFromText(text: string): number {
|
|
15
|
-
if (!text || text.length === 0) return 0;
|
|
16
|
-
const chars = text.length;
|
|
17
|
-
let words = 0;
|
|
18
|
-
let inWord = false;
|
|
19
|
-
let codeMarkers = 0;
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < chars; i++) {
|
|
22
|
-
const code = text.charCodeAt(i);
|
|
23
|
-
// Whitespace: space(32), tab(9), LF(10), CR(13)
|
|
24
|
-
if (code === 32 || code === 9 || code === 10 || code === 13) {
|
|
25
|
-
if (inWord) {
|
|
26
|
-
words++;
|
|
27
|
-
inWord = false;
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
inWord = true;
|
|
31
|
-
// Code markers: { (123), } (125), ; (59), = (61), < (60), > (62), / (47), \ (92)
|
|
32
|
-
if (code === 123 || code === 125 || code === 59 || code === 61 || code === 60 || code === 62 || code === 47 || code === 92) {
|
|
33
|
-
codeMarkers++;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (inWord) words++;
|
|
38
|
-
|
|
39
|
-
const isCodeHeavy = codeMarkers > chars * 0.05;
|
|
40
|
-
const charDivisor = isCodeHeavy ? 3.0 : 3.8;
|
|
41
|
-
const wordFactor = isCodeHeavy ? 1.6 : 1.3;
|
|
42
|
-
return Math.ceil(Math.max(chars / charDivisor, words * wordFactor));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Estimates tokens for one multimodal, thinking, or tool content part.
|
|
47
|
-
*
|
|
48
|
-
* @example `const tokens = estimateTokensFromPart({ type: "text", text: prompt });`
|
|
49
|
-
*/
|
|
50
|
-
export function estimateTokensFromPart(part: ContentPart): number {
|
|
51
|
-
switch (part.type) {
|
|
52
|
-
case "text":
|
|
53
|
-
return estimateTokensFromText(part.text);
|
|
54
|
-
case "thinking":
|
|
55
|
-
return estimateTokensFromText(part.thinking);
|
|
56
|
-
case "image":
|
|
57
|
-
// Standard vision token cost ~258 tokens per low-res / 1000-1600 per high-res tile
|
|
58
|
-
return 258;
|
|
59
|
-
case "audio":
|
|
60
|
-
// ~25 tokens per second of audio
|
|
61
|
-
return 150;
|
|
62
|
-
case "video":
|
|
63
|
-
// ~250 tokens per frame / minute
|
|
64
|
-
return 500;
|
|
65
|
-
case "file":
|
|
66
|
-
return 400;
|
|
67
|
-
case "tool_call":
|
|
68
|
-
return (
|
|
69
|
-
estimateTokensFromText(part.name) +
|
|
70
|
-
estimateTokensFromText(JSON.stringify(part.arguments)) +
|
|
71
|
-
15
|
|
72
|
-
);
|
|
73
|
-
case "tool_result":
|
|
74
|
-
return (
|
|
75
|
-
estimateTokensFromText(part.name) +
|
|
76
|
-
estimateTokensFromText(
|
|
77
|
-
typeof part.result === "string" ? part.result : JSON.stringify(part.result)
|
|
78
|
-
) +
|
|
79
|
-
10
|
|
80
|
-
);
|
|
81
|
-
default:
|
|
82
|
-
return 0;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Estimates tokens for one normalized message including metadata overhead.
|
|
88
|
-
*
|
|
89
|
-
* @example `const tokens = estimateTokensFromMessage({ role: "user", content: "Hi" });`
|
|
90
|
-
*/
|
|
91
|
-
export function estimateTokensFromMessage(message: Message): number {
|
|
92
|
-
let count = 4; // Message metadata overhead (role, markers)
|
|
93
|
-
if (typeof message.content === "string") {
|
|
94
|
-
count += estimateTokensFromText(message.content);
|
|
95
|
-
} else if (Array.isArray(message.content)) {
|
|
96
|
-
for (const part of message.content) {
|
|
97
|
-
count += estimateTokensFromPart(part);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return count;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Estimates tokens for text, messages, or a complete ProviderContext.
|
|
105
|
-
*
|
|
106
|
-
* @example `const tokens = countTokens({ messages });`
|
|
107
|
-
*/
|
|
108
|
-
export function countTokens(
|
|
109
|
-
input: string | Message[] | ProviderContext
|
|
110
|
-
): number {
|
|
111
|
-
if (typeof input === "string") {
|
|
112
|
-
return estimateTokensFromText(input);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (Array.isArray(input)) {
|
|
116
|
-
let total = 0;
|
|
117
|
-
for (const msg of input) {
|
|
118
|
-
total += estimateTokensFromMessage(msg);
|
|
119
|
-
}
|
|
120
|
-
return total;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let total = 0;
|
|
124
|
-
if (input.systemPrompt) {
|
|
125
|
-
total += estimateTokensFromText(input.systemPrompt) + 5;
|
|
126
|
-
}
|
|
127
|
-
if (input.messages) {
|
|
128
|
-
for (const msg of input.messages) {
|
|
129
|
-
total += estimateTokensFromMessage(msg);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (input.tools) {
|
|
133
|
-
total += estimateTokensFromText(JSON.stringify(input.tools)) + 20;
|
|
134
|
-
}
|
|
135
|
-
return total;
|
|
136
|
-
}
|
|
File without changes
|
|
File without changes
|