@opencode/ai 2.0.22 → 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 (92) hide show
  1. package/README.md +61 -1
  2. package/dist/cache-policy.js +1 -0
  3. package/dist/protocols/alibaba-chat.d.ts +3 -3
  4. package/dist/protocols/alibaba-responses.d.ts +16 -12
  5. package/dist/protocols/alibaba-responses.js +3 -5
  6. package/dist/protocols/anthropic-messages.js +44 -39
  7. package/dist/protocols/bedrock-converse.js +3 -3
  8. package/dist/protocols/cartesia-speech.js +3 -3
  9. package/dist/protocols/cohere-chat.d.ts +178 -0
  10. package/dist/protocols/cohere-chat.js +299 -0
  11. package/dist/protocols/elevenlabs-speech.js +3 -3
  12. package/dist/protocols/gemini.d.ts +2 -2
  13. package/dist/protocols/gemini.js +3 -3
  14. package/dist/protocols/google-interactions.d.ts +236 -0
  15. package/dist/protocols/google-interactions.js +478 -0
  16. package/dist/protocols/google-speech.js +1 -1
  17. package/dist/protocols/google-transcription.js +1 -1
  18. package/dist/protocols/index.d.ts +2 -0
  19. package/dist/protocols/index.js +2 -0
  20. package/dist/protocols/meta-responses.d.ts +31 -58
  21. package/dist/protocols/meta-responses.js +8 -27
  22. package/dist/protocols/mistral-chat.d.ts +24 -9
  23. package/dist/protocols/mistral-chat.js +22 -10
  24. package/dist/protocols/open-responses.d.ts +406 -91
  25. package/dist/protocols/open-responses.js +25 -15
  26. package/dist/protocols/openai-chat.d.ts +401 -5
  27. package/dist/protocols/openai-chat.js +18 -21
  28. package/dist/protocols/openai-compatible-chat.d.ts +1 -1
  29. package/dist/protocols/openai-compatible-responses.d.ts +16 -4
  30. package/dist/protocols/openai-images.js +2 -2
  31. package/dist/protocols/openai-responses.d.ts +87 -223
  32. package/dist/protocols/openai-responses.js +6 -13
  33. package/dist/protocols/openai-speech.js +1 -1
  34. package/dist/protocols/openai-transcription.js +1 -1
  35. package/dist/protocols/shared.js +2 -2
  36. package/dist/protocols/stability-images.js +1 -1
  37. package/dist/protocols/utils/bedrock-media.js +2 -2
  38. package/dist/protocols/utils/responses-checkpoint.js +2 -2
  39. package/dist/protocols/utils/responses-hosted-tools.js +1 -1
  40. package/dist/protocols/utils/tool-stream.js +15 -7
  41. package/dist/protocols/venice-chat.d.ts +280 -0
  42. package/dist/protocols/venice-chat.js +283 -0
  43. package/dist/protocols/xai-responses.d.ts +18 -21
  44. package/dist/protocols/xai-responses.js +2 -8
  45. package/dist/protocols/zai-chat.d.ts +3 -3
  46. package/dist/provider-error.js +7 -1
  47. package/dist/providers/alibaba.d.ts +17 -13
  48. package/dist/providers/amazon-bedrock/mantle/messages.d.ts +2 -0
  49. package/dist/providers/amazon-bedrock/mantle/messages.js +1 -0
  50. package/dist/providers/amazon-bedrock-mantle.d.ts +301 -9
  51. package/dist/providers/amazon-bedrock-mantle.js +28 -3
  52. package/dist/providers/azure.d.ts +13 -61
  53. package/dist/providers/baseten.d.ts +2 -2
  54. package/dist/providers/cerebras.d.ts +2 -2
  55. package/dist/providers/cloudflare-ai-gateway.d.ts +14 -62
  56. package/dist/providers/cloudflare-workers-ai.d.ts +2 -2
  57. package/dist/providers/cohere/chat.d.ts +4 -0
  58. package/dist/providers/cohere/chat.js +8 -0
  59. package/dist/providers/cohere.d.ts +395 -0
  60. package/dist/providers/cohere.js +48 -0
  61. package/dist/providers/deepinfra.d.ts +2 -2
  62. package/dist/providers/deepseek.d.ts +2 -2
  63. package/dist/providers/digitalocean.d.ts +5 -5
  64. package/dist/providers/fireworks.d.ts +2 -2
  65. package/dist/providers/google/interactions.d.ts +7 -0
  66. package/dist/providers/google/interactions.js +8 -0
  67. package/dist/providers/google-vertex-chat.d.ts +1 -1
  68. package/dist/providers/google-vertex-responses.d.ts +16 -4
  69. package/dist/providers/google-vertex.d.ts +1 -1
  70. package/dist/providers/google.d.ts +95 -4
  71. package/dist/providers/google.js +8 -1
  72. package/dist/providers/groq.d.ts +4 -4
  73. package/dist/providers/index.d.ts +2 -0
  74. package/dist/providers/index.js +2 -0
  75. package/dist/providers/meta.d.ts +15 -28
  76. package/dist/providers/minimax.d.ts +17 -5
  77. package/dist/providers/mistral.d.ts +10 -4
  78. package/dist/providers/moonshot.d.ts +17 -5
  79. package/dist/providers/openai-compatible-responses.d.ts +16 -4
  80. package/dist/providers/openai-compatible.d.ts +1 -1
  81. package/dist/providers/openai-options.d.ts +1 -1
  82. package/dist/providers/openai.d.ts +7 -31
  83. package/dist/providers/openrouter.d.ts +5 -5
  84. package/dist/providers/togetherai.d.ts +2 -2
  85. package/dist/providers/venice.d.ts +183 -0
  86. package/dist/providers/venice.js +38 -0
  87. package/dist/providers/xai.d.ts +1 -1
  88. package/dist/providers/zai-coding-plan.d.ts +17 -5
  89. package/dist/providers/zai.d.ts +1 -1
  90. package/dist/route/client.js +2 -5
  91. package/dist/route/framing.js +23 -23
  92. 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
 
@@ -40,6 +40,7 @@ const RESPECTS_INLINE_HINTS = new Set([
40
40
  "alibaba-messages",
41
41
  "anthropic-messages",
42
42
  "anthropic-compatible-messages",
43
+ "bedrock-mantle-messages",
43
44
  "cloudflare-ai-gateway-messages",
44
45
  "google-vertex-messages",
45
46
  "meta-messages",
@@ -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;
@@ -188,9 +188,9 @@ export declare const protocol: Protocol<{
188
188
  readonly [x: string]: unknown;
189
189
  readonly cache_read_input_tokens?: number | null | undefined;
190
190
  readonly cached_tokens?: number | null | undefined;
191
+ readonly total_tokens?: number | null | undefined;
191
192
  readonly prompt_tokens?: number | null | undefined;
192
193
  readonly completion_tokens?: number | null | undefined;
193
- readonly total_tokens?: number | null | undefined;
194
194
  readonly prompt_tokens_details?: {
195
195
  readonly [x: string]: unknown;
196
196
  readonly cached_tokens?: number | null | undefined;
@@ -229,9 +229,9 @@ export declare const protocol: Protocol<{
229
229
  readonly [x: string]: unknown;
230
230
  readonly cache_read_input_tokens?: number | null | undefined;
231
231
  readonly cached_tokens?: number | null | undefined;
232
+ readonly total_tokens?: number | null | undefined;
232
233
  readonly prompt_tokens?: number | null | undefined;
233
234
  readonly completion_tokens?: number | null | undefined;
234
- readonly total_tokens?: number | null | undefined;
235
235
  readonly prompt_tokens_details?: {
236
236
  readonly [x: string]: unknown;
237
237
  readonly cached_tokens?: number | null | 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: {
@@ -462,7 +462,7 @@ const serverToolResultType = (name) => {
462
462
  return "web_fetch_tool_result";
463
463
  return undefined;
464
464
  };
465
- const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part, providerMetadataKey) {
465
+ const lowerServerToolResult = Effect.fnUntraced(function* (part, providerMetadataKey) {
466
466
  const wireType = serverToolResultType(part.name);
467
467
  if (!wireType)
468
468
  return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`);
@@ -538,7 +538,7 @@ const citationsFromMetadata = (metadata) => {
538
538
  return undefined;
539
539
  };
540
540
  const isHttpUrl = (value) => /^https?:\/\//i.test(value.trim());
541
- const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part, breakpoints) {
541
+ const lowerMedia = Effect.fnUntraced(function* (part, breakpoints) {
542
542
  const mime = part.media.mediaType.toLowerCase();
543
543
  const cacheControlValue = breakpoints ? cacheControl(breakpoints, part.cache) : undefined;
544
544
  const fileId = fileIdFromMetadata(part.metadata);
@@ -675,9 +675,6 @@ const requireThinkingSignature = (request) => {
675
675
  return false;
676
676
  return true;
677
677
  };
678
- // Mid-conversation system messages became available with Opus 4.8 and version
679
- // 5 of the other supported Claude families. Treat later family versions as
680
- // compatible without assuming that every Anthropic Messages model is Claude.
681
678
  // Opus 4.8 and every Claude 5 model accept mid-conversation system messages; later versions inherit support.
682
679
  const supportsNativeSystemUpdates = (request) => {
683
680
  const version = claudeVersion(String(request.model.id));
@@ -687,23 +684,8 @@ const supportsNativeSystemUpdates = (request) => {
687
684
  return version.minor >= 8;
688
685
  return version.major >= 5;
689
686
  };
690
- const endsInServerToolUse = (message) => {
691
- const last = message.content.at(-1);
692
- return message.role === "assistant" && last?.type === "tool-call" && last.providerExecuted === true;
693
- };
694
- const canUseNativeSystemUpdate = (request, index) => {
695
- const previous = request.messages[index - 1];
696
- const next = request.messages[index + 1];
697
- // Vertex currently rejects/404s for a system message after local tool results,
698
- // so fold it into the user tool-result turn across continuations and history.
699
- if (request.model.route.id === "google-vertex-messages" && previous?.role === "tool")
700
- return false;
701
- return (previous !== undefined &&
702
- previous.role !== "system" &&
703
- (previous.role === "user" || previous.role === "tool" || endsInServerToolUse(previous)) &&
704
- next?.role !== "system" &&
705
- (next === undefined || next.role === "assistant"));
706
- };
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");
707
689
  const splitsLocalToolResults = (messages, index) => {
708
690
  const pending = new Set();
709
691
  for (const message of messages.slice(0, index)) {
@@ -716,7 +698,7 @@ const splitsLocalToolResults = (messages, index) => {
716
698
  }
717
699
  return pending.size > 0;
718
700
  };
719
- const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (message, breakpoints) {
701
+ const lowerNativeSystemUpdate = Effect.fnUntraced(function* (message, breakpoints) {
720
702
  const content = yield* ProviderShared.systemUpdateText("Anthropic Messages", message);
721
703
  return {
722
704
  role: "system",
@@ -727,9 +709,33 @@ const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUp
727
709
  })),
728
710
  };
729
711
  });
730
- 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) {
731
724
  const messages = [];
732
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
+ });
733
739
  for (const [index, message] of request.messages.entries()) {
734
740
  if (message.role === "system") {
735
741
  const update = effortUpdate(message);
@@ -740,17 +746,10 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
740
746
  }
741
747
  if (splitsLocalToolResults(request.messages, index))
742
748
  return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result");
743
- if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request, index)) {
744
- messages.push(yield* lowerNativeSystemUpdate(message, breakpoints));
745
- continue;
746
- }
747
- const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
748
- const block = { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
749
- const previous = messages.at(-1);
750
- if (previous?.role === "user")
751
- messages[messages.length - 1] = { role: "user", content: [...previous.content, block] };
749
+ if (holdUpdates)
750
+ held.push(message);
752
751
  else
753
- messages.push({ role: "user", content: [block] });
752
+ appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(message, breakpoints));
754
753
  continue;
755
754
  }
756
755
  if (message.role === "user") {
@@ -826,8 +825,10 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
826
825
  }
827
826
  return yield* invalid(`Anthropic Messages assistant messages only support text, reasoning, and tool-call content for now`);
828
827
  }
829
- if (content.length > 0)
830
- messages.push({ role: "assistant", content });
828
+ if (content.length === 0)
829
+ continue;
830
+ yield* releaseHeld();
831
+ messages.push({ role: "assistant", content });
831
832
  continue;
832
833
  }
833
834
  const content = [];
@@ -848,8 +849,12 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
848
849
  else
849
850
  messages.push({ role: "user", content });
850
851
  }
852
+ yield* releaseHeld();
851
853
  return messages;
852
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`.
853
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;
@@ -1132,7 +1137,7 @@ const onContentBlockStart = (state, event) => {
1132
1137
  const events = [];
1133
1138
  return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]];
1134
1139
  };
1135
- const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (state, event) {
1140
+ const onContentBlockDelta = Effect.fnUntraced(function* (state, event) {
1136
1141
  const delta = event.delta;
1137
1142
  if (delta.type === "compaction_delta") {
1138
1143
  if (event.index === undefined || !(event.index in state.compactions) || delta.content === undefined)
@@ -1190,7 +1195,7 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f
1190
1195
  }
1191
1196
  return [state, NO_EVENTS];
1192
1197
  });
1193
- const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(function* (state, event) {
1198
+ const onContentBlockStop = Effect.fnUntraced(function* (state, event) {
1194
1199
  if (event.index === undefined)
1195
1200
  return [state, NO_EVENTS];
1196
1201
  if (event.index in state.compactions) {
@@ -1246,7 +1251,7 @@ const onMessageDelta = (state, event) => {
1246
1251
  NO_EVENTS,
1247
1252
  ];
1248
1253
  };
1249
- const onMessageStop = Effect.fn("AnthropicMessages.onMessageStop")(function* (state) {
1254
+ const onMessageStop = Effect.fnUntraced(function* (state) {
1250
1255
  if (Object.keys(state.compactions).length)
1251
1256
  return yield* ProviderShared.eventError(ADAPTER, "Response ended with an incomplete compaction block");
1252
1257
  const result = yield* ToolStream.finishAll(ADAPTER, state.tools);
@@ -211,7 +211,7 @@ const lowerToolCall = (part, normalizeID) => ({
211
211
  input: removeEmptyToolInputKeys(part.input),
212
212
  },
213
213
  });
214
- const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part, documentNames) {
214
+ const lowerToolResultContent = Effect.fnUntraced(function* (part, documentNames) {
215
215
  if (part.result.type === "text" || part.result.type === "error")
216
216
  return [{ text: ProviderShared.toolResultText(part) }];
217
217
  if (part.result.type === "json")
@@ -227,7 +227,7 @@ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent
227
227
  }
228
228
  return content;
229
229
  });
230
- const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part, documentNames, normalizeID) {
230
+ const lowerToolResult = Effect.fnUntraced(function* (part, documentNames, normalizeID) {
231
231
  return {
232
232
  toolResult: {
233
233
  toolUseId: normalizeID(part.id),
@@ -238,7 +238,7 @@ const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (
238
238
  });
239
239
  // Keep Claude and Nova tool-result images inline; put other models' images beside the result.
240
240
  const keepToolImagesInline = (id) => id.includes("anthropic.claude-") || id.includes("amazon.nova-");
241
- const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (request, breakpoints) {
241
+ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
242
242
  const messages = [];
243
243
  const documentNames = new Set();
244
244
  // Mistral can reject replay IDs even when they satisfy Converse's broader ID syntax.
@@ -38,7 +38,7 @@ const decodeEvent = route.decodeFrame(SseEvent);
38
38
  /** Timestamps exist only on the SSE endpoint, so a `generate` that asks for them collects an SSE stream. */
39
39
  const usesSse = (request) => request.mode === "stream" || request.timestamps === true;
40
40
  const CONTAINERS = { pcm: "raw", wav: "wav", mp3: "mp3" };
41
- const outputFormat = Effect.fn("CartesiaSpeech.outputFormat")(function* (request) {
41
+ const outputFormat = Effect.fnUntraced(function* (request) {
42
42
  const sse = usesSse(request);
43
43
  const format = request.format ?? (sse ? "pcm" : "mp3");
44
44
  const container = CONTAINERS[format];
@@ -69,7 +69,7 @@ const fromRequest = Effect.fn("CartesiaSpeech.fromRequest")(function* (request)
69
69
  // ---------------------------------------------------------------------------
70
70
  // 6. Stream parsing
71
71
  // ---------------------------------------------------------------------------
72
- const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state, frame) {
72
+ const onEvent = Effect.fnUntraced(function* (state, frame) {
73
73
  const event = yield* decodeEvent(frame);
74
74
  if (event.type === "chunk" && event.data !== undefined)
75
75
  return SpeechStream.delta(state, event.data);
@@ -89,7 +89,7 @@ const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state, frame) {
89
89
  });
90
90
  return [state, []];
91
91
  });
92
- const finish = Effect.fn("CartesiaSpeech.finish")(function* (state, context) {
92
+ const finish = Effect.fnUntraced(function* (state, context) {
93
93
  if (usesSse(context.request) && !state.done)
94
94
  return yield* route.incomplete();
95
95
  const format = yield* outputFormat(context.request);
@@ -0,0 +1,178 @@
1
+ import { Schema } from "effect";
2
+ import { Route } from "../route/client.js";
3
+ import { Protocol } from "../route/protocol.js";
4
+ import { Lifecycle } from "./utils/lifecycle.js";
5
+ import { ToolStream } from "./utils/tool-stream.js";
6
+ export declare const DEFAULT_BASE_URL = "https://api.cohere.com/v2";
7
+ declare const Options: Schema.Struct<{
8
+ readonly thinking: Schema.optional<Schema.Struct<{
9
+ readonly type: Schema.optional<Schema.Literals<readonly ["enabled", "disabled"]>>;
10
+ readonly tokenBudget: Schema.optional<Schema.Int>;
11
+ }>>;
12
+ }>;
13
+ export type ProviderOptionsInput = Schema.Schema.Type<typeof Options>;
14
+ export declare const protocol: Protocol<{
15
+ readonly model: string;
16
+ readonly messages: readonly {
17
+ readonly role: "tool" | "system" | "user" | "assistant";
18
+ readonly content?: string | readonly ({
19
+ readonly type: "text";
20
+ readonly text: string;
21
+ } | {
22
+ readonly type: "thinking";
23
+ readonly thinking: string;
24
+ } | {
25
+ readonly type: "image_url";
26
+ readonly image_url: {
27
+ readonly url: string;
28
+ };
29
+ })[] | undefined;
30
+ readonly tool_calls?: readonly {
31
+ readonly id: string;
32
+ readonly type: "function";
33
+ readonly function: {
34
+ readonly name: string;
35
+ readonly arguments: string;
36
+ };
37
+ }[] | undefined;
38
+ readonly tool_call_id?: string | undefined;
39
+ readonly tool_plan?: string | undefined;
40
+ }[];
41
+ readonly stream: true;
42
+ readonly max_tokens?: number | undefined;
43
+ readonly tools?: readonly {
44
+ readonly type: "function";
45
+ readonly function: {
46
+ readonly name: string;
47
+ readonly parameters: unknown;
48
+ readonly description?: string | undefined;
49
+ };
50
+ }[] | undefined;
51
+ readonly temperature?: number | undefined;
52
+ readonly seed?: number | undefined;
53
+ readonly thinking?: {
54
+ readonly type: string;
55
+ readonly token_budget?: number | undefined;
56
+ } | undefined;
57
+ readonly tool_choice?: "NONE" | "REQUIRED" | undefined;
58
+ readonly stop_sequences?: readonly string[] | undefined;
59
+ readonly p?: number | undefined;
60
+ readonly k?: number | undefined;
61
+ readonly frequency_penalty?: number | undefined;
62
+ readonly presence_penalty?: number | undefined;
63
+ }, string, {
64
+ readonly type: "message-start";
65
+ } | {
66
+ readonly type: "content-start" | "content-delta";
67
+ readonly index: number;
68
+ readonly delta: {
69
+ readonly message: {
70
+ readonly content: {
71
+ readonly text?: string | undefined;
72
+ readonly thinking?: string | undefined;
73
+ };
74
+ };
75
+ };
76
+ } | {
77
+ readonly type: "content-end";
78
+ readonly index: number;
79
+ } | {
80
+ readonly type: "tool-plan-delta";
81
+ readonly delta: {
82
+ readonly message: {
83
+ readonly tool_plan: string;
84
+ };
85
+ };
86
+ } | {
87
+ readonly type: "tool-call-start" | "tool-call-delta";
88
+ readonly index: number;
89
+ readonly delta: {
90
+ readonly message: {
91
+ readonly tool_calls: {
92
+ readonly function: {
93
+ readonly name?: string | undefined;
94
+ readonly arguments?: string | undefined;
95
+ };
96
+ readonly id?: string | undefined;
97
+ };
98
+ };
99
+ };
100
+ } | {
101
+ readonly type: "tool-call-end";
102
+ readonly index: number;
103
+ } | {
104
+ readonly type: "message-end";
105
+ readonly delta: {
106
+ readonly finish_reason: string;
107
+ readonly usage?: {
108
+ readonly tokens?: {
109
+ readonly input_tokens?: number | undefined;
110
+ readonly output_tokens?: number | undefined;
111
+ readonly reasoning_tokens?: number | undefined;
112
+ } | undefined;
113
+ readonly billed_units?: {
114
+ readonly input_tokens?: number | undefined;
115
+ readonly output_tokens?: number | undefined;
116
+ readonly reasoning_tokens?: number | undefined;
117
+ } | undefined;
118
+ readonly cached_tokens?: number | undefined;
119
+ } | undefined;
120
+ };
121
+ } | {
122
+ readonly type: "citation-start" | "citation-end";
123
+ }, {
124
+ readonly tools: Partial<Record<number, ToolStream.PendingTool>>;
125
+ readonly lifecycle: Lifecycle.State;
126
+ readonly finished: boolean;
127
+ }>;
128
+ export declare const route: Route<{
129
+ readonly model: string;
130
+ readonly messages: readonly {
131
+ readonly role: "tool" | "system" | "user" | "assistant";
132
+ readonly content?: string | readonly ({
133
+ readonly type: "text";
134
+ readonly text: string;
135
+ } | {
136
+ readonly type: "thinking";
137
+ readonly thinking: string;
138
+ } | {
139
+ readonly type: "image_url";
140
+ readonly image_url: {
141
+ readonly url: string;
142
+ };
143
+ })[] | undefined;
144
+ readonly tool_calls?: readonly {
145
+ readonly id: string;
146
+ readonly type: "function";
147
+ readonly function: {
148
+ readonly name: string;
149
+ readonly arguments: string;
150
+ };
151
+ }[] | undefined;
152
+ readonly tool_call_id?: string | undefined;
153
+ readonly tool_plan?: string | undefined;
154
+ }[];
155
+ readonly stream: true;
156
+ readonly max_tokens?: number | undefined;
157
+ readonly tools?: readonly {
158
+ readonly type: "function";
159
+ readonly function: {
160
+ readonly name: string;
161
+ readonly parameters: unknown;
162
+ readonly description?: string | undefined;
163
+ };
164
+ }[] | undefined;
165
+ readonly temperature?: number | undefined;
166
+ readonly seed?: number | undefined;
167
+ readonly thinking?: {
168
+ readonly type: string;
169
+ readonly token_budget?: number | undefined;
170
+ } | undefined;
171
+ readonly tool_choice?: "NONE" | "REQUIRED" | undefined;
172
+ readonly stop_sequences?: readonly string[] | undefined;
173
+ readonly p?: number | undefined;
174
+ readonly k?: number | undefined;
175
+ readonly frequency_penalty?: number | undefined;
176
+ readonly presence_penalty?: number | undefined;
177
+ }, import("../route/transport/http.js").HttpPrepared<string>, import("../route/client.js").CompactionOperations | undefined>;
178
+ export * as CohereChat from "./cohere-chat.js";