@tanstack/ai-anthropic 0.16.3 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/esm/adapters/summarize.js +39 -12
  2. package/dist/esm/adapters/summarize.js.map +1 -1
  3. package/dist/esm/adapters/text.js +841 -940
  4. package/dist/esm/adapters/text.js.map +1 -1
  5. package/dist/esm/index.js +3 -13
  6. package/dist/esm/model-meta.js +399 -74
  7. package/dist/esm/model-meta.js.map +1 -1
  8. package/dist/esm/text/text-provider-options.js +19 -26
  9. package/dist/esm/text/text-provider-options.js.map +1 -1
  10. package/dist/esm/tools/bash-tool.js +11 -12
  11. package/dist/esm/tools/bash-tool.js.map +1 -1
  12. package/dist/esm/tools/code-execution-tool.js +29 -30
  13. package/dist/esm/tools/code-execution-tool.js.map +1 -1
  14. package/dist/esm/tools/computer-use-tool.js +11 -12
  15. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  16. package/dist/esm/tools/custom-tool.js +29 -30
  17. package/dist/esm/tools/custom-tool.js.map +1 -1
  18. package/dist/esm/tools/index.js +1 -14
  19. package/dist/esm/tools/memory-tool.js +14 -15
  20. package/dist/esm/tools/memory-tool.js.map +1 -1
  21. package/dist/esm/tools/text-editor-tool.js +11 -14
  22. package/dist/esm/tools/text-editor-tool.js.map +1 -1
  23. package/dist/esm/tools/tool-converter.js +41 -25
  24. package/dist/esm/tools/tool-converter.js.map +1 -1
  25. package/dist/esm/tools/web-fetch-tool.js +15 -16
  26. package/dist/esm/tools/web-fetch-tool.js.map +1 -1
  27. package/dist/esm/tools/web-search-tool.js +32 -56
  28. package/dist/esm/tools/web-search-tool.js.map +1 -1
  29. package/dist/esm/usage.js +33 -32
  30. package/dist/esm/usage.js.map +1 -1
  31. package/dist/esm/utils/client.js +23 -14
  32. package/dist/esm/utils/client.js.map +1 -1
  33. package/package.json +6 -6
  34. package/dist/esm/index.js.map +0 -1
  35. package/dist/esm/tools/index.js.map +0 -1
@@ -1,954 +1,855 @@
1
- import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
2
- import { toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
3
- import { BaseTextAdapter } from "@tanstack/ai/adapters";
1
+ import { readCodeExecutionConfig, readCodeExecutionSkills } from "../tools/code-execution-tool.js";
4
2
  import { convertToolsToProviderFormat } from "../tools/tool-converter.js";
5
- import { readCodeExecutionSkills, readCodeExecutionConfig } from "../tools/code-execution-tool.js";
6
3
  import { validateTextProviderOptions } from "../text/text-provider-options.js";
7
4
  import { buildAnthropicUsage } from "../usage.js";
8
5
  import { createAnthropicClient, generateId, getAnthropicApiKeyFromEnv } from "../utils/client.js";
9
- import { getAnthropicDefaultMaxTokens, ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS } from "../model-meta.js";
6
+ import { ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS, getAnthropicDefaultMaxTokens } from "../model-meta.js";
7
+ import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
8
+ import { toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
9
+ import { BaseTextAdapter } from "@tanstack/ai/adapters";
10
+ //#region src/adapters/text.ts
11
+ /**
12
+ * Narrow an opaque tool-call `metadata` to {@link AnthropicServerToolMetadata}
13
+ * when it follows the provider-executed convention, else `null`.
14
+ */
10
15
  function readAnthropicServerToolMetadata(metadata) {
11
- if (typeof metadata !== "object" || metadata === null) return null;
12
- const outer = metadata;
13
- if (outer.providerExecuted !== true) return null;
14
- const inner = outer.anthropic;
15
- if (typeof inner !== "object" || inner === null) return null;
16
- const { serverToolType, resultBlockType, result } = inner;
17
- if (typeof serverToolType !== "string" || resultBlockType !== "web_search_tool_result" && resultBlockType !== "web_fetch_tool_result") {
18
- return null;
19
- }
20
- return {
21
- // Validated as a string above; widen back to the SDK's tool-name union.
22
- serverToolType,
23
- resultBlockType,
24
- result
25
- };
16
+ if (typeof metadata !== "object" || metadata === null) return null;
17
+ const outer = metadata;
18
+ if (outer.providerExecuted !== true) return null;
19
+ const inner = outer.anthropic;
20
+ if (typeof inner !== "object" || inner === null) return null;
21
+ const { serverToolType, resultBlockType, result } = inner;
22
+ if (typeof serverToolType !== "string" || resultBlockType !== "web_search_tool_result" && resultBlockType !== "web_fetch_tool_result") return null;
23
+ return {
24
+ serverToolType,
25
+ resultBlockType,
26
+ result
27
+ };
26
28
  }
29
+ /**
30
+ * Reconstruct the `*_tool_result` block param from stored server-tool metadata.
31
+ * The `result` content is opaque round-trip data, asserted to the SDK's param
32
+ * content type at this single boundary.
33
+ */
27
34
  function buildServerToolResultBlock(toolUseId, meta) {
28
- if (meta.resultBlockType === "web_search_tool_result") {
29
- return {
30
- type: "web_search_tool_result",
31
- tool_use_id: toolUseId,
32
- content: meta.result
33
- };
34
- }
35
- return {
36
- type: "web_fetch_tool_result",
37
- tool_use_id: toolUseId,
38
- content: meta.result
39
- };
35
+ if (meta.resultBlockType === "web_search_tool_result") return {
36
+ type: "web_search_tool_result",
37
+ tool_use_id: toolUseId,
38
+ content: meta.result
39
+ };
40
+ return {
41
+ type: "web_fetch_tool_result",
42
+ tool_use_id: toolUseId,
43
+ content: meta.result
44
+ };
40
45
  }
46
+ /**
47
+ * Computes the `betas` array for a Messages request. Unions:
48
+ * - `interleaved-thinking-2025-05-14` when interleaved thinking is enabled,
49
+ * - `code-execution-2025-08-25` when a `code_execution` tool is present,
50
+ * - `skills-2025-10-02` when that tool carries skills.
51
+ * Returns `undefined` when none apply (so the call site omits `betas`).
52
+ */
41
53
  function computeAnthropicBetas(tools, modelOptions) {
42
- const betas = /* @__PURE__ */ new Set();
43
- const useInterleavedThinking = modelOptions?.thinking?.type === "enabled" && typeof modelOptions.thinking.budget_tokens === "number" && modelOptions.thinking.budget_tokens > 0;
44
- if (useInterleavedThinking) betas.add("interleaved-thinking-2025-05-14");
45
- const codeExecTool = tools?.find((t) => t.name === "code_execution");
46
- if (codeExecTool) {
47
- const cfgType = readCodeExecutionConfig(codeExecTool)?.type;
48
- betas.add(
49
- cfgType === "code_execution_20250522" ? "code-execution-2025-05-22" : "code-execution-2025-08-25"
50
- );
51
- }
52
- const hasSkills = tools?.some(
53
- (t) => t.name === "code_execution" && (readCodeExecutionSkills(t)?.length ?? 0) > 0
54
- );
55
- if (hasSkills) betas.add("skills-2025-10-02");
56
- return betas.size > 0 ? Array.from(betas) : void 0;
57
- }
58
- class AnthropicTextAdapter extends BaseTextAdapter {
59
- kind = "text";
60
- name = "anthropic";
61
- client;
62
- constructor(config, model) {
63
- super({}, model);
64
- this.client = createAnthropicClient(config);
65
- }
66
- async *chatStream(options) {
67
- const { logger } = options;
68
- try {
69
- const requestParams = this.mapCommonOptionsToAnthropic(options);
70
- logger.request(
71
- `activity=chat provider=anthropic model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
72
- { provider: "anthropic", model: this.model }
73
- );
74
- const betas = computeAnthropicBetas(options.tools, options.modelOptions);
75
- const stream = await this.client.beta.messages.create(
76
- {
77
- ...requestParams,
78
- stream: true,
79
- ...betas && { betas }
80
- },
81
- {
82
- signal: options.request?.signal,
83
- headers: options.request?.headers
84
- }
85
- );
86
- yield* this.processAnthropicStream(
87
- stream,
88
- options,
89
- () => generateId(this.name),
90
- logger
91
- );
92
- } catch (error) {
93
- const err = error;
94
- const rawEvent = toRunErrorRawEvent(error);
95
- logger.errors("anthropic.chatStream fatal", {
96
- error,
97
- source: "anthropic.chatStream"
98
- });
99
- yield {
100
- type: EventType.RUN_ERROR,
101
- model: options.model,
102
- timestamp: Date.now(),
103
- message: err.message || "Unknown error occurred",
104
- code: err.code || String(err.status),
105
- // Forward the Anthropic SDK error's `.error` response body (e.g.
106
- // `{ type, message }`) when present; never the raw exception object.
107
- ...rawEvent !== void 0 && { rawEvent },
108
- error: {
109
- message: err.message || "Unknown error occurred",
110
- code: err.code || String(err.status)
111
- }
112
- };
113
- }
114
- }
115
- /**
116
- * Generate structured output using Anthropic's tool-based approach.
117
- * Anthropic doesn't have native structured output, so we use a tool with the schema
118
- * and force the model to call it.
119
- * The outputSchema is already JSON Schema (converted in the ai layer).
120
- */
121
- async structuredOutput(options) {
122
- const { chatOptions, outputSchema } = options;
123
- const { logger } = chatOptions;
124
- const requestParams = this.mapCommonOptionsToAnthropic(chatOptions, {
125
- stream: false
126
- });
127
- const structuredOutputTool = {
128
- name: "structured_output",
129
- description: "Use this tool to provide your response in the required structured format.",
130
- input_schema: {
131
- type: "object",
132
- properties: outputSchema.properties ?? {},
133
- required: outputSchema.required ?? []
134
- }
135
- };
136
- try {
137
- logger.request(
138
- `activity=chat provider=anthropic model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,
139
- { provider: "anthropic", model: this.model }
140
- );
141
- const betas = computeAnthropicBetas(
142
- chatOptions.tools,
143
- chatOptions.modelOptions
144
- );
145
- const response = await this.client.beta.messages.create(
146
- {
147
- ...requestParams,
148
- stream: false,
149
- tools: [structuredOutputTool],
150
- tool_choice: { type: "tool", name: "structured_output" },
151
- ...betas && { betas }
152
- },
153
- {
154
- signal: chatOptions.request?.signal,
155
- headers: chatOptions.request?.headers
156
- }
157
- );
158
- let parsed = null;
159
- let rawText = "";
160
- for (const block of response.content) {
161
- if (block.type === "tool_use" && block.name === "structured_output") {
162
- parsed = block.input;
163
- rawText = JSON.stringify(block.input);
164
- break;
165
- }
166
- }
167
- if (parsed === null) {
168
- rawText = response.content.map((b) => {
169
- if (b.type === "text") {
170
- return b.text;
171
- }
172
- return "";
173
- }).join("");
174
- try {
175
- parsed = JSON.parse(rawText);
176
- } catch {
177
- throw new Error(
178
- `Failed to extract structured output from response. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`
179
- );
180
- }
181
- }
182
- return {
183
- data: parsed,
184
- rawText,
185
- usage: buildAnthropicUsage(response.usage)
186
- };
187
- } catch (error) {
188
- const err = error;
189
- logger.errors("anthropic.structuredOutput fatal", {
190
- error,
191
- source: "anthropic.structuredOutput"
192
- });
193
- throw new Error(
194
- `Structured output generation failed: ${err.message || "Unknown error occurred"}`
195
- );
196
- }
197
- }
198
- mapCommonOptionsToAnthropic(options, { stream = true } = {}) {
199
- const modelOptions = options.modelOptions;
200
- const formattedMessages = this.formatMessages(options.messages);
201
- const tools = options.tools ? convertToolsToProviderFormat(options.tools) : void 0;
202
- const validProviderOptions = {};
203
- if (modelOptions) {
204
- const validKeys = [
205
- "container",
206
- "context_management",
207
- "effort",
208
- "mcp_servers",
209
- "output_config",
210
- "service_tier",
211
- "stop_sequences",
212
- "thinking",
213
- "tool_choice",
214
- "top_k",
215
- "temperature",
216
- "top_p"
217
- ];
218
- const droppedKeyExemptSet = /* @__PURE__ */ new Set([...validKeys, "max_tokens"]);
219
- const droppedKeys = Object.keys(modelOptions).filter(
220
- (key) => !droppedKeyExemptSet.has(key)
221
- );
222
- if (droppedKeys.length > 0) {
223
- options.logger.errors(
224
- `anthropic.mapCommonOptionsToAnthropic dropped unknown modelOptions key(s): ${droppedKeys.join(", ")}`,
225
- {
226
- source: "anthropic.mapCommonOptionsToAnthropic",
227
- droppedKeys,
228
- hint: droppedKeys.includes("system") ? "pass system prompts via the top-level `systemPrompts` option; `modelOptions.system` is no longer honored" : void 0
229
- }
230
- );
231
- }
232
- for (const key of validKeys) {
233
- if (key in modelOptions) {
234
- const value = modelOptions[key];
235
- if (key === "tool_choice" && typeof value === "string") {
236
- validProviderOptions[key] = {
237
- type: value
238
- };
239
- } else {
240
- validProviderOptions[key] = value;
241
- }
242
- }
243
- }
244
- }
245
- const thinkingBudget = validProviderOptions.thinking?.type === "enabled" ? validProviderOptions.thinking.budget_tokens : void 0;
246
- const defaultMaxTokens = modelOptions?.max_tokens ?? getAnthropicDefaultMaxTokens(this.model, { stream });
247
- const maxTokens = thinkingBudget && thinkingBudget >= defaultMaxTokens ? thinkingBudget + 1 : defaultMaxTokens;
248
- const systemBlocks = (() => {
249
- const normalized = normalizeSystemPrompts(
250
- options.systemPrompts
251
- );
252
- if (normalized.length === 0) return void 0;
253
- return normalized.map(
254
- (p) => ({
255
- type: "text",
256
- text: p.content,
257
- ...p.metadata?.cache_control && {
258
- cache_control: p.metadata.cache_control
259
- }
260
- })
261
- );
262
- })();
263
- const combinedSchema = options.outputSchema;
264
- const outputConfig = combinedSchema ? {
265
- output_config: {
266
- ...validProviderOptions.output_config ?? {},
267
- format: {
268
- type: "json_schema",
269
- schema: combinedSchema
270
- }
271
- }
272
- } : void 0;
273
- const toolSkills = options.tools?.map(
274
- (tool) => tool.name === "code_execution" ? readCodeExecutionSkills(tool) : void 0
275
- ).find((skills) => skills && skills.length > 0);
276
- if (toolSkills && toolSkills.length > 0) {
277
- const existingContainer = validProviderOptions.container ?? void 0;
278
- validProviderOptions.container = {
279
- id: existingContainer?.id ?? null,
280
- skills: toolSkills
281
- };
282
- }
283
- const requestParams = {
284
- model: options.model,
285
- max_tokens: maxTokens,
286
- messages: formattedMessages,
287
- ...systemBlocks !== void 0 && { system: systemBlocks },
288
- ...tools !== void 0 && { tools },
289
- ...validProviderOptions,
290
- ...outputConfig ?? {}
291
- };
292
- validateTextProviderOptions(requestParams);
293
- return requestParams;
294
- }
295
- /**
296
- * Anthropic supports `output_config.format` + `tools` in a single streaming
297
- * Messages request only for Claude 4.5+ (GA 2026-01-29). For 4.4 and
298
- * earlier we keep the forced-tool-use workaround in
299
- * {@link structuredOutput} via the engine's finalization path.
300
- */
301
- supportsCombinedToolsAndSchema() {
302
- return ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS.has(this.model);
303
- }
304
- convertContentPartToAnthropic(part) {
305
- switch (part.type) {
306
- case "text": {
307
- const metadata = part.metadata;
308
- return {
309
- type: "text",
310
- text: part.content,
311
- ...metadata
312
- };
313
- }
314
- case "image": {
315
- const metadata = part.metadata;
316
- const imageSource = part.source.type === "data" ? {
317
- type: "base64",
318
- data: part.source.value,
319
- media_type: part.source.mimeType
320
- } : {
321
- type: "url",
322
- url: part.source.value
323
- };
324
- return {
325
- type: "image",
326
- source: imageSource,
327
- ...metadata
328
- };
329
- }
330
- case "document": {
331
- const metadata = part.metadata;
332
- const docSource = part.source.type === "data" ? {
333
- type: "base64",
334
- data: part.source.value,
335
- media_type: part.source.mimeType
336
- } : {
337
- type: "url",
338
- url: part.source.value
339
- };
340
- return {
341
- type: "document",
342
- source: docSource,
343
- ...metadata
344
- };
345
- }
346
- case "audio":
347
- case "video":
348
- throw new Error(
349
- `Anthropic does not support ${part.type} content directly`
350
- );
351
- default: {
352
- const _exhaustiveCheck = part;
353
- throw new Error(
354
- `Unsupported content part type: ${_exhaustiveCheck.type}`
355
- );
356
- }
357
- }
358
- }
359
- formatMessages(messages) {
360
- const formattedMessages = [];
361
- for (const message of messages) {
362
- const role = message.role;
363
- if (role === "tool" && message.toolCallId) {
364
- const toolContent = message.content;
365
- formattedMessages.push({
366
- role: "user",
367
- content: [
368
- {
369
- type: "tool_result",
370
- tool_use_id: message.toolCallId,
371
- content: Array.isArray(toolContent) ? toolContent.map(
372
- (part) => this.convertContentPartToAnthropic(part)
373
- ) : typeof toolContent === "string" ? toolContent : ""
374
- }
375
- ]
376
- });
377
- continue;
378
- }
379
- if (role === "assistant" && message.toolCalls?.length) {
380
- const contentBlocks = [];
381
- this.appendThinkingBlocks(contentBlocks, message.thinking);
382
- if (message.content) {
383
- const content = typeof message.content === "string" ? message.content : "";
384
- const textBlock = {
385
- type: "text",
386
- text: content
387
- };
388
- contentBlocks.push(textBlock);
389
- }
390
- for (const toolCall of message.toolCalls) {
391
- let parsedInput = {};
392
- try {
393
- const parsed = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
394
- parsedInput = parsed && typeof parsed === "object" ? parsed : {};
395
- } catch {
396
- parsedInput = toolCall.function.arguments;
397
- }
398
- const serverMeta = readAnthropicServerToolMetadata(toolCall.metadata);
399
- if (serverMeta) {
400
- const serverToolUseBlock = {
401
- type: "server_tool_use",
402
- id: toolCall.id,
403
- name: serverMeta.serverToolType,
404
- input: parsedInput
405
- };
406
- contentBlocks.push(serverToolUseBlock);
407
- contentBlocks.push(
408
- buildServerToolResultBlock(toolCall.id, serverMeta)
409
- );
410
- continue;
411
- }
412
- const toolUseBlock = {
413
- type: "tool_use",
414
- id: toolCall.id,
415
- name: toolCall.function.name,
416
- input: parsedInput
417
- };
418
- contentBlocks.push(toolUseBlock);
419
- }
420
- formattedMessages.push({
421
- role: "assistant",
422
- content: contentBlocks
423
- });
424
- continue;
425
- }
426
- if (role === "assistant") {
427
- const contentBlocks = [];
428
- this.appendThinkingBlocks(contentBlocks, message.thinking);
429
- if (Array.isArray(message.content)) {
430
- for (const part of message.content) {
431
- contentBlocks.push(this.convertContentPartToAnthropic(part));
432
- }
433
- } else if (message.content) {
434
- contentBlocks.push({
435
- type: "text",
436
- text: message.content
437
- });
438
- }
439
- formattedMessages.push({
440
- role: "assistant",
441
- content: contentBlocks.length > 0 ? contentBlocks : ""
442
- });
443
- continue;
444
- }
445
- if (role === "user" && Array.isArray(message.content)) {
446
- const contentBlocks = message.content.map(
447
- (part) => this.convertContentPartToAnthropic(part)
448
- );
449
- formattedMessages.push({
450
- role: "user",
451
- content: contentBlocks
452
- });
453
- continue;
454
- }
455
- formattedMessages.push({
456
- role: "user",
457
- content: typeof message.content === "string" ? message.content : message.content ? message.content.map(
458
- (c) => this.convertContentPartToAnthropic(c)
459
- ) : ""
460
- });
461
- }
462
- return this.mergeConsecutiveSameRoleMessages(formattedMessages);
463
- }
464
- appendThinkingBlocks(contentBlocks, thinkingParts) {
465
- if (!thinkingParts?.length) return;
466
- for (const thinking of thinkingParts) {
467
- if (!thinking.signature) continue;
468
- const block = {
469
- type: "thinking",
470
- thinking: thinking.content,
471
- signature: thinking.signature
472
- };
473
- contentBlocks.push(block);
474
- }
475
- }
476
- /**
477
- * Merge consecutive messages of the same role into a single message.
478
- * Anthropic's API requires strictly alternating user/assistant roles.
479
- * Tool results are wrapped as role:'user' messages, which can collide
480
- * with actual user messages in multi-turn conversations.
481
- *
482
- * Also filters out empty assistant messages (e.g., from a previous failed request).
483
- */
484
- mergeConsecutiveSameRoleMessages(messages) {
485
- const merged = [];
486
- for (const msg of messages) {
487
- if (msg.role === "assistant") {
488
- const hasContent = Array.isArray(msg.content) ? msg.content.length > 0 : typeof msg.content === "string" && msg.content.length > 0;
489
- if (!hasContent) {
490
- continue;
491
- }
492
- }
493
- const prev = merged[merged.length - 1];
494
- if (prev && prev.role === msg.role) {
495
- const prevBlocks = Array.isArray(prev.content) ? prev.content : typeof prev.content === "string" && prev.content ? [{ type: "text", text: prev.content }] : [];
496
- const msgBlocks = Array.isArray(msg.content) ? msg.content : typeof msg.content === "string" && msg.content ? [{ type: "text", text: msg.content }] : [];
497
- prev.content = [...prevBlocks, ...msgBlocks];
498
- } else {
499
- merged.push({ ...msg });
500
- }
501
- }
502
- for (const msg of merged) {
503
- if (Array.isArray(msg.content)) {
504
- const seenToolResultIds = /* @__PURE__ */ new Set();
505
- msg.content = msg.content.filter((block) => {
506
- if (block.type === "tool_result" && block.tool_use_id) {
507
- if (seenToolResultIds.has(block.tool_use_id)) {
508
- return false;
509
- }
510
- seenToolResultIds.add(block.tool_use_id);
511
- }
512
- return true;
513
- });
514
- }
515
- }
516
- return merged;
517
- }
518
- async *processAnthropicStream(stream, options, genId, logger) {
519
- const model = options.model;
520
- let accumulatedContent = "";
521
- let accumulatedThinking = "";
522
- let accumulatedSignature = "";
523
- const toolCallsMap = /* @__PURE__ */ new Map();
524
- let currentToolIndex = -1;
525
- let currentServerTool = null;
526
- const completedServerTools = /* @__PURE__ */ new Map();
527
- const runId = options.runId ?? genId();
528
- const threadId = options.threadId ?? genId();
529
- const messageId = genId();
530
- let stepId = null;
531
- let reasoningMessageId = null;
532
- let hasClosedReasoning = false;
533
- let hasEmittedRunStarted = false;
534
- let hasEmittedTextMessageStart = false;
535
- let hasEmittedRunFinished = false;
536
- let currentBlockType = null;
537
- try {
538
- for await (const event of stream) {
539
- logger.provider(`provider=anthropic type=${event.type}`, {
540
- chunk: event
541
- });
542
- if (!hasEmittedRunStarted) {
543
- hasEmittedRunStarted = true;
544
- yield {
545
- type: EventType.RUN_STARTED,
546
- runId,
547
- threadId,
548
- model,
549
- timestamp: Date.now(),
550
- parentRunId: options.parentRunId
551
- };
552
- }
553
- if (event.type === "content_block_start") {
554
- currentBlockType = event.content_block.type;
555
- if (event.content_block.type === "tool_use") {
556
- currentToolIndex++;
557
- toolCallsMap.set(currentToolIndex, {
558
- id: event.content_block.id,
559
- name: event.content_block.name,
560
- input: "",
561
- started: false
562
- });
563
- } else if (event.content_block.type === "server_tool_use") {
564
- currentServerTool = {
565
- id: event.content_block.id,
566
- name: event.content_block.name,
567
- input: ""
568
- };
569
- } else if (event.content_block.type === "web_fetch_tool_result" || event.content_block.type === "web_search_tool_result") {
570
- const content = event.content_block.content;
571
- const errorBlock = !Array.isArray(content) && (content.type === "web_fetch_tool_result_error" || content.type === "web_search_tool_result_error") ? content : null;
572
- if (errorBlock) {
573
- logger.errors(
574
- `anthropic.${event.content_block.type} error_code=${errorBlock.error_code}`,
575
- {
576
- toolUseId: event.content_block.tool_use_id,
577
- blockType: event.content_block.type,
578
- errorCode: errorBlock.error_code,
579
- source: "anthropic.processAnthropicStream"
580
- }
581
- );
582
- }
583
- const serverTool = completedServerTools.get(
584
- event.content_block.tool_use_id
585
- );
586
- if (serverTool) {
587
- completedServerTools.delete(serverTool.id);
588
- let parsedInput = {};
589
- try {
590
- const parsed = serverTool.input ? JSON.parse(serverTool.input) : {};
591
- parsedInput = parsed && typeof parsed === "object" ? parsed : {};
592
- } catch {
593
- parsedInput = {};
594
- }
595
- const serverToolMetadata = {
596
- providerExecuted: true,
597
- anthropic: {
598
- serverToolType: serverTool.name,
599
- resultBlockType: event.content_block.type,
600
- result: content
601
- }
602
- };
603
- currentToolIndex++;
604
- yield {
605
- type: EventType.TOOL_CALL_START,
606
- toolCallId: serverTool.id,
607
- toolCallName: serverTool.name,
608
- toolName: serverTool.name,
609
- parentMessageId: messageId,
610
- model,
611
- timestamp: Date.now(),
612
- index: currentToolIndex,
613
- metadata: serverToolMetadata
614
- };
615
- yield {
616
- type: EventType.TOOL_CALL_END,
617
- toolCallId: serverTool.id,
618
- toolCallName: serverTool.name,
619
- toolName: serverTool.name,
620
- model,
621
- timestamp: Date.now(),
622
- input: parsedInput
623
- };
624
- hasEmittedTextMessageStart = false;
625
- }
626
- } else if (event.content_block.type === "thinking") {
627
- accumulatedThinking = "";
628
- accumulatedSignature = "";
629
- stepId = genId();
630
- reasoningMessageId = genId();
631
- yield {
632
- type: EventType.REASONING_START,
633
- messageId: reasoningMessageId,
634
- model,
635
- timestamp: Date.now()
636
- };
637
- yield {
638
- type: EventType.REASONING_MESSAGE_START,
639
- messageId: reasoningMessageId,
640
- role: "reasoning",
641
- model,
642
- timestamp: Date.now()
643
- };
644
- yield {
645
- type: EventType.STEP_STARTED,
646
- stepName: stepId,
647
- stepId,
648
- model,
649
- timestamp: Date.now(),
650
- stepType: "thinking"
651
- };
652
- }
653
- } else if (event.type === "content_block_delta") {
654
- if (event.delta.type === "text_delta") {
655
- if (reasoningMessageId && !hasClosedReasoning) {
656
- hasClosedReasoning = true;
657
- yield {
658
- type: EventType.REASONING_MESSAGE_END,
659
- messageId: reasoningMessageId,
660
- model,
661
- timestamp: Date.now()
662
- };
663
- yield {
664
- type: EventType.REASONING_END,
665
- messageId: reasoningMessageId,
666
- model,
667
- timestamp: Date.now()
668
- };
669
- }
670
- if (!hasEmittedTextMessageStart) {
671
- hasEmittedTextMessageStart = true;
672
- yield {
673
- type: EventType.TEXT_MESSAGE_START,
674
- messageId,
675
- model,
676
- timestamp: Date.now(),
677
- role: "assistant"
678
- };
679
- }
680
- const delta = event.delta.text;
681
- accumulatedContent += delta;
682
- yield {
683
- type: EventType.TEXT_MESSAGE_CONTENT,
684
- messageId,
685
- model,
686
- timestamp: Date.now(),
687
- delta,
688
- content: accumulatedContent
689
- };
690
- } else if (event.delta.type === "thinking_delta" && reasoningMessageId) {
691
- const delta = event.delta.thinking;
692
- accumulatedThinking += delta;
693
- yield {
694
- type: EventType.REASONING_MESSAGE_CONTENT,
695
- messageId: reasoningMessageId,
696
- delta,
697
- model,
698
- timestamp: Date.now()
699
- };
700
- yield {
701
- type: EventType.STEP_FINISHED,
702
- stepName: stepId || genId(),
703
- stepId: stepId || genId(),
704
- model,
705
- timestamp: Date.now(),
706
- delta,
707
- content: accumulatedThinking
708
- };
709
- } else if (event.delta.type === "signature_delta") {
710
- accumulatedSignature += event.delta.signature || "";
711
- } else if (event.delta.type === "input_json_delta") {
712
- if (currentBlockType === "tool_use") {
713
- const existing = toolCallsMap.get(currentToolIndex);
714
- if (existing) {
715
- if (!existing.started) {
716
- existing.started = true;
717
- yield {
718
- type: EventType.TOOL_CALL_START,
719
- toolCallId: existing.id,
720
- toolCallName: existing.name,
721
- toolName: existing.name,
722
- parentMessageId: messageId,
723
- model,
724
- timestamp: Date.now(),
725
- index: currentToolIndex
726
- };
727
- }
728
- existing.input += event.delta.partial_json;
729
- yield {
730
- type: EventType.TOOL_CALL_ARGS,
731
- toolCallId: existing.id,
732
- model,
733
- timestamp: Date.now(),
734
- delta: event.delta.partial_json,
735
- args: existing.input
736
- };
737
- }
738
- } else if (currentBlockType === "server_tool_use" && currentServerTool) {
739
- currentServerTool.input += event.delta.partial_json;
740
- }
741
- }
742
- } else if (event.type === "content_block_stop") {
743
- if (currentBlockType === "thinking") {
744
- if (accumulatedSignature && stepId) {
745
- yield {
746
- type: EventType.STEP_FINISHED,
747
- stepName: stepId,
748
- stepId,
749
- model,
750
- timestamp: Date.now(),
751
- delta: "",
752
- content: accumulatedThinking,
753
- signature: accumulatedSignature
754
- };
755
- }
756
- } else if (currentBlockType === "tool_use") {
757
- const existing = toolCallsMap.get(currentToolIndex);
758
- if (existing) {
759
- if (!existing.started) {
760
- existing.started = true;
761
- yield {
762
- type: EventType.TOOL_CALL_START,
763
- toolCallId: existing.id,
764
- toolCallName: existing.name,
765
- toolName: existing.name,
766
- parentMessageId: messageId,
767
- model,
768
- timestamp: Date.now(),
769
- index: currentToolIndex
770
- };
771
- }
772
- let parsedInput = {};
773
- try {
774
- const parsed = existing.input ? JSON.parse(existing.input) : {};
775
- parsedInput = parsed && typeof parsed === "object" ? parsed : {};
776
- } catch {
777
- parsedInput = {};
778
- }
779
- yield {
780
- type: EventType.TOOL_CALL_END,
781
- toolCallId: existing.id,
782
- toolCallName: existing.name,
783
- toolName: existing.name,
784
- model,
785
- timestamp: Date.now(),
786
- input: parsedInput
787
- };
788
- hasEmittedTextMessageStart = false;
789
- }
790
- } else if (currentBlockType === "server_tool_use") {
791
- if (currentServerTool) {
792
- logger.provider(
793
- `provider=anthropic server_tool_use name=${currentServerTool.name}`,
794
- {
795
- toolUseId: currentServerTool.id,
796
- name: currentServerTool.name,
797
- input: currentServerTool.input
798
- }
799
- );
800
- completedServerTools.set(currentServerTool.id, currentServerTool);
801
- }
802
- currentServerTool = null;
803
- } else if (currentBlockType === "web_fetch_tool_result" || currentBlockType === "web_search_tool_result") {
804
- } else {
805
- if (hasEmittedTextMessageStart && accumulatedContent) {
806
- yield {
807
- type: EventType.TEXT_MESSAGE_END,
808
- messageId,
809
- model,
810
- timestamp: Date.now()
811
- };
812
- }
813
- }
814
- currentBlockType = null;
815
- } else if (event.type === "message_stop") {
816
- if (reasoningMessageId && !hasClosedReasoning) {
817
- hasClosedReasoning = true;
818
- yield {
819
- type: EventType.REASONING_MESSAGE_END,
820
- messageId: reasoningMessageId,
821
- model,
822
- timestamp: Date.now()
823
- };
824
- yield {
825
- type: EventType.REASONING_END,
826
- messageId: reasoningMessageId,
827
- model,
828
- timestamp: Date.now()
829
- };
830
- }
831
- if (!hasEmittedRunFinished) {
832
- yield {
833
- type: EventType.RUN_FINISHED,
834
- runId,
835
- threadId,
836
- model,
837
- timestamp: Date.now(),
838
- finishReason: "stop"
839
- };
840
- }
841
- } else if (event.type === "message_delta") {
842
- if (event.delta.stop_reason) {
843
- hasEmittedRunFinished = true;
844
- if (reasoningMessageId && !hasClosedReasoning) {
845
- hasClosedReasoning = true;
846
- yield {
847
- type: EventType.REASONING_MESSAGE_END,
848
- messageId: reasoningMessageId,
849
- model,
850
- timestamp: Date.now()
851
- };
852
- yield {
853
- type: EventType.REASONING_END,
854
- messageId: reasoningMessageId,
855
- model,
856
- timestamp: Date.now()
857
- };
858
- }
859
- switch (event.delta.stop_reason) {
860
- case "tool_use": {
861
- yield {
862
- type: EventType.RUN_FINISHED,
863
- runId,
864
- threadId,
865
- model,
866
- timestamp: Date.now(),
867
- finishReason: "tool_calls",
868
- usage: buildAnthropicUsage(event.usage)
869
- };
870
- break;
871
- }
872
- case "max_tokens": {
873
- if (options.modelOptions?.max_tokens == null) {
874
- const defaultedMaxTokens = getAnthropicDefaultMaxTokens(model);
875
- logger.warn(
876
- `anthropic response truncated at the default max_tokens (${defaultedMaxTokens}) for model=${model}; pass maxTokens (or modelOptions.max_tokens) to raise the output ceiling`,
877
- {
878
- source: "anthropic.processAnthropicStream",
879
- model,
880
- defaultedMaxTokens
881
- }
882
- );
883
- }
884
- yield {
885
- type: EventType.RUN_ERROR,
886
- model,
887
- timestamp: Date.now(),
888
- message: "The response was cut off because the maximum token limit was reached.",
889
- code: "max_tokens",
890
- error: {
891
- message: "The response was cut off because the maximum token limit was reached.",
892
- code: "max_tokens"
893
- }
894
- };
895
- break;
896
- }
897
- case "stop_sequence":
898
- case "end_turn":
899
- case "pause_turn":
900
- case "refusal":
901
- case "model_context_window_exceeded":
902
- case "compaction":
903
- default: {
904
- yield {
905
- type: EventType.RUN_FINISHED,
906
- runId,
907
- threadId,
908
- model,
909
- timestamp: Date.now(),
910
- finishReason: "stop",
911
- usage: buildAnthropicUsage(event.usage)
912
- };
913
- }
914
- }
915
- }
916
- }
917
- }
918
- } catch (error) {
919
- const err = error;
920
- const rawEvent = toRunErrorRawEvent(error);
921
- logger.errors("anthropic.processAnthropicStream fatal", {
922
- error,
923
- source: "anthropic.processAnthropicStream"
924
- });
925
- yield {
926
- type: EventType.RUN_ERROR,
927
- model,
928
- timestamp: Date.now(),
929
- message: err.message || "Unknown error occurred",
930
- code: err.code || String(err.status),
931
- // Forward the Anthropic SDK error's `.error` response body when present.
932
- ...rawEvent !== void 0 && { rawEvent },
933
- error: {
934
- message: err.message || "Unknown error occurred",
935
- code: err.code || String(err.status)
936
- }
937
- };
938
- }
939
- }
54
+ const betas = /* @__PURE__ */ new Set();
55
+ if (modelOptions?.thinking?.type === "enabled" && typeof modelOptions.thinking.budget_tokens === "number" && modelOptions.thinking.budget_tokens > 0) betas.add("interleaved-thinking-2025-05-14");
56
+ const codeExecTool = tools?.find((t) => t.name === "code_execution");
57
+ if (codeExecTool) {
58
+ const cfgType = readCodeExecutionConfig(codeExecTool)?.type;
59
+ betas.add(cfgType === "code_execution_20250522" ? "code-execution-2025-05-22" : "code-execution-2025-08-25");
60
+ }
61
+ if (tools?.some((t) => t.name === "code_execution" && (readCodeExecutionSkills(t)?.length ?? 0) > 0)) betas.add("skills-2025-10-02");
62
+ return betas.size > 0 ? Array.from(betas) : void 0;
940
63
  }
64
+ /**
65
+ * Anthropic Text (Chat) Adapter
66
+ *
67
+ * Tree-shakeable adapter for Anthropic chat/text completion functionality.
68
+ * Import only what you need for smaller bundle sizes.
69
+ */
70
+ var AnthropicTextAdapter = class extends BaseTextAdapter {
71
+ kind = "text";
72
+ name = "anthropic";
73
+ client;
74
+ constructor(config, model) {
75
+ super({}, model);
76
+ this.client = createAnthropicClient(config);
77
+ }
78
+ async *chatStream(options) {
79
+ const { logger } = options;
80
+ try {
81
+ const requestParams = this.mapCommonOptionsToAnthropic(options);
82
+ logger.request(`activity=chat provider=anthropic model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`, {
83
+ provider: "anthropic",
84
+ model: this.model
85
+ });
86
+ const betas = computeAnthropicBetas(options.tools, options.modelOptions);
87
+ const stream = await this.client.beta.messages.create({
88
+ ...requestParams,
89
+ stream: true,
90
+ ...betas && { betas }
91
+ }, {
92
+ signal: options.request?.signal,
93
+ headers: options.request?.headers
94
+ });
95
+ yield* this.processAnthropicStream(stream, options, () => generateId(this.name), logger);
96
+ } catch (error) {
97
+ const err = error;
98
+ const rawEvent = toRunErrorRawEvent(error);
99
+ logger.errors("anthropic.chatStream fatal", {
100
+ error,
101
+ source: "anthropic.chatStream"
102
+ });
103
+ yield {
104
+ type: EventType.RUN_ERROR,
105
+ model: options.model,
106
+ timestamp: Date.now(),
107
+ message: err.message || "Unknown error occurred",
108
+ code: err.code || String(err.status),
109
+ ...rawEvent !== void 0 && { rawEvent },
110
+ error: {
111
+ message: err.message || "Unknown error occurred",
112
+ code: err.code || String(err.status)
113
+ }
114
+ };
115
+ }
116
+ }
117
+ /**
118
+ * Generate structured output using Anthropic's tool-based approach.
119
+ * Anthropic doesn't have native structured output, so we use a tool with the schema
120
+ * and force the model to call it.
121
+ * The outputSchema is already JSON Schema (converted in the ai layer).
122
+ */
123
+ async structuredOutput(options) {
124
+ const { chatOptions, outputSchema } = options;
125
+ const { logger } = chatOptions;
126
+ const requestParams = this.mapCommonOptionsToAnthropic(chatOptions, { stream: false });
127
+ const structuredOutputTool = {
128
+ name: "structured_output",
129
+ description: "Use this tool to provide your response in the required structured format.",
130
+ input_schema: {
131
+ type: "object",
132
+ properties: outputSchema.properties ?? {},
133
+ required: outputSchema.required ?? []
134
+ }
135
+ };
136
+ try {
137
+ logger.request(`activity=chat provider=anthropic model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`, {
138
+ provider: "anthropic",
139
+ model: this.model
140
+ });
141
+ const betas = computeAnthropicBetas(chatOptions.tools, chatOptions.modelOptions);
142
+ const response = await this.client.beta.messages.create({
143
+ ...requestParams,
144
+ stream: false,
145
+ tools: [structuredOutputTool],
146
+ tool_choice: {
147
+ type: "tool",
148
+ name: "structured_output"
149
+ },
150
+ ...betas && { betas }
151
+ }, {
152
+ signal: chatOptions.request?.signal,
153
+ headers: chatOptions.request?.headers
154
+ });
155
+ let parsed = null;
156
+ let rawText = "";
157
+ for (const block of response.content) if (block.type === "tool_use" && block.name === "structured_output") {
158
+ parsed = block.input;
159
+ rawText = JSON.stringify(block.input);
160
+ break;
161
+ }
162
+ if (parsed === null) {
163
+ rawText = response.content.map((b) => {
164
+ if (b.type === "text") return b.text;
165
+ return "";
166
+ }).join("");
167
+ try {
168
+ parsed = JSON.parse(rawText);
169
+ } catch {
170
+ throw new Error(`Failed to extract structured output from response. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`);
171
+ }
172
+ }
173
+ return {
174
+ data: parsed,
175
+ rawText,
176
+ usage: buildAnthropicUsage(response.usage)
177
+ };
178
+ } catch (error) {
179
+ const err = error;
180
+ logger.errors("anthropic.structuredOutput fatal", {
181
+ error,
182
+ source: "anthropic.structuredOutput"
183
+ });
184
+ throw new Error(`Structured output generation failed: ${err.message || "Unknown error occurred"}`);
185
+ }
186
+ }
187
+ mapCommonOptionsToAnthropic(options, { stream = true } = {}) {
188
+ const modelOptions = options.modelOptions;
189
+ const formattedMessages = this.formatMessages(options.messages);
190
+ const tools = options.tools ? convertToolsToProviderFormat(options.tools) : void 0;
191
+ const validProviderOptions = {};
192
+ if (modelOptions) {
193
+ const validKeys = [
194
+ "container",
195
+ "context_management",
196
+ "effort",
197
+ "mcp_servers",
198
+ "output_config",
199
+ "service_tier",
200
+ "stop_sequences",
201
+ "thinking",
202
+ "tool_choice",
203
+ "top_k",
204
+ "temperature",
205
+ "top_p"
206
+ ];
207
+ const droppedKeyExemptSet = /* @__PURE__ */ new Set([...validKeys, "max_tokens"]);
208
+ const droppedKeys = Object.keys(modelOptions).filter((key) => !droppedKeyExemptSet.has(key));
209
+ if (droppedKeys.length > 0) options.logger.errors(`anthropic.mapCommonOptionsToAnthropic dropped unknown modelOptions key(s): ${droppedKeys.join(", ")}`, {
210
+ source: "anthropic.mapCommonOptionsToAnthropic",
211
+ droppedKeys,
212
+ hint: droppedKeys.includes("system") ? "pass system prompts via the top-level `systemPrompts` option; `modelOptions.system` is no longer honored" : void 0
213
+ });
214
+ for (const key of validKeys) if (key in modelOptions) {
215
+ const value = modelOptions[key];
216
+ if (key === "tool_choice" && typeof value === "string") validProviderOptions[key] = { type: value };
217
+ else validProviderOptions[key] = value;
218
+ }
219
+ }
220
+ const thinkingBudget = validProviderOptions.thinking?.type === "enabled" ? validProviderOptions.thinking.budget_tokens : void 0;
221
+ const defaultMaxTokens = modelOptions?.max_tokens ?? getAnthropicDefaultMaxTokens(this.model, { stream });
222
+ const maxTokens = thinkingBudget && thinkingBudget >= defaultMaxTokens ? thinkingBudget + 1 : defaultMaxTokens;
223
+ const systemBlocks = (() => {
224
+ const normalized = normalizeSystemPrompts(options.systemPrompts);
225
+ if (normalized.length === 0) return void 0;
226
+ return normalized.map((p) => ({
227
+ type: "text",
228
+ text: p.content,
229
+ ...p.metadata?.cache_control && { cache_control: p.metadata.cache_control }
230
+ }));
231
+ })();
232
+ const combinedSchema = options.outputSchema;
233
+ const outputConfig = combinedSchema ? { output_config: {
234
+ ...validProviderOptions.output_config ?? {},
235
+ format: {
236
+ type: "json_schema",
237
+ schema: combinedSchema
238
+ }
239
+ } } : void 0;
240
+ const toolSkills = options.tools?.map((tool) => tool.name === "code_execution" ? readCodeExecutionSkills(tool) : void 0).find((skills) => skills && skills.length > 0);
241
+ if (toolSkills && toolSkills.length > 0) validProviderOptions.container = {
242
+ id: (validProviderOptions.container ?? void 0)?.id ?? null,
243
+ skills: toolSkills
244
+ };
245
+ const requestParams = {
246
+ model: options.model,
247
+ max_tokens: maxTokens,
248
+ messages: formattedMessages,
249
+ ...systemBlocks !== void 0 && { system: systemBlocks },
250
+ ...tools !== void 0 && { tools },
251
+ ...validProviderOptions,
252
+ ...outputConfig ?? {}
253
+ };
254
+ validateTextProviderOptions(requestParams);
255
+ return requestParams;
256
+ }
257
+ /**
258
+ * Anthropic supports `output_config.format` + `tools` in a single streaming
259
+ * Messages request only for Claude 4.5+ (GA 2026-01-29). For 4.4 and
260
+ * earlier we keep the forced-tool-use workaround in
261
+ * {@link structuredOutput} via the engine's finalization path.
262
+ */
263
+ supportsCombinedToolsAndSchema() {
264
+ return ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS.has(this.model);
265
+ }
266
+ convertContentPartToAnthropic(part) {
267
+ switch (part.type) {
268
+ case "text": {
269
+ const metadata = part.metadata;
270
+ return {
271
+ type: "text",
272
+ text: part.content,
273
+ ...metadata
274
+ };
275
+ }
276
+ case "image": {
277
+ const metadata = part.metadata;
278
+ return {
279
+ type: "image",
280
+ source: part.source.type === "data" ? {
281
+ type: "base64",
282
+ data: part.source.value,
283
+ media_type: part.source.mimeType
284
+ } : {
285
+ type: "url",
286
+ url: part.source.value
287
+ },
288
+ ...metadata
289
+ };
290
+ }
291
+ case "document": {
292
+ const metadata = part.metadata;
293
+ return {
294
+ type: "document",
295
+ source: part.source.type === "data" ? {
296
+ type: "base64",
297
+ data: part.source.value,
298
+ media_type: part.source.mimeType
299
+ } : {
300
+ type: "url",
301
+ url: part.source.value
302
+ },
303
+ ...metadata
304
+ };
305
+ }
306
+ case "audio":
307
+ case "video": throw new Error(`Anthropic does not support ${part.type} content directly`);
308
+ default: throw new Error(`Unsupported content part type: ${part.type}`);
309
+ }
310
+ }
311
+ formatMessages(messages) {
312
+ const formattedMessages = [];
313
+ for (const message of messages) {
314
+ const role = message.role;
315
+ if (role === "tool" && message.toolCallId) {
316
+ const toolContent = message.content;
317
+ formattedMessages.push({
318
+ role: "user",
319
+ content: [{
320
+ type: "tool_result",
321
+ tool_use_id: message.toolCallId,
322
+ content: Array.isArray(toolContent) ? toolContent.map((part) => this.convertContentPartToAnthropic(part)) : typeof toolContent === "string" ? toolContent : ""
323
+ }]
324
+ });
325
+ continue;
326
+ }
327
+ if (role === "assistant" && message.toolCalls?.length) {
328
+ const contentBlocks = [];
329
+ this.appendThinkingBlocks(contentBlocks, message.thinking);
330
+ if (message.content) {
331
+ const textBlock = {
332
+ type: "text",
333
+ text: typeof message.content === "string" ? message.content : ""
334
+ };
335
+ contentBlocks.push(textBlock);
336
+ }
337
+ for (const toolCall of message.toolCalls) {
338
+ let parsedInput = {};
339
+ try {
340
+ const parsed = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
341
+ parsedInput = parsed && typeof parsed === "object" ? parsed : {};
342
+ } catch {
343
+ parsedInput = toolCall.function.arguments;
344
+ }
345
+ const serverMeta = readAnthropicServerToolMetadata(toolCall.metadata);
346
+ if (serverMeta) {
347
+ const serverToolUseBlock = {
348
+ type: "server_tool_use",
349
+ id: toolCall.id,
350
+ name: serverMeta.serverToolType,
351
+ input: parsedInput
352
+ };
353
+ contentBlocks.push(serverToolUseBlock);
354
+ contentBlocks.push(buildServerToolResultBlock(toolCall.id, serverMeta));
355
+ continue;
356
+ }
357
+ const toolUseBlock = {
358
+ type: "tool_use",
359
+ id: toolCall.id,
360
+ name: toolCall.function.name,
361
+ input: parsedInput
362
+ };
363
+ contentBlocks.push(toolUseBlock);
364
+ }
365
+ formattedMessages.push({
366
+ role: "assistant",
367
+ content: contentBlocks
368
+ });
369
+ continue;
370
+ }
371
+ if (role === "assistant") {
372
+ const contentBlocks = [];
373
+ this.appendThinkingBlocks(contentBlocks, message.thinking);
374
+ if (Array.isArray(message.content)) for (const part of message.content) contentBlocks.push(this.convertContentPartToAnthropic(part));
375
+ else if (message.content) contentBlocks.push({
376
+ type: "text",
377
+ text: message.content
378
+ });
379
+ formattedMessages.push({
380
+ role: "assistant",
381
+ content: contentBlocks.length > 0 ? contentBlocks : ""
382
+ });
383
+ continue;
384
+ }
385
+ if (role === "user" && Array.isArray(message.content)) {
386
+ const contentBlocks = message.content.map((part) => this.convertContentPartToAnthropic(part));
387
+ formattedMessages.push({
388
+ role: "user",
389
+ content: contentBlocks
390
+ });
391
+ continue;
392
+ }
393
+ formattedMessages.push({
394
+ role: "user",
395
+ content: typeof message.content === "string" ? message.content : message.content ? message.content.map((c) => this.convertContentPartToAnthropic(c)) : ""
396
+ });
397
+ }
398
+ return this.mergeConsecutiveSameRoleMessages(formattedMessages);
399
+ }
400
+ appendThinkingBlocks(contentBlocks, thinkingParts) {
401
+ if (!thinkingParts?.length) return;
402
+ for (const thinking of thinkingParts) {
403
+ if (!thinking.signature) continue;
404
+ const block = {
405
+ type: "thinking",
406
+ thinking: thinking.content,
407
+ signature: thinking.signature
408
+ };
409
+ contentBlocks.push(block);
410
+ }
411
+ }
412
+ /**
413
+ * Merge consecutive messages of the same role into a single message.
414
+ * Anthropic's API requires strictly alternating user/assistant roles.
415
+ * Tool results are wrapped as role:'user' messages, which can collide
416
+ * with actual user messages in multi-turn conversations.
417
+ *
418
+ * Also filters out empty assistant messages (e.g., from a previous failed request).
419
+ */
420
+ mergeConsecutiveSameRoleMessages(messages) {
421
+ const merged = [];
422
+ for (const msg of messages) {
423
+ if (msg.role === "assistant") {
424
+ if (!(Array.isArray(msg.content) ? msg.content.length > 0 : typeof msg.content === "string" && msg.content.length > 0)) continue;
425
+ }
426
+ const prev = merged[merged.length - 1];
427
+ if (prev && prev.role === msg.role) {
428
+ const prevBlocks = Array.isArray(prev.content) ? prev.content : typeof prev.content === "string" && prev.content ? [{
429
+ type: "text",
430
+ text: prev.content
431
+ }] : [];
432
+ const msgBlocks = Array.isArray(msg.content) ? msg.content : typeof msg.content === "string" && msg.content ? [{
433
+ type: "text",
434
+ text: msg.content
435
+ }] : [];
436
+ prev.content = [...prevBlocks, ...msgBlocks];
437
+ } else merged.push({ ...msg });
438
+ }
439
+ for (const msg of merged) if (Array.isArray(msg.content)) {
440
+ const seenToolResultIds = /* @__PURE__ */ new Set();
441
+ msg.content = msg.content.filter((block) => {
442
+ if (block.type === "tool_result" && block.tool_use_id) {
443
+ if (seenToolResultIds.has(block.tool_use_id)) return false;
444
+ seenToolResultIds.add(block.tool_use_id);
445
+ }
446
+ return true;
447
+ });
448
+ }
449
+ return merged;
450
+ }
451
+ async *processAnthropicStream(stream, options, genId, logger) {
452
+ const model = options.model;
453
+ let accumulatedContent = "";
454
+ let accumulatedThinking = "";
455
+ let accumulatedSignature = "";
456
+ const toolCallsMap = /* @__PURE__ */ new Map();
457
+ let currentToolIndex = -1;
458
+ let currentServerTool = null;
459
+ const completedServerTools = /* @__PURE__ */ new Map();
460
+ const runId = options.runId ?? genId();
461
+ const threadId = options.threadId ?? genId();
462
+ const messageId = genId();
463
+ let stepId = null;
464
+ let reasoningMessageId = null;
465
+ let hasClosedReasoning = false;
466
+ let hasEmittedRunStarted = false;
467
+ let hasEmittedTextMessageStart = false;
468
+ let hasEmittedRunFinished = false;
469
+ let currentBlockType = null;
470
+ try {
471
+ for await (const event of stream) {
472
+ logger.provider(`provider=anthropic type=${event.type}`, { chunk: event });
473
+ if (!hasEmittedRunStarted) {
474
+ hasEmittedRunStarted = true;
475
+ yield {
476
+ type: EventType.RUN_STARTED,
477
+ runId,
478
+ threadId,
479
+ model,
480
+ timestamp: Date.now(),
481
+ parentRunId: options.parentRunId
482
+ };
483
+ }
484
+ if (event.type === "content_block_start") {
485
+ currentBlockType = event.content_block.type;
486
+ if (event.content_block.type === "tool_use") {
487
+ currentToolIndex++;
488
+ toolCallsMap.set(currentToolIndex, {
489
+ id: event.content_block.id,
490
+ name: event.content_block.name,
491
+ input: "",
492
+ started: false
493
+ });
494
+ } else if (event.content_block.type === "server_tool_use") currentServerTool = {
495
+ id: event.content_block.id,
496
+ name: event.content_block.name,
497
+ input: ""
498
+ };
499
+ else if (event.content_block.type === "web_fetch_tool_result" || event.content_block.type === "web_search_tool_result") {
500
+ const content = event.content_block.content;
501
+ const errorBlock = !Array.isArray(content) && (content.type === "web_fetch_tool_result_error" || content.type === "web_search_tool_result_error") ? content : null;
502
+ if (errorBlock) logger.errors(`anthropic.${event.content_block.type} error_code=${errorBlock.error_code}`, {
503
+ toolUseId: event.content_block.tool_use_id,
504
+ blockType: event.content_block.type,
505
+ errorCode: errorBlock.error_code,
506
+ source: "anthropic.processAnthropicStream"
507
+ });
508
+ const serverTool = completedServerTools.get(event.content_block.tool_use_id);
509
+ if (serverTool) {
510
+ completedServerTools.delete(serverTool.id);
511
+ let parsedInput = {};
512
+ try {
513
+ const parsed = serverTool.input ? JSON.parse(serverTool.input) : {};
514
+ parsedInput = parsed && typeof parsed === "object" ? parsed : {};
515
+ } catch {
516
+ parsedInput = {};
517
+ }
518
+ const serverToolMetadata = {
519
+ providerExecuted: true,
520
+ anthropic: {
521
+ serverToolType: serverTool.name,
522
+ resultBlockType: event.content_block.type,
523
+ result: content
524
+ }
525
+ };
526
+ currentToolIndex++;
527
+ yield {
528
+ type: EventType.TOOL_CALL_START,
529
+ toolCallId: serverTool.id,
530
+ toolCallName: serverTool.name,
531
+ toolName: serverTool.name,
532
+ parentMessageId: messageId,
533
+ model,
534
+ timestamp: Date.now(),
535
+ index: currentToolIndex,
536
+ metadata: serverToolMetadata
537
+ };
538
+ yield {
539
+ type: EventType.TOOL_CALL_END,
540
+ toolCallId: serverTool.id,
541
+ toolCallName: serverTool.name,
542
+ toolName: serverTool.name,
543
+ model,
544
+ timestamp: Date.now(),
545
+ input: parsedInput
546
+ };
547
+ hasEmittedTextMessageStart = false;
548
+ }
549
+ } else if (event.content_block.type === "thinking") {
550
+ accumulatedThinking = "";
551
+ accumulatedSignature = "";
552
+ stepId = genId();
553
+ reasoningMessageId = genId();
554
+ yield {
555
+ type: EventType.REASONING_START,
556
+ messageId: reasoningMessageId,
557
+ model,
558
+ timestamp: Date.now()
559
+ };
560
+ yield {
561
+ type: EventType.REASONING_MESSAGE_START,
562
+ messageId: reasoningMessageId,
563
+ role: "reasoning",
564
+ model,
565
+ timestamp: Date.now()
566
+ };
567
+ yield {
568
+ type: EventType.STEP_STARTED,
569
+ stepName: stepId,
570
+ stepId,
571
+ model,
572
+ timestamp: Date.now(),
573
+ stepType: "thinking"
574
+ };
575
+ }
576
+ } else if (event.type === "content_block_delta") {
577
+ if (event.delta.type === "text_delta") {
578
+ if (reasoningMessageId && !hasClosedReasoning) {
579
+ hasClosedReasoning = true;
580
+ yield {
581
+ type: EventType.REASONING_MESSAGE_END,
582
+ messageId: reasoningMessageId,
583
+ model,
584
+ timestamp: Date.now()
585
+ };
586
+ yield {
587
+ type: EventType.REASONING_END,
588
+ messageId: reasoningMessageId,
589
+ model,
590
+ timestamp: Date.now()
591
+ };
592
+ }
593
+ if (!hasEmittedTextMessageStart) {
594
+ hasEmittedTextMessageStart = true;
595
+ yield {
596
+ type: EventType.TEXT_MESSAGE_START,
597
+ messageId,
598
+ model,
599
+ timestamp: Date.now(),
600
+ role: "assistant"
601
+ };
602
+ }
603
+ const delta = event.delta.text;
604
+ accumulatedContent += delta;
605
+ yield {
606
+ type: EventType.TEXT_MESSAGE_CONTENT,
607
+ messageId,
608
+ model,
609
+ timestamp: Date.now(),
610
+ delta,
611
+ content: accumulatedContent
612
+ };
613
+ } else if (event.delta.type === "thinking_delta" && reasoningMessageId) {
614
+ const delta = event.delta.thinking;
615
+ accumulatedThinking += delta;
616
+ yield {
617
+ type: EventType.REASONING_MESSAGE_CONTENT,
618
+ messageId: reasoningMessageId,
619
+ delta,
620
+ model,
621
+ timestamp: Date.now()
622
+ };
623
+ yield {
624
+ type: EventType.STEP_FINISHED,
625
+ stepName: stepId || genId(),
626
+ stepId: stepId || genId(),
627
+ model,
628
+ timestamp: Date.now(),
629
+ delta,
630
+ content: accumulatedThinking
631
+ };
632
+ } else if (event.delta.type === "signature_delta") accumulatedSignature += event.delta.signature || "";
633
+ else if (event.delta.type === "input_json_delta") {
634
+ if (currentBlockType === "tool_use") {
635
+ const existing = toolCallsMap.get(currentToolIndex);
636
+ if (existing) {
637
+ if (!existing.started) {
638
+ existing.started = true;
639
+ yield {
640
+ type: EventType.TOOL_CALL_START,
641
+ toolCallId: existing.id,
642
+ toolCallName: existing.name,
643
+ toolName: existing.name,
644
+ parentMessageId: messageId,
645
+ model,
646
+ timestamp: Date.now(),
647
+ index: currentToolIndex
648
+ };
649
+ }
650
+ existing.input += event.delta.partial_json;
651
+ yield {
652
+ type: EventType.TOOL_CALL_ARGS,
653
+ toolCallId: existing.id,
654
+ model,
655
+ timestamp: Date.now(),
656
+ delta: event.delta.partial_json,
657
+ args: existing.input
658
+ };
659
+ }
660
+ } else if (currentBlockType === "server_tool_use" && currentServerTool) currentServerTool.input += event.delta.partial_json;
661
+ }
662
+ } else if (event.type === "content_block_stop") {
663
+ if (currentBlockType === "thinking") {
664
+ if (accumulatedSignature && stepId) yield {
665
+ type: EventType.STEP_FINISHED,
666
+ stepName: stepId,
667
+ stepId,
668
+ model,
669
+ timestamp: Date.now(),
670
+ delta: "",
671
+ content: accumulatedThinking,
672
+ signature: accumulatedSignature
673
+ };
674
+ } else if (currentBlockType === "tool_use") {
675
+ const existing = toolCallsMap.get(currentToolIndex);
676
+ if (existing) {
677
+ if (!existing.started) {
678
+ existing.started = true;
679
+ yield {
680
+ type: EventType.TOOL_CALL_START,
681
+ toolCallId: existing.id,
682
+ toolCallName: existing.name,
683
+ toolName: existing.name,
684
+ parentMessageId: messageId,
685
+ model,
686
+ timestamp: Date.now(),
687
+ index: currentToolIndex
688
+ };
689
+ }
690
+ let parsedInput = {};
691
+ try {
692
+ const parsed = existing.input ? JSON.parse(existing.input) : {};
693
+ parsedInput = parsed && typeof parsed === "object" ? parsed : {};
694
+ } catch {
695
+ parsedInput = {};
696
+ }
697
+ yield {
698
+ type: EventType.TOOL_CALL_END,
699
+ toolCallId: existing.id,
700
+ toolCallName: existing.name,
701
+ toolName: existing.name,
702
+ model,
703
+ timestamp: Date.now(),
704
+ input: parsedInput
705
+ };
706
+ hasEmittedTextMessageStart = false;
707
+ }
708
+ } else if (currentBlockType === "server_tool_use") {
709
+ if (currentServerTool) {
710
+ logger.provider(`provider=anthropic server_tool_use name=${currentServerTool.name}`, {
711
+ toolUseId: currentServerTool.id,
712
+ name: currentServerTool.name,
713
+ input: currentServerTool.input
714
+ });
715
+ completedServerTools.set(currentServerTool.id, currentServerTool);
716
+ }
717
+ currentServerTool = null;
718
+ } else if (currentBlockType === "web_fetch_tool_result" || currentBlockType === "web_search_tool_result") {} else if (hasEmittedTextMessageStart && accumulatedContent) yield {
719
+ type: EventType.TEXT_MESSAGE_END,
720
+ messageId,
721
+ model,
722
+ timestamp: Date.now()
723
+ };
724
+ currentBlockType = null;
725
+ } else if (event.type === "message_stop") {
726
+ if (reasoningMessageId && !hasClosedReasoning) {
727
+ hasClosedReasoning = true;
728
+ yield {
729
+ type: EventType.REASONING_MESSAGE_END,
730
+ messageId: reasoningMessageId,
731
+ model,
732
+ timestamp: Date.now()
733
+ };
734
+ yield {
735
+ type: EventType.REASONING_END,
736
+ messageId: reasoningMessageId,
737
+ model,
738
+ timestamp: Date.now()
739
+ };
740
+ }
741
+ if (!hasEmittedRunFinished) yield {
742
+ type: EventType.RUN_FINISHED,
743
+ runId,
744
+ threadId,
745
+ model,
746
+ timestamp: Date.now(),
747
+ finishReason: "stop"
748
+ };
749
+ } else if (event.type === "message_delta") {
750
+ if (event.delta.stop_reason) {
751
+ hasEmittedRunFinished = true;
752
+ if (reasoningMessageId && !hasClosedReasoning) {
753
+ hasClosedReasoning = true;
754
+ yield {
755
+ type: EventType.REASONING_MESSAGE_END,
756
+ messageId: reasoningMessageId,
757
+ model,
758
+ timestamp: Date.now()
759
+ };
760
+ yield {
761
+ type: EventType.REASONING_END,
762
+ messageId: reasoningMessageId,
763
+ model,
764
+ timestamp: Date.now()
765
+ };
766
+ }
767
+ switch (event.delta.stop_reason) {
768
+ case "tool_use":
769
+ yield {
770
+ type: EventType.RUN_FINISHED,
771
+ runId,
772
+ threadId,
773
+ model,
774
+ timestamp: Date.now(),
775
+ finishReason: "tool_calls",
776
+ usage: buildAnthropicUsage(event.usage)
777
+ };
778
+ break;
779
+ case "max_tokens":
780
+ if (options.modelOptions?.max_tokens == null) {
781
+ const defaultedMaxTokens = getAnthropicDefaultMaxTokens(model);
782
+ logger.warn(`anthropic response truncated at the default max_tokens (${defaultedMaxTokens}) for model=${model}; pass maxTokens (or modelOptions.max_tokens) to raise the output ceiling`, {
783
+ source: "anthropic.processAnthropicStream",
784
+ model,
785
+ defaultedMaxTokens
786
+ });
787
+ }
788
+ yield {
789
+ type: EventType.RUN_ERROR,
790
+ model,
791
+ timestamp: Date.now(),
792
+ message: "The response was cut off because the maximum token limit was reached.",
793
+ code: "max_tokens",
794
+ error: {
795
+ message: "The response was cut off because the maximum token limit was reached.",
796
+ code: "max_tokens"
797
+ }
798
+ };
799
+ break;
800
+ default: yield {
801
+ type: EventType.RUN_FINISHED,
802
+ runId,
803
+ threadId,
804
+ model,
805
+ timestamp: Date.now(),
806
+ finishReason: "stop",
807
+ usage: buildAnthropicUsage(event.usage)
808
+ };
809
+ }
810
+ }
811
+ }
812
+ }
813
+ } catch (error) {
814
+ const err = error;
815
+ const rawEvent = toRunErrorRawEvent(error);
816
+ logger.errors("anthropic.processAnthropicStream fatal", {
817
+ error,
818
+ source: "anthropic.processAnthropicStream"
819
+ });
820
+ yield {
821
+ type: EventType.RUN_ERROR,
822
+ model,
823
+ timestamp: Date.now(),
824
+ message: err.message || "Unknown error occurred",
825
+ code: err.code || String(err.status),
826
+ ...rawEvent !== void 0 && { rawEvent },
827
+ error: {
828
+ message: err.message || "Unknown error occurred",
829
+ code: err.code || String(err.status)
830
+ }
831
+ };
832
+ }
833
+ }
834
+ };
835
+ /**
836
+ * Creates an Anthropic chat adapter with explicit API key.
837
+ * Type resolution happens here at the call site.
838
+ */
941
839
  function createAnthropicChat(model, apiKey, config) {
942
- return new AnthropicTextAdapter({ apiKey, ...config }, model);
840
+ return new AnthropicTextAdapter({
841
+ apiKey,
842
+ ...config
843
+ }, model);
943
844
  }
845
+ /**
846
+ * Creates an Anthropic text adapter with automatic API key detection.
847
+ * Type resolution happens here at the call site.
848
+ */
944
849
  function anthropicText(model, config) {
945
- const apiKey = getAnthropicApiKeyFromEnv();
946
- return createAnthropicChat(model, apiKey, config);
850
+ return createAnthropicChat(model, getAnthropicApiKeyFromEnv(), config);
947
851
  }
948
- export {
949
- AnthropicTextAdapter,
950
- anthropicText,
951
- computeAnthropicBetas,
952
- createAnthropicChat
953
- };
954
- //# sourceMappingURL=text.js.map
852
+ //#endregion
853
+ export { AnthropicTextAdapter, anthropicText, computeAnthropicBetas, createAnthropicChat };
854
+
855
+ //# sourceMappingURL=text.js.map