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