@opencode/ai 2.0.21 → 2.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +61 -1
  2. package/dist/cache-policy.js +10 -2
  3. package/dist/llm.d.ts +4 -0
  4. package/dist/promise.d.ts +8 -0
  5. package/dist/protocols/alibaba-chat.d.ts +7 -3
  6. package/dist/protocols/alibaba-chat.js +2 -1
  7. package/dist/protocols/alibaba-responses.d.ts +16 -12
  8. package/dist/protocols/alibaba-responses.js +3 -5
  9. package/dist/protocols/anthropic-messages.js +58 -51
  10. package/dist/protocols/bedrock-converse.js +33 -4
  11. package/dist/protocols/cartesia-speech.js +3 -3
  12. package/dist/protocols/cohere-chat.d.ts +178 -0
  13. package/dist/protocols/cohere-chat.js +299 -0
  14. package/dist/protocols/elevenlabs-speech.js +3 -3
  15. package/dist/protocols/gemini.d.ts +2 -2
  16. package/dist/protocols/gemini.js +3 -3
  17. package/dist/protocols/google-interactions.d.ts +236 -0
  18. package/dist/protocols/google-interactions.js +478 -0
  19. package/dist/protocols/google-speech.js +1 -1
  20. package/dist/protocols/google-transcription.js +1 -1
  21. package/dist/protocols/index.d.ts +2 -0
  22. package/dist/protocols/index.js +2 -0
  23. package/dist/protocols/meta-responses.d.ts +31 -58
  24. package/dist/protocols/meta-responses.js +8 -27
  25. package/dist/protocols/mistral-chat.d.ts +24 -9
  26. package/dist/protocols/mistral-chat.js +22 -10
  27. package/dist/protocols/open-responses.d.ts +410 -91
  28. package/dist/protocols/open-responses.js +25 -15
  29. package/dist/protocols/openai-chat.d.ts +409 -5
  30. package/dist/protocols/openai-chat.js +27 -26
  31. package/dist/protocols/openai-compatible-chat.d.ts +1 -1
  32. package/dist/protocols/openai-compatible-responses.d.ts +16 -4
  33. package/dist/protocols/openai-images.js +2 -2
  34. package/dist/protocols/openai-responses.d.ts +87 -223
  35. package/dist/protocols/openai-responses.js +10 -16
  36. package/dist/protocols/openai-speech.js +1 -1
  37. package/dist/protocols/openai-transcription.js +1 -1
  38. package/dist/protocols/shared.js +2 -2
  39. package/dist/protocols/stability-images.js +1 -1
  40. package/dist/protocols/utils/bedrock-media.js +2 -2
  41. package/dist/protocols/utils/cache.d.ts +5 -0
  42. package/dist/protocols/utils/cache.js +9 -1
  43. package/dist/protocols/utils/responses-checkpoint.js +2 -2
  44. package/dist/protocols/utils/responses-hosted-tools.js +1 -1
  45. package/dist/protocols/utils/tool-stream.d.ts +12 -0
  46. package/dist/protocols/utils/tool-stream.js +15 -7
  47. package/dist/protocols/venice-chat.d.ts +280 -0
  48. package/dist/protocols/venice-chat.js +283 -0
  49. package/dist/protocols/xai-responses.d.ts +18 -21
  50. package/dist/protocols/xai-responses.js +2 -8
  51. package/dist/protocols/zai-chat.d.ts +7 -3
  52. package/dist/protocols/zai-chat.js +6 -3
  53. package/dist/provider-error.js +27 -7
  54. package/dist/providers/alibaba.d.ts +17 -13
  55. package/dist/providers/amazon-bedrock/mantle/messages.d.ts +2 -0
  56. package/dist/providers/amazon-bedrock/mantle/messages.js +1 -0
  57. package/dist/providers/amazon-bedrock-mantle.d.ts +301 -9
  58. package/dist/providers/amazon-bedrock-mantle.js +28 -3
  59. package/dist/providers/azure.d.ts +13 -61
  60. package/dist/providers/baseten.d.ts +2 -2
  61. package/dist/providers/cerebras.d.ts +2 -2
  62. package/dist/providers/cloudflare-ai-gateway.d.ts +14 -62
  63. package/dist/providers/cloudflare-workers-ai.d.ts +2 -2
  64. package/dist/providers/cohere/chat.d.ts +4 -0
  65. package/dist/providers/cohere/chat.js +8 -0
  66. package/dist/providers/cohere.d.ts +395 -0
  67. package/dist/providers/cohere.js +48 -0
  68. package/dist/providers/deepinfra.d.ts +2 -2
  69. package/dist/providers/deepseek.d.ts +2 -2
  70. package/dist/providers/digitalocean.d.ts +469 -0
  71. package/dist/providers/digitalocean.js +53 -0
  72. package/dist/providers/fireworks.d.ts +2 -2
  73. package/dist/providers/google/interactions.d.ts +7 -0
  74. package/dist/providers/google/interactions.js +8 -0
  75. package/dist/providers/google-vertex-chat.d.ts +1 -1
  76. package/dist/providers/google-vertex-messages.js +1 -1
  77. package/dist/providers/google-vertex-responses.d.ts +16 -4
  78. package/dist/providers/google-vertex.d.ts +1 -1
  79. package/dist/providers/google.d.ts +95 -4
  80. package/dist/providers/google.js +8 -1
  81. package/dist/providers/groq.d.ts +8 -4
  82. package/dist/providers/groq.js +1 -1
  83. package/dist/providers/index.d.ts +3 -0
  84. package/dist/providers/index.js +3 -0
  85. package/dist/providers/meta.d.ts +15 -28
  86. package/dist/providers/minimax.d.ts +17 -5
  87. package/dist/providers/mistral.d.ts +10 -4
  88. package/dist/providers/moonshot.d.ts +17 -5
  89. package/dist/providers/openai-compatible-responses.d.ts +16 -4
  90. package/dist/providers/openai-compatible.d.ts +1 -1
  91. package/dist/providers/openai-options.d.ts +1 -1
  92. package/dist/providers/openai.d.ts +7 -31
  93. package/dist/providers/openrouter.d.ts +9 -5
  94. package/dist/providers/openrouter.js +1 -13
  95. package/dist/providers/togetherai.d.ts +2 -2
  96. package/dist/providers/venice.d.ts +183 -0
  97. package/dist/providers/venice.js +38 -0
  98. package/dist/providers/xai.d.ts +1 -1
  99. package/dist/providers/zai-coding-plan.d.ts +17 -5
  100. package/dist/providers/zai.d.ts +1 -1
  101. package/dist/route/client.d.ts +4 -0
  102. package/dist/route/client.js +2 -5
  103. package/dist/route/executor.js +13 -6
  104. package/dist/route/framing.js +23 -23
  105. package/dist/route/transport/http.d.ts +2 -1
  106. package/dist/route/transport/http.js +28 -6
  107. package/dist/schema/events.d.ts +176 -0
  108. package/dist/schema/events.js +4 -0
  109. package/dist/schema/options.d.ts +11 -0
  110. package/dist/schema/options.js +15 -2
  111. package/dist/testing.d.ts +32 -0
  112. package/dist/tool-history.js +17 -3
  113. package/package.json +3 -3
package/README.md CHANGED
@@ -27,6 +27,31 @@ Run `LLM.stream(...)` instead of `generate` when you want incremental `LLMEvent`
27
27
  `LLM.request(...)`. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses,
28
28
  Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
29
29
 
30
+ ### Google Interactions
31
+
32
+ `Google.configure({ apiKey }).interactions(modelID)` selects the Interactions API; `.model(modelID)` still selects
33
+ GenerateContent. The package entrypoint is `@opencode/ai/providers/google/interactions`.
34
+
35
+ ```ts
36
+ const model = Google.configure({ apiKey }).interactions("gemini-3.8-flash")
37
+ const response = yield* LLM.generate({
38
+ model,
39
+ prompt: "Say hello.",
40
+ providerOptions: { thinkingLevel: "low", thinkingSummaries: "auto", store: true },
41
+ })
42
+ ```
43
+
44
+ Interactions supports text output, streamed function calls, native tool results, thought signatures, and multimodal
45
+ input. Full-history replay is the default (`store: false`); implicit caching works without retained interactions.
46
+ For server-side continuation, set `store: true` on the predecessor, read `interactionId` from the final event's
47
+ `providerMetadata.google`, and pass `previousInteractionId` on the next request with **only new messages**. Repeat
48
+ the system instructions and tool declarations on each request. Set `store: true` on each response you intend to
49
+ continue from. The package does not automatically select or persist continuation IDs.
50
+
51
+ Raw usage is preserved in `usage.providerMetadata.google`. `inputTokens` follows Google's top-level accounting;
52
+ `contextTokens` uses its full `raw_prompt_token` count when supplied. These can differ substantially with server-side
53
+ continuation. Explicit caches, hosted tools, and generated media are not supported by this initial protocol.
54
+
30
55
  The same configured facade names image, video, speech, and transcription models. `Image.generate` resolves the
31
56
  provider's image route from the model and returns `Media.Asset`s with lazily decoded bytes:
32
57
 
@@ -81,6 +106,41 @@ for await (const event of ai.llm.stream(ai.llm.request(input))) {
81
106
  await ai.dispose()
82
107
  ```
83
108
 
109
+ ## Venice AI
110
+
111
+ `Venice` provides native Chat Completions with streaming tools and reasoning. `model` and `chat`
112
+ select the same API; credentials default to `VENICE_API_KEY`.
113
+
114
+ ```ts
115
+ import { LLM } from "@opencode/ai"
116
+ import { Venice } from "@opencode/ai/providers"
117
+ import { Effect } from "effect"
118
+
119
+ const program = Effect.gen(function* () {
120
+ const response = yield* LLM.generate({
121
+ model: Venice.configure({ apiKey: process.env.VENICE_API_KEY }).chat("qwen3-6-27b"),
122
+ prompt: "Explain this design.",
123
+ providerOptions: {
124
+ reasoningEffort: "high",
125
+ veniceParameters: { includeVeniceSystemPrompt: false },
126
+ },
127
+ })
128
+ console.log(response.text)
129
+ })
130
+ ```
131
+
132
+ Effort lowers to `reasoning.effort`; `reasoning.enabled` and `reasoning.summary` are also available.
133
+ Supported effort levels and toggles depend on the selected model. Omitted controls preserve its defaults.
134
+ Venice's added system prompt is disabled by default, matching the previous OpenCode Venice SDK behavior.
135
+
136
+ Replay complete `response.message` values to retain signed/encrypted reasoning and Gemini thought
137
+ signatures, including per-tool signatures. Venice's encrypted scalar trailers are excluded from visible
138
+ reasoning but retained in provider metadata for replay. Cache affinity uses `promptCacheKey`, and cache-write
139
+ usage reads Venice's `cache_creation_input_tokens` field.
140
+
141
+ The native package entrypoint is `@opencode/ai/providers/venice`. Image generation, embeddings, Responses, and
142
+ client-side E2EE are not implemented by this provider.
143
+
84
144
  ## Experimental evaluation
85
145
 
86
146
  Evaluation models compare shared state with typed choice, score, and boolean questions. The API is
@@ -1223,7 +1283,7 @@ const gateway = CloudflareAIGateway.configure({
1223
1283
  }).model("workers-ai/@cf/meta/llama-3.1-8b-instruct")
1224
1284
  ```
1225
1285
 
1226
- Included LLM providers: OpenAI, Anthropic, Google (Gemini), Google Vertex, Amazon Bedrock, Azure OpenAI, Baseten, Cerebras, Cloudflare AI Gateway, Cloudflare Workers AI, DeepInfra, DeepSeek, Fireworks, Groq, Mistral, OpenRouter, TogetherAI, and xAI. Z.ai currently exposes image generation. Generic Chat Completions, Responses, and Anthropic Messages-compatible entrypoints support custom endpoints.
1286
+ Included LLM providers: OpenAI, Anthropic, Google (Gemini), Google Vertex, Amazon Bedrock, Azure OpenAI, Baseten, Cerebras, Cohere, Cloudflare AI Gateway, Cloudflare Workers AI, DeepInfra, DeepSeek, Fireworks, Groq, Mistral, OpenRouter, TogetherAI, and xAI. Z.ai currently exposes image generation. Generic Chat Completions, Responses, and Anthropic Messages-compatible entrypoints support custom endpoints.
1227
1287
 
1228
1288
  Each named provider owns its module, endpoint, authentication, and route setup. Providers with the same wire format compose the shared protocol directly:
1229
1289
 
@@ -36,9 +36,11 @@ const resolve = (policy) => {
36
36
  // prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the
37
37
  // whole policy pass for these — emitting hints would be harmless but pointless.
38
38
  const RESPECTS_INLINE_HINTS = new Set([
39
+ "alibaba-chat",
39
40
  "alibaba-messages",
40
41
  "anthropic-messages",
41
42
  "anthropic-compatible-messages",
43
+ "bedrock-mantle-messages",
42
44
  "cloudflare-ai-gateway-messages",
43
45
  "google-vertex-messages",
44
46
  "meta-messages",
@@ -47,17 +49,19 @@ const RESPECTS_INLINE_HINTS = new Set([
47
49
  "zai-coding-messages",
48
50
  "bedrock-converse",
49
51
  "openrouter",
52
+ "digitalocean",
50
53
  ]);
51
54
  // OpenRouter upstreams other than Anthropic and Alibaba Qwen cache without breakpoints. Gemini uses only the last
52
55
  // breakpoint, so a conversation-tail breakpoint writes a new cache every step and costs more than none. Qwen ignores
53
56
  // breakpoints on tool definitions and caches tools with the system prompt.
57
+ const QWEN = { system: true, messages: { tail: 1 } };
54
58
  const openRouterPolicy = (modelID) => {
55
59
  // `~anthropic/claude-sonnet-latest` style IDs are OpenRouter aliases for the latest model in a family.
56
60
  const id = modelID.replace(/^~/, "");
57
61
  if (id.startsWith("anthropic/"))
58
62
  return AUTO;
59
63
  if (id.startsWith("qwen/"))
60
- return { system: true, messages: { tail: 1 } };
64
+ return QWEN;
61
65
  return NONE;
62
66
  };
63
67
  const makeHint = (ttlSeconds) => ttlSeconds !== undefined ? new CacheHint({ type: "ephemeral", ttlSeconds }) : new CacheHint({ type: "ephemeral" });
@@ -142,7 +146,11 @@ export const applyCachePolicy = (request) => {
142
146
  return request;
143
147
  const policy = request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")
144
148
  ? openRouterPolicy(request.model.id)
145
- : resolve(request.cache);
149
+ : request.model.route.id === "alibaba-chat" && (request.cache === undefined || request.cache === "auto")
150
+ ? request.model.id.toLowerCase().startsWith("qwen")
151
+ ? QWEN
152
+ : NONE
153
+ : resolve(request.cache);
146
154
  if (!policy.tools && !policy.system && !policy.messages)
147
155
  return request;
148
156
  const hint = makeHint(policy.ttlSeconds);
package/dist/llm.d.ts CHANGED
@@ -206,6 +206,8 @@ export declare class GenerateObjectResponse<T> {
206
206
  readonly reason: {
207
207
  readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
208
208
  readonly raw?: string | undefined;
209
+ readonly category?: string | undefined;
210
+ readonly explanation?: string | undefined;
209
211
  };
210
212
  readonly index: number;
211
213
  readonly providerMetadata?: {
@@ -219,6 +221,8 @@ export declare class GenerateObjectResponse<T> {
219
221
  readonly reason: {
220
222
  readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
221
223
  readonly raw?: string | undefined;
224
+ readonly category?: string | undefined;
225
+ readonly explanation?: string | undefined;
222
226
  };
223
227
  readonly providerMetadata?: {
224
228
  readonly [x: string]: {
package/dist/promise.d.ts CHANGED
@@ -233,6 +233,8 @@ export declare const make: (options?: Options) => {
233
233
  readonly reason: {
234
234
  readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
235
235
  readonly raw?: string | undefined;
236
+ readonly category?: string | undefined;
237
+ readonly explanation?: string | undefined;
236
238
  };
237
239
  readonly index: number;
238
240
  readonly providerMetadata?: {
@@ -246,6 +248,8 @@ export declare const make: (options?: Options) => {
246
248
  readonly reason: {
247
249
  readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
248
250
  readonly raw?: string | undefined;
251
+ readonly category?: string | undefined;
252
+ readonly explanation?: string | undefined;
249
253
  };
250
254
  readonly providerMetadata?: {
251
255
  readonly [x: string]: {
@@ -720,6 +724,8 @@ export declare const ai: {
720
724
  readonly reason: {
721
725
  readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
722
726
  readonly raw?: string | undefined;
727
+ readonly category?: string | undefined;
728
+ readonly explanation?: string | undefined;
723
729
  };
724
730
  readonly index: number;
725
731
  readonly providerMetadata?: {
@@ -733,6 +739,8 @@ export declare const ai: {
733
739
  readonly reason: {
734
740
  readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
735
741
  readonly raw?: string | undefined;
742
+ readonly category?: string | undefined;
743
+ readonly explanation?: string | undefined;
736
744
  };
737
745
  readonly providerMetadata?: {
738
746
  readonly [x: string]: {
@@ -152,10 +152,10 @@ export declare const protocol: Protocol<{
152
152
  readonly top_k?: number | undefined;
153
153
  readonly frequency_penalty?: number | undefined;
154
154
  readonly presence_penalty?: number | undefined;
155
+ readonly store?: boolean | undefined;
155
156
  readonly prompt_cache_key?: string | undefined;
156
157
  readonly parallel_tool_calls?: boolean | undefined;
157
158
  readonly reasoning_effort?: OpenResponsesOptions.ReasoningEffort | undefined;
158
- readonly store?: boolean | undefined;
159
159
  readonly stream_options?: {
160
160
  readonly include_usage: boolean;
161
161
  } | undefined;
@@ -186,16 +186,18 @@ export declare const protocol: Protocol<{
186
186
  } | null | undefined;
187
187
  readonly usage?: {
188
188
  readonly [x: string]: unknown;
189
+ readonly cache_read_input_tokens?: number | null | undefined;
189
190
  readonly cached_tokens?: number | null | undefined;
191
+ readonly total_tokens?: number | null | undefined;
190
192
  readonly prompt_tokens?: number | null | undefined;
191
193
  readonly completion_tokens?: number | null | undefined;
192
- readonly total_tokens?: number | null | undefined;
193
194
  readonly prompt_tokens_details?: {
194
195
  readonly [x: string]: unknown;
195
196
  readonly cached_tokens?: number | null | undefined;
196
197
  readonly cache_write_tokens?: number | null | undefined;
197
198
  } | null | undefined;
198
199
  readonly prompt_cache_hit_tokens?: number | null | undefined;
200
+ readonly cache_created_input_tokens?: number | null | undefined;
199
201
  readonly completion_tokens_details?: {
200
202
  readonly [x: string]: unknown;
201
203
  readonly reasoning_tokens?: number | null | undefined;
@@ -225,16 +227,18 @@ export declare const protocol: Protocol<{
225
227
  } | null | undefined;
226
228
  readonly usage?: {
227
229
  readonly [x: string]: unknown;
230
+ readonly cache_read_input_tokens?: number | null | undefined;
228
231
  readonly cached_tokens?: number | null | undefined;
232
+ readonly total_tokens?: number | null | undefined;
229
233
  readonly prompt_tokens?: number | null | undefined;
230
234
  readonly completion_tokens?: number | null | undefined;
231
- readonly total_tokens?: number | null | undefined;
232
235
  readonly prompt_tokens_details?: {
233
236
  readonly [x: string]: unknown;
234
237
  readonly cached_tokens?: number | null | undefined;
235
238
  readonly cache_write_tokens?: number | null | undefined;
236
239
  } | null | undefined;
237
240
  readonly prompt_cache_hit_tokens?: number | null | undefined;
241
+ readonly cache_created_input_tokens?: number | null | undefined;
238
242
  readonly completion_tokens_details?: {
239
243
  readonly [x: string]: unknown;
240
244
  readonly reasoning_tokens?: number | null | undefined;
@@ -2,6 +2,7 @@ import { Effect, Schema } from "effect";
2
2
  import { Protocol } from "../route/protocol.js";
3
3
  import { OpenAIChat } from "./openai-chat.js";
4
4
  import { JsonObject, ProviderShared } from "./shared.js";
5
+ import { cacheControl } from "./utils/cache.js";
5
6
  import { OpenResponsesOptions } from "./utils/open-responses-options.js";
6
7
  const Options = Schema.Struct({
7
8
  reasoningEffort: Schema.optional(OpenResponsesOptions.ReasoningEffort),
@@ -53,7 +54,7 @@ export const protocol = Protocol.make({
53
54
  from: Effect.fn("AlibabaChat.fromRequest")(function* (req) {
54
55
  const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {});
55
56
  return {
56
- ...(yield* OpenAIChat.protocol.body.from(req)),
57
+ ...(yield* OpenAIChat.fromRequest(req, { cacheControl: cacheControl() })),
57
58
  enable_thinking: opts.enableThinking,
58
59
  // Alibaba also rejects an explicit budget that is not below `max_completion_tokens`.
59
60
  thinking_budget: opts.thinkingBudget === undefined
@@ -48,6 +48,15 @@ export declare const protocol: Protocol<{
48
48
  readonly error?: unknown;
49
49
  readonly arguments?: string | undefined;
50
50
  readonly server_label?: string | undefined;
51
+ } | {
52
+ readonly type: "configuration_update";
53
+ readonly reasoning: {
54
+ readonly effort: OpenResponsesOptions.ReasoningEffort;
55
+ };
56
+ } | {
57
+ readonly [x: string]: unknown;
58
+ readonly type: string;
59
+ readonly id: string;
51
60
  } | {
52
61
  readonly type: "reasoning";
53
62
  readonly summary: readonly {
@@ -124,12 +133,6 @@ export declare const protocol: Protocol<{
124
133
  readonly type: "input_video";
125
134
  readonly video_url: string;
126
135
  })[];
127
- } | {
128
- readonly [x: string]: unknown;
129
- readonly id: string;
130
- readonly type: "web_extractor_call";
131
- readonly urls?: readonly string[] | undefined;
132
- readonly goal?: string | undefined;
133
136
  })[];
134
137
  readonly model: string;
135
138
  readonly stream: true;
@@ -142,6 +145,9 @@ export declare const protocol: Protocol<{
142
145
  readonly effort?: OpenResponsesOptions.ReasoningEffort | undefined;
143
146
  } | undefined;
144
147
  readonly tools?: readonly ({
148
+ readonly [x: string]: unknown;
149
+ readonly type: string;
150
+ } | {
145
151
  readonly type: "function";
146
152
  readonly description: string;
147
153
  readonly name: string;
@@ -149,8 +155,6 @@ export declare const protocol: Protocol<{
149
155
  readonly [x: string]: unknown;
150
156
  };
151
157
  readonly strict?: boolean | undefined;
152
- } | {
153
- readonly type: "web_search" | "code_interpreter" | "web_extractor";
154
158
  })[] | undefined;
155
159
  readonly text?: {
156
160
  readonly verbosity?: OpenResponsesOptions.TextVerbosity | undefined;
@@ -171,9 +175,10 @@ export declare const protocol: Protocol<{
171
175
  readonly top_p?: number | undefined;
172
176
  readonly frequency_penalty?: number | undefined;
173
177
  readonly presence_penalty?: number | undefined;
178
+ readonly store?: boolean | undefined;
179
+ readonly max_output_tokens?: number | undefined;
174
180
  readonly prompt_cache_key?: string | undefined;
175
181
  readonly parallel_tool_calls?: boolean | undefined;
176
- readonly store?: boolean | undefined;
177
182
  readonly include?: readonly OpenResponsesOptions.ResponseIncludable[] | undefined;
178
183
  readonly truncation?: "auto" | "disabled" | undefined;
179
184
  readonly stream_options?: {
@@ -181,7 +186,6 @@ export declare const protocol: Protocol<{
181
186
  } | undefined;
182
187
  readonly safety_identifier?: string | undefined;
183
188
  readonly top_logprobs?: number | undefined;
184
- readonly max_output_tokens?: number | undefined;
185
189
  readonly max_tool_calls?: number | undefined;
186
190
  readonly previous_response_id?: string | undefined;
187
191
  readonly conversation?: string | undefined;
@@ -204,8 +208,8 @@ export declare const protocol: Protocol<{
204
208
  readonly name?: string | undefined;
205
209
  readonly namespace?: string | undefined;
206
210
  readonly arguments?: string | undefined;
207
- readonly encrypted_content?: string | null | undefined;
208
211
  readonly call_id?: string | undefined;
212
+ readonly encrypted_content?: string | null | undefined;
209
213
  }[] | undefined;
210
214
  readonly error?: unknown;
211
215
  readonly usage?: {
@@ -232,8 +236,8 @@ export declare const protocol: Protocol<{
232
236
  readonly name?: string | undefined;
233
237
  readonly namespace?: string | undefined;
234
238
  readonly arguments?: string | undefined;
235
- readonly encrypted_content?: string | null | undefined;
236
239
  readonly call_id?: string | undefined;
240
+ readonly encrypted_content?: string | null | undefined;
237
241
  } | null | undefined;
238
242
  readonly delta?: string | undefined;
239
243
  readonly arguments?: string | undefined;
@@ -1,7 +1,7 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { Protocol } from "../route/protocol.js";
3
3
  import { OpenResponses } from "./open-responses.js";
4
- import { JsonObject, optionalArray, ProviderShared } from "./shared.js";
4
+ import { JsonObject, ProviderShared } from "./shared.js";
5
5
  import { OpenResponsesOptions } from "./utils/open-responses-options.js";
6
6
  import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js";
7
7
  const Options = Schema.Struct({
@@ -20,8 +20,6 @@ const WebExtractorItem = Schema.StructWithRest(Schema.Struct({
20
20
  }), [JsonObject]);
21
21
  const Body = Schema.Struct({
22
22
  ...OpenResponses.coreFields,
23
- input: Schema.Array(Schema.Union([OpenResponses.InputItem, WebExtractorItem])),
24
- tools: optionalArray(Schema.Union([OpenResponses.Tool, NativeTool])),
25
23
  enable_thinking: Options.fields.enableThinking,
26
24
  previous_response_id: Options.fields.previousResponseId,
27
25
  conversation: Options.fields.conversation,
@@ -45,7 +43,7 @@ export const protocol = Protocol.make({
45
43
  const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {});
46
44
  const body = yield* OpenResponses.fromRequestWithAdapter(req, adapter);
47
45
  const choice = body.tool_choice;
48
- return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Body))({
46
+ return {
49
47
  ...body,
50
48
  enable_thinking: opts.enableThinking,
51
49
  previous_response_id: opts.previousResponseId,
@@ -54,7 +52,7 @@ export const protocol = Protocol.make({
54
52
  tool_choice: typeof choice === "object" && choice.type === "function"
55
53
  ? { type: "allowed_tools", mode: "required", tools: [choice] }
56
54
  : choice,
57
- });
55
+ };
58
56
  }),
59
57
  },
60
58
  stream: {
@@ -359,6 +359,7 @@ const AnthropicStreamDelta = Schema.Struct({
359
359
  signature: Schema.optional(Schema.String),
360
360
  stop_reason: optionalNull(Schema.String),
361
361
  stop_sequence: optionalNull(Schema.String),
362
+ stop_details: optionalNull(Schema.Struct({ category: optionalNull(Schema.String), explanation: optionalNull(Schema.String) })),
362
363
  });
363
364
  const decodeAnthropicStreamDelta = Schema.decodeUnknownOption(AnthropicStreamDelta);
364
365
  const AnthropicEvent = Schema.Struct({
@@ -461,7 +462,7 @@ const serverToolResultType = (name) => {
461
462
  return "web_fetch_tool_result";
462
463
  return undefined;
463
464
  };
464
- const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part, providerMetadataKey) {
465
+ const lowerServerToolResult = Effect.fnUntraced(function* (part, providerMetadataKey) {
465
466
  const wireType = serverToolResultType(part.name);
466
467
  if (!wireType)
467
468
  return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`);
@@ -537,7 +538,7 @@ const citationsFromMetadata = (metadata) => {
537
538
  return undefined;
538
539
  };
539
540
  const isHttpUrl = (value) => /^https?:\/\//i.test(value.trim());
540
- const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part, breakpoints) {
541
+ const lowerMedia = Effect.fnUntraced(function* (part, breakpoints) {
541
542
  const mime = part.media.mediaType.toLowerCase();
542
543
  const cacheControlValue = breakpoints ? cacheControl(breakpoints, part.cache) : undefined;
543
544
  const fileId = fileIdFromMetadata(part.metadata);
@@ -674,37 +675,17 @@ const requireThinkingSignature = (request) => {
674
675
  return false;
675
676
  return true;
676
677
  };
677
- // Mid-conversation system messages became available with Opus 4.8 and version
678
- // 5 of the other supported Claude families. Treat later family versions as
679
- // compatible without assuming that every Anthropic Messages model is Claude.
678
+ // Opus 4.8 and every Claude 5 model accept mid-conversation system messages; later versions inherit support.
680
679
  const supportsNativeSystemUpdates = (request) => {
681
- const match = /(?:^|[./])claude-(fable|haiku|mythos|opus|sonnet)-(\d+)(?:[.-](\d+))?/.exec(String(request.model.id).toLowerCase());
682
- if (!match)
683
- return false;
684
- const major = Number(match[2]);
685
- if (match[1] !== "opus")
686
- return major >= 5;
687
- if (major !== 4)
688
- return major >= 5;
689
- return match[3] !== undefined && match[3].length <= 2 && Number(match[3]) >= 8;
690
- };
691
- const endsInServerToolUse = (message) => {
692
- const last = message.content.at(-1);
693
- return message.role === "assistant" && last?.type === "tool-call" && last.providerExecuted === true;
694
- };
695
- const canUseNativeSystemUpdate = (request, index) => {
696
- const previous = request.messages[index - 1];
697
- const next = request.messages[index + 1];
698
- // Vertex currently rejects/404s for a system message after local tool results,
699
- // so fold it into the user tool-result turn across continuations and history.
700
- if (request.model.route.id === "google-vertex-messages" && previous?.role === "tool")
680
+ const version = claudeVersion(String(request.model.id));
681
+ if (version === undefined)
701
682
  return false;
702
- return (previous !== undefined &&
703
- previous.role !== "system" &&
704
- (previous.role === "user" || previous.role === "tool" || endsInServerToolUse(previous)) &&
705
- next?.role !== "system" &&
706
- (next === undefined || next.role === "assistant"));
683
+ if (version.family === "opus" && version.major === 4)
684
+ return version.minor >= 8;
685
+ return version.major >= 5;
707
686
  };
687
+ // Native system messages must follow a user turn (tool results count) or a paused server-tool turn.
688
+ const acceptsNativeSystemAfter = (message) => message?.role === "user" || (message?.role === "assistant" && message.content.at(-1)?.type === "server_tool_use");
708
689
  const splitsLocalToolResults = (messages, index) => {
709
690
  const pending = new Set();
710
691
  for (const message of messages.slice(0, index)) {
@@ -717,7 +698,7 @@ const splitsLocalToolResults = (messages, index) => {
717
698
  }
718
699
  return pending.size > 0;
719
700
  };
720
- const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (message, breakpoints) {
701
+ const lowerNativeSystemUpdate = Effect.fnUntraced(function* (message, breakpoints) {
721
702
  const content = yield* ProviderShared.systemUpdateText("Anthropic Messages", message);
722
703
  return {
723
704
  role: "system",
@@ -728,9 +709,33 @@ const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUp
728
709
  })),
729
710
  };
730
711
  });
731
- const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (request, breakpoints) {
712
+ const lowerWrappedSystemUpdate = Effect.fnUntraced(function* (message, breakpoints) {
713
+ const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
714
+ return { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
715
+ });
716
+ const appendToUserTurn = (messages, block) => {
717
+ const last = messages.at(-1);
718
+ if (last?.role === "user")
719
+ messages[messages.length - 1] = { role: "user", content: [...last.content, block] };
720
+ else
721
+ messages.push({ role: "user", content: [block] });
722
+ };
723
+ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
732
724
  const messages = [];
733
725
  const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
726
+ // Text updates stay where they are unless a user turn follows them; then they move after the latest
727
+ // user turn, the nearest spot where Anthropic accepts a native system message.
728
+ const holdUpdates = supportsNativeSystemUpdates(request);
729
+ const held = [];
730
+ const releaseHeld = Effect.fnUntraced(function* () {
731
+ const native = acceptsNativeSystemAfter(messages.findLast((message) => message.role !== "system"));
732
+ for (const update of held.splice(0)) {
733
+ if (native)
734
+ messages.push(yield* lowerNativeSystemUpdate(update, breakpoints));
735
+ else
736
+ appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(update, breakpoints));
737
+ }
738
+ });
734
739
  for (const [index, message] of request.messages.entries()) {
735
740
  if (message.role === "system") {
736
741
  const update = effortUpdate(message);
@@ -741,17 +746,10 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
741
746
  }
742
747
  if (splitsLocalToolResults(request.messages, index))
743
748
  return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result");
744
- if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request, index)) {
745
- messages.push(yield* lowerNativeSystemUpdate(message, breakpoints));
746
- continue;
747
- }
748
- const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
749
- const block = { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
750
- const previous = messages.at(-1);
751
- if (previous?.role === "user")
752
- messages[messages.length - 1] = { role: "user", content: [...previous.content, block] };
749
+ if (holdUpdates)
750
+ held.push(message);
753
751
  else
754
- messages.push({ role: "user", content: [block] });
752
+ appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(message, breakpoints));
755
753
  continue;
756
754
  }
757
755
  if (message.role === "user") {
@@ -827,8 +825,10 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
827
825
  }
828
826
  return yield* invalid(`Anthropic Messages assistant messages only support text, reasoning, and tool-call content for now`);
829
827
  }
830
- if (content.length > 0)
831
- messages.push({ role: "assistant", content });
828
+ if (content.length === 0)
829
+ continue;
830
+ yield* releaseHeld();
831
+ messages.push({ role: "assistant", content });
832
832
  continue;
833
833
  }
834
834
  const content = [];
@@ -849,8 +849,13 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
849
849
  else
850
850
  messages.push({ role: "user", content });
851
851
  }
852
+ yield* releaseHeld();
852
853
  return messages;
853
854
  });
855
+ // TODO: Move per-model capability heuristics (`supportsEffortUpdates`, `supportsNativeSystemUpdates`,
856
+ // `supportsThinkingBlockBinding`) into explicit model/provider `compatibility` metadata so the protocol
857
+ // only reads `request.model.compatibility`.
858
+ // Per-turn effort started with Claude Opus 5 and every Claude 5.1 model; later versions of any family inherit it.
854
859
  const supportsEffortUpdates = (model) => {
855
860
  const override = model.compatibility?.supportsEffortUpdates;
856
861
  if (override !== undefined)
@@ -858,10 +863,8 @@ const supportsEffortUpdates = (model) => {
858
863
  const version = claudeVersion(model.id);
859
864
  if (version === undefined)
860
865
  return false;
861
- if (version.family === "opus")
862
- return version.major >= 5;
863
- if (version.family !== "fable" && version.family !== "mythos")
864
- return false;
866
+ if (version.family === "opus" && version.major >= 5)
867
+ return true;
865
868
  return version.major > 5 || (version.major === 5 && version.minor >= 1);
866
869
  };
867
870
  const applyThinkingBindingDefault = (model, thinking) => {
@@ -1134,7 +1137,7 @@ const onContentBlockStart = (state, event) => {
1134
1137
  const events = [];
1135
1138
  return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]];
1136
1139
  };
1137
- const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (state, event) {
1140
+ const onContentBlockDelta = Effect.fnUntraced(function* (state, event) {
1138
1141
  const delta = event.delta;
1139
1142
  if (delta.type === "compaction_delta") {
1140
1143
  if (event.index === undefined || !(event.index in state.compactions) || delta.content === undefined)
@@ -1192,7 +1195,7 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f
1192
1195
  }
1193
1196
  return [state, NO_EVENTS];
1194
1197
  });
1195
- const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(function* (state, event) {
1198
+ const onContentBlockStop = Effect.fnUntraced(function* (state, event) {
1196
1199
  if (event.index === undefined)
1197
1200
  return [state, NO_EVENTS];
1198
1201
  if (event.index in state.compactions) {
@@ -1227,10 +1230,14 @@ const onMessageDelta = (state, event) => {
1227
1230
  const finishMetadata = stopSequence === null || stopSequence === undefined
1228
1231
  ? state.pendingFinish?.providerMetadata
1229
1232
  : providerMetadata(state.providerMetadataKey, { stopSequence });
1233
+ const category = event.delta?.stop_details?.category;
1234
+ const explanation = event.delta?.stop_details?.explanation;
1230
1235
  return {
1231
1236
  reason: {
1232
1237
  normalized: mapFinishReason(stopReason),
1233
1238
  raw: stopReason,
1239
+ ...(category ? { category } : {}),
1240
+ ...(explanation ? { explanation } : {}),
1234
1241
  },
1235
1242
  providerMetadata: finishMetadata,
1236
1243
  };
@@ -1244,7 +1251,7 @@ const onMessageDelta = (state, event) => {
1244
1251
  NO_EVENTS,
1245
1252
  ];
1246
1253
  };
1247
- const onMessageStop = Effect.fn("AnthropicMessages.onMessageStop")(function* (state) {
1254
+ const onMessageStop = Effect.fnUntraced(function* (state) {
1248
1255
  if (Object.keys(state.compactions).length)
1249
1256
  return yield* ProviderShared.eventError(ADAPTER, "Response ended with an incomplete compaction block");
1250
1257
  const result = yield* ToolStream.finishAll(ADAPTER, state.tools);