@opencode/ai 0.0.0-reserved → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +879 -2
- package/dist/cache-policy.d.ts +2 -0
- package/dist/cache-policy.js +129 -0
- package/dist/image-client.d.ts +19 -0
- package/dist/image-client.js +28 -0
- package/dist/image.d.ts +110 -0
- package/dist/image.js +103 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +12 -0
- package/dist/llm.d.ts +252 -0
- package/dist/llm.js +74 -0
- package/dist/protocols/alibaba-chat.d.ts +225 -0
- package/dist/protocols/alibaba-chat.js +75 -0
- package/dist/protocols/alibaba-messages.d.ts +350 -0
- package/dist/protocols/alibaba-messages.js +40 -0
- package/dist/protocols/alibaba-responses.d.ts +255 -0
- package/dist/protocols/alibaba-responses.js +80 -0
- package/dist/protocols/anthropic-messages.d.ts +980 -0
- package/dist/protocols/anthropic-messages.js +1460 -0
- package/dist/protocols/bedrock-converse.d.ts +499 -0
- package/dist/protocols/bedrock-converse.js +643 -0
- package/dist/protocols/bedrock-event-stream.d.ts +9 -0
- package/dist/protocols/bedrock-event-stream.js +97 -0
- package/dist/protocols/gemini.d.ts +285 -0
- package/dist/protocols/gemini.js +671 -0
- package/dist/protocols/google-images.d.ts +30 -0
- package/dist/protocols/google-images.js +181 -0
- package/dist/protocols/index.d.ts +12 -0
- package/dist/protocols/index.js +12 -0
- package/dist/protocols/meta-images.d.ts +26 -0
- package/dist/protocols/meta-images.js +89 -0
- package/dist/protocols/meta-messages.d.ts +356 -0
- package/dist/protocols/meta-messages.js +41 -0
- package/dist/protocols/meta-responses.d.ts +473 -0
- package/dist/protocols/meta-responses.js +173 -0
- package/dist/protocols/mistral-chat.d.ts +540 -0
- package/dist/protocols/mistral-chat.js +624 -0
- package/dist/protocols/open-responses-channel.d.ts +25 -0
- package/dist/protocols/open-responses-channel.js +141 -0
- package/dist/protocols/open-responses-continuation.d.ts +16 -0
- package/dist/protocols/open-responses-continuation.js +170 -0
- package/dist/protocols/open-responses.d.ts +1715 -0
- package/dist/protocols/open-responses.js +1228 -0
- package/dist/protocols/openai-chat.d.ts +860 -0
- package/dist/protocols/openai-chat.js +966 -0
- package/dist/protocols/openai-compatible-chat.d.ts +109 -0
- package/dist/protocols/openai-compatible-chat.js +19 -0
- package/dist/protocols/openai-compatible-responses.d.ts +172 -0
- package/dist/protocols/openai-compatible-responses.js +18 -0
- package/dist/protocols/openai-images.d.ts +32 -0
- package/dist/protocols/openai-images.js +183 -0
- package/dist/protocols/openai-responses.d.ts +1389 -0
- package/dist/protocols/openai-responses.js +260 -0
- package/dist/protocols/shared.d.ts +193 -0
- package/dist/protocols/shared.js +290 -0
- package/dist/protocols/utils/bedrock-auth.d.ts +60 -0
- package/dist/protocols/utils/bedrock-auth.js +85 -0
- package/dist/protocols/utils/bedrock-cache.d.ts +19 -0
- package/dist/protocols/utils/bedrock-cache.js +49 -0
- package/dist/protocols/utils/bedrock-media.d.ts +55 -0
- package/dist/protocols/utils/bedrock-media.js +73 -0
- package/dist/protocols/utils/cache.d.ts +6 -0
- package/dist/protocols/utils/cache.js +5 -0
- package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
- package/dist/protocols/utils/gemini-tool-schema.js +103 -0
- package/dist/protocols/utils/image-input.d.ts +21 -0
- package/dist/protocols/utils/image-input.js +20 -0
- package/dist/protocols/utils/lifecycle.d.ts +24 -0
- package/dist/protocols/utils/lifecycle.js +72 -0
- package/dist/protocols/utils/meta-image.d.ts +2 -0
- package/dist/protocols/utils/meta-image.js +13 -0
- package/dist/protocols/utils/open-responses-options.d.ts +56 -0
- package/dist/protocols/utils/open-responses-options.js +61 -0
- package/dist/protocols/utils/openai-image.d.ts +5 -0
- package/dist/protocols/utils/openai-image.js +18 -0
- package/dist/protocols/utils/openai-options.d.ts +16 -0
- package/dist/protocols/utils/openai-options.js +14 -0
- package/dist/protocols/utils/partial-json-options.d.ts +62 -0
- package/dist/protocols/utils/partial-json-options.js +52 -0
- package/dist/protocols/utils/partial-json.d.ts +8 -0
- package/dist/protocols/utils/partial-json.js +279 -0
- package/dist/protocols/utils/responses-checkpoint.d.ts +4 -0
- package/dist/protocols/utils/responses-checkpoint.js +107 -0
- package/dist/protocols/utils/responses-compaction.d.ts +4 -0
- package/dist/protocols/utils/responses-compaction.js +129 -0
- package/dist/protocols/utils/responses-hosted-tools.d.ts +26 -0
- package/dist/protocols/utils/responses-hosted-tools.js +32 -0
- package/dist/protocols/utils/tool-schema.d.ts +8 -0
- package/dist/protocols/utils/tool-schema.js +54 -0
- package/dist/protocols/utils/tool-stream.d.ts +710 -0
- package/dist/protocols/utils/tool-stream.js +155 -0
- package/dist/protocols/xai-images.d.ts +26 -0
- package/dist/protocols/xai-images.js +106 -0
- package/dist/protocols/xai-responses.d.ts +250 -0
- package/dist/protocols/xai-responses.js +81 -0
- package/dist/protocols/zai-chat.d.ts +202 -0
- package/dist/protocols/zai-chat.js +49 -0
- package/dist/protocols/zai-images.d.ts +22 -0
- package/dist/protocols/zai-images.js +79 -0
- package/dist/protocols/zai-messages.d.ts +344 -0
- package/dist/protocols/zai-messages.js +27 -0
- package/dist/protocols.d.ts +1 -0
- package/dist/protocols.js +1 -0
- package/dist/provider-error.d.ts +15 -0
- package/dist/provider-error.js +125 -0
- package/dist/provider-package.d.ts +15 -0
- package/dist/provider-package.js +1 -0
- package/dist/provider.d.ts +23 -0
- package/dist/provider.js +2 -0
- package/dist/providers/alibaba/chat.d.ts +1 -0
- package/dist/providers/alibaba/chat.js +1 -0
- package/dist/providers/alibaba/messages.d.ts +3 -0
- package/dist/providers/alibaba/messages.js +1 -0
- package/dist/providers/alibaba/responses.d.ts +3 -0
- package/dist/providers/alibaba/responses.js +1 -0
- package/dist/providers/alibaba.d.ts +711 -0
- package/dist/providers/alibaba.js +94 -0
- package/dist/providers/amazon-bedrock/mantle/chat.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/chat.js +1 -0
- package/dist/providers/amazon-bedrock/mantle/responses.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/responses.js +1 -0
- package/dist/providers/amazon-bedrock/mantle.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle.js +1 -0
- package/dist/providers/amazon-bedrock-mantle.d.ts +313 -0
- package/dist/providers/amazon-bedrock-mantle.js +79 -0
- package/dist/providers/amazon-bedrock.d.ts +173 -0
- package/dist/providers/amazon-bedrock.js +41 -0
- package/dist/providers/anthropic-compatible.d.ts +309 -0
- package/dist/providers/anthropic-compatible.js +50 -0
- package/dist/providers/anthropic.d.ts +307 -0
- package/dist/providers/anthropic.js +42 -0
- package/dist/providers/azure/chat.d.ts +2 -0
- package/dist/providers/azure/chat.js +1 -0
- package/dist/providers/azure/responses.d.ts +2 -0
- package/dist/providers/azure/responses.js +1 -0
- package/dist/providers/azure.d.ts +598 -0
- package/dist/providers/azure.js +115 -0
- package/dist/providers/baseten.d.ts +228 -0
- package/dist/providers/baseten.js +38 -0
- package/dist/providers/cerebras.d.ts +227 -0
- package/dist/providers/cerebras.js +40 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts +237 -0
- package/dist/providers/cloudflare-ai-gateway.js +65 -0
- package/dist/providers/cloudflare-workers-ai.d.ts +231 -0
- package/dist/providers/cloudflare-workers-ai.js +51 -0
- package/dist/providers/deepinfra.d.ts +227 -0
- package/dist/providers/deepinfra.js +43 -0
- package/dist/providers/deepseek.d.ts +228 -0
- package/dist/providers/deepseek.js +41 -0
- package/dist/providers/fireworks.d.ts +228 -0
- package/dist/providers/fireworks.js +38 -0
- package/dist/providers/google-vertex/chat.d.ts +2 -0
- package/dist/providers/google-vertex/chat.js +1 -0
- package/dist/providers/google-vertex/gemini.d.ts +2 -0
- package/dist/providers/google-vertex/gemini.js +1 -0
- package/dist/providers/google-vertex/messages.d.ts +2 -0
- package/dist/providers/google-vertex/messages.js +1 -0
- package/dist/providers/google-vertex/responses.d.ts +2 -0
- package/dist/providers/google-vertex/responses.js +1 -0
- package/dist/providers/google-vertex-chat.d.ts +132 -0
- package/dist/providers/google-vertex-chat.js +55 -0
- package/dist/providers/google-vertex-messages.d.ts +306 -0
- package/dist/providers/google-vertex-messages.js +75 -0
- package/dist/providers/google-vertex-responses.d.ts +197 -0
- package/dist/providers/google-vertex-responses.js +56 -0
- package/dist/providers/google-vertex-shared.d.ts +22 -0
- package/dist/providers/google-vertex-shared.js +72 -0
- package/dist/providers/google-vertex.d.ts +119 -0
- package/dist/providers/google-vertex.js +98 -0
- package/dist/providers/google.d.ts +114 -0
- package/dist/providers/google.js +42 -0
- package/dist/providers/groq.d.ts +314 -0
- package/dist/providers/groq.js +81 -0
- package/dist/providers/index.d.ts +31 -0
- package/dist/providers/index.js +31 -0
- package/dist/providers/meta/chat.d.ts +2 -0
- package/dist/providers/meta/chat.js +1 -0
- package/dist/providers/meta/messages.d.ts +2 -0
- package/dist/providers/meta/messages.js +1 -0
- package/dist/providers/meta/responses.d.ts +2 -0
- package/dist/providers/meta/responses.js +1 -0
- package/dist/providers/meta.d.ts +636 -0
- package/dist/providers/meta.js +113 -0
- package/dist/providers/minimax/chat.d.ts +4 -0
- package/dist/providers/minimax/chat.js +8 -0
- package/dist/providers/minimax/messages.d.ts +1 -0
- package/dist/providers/minimax/messages.js +1 -0
- package/dist/providers/minimax/responses.d.ts +4 -0
- package/dist/providers/minimax/responses.js +8 -0
- package/dist/providers/minimax.d.ts +598 -0
- package/dist/providers/minimax.js +89 -0
- package/dist/providers/mistral.d.ts +207 -0
- package/dist/providers/mistral.js +28 -0
- package/dist/providers/moonshot/chat.d.ts +1 -0
- package/dist/providers/moonshot/chat.js +1 -0
- package/dist/providers/moonshot/messages.d.ts +4 -0
- package/dist/providers/moonshot/messages.js +8 -0
- package/dist/providers/moonshot/responses.d.ts +4 -0
- package/dist/providers/moonshot/responses.js +8 -0
- package/dist/providers/moonshot.d.ts +596 -0
- package/dist/providers/moonshot.js +90 -0
- package/dist/providers/open-responses-options.d.ts +6 -0
- package/dist/providers/open-responses-options.js +1 -0
- package/dist/providers/openai/chat.d.ts +2 -0
- package/dist/providers/openai/chat.js +1 -0
- package/dist/providers/openai/responses.d.ts +2 -0
- package/dist/providers/openai/responses.js +1 -0
- package/dist/providers/openai-compatible/responses.d.ts +1 -0
- package/dist/providers/openai-compatible/responses.js +1 -0
- package/dist/providers/openai-compatible-responses.d.ts +195 -0
- package/dist/providers/openai-compatible-responses.js +32 -0
- package/dist/providers/openai-compatible.d.ts +130 -0
- package/dist/providers/openai-compatible.js +33 -0
- package/dist/providers/openai-options.d.ts +26 -0
- package/dist/providers/openai-options.js +41 -0
- package/dist/providers/openai.d.ts +404 -0
- package/dist/providers/openai.js +82 -0
- package/dist/providers/openrouter.d.ts +563 -0
- package/dist/providers/openrouter.js +114 -0
- package/dist/providers/togetherai.d.ts +227 -0
- package/dist/providers/togetherai.js +40 -0
- package/dist/providers/xai.d.ts +163 -0
- package/dist/providers/xai.js +88 -0
- package/dist/providers/zai/chat.d.ts +1 -0
- package/dist/providers/zai/chat.js +1 -0
- package/dist/providers/zai-coding-plan/chat.d.ts +1 -0
- package/dist/providers/zai-coding-plan/chat.js +1 -0
- package/dist/providers/zai-coding-plan/messages.d.ts +4 -0
- package/dist/providers/zai-coding-plan/messages.js +8 -0
- package/dist/providers/zai-coding-plan/responses.d.ts +4 -0
- package/dist/providers/zai-coding-plan/responses.js +8 -0
- package/dist/providers/zai-coding-plan.d.ts +577 -0
- package/dist/providers/zai-coding-plan.js +63 -0
- package/dist/providers/zai.d.ts +149 -0
- package/dist/providers/zai.js +53 -0
- package/dist/providers.d.ts +1 -0
- package/dist/providers.js +1 -0
- package/dist/route/auth-options.d.ts +34 -0
- package/dist/route/auth-options.js +14 -0
- package/dist/route/auth.d.ts +51 -0
- package/dist/route/auth.js +87 -0
- package/dist/route/client.d.ts +430 -0
- package/dist/route/client.js +307 -0
- package/dist/route/endpoint.d.ts +28 -0
- package/dist/route/endpoint.js +21 -0
- package/dist/route/executor.d.ts +27 -0
- package/dist/route/executor.js +183 -0
- package/dist/route/framing.d.ts +29 -0
- package/dist/route/framing.js +14 -0
- package/dist/route/index.d.ts +16 -0
- package/dist/route/index.js +9 -0
- package/dist/route/protocol.d.ts +76 -0
- package/dist/route/protocol.js +16 -0
- package/dist/route/transport/http.d.ts +34 -0
- package/dist/route/transport/http.js +68 -0
- package/dist/route/transport/index.d.ts +41 -0
- package/dist/route/transport/index.js +4 -0
- package/dist/route/transport/websocket-channel.d.ts +57 -0
- package/dist/route/transport/websocket-channel.js +1 -0
- package/dist/route/transport/websocket.d.ts +56 -0
- package/dist/route/transport/websocket.js +370 -0
- package/dist/route.d.ts +1 -0
- package/dist/route.js +1 -0
- package/dist/schema/errors.d.ts +170 -0
- package/dist/schema/errors.js +131 -0
- package/dist/schema/events.d.ts +8123 -0
- package/dist/schema/events.js +550 -0
- package/dist/schema/ids.d.ts +17 -0
- package/dist/schema/ids.js +10 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/messages.d.ts +646 -0
- package/dist/schema/messages.js +301 -0
- package/dist/schema/options.d.ts +159 -0
- package/dist/schema/options.js +189 -0
- package/dist/testing.d.ts +1693 -0
- package/dist/testing.js +129 -0
- package/dist/tool-history.d.ts +2 -0
- package/dist/tool-history.js +84 -0
- package/dist/tool-runtime.d.ts +15 -0
- package/dist/tool-runtime.js +62 -0
- package/dist/tool.d.ts +136 -0
- package/dist/tool.js +66 -0
- package/dist/utils/record.d.ts +2 -0
- package/dist/utils/record.js +2 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.js +12 -0
- package/package.json +43 -10
|
@@ -0,0 +1,1460 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
|
+
import { Tool } from "@opencode/schema/tool";
|
|
4
|
+
import { Route } from "../route/client.js";
|
|
5
|
+
import { Auth } from "../route/auth.js";
|
|
6
|
+
import { Endpoint } from "../route/endpoint.js";
|
|
7
|
+
import { Framing } from "../route/framing.js";
|
|
8
|
+
import { Protocol } from "../route/protocol.js";
|
|
9
|
+
import { Headers } from "effect/unstable/http";
|
|
10
|
+
import { HttpTransport } from "../route/transport/index.js";
|
|
11
|
+
import { AIError, HttpOptions, LLMRequest, LLMEvent, mergeJsonRecords, Usage, } from "../schema/index.js";
|
|
12
|
+
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
13
|
+
import { classifyProviderFailure } from "../provider-error.js";
|
|
14
|
+
import * as Cache from "./utils/cache.js";
|
|
15
|
+
import { Lifecycle } from "./utils/lifecycle.js";
|
|
16
|
+
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
17
|
+
import { ToolStream } from "./utils/tool-stream.js";
|
|
18
|
+
const ADAPTER = "anthropic-messages";
|
|
19
|
+
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
|
|
20
|
+
export const PATH = "/messages";
|
|
21
|
+
export const DEFAULT_MAX_TOKENS = 32_000;
|
|
22
|
+
const SSE_EVENTS = new Set([
|
|
23
|
+
"message",
|
|
24
|
+
"message_start",
|
|
25
|
+
"message_delta",
|
|
26
|
+
"message_stop",
|
|
27
|
+
"content_block_start",
|
|
28
|
+
"content_block_delta",
|
|
29
|
+
"content_block_stop",
|
|
30
|
+
"ping",
|
|
31
|
+
"error",
|
|
32
|
+
]);
|
|
33
|
+
export const framing = Framing.sseEvents(SSE_EVENTS);
|
|
34
|
+
export const ContextManagement = Schema.Struct({
|
|
35
|
+
edits: Schema.Array(Schema.Struct({
|
|
36
|
+
type: Schema.Literal("compact_20260112"),
|
|
37
|
+
trigger: Schema.optional(Schema.Struct({
|
|
38
|
+
type: Schema.Literal("input_tokens"),
|
|
39
|
+
value: Schema.Int.check(Schema.isGreaterThanOrEqualTo(50000)),
|
|
40
|
+
})),
|
|
41
|
+
pauseAfterCompaction: Schema.optional(Schema.Boolean),
|
|
42
|
+
instructions: Schema.optional(Schema.String),
|
|
43
|
+
})),
|
|
44
|
+
});
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Request Body Schema
|
|
47
|
+
// =============================================================================
|
|
48
|
+
const AnthropicCacheControl = Schema.Struct({
|
|
49
|
+
type: Schema.tag("ephemeral"),
|
|
50
|
+
ttl: Schema.optional(Schema.Literals(["5m", "1h"])),
|
|
51
|
+
});
|
|
52
|
+
const AnthropicTextBlock = Schema.Struct({
|
|
53
|
+
type: Schema.tag("text"),
|
|
54
|
+
text: Schema.String,
|
|
55
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
56
|
+
});
|
|
57
|
+
// SDK: Base64ImageSource:201 {type:"base64", media_type:"image/jpeg"|... , data}, URLImageSource:3817 {type:"url", url}, FileImageSource:2350 {type:"file", file_id}
|
|
58
|
+
// SDK: ImageBlockParam:2356 {source: Base64|URL|File, cache_control, transformations:2381 {oversized_image?}}
|
|
59
|
+
const AnthropicBase64ImageSource = Schema.Struct({
|
|
60
|
+
type: Schema.tag("base64"),
|
|
61
|
+
media_type: Schema.String,
|
|
62
|
+
data: Schema.String,
|
|
63
|
+
});
|
|
64
|
+
const AnthropicURLImageSource = Schema.Struct({ type: Schema.tag("url"), url: Schema.String });
|
|
65
|
+
const AnthropicFileImageSource = Schema.Struct({ type: Schema.tag("file"), file_id: Schema.String });
|
|
66
|
+
const AnthropicImageSource = Schema.Union([
|
|
67
|
+
AnthropicBase64ImageSource,
|
|
68
|
+
AnthropicURLImageSource,
|
|
69
|
+
AnthropicFileImageSource,
|
|
70
|
+
]);
|
|
71
|
+
const AnthropicImageTransformations = Schema.Struct({
|
|
72
|
+
oversized_image: Schema.optional(Schema.Literals(["downsize", "error"])),
|
|
73
|
+
});
|
|
74
|
+
const AnthropicImageBlock = Schema.Struct({
|
|
75
|
+
type: Schema.tag("image"),
|
|
76
|
+
source: AnthropicImageSource,
|
|
77
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
78
|
+
transformations: Schema.optional(AnthropicImageTransformations),
|
|
79
|
+
});
|
|
80
|
+
// SDK: Base64PDFSource:209 {type:"base64", media_type:"application/pdf", data}, PlainTextSource:2716 {type:"text", media_type:"text/plain", data},
|
|
81
|
+
// SDK: URLPDFSource:3823 {type:"url", url}, FileDocumentSource:2344 {type:"file", file_id}, ContentBlockSource:2266 {type:"content", content}
|
|
82
|
+
// SDK: DocumentBlockParam:2297 {source: 5-way union, cache_control, citations, context, title}
|
|
83
|
+
const AnthropicBase64PDFSource = Schema.Struct({
|
|
84
|
+
type: Schema.tag("base64"),
|
|
85
|
+
media_type: Schema.Literal("application/pdf"),
|
|
86
|
+
data: Schema.String,
|
|
87
|
+
});
|
|
88
|
+
const AnthropicPlainTextSource = Schema.Struct({
|
|
89
|
+
type: Schema.tag("text"),
|
|
90
|
+
media_type: Schema.Literal("text/plain"),
|
|
91
|
+
data: Schema.String,
|
|
92
|
+
});
|
|
93
|
+
const AnthropicURLPDFSource = Schema.Struct({ type: Schema.tag("url"), url: Schema.String });
|
|
94
|
+
const AnthropicFileDocumentSource = Schema.Struct({ type: Schema.tag("file"), file_id: Schema.String });
|
|
95
|
+
const AnthropicDocumentSource = Schema.Union([
|
|
96
|
+
AnthropicBase64PDFSource,
|
|
97
|
+
AnthropicPlainTextSource,
|
|
98
|
+
AnthropicURLPDFSource,
|
|
99
|
+
AnthropicFileDocumentSource,
|
|
100
|
+
]);
|
|
101
|
+
const AnthropicDocumentBlock = Schema.Struct({
|
|
102
|
+
type: Schema.tag("document"),
|
|
103
|
+
source: AnthropicDocumentSource,
|
|
104
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
105
|
+
title: Schema.optional(Schema.String),
|
|
106
|
+
context: Schema.optional(Schema.String),
|
|
107
|
+
citations: Schema.optional(Schema.Struct({ enabled: Schema.Boolean })),
|
|
108
|
+
});
|
|
109
|
+
const AnthropicThinkingBlock = Schema.Struct({
|
|
110
|
+
type: Schema.tag("thinking"),
|
|
111
|
+
thinking: Schema.String,
|
|
112
|
+
signature: Schema.String,
|
|
113
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
114
|
+
});
|
|
115
|
+
// Safety-filtered thinking arrives as an opaque encrypted `data` payload with
|
|
116
|
+
// no visible text. It must round-trip verbatim so multi-turn thinking + tool
|
|
117
|
+
// use conversations keep their reasoning continuity.
|
|
118
|
+
const AnthropicRedactedThinkingBlock = Schema.Struct({
|
|
119
|
+
type: Schema.tag("redacted_thinking"),
|
|
120
|
+
data: Schema.String,
|
|
121
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
122
|
+
});
|
|
123
|
+
const AnthropicToolUseBlock = Schema.Struct({
|
|
124
|
+
type: Schema.tag("tool_use"),
|
|
125
|
+
id: Schema.String,
|
|
126
|
+
name: Schema.String,
|
|
127
|
+
input: Schema.Unknown,
|
|
128
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
129
|
+
});
|
|
130
|
+
const AnthropicServerToolUseBlock = Schema.Struct({
|
|
131
|
+
type: Schema.tag("server_tool_use"),
|
|
132
|
+
id: Schema.String,
|
|
133
|
+
name: Schema.String,
|
|
134
|
+
input: Schema.Unknown,
|
|
135
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
136
|
+
});
|
|
137
|
+
// Server tool result blocks: web_search_tool_result, code_execution_tool_result,
|
|
138
|
+
// and web_fetch_tool_result. The provider executes the tool and inlines the
|
|
139
|
+
// structured result into the assistant turn — there is no client tool_result
|
|
140
|
+
// round-trip. We round-trip the structured `content` payload as opaque JSON so
|
|
141
|
+
// the next request can echo it back when continuing the conversation.
|
|
142
|
+
const AnthropicServerToolResultType = Schema.Literals([
|
|
143
|
+
"web_search_tool_result",
|
|
144
|
+
"code_execution_tool_result",
|
|
145
|
+
"web_fetch_tool_result",
|
|
146
|
+
]);
|
|
147
|
+
const AnthropicServerToolResultBlock = Schema.Struct({
|
|
148
|
+
type: AnthropicServerToolResultType,
|
|
149
|
+
tool_use_id: Schema.String,
|
|
150
|
+
content: Schema.Unknown,
|
|
151
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
152
|
+
});
|
|
153
|
+
// Anthropic accepts either a plain string or an ordered array of text, image, and
|
|
154
|
+
// document blocks inside `tool_result.content`. The array form keeps media as native
|
|
155
|
+
// model input instead of JSON-stringifying base64 into prompt text.
|
|
156
|
+
const AnthropicToolResultContent = Schema.Union([AnthropicTextBlock, AnthropicImageBlock, AnthropicDocumentBlock]);
|
|
157
|
+
const AnthropicToolResultBlock = Schema.Struct({
|
|
158
|
+
type: Schema.tag("tool_result"),
|
|
159
|
+
tool_use_id: Schema.String,
|
|
160
|
+
content: Schema.Union([Schema.String, Schema.Array(AnthropicToolResultContent)]),
|
|
161
|
+
is_error: Schema.optional(Schema.Boolean),
|
|
162
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
163
|
+
});
|
|
164
|
+
const AnthropicUserBlock = Schema.Union([
|
|
165
|
+
AnthropicTextBlock,
|
|
166
|
+
AnthropicImageBlock,
|
|
167
|
+
AnthropicDocumentBlock,
|
|
168
|
+
AnthropicToolResultBlock,
|
|
169
|
+
]);
|
|
170
|
+
const AnthropicCompactionBlock = Schema.Struct({
|
|
171
|
+
type: Schema.Literal("compaction"),
|
|
172
|
+
content: Schema.NullOr(Schema.String),
|
|
173
|
+
});
|
|
174
|
+
const AnthropicAssistantBlock = Schema.Union([
|
|
175
|
+
AnthropicCompactionBlock,
|
|
176
|
+
AnthropicTextBlock,
|
|
177
|
+
AnthropicThinkingBlock,
|
|
178
|
+
AnthropicRedactedThinkingBlock,
|
|
179
|
+
AnthropicToolUseBlock,
|
|
180
|
+
AnthropicServerToolUseBlock,
|
|
181
|
+
AnthropicServerToolResultBlock,
|
|
182
|
+
]);
|
|
183
|
+
const AnthropicMessage = Schema.Union([
|
|
184
|
+
Schema.Struct({ role: Schema.Literal("user"), content: Schema.Array(AnthropicUserBlock) }),
|
|
185
|
+
Schema.Struct({ role: Schema.Literal("assistant"), content: Schema.Array(AnthropicAssistantBlock) }),
|
|
186
|
+
Schema.Struct({ role: Schema.Literal("system"), content: Schema.Array(AnthropicTextBlock) }),
|
|
187
|
+
]).pipe(Schema.toTaggedUnion("role"));
|
|
188
|
+
const AnthropicTool = Schema.Struct({
|
|
189
|
+
name: Schema.String,
|
|
190
|
+
description: Schema.String,
|
|
191
|
+
input_schema: JsonObject,
|
|
192
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
193
|
+
});
|
|
194
|
+
const AnthropicToolChoice = Schema.Union([
|
|
195
|
+
Schema.Struct({
|
|
196
|
+
type: Schema.Literals(["auto", "any", "none"]),
|
|
197
|
+
disable_parallel_tool_use: Schema.optional(Schema.Boolean),
|
|
198
|
+
}),
|
|
199
|
+
Schema.Struct({
|
|
200
|
+
type: Schema.tag("tool"),
|
|
201
|
+
name: Schema.String,
|
|
202
|
+
disable_parallel_tool_use: Schema.optional(Schema.Boolean),
|
|
203
|
+
}),
|
|
204
|
+
]);
|
|
205
|
+
const AnthropicThinkingBlockBinding = Schema.Struct({
|
|
206
|
+
prefix_mismatch_behavior: Schema.optional(Schema.String),
|
|
207
|
+
});
|
|
208
|
+
const AnthropicThinking = Schema.Union([
|
|
209
|
+
Schema.Struct({
|
|
210
|
+
type: Schema.tag("enabled"),
|
|
211
|
+
budget_tokens: Schema.Number,
|
|
212
|
+
display: Schema.optional(Schema.Literals(["summarized", "omitted"])),
|
|
213
|
+
block_binding: Schema.optional(AnthropicThinkingBlockBinding),
|
|
214
|
+
}),
|
|
215
|
+
Schema.Struct({
|
|
216
|
+
type: Schema.tag("adaptive"),
|
|
217
|
+
display: Schema.optional(Schema.Literals(["summarized", "omitted"])),
|
|
218
|
+
block_binding: Schema.optional(AnthropicThinkingBlockBinding),
|
|
219
|
+
}),
|
|
220
|
+
Schema.Struct({
|
|
221
|
+
type: Schema.tag("disabled"),
|
|
222
|
+
}),
|
|
223
|
+
]);
|
|
224
|
+
// SDK OutputConfig:2684 {effort?: "low"|"medium"|"high"|"xhigh"|"max"|null, format?: JSONOutputFormat:2399}
|
|
225
|
+
const AnthropicJsonOutputFormat = Schema.Struct({
|
|
226
|
+
type: Schema.Literal("json_schema"),
|
|
227
|
+
schema: JsonObject,
|
|
228
|
+
});
|
|
229
|
+
const AnthropicOutputConfig = Schema.Struct({
|
|
230
|
+
effort: Schema.optional(Schema.String),
|
|
231
|
+
format: Schema.optional(Schema.NullOr(AnthropicJsonOutputFormat)),
|
|
232
|
+
});
|
|
233
|
+
// SDK Metadata:2649 {user_id?: string|null}
|
|
234
|
+
const AnthropicMetadata = Schema.Struct({ user_id: optionalNull(Schema.String) });
|
|
235
|
+
// SDK MessageCreateParamsContainer:2596 ContainerParams|string; ContainerParams:2172 {id?, skills?}
|
|
236
|
+
const AnthropicContainer = Schema.Union([
|
|
237
|
+
Schema.String,
|
|
238
|
+
Schema.Struct({
|
|
239
|
+
id: optionalNull(Schema.String),
|
|
240
|
+
skills: optionalNull(Schema.Array(JsonObject)),
|
|
241
|
+
}),
|
|
242
|
+
]);
|
|
243
|
+
const AnthropicBodyFields = {
|
|
244
|
+
context_management: Schema.optional(Schema.Struct({
|
|
245
|
+
edits: Schema.Array(Schema.Struct({
|
|
246
|
+
type: Schema.Literal("compact_20260112"),
|
|
247
|
+
trigger: ContextManagement.fields.edits.value.fields.trigger,
|
|
248
|
+
pause_after_compaction: Schema.optional(Schema.Boolean),
|
|
249
|
+
instructions: Schema.optional(Schema.String),
|
|
250
|
+
})),
|
|
251
|
+
})),
|
|
252
|
+
model: Schema.String,
|
|
253
|
+
system: optionalArray(AnthropicTextBlock),
|
|
254
|
+
messages: Schema.Array(AnthropicMessage),
|
|
255
|
+
tools: optionalArray(AnthropicTool),
|
|
256
|
+
tool_choice: Schema.optional(AnthropicToolChoice),
|
|
257
|
+
stream: Schema.Literal(true),
|
|
258
|
+
max_tokens: Schema.Number,
|
|
259
|
+
temperature: Schema.optional(Schema.Number),
|
|
260
|
+
top_p: Schema.optional(Schema.Number),
|
|
261
|
+
top_k: Schema.optional(Schema.Number),
|
|
262
|
+
stop_sequences: optionalArray(Schema.String),
|
|
263
|
+
thinking: Schema.optional(AnthropicThinking),
|
|
264
|
+
output_config: Schema.optional(AnthropicOutputConfig),
|
|
265
|
+
// SDK top-level passthrough: cache_control:4638, container:4643, inference_geo:4649, metadata:4654, service_tier:4670
|
|
266
|
+
cache_control: Schema.optional(AnthropicCacheControl),
|
|
267
|
+
container: Schema.optional(Schema.NullOr(AnthropicContainer)),
|
|
268
|
+
inference_geo: Schema.optional(Schema.NullOr(Schema.String)),
|
|
269
|
+
metadata: Schema.optional(AnthropicMetadata),
|
|
270
|
+
service_tier: Schema.optional(Schema.Literals(["auto", "standard_only"])),
|
|
271
|
+
};
|
|
272
|
+
export const AnthropicMessagesBody = Schema.Struct(AnthropicBodyFields);
|
|
273
|
+
const AnthropicIterationUsage = Schema.StructWithRest(Schema.Struct({
|
|
274
|
+
input_tokens: optionalNull(Schema.Number),
|
|
275
|
+
output_tokens: Schema.optional(Schema.Number),
|
|
276
|
+
cache_creation_input_tokens: optionalNull(Schema.Number),
|
|
277
|
+
cache_read_input_tokens: optionalNull(Schema.Number),
|
|
278
|
+
server_tool_use: optionalNull(Schema.StructWithRest(Schema.Struct({ web_search_requests: Schema.optional(Schema.Number) }), [
|
|
279
|
+
Schema.Record(Schema.String, Schema.Unknown),
|
|
280
|
+
])),
|
|
281
|
+
output_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({ thinking_tokens: Schema.optional(Schema.Number) }), [
|
|
282
|
+
Schema.Record(Schema.String, Schema.Unknown),
|
|
283
|
+
])),
|
|
284
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
285
|
+
const AnthropicUsage = Schema.StructWithRest(Schema.Struct({
|
|
286
|
+
...AnthropicIterationUsage.schema.fields,
|
|
287
|
+
iterations: Schema.optional(Schema.Array(AnthropicIterationUsage)),
|
|
288
|
+
}), [JsonObject]);
|
|
289
|
+
const AnthropicStreamBlock = Schema.Struct({
|
|
290
|
+
type: Schema.String,
|
|
291
|
+
id: Schema.optional(Schema.String),
|
|
292
|
+
name: Schema.optional(Schema.String),
|
|
293
|
+
text: Schema.optional(Schema.String),
|
|
294
|
+
thinking: Schema.optional(Schema.String),
|
|
295
|
+
signature: Schema.optional(Schema.String),
|
|
296
|
+
// redacted_thinking blocks arrive whole in content_block_start with the
|
|
297
|
+
// encrypted payload in `data`; there is no streaming delta sequence.
|
|
298
|
+
data: Schema.optional(Schema.String),
|
|
299
|
+
input: Schema.optional(Schema.Unknown),
|
|
300
|
+
// *_tool_result blocks arrive whole as content_block_start (no streaming
|
|
301
|
+
// delta) with the structured payload in `content` and the originating
|
|
302
|
+
// server_tool_use id in `tool_use_id`.
|
|
303
|
+
tool_use_id: Schema.optional(Schema.String),
|
|
304
|
+
content: Schema.optional(Schema.Unknown),
|
|
305
|
+
});
|
|
306
|
+
const decodeAnthropicStreamBlock = Schema.decodeUnknownOption(AnthropicStreamBlock);
|
|
307
|
+
const AnthropicStreamDelta = Schema.Struct({
|
|
308
|
+
content: optionalNull(Schema.String),
|
|
309
|
+
type: Schema.optional(Schema.String),
|
|
310
|
+
text: Schema.optional(Schema.String),
|
|
311
|
+
thinking: Schema.optional(Schema.String),
|
|
312
|
+
partial_json: Schema.optional(Schema.String),
|
|
313
|
+
signature: Schema.optional(Schema.String),
|
|
314
|
+
stop_reason: optionalNull(Schema.String),
|
|
315
|
+
stop_sequence: optionalNull(Schema.String),
|
|
316
|
+
});
|
|
317
|
+
const decodeAnthropicStreamDelta = Schema.decodeUnknownOption(AnthropicStreamDelta);
|
|
318
|
+
const AnthropicEvent = Schema.Struct({
|
|
319
|
+
type: Schema.String,
|
|
320
|
+
index: Schema.optional(Schema.Number),
|
|
321
|
+
message: Schema.optional(Schema.Struct({ usage: Schema.optional(AnthropicUsage) })),
|
|
322
|
+
content_block: Schema.optional(Schema.Unknown),
|
|
323
|
+
delta: Schema.optional(Schema.Unknown),
|
|
324
|
+
usage: Schema.optional(AnthropicUsage),
|
|
325
|
+
// `type` and `message` are both required per Anthropic's spec, but
|
|
326
|
+
// OpenAI-compatible proxies and gateway translations occasionally drop one
|
|
327
|
+
// or the other; mark them optional so a partial payload still parses and
|
|
328
|
+
// the parser can fall back to whichever field is populated.
|
|
329
|
+
error: Schema.optional(Schema.Struct({ type: Schema.optional(Schema.String), message: Schema.optional(Schema.String) })),
|
|
330
|
+
});
|
|
331
|
+
const invalid = ProviderShared.invalidRequest;
|
|
332
|
+
// =============================================================================
|
|
333
|
+
// Request Lowering
|
|
334
|
+
// =============================================================================
|
|
335
|
+
// Anthropic accepts at most 4 explicit cache_control breakpoints per request,
|
|
336
|
+
// across `tools`, `system`, and `messages`. Beyond the cap the API returns a
|
|
337
|
+
// 400 — so the lowering layer counts emitted markers and silently drops any
|
|
338
|
+
// that exceed it.
|
|
339
|
+
const ANTHROPIC_BREAKPOINT_CAP = 4;
|
|
340
|
+
const EPHEMERAL_5M = { type: "ephemeral" };
|
|
341
|
+
const EPHEMERAL_1H = { type: "ephemeral", ttl: "1h" };
|
|
342
|
+
const cacheControl = (breakpoints, cache) => {
|
|
343
|
+
if (cache?.type !== "ephemeral" && cache?.type !== "persistent")
|
|
344
|
+
return undefined;
|
|
345
|
+
if (breakpoints.remaining <= 0) {
|
|
346
|
+
breakpoints.dropped += 1;
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
breakpoints.remaining -= 1;
|
|
350
|
+
return Cache.ttlBucket(cache.ttlSeconds) === "1h" ? EPHEMERAL_1H : EPHEMERAL_5M;
|
|
351
|
+
};
|
|
352
|
+
const providerMetadata = (key, metadata) => ({ [key]: metadata });
|
|
353
|
+
const signatureFromMetadata = (metadata, key) => {
|
|
354
|
+
const provider = metadata?.[key];
|
|
355
|
+
if (!ProviderShared.isRecord(provider))
|
|
356
|
+
return undefined;
|
|
357
|
+
return typeof provider.signature === "string" ? provider.signature : undefined;
|
|
358
|
+
};
|
|
359
|
+
const redactedDataFromMetadata = (metadata, key) => {
|
|
360
|
+
const provider = metadata?.[key];
|
|
361
|
+
if (!ProviderShared.isRecord(provider))
|
|
362
|
+
return undefined;
|
|
363
|
+
return typeof provider.redactedData === "string" ? provider.redactedData : undefined;
|
|
364
|
+
};
|
|
365
|
+
const lowerTool = (breakpoints, tool, inputSchema) => ({
|
|
366
|
+
name: tool.name,
|
|
367
|
+
description: tool.description,
|
|
368
|
+
input_schema: inputSchema,
|
|
369
|
+
cache_control: cacheControl(breakpoints, tool.cache),
|
|
370
|
+
});
|
|
371
|
+
const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("Anthropic Messages", toolChoice, {
|
|
372
|
+
auto: () => ({
|
|
373
|
+
type: "auto",
|
|
374
|
+
...(toolChoice.disableParallelToolUse === undefined
|
|
375
|
+
? {}
|
|
376
|
+
: { disable_parallel_tool_use: toolChoice.disableParallelToolUse }),
|
|
377
|
+
}),
|
|
378
|
+
none: () => ({ type: "none" }),
|
|
379
|
+
required: () => ({
|
|
380
|
+
type: "any",
|
|
381
|
+
...(toolChoice.disableParallelToolUse === undefined
|
|
382
|
+
? {}
|
|
383
|
+
: { disable_parallel_tool_use: toolChoice.disableParallelToolUse }),
|
|
384
|
+
}),
|
|
385
|
+
tool: (name) => ({
|
|
386
|
+
type: "tool",
|
|
387
|
+
name,
|
|
388
|
+
...(toolChoice.disableParallelToolUse === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: { disable_parallel_tool_use: toolChoice.disableParallelToolUse }),
|
|
391
|
+
}),
|
|
392
|
+
});
|
|
393
|
+
const scrubToolCallID = (id) => id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
394
|
+
const lowerToolCall = (part) => ({
|
|
395
|
+
type: "tool_use",
|
|
396
|
+
id: scrubToolCallID(part.id),
|
|
397
|
+
name: part.name,
|
|
398
|
+
input: part.input,
|
|
399
|
+
});
|
|
400
|
+
const lowerServerToolCall = (part) => ({
|
|
401
|
+
type: "server_tool_use",
|
|
402
|
+
id: scrubToolCallID(part.id),
|
|
403
|
+
name: part.name,
|
|
404
|
+
input: part.input,
|
|
405
|
+
});
|
|
406
|
+
// Server tool result blocks are typed by name. Anthropic ships three today;
|
|
407
|
+
// extend this list when new server tools land. The block content is the
|
|
408
|
+
// structured payload returned by the provider, which we round-trip as-is.
|
|
409
|
+
const serverToolResultType = (name) => {
|
|
410
|
+
if (name === "web_search")
|
|
411
|
+
return "web_search_tool_result";
|
|
412
|
+
if (name === "code_execution")
|
|
413
|
+
return "code_execution_tool_result";
|
|
414
|
+
if (name === "web_fetch")
|
|
415
|
+
return "web_fetch_tool_result";
|
|
416
|
+
return undefined;
|
|
417
|
+
};
|
|
418
|
+
const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part, providerMetadataKey) {
|
|
419
|
+
const wireType = serverToolResultType(part.name);
|
|
420
|
+
if (!wireType)
|
|
421
|
+
return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`);
|
|
422
|
+
// Prefer the provider-owned replay payload; fall back to the result value for
|
|
423
|
+
// histories constructed directly from provider events.
|
|
424
|
+
const payload = part.providerMetadata?.[providerMetadataKey]?.["result"] ?? part.result.value;
|
|
425
|
+
return {
|
|
426
|
+
type: wireType,
|
|
427
|
+
tool_use_id: scrubToolCallID(part.id),
|
|
428
|
+
content: payload,
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
const fileIdFromMetadata = (metadata) => {
|
|
432
|
+
if (!ProviderShared.isRecord(metadata))
|
|
433
|
+
return undefined;
|
|
434
|
+
const anthropic = metadata.anthropic;
|
|
435
|
+
if (ProviderShared.isRecord(anthropic)) {
|
|
436
|
+
if (typeof anthropic.file_id === "string")
|
|
437
|
+
return anthropic.file_id;
|
|
438
|
+
if (typeof anthropic.fileId === "string")
|
|
439
|
+
return anthropic.fileId;
|
|
440
|
+
}
|
|
441
|
+
if (typeof metadata.file_id === "string")
|
|
442
|
+
return metadata.file_id;
|
|
443
|
+
if (typeof metadata.fileId === "string")
|
|
444
|
+
return metadata.fileId;
|
|
445
|
+
return undefined;
|
|
446
|
+
};
|
|
447
|
+
const transformationsFromMetadata = (metadata) => {
|
|
448
|
+
if (!ProviderShared.isRecord(metadata))
|
|
449
|
+
return undefined;
|
|
450
|
+
const anthropic = ProviderShared.isRecord(metadata.anthropic) ? metadata.anthropic : undefined;
|
|
451
|
+
const raw = anthropic?.transformations ?? metadata.transformations;
|
|
452
|
+
if (ProviderShared.isRecord(raw)) {
|
|
453
|
+
const value = raw.oversized_image;
|
|
454
|
+
if (value === "downsize" || value === "error")
|
|
455
|
+
return { oversized_image: value };
|
|
456
|
+
}
|
|
457
|
+
if (anthropic && (anthropic.oversized_image === "downsize" || anthropic.oversized_image === "error"))
|
|
458
|
+
return { oversized_image: anthropic.oversized_image };
|
|
459
|
+
return undefined;
|
|
460
|
+
};
|
|
461
|
+
const documentTitleFromPart = (part) => {
|
|
462
|
+
if (ProviderShared.isRecord(part.metadata)) {
|
|
463
|
+
const anthropic = part.metadata.anthropic;
|
|
464
|
+
if (ProviderShared.isRecord(anthropic) && typeof anthropic.title === "string")
|
|
465
|
+
return anthropic.title;
|
|
466
|
+
if (typeof part.metadata.title === "string")
|
|
467
|
+
return part.metadata.title;
|
|
468
|
+
}
|
|
469
|
+
if (typeof part.filename === "string" && part.filename.length > 0)
|
|
470
|
+
return part.filename;
|
|
471
|
+
return undefined;
|
|
472
|
+
};
|
|
473
|
+
const documentContextFromMetadata = (metadata) => {
|
|
474
|
+
if (!ProviderShared.isRecord(metadata))
|
|
475
|
+
return undefined;
|
|
476
|
+
const anthropic = ProviderShared.isRecord(metadata.anthropic) ? metadata.anthropic : undefined;
|
|
477
|
+
if (anthropic && typeof anthropic.context === "string")
|
|
478
|
+
return anthropic.context;
|
|
479
|
+
if (typeof metadata.context === "string")
|
|
480
|
+
return metadata.context;
|
|
481
|
+
return undefined;
|
|
482
|
+
};
|
|
483
|
+
const citationsFromMetadata = (metadata) => {
|
|
484
|
+
if (!ProviderShared.isRecord(metadata))
|
|
485
|
+
return undefined;
|
|
486
|
+
const raw = ProviderShared.isRecord(metadata.anthropic)
|
|
487
|
+
? (metadata.anthropic.citations ?? metadata.citations)
|
|
488
|
+
: metadata.citations;
|
|
489
|
+
if (ProviderShared.isRecord(raw) && typeof raw.enabled === "boolean")
|
|
490
|
+
return { enabled: raw.enabled };
|
|
491
|
+
return undefined;
|
|
492
|
+
};
|
|
493
|
+
const isHttpUrl = (value) => /^https?:\/\//i.test(value.trim());
|
|
494
|
+
const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part, breakpoints) {
|
|
495
|
+
const mime = part.mediaType.toLowerCase();
|
|
496
|
+
const cacheControlValue = breakpoints ? cacheControl(breakpoints, part.cache) : undefined;
|
|
497
|
+
const fileId = fileIdFromMetadata(part.metadata);
|
|
498
|
+
// SDK file sources: FileImageSource:2350 / FileDocumentSource:2344 {type:"file", file_id}
|
|
499
|
+
if (fileId) {
|
|
500
|
+
if (mime.startsWith("image/"))
|
|
501
|
+
return {
|
|
502
|
+
type: "image",
|
|
503
|
+
source: { type: "file", file_id: fileId },
|
|
504
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
505
|
+
...(transformationsFromMetadata(part.metadata) === undefined
|
|
506
|
+
? {}
|
|
507
|
+
: { transformations: transformationsFromMetadata(part.metadata) }),
|
|
508
|
+
};
|
|
509
|
+
return {
|
|
510
|
+
type: "document",
|
|
511
|
+
source: { type: "file", file_id: fileId },
|
|
512
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
513
|
+
...(documentTitleFromPart(part) === undefined ? {} : { title: documentTitleFromPart(part) }),
|
|
514
|
+
...(documentContextFromMetadata(part.metadata) === undefined
|
|
515
|
+
? {}
|
|
516
|
+
: { context: documentContextFromMetadata(part.metadata) }),
|
|
517
|
+
...(citationsFromMetadata(part.metadata) === undefined
|
|
518
|
+
? {}
|
|
519
|
+
: { citations: citationsFromMetadata(part.metadata) }),
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const rawString = typeof part.data === "string" ? part.data.trim() : undefined;
|
|
523
|
+
// SDK URL sources: URLImageSource:3817 / URLPDFSource:3823 {type:"url", url}
|
|
524
|
+
if (rawString && isHttpUrl(rawString) && !rawString.startsWith("data:")) {
|
|
525
|
+
if (mime.startsWith("image/"))
|
|
526
|
+
return {
|
|
527
|
+
type: "image",
|
|
528
|
+
source: { type: "url", url: rawString },
|
|
529
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
530
|
+
...(transformationsFromMetadata(part.metadata) === undefined
|
|
531
|
+
? {}
|
|
532
|
+
: { transformations: transformationsFromMetadata(part.metadata) }),
|
|
533
|
+
};
|
|
534
|
+
if (mime === "application/pdf")
|
|
535
|
+
return {
|
|
536
|
+
type: "document",
|
|
537
|
+
source: { type: "url", url: rawString },
|
|
538
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
539
|
+
...(documentTitleFromPart(part) === undefined ? {} : { title: documentTitleFromPart(part) }),
|
|
540
|
+
...(documentContextFromMetadata(part.metadata) === undefined
|
|
541
|
+
? {}
|
|
542
|
+
: { context: documentContextFromMetadata(part.metadata) }),
|
|
543
|
+
...(citationsFromMetadata(part.metadata) === undefined
|
|
544
|
+
? {}
|
|
545
|
+
: { citations: citationsFromMetadata(part.metadata) }),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
// SDK PlainTextSource:2716 {type:"text", media_type:"text/plain", data}
|
|
549
|
+
if (mime === "text/plain") {
|
|
550
|
+
const textData = typeof part.data !== "string"
|
|
551
|
+
? Buffer.from(part.data).toString("utf8")
|
|
552
|
+
: part.data.startsWith("data:")
|
|
553
|
+
? (() => {
|
|
554
|
+
const comma = part.data.indexOf(",");
|
|
555
|
+
const payload = comma >= 0 ? part.data.slice(comma + 1) : part.data;
|
|
556
|
+
return part.data.includes(";base64")
|
|
557
|
+
? Buffer.from(payload, "base64").toString("utf8")
|
|
558
|
+
: decodeURIComponent(payload);
|
|
559
|
+
})()
|
|
560
|
+
: part.data;
|
|
561
|
+
return {
|
|
562
|
+
type: "document",
|
|
563
|
+
source: { type: "text", media_type: "text/plain", data: textData },
|
|
564
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
565
|
+
...(documentTitleFromPart(part) === undefined ? {} : { title: documentTitleFromPart(part) }),
|
|
566
|
+
...(documentContextFromMetadata(part.metadata) === undefined
|
|
567
|
+
? {}
|
|
568
|
+
: { context: documentContextFromMetadata(part.metadata) }),
|
|
569
|
+
...(citationsFromMetadata(part.metadata) === undefined
|
|
570
|
+
? {}
|
|
571
|
+
: { citations: citationsFromMetadata(part.metadata) }),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
575
|
+
if (media.mime === "application/pdf")
|
|
576
|
+
return {
|
|
577
|
+
type: "document",
|
|
578
|
+
source: {
|
|
579
|
+
type: "base64",
|
|
580
|
+
media_type: "application/pdf",
|
|
581
|
+
data: media.base64,
|
|
582
|
+
},
|
|
583
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
584
|
+
...(documentTitleFromPart(part) === undefined ? {} : { title: documentTitleFromPart(part) }),
|
|
585
|
+
...(documentContextFromMetadata(part.metadata) === undefined
|
|
586
|
+
? {}
|
|
587
|
+
: { context: documentContextFromMetadata(part.metadata) }),
|
|
588
|
+
...(citationsFromMetadata(part.metadata) === undefined
|
|
589
|
+
? {}
|
|
590
|
+
: { citations: citationsFromMetadata(part.metadata) }),
|
|
591
|
+
};
|
|
592
|
+
if (!media.mime.startsWith("image/"))
|
|
593
|
+
return yield* invalid(`Anthropic Messages does not support media type ${part.mediaType}`);
|
|
594
|
+
return {
|
|
595
|
+
type: "image",
|
|
596
|
+
source: {
|
|
597
|
+
type: "base64",
|
|
598
|
+
media_type: media.mime,
|
|
599
|
+
data: media.base64,
|
|
600
|
+
},
|
|
601
|
+
...(cacheControlValue === undefined ? {} : { cache_control: cacheControlValue }),
|
|
602
|
+
...(transformationsFromMetadata(part.metadata) === undefined
|
|
603
|
+
? {}
|
|
604
|
+
: { transformations: transformationsFromMetadata(part.metadata) }),
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
// Tool results may carry structured text, images, and documents. Keep media as provider-native
|
|
608
|
+
// content instead of JSON-stringifying base64 into a prompt string.
|
|
609
|
+
const lowerToolResultContentItem = Effect.fnUntraced(function* (item) {
|
|
610
|
+
if (item.type === "text")
|
|
611
|
+
return { type: "text", text: item.text };
|
|
612
|
+
return yield* lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name });
|
|
613
|
+
});
|
|
614
|
+
const lowerToolResultContent = Effect.fnUntraced(function* (part) {
|
|
615
|
+
// Text / json / error results stay as a string for backward compatibility
|
|
616
|
+
// with existing cassettes and provider expectations.
|
|
617
|
+
if (part.result.type !== "content")
|
|
618
|
+
return ProviderShared.toolResultText(part);
|
|
619
|
+
// Preserve the narrowed array element type when compiled through a consumer package.
|
|
620
|
+
const content = part.result.value;
|
|
621
|
+
return yield* Effect.forEach(content, lowerToolResultContentItem);
|
|
622
|
+
});
|
|
623
|
+
const requireThinkingSignature = (request) => {
|
|
624
|
+
if (request.model.compatibility?.requireSignature !== undefined)
|
|
625
|
+
return request.model.compatibility.requireSignature;
|
|
626
|
+
const provider = request.model.provider.toLowerCase();
|
|
627
|
+
const model = request.model.id.toLowerCase();
|
|
628
|
+
const baseURL = (request.model.route.endpoint.baseURL ?? "").toLowerCase();
|
|
629
|
+
if (provider === "kimi-for-coding" ||
|
|
630
|
+
provider === "moonshotai" ||
|
|
631
|
+
provider === "moonshotai-cn" ||
|
|
632
|
+
model.startsWith("kimi-") ||
|
|
633
|
+
baseURL.includes("api.kimi.com/coding") ||
|
|
634
|
+
baseURL.includes("api.moonshot.ai/anthropic") ||
|
|
635
|
+
baseURL.includes("api.moonshot.cn/anthropic"))
|
|
636
|
+
return false;
|
|
637
|
+
if (provider.includes("xiaomi") || model.includes("mimo") || baseURL.includes("xiaomimimo.com"))
|
|
638
|
+
return false;
|
|
639
|
+
return true;
|
|
640
|
+
};
|
|
641
|
+
// Mid-conversation system messages became available with Opus 4.8 and version
|
|
642
|
+
// 5 of the other supported Claude families. Treat later family versions as
|
|
643
|
+
// compatible without assuming that every Anthropic Messages model is Claude.
|
|
644
|
+
const supportsNativeSystemUpdates = (request) => {
|
|
645
|
+
const match = /(?:^|[./])claude-(fable|haiku|mythos|opus|sonnet)-(\d+)(?:[.-](\d+))?/.exec(String(request.model.id).toLowerCase());
|
|
646
|
+
if (!match)
|
|
647
|
+
return false;
|
|
648
|
+
const major = Number(match[2]);
|
|
649
|
+
if (match[1] !== "opus")
|
|
650
|
+
return major >= 5;
|
|
651
|
+
if (major !== 4)
|
|
652
|
+
return major >= 5;
|
|
653
|
+
return match[3] !== undefined && match[3].length <= 2 && Number(match[3]) >= 8;
|
|
654
|
+
};
|
|
655
|
+
const endsInServerToolUse = (message) => {
|
|
656
|
+
const last = message.content.at(-1);
|
|
657
|
+
return message.role === "assistant" && last?.type === "tool-call" && last.providerExecuted === true;
|
|
658
|
+
};
|
|
659
|
+
const canUseNativeSystemUpdate = (request, index) => {
|
|
660
|
+
const previous = request.messages[index - 1];
|
|
661
|
+
const next = request.messages[index + 1];
|
|
662
|
+
// Vertex currently rejects/404s for a system message after local tool results,
|
|
663
|
+
// so fold it into the user tool-result turn across continuations and history.
|
|
664
|
+
if (request.model.route.id === "google-vertex-messages" && previous?.role === "tool")
|
|
665
|
+
return false;
|
|
666
|
+
return (previous !== undefined &&
|
|
667
|
+
previous.role !== "system" &&
|
|
668
|
+
(previous.role === "user" || previous.role === "tool" || endsInServerToolUse(previous)) &&
|
|
669
|
+
next?.role !== "system" &&
|
|
670
|
+
(next === undefined || next.role === "assistant"));
|
|
671
|
+
};
|
|
672
|
+
const splitsLocalToolResults = (messages, index) => {
|
|
673
|
+
const pending = new Set();
|
|
674
|
+
for (const message of messages.slice(0, index)) {
|
|
675
|
+
for (const part of message.content) {
|
|
676
|
+
if (message.role === "assistant" && part.type === "tool-call" && part.providerExecuted !== true)
|
|
677
|
+
pending.add(part.id);
|
|
678
|
+
if (message.role === "tool" && part.type === "tool-result")
|
|
679
|
+
pending.delete(part.id);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return pending.size > 0;
|
|
683
|
+
};
|
|
684
|
+
const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (message, breakpoints) {
|
|
685
|
+
const content = yield* ProviderShared.systemUpdateText("Anthropic Messages", message);
|
|
686
|
+
return {
|
|
687
|
+
role: "system",
|
|
688
|
+
content: content.map((part) => ({
|
|
689
|
+
type: "text",
|
|
690
|
+
text: part.text,
|
|
691
|
+
cache_control: cacheControl(breakpoints, part.cache),
|
|
692
|
+
})),
|
|
693
|
+
};
|
|
694
|
+
});
|
|
695
|
+
const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (request, breakpoints) {
|
|
696
|
+
const messages = [];
|
|
697
|
+
const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
|
|
698
|
+
for (const [index, message] of request.messages.entries()) {
|
|
699
|
+
if (message.role === "system") {
|
|
700
|
+
if (splitsLocalToolResults(request.messages, index))
|
|
701
|
+
return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result");
|
|
702
|
+
if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request, index)) {
|
|
703
|
+
messages.push(yield* lowerNativeSystemUpdate(message, breakpoints));
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
|
|
707
|
+
const block = { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
|
|
708
|
+
const previous = messages.at(-1);
|
|
709
|
+
if (previous?.role === "user")
|
|
710
|
+
messages[messages.length - 1] = { role: "user", content: [...previous.content, block] };
|
|
711
|
+
else
|
|
712
|
+
messages.push({ role: "user", content: [block] });
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (message.role === "user") {
|
|
716
|
+
const content = [];
|
|
717
|
+
for (const part of message.content) {
|
|
718
|
+
if (part.type === "text") {
|
|
719
|
+
if (part.text.trim().length === 0)
|
|
720
|
+
continue;
|
|
721
|
+
content.push({ type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) });
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (part.type === "media") {
|
|
725
|
+
content.push(yield* lowerMedia(part, breakpoints));
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
return yield* ProviderShared.unsupportedContent("Anthropic Messages", "user", ["text", "media"]);
|
|
729
|
+
}
|
|
730
|
+
if (content.length > 0)
|
|
731
|
+
messages.push({ role: "user", content });
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (message.role === "assistant") {
|
|
735
|
+
const content = [];
|
|
736
|
+
for (const part of message.content) {
|
|
737
|
+
if (part.type === "compaction") {
|
|
738
|
+
if (part.provider !== request.model.provider || part.text === undefined)
|
|
739
|
+
return yield* invalid("Compaction state must be replayed to its originating provider and API");
|
|
740
|
+
content.push({ type: "compaction", content: part.text });
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (part.type === "text") {
|
|
744
|
+
if (part.text.trim().length === 0)
|
|
745
|
+
continue;
|
|
746
|
+
content.push({ type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) });
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (part.type === "reasoning") {
|
|
750
|
+
// A signature marks visible thinking; only signature-less parts carrying
|
|
751
|
+
// redactedData round-trip as opaque redacted_thinking blocks.
|
|
752
|
+
const signature = part.encrypted ?? signatureFromMetadata(part.providerMetadata, providerMetadataKey);
|
|
753
|
+
const redactedData = redactedDataFromMetadata(part.providerMetadata, providerMetadataKey);
|
|
754
|
+
if (signature === undefined && redactedData !== undefined) {
|
|
755
|
+
content.push({ type: "redacted_thinking", data: redactedData });
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (typeof signature !== "string" || signature.trim().length === 0) {
|
|
759
|
+
if (part.text.trim().length === 0)
|
|
760
|
+
continue;
|
|
761
|
+
if (!requireThinkingSignature(request)) {
|
|
762
|
+
content.push({ type: "thinking", thinking: part.text, signature: "" });
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
// Without a signature this cannot be a valid thinking block per
|
|
766
|
+
// the SDK ThinkingBlockParam:3217 — demote to text so the
|
|
767
|
+
// conversation remains sendable.
|
|
768
|
+
content.push({
|
|
769
|
+
type: "text",
|
|
770
|
+
text: part.text,
|
|
771
|
+
cache_control: cacheControl(breakpoints, part.cache),
|
|
772
|
+
});
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
content.push({ type: "thinking", thinking: part.text, signature });
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (part.type === "tool-call") {
|
|
779
|
+
content.push(part.providerExecuted ? lowerServerToolCall(part) : lowerToolCall(part));
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (part.type === "tool-result" && part.providerExecuted) {
|
|
783
|
+
content.push(yield* lowerServerToolResult(part, providerMetadataKey));
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
return yield* invalid(`Anthropic Messages assistant messages only support text, reasoning, and tool-call content for now`);
|
|
787
|
+
}
|
|
788
|
+
if (content.length > 0)
|
|
789
|
+
messages.push({ role: "assistant", content });
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const content = [];
|
|
793
|
+
for (const part of message.content) {
|
|
794
|
+
if (!ProviderShared.supportsContent(part, ["tool-result"]))
|
|
795
|
+
return yield* ProviderShared.unsupportedContent("Anthropic Messages", "tool", ["tool-result"]);
|
|
796
|
+
content.push({
|
|
797
|
+
type: "tool_result",
|
|
798
|
+
tool_use_id: scrubToolCallID(part.id),
|
|
799
|
+
content: yield* lowerToolResultContent(part),
|
|
800
|
+
is_error: part.result.type === "error" ? true : undefined,
|
|
801
|
+
cache_control: cacheControl(breakpoints, part.cache),
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
const previous = messages.at(-1);
|
|
805
|
+
if (previous?.role === "user" && previous.content.every((block) => block.type === "tool_result"))
|
|
806
|
+
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] };
|
|
807
|
+
else
|
|
808
|
+
messages.push({ role: "user", content });
|
|
809
|
+
}
|
|
810
|
+
return messages;
|
|
811
|
+
});
|
|
812
|
+
const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (request) {
|
|
813
|
+
const input = request.providerOptions;
|
|
814
|
+
const rawServiceTier = input?.service_tier ??
|
|
815
|
+
input?.serviceTier;
|
|
816
|
+
const service_tier = rawServiceTier === "auto" || rawServiceTier === "standard_only"
|
|
817
|
+
? rawServiceTier
|
|
818
|
+
: undefined;
|
|
819
|
+
const rawMetadata = input?.metadata;
|
|
820
|
+
const metadata = ProviderShared.isRecord(rawMetadata) && (typeof rawMetadata.user_id === "string" || rawMetadata.user_id === null)
|
|
821
|
+
? { user_id: rawMetadata.user_id }
|
|
822
|
+
: undefined;
|
|
823
|
+
const container = typeof input?.container === "string" ||
|
|
824
|
+
ProviderShared.isRecord(input?.container)
|
|
825
|
+
? input.container
|
|
826
|
+
: undefined;
|
|
827
|
+
const rawInferenceGeo = input?.inference_geo ??
|
|
828
|
+
input?.inferenceGeo;
|
|
829
|
+
const inference_geo = typeof rawInferenceGeo === "string" ? rawInferenceGeo : undefined;
|
|
830
|
+
const rawCacheControl = input?.cache_control ??
|
|
831
|
+
input?.cacheControl;
|
|
832
|
+
const cache_control = ProviderShared.isRecord(rawCacheControl) && rawCacheControl.type === "ephemeral"
|
|
833
|
+
? rawCacheControl
|
|
834
|
+
: undefined;
|
|
835
|
+
const rawOutputConfig = input?.output_config ??
|
|
836
|
+
input?.outputConfig;
|
|
837
|
+
const outputConfigEffort = typeof input?.effort === "string"
|
|
838
|
+
? input.effort
|
|
839
|
+
: ProviderShared.isRecord(rawOutputConfig) && typeof rawOutputConfig.effort === "string"
|
|
840
|
+
? rawOutputConfig.effort
|
|
841
|
+
: undefined;
|
|
842
|
+
const outputConfigFormat = ProviderShared.isRecord(rawOutputConfig) && ProviderShared.isRecord(rawOutputConfig.format)
|
|
843
|
+
? rawOutputConfig.format
|
|
844
|
+
: undefined;
|
|
845
|
+
const output_config = outputConfigEffort === undefined && outputConfigFormat === undefined
|
|
846
|
+
? undefined
|
|
847
|
+
: {
|
|
848
|
+
...(outputConfigEffort === undefined ? {} : { effort: outputConfigEffort }),
|
|
849
|
+
...(outputConfigFormat === undefined ? {} : { format: outputConfigFormat }),
|
|
850
|
+
};
|
|
851
|
+
const thinking = yield* resolveThinking(input?.thinking);
|
|
852
|
+
return {
|
|
853
|
+
thinking: applyThinkingBindingDefault(request.model, thinking),
|
|
854
|
+
effort: outputConfigEffort,
|
|
855
|
+
output_config,
|
|
856
|
+
service_tier,
|
|
857
|
+
metadata,
|
|
858
|
+
container,
|
|
859
|
+
inference_geo,
|
|
860
|
+
cache_control,
|
|
861
|
+
};
|
|
862
|
+
});
|
|
863
|
+
const supportsThinkingBlockBinding = (model) => {
|
|
864
|
+
const override = model.compatibility?.supportsThinkingBlockBinding;
|
|
865
|
+
if (override !== undefined)
|
|
866
|
+
return override;
|
|
867
|
+
// Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
|
|
868
|
+
const version = /(?:^|[./])claude-[a-z]+-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/i.exec(model.id)?.groups;
|
|
869
|
+
if (!version)
|
|
870
|
+
return false;
|
|
871
|
+
const major = Number(version.major);
|
|
872
|
+
const minor = Number(version.minor ?? 0);
|
|
873
|
+
return major > 5 || (major === 5 && minor >= 1);
|
|
874
|
+
};
|
|
875
|
+
const applyThinkingBindingDefault = (model, thinking) => {
|
|
876
|
+
if (thinking?.type === "disabled")
|
|
877
|
+
return thinking;
|
|
878
|
+
if (!supportsThinkingBlockBinding(model))
|
|
879
|
+
return thinking;
|
|
880
|
+
return {
|
|
881
|
+
...(thinking ?? { type: "adaptive" }),
|
|
882
|
+
block_binding: {
|
|
883
|
+
prefix_mismatch_behavior: "drop_block",
|
|
884
|
+
...thinking?.block_binding,
|
|
885
|
+
},
|
|
886
|
+
};
|
|
887
|
+
};
|
|
888
|
+
const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function* (input) {
|
|
889
|
+
if (!ProviderShared.isRecord(input))
|
|
890
|
+
return undefined;
|
|
891
|
+
if (input.type === "disabled")
|
|
892
|
+
return { type: "disabled" };
|
|
893
|
+
if (input.type !== "adaptive" && input.type !== "enabled")
|
|
894
|
+
return undefined;
|
|
895
|
+
const block_binding = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Schema.UndefinedOr(AnthropicThinkingBlockBinding)))(input.block_binding);
|
|
896
|
+
const display = input.display === "summarized" || input.display === "omitted"
|
|
897
|
+
? input.display
|
|
898
|
+
: undefined;
|
|
899
|
+
if (input.type === "adaptive")
|
|
900
|
+
return { type: "adaptive", display, block_binding };
|
|
901
|
+
const budget = typeof input.budgetTokens === "number"
|
|
902
|
+
? input.budgetTokens
|
|
903
|
+
: typeof input.budget_tokens === "number"
|
|
904
|
+
? input.budget_tokens
|
|
905
|
+
: undefined;
|
|
906
|
+
if (budget === undefined)
|
|
907
|
+
return yield* ProviderShared.invalidRequest("Anthropic thinking provider option requires budgetTokens");
|
|
908
|
+
return { type: "enabled", budget_tokens: budget, display, block_binding };
|
|
909
|
+
});
|
|
910
|
+
const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request) {
|
|
911
|
+
const management = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Schema.UndefinedOr(ContextManagement)))(request.providerOptions?.contextManagement);
|
|
912
|
+
const generation = request.generation;
|
|
913
|
+
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
|
|
914
|
+
// Allocate the 4-breakpoint budget in invalidation order: tools → system →
|
|
915
|
+
// messages. Tools live highest in the cache hierarchy, so when callers
|
|
916
|
+
// over-mark we keep their tool hints and shed the message-tail ones first.
|
|
917
|
+
const breakpoints = Cache.newBreakpoints(ANTHROPIC_BREAKPOINT_CAP);
|
|
918
|
+
const flattened = ProviderShared.flattenToolRequest(request);
|
|
919
|
+
const tools = flattened.tools.length === 0
|
|
920
|
+
? undefined
|
|
921
|
+
: flattened.tools.map((tool) => lowerTool(breakpoints, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility)));
|
|
922
|
+
// Anthropic rejects tool_choice when tools are absent; "none" is only meaningful with tools present.
|
|
923
|
+
const toolChoice = tools === undefined || !request.toolChoice ? undefined : yield* lowerToolChoice(request.toolChoice);
|
|
924
|
+
const systemParts = request.system.filter((part) => part.text.length > 0);
|
|
925
|
+
const system = systemParts.length === 0
|
|
926
|
+
? undefined
|
|
927
|
+
: systemParts.map((part) => ({
|
|
928
|
+
type: "text",
|
|
929
|
+
text: part.text,
|
|
930
|
+
cache_control: cacheControl(breakpoints, part.cache),
|
|
931
|
+
}));
|
|
932
|
+
const messages = yield* lowerMessages(flattened.request, breakpoints);
|
|
933
|
+
if (breakpoints.dropped > 0) {
|
|
934
|
+
yield* Effect.logWarning(`Anthropic Messages: dropped ${breakpoints.dropped} cache breakpoint(s); the API allows at most ${ANTHROPIC_BREAKPOINT_CAP} per request.`);
|
|
935
|
+
}
|
|
936
|
+
const options = yield* resolveOptions(request);
|
|
937
|
+
const body = {
|
|
938
|
+
model: request.model.id,
|
|
939
|
+
system,
|
|
940
|
+
messages,
|
|
941
|
+
tools,
|
|
942
|
+
tool_choice: toolChoice,
|
|
943
|
+
stream: true,
|
|
944
|
+
max_tokens: generation?.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
945
|
+
temperature: generation?.temperature,
|
|
946
|
+
top_p: generation?.topP,
|
|
947
|
+
top_k: generation?.topK,
|
|
948
|
+
stop_sequences: generation?.stop,
|
|
949
|
+
thinking: options.thinking,
|
|
950
|
+
output_config: options.output_config,
|
|
951
|
+
// top-level passthrough per SDK MessageCreateParamsBase:4638,4643,4649,4654,4670
|
|
952
|
+
cache_control: options.cache_control,
|
|
953
|
+
container: options.container,
|
|
954
|
+
inference_geo: options.inference_geo,
|
|
955
|
+
metadata: options.metadata,
|
|
956
|
+
service_tier: options.service_tier,
|
|
957
|
+
};
|
|
958
|
+
if (!management)
|
|
959
|
+
return body;
|
|
960
|
+
return {
|
|
961
|
+
...body,
|
|
962
|
+
context_management: {
|
|
963
|
+
edits: management.edits.map((edit) => ({
|
|
964
|
+
type: edit.type,
|
|
965
|
+
trigger: edit.trigger,
|
|
966
|
+
pause_after_compaction: edit.pauseAfterCompaction,
|
|
967
|
+
instructions: edit.instructions,
|
|
968
|
+
})),
|
|
969
|
+
},
|
|
970
|
+
};
|
|
971
|
+
});
|
|
972
|
+
// =============================================================================
|
|
973
|
+
// Stream Parsing
|
|
974
|
+
// =============================================================================
|
|
975
|
+
const mapFinishReason = (reason) => {
|
|
976
|
+
if (reason === "end_turn" || reason === "stop_sequence" || reason === "pause_turn")
|
|
977
|
+
return "stop";
|
|
978
|
+
if (reason === "max_tokens" || reason === "model_context_window_exceeded")
|
|
979
|
+
return "length";
|
|
980
|
+
if (reason === "tool_use")
|
|
981
|
+
return "tool-calls";
|
|
982
|
+
if (reason === "refusal")
|
|
983
|
+
return "content-filter";
|
|
984
|
+
return "unknown";
|
|
985
|
+
};
|
|
986
|
+
// Anthropic reports the non-overlapping breakdown natively — its
|
|
987
|
+
// `input_tokens` is the *non-cached* count per the Messages API docs, with
|
|
988
|
+
// cache reads and writes as separate fields. We sum them to derive the
|
|
989
|
+
// inclusive `inputTokens` the rest of the contract expects. Extended
|
|
990
|
+
// thinking tokens are included in `output_tokens`; newer responses also
|
|
991
|
+
// expose that subset through `output_tokens_details.thinking_tokens`.
|
|
992
|
+
const mapUsage = (usage, providerMetadataKey) => {
|
|
993
|
+
if (!usage)
|
|
994
|
+
return undefined;
|
|
995
|
+
const iterations = usage.iterations?.length ? usage.iterations : [usage];
|
|
996
|
+
const last = usage.iterations?.at(-1);
|
|
997
|
+
const nonCached = ProviderShared.sumTokens(...iterations.map((item) => item.input_tokens ?? undefined));
|
|
998
|
+
const cacheRead = ProviderShared.sumTokens(...iterations.map((item) => item.cache_read_input_tokens ?? undefined));
|
|
999
|
+
const cacheWrite = ProviderShared.sumTokens(...iterations.map((item) => item.cache_creation_input_tokens ?? undefined));
|
|
1000
|
+
const inputTokens = ProviderShared.sumTokens(nonCached, cacheRead, cacheWrite);
|
|
1001
|
+
const outputTokens = ProviderShared.sumTokens(...iterations.map((item) => item.output_tokens));
|
|
1002
|
+
return new Usage({
|
|
1003
|
+
inputTokens,
|
|
1004
|
+
outputTokens,
|
|
1005
|
+
contextTokens: last?.type === "message"
|
|
1006
|
+
? ProviderShared.sumTokens(last.input_tokens ?? undefined, last.cache_read_input_tokens ?? undefined, last.cache_creation_input_tokens ?? undefined)
|
|
1007
|
+
: undefined,
|
|
1008
|
+
nonCachedInputTokens: nonCached,
|
|
1009
|
+
cacheReadInputTokens: cacheRead,
|
|
1010
|
+
cacheWriteInputTokens: cacheWrite,
|
|
1011
|
+
reasoningTokens: ProviderShared.sumTokens(...iterations.map((item) => item.output_tokens_details?.thinking_tokens)),
|
|
1012
|
+
totalTokens: ProviderShared.totalTokens(inputTokens, outputTokens, undefined),
|
|
1013
|
+
providerMetadata: { [providerMetadataKey]: usage },
|
|
1014
|
+
});
|
|
1015
|
+
};
|
|
1016
|
+
// Anthropic emits usage on `message_start` and again on `message_delta` — the
|
|
1017
|
+
// final delta carries the authoritative totals. Right-biased merge: each
|
|
1018
|
+
// field prefers `right` when defined, falls back to `left`. `inputTokens` is
|
|
1019
|
+
// recomputed from the merged breakdown so the inclusive total stays
|
|
1020
|
+
// consistent with `nonCached + cacheRead + cacheWrite`.
|
|
1021
|
+
const mergeUsage = (left, right, providerMetadataKey) => {
|
|
1022
|
+
if (!left)
|
|
1023
|
+
return right;
|
|
1024
|
+
if (!right)
|
|
1025
|
+
return left;
|
|
1026
|
+
const nonCachedInputTokens = right.nonCachedInputTokens ?? left.nonCachedInputTokens;
|
|
1027
|
+
const cacheReadInputTokens = right.cacheReadInputTokens ?? left.cacheReadInputTokens;
|
|
1028
|
+
const cacheWriteInputTokens = right.cacheWriteInputTokens ?? left.cacheWriteInputTokens;
|
|
1029
|
+
const inputTokens = ProviderShared.sumTokens(nonCachedInputTokens, cacheReadInputTokens, cacheWriteInputTokens);
|
|
1030
|
+
const outputTokens = right.outputTokens ?? left.outputTokens;
|
|
1031
|
+
const reasoningTokens = right.reasoningTokens ?? left.reasoningTokens;
|
|
1032
|
+
return new Usage({
|
|
1033
|
+
inputTokens,
|
|
1034
|
+
outputTokens,
|
|
1035
|
+
contextTokens: right.contextTokens ?? left.contextTokens,
|
|
1036
|
+
nonCachedInputTokens,
|
|
1037
|
+
cacheReadInputTokens,
|
|
1038
|
+
cacheWriteInputTokens,
|
|
1039
|
+
reasoningTokens,
|
|
1040
|
+
totalTokens: ProviderShared.totalTokens(inputTokens, outputTokens, undefined),
|
|
1041
|
+
providerMetadata: {
|
|
1042
|
+
[providerMetadataKey]: mergeJsonRecords(left.providerMetadata?.[providerMetadataKey], right.providerMetadata?.[providerMetadataKey]) ??
|
|
1043
|
+
{},
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
};
|
|
1047
|
+
// Server tool result blocks come whole in `content_block_start` (no streaming
|
|
1048
|
+
// delta sequence). We convert the payload to a `tool-result` event with
|
|
1049
|
+
// `providerExecuted: true`. The runtime appends it to the assistant message
|
|
1050
|
+
// for round-trip; downstream consumers can inspect `result.value` for the
|
|
1051
|
+
// structured payload.
|
|
1052
|
+
const SERVER_TOOL_RESULT_NAMES = {
|
|
1053
|
+
web_search_tool_result: "web_search",
|
|
1054
|
+
code_execution_tool_result: "code_execution",
|
|
1055
|
+
web_fetch_tool_result: "web_fetch",
|
|
1056
|
+
};
|
|
1057
|
+
const isServerToolResultType = (type) => type in SERVER_TOOL_RESULT_NAMES;
|
|
1058
|
+
const serverToolResultEvent = (block, providerMetadataKey) => {
|
|
1059
|
+
if (!block.type || !isServerToolResultType(block.type))
|
|
1060
|
+
return undefined;
|
|
1061
|
+
const errorPayload = typeof block.content === "object" && block.content !== null && "type" in block.content
|
|
1062
|
+
? String(block.content.type)
|
|
1063
|
+
: "";
|
|
1064
|
+
const isError = errorPayload.endsWith("_tool_result_error");
|
|
1065
|
+
return LLMEvent.toolResult({
|
|
1066
|
+
id: block.tool_use_id ?? "",
|
|
1067
|
+
name: SERVER_TOOL_RESULT_NAMES[block.type],
|
|
1068
|
+
result: isError ? { type: "error", value: block.content } : { type: "json", value: block.content },
|
|
1069
|
+
providerExecuted: true,
|
|
1070
|
+
// The complete payload is irreducible provider replay state: subsequent
|
|
1071
|
+
// stateless requests must round-trip the typed result block verbatim.
|
|
1072
|
+
providerMetadata: providerMetadata(providerMetadataKey, { blockType: block.type, result: block.content }),
|
|
1073
|
+
});
|
|
1074
|
+
};
|
|
1075
|
+
const NO_EVENTS = [];
|
|
1076
|
+
const onMessageStart = (state, event) => {
|
|
1077
|
+
const usage = mapUsage(event.message?.usage, state.providerMetadataKey);
|
|
1078
|
+
return [usage ? { ...state, usage: mergeUsage(state.usage, usage, state.providerMetadataKey) } : state, NO_EVENTS];
|
|
1079
|
+
};
|
|
1080
|
+
const onContentBlockStart = (state, event) => {
|
|
1081
|
+
const block = event.content_block;
|
|
1082
|
+
if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
1083
|
+
if (event.index === undefined || !block.id)
|
|
1084
|
+
return [state, NO_EVENTS];
|
|
1085
|
+
const events = [];
|
|
1086
|
+
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
1087
|
+
return [
|
|
1088
|
+
{
|
|
1089
|
+
...state,
|
|
1090
|
+
lifecycle,
|
|
1091
|
+
tools: ToolStream.start(state.tools, event.index, {
|
|
1092
|
+
id: block.id,
|
|
1093
|
+
name: block.name ?? "",
|
|
1094
|
+
input: block.input !== undefined && (!ProviderShared.isRecord(block.input) || Object.keys(block.input).length > 0)
|
|
1095
|
+
? ProviderShared.encodeJson(block.input)
|
|
1096
|
+
: undefined,
|
|
1097
|
+
providerExecuted: block.type === "server_tool_use",
|
|
1098
|
+
}),
|
|
1099
|
+
},
|
|
1100
|
+
[
|
|
1101
|
+
...events,
|
|
1102
|
+
LLMEvent.toolInputStart({
|
|
1103
|
+
id: block.id,
|
|
1104
|
+
name: block.name ?? "",
|
|
1105
|
+
providerExecuted: block.type === "server_tool_use" ? true : undefined,
|
|
1106
|
+
}),
|
|
1107
|
+
],
|
|
1108
|
+
];
|
|
1109
|
+
}
|
|
1110
|
+
if (block.type === "text" && block.text !== undefined) {
|
|
1111
|
+
const events = [];
|
|
1112
|
+
const id = `text-${event.index ?? 0}`;
|
|
1113
|
+
const lifecycle = Lifecycle.textStart(state.lifecycle, events, id);
|
|
1114
|
+
return [
|
|
1115
|
+
{ ...state, lifecycle: block.text ? Lifecycle.textDelta(lifecycle, events, id, block.text) : lifecycle },
|
|
1116
|
+
events,
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
if (block.type === "thinking" && block.thinking !== undefined) {
|
|
1120
|
+
const events = [];
|
|
1121
|
+
const id = `reasoning-${event.index ?? 0}`;
|
|
1122
|
+
const metadata = block.signature === undefined
|
|
1123
|
+
? undefined
|
|
1124
|
+
: providerMetadata(state.providerMetadataKey, { signature: block.signature });
|
|
1125
|
+
const lifecycle = Lifecycle.reasoningStart(state.lifecycle, events, id, metadata);
|
|
1126
|
+
return [
|
|
1127
|
+
{
|
|
1128
|
+
...state,
|
|
1129
|
+
lifecycle: block.thinking
|
|
1130
|
+
? Lifecycle.reasoningDelta(lifecycle, events, id, block.thinking, metadata)
|
|
1131
|
+
: lifecycle,
|
|
1132
|
+
reasoningSignatures: event.index === undefined || block.signature === undefined
|
|
1133
|
+
? state.reasoningSignatures
|
|
1134
|
+
: { ...state.reasoningSignatures, [event.index]: block.signature },
|
|
1135
|
+
},
|
|
1136
|
+
events,
|
|
1137
|
+
];
|
|
1138
|
+
}
|
|
1139
|
+
// Redacted thinking surfaces as an empty reasoning part carrying the opaque
|
|
1140
|
+
// payload as `redactedData` metadata (same model as Vercel's
|
|
1141
|
+
// @ai-sdk/anthropic). The existing content_block_stop closes the part.
|
|
1142
|
+
if (block.type === "redacted_thinking" && block.data !== undefined) {
|
|
1143
|
+
const events = [];
|
|
1144
|
+
return [
|
|
1145
|
+
{
|
|
1146
|
+
...state,
|
|
1147
|
+
lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${event.index ?? 0}`, providerMetadata(state.providerMetadataKey, { redactedData: block.data })),
|
|
1148
|
+
},
|
|
1149
|
+
events,
|
|
1150
|
+
];
|
|
1151
|
+
}
|
|
1152
|
+
const result = serverToolResultEvent(block, state.providerMetadataKey);
|
|
1153
|
+
if (!result)
|
|
1154
|
+
return [state, NO_EVENTS];
|
|
1155
|
+
const events = [];
|
|
1156
|
+
return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]];
|
|
1157
|
+
};
|
|
1158
|
+
const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (state, event) {
|
|
1159
|
+
const delta = event.delta;
|
|
1160
|
+
if (delta.type === "compaction_delta") {
|
|
1161
|
+
if (event.index === undefined || !(event.index in state.compactions) || delta.content === undefined)
|
|
1162
|
+
return yield* ProviderShared.eventError(ADAPTER, "Compaction delta is missing its block or content");
|
|
1163
|
+
return [
|
|
1164
|
+
{ ...state, compactions: { ...state.compactions, [event.index]: delta.content } },
|
|
1165
|
+
NO_EVENTS,
|
|
1166
|
+
];
|
|
1167
|
+
}
|
|
1168
|
+
if (delta.type === "text_delta" && delta.text) {
|
|
1169
|
+
if (!state.lifecycle.text.has(`text-${event.index ?? 0}`))
|
|
1170
|
+
return [state, NO_EVENTS];
|
|
1171
|
+
const events = [];
|
|
1172
|
+
return [
|
|
1173
|
+
{ ...state, lifecycle: Lifecycle.textDelta(state.lifecycle, events, `text-${event.index ?? 0}`, delta.text) },
|
|
1174
|
+
events,
|
|
1175
|
+
];
|
|
1176
|
+
}
|
|
1177
|
+
if (delta.type === "thinking_delta" && delta.thinking) {
|
|
1178
|
+
if (!state.lifecycle.reasoning.has(`reasoning-${event.index ?? 0}`))
|
|
1179
|
+
return [state, NO_EVENTS];
|
|
1180
|
+
const events = [];
|
|
1181
|
+
return [
|
|
1182
|
+
{
|
|
1183
|
+
...state,
|
|
1184
|
+
lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${event.index ?? 0}`, delta.thinking),
|
|
1185
|
+
},
|
|
1186
|
+
events,
|
|
1187
|
+
];
|
|
1188
|
+
}
|
|
1189
|
+
if (delta.type === "signature_delta" && delta.signature) {
|
|
1190
|
+
const index = event.index ?? 0;
|
|
1191
|
+
if (!state.lifecycle.reasoning.has(`reasoning-${index}`))
|
|
1192
|
+
return [state, NO_EVENTS];
|
|
1193
|
+
return [
|
|
1194
|
+
{
|
|
1195
|
+
...state,
|
|
1196
|
+
reasoningSignatures: { ...state.reasoningSignatures, [index]: delta.signature },
|
|
1197
|
+
},
|
|
1198
|
+
NO_EVENTS,
|
|
1199
|
+
];
|
|
1200
|
+
}
|
|
1201
|
+
if (delta.type === "input_json_delta" && event.index !== undefined) {
|
|
1202
|
+
if (!delta.partial_json)
|
|
1203
|
+
return [state, NO_EVENTS];
|
|
1204
|
+
if (!state.tools[event.index])
|
|
1205
|
+
return [state, NO_EVENTS];
|
|
1206
|
+
const result = ToolStream.appendExisting(ADAPTER, state.tools, event.index, delta.partial_json, "Anthropic Messages tool argument delta is missing its tool call");
|
|
1207
|
+
if (ToolStream.isError(result))
|
|
1208
|
+
return yield* result;
|
|
1209
|
+
const events = [];
|
|
1210
|
+
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
|
|
1211
|
+
events.push(...result.events);
|
|
1212
|
+
return [{ ...state, lifecycle, tools: result.tools }, events];
|
|
1213
|
+
}
|
|
1214
|
+
return [state, NO_EVENTS];
|
|
1215
|
+
});
|
|
1216
|
+
const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(function* (state, event) {
|
|
1217
|
+
if (event.index === undefined)
|
|
1218
|
+
return [state, NO_EVENTS];
|
|
1219
|
+
if (event.index in state.compactions) {
|
|
1220
|
+
const { [event.index]: content, ...compactions } = state.compactions;
|
|
1221
|
+
const events = [];
|
|
1222
|
+
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
1223
|
+
events.push(LLMEvent.compaction({
|
|
1224
|
+
provider: state.provider,
|
|
1225
|
+
text: content,
|
|
1226
|
+
}));
|
|
1227
|
+
return [{ ...state, compactions, lifecycle }, events];
|
|
1228
|
+
}
|
|
1229
|
+
const result = yield* ToolStream.finish(ADAPTER, state.tools, event.index);
|
|
1230
|
+
const events = [];
|
|
1231
|
+
const resultEvents = result.events ?? [];
|
|
1232
|
+
const signature = state.reasoningSignatures[event.index];
|
|
1233
|
+
const lifecycle = resultEvents.length
|
|
1234
|
+
? Lifecycle.stepStart(state.lifecycle, events)
|
|
1235
|
+
: Lifecycle.reasoningEnd(Lifecycle.textEnd(state.lifecycle, events, `text-${event.index}`), events, `reasoning-${event.index}`, signature === undefined ? undefined : providerMetadata(state.providerMetadataKey, { signature }));
|
|
1236
|
+
events.push(...resultEvents);
|
|
1237
|
+
const reasoningSignatures = { ...state.reasoningSignatures };
|
|
1238
|
+
delete reasoningSignatures[event.index];
|
|
1239
|
+
return [{ ...state, lifecycle, tools: result.tools, reasoningSignatures }, events];
|
|
1240
|
+
});
|
|
1241
|
+
const onMessageDelta = (state, event) => {
|
|
1242
|
+
const usage = mergeUsage(state.usage, mapUsage(event.usage, state.providerMetadataKey), state.providerMetadataKey);
|
|
1243
|
+
const pendingFinish = (() => {
|
|
1244
|
+
const stopReason = event.delta?.stop_reason;
|
|
1245
|
+
if (stopReason === null || stopReason === undefined)
|
|
1246
|
+
return state.pendingFinish;
|
|
1247
|
+
const stopSequence = event.delta?.stop_sequence;
|
|
1248
|
+
const finishMetadata = stopSequence === null || stopSequence === undefined
|
|
1249
|
+
? state.pendingFinish?.providerMetadata
|
|
1250
|
+
: providerMetadata(state.providerMetadataKey, { stopSequence });
|
|
1251
|
+
return {
|
|
1252
|
+
reason: {
|
|
1253
|
+
normalized: mapFinishReason(stopReason),
|
|
1254
|
+
raw: stopReason,
|
|
1255
|
+
},
|
|
1256
|
+
providerMetadata: finishMetadata,
|
|
1257
|
+
};
|
|
1258
|
+
})();
|
|
1259
|
+
return [
|
|
1260
|
+
{
|
|
1261
|
+
...state,
|
|
1262
|
+
usage,
|
|
1263
|
+
pendingFinish,
|
|
1264
|
+
},
|
|
1265
|
+
NO_EVENTS,
|
|
1266
|
+
];
|
|
1267
|
+
};
|
|
1268
|
+
const onMessageStop = Effect.fn("AnthropicMessages.onMessageStop")(function* (state) {
|
|
1269
|
+
if (Object.keys(state.compactions).length)
|
|
1270
|
+
return yield* ProviderShared.eventError(ADAPTER, "Response ended with an incomplete compaction block");
|
|
1271
|
+
const result = yield* ToolStream.finishAll(ADAPTER, state.tools);
|
|
1272
|
+
const events = [];
|
|
1273
|
+
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
|
|
1274
|
+
events.push(...result.events);
|
|
1275
|
+
const closed = Object.entries(state.reasoningSignatures).reduce((current, [index, signature]) => Lifecycle.reasoningEnd(current, events, `reasoning-${index}`, providerMetadata(state.providerMetadataKey, { signature })), lifecycle);
|
|
1276
|
+
const finished = Lifecycle.finish(closed, events, {
|
|
1277
|
+
reason: state.pendingFinish?.reason ?? {
|
|
1278
|
+
normalized: "unknown",
|
|
1279
|
+
raw: undefined,
|
|
1280
|
+
},
|
|
1281
|
+
usage: state.usage,
|
|
1282
|
+
providerMetadata: state.pendingFinish?.providerMetadata,
|
|
1283
|
+
});
|
|
1284
|
+
return [{ ...state, lifecycle: finished, tools: result.tools }, events];
|
|
1285
|
+
});
|
|
1286
|
+
// Prefix `error.type` so overloads, rate limits, and quota errors are visible
|
|
1287
|
+
// even when the provider message is generic or empty.
|
|
1288
|
+
const providerErrorMessage = (event) => {
|
|
1289
|
+
const type = event.error?.type;
|
|
1290
|
+
const message = event.error?.message;
|
|
1291
|
+
if (type && message)
|
|
1292
|
+
return `${type}: ${message}`;
|
|
1293
|
+
return message || type || "Anthropic Messages stream error";
|
|
1294
|
+
};
|
|
1295
|
+
const onError = (event) => {
|
|
1296
|
+
const message = providerErrorMessage(event);
|
|
1297
|
+
const body = ProviderShared.encodeJson(event);
|
|
1298
|
+
return Effect.fail(new AIError({
|
|
1299
|
+
reason: classifyProviderFailure({ message, rawBody: body }),
|
|
1300
|
+
}));
|
|
1301
|
+
};
|
|
1302
|
+
const STREAM_BLOCK_TYPES = new Set([
|
|
1303
|
+
"compaction",
|
|
1304
|
+
"text",
|
|
1305
|
+
"thinking",
|
|
1306
|
+
"redacted_thinking",
|
|
1307
|
+
"tool_use",
|
|
1308
|
+
"server_tool_use",
|
|
1309
|
+
]);
|
|
1310
|
+
const STREAM_DELTA_TYPES = new Set([
|
|
1311
|
+
"compaction_delta",
|
|
1312
|
+
"text_delta",
|
|
1313
|
+
"thinking_delta",
|
|
1314
|
+
"signature_delta",
|
|
1315
|
+
"input_json_delta",
|
|
1316
|
+
]);
|
|
1317
|
+
const invalidStreamEvent = (event) => Effect.fail(ProviderShared.eventError(ADAPTER, "Invalid anthropic/anthropic-messages stream event", ProviderShared.encodeJson(event)));
|
|
1318
|
+
const step = (state, event) => {
|
|
1319
|
+
if (!SSE_EVENTS.has(event.type))
|
|
1320
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1321
|
+
if (event.type !== "content_block_start" &&
|
|
1322
|
+
event.content_block !== undefined &&
|
|
1323
|
+
Option.isNone(decodeAnthropicStreamBlock(event.content_block)))
|
|
1324
|
+
return invalidStreamEvent(event);
|
|
1325
|
+
if (event.type !== "content_block_delta" &&
|
|
1326
|
+
event.delta !== undefined &&
|
|
1327
|
+
Option.isNone(decodeAnthropicStreamDelta(event.delta)))
|
|
1328
|
+
return invalidStreamEvent(event);
|
|
1329
|
+
if (event.type === "message_start")
|
|
1330
|
+
return Effect.succeed(onMessageStart(state, event));
|
|
1331
|
+
if (event.type === "content_block_start") {
|
|
1332
|
+
if (!ProviderShared.isRecord(event.content_block) || typeof event.content_block.type !== "string")
|
|
1333
|
+
return invalidStreamEvent(event);
|
|
1334
|
+
if (event.content_block.type === "compaction") {
|
|
1335
|
+
const decoded = Schema.decodeUnknownOption(AnthropicCompactionBlock)(event.content_block);
|
|
1336
|
+
if (event.index === undefined || Option.isNone(decoded))
|
|
1337
|
+
return invalidStreamEvent(event);
|
|
1338
|
+
return Effect.succeed([
|
|
1339
|
+
{ ...state, compactions: { ...state.compactions, [event.index]: decoded.value.content } },
|
|
1340
|
+
NO_EVENTS,
|
|
1341
|
+
]);
|
|
1342
|
+
}
|
|
1343
|
+
if (!STREAM_BLOCK_TYPES.has(event.content_block.type) && !isServerToolResultType(event.content_block.type))
|
|
1344
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1345
|
+
const decoded = decodeAnthropicStreamBlock(event.content_block);
|
|
1346
|
+
if (Option.isNone(decoded))
|
|
1347
|
+
return invalidStreamEvent(event);
|
|
1348
|
+
const block = decoded.value;
|
|
1349
|
+
if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
1350
|
+
if (event.index === undefined)
|
|
1351
|
+
return Effect.fail(ProviderShared.eventError(ADAPTER, `Anthropic ${block.type} missing index`));
|
|
1352
|
+
if (!block.id)
|
|
1353
|
+
return Effect.fail(ProviderShared.eventError(ADAPTER, `Anthropic tool_use missing id at index ${event.index}`));
|
|
1354
|
+
}
|
|
1355
|
+
return Effect.succeed(onContentBlockStart(state, { ...event, content_block: block }));
|
|
1356
|
+
}
|
|
1357
|
+
if (event.type === "content_block_delta") {
|
|
1358
|
+
if (!ProviderShared.isRecord(event.delta))
|
|
1359
|
+
return invalidStreamEvent(event);
|
|
1360
|
+
if (typeof event.delta.type === "string" && !STREAM_DELTA_TYPES.has(event.delta.type))
|
|
1361
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1362
|
+
const decoded = decodeAnthropicStreamDelta(event.delta);
|
|
1363
|
+
if (Option.isNone(decoded))
|
|
1364
|
+
return invalidStreamEvent(event);
|
|
1365
|
+
return onContentBlockDelta(state, { ...event, delta: decoded.value });
|
|
1366
|
+
}
|
|
1367
|
+
if (event.type === "content_block_stop")
|
|
1368
|
+
return onContentBlockStop(state, event);
|
|
1369
|
+
if (event.type === "message_delta") {
|
|
1370
|
+
const decoded = decodeAnthropicStreamDelta(event.delta);
|
|
1371
|
+
if (Option.isNone(decoded))
|
|
1372
|
+
return invalidStreamEvent(event);
|
|
1373
|
+
return Effect.succeed(onMessageDelta(state, { ...event, delta: decoded.value }));
|
|
1374
|
+
}
|
|
1375
|
+
if (event.type === "message_stop")
|
|
1376
|
+
return onMessageStop(state);
|
|
1377
|
+
if (event.type === "error")
|
|
1378
|
+
return onError(event);
|
|
1379
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1380
|
+
};
|
|
1381
|
+
// =============================================================================
|
|
1382
|
+
// Protocol And Anthropic Route
|
|
1383
|
+
// =============================================================================
|
|
1384
|
+
/**
|
|
1385
|
+
* The Anthropic Messages protocol — request body construction, body schema,
|
|
1386
|
+
* and the streaming-event state machine shared by Anthropic-compatible and
|
|
1387
|
+
* Vertex-hosted Messages routes.
|
|
1388
|
+
*/
|
|
1389
|
+
export const protocol = Protocol.make({
|
|
1390
|
+
id: ADAPTER,
|
|
1391
|
+
body: {
|
|
1392
|
+
schema: AnthropicMessagesBody,
|
|
1393
|
+
from: fromRequest,
|
|
1394
|
+
},
|
|
1395
|
+
stream: {
|
|
1396
|
+
event: Protocol.jsonEvent(AnthropicEvent),
|
|
1397
|
+
initial: (request) => ({
|
|
1398
|
+
provider: request.model.provider,
|
|
1399
|
+
compactions: {},
|
|
1400
|
+
providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider),
|
|
1401
|
+
tools: ToolStream.empty(),
|
|
1402
|
+
reasoningSignatures: {},
|
|
1403
|
+
lifecycle: Lifecycle.initial(),
|
|
1404
|
+
}),
|
|
1405
|
+
step,
|
|
1406
|
+
},
|
|
1407
|
+
});
|
|
1408
|
+
export const transport = () => {
|
|
1409
|
+
const http = HttpTransport.httpJson({ framing });
|
|
1410
|
+
return {
|
|
1411
|
+
...http,
|
|
1412
|
+
prepare: (input) => {
|
|
1413
|
+
const requiredBetas = requiredBetaHeaders(input.body);
|
|
1414
|
+
if (requiredBetas.length === 0)
|
|
1415
|
+
return http.prepare(input);
|
|
1416
|
+
const headers = Headers.fromInput(input.request.http?.headers);
|
|
1417
|
+
const existingBetas = (headers["anthropic-beta"] ?? "")
|
|
1418
|
+
.split(",")
|
|
1419
|
+
.map((item) => item.trim())
|
|
1420
|
+
.filter(Boolean);
|
|
1421
|
+
const betas = new Set([...existingBetas, ...requiredBetas]);
|
|
1422
|
+
return http.prepare({
|
|
1423
|
+
...input,
|
|
1424
|
+
request: LLMRequest.update(input.request, {
|
|
1425
|
+
http: new HttpOptions({
|
|
1426
|
+
...input.request.http,
|
|
1427
|
+
headers: { ...headers, "anthropic-beta": [...betas].join(",") },
|
|
1428
|
+
}),
|
|
1429
|
+
}),
|
|
1430
|
+
});
|
|
1431
|
+
},
|
|
1432
|
+
};
|
|
1433
|
+
};
|
|
1434
|
+
function requiredBetaHeaders(body) {
|
|
1435
|
+
// Always request interleaved thinking. The API accepts the header on any
|
|
1436
|
+
// model and ignores it where unsupported, while manual-thinking models need
|
|
1437
|
+
// it for thinking between tool calls.
|
|
1438
|
+
const betas = ["interleaved-thinking-2025-05-14"];
|
|
1439
|
+
const requestsCompaction = (body.context_management?.edits.length ?? 0) > 0;
|
|
1440
|
+
const replaysCompaction = body.messages.some((message) => message.content.some((block) => block.type === "compaction"));
|
|
1441
|
+
if (requestsCompaction || replaysCompaction)
|
|
1442
|
+
betas.push("compact-2026-01-12");
|
|
1443
|
+
const thinking = body.thinking;
|
|
1444
|
+
if (thinking && thinking.type !== "disabled" && thinking.block_binding)
|
|
1445
|
+
betas.push("thinking-binding-controls-2026-08-01");
|
|
1446
|
+
return betas;
|
|
1447
|
+
}
|
|
1448
|
+
export const route = Route.make({
|
|
1449
|
+
id: ADAPTER,
|
|
1450
|
+
provider: "anthropic",
|
|
1451
|
+
providerMetadataKey: "anthropic",
|
|
1452
|
+
protocol,
|
|
1453
|
+
endpoint: Endpoint.path((input) => (input.request.model.provider === "anthropic" ? `${PATH}?beta=true` : PATH), {
|
|
1454
|
+
baseURL: DEFAULT_BASE_URL,
|
|
1455
|
+
}),
|
|
1456
|
+
auth: Auth.none,
|
|
1457
|
+
transport: transport(),
|
|
1458
|
+
headers: () => ({ "anthropic-version": "2023-06-01" }),
|
|
1459
|
+
});
|
|
1460
|
+
export * as AnthropicMessages from "./anthropic-messages.js";
|