veryfront 0.1.381 → 0.1.382
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/esm/deno.js +1 -1
- package/esm/src/provider/index.d.ts +3 -0
- package/esm/src/provider/index.d.ts.map +1 -1
- package/esm/src/provider/index.js +1 -0
- package/esm/src/provider/runtime-loader.d.ts +1 -11
- package/esm/src/provider/runtime-loader.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader.js +2 -775
- package/esm/src/provider/shared/index.d.ts +2 -1
- package/esm/src/provider/shared/index.d.ts.map +1 -1
- package/esm/src/provider/shared/index.js +3 -1
- package/esm/src/provider/veryfront-cloud/model-catalog.d.ts +31 -0
- package/esm/src/provider/veryfront-cloud/model-catalog.d.ts.map +1 -0
- package/esm/src/provider/veryfront-cloud/model-catalog.js +163 -0
- package/esm/src/provider/veryfront-cloud/provider.d.ts.map +1 -1
- package/esm/src/provider/veryfront-cloud/provider.js +1 -7
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/deps/esm.sh/@types/react-dom@19.2.3/client.d.ts +1 -1
- package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/global.d.ts +1 -0
- package/src/deps/esm.sh/@types/{react@19.2.3 → react@19.2.14}/index.d.ts +93 -24
- package/src/deps/esm.sh/react-dom@19.2.4/client.d.ts +1 -1
- package/src/src/provider/index.ts +20 -0
- package/src/src/provider/runtime-loader.ts +1 -1008
- package/src/src/provider/shared/index.ts +3 -1
- package/src/src/provider/veryfront-cloud/model-catalog.ts +220 -0
- package/src/src/provider/veryfront-cloud/provider.ts +3 -7
- package/src/src/utils/version-constant.ts +1 -1
- package/esm/src/provider/runtime-loader/provider-finish-reasons.d.ts +0 -9
- package/esm/src/provider/runtime-loader/provider-finish-reasons.d.ts.map +0 -1
- package/esm/src/provider/runtime-loader/provider-finish-reasons.js +0 -60
- package/src/src/provider/runtime-loader/provider-finish-reasons.ts +0 -69
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { getAnthropicMessagesUrl } from "./runtime-loader/provider-endpoints.js";
|
|
2
1
|
import { isNumberArray } from "./runtime-loader/provider-embedding-responses.js";
|
|
3
|
-
import {
|
|
4
|
-
import { createAnthropicRequestInit } from "./runtime-loader/provider-request-init.js";
|
|
5
|
-
import { parseSseChunk } from "./runtime-loader/provider-sse.js";
|
|
6
|
-
import { extractAnthropicUsage, mergeUsage, } from "./runtime-loader/provider-usage.js";
|
|
2
|
+
import { mergeUsage } from "./runtime-loader/provider-usage.js";
|
|
7
3
|
import { buildProviderError, parseRetryAfterMs, requestJson, requestStream, } from "./runtime-loader/provider-http.js";
|
|
8
4
|
import { readRecord } from "./runtime-loader/provider-records.js";
|
|
9
5
|
import { TOOL_INPUT_PENDING_THRESHOLD_MS, withToolInputStatusTransitions, } from "./runtime-loader/tool-input-status.js";
|
|
10
6
|
export { ProviderError, ProviderOverloadedError, ProviderQuotaError, ProviderRateLimitError, ProviderRequestError, } from "./runtime-loader/provider-http.js";
|
|
11
7
|
export { TOOL_INPUT_PENDING_THRESHOLD_MS, withToolInputStatusTransitions };
|
|
12
|
-
export { buildProviderError, isNumberArray, mergeUsage, parseRetryAfterMs,
|
|
8
|
+
export { buildProviderError, isNumberArray, mergeUsage, parseRetryAfterMs, readRecord, requestJson, requestStream, };
|
|
13
9
|
export function createWarningCollector() {
|
|
14
10
|
const list = [];
|
|
15
11
|
return {
|
|
@@ -118,715 +114,6 @@ export function readProviderOptions(providerOptions, ...providerNames) {
|
|
|
118
114
|
}
|
|
119
115
|
return merged;
|
|
120
116
|
}
|
|
121
|
-
function normalizeAnthropicToolChoice(toolChoice) {
|
|
122
|
-
if (typeof toolChoice === "string") {
|
|
123
|
-
return { type: toolChoice };
|
|
124
|
-
}
|
|
125
|
-
return toolChoice;
|
|
126
|
-
}
|
|
127
|
-
function toSnakeCaseRecord(record) {
|
|
128
|
-
return Object.fromEntries(Object.entries(record).map(([key, value]) => [
|
|
129
|
-
key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`),
|
|
130
|
-
value,
|
|
131
|
-
]));
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Recursive snake_case key converter for nested config objects (used for
|
|
135
|
-
* Anthropic mcp_servers, where authorizationToken / toolConfiguration /
|
|
136
|
-
* allowedTools all need conversion).
|
|
137
|
-
*/
|
|
138
|
-
function deepSnakeCase(value) {
|
|
139
|
-
if (Array.isArray(value)) {
|
|
140
|
-
return value.map(deepSnakeCase);
|
|
141
|
-
}
|
|
142
|
-
if (value !== null && typeof value === "object") {
|
|
143
|
-
return Object.fromEntries(Object.entries(value).map(([key, v]) => [
|
|
144
|
-
key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`),
|
|
145
|
-
deepSnakeCase(v),
|
|
146
|
-
]));
|
|
147
|
-
}
|
|
148
|
-
return value;
|
|
149
|
-
}
|
|
150
|
-
function pushAnthropicUserContent(messages, content) {
|
|
151
|
-
if (content.length === 0) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const lastMessage = messages.at(-1);
|
|
155
|
-
if (lastMessage?.role === "user") {
|
|
156
|
-
lastMessage.content.push(...content);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
messages.push({
|
|
160
|
-
role: "user",
|
|
161
|
-
content,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Resolves a {@link ProviderCacheTtl} into Anthropic's `cache_control` shape.
|
|
166
|
-
*
|
|
167
|
-
* Returns `undefined` when caching is not requested (`false` / `undefined`),
|
|
168
|
-
* `{ type: "ephemeral" }` for the 5-minute default (`true` / `"5m"`), or
|
|
169
|
-
* `{ type: "ephemeral", ttl: "1h" }` for the extended 1-hour cache.
|
|
170
|
-
*/
|
|
171
|
-
function resolveAnthropicCacheControlBlock(ttl) {
|
|
172
|
-
if (ttl === undefined || ttl === false) {
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
if (ttl === "1h") {
|
|
176
|
-
return { type: "ephemeral", ttl: "1h" };
|
|
177
|
-
}
|
|
178
|
-
return { type: "ephemeral" };
|
|
179
|
-
}
|
|
180
|
-
function toAnthropicMessages(prompt, systemCacheControl) {
|
|
181
|
-
const systemParts = [];
|
|
182
|
-
const messages = [];
|
|
183
|
-
for (const message of prompt) {
|
|
184
|
-
switch (message.role) {
|
|
185
|
-
case "system":
|
|
186
|
-
if (message.content.length > 0) {
|
|
187
|
-
systemParts.push(message.content);
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
case "user":
|
|
191
|
-
pushAnthropicUserContent(messages, [{
|
|
192
|
-
type: "text",
|
|
193
|
-
text: readTextParts(message.content),
|
|
194
|
-
}]);
|
|
195
|
-
break;
|
|
196
|
-
case "assistant":
|
|
197
|
-
messages.push({
|
|
198
|
-
role: "assistant",
|
|
199
|
-
content: message.content.map((part) => {
|
|
200
|
-
if (part.type === "text") {
|
|
201
|
-
return { type: "text", text: part.text };
|
|
202
|
-
}
|
|
203
|
-
if (part.type === "reasoning") {
|
|
204
|
-
// Redacted thinking blocks roundtrip as the encrypted blob
|
|
205
|
-
// form Anthropic gave us. Plain thinking blocks need the
|
|
206
|
-
// signature to verify on the server.
|
|
207
|
-
if (typeof part.redactedData === "string") {
|
|
208
|
-
return {
|
|
209
|
-
type: "redacted_thinking",
|
|
210
|
-
data: part.redactedData,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
type: "thinking",
|
|
215
|
-
thinking: part.text ?? "",
|
|
216
|
-
...(typeof part.signature === "string" ? { signature: part.signature } : {}),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
return {
|
|
220
|
-
type: "tool_use",
|
|
221
|
-
id: part.toolCallId,
|
|
222
|
-
name: part.toolName,
|
|
223
|
-
input: part.input,
|
|
224
|
-
};
|
|
225
|
-
}),
|
|
226
|
-
});
|
|
227
|
-
break;
|
|
228
|
-
case "tool":
|
|
229
|
-
pushAnthropicUserContent(messages, message.content.map((part) => ({
|
|
230
|
-
type: "tool_result",
|
|
231
|
-
tool_use_id: part.toolCallId,
|
|
232
|
-
content: stringifyJsonValue(part.output.value),
|
|
233
|
-
})));
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (systemParts.length === 0) {
|
|
238
|
-
return { messages };
|
|
239
|
-
}
|
|
240
|
-
const joined = systemParts.join("\n\n");
|
|
241
|
-
// Cache-controlled system prompts must use the array-of-blocks form so the
|
|
242
|
-
// breakpoint lands on an individual content block. Callers that don't opt
|
|
243
|
-
// in keep the legacy raw-string form for backward compatibility.
|
|
244
|
-
if (systemCacheControl) {
|
|
245
|
-
return {
|
|
246
|
-
system: [{
|
|
247
|
-
type: "text",
|
|
248
|
-
text: joined,
|
|
249
|
-
cache_control: systemCacheControl,
|
|
250
|
-
}],
|
|
251
|
-
messages,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return { system: joined, messages };
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Short-name → latest-versioned-type alias map for Anthropic provider tools.
|
|
258
|
-
*
|
|
259
|
-
* Anthropic tool types are date-stamped (e.g. `code_execution_20260120`) so
|
|
260
|
-
* callers either pin a version or get the latest. We accept both: a caller
|
|
261
|
-
* can pass `anthropic.code_execution` and we map to the latest known version,
|
|
262
|
-
* or pass `anthropic.code_execution_20250522` and we forward verbatim.
|
|
263
|
-
*
|
|
264
|
-
* Versions chosen here are the latest documented releases as of 2026-04-15
|
|
265
|
-
* — see https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview.
|
|
266
|
-
* When Anthropic ships newer versions, update this map.
|
|
267
|
-
*/
|
|
268
|
-
const ANTHROPIC_TOOL_VERSION_ALIASES = {
|
|
269
|
-
code_execution: "code_execution_20260120",
|
|
270
|
-
computer_use: "computer_20250124",
|
|
271
|
-
computer: "computer_20250124",
|
|
272
|
-
text_editor: "text_editor_20250728",
|
|
273
|
-
bash: "bash_20250124",
|
|
274
|
-
memory: "memory_20250818",
|
|
275
|
-
web_search: "web_search_20250305",
|
|
276
|
-
web_fetch: "web_fetch_20250910",
|
|
277
|
-
};
|
|
278
|
-
function resolveAnthropicProviderType(rawType) {
|
|
279
|
-
// Already-versioned types (contain a date stamp suffix) pass through verbatim.
|
|
280
|
-
if (/_\d{8}$/.test(rawType)) {
|
|
281
|
-
return rawType;
|
|
282
|
-
}
|
|
283
|
-
return ANTHROPIC_TOOL_VERSION_ALIASES[rawType] ?? rawType;
|
|
284
|
-
}
|
|
285
|
-
function toAnthropicTools(tools, toolsCacheControl) {
|
|
286
|
-
if (!tools) {
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
const normalized = [];
|
|
290
|
-
for (const tool of tools) {
|
|
291
|
-
if (tool.type === "function") {
|
|
292
|
-
normalized.push({
|
|
293
|
-
name: tool.name,
|
|
294
|
-
...(typeof tool.description === "string" ? { description: tool.description } : {}),
|
|
295
|
-
input_schema: unwrapToolInputSchema(tool.inputSchema),
|
|
296
|
-
});
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (!tool.id.startsWith("anthropic.")) {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
const rawType = tool.id.slice("anthropic.".length);
|
|
303
|
-
if (rawType.length === 0) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
normalized.push({
|
|
307
|
-
type: resolveAnthropicProviderType(rawType),
|
|
308
|
-
name: tool.name,
|
|
309
|
-
...toSnakeCaseRecord(tool.args),
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
if (normalized.length === 0) {
|
|
313
|
-
return undefined;
|
|
314
|
-
}
|
|
315
|
-
// Attach the cache breakpoint to the final tool entry so Anthropic caches
|
|
316
|
-
// the entire tools block up to and including that definition. Earlier tool
|
|
317
|
-
// entries are implicitly covered by the same breakpoint per Anthropic's
|
|
318
|
-
// walk-backward cache lookup behaviour.
|
|
319
|
-
if (toolsCacheControl) {
|
|
320
|
-
const lastIndex = normalized.length - 1;
|
|
321
|
-
normalized[lastIndex] = {
|
|
322
|
-
...normalized[lastIndex],
|
|
323
|
-
cache_control: toolsCacheControl,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
return normalized;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Anthropic's Messages API requires `max_tokens` on every call, so the
|
|
330
|
-
* outbound request builder must always supply a number. Picking the right
|
|
331
|
-
* one means knowing the model: different Claude families have wildly
|
|
332
|
-
* different maximum output budgets, and a flat default either truncates
|
|
333
|
-
* modern models mid-response or gets rejected with "too many tokens" on
|
|
334
|
-
* older ones. Return the model's advertised maximum as the default, and
|
|
335
|
-
* clamp caller-provided values at that same ceiling for known models so a
|
|
336
|
-
* bad input becomes a clipped response rather than an API error. Unknown
|
|
337
|
-
* model ids get a conservative 4096 fallback and pass caller values
|
|
338
|
-
* through unchanged, since we have no intel to clamp against.
|
|
339
|
-
*/
|
|
340
|
-
function getAnthropicModelCapabilities(modelId) {
|
|
341
|
-
if (modelId.includes("claude-sonnet-4-6") || modelId.includes("claude-opus-4-6")) {
|
|
342
|
-
return { maxOutputTokens: 128_000, isKnownModel: true };
|
|
343
|
-
}
|
|
344
|
-
if (modelId.includes("claude-sonnet-4-5") ||
|
|
345
|
-
modelId.includes("claude-opus-4-5") ||
|
|
346
|
-
modelId.includes("claude-haiku-4-5")) {
|
|
347
|
-
return { maxOutputTokens: 64_000, isKnownModel: true };
|
|
348
|
-
}
|
|
349
|
-
if (modelId.includes("claude-opus-4-1")) {
|
|
350
|
-
return { maxOutputTokens: 32_000, isKnownModel: true };
|
|
351
|
-
}
|
|
352
|
-
if (modelId.includes("claude-sonnet-4-")) {
|
|
353
|
-
return { maxOutputTokens: 64_000, isKnownModel: true };
|
|
354
|
-
}
|
|
355
|
-
if (modelId.includes("claude-opus-4-")) {
|
|
356
|
-
return { maxOutputTokens: 32_000, isKnownModel: true };
|
|
357
|
-
}
|
|
358
|
-
if (modelId.includes("claude-3-haiku")) {
|
|
359
|
-
return { maxOutputTokens: 4096, isKnownModel: true };
|
|
360
|
-
}
|
|
361
|
-
return { maxOutputTokens: 4096, isKnownModel: false };
|
|
362
|
-
}
|
|
363
|
-
function resolveAnthropicMaxTokens(modelId, callerMaxOutputTokens) {
|
|
364
|
-
const { maxOutputTokens: modelMax, isKnownModel } = getAnthropicModelCapabilities(modelId);
|
|
365
|
-
const requested = callerMaxOutputTokens ?? modelMax;
|
|
366
|
-
if (isKnownModel && requested > modelMax) {
|
|
367
|
-
return modelMax;
|
|
368
|
-
}
|
|
369
|
-
return requested;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Map a unified reasoning effort level to an Anthropic `thinking.budget_tokens`
|
|
373
|
-
* value. Anthropic's minimum accepted budget is 1024; higher tiers give Claude
|
|
374
|
-
* more headroom to explore. `max` maps to the upper bound documented for
|
|
375
|
-
* Claude 4.x family (32k tokens of thinking — caller can override via
|
|
376
|
-
* `budgetTokens` if they need more).
|
|
377
|
-
*/
|
|
378
|
-
function resolveAnthropicThinkingBudget(option) {
|
|
379
|
-
if (!option || option.enabled !== true) {
|
|
380
|
-
return undefined;
|
|
381
|
-
}
|
|
382
|
-
if (typeof option.budgetTokens === "number" && option.budgetTokens >= 1024) {
|
|
383
|
-
return option.budgetTokens;
|
|
384
|
-
}
|
|
385
|
-
switch (option.effort) {
|
|
386
|
-
case "low":
|
|
387
|
-
return 1024;
|
|
388
|
-
case "high":
|
|
389
|
-
return 16_384;
|
|
390
|
-
case "max":
|
|
391
|
-
return 32_768;
|
|
392
|
-
case "medium":
|
|
393
|
-
default:
|
|
394
|
-
return 4096;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
function buildAnthropicMessagesRequest(modelId, providerName, options, stream, warnings) {
|
|
398
|
-
const systemCacheControl = resolveAnthropicCacheControlBlock(options.cacheControl?.system);
|
|
399
|
-
const toolsCacheControl = resolveAnthropicCacheControlBlock(options.cacheControl?.tools);
|
|
400
|
-
const { system, messages } = toAnthropicMessages(options.prompt, systemCacheControl);
|
|
401
|
-
const anthropicTools = toAnthropicTools(options.tools, toolsCacheControl);
|
|
402
|
-
const thinkingBudget = resolveAnthropicThinkingBudget(options.reasoning);
|
|
403
|
-
const thinkingEnabled = thinkingBudget !== undefined;
|
|
404
|
-
// Anthropic doesn't support these unified options at all — emit warnings
|
|
405
|
-
// so callers don't quietly pass values that have zero effect.
|
|
406
|
-
if (options.presencePenalty !== undefined) {
|
|
407
|
-
warnings.push({
|
|
408
|
-
type: "unsupported-setting",
|
|
409
|
-
provider: "anthropic",
|
|
410
|
-
setting: "presencePenalty",
|
|
411
|
-
details: "Anthropic Messages API has no equivalent and the value was dropped.",
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
if (options.frequencyPenalty !== undefined) {
|
|
415
|
-
warnings.push({
|
|
416
|
-
type: "unsupported-setting",
|
|
417
|
-
provider: "anthropic",
|
|
418
|
-
setting: "frequencyPenalty",
|
|
419
|
-
details: "Anthropic Messages API has no equivalent and the value was dropped.",
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
if (options.seed !== undefined) {
|
|
423
|
-
warnings.push({
|
|
424
|
-
type: "unsupported-setting",
|
|
425
|
-
provider: "anthropic",
|
|
426
|
-
setting: "seed",
|
|
427
|
-
details: "Anthropic Messages API does not support deterministic seeding.",
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
if (options.topK !== undefined) {
|
|
431
|
-
warnings.push({
|
|
432
|
-
type: "unsupported-setting",
|
|
433
|
-
provider: "anthropic",
|
|
434
|
-
setting: "topK",
|
|
435
|
-
details: "Anthropic Messages API does not expose top_k on this surface.",
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
if (options.stopSequences && options.stopSequences.length > 4) {
|
|
439
|
-
warnings.push({
|
|
440
|
-
type: "unsupported-setting",
|
|
441
|
-
provider: "anthropic",
|
|
442
|
-
setting: "stopSequences",
|
|
443
|
-
details: `Anthropic accepts at most 4 stop sequences; ${options.stopSequences.length} were provided and the extras were truncated.`,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
if (thinkingEnabled && options.temperature !== undefined) {
|
|
447
|
-
warnings.push({
|
|
448
|
-
type: "unsupported-setting",
|
|
449
|
-
provider: "anthropic",
|
|
450
|
-
setting: "temperature",
|
|
451
|
-
details: "Dropped because Anthropic rejects sampling params when extended thinking is enabled.",
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
if (thinkingEnabled && options.topP !== undefined) {
|
|
455
|
-
warnings.push({
|
|
456
|
-
type: "unsupported-setting",
|
|
457
|
-
provider: "anthropic",
|
|
458
|
-
setting: "topP",
|
|
459
|
-
details: "Dropped because Anthropic rejects sampling params when extended thinking is enabled.",
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
463
|
-
warnings.push({
|
|
464
|
-
type: "unsupported-setting",
|
|
465
|
-
provider: "anthropic",
|
|
466
|
-
setting: "responseFormat",
|
|
467
|
-
details: "Anthropic Messages API does not have a structured-output response_format equivalent. Use a tool with the schema as input_schema instead.",
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
// Anthropic requires max_tokens > budget_tokens when thinking is enabled.
|
|
471
|
-
// Growing max_tokens by the thinking budget preserves the caller's intended
|
|
472
|
-
// output budget, and we clamp the sum at the model's advertised maximum so
|
|
473
|
-
// the request never exceeds the API's hard cap.
|
|
474
|
-
const baseMaxTokens = resolveAnthropicMaxTokens(modelId, options.maxOutputTokens);
|
|
475
|
-
const maxTokens = thinkingEnabled
|
|
476
|
-
? Math.min(baseMaxTokens + (thinkingBudget ?? 0), getAnthropicModelCapabilities(modelId).maxOutputTokens)
|
|
477
|
-
: baseMaxTokens;
|
|
478
|
-
const body = {
|
|
479
|
-
model: modelId,
|
|
480
|
-
messages,
|
|
481
|
-
max_tokens: maxTokens,
|
|
482
|
-
...(stream ? { stream: true } : {}),
|
|
483
|
-
...(system ? { system } : {}),
|
|
484
|
-
// Sampling params are mutually exclusive with thinking on Anthropic — the
|
|
485
|
-
// API rejects the combo outright. Drop them silently when thinking is on
|
|
486
|
-
// (callers see thinking's output instead of what they'd have gotten from
|
|
487
|
-
// custom sampling, which is the documented tradeoff).
|
|
488
|
-
...(!thinkingEnabled && options.temperature !== undefined
|
|
489
|
-
? { temperature: options.temperature }
|
|
490
|
-
: {}),
|
|
491
|
-
...(!thinkingEnabled && options.topP !== undefined ? { top_p: options.topP } : {}),
|
|
492
|
-
...(options.stopSequences && options.stopSequences.length > 0
|
|
493
|
-
? { stop_sequences: options.stopSequences.slice(0, 4) }
|
|
494
|
-
: {}),
|
|
495
|
-
...(anthropicTools ? { tools: anthropicTools } : {}),
|
|
496
|
-
...(options.toolChoice !== undefined
|
|
497
|
-
? { tool_choice: normalizeAnthropicToolChoice(options.toolChoice) }
|
|
498
|
-
: {}),
|
|
499
|
-
...(thinkingEnabled ? { thinking: { type: "enabled", budget_tokens: thinkingBudget } } : {}),
|
|
500
|
-
...(typeof options.userId === "string" && options.userId.length > 0
|
|
501
|
-
? { metadata: { user_id: options.userId } }
|
|
502
|
-
: {}),
|
|
503
|
-
...(options.mcpServers && options.mcpServers.length > 0
|
|
504
|
-
? { mcp_servers: deepSnakeCase(options.mcpServers) }
|
|
505
|
-
: {}),
|
|
506
|
-
...(options.anthropicContainer !== undefined ? { container: options.anthropicContainer } : {}),
|
|
507
|
-
};
|
|
508
|
-
Object.assign(body, readProviderOptions(options.providerOptions, "anthropic", providerName));
|
|
509
|
-
return body;
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Best-effort camelCase normalization of a single Anthropic citation
|
|
513
|
-
* record. Handles the union of fields across web_search_result_location,
|
|
514
|
-
* web_fetch_result_location, char_location, page_location, and
|
|
515
|
-
* content_block_location citation kinds — see
|
|
516
|
-
* https://docs.claude.com/en/docs/build-with-claude/citations
|
|
517
|
-
*/
|
|
518
|
-
function normalizeAnthropicCitation(raw) {
|
|
519
|
-
const r = readRecord(raw);
|
|
520
|
-
if (!r)
|
|
521
|
-
return undefined;
|
|
522
|
-
const typeStr = typeof r.type === "string" ? r.type : undefined;
|
|
523
|
-
if (!typeStr)
|
|
524
|
-
return undefined;
|
|
525
|
-
const out = { type: typeStr };
|
|
526
|
-
if (typeof r.cited_text === "string")
|
|
527
|
-
out.citedText = r.cited_text;
|
|
528
|
-
if (typeof r.url === "string")
|
|
529
|
-
out.url = r.url;
|
|
530
|
-
if (typeof r.title === "string")
|
|
531
|
-
out.title = r.title;
|
|
532
|
-
if (typeof r.start_char_index === "number")
|
|
533
|
-
out.startCharIndex = r.start_char_index;
|
|
534
|
-
if (typeof r.end_char_index === "number")
|
|
535
|
-
out.endCharIndex = r.end_char_index;
|
|
536
|
-
if (typeof r.start_block_index === "number")
|
|
537
|
-
out.startBlockIndex = r.start_block_index;
|
|
538
|
-
if (typeof r.end_block_index === "number")
|
|
539
|
-
out.endBlockIndex = r.end_block_index;
|
|
540
|
-
if (typeof r.start_page_number === "number")
|
|
541
|
-
out.startPageNumber = r.start_page_number;
|
|
542
|
-
if (typeof r.end_page_number === "number")
|
|
543
|
-
out.endPageNumber = r.end_page_number;
|
|
544
|
-
if (typeof r.document_index === "number")
|
|
545
|
-
out.documentIndex = r.document_index;
|
|
546
|
-
if (typeof r.document_title === "string")
|
|
547
|
-
out.documentTitle = r.document_title;
|
|
548
|
-
return out;
|
|
549
|
-
}
|
|
550
|
-
function buildAnthropicGenerateResult(payload) {
|
|
551
|
-
const record = readRecord(payload);
|
|
552
|
-
const content = Array.isArray(record?.content) ? record.content : [];
|
|
553
|
-
const normalized = [];
|
|
554
|
-
for (const blockValue of content) {
|
|
555
|
-
const block = readRecord(blockValue);
|
|
556
|
-
const blockType = typeof block?.type === "string" ? block.type : undefined;
|
|
557
|
-
if (blockType === "text" && typeof block?.text === "string" && block.text.length > 0) {
|
|
558
|
-
const citationsRaw = Array.isArray(block.citations) ? block.citations : undefined;
|
|
559
|
-
const citations = citationsRaw
|
|
560
|
-
?.flatMap((c) => {
|
|
561
|
-
const normalizedCitation = normalizeAnthropicCitation(c);
|
|
562
|
-
return normalizedCitation ? [normalizedCitation] : [];
|
|
563
|
-
});
|
|
564
|
-
normalized.push({
|
|
565
|
-
type: "text",
|
|
566
|
-
text: block.text,
|
|
567
|
-
...(citations && citations.length > 0 ? { citations } : {}),
|
|
568
|
-
});
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
// Thinking blocks carry the cleartext trace plus a signature that
|
|
572
|
-
// Anthropic uses to verify on subsequent turns. Surfacing both lets
|
|
573
|
-
// callers persist them as `reasoning` content parts and replay on
|
|
574
|
-
// the next turn so Claude can continue from the same thinking.
|
|
575
|
-
if (blockType === "thinking") {
|
|
576
|
-
normalized.push({
|
|
577
|
-
type: "reasoning",
|
|
578
|
-
...(typeof block?.thinking === "string" ? { text: block.thinking } : {}),
|
|
579
|
-
...(typeof block?.signature === "string" ? { signature: block.signature } : {}),
|
|
580
|
-
});
|
|
581
|
-
continue;
|
|
582
|
-
}
|
|
583
|
-
// Redacted thinking blocks arrive when Claude's safety classifier
|
|
584
|
-
// hides the trace. Pass the encrypted blob through opaquely so the
|
|
585
|
-
// caller can replay it on the next turn (Anthropic still needs the
|
|
586
|
-
// blob to verify continuity even though it can't read it).
|
|
587
|
-
if (blockType === "redacted_thinking" && typeof block?.data === "string") {
|
|
588
|
-
normalized.push({
|
|
589
|
-
type: "reasoning",
|
|
590
|
-
redactedData: block.data,
|
|
591
|
-
});
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
if ((blockType === "tool_use" || blockType === "server_tool_use") &&
|
|
595
|
-
typeof block?.id === "string" &&
|
|
596
|
-
typeof block?.name === "string") {
|
|
597
|
-
normalized.push({
|
|
598
|
-
type: "tool-call",
|
|
599
|
-
toolCallId: block.id,
|
|
600
|
-
toolName: block.name,
|
|
601
|
-
input: stringifyJsonValue(block.input ?? {}),
|
|
602
|
-
});
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
if (blockType === "web_search_tool_result" &&
|
|
606
|
-
typeof block?.tool_use_id === "string" &&
|
|
607
|
-
Array.isArray(block?.content)) {
|
|
608
|
-
normalized.push({
|
|
609
|
-
type: "tool-result",
|
|
610
|
-
toolCallId: block.tool_use_id,
|
|
611
|
-
toolName: "web_search",
|
|
612
|
-
result: block.content,
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
if (blockType === "web_fetch_tool_result" &&
|
|
616
|
-
typeof block?.tool_use_id === "string" &&
|
|
617
|
-
readRecord(block?.content)) {
|
|
618
|
-
normalized.push({
|
|
619
|
-
type: "tool-result",
|
|
620
|
-
toolCallId: block.tool_use_id,
|
|
621
|
-
toolName: "web_fetch",
|
|
622
|
-
result: block.content,
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return {
|
|
627
|
-
content: normalized,
|
|
628
|
-
finishReason: normalizeAnthropicFinishReason(record?.stop_reason),
|
|
629
|
-
usage: extractAnthropicUsage(payload),
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
async function* streamAnthropicCompatibleParts(stream) {
|
|
633
|
-
const decoder = new TextDecoder();
|
|
634
|
-
let buffer = "";
|
|
635
|
-
const toolCalls = new Map();
|
|
636
|
-
const reasoningBlocks = new Map();
|
|
637
|
-
let finishReason = null;
|
|
638
|
-
let usage;
|
|
639
|
-
for await (const chunk of stream) {
|
|
640
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
641
|
-
const parsed = parseSseChunk(buffer);
|
|
642
|
-
buffer = parsed.remainder;
|
|
643
|
-
for (const event of parsed.events) {
|
|
644
|
-
if (event === "[DONE]") {
|
|
645
|
-
continue;
|
|
646
|
-
}
|
|
647
|
-
const record = readRecord(event);
|
|
648
|
-
const eventType = typeof record?.type === "string" ? record.type : undefined;
|
|
649
|
-
usage = mergeUsage(usage, extractAnthropicUsage(record));
|
|
650
|
-
if (eventType === "message_start") {
|
|
651
|
-
usage = mergeUsage(usage, extractAnthropicUsage(record?.message));
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (eventType === "content_block_start") {
|
|
655
|
-
const index = typeof record?.index === "number" ? record.index : 0;
|
|
656
|
-
const contentBlock = readRecord(record?.content_block);
|
|
657
|
-
const blockType = typeof contentBlock?.type === "string" ? contentBlock.type : undefined;
|
|
658
|
-
if (blockType === "text" && typeof contentBlock?.text === "string" &&
|
|
659
|
-
contentBlock.text.length > 0) {
|
|
660
|
-
yield { type: "text-delta", delta: contentBlock.text };
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
if (blockType === "thinking") {
|
|
664
|
-
const reasoningId = `thinking-${index}`;
|
|
665
|
-
reasoningBlocks.set(index, { id: reasoningId });
|
|
666
|
-
yield {
|
|
667
|
-
type: "reasoning-start",
|
|
668
|
-
id: reasoningId,
|
|
669
|
-
};
|
|
670
|
-
if (typeof contentBlock?.thinking === "string" && contentBlock.thinking.length > 0) {
|
|
671
|
-
yield {
|
|
672
|
-
type: "reasoning-delta",
|
|
673
|
-
id: reasoningId,
|
|
674
|
-
delta: contentBlock.thinking,
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
// Redacted thinking blocks arrive as opaque encrypted payloads when
|
|
680
|
-
// Claude's safety classifier flags the reasoning trace. Surface them
|
|
681
|
-
// as a zero-length reasoning block so callers know thinking happened
|
|
682
|
-
// without leaking the (legitimately hidden) contents.
|
|
683
|
-
if (blockType === "redacted_thinking") {
|
|
684
|
-
const reasoningId = `thinking-${index}`;
|
|
685
|
-
reasoningBlocks.set(index, { id: reasoningId });
|
|
686
|
-
yield {
|
|
687
|
-
type: "reasoning-start",
|
|
688
|
-
id: reasoningId,
|
|
689
|
-
};
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
if ((blockType === "tool_use" || blockType === "server_tool_use") &&
|
|
693
|
-
typeof contentBlock?.id === "string" &&
|
|
694
|
-
typeof contentBlock?.name === "string") {
|
|
695
|
-
const providerExecuted = blockType === "server_tool_use" ? true : undefined;
|
|
696
|
-
const current = {
|
|
697
|
-
id: contentBlock.id,
|
|
698
|
-
name: contentBlock.name,
|
|
699
|
-
input: "",
|
|
700
|
-
...(providerExecuted ? { providerExecuted } : {}),
|
|
701
|
-
};
|
|
702
|
-
toolCalls.set(index, current);
|
|
703
|
-
yield {
|
|
704
|
-
type: "tool-input-start",
|
|
705
|
-
id: current.id,
|
|
706
|
-
toolName: current.name,
|
|
707
|
-
...(providerExecuted ? { providerExecuted } : {}),
|
|
708
|
-
};
|
|
709
|
-
const initialInput = contentBlock.input;
|
|
710
|
-
if (initialInput !== undefined) {
|
|
711
|
-
const serializedInput = stringifyJsonValue(initialInput);
|
|
712
|
-
current.input += serializedInput;
|
|
713
|
-
yield {
|
|
714
|
-
type: "tool-input-delta",
|
|
715
|
-
id: current.id,
|
|
716
|
-
delta: serializedInput,
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
if (blockType === "web_search_tool_result" &&
|
|
722
|
-
typeof contentBlock?.tool_use_id === "string" &&
|
|
723
|
-
Array.isArray(contentBlock?.content)) {
|
|
724
|
-
yield {
|
|
725
|
-
type: "tool-result",
|
|
726
|
-
toolCallId: contentBlock.tool_use_id,
|
|
727
|
-
toolName: "web_search",
|
|
728
|
-
result: contentBlock.content,
|
|
729
|
-
providerExecuted: true,
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
if (blockType === "web_fetch_tool_result" &&
|
|
733
|
-
typeof contentBlock?.tool_use_id === "string" &&
|
|
734
|
-
readRecord(contentBlock?.content)) {
|
|
735
|
-
yield {
|
|
736
|
-
type: "tool-result",
|
|
737
|
-
toolCallId: contentBlock.tool_use_id,
|
|
738
|
-
toolName: "web_fetch",
|
|
739
|
-
result: contentBlock.content,
|
|
740
|
-
providerExecuted: true,
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
continue;
|
|
744
|
-
}
|
|
745
|
-
if (eventType === "content_block_delta") {
|
|
746
|
-
const index = typeof record?.index === "number" ? record.index : 0;
|
|
747
|
-
const delta = readRecord(record?.delta);
|
|
748
|
-
const deltaType = typeof delta?.type === "string" ? delta.type : undefined;
|
|
749
|
-
if (deltaType === "text_delta" && typeof delta?.text === "string" && delta.text.length > 0) {
|
|
750
|
-
yield { type: "text-delta", delta: delta.text };
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
if (deltaType === "thinking_delta" && typeof delta?.thinking === "string" &&
|
|
754
|
-
delta.thinking.length > 0) {
|
|
755
|
-
const current = reasoningBlocks.get(index);
|
|
756
|
-
if (!current) {
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
yield {
|
|
760
|
-
type: "reasoning-delta",
|
|
761
|
-
id: current.id,
|
|
762
|
-
delta: delta.thinking,
|
|
763
|
-
};
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (deltaType === "input_json_delta" && typeof delta?.partial_json === "string") {
|
|
767
|
-
const current = toolCalls.get(index);
|
|
768
|
-
if (!current) {
|
|
769
|
-
continue;
|
|
770
|
-
}
|
|
771
|
-
current.input += delta.partial_json;
|
|
772
|
-
yield {
|
|
773
|
-
type: "tool-input-delta",
|
|
774
|
-
id: current.id,
|
|
775
|
-
delta: delta.partial_json,
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
continue;
|
|
779
|
-
}
|
|
780
|
-
if (eventType === "content_block_stop") {
|
|
781
|
-
const index = typeof record?.index === "number" ? record.index : 0;
|
|
782
|
-
const reasoning = reasoningBlocks.get(index);
|
|
783
|
-
if (reasoning) {
|
|
784
|
-
yield {
|
|
785
|
-
type: "reasoning-end",
|
|
786
|
-
id: reasoning.id,
|
|
787
|
-
};
|
|
788
|
-
reasoningBlocks.delete(index);
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
const current = toolCalls.get(index);
|
|
792
|
-
if (!current) {
|
|
793
|
-
continue;
|
|
794
|
-
}
|
|
795
|
-
yield {
|
|
796
|
-
type: "tool-call",
|
|
797
|
-
toolCallId: current.id,
|
|
798
|
-
toolName: current.name,
|
|
799
|
-
input: current.input.length > 0 ? current.input : "{}",
|
|
800
|
-
...(current.providerExecuted ? { providerExecuted: true } : {}),
|
|
801
|
-
};
|
|
802
|
-
toolCalls.delete(index);
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
if (eventType === "message_delta") {
|
|
806
|
-
const delta = readRecord(record?.delta);
|
|
807
|
-
const normalizedFinishReason = normalizeAnthropicFinishReason(delta?.stop_reason);
|
|
808
|
-
if (normalizedFinishReason) {
|
|
809
|
-
finishReason = normalizedFinishReason;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (buffer.trim().length > 0) {
|
|
815
|
-
const parsed = parseSseChunk(`${buffer}\n\n`);
|
|
816
|
-
for (const event of parsed.events) {
|
|
817
|
-
if (event === "[DONE]") {
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
const record = readRecord(event);
|
|
821
|
-
usage = mergeUsage(usage, extractAnthropicUsage(record));
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
yield {
|
|
825
|
-
type: "finish",
|
|
826
|
-
finishReason,
|
|
827
|
-
...(usage ? { usage } : {}),
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
117
|
export function unwrapToolInputSchema(inputSchema) {
|
|
831
118
|
if (typeof inputSchema !== "object" || inputSchema === null || Array.isArray(inputSchema)) {
|
|
832
119
|
return inputSchema;
|
|
@@ -834,63 +121,3 @@ export function unwrapToolInputSchema(inputSchema) {
|
|
|
834
121
|
const candidate = Reflect.get(inputSchema, "jsonSchema");
|
|
835
122
|
return candidate ?? inputSchema;
|
|
836
123
|
}
|
|
837
|
-
export function createAnthropicModelRuntime(config, modelId) {
|
|
838
|
-
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
839
|
-
return {
|
|
840
|
-
provider: config.name ?? "anthropic",
|
|
841
|
-
modelId,
|
|
842
|
-
specificationVersion: "v3",
|
|
843
|
-
supportedUrls: {},
|
|
844
|
-
doGenerate(optionsForRuntime) {
|
|
845
|
-
const options = optionsForRuntime;
|
|
846
|
-
const url = getAnthropicMessagesUrl(config.baseURL);
|
|
847
|
-
const warnings = createWarningCollector();
|
|
848
|
-
const body = buildAnthropicMessagesRequest(modelId, config.name ?? "anthropic", options, false, warnings);
|
|
849
|
-
return requestJson({
|
|
850
|
-
url,
|
|
851
|
-
fetchImpl,
|
|
852
|
-
providerLabel: config.name ?? "anthropic",
|
|
853
|
-
providerKind: "anthropic",
|
|
854
|
-
init: createAnthropicRequestInit({
|
|
855
|
-
apiKey: config.apiKey,
|
|
856
|
-
authToken: config.authToken,
|
|
857
|
-
extraHeaders: options.headers,
|
|
858
|
-
body: JSON.stringify(body),
|
|
859
|
-
signal: options.abortSignal,
|
|
860
|
-
}),
|
|
861
|
-
}).then((payload) => {
|
|
862
|
-
const drained = warnings.drain();
|
|
863
|
-
return {
|
|
864
|
-
...buildAnthropicGenerateResult(payload),
|
|
865
|
-
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
866
|
-
};
|
|
867
|
-
});
|
|
868
|
-
},
|
|
869
|
-
doStream(optionsForRuntime) {
|
|
870
|
-
const options = optionsForRuntime;
|
|
871
|
-
const url = getAnthropicMessagesUrl(config.baseURL);
|
|
872
|
-
const warnings = createWarningCollector();
|
|
873
|
-
const body = buildAnthropicMessagesRequest(modelId, config.name ?? "anthropic", options, true, warnings);
|
|
874
|
-
return requestStream({
|
|
875
|
-
url,
|
|
876
|
-
fetchImpl,
|
|
877
|
-
providerLabel: config.name ?? "anthropic",
|
|
878
|
-
providerKind: "anthropic",
|
|
879
|
-
init: createAnthropicRequestInit({
|
|
880
|
-
apiKey: config.apiKey,
|
|
881
|
-
authToken: config.authToken,
|
|
882
|
-
extraHeaders: options.headers,
|
|
883
|
-
enableFineGrainedToolStreaming: true,
|
|
884
|
-
body: JSON.stringify(body),
|
|
885
|
-
signal: options.abortSignal,
|
|
886
|
-
}),
|
|
887
|
-
}).then((responseStream) => {
|
|
888
|
-
const drained = warnings.drain();
|
|
889
|
-
return {
|
|
890
|
-
stream: ReadableStream.from(withToolInputStatusTransitions(streamAnthropicCompatibleParts(responseStream))),
|
|
891
|
-
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
892
|
-
};
|
|
893
|
-
});
|
|
894
|
-
},
|
|
895
|
-
};
|
|
896
|
-
}
|