@tanstack/ai-anthropic 0.16.3 → 0.16.5
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/dist/esm/adapters/summarize.js +39 -12
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.js +842 -940
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/index.js +3 -13
- package/dist/esm/model-meta.d.ts +11 -11
- package/dist/esm/model-meta.js +399 -74
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/text/text-provider-options.d.ts +16 -1
- package/dist/esm/text/text-provider-options.js +19 -26
- package/dist/esm/text/text-provider-options.js.map +1 -1
- package/dist/esm/tools/bash-tool.js +11 -12
- package/dist/esm/tools/bash-tool.js.map +1 -1
- package/dist/esm/tools/code-execution-tool.js +29 -30
- package/dist/esm/tools/code-execution-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.js +11 -12
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/custom-tool.js +29 -30
- package/dist/esm/tools/custom-tool.js.map +1 -1
- package/dist/esm/tools/index.js +1 -14
- package/dist/esm/tools/memory-tool.js +14 -15
- package/dist/esm/tools/memory-tool.js.map +1 -1
- package/dist/esm/tools/text-editor-tool.js +11 -14
- package/dist/esm/tools/text-editor-tool.js.map +1 -1
- package/dist/esm/tools/tool-converter.js +41 -25
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/tools/web-fetch-tool.js +15 -16
- package/dist/esm/tools/web-fetch-tool.js.map +1 -1
- package/dist/esm/tools/web-search-tool.js +32 -56
- package/dist/esm/tools/web-search-tool.js.map +1 -1
- package/dist/esm/usage.js +33 -32
- package/dist/esm/usage.js.map +1 -1
- package/dist/esm/utils/client.js +23 -14
- package/dist/esm/utils/client.js.map +1 -1
- package/package.json +6 -6
- package/src/adapters/text.ts +1 -0
- package/src/model-meta.ts +41 -20
- package/src/text/text-provider-options.ts +18 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/tools/index.js.map +0 -1
|
@@ -1,954 +1,856 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
"cache_control",
|
|
195
|
+
"container",
|
|
196
|
+
"context_management",
|
|
197
|
+
"effort",
|
|
198
|
+
"mcp_servers",
|
|
199
|
+
"output_config",
|
|
200
|
+
"service_tier",
|
|
201
|
+
"stop_sequences",
|
|
202
|
+
"thinking",
|
|
203
|
+
"tool_choice",
|
|
204
|
+
"top_k",
|
|
205
|
+
"temperature",
|
|
206
|
+
"top_p"
|
|
207
|
+
];
|
|
208
|
+
const droppedKeyExemptSet = /* @__PURE__ */ new Set([...validKeys, "max_tokens"]);
|
|
209
|
+
const droppedKeys = Object.keys(modelOptions).filter((key) => !droppedKeyExemptSet.has(key));
|
|
210
|
+
if (droppedKeys.length > 0) options.logger.errors(`anthropic.mapCommonOptionsToAnthropic dropped unknown modelOptions key(s): ${droppedKeys.join(", ")}`, {
|
|
211
|
+
source: "anthropic.mapCommonOptionsToAnthropic",
|
|
212
|
+
droppedKeys,
|
|
213
|
+
hint: droppedKeys.includes("system") ? "pass system prompts via the top-level `systemPrompts` option; `modelOptions.system` is no longer honored" : void 0
|
|
214
|
+
});
|
|
215
|
+
for (const key of validKeys) if (key in modelOptions) {
|
|
216
|
+
const value = modelOptions[key];
|
|
217
|
+
if (key === "tool_choice" && typeof value === "string") validProviderOptions[key] = { type: value };
|
|
218
|
+
else validProviderOptions[key] = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const thinkingBudget = validProviderOptions.thinking?.type === "enabled" ? validProviderOptions.thinking.budget_tokens : void 0;
|
|
222
|
+
const defaultMaxTokens = modelOptions?.max_tokens ?? getAnthropicDefaultMaxTokens(this.model, { stream });
|
|
223
|
+
const maxTokens = thinkingBudget && thinkingBudget >= defaultMaxTokens ? thinkingBudget + 1 : defaultMaxTokens;
|
|
224
|
+
const systemBlocks = (() => {
|
|
225
|
+
const normalized = normalizeSystemPrompts(options.systemPrompts);
|
|
226
|
+
if (normalized.length === 0) return void 0;
|
|
227
|
+
return normalized.map((p) => ({
|
|
228
|
+
type: "text",
|
|
229
|
+
text: p.content,
|
|
230
|
+
...p.metadata?.cache_control && { cache_control: p.metadata.cache_control }
|
|
231
|
+
}));
|
|
232
|
+
})();
|
|
233
|
+
const combinedSchema = options.outputSchema;
|
|
234
|
+
const outputConfig = combinedSchema ? { output_config: {
|
|
235
|
+
...validProviderOptions.output_config ?? {},
|
|
236
|
+
format: {
|
|
237
|
+
type: "json_schema",
|
|
238
|
+
schema: combinedSchema
|
|
239
|
+
}
|
|
240
|
+
} } : void 0;
|
|
241
|
+
const toolSkills = options.tools?.map((tool) => tool.name === "code_execution" ? readCodeExecutionSkills(tool) : void 0).find((skills) => skills && skills.length > 0);
|
|
242
|
+
if (toolSkills && toolSkills.length > 0) validProviderOptions.container = {
|
|
243
|
+
id: (validProviderOptions.container ?? void 0)?.id ?? null,
|
|
244
|
+
skills: toolSkills
|
|
245
|
+
};
|
|
246
|
+
const requestParams = {
|
|
247
|
+
model: options.model,
|
|
248
|
+
max_tokens: maxTokens,
|
|
249
|
+
messages: formattedMessages,
|
|
250
|
+
...systemBlocks !== void 0 && { system: systemBlocks },
|
|
251
|
+
...tools !== void 0 && { tools },
|
|
252
|
+
...validProviderOptions,
|
|
253
|
+
...outputConfig ?? {}
|
|
254
|
+
};
|
|
255
|
+
validateTextProviderOptions(requestParams);
|
|
256
|
+
return requestParams;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Anthropic supports `output_config.format` + `tools` in a single streaming
|
|
260
|
+
* Messages request only for Claude 4.5+ (GA 2026-01-29). For 4.4 and
|
|
261
|
+
* earlier we keep the forced-tool-use workaround in
|
|
262
|
+
* {@link structuredOutput} via the engine's finalization path.
|
|
263
|
+
*/
|
|
264
|
+
supportsCombinedToolsAndSchema() {
|
|
265
|
+
return ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS.has(this.model);
|
|
266
|
+
}
|
|
267
|
+
convertContentPartToAnthropic(part) {
|
|
268
|
+
switch (part.type) {
|
|
269
|
+
case "text": {
|
|
270
|
+
const metadata = part.metadata;
|
|
271
|
+
return {
|
|
272
|
+
type: "text",
|
|
273
|
+
text: part.content,
|
|
274
|
+
...metadata
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
case "image": {
|
|
278
|
+
const metadata = part.metadata;
|
|
279
|
+
return {
|
|
280
|
+
type: "image",
|
|
281
|
+
source: part.source.type === "data" ? {
|
|
282
|
+
type: "base64",
|
|
283
|
+
data: part.source.value,
|
|
284
|
+
media_type: part.source.mimeType
|
|
285
|
+
} : {
|
|
286
|
+
type: "url",
|
|
287
|
+
url: part.source.value
|
|
288
|
+
},
|
|
289
|
+
...metadata
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
case "document": {
|
|
293
|
+
const metadata = part.metadata;
|
|
294
|
+
return {
|
|
295
|
+
type: "document",
|
|
296
|
+
source: part.source.type === "data" ? {
|
|
297
|
+
type: "base64",
|
|
298
|
+
data: part.source.value,
|
|
299
|
+
media_type: part.source.mimeType
|
|
300
|
+
} : {
|
|
301
|
+
type: "url",
|
|
302
|
+
url: part.source.value
|
|
303
|
+
},
|
|
304
|
+
...metadata
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
case "audio":
|
|
308
|
+
case "video": throw new Error(`Anthropic does not support ${part.type} content directly`);
|
|
309
|
+
default: throw new Error(`Unsupported content part type: ${part.type}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
formatMessages(messages) {
|
|
313
|
+
const formattedMessages = [];
|
|
314
|
+
for (const message of messages) {
|
|
315
|
+
const role = message.role;
|
|
316
|
+
if (role === "tool" && message.toolCallId) {
|
|
317
|
+
const toolContent = message.content;
|
|
318
|
+
formattedMessages.push({
|
|
319
|
+
role: "user",
|
|
320
|
+
content: [{
|
|
321
|
+
type: "tool_result",
|
|
322
|
+
tool_use_id: message.toolCallId,
|
|
323
|
+
content: Array.isArray(toolContent) ? toolContent.map((part) => this.convertContentPartToAnthropic(part)) : typeof toolContent === "string" ? toolContent : ""
|
|
324
|
+
}]
|
|
325
|
+
});
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (role === "assistant" && message.toolCalls?.length) {
|
|
329
|
+
const contentBlocks = [];
|
|
330
|
+
this.appendThinkingBlocks(contentBlocks, message.thinking);
|
|
331
|
+
if (message.content) {
|
|
332
|
+
const textBlock = {
|
|
333
|
+
type: "text",
|
|
334
|
+
text: typeof message.content === "string" ? message.content : ""
|
|
335
|
+
};
|
|
336
|
+
contentBlocks.push(textBlock);
|
|
337
|
+
}
|
|
338
|
+
for (const toolCall of message.toolCalls) {
|
|
339
|
+
let parsedInput = {};
|
|
340
|
+
try {
|
|
341
|
+
const parsed = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
|
|
342
|
+
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
343
|
+
} catch {
|
|
344
|
+
parsedInput = toolCall.function.arguments;
|
|
345
|
+
}
|
|
346
|
+
const serverMeta = readAnthropicServerToolMetadata(toolCall.metadata);
|
|
347
|
+
if (serverMeta) {
|
|
348
|
+
const serverToolUseBlock = {
|
|
349
|
+
type: "server_tool_use",
|
|
350
|
+
id: toolCall.id,
|
|
351
|
+
name: serverMeta.serverToolType,
|
|
352
|
+
input: parsedInput
|
|
353
|
+
};
|
|
354
|
+
contentBlocks.push(serverToolUseBlock);
|
|
355
|
+
contentBlocks.push(buildServerToolResultBlock(toolCall.id, serverMeta));
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const toolUseBlock = {
|
|
359
|
+
type: "tool_use",
|
|
360
|
+
id: toolCall.id,
|
|
361
|
+
name: toolCall.function.name,
|
|
362
|
+
input: parsedInput
|
|
363
|
+
};
|
|
364
|
+
contentBlocks.push(toolUseBlock);
|
|
365
|
+
}
|
|
366
|
+
formattedMessages.push({
|
|
367
|
+
role: "assistant",
|
|
368
|
+
content: contentBlocks
|
|
369
|
+
});
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (role === "assistant") {
|
|
373
|
+
const contentBlocks = [];
|
|
374
|
+
this.appendThinkingBlocks(contentBlocks, message.thinking);
|
|
375
|
+
if (Array.isArray(message.content)) for (const part of message.content) contentBlocks.push(this.convertContentPartToAnthropic(part));
|
|
376
|
+
else if (message.content) contentBlocks.push({
|
|
377
|
+
type: "text",
|
|
378
|
+
text: message.content
|
|
379
|
+
});
|
|
380
|
+
formattedMessages.push({
|
|
381
|
+
role: "assistant",
|
|
382
|
+
content: contentBlocks.length > 0 ? contentBlocks : ""
|
|
383
|
+
});
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (role === "user" && Array.isArray(message.content)) {
|
|
387
|
+
const contentBlocks = message.content.map((part) => this.convertContentPartToAnthropic(part));
|
|
388
|
+
formattedMessages.push({
|
|
389
|
+
role: "user",
|
|
390
|
+
content: contentBlocks
|
|
391
|
+
});
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
formattedMessages.push({
|
|
395
|
+
role: "user",
|
|
396
|
+
content: typeof message.content === "string" ? message.content : message.content ? message.content.map((c) => this.convertContentPartToAnthropic(c)) : ""
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return this.mergeConsecutiveSameRoleMessages(formattedMessages);
|
|
400
|
+
}
|
|
401
|
+
appendThinkingBlocks(contentBlocks, thinkingParts) {
|
|
402
|
+
if (!thinkingParts?.length) return;
|
|
403
|
+
for (const thinking of thinkingParts) {
|
|
404
|
+
if (!thinking.signature) continue;
|
|
405
|
+
const block = {
|
|
406
|
+
type: "thinking",
|
|
407
|
+
thinking: thinking.content,
|
|
408
|
+
signature: thinking.signature
|
|
409
|
+
};
|
|
410
|
+
contentBlocks.push(block);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Merge consecutive messages of the same role into a single message.
|
|
415
|
+
* Anthropic's API requires strictly alternating user/assistant roles.
|
|
416
|
+
* Tool results are wrapped as role:'user' messages, which can collide
|
|
417
|
+
* with actual user messages in multi-turn conversations.
|
|
418
|
+
*
|
|
419
|
+
* Also filters out empty assistant messages (e.g., from a previous failed request).
|
|
420
|
+
*/
|
|
421
|
+
mergeConsecutiveSameRoleMessages(messages) {
|
|
422
|
+
const merged = [];
|
|
423
|
+
for (const msg of messages) {
|
|
424
|
+
if (msg.role === "assistant") {
|
|
425
|
+
if (!(Array.isArray(msg.content) ? msg.content.length > 0 : typeof msg.content === "string" && msg.content.length > 0)) continue;
|
|
426
|
+
}
|
|
427
|
+
const prev = merged[merged.length - 1];
|
|
428
|
+
if (prev && prev.role === msg.role) {
|
|
429
|
+
const prevBlocks = Array.isArray(prev.content) ? prev.content : typeof prev.content === "string" && prev.content ? [{
|
|
430
|
+
type: "text",
|
|
431
|
+
text: prev.content
|
|
432
|
+
}] : [];
|
|
433
|
+
const msgBlocks = Array.isArray(msg.content) ? msg.content : typeof msg.content === "string" && msg.content ? [{
|
|
434
|
+
type: "text",
|
|
435
|
+
text: msg.content
|
|
436
|
+
}] : [];
|
|
437
|
+
prev.content = [...prevBlocks, ...msgBlocks];
|
|
438
|
+
} else merged.push({ ...msg });
|
|
439
|
+
}
|
|
440
|
+
for (const msg of merged) if (Array.isArray(msg.content)) {
|
|
441
|
+
const seenToolResultIds = /* @__PURE__ */ new Set();
|
|
442
|
+
msg.content = msg.content.filter((block) => {
|
|
443
|
+
if (block.type === "tool_result" && block.tool_use_id) {
|
|
444
|
+
if (seenToolResultIds.has(block.tool_use_id)) return false;
|
|
445
|
+
seenToolResultIds.add(block.tool_use_id);
|
|
446
|
+
}
|
|
447
|
+
return true;
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return merged;
|
|
451
|
+
}
|
|
452
|
+
async *processAnthropicStream(stream, options, genId, logger) {
|
|
453
|
+
const model = options.model;
|
|
454
|
+
let accumulatedContent = "";
|
|
455
|
+
let accumulatedThinking = "";
|
|
456
|
+
let accumulatedSignature = "";
|
|
457
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
458
|
+
let currentToolIndex = -1;
|
|
459
|
+
let currentServerTool = null;
|
|
460
|
+
const completedServerTools = /* @__PURE__ */ new Map();
|
|
461
|
+
const runId = options.runId ?? genId();
|
|
462
|
+
const threadId = options.threadId ?? genId();
|
|
463
|
+
const messageId = genId();
|
|
464
|
+
let stepId = null;
|
|
465
|
+
let reasoningMessageId = null;
|
|
466
|
+
let hasClosedReasoning = false;
|
|
467
|
+
let hasEmittedRunStarted = false;
|
|
468
|
+
let hasEmittedTextMessageStart = false;
|
|
469
|
+
let hasEmittedRunFinished = false;
|
|
470
|
+
let currentBlockType = null;
|
|
471
|
+
try {
|
|
472
|
+
for await (const event of stream) {
|
|
473
|
+
logger.provider(`provider=anthropic type=${event.type}`, { chunk: event });
|
|
474
|
+
if (!hasEmittedRunStarted) {
|
|
475
|
+
hasEmittedRunStarted = true;
|
|
476
|
+
yield {
|
|
477
|
+
type: EventType.RUN_STARTED,
|
|
478
|
+
runId,
|
|
479
|
+
threadId,
|
|
480
|
+
model,
|
|
481
|
+
timestamp: Date.now(),
|
|
482
|
+
parentRunId: options.parentRunId
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (event.type === "content_block_start") {
|
|
486
|
+
currentBlockType = event.content_block.type;
|
|
487
|
+
if (event.content_block.type === "tool_use") {
|
|
488
|
+
currentToolIndex++;
|
|
489
|
+
toolCallsMap.set(currentToolIndex, {
|
|
490
|
+
id: event.content_block.id,
|
|
491
|
+
name: event.content_block.name,
|
|
492
|
+
input: "",
|
|
493
|
+
started: false
|
|
494
|
+
});
|
|
495
|
+
} else if (event.content_block.type === "server_tool_use") currentServerTool = {
|
|
496
|
+
id: event.content_block.id,
|
|
497
|
+
name: event.content_block.name,
|
|
498
|
+
input: ""
|
|
499
|
+
};
|
|
500
|
+
else if (event.content_block.type === "web_fetch_tool_result" || event.content_block.type === "web_search_tool_result") {
|
|
501
|
+
const content = event.content_block.content;
|
|
502
|
+
const errorBlock = !Array.isArray(content) && (content.type === "web_fetch_tool_result_error" || content.type === "web_search_tool_result_error") ? content : null;
|
|
503
|
+
if (errorBlock) logger.errors(`anthropic.${event.content_block.type} error_code=${errorBlock.error_code}`, {
|
|
504
|
+
toolUseId: event.content_block.tool_use_id,
|
|
505
|
+
blockType: event.content_block.type,
|
|
506
|
+
errorCode: errorBlock.error_code,
|
|
507
|
+
source: "anthropic.processAnthropicStream"
|
|
508
|
+
});
|
|
509
|
+
const serverTool = completedServerTools.get(event.content_block.tool_use_id);
|
|
510
|
+
if (serverTool) {
|
|
511
|
+
completedServerTools.delete(serverTool.id);
|
|
512
|
+
let parsedInput = {};
|
|
513
|
+
try {
|
|
514
|
+
const parsed = serverTool.input ? JSON.parse(serverTool.input) : {};
|
|
515
|
+
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
516
|
+
} catch {
|
|
517
|
+
parsedInput = {};
|
|
518
|
+
}
|
|
519
|
+
const serverToolMetadata = {
|
|
520
|
+
providerExecuted: true,
|
|
521
|
+
anthropic: {
|
|
522
|
+
serverToolType: serverTool.name,
|
|
523
|
+
resultBlockType: event.content_block.type,
|
|
524
|
+
result: content
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
currentToolIndex++;
|
|
528
|
+
yield {
|
|
529
|
+
type: EventType.TOOL_CALL_START,
|
|
530
|
+
toolCallId: serverTool.id,
|
|
531
|
+
toolCallName: serverTool.name,
|
|
532
|
+
toolName: serverTool.name,
|
|
533
|
+
parentMessageId: messageId,
|
|
534
|
+
model,
|
|
535
|
+
timestamp: Date.now(),
|
|
536
|
+
index: currentToolIndex,
|
|
537
|
+
metadata: serverToolMetadata
|
|
538
|
+
};
|
|
539
|
+
yield {
|
|
540
|
+
type: EventType.TOOL_CALL_END,
|
|
541
|
+
toolCallId: serverTool.id,
|
|
542
|
+
toolCallName: serverTool.name,
|
|
543
|
+
toolName: serverTool.name,
|
|
544
|
+
model,
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
input: parsedInput
|
|
547
|
+
};
|
|
548
|
+
hasEmittedTextMessageStart = false;
|
|
549
|
+
}
|
|
550
|
+
} else if (event.content_block.type === "thinking") {
|
|
551
|
+
accumulatedThinking = "";
|
|
552
|
+
accumulatedSignature = "";
|
|
553
|
+
stepId = genId();
|
|
554
|
+
reasoningMessageId = genId();
|
|
555
|
+
yield {
|
|
556
|
+
type: EventType.REASONING_START,
|
|
557
|
+
messageId: reasoningMessageId,
|
|
558
|
+
model,
|
|
559
|
+
timestamp: Date.now()
|
|
560
|
+
};
|
|
561
|
+
yield {
|
|
562
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
563
|
+
messageId: reasoningMessageId,
|
|
564
|
+
role: "reasoning",
|
|
565
|
+
model,
|
|
566
|
+
timestamp: Date.now()
|
|
567
|
+
};
|
|
568
|
+
yield {
|
|
569
|
+
type: EventType.STEP_STARTED,
|
|
570
|
+
stepName: stepId,
|
|
571
|
+
stepId,
|
|
572
|
+
model,
|
|
573
|
+
timestamp: Date.now(),
|
|
574
|
+
stepType: "thinking"
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
} else if (event.type === "content_block_delta") {
|
|
578
|
+
if (event.delta.type === "text_delta") {
|
|
579
|
+
if (reasoningMessageId && !hasClosedReasoning) {
|
|
580
|
+
hasClosedReasoning = true;
|
|
581
|
+
yield {
|
|
582
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
583
|
+
messageId: reasoningMessageId,
|
|
584
|
+
model,
|
|
585
|
+
timestamp: Date.now()
|
|
586
|
+
};
|
|
587
|
+
yield {
|
|
588
|
+
type: EventType.REASONING_END,
|
|
589
|
+
messageId: reasoningMessageId,
|
|
590
|
+
model,
|
|
591
|
+
timestamp: Date.now()
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
if (!hasEmittedTextMessageStart) {
|
|
595
|
+
hasEmittedTextMessageStart = true;
|
|
596
|
+
yield {
|
|
597
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
598
|
+
messageId,
|
|
599
|
+
model,
|
|
600
|
+
timestamp: Date.now(),
|
|
601
|
+
role: "assistant"
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
const delta = event.delta.text;
|
|
605
|
+
accumulatedContent += delta;
|
|
606
|
+
yield {
|
|
607
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
608
|
+
messageId,
|
|
609
|
+
model,
|
|
610
|
+
timestamp: Date.now(),
|
|
611
|
+
delta,
|
|
612
|
+
content: accumulatedContent
|
|
613
|
+
};
|
|
614
|
+
} else if (event.delta.type === "thinking_delta" && reasoningMessageId) {
|
|
615
|
+
const delta = event.delta.thinking;
|
|
616
|
+
accumulatedThinking += delta;
|
|
617
|
+
yield {
|
|
618
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
619
|
+
messageId: reasoningMessageId,
|
|
620
|
+
delta,
|
|
621
|
+
model,
|
|
622
|
+
timestamp: Date.now()
|
|
623
|
+
};
|
|
624
|
+
yield {
|
|
625
|
+
type: EventType.STEP_FINISHED,
|
|
626
|
+
stepName: stepId || genId(),
|
|
627
|
+
stepId: stepId || genId(),
|
|
628
|
+
model,
|
|
629
|
+
timestamp: Date.now(),
|
|
630
|
+
delta,
|
|
631
|
+
content: accumulatedThinking
|
|
632
|
+
};
|
|
633
|
+
} else if (event.delta.type === "signature_delta") accumulatedSignature += event.delta.signature || "";
|
|
634
|
+
else if (event.delta.type === "input_json_delta") {
|
|
635
|
+
if (currentBlockType === "tool_use") {
|
|
636
|
+
const existing = toolCallsMap.get(currentToolIndex);
|
|
637
|
+
if (existing) {
|
|
638
|
+
if (!existing.started) {
|
|
639
|
+
existing.started = true;
|
|
640
|
+
yield {
|
|
641
|
+
type: EventType.TOOL_CALL_START,
|
|
642
|
+
toolCallId: existing.id,
|
|
643
|
+
toolCallName: existing.name,
|
|
644
|
+
toolName: existing.name,
|
|
645
|
+
parentMessageId: messageId,
|
|
646
|
+
model,
|
|
647
|
+
timestamp: Date.now(),
|
|
648
|
+
index: currentToolIndex
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
existing.input += event.delta.partial_json;
|
|
652
|
+
yield {
|
|
653
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
654
|
+
toolCallId: existing.id,
|
|
655
|
+
model,
|
|
656
|
+
timestamp: Date.now(),
|
|
657
|
+
delta: event.delta.partial_json,
|
|
658
|
+
args: existing.input
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
} else if (currentBlockType === "server_tool_use" && currentServerTool) currentServerTool.input += event.delta.partial_json;
|
|
662
|
+
}
|
|
663
|
+
} else if (event.type === "content_block_stop") {
|
|
664
|
+
if (currentBlockType === "thinking") {
|
|
665
|
+
if (accumulatedSignature && stepId) yield {
|
|
666
|
+
type: EventType.STEP_FINISHED,
|
|
667
|
+
stepName: stepId,
|
|
668
|
+
stepId,
|
|
669
|
+
model,
|
|
670
|
+
timestamp: Date.now(),
|
|
671
|
+
delta: "",
|
|
672
|
+
content: accumulatedThinking,
|
|
673
|
+
signature: accumulatedSignature
|
|
674
|
+
};
|
|
675
|
+
} else if (currentBlockType === "tool_use") {
|
|
676
|
+
const existing = toolCallsMap.get(currentToolIndex);
|
|
677
|
+
if (existing) {
|
|
678
|
+
if (!existing.started) {
|
|
679
|
+
existing.started = true;
|
|
680
|
+
yield {
|
|
681
|
+
type: EventType.TOOL_CALL_START,
|
|
682
|
+
toolCallId: existing.id,
|
|
683
|
+
toolCallName: existing.name,
|
|
684
|
+
toolName: existing.name,
|
|
685
|
+
parentMessageId: messageId,
|
|
686
|
+
model,
|
|
687
|
+
timestamp: Date.now(),
|
|
688
|
+
index: currentToolIndex
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
let parsedInput = {};
|
|
692
|
+
try {
|
|
693
|
+
const parsed = existing.input ? JSON.parse(existing.input) : {};
|
|
694
|
+
parsedInput = parsed && typeof parsed === "object" ? parsed : {};
|
|
695
|
+
} catch {
|
|
696
|
+
parsedInput = {};
|
|
697
|
+
}
|
|
698
|
+
yield {
|
|
699
|
+
type: EventType.TOOL_CALL_END,
|
|
700
|
+
toolCallId: existing.id,
|
|
701
|
+
toolCallName: existing.name,
|
|
702
|
+
toolName: existing.name,
|
|
703
|
+
model,
|
|
704
|
+
timestamp: Date.now(),
|
|
705
|
+
input: parsedInput
|
|
706
|
+
};
|
|
707
|
+
hasEmittedTextMessageStart = false;
|
|
708
|
+
}
|
|
709
|
+
} else if (currentBlockType === "server_tool_use") {
|
|
710
|
+
if (currentServerTool) {
|
|
711
|
+
logger.provider(`provider=anthropic server_tool_use name=${currentServerTool.name}`, {
|
|
712
|
+
toolUseId: currentServerTool.id,
|
|
713
|
+
name: currentServerTool.name,
|
|
714
|
+
input: currentServerTool.input
|
|
715
|
+
});
|
|
716
|
+
completedServerTools.set(currentServerTool.id, currentServerTool);
|
|
717
|
+
}
|
|
718
|
+
currentServerTool = null;
|
|
719
|
+
} else if (currentBlockType === "web_fetch_tool_result" || currentBlockType === "web_search_tool_result") {} else if (hasEmittedTextMessageStart && accumulatedContent) yield {
|
|
720
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
721
|
+
messageId,
|
|
722
|
+
model,
|
|
723
|
+
timestamp: Date.now()
|
|
724
|
+
};
|
|
725
|
+
currentBlockType = null;
|
|
726
|
+
} else if (event.type === "message_stop") {
|
|
727
|
+
if (reasoningMessageId && !hasClosedReasoning) {
|
|
728
|
+
hasClosedReasoning = true;
|
|
729
|
+
yield {
|
|
730
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
731
|
+
messageId: reasoningMessageId,
|
|
732
|
+
model,
|
|
733
|
+
timestamp: Date.now()
|
|
734
|
+
};
|
|
735
|
+
yield {
|
|
736
|
+
type: EventType.REASONING_END,
|
|
737
|
+
messageId: reasoningMessageId,
|
|
738
|
+
model,
|
|
739
|
+
timestamp: Date.now()
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
if (!hasEmittedRunFinished) yield {
|
|
743
|
+
type: EventType.RUN_FINISHED,
|
|
744
|
+
runId,
|
|
745
|
+
threadId,
|
|
746
|
+
model,
|
|
747
|
+
timestamp: Date.now(),
|
|
748
|
+
finishReason: "stop"
|
|
749
|
+
};
|
|
750
|
+
} else if (event.type === "message_delta") {
|
|
751
|
+
if (event.delta.stop_reason) {
|
|
752
|
+
hasEmittedRunFinished = true;
|
|
753
|
+
if (reasoningMessageId && !hasClosedReasoning) {
|
|
754
|
+
hasClosedReasoning = true;
|
|
755
|
+
yield {
|
|
756
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
757
|
+
messageId: reasoningMessageId,
|
|
758
|
+
model,
|
|
759
|
+
timestamp: Date.now()
|
|
760
|
+
};
|
|
761
|
+
yield {
|
|
762
|
+
type: EventType.REASONING_END,
|
|
763
|
+
messageId: reasoningMessageId,
|
|
764
|
+
model,
|
|
765
|
+
timestamp: Date.now()
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
switch (event.delta.stop_reason) {
|
|
769
|
+
case "tool_use":
|
|
770
|
+
yield {
|
|
771
|
+
type: EventType.RUN_FINISHED,
|
|
772
|
+
runId,
|
|
773
|
+
threadId,
|
|
774
|
+
model,
|
|
775
|
+
timestamp: Date.now(),
|
|
776
|
+
finishReason: "tool_calls",
|
|
777
|
+
usage: buildAnthropicUsage(event.usage)
|
|
778
|
+
};
|
|
779
|
+
break;
|
|
780
|
+
case "max_tokens":
|
|
781
|
+
if (options.modelOptions?.max_tokens == null) {
|
|
782
|
+
const defaultedMaxTokens = getAnthropicDefaultMaxTokens(model);
|
|
783
|
+
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`, {
|
|
784
|
+
source: "anthropic.processAnthropicStream",
|
|
785
|
+
model,
|
|
786
|
+
defaultedMaxTokens
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
yield {
|
|
790
|
+
type: EventType.RUN_ERROR,
|
|
791
|
+
model,
|
|
792
|
+
timestamp: Date.now(),
|
|
793
|
+
message: "The response was cut off because the maximum token limit was reached.",
|
|
794
|
+
code: "max_tokens",
|
|
795
|
+
error: {
|
|
796
|
+
message: "The response was cut off because the maximum token limit was reached.",
|
|
797
|
+
code: "max_tokens"
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
break;
|
|
801
|
+
default: yield {
|
|
802
|
+
type: EventType.RUN_FINISHED,
|
|
803
|
+
runId,
|
|
804
|
+
threadId,
|
|
805
|
+
model,
|
|
806
|
+
timestamp: Date.now(),
|
|
807
|
+
finishReason: "stop",
|
|
808
|
+
usage: buildAnthropicUsage(event.usage)
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
} catch (error) {
|
|
815
|
+
const err = error;
|
|
816
|
+
const rawEvent = toRunErrorRawEvent(error);
|
|
817
|
+
logger.errors("anthropic.processAnthropicStream fatal", {
|
|
818
|
+
error,
|
|
819
|
+
source: "anthropic.processAnthropicStream"
|
|
820
|
+
});
|
|
821
|
+
yield {
|
|
822
|
+
type: EventType.RUN_ERROR,
|
|
823
|
+
model,
|
|
824
|
+
timestamp: Date.now(),
|
|
825
|
+
message: err.message || "Unknown error occurred",
|
|
826
|
+
code: err.code || String(err.status),
|
|
827
|
+
...rawEvent !== void 0 && { rawEvent },
|
|
828
|
+
error: {
|
|
829
|
+
message: err.message || "Unknown error occurred",
|
|
830
|
+
code: err.code || String(err.status)
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
/**
|
|
837
|
+
* Creates an Anthropic chat adapter with explicit API key.
|
|
838
|
+
* Type resolution happens here at the call site.
|
|
839
|
+
*/
|
|
941
840
|
function createAnthropicChat(model, apiKey, config) {
|
|
942
|
-
|
|
841
|
+
return new AnthropicTextAdapter({
|
|
842
|
+
apiKey,
|
|
843
|
+
...config
|
|
844
|
+
}, model);
|
|
943
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Creates an Anthropic text adapter with automatic API key detection.
|
|
848
|
+
* Type resolution happens here at the call site.
|
|
849
|
+
*/
|
|
944
850
|
function anthropicText(model, config) {
|
|
945
|
-
|
|
946
|
-
return createAnthropicChat(model, apiKey, config);
|
|
851
|
+
return createAnthropicChat(model, getAnthropicApiKeyFromEnv(), config);
|
|
947
852
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
createAnthropicChat
|
|
953
|
-
};
|
|
954
|
-
//# sourceMappingURL=text.js.map
|
|
853
|
+
//#endregion
|
|
854
|
+
export { AnthropicTextAdapter, anthropicText, computeAnthropicBetas, createAnthropicChat };
|
|
855
|
+
|
|
856
|
+
//# sourceMappingURL=text.js.map
|