@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.
- package/README.md +61 -1
- package/dist/cache-policy.js +10 -2
- package/dist/llm.d.ts +4 -0
- package/dist/promise.d.ts +8 -0
- package/dist/protocols/alibaba-chat.d.ts +7 -3
- package/dist/protocols/alibaba-chat.js +2 -1
- package/dist/protocols/alibaba-responses.d.ts +16 -12
- package/dist/protocols/alibaba-responses.js +3 -5
- package/dist/protocols/anthropic-messages.js +58 -51
- package/dist/protocols/bedrock-converse.js +33 -4
- package/dist/protocols/cartesia-speech.js +3 -3
- package/dist/protocols/cohere-chat.d.ts +178 -0
- package/dist/protocols/cohere-chat.js +299 -0
- package/dist/protocols/elevenlabs-speech.js +3 -3
- package/dist/protocols/gemini.d.ts +2 -2
- package/dist/protocols/gemini.js +3 -3
- package/dist/protocols/google-interactions.d.ts +236 -0
- package/dist/protocols/google-interactions.js +478 -0
- package/dist/protocols/google-speech.js +1 -1
- package/dist/protocols/google-transcription.js +1 -1
- package/dist/protocols/index.d.ts +2 -0
- package/dist/protocols/index.js +2 -0
- package/dist/protocols/meta-responses.d.ts +31 -58
- package/dist/protocols/meta-responses.js +8 -27
- package/dist/protocols/mistral-chat.d.ts +24 -9
- package/dist/protocols/mistral-chat.js +22 -10
- package/dist/protocols/open-responses.d.ts +410 -91
- package/dist/protocols/open-responses.js +25 -15
- package/dist/protocols/openai-chat.d.ts +409 -5
- package/dist/protocols/openai-chat.js +27 -26
- package/dist/protocols/openai-compatible-chat.d.ts +1 -1
- package/dist/protocols/openai-compatible-responses.d.ts +16 -4
- package/dist/protocols/openai-images.js +2 -2
- package/dist/protocols/openai-responses.d.ts +87 -223
- package/dist/protocols/openai-responses.js +10 -16
- package/dist/protocols/openai-speech.js +1 -1
- package/dist/protocols/openai-transcription.js +1 -1
- package/dist/protocols/shared.js +2 -2
- package/dist/protocols/stability-images.js +1 -1
- package/dist/protocols/utils/bedrock-media.js +2 -2
- package/dist/protocols/utils/cache.d.ts +5 -0
- package/dist/protocols/utils/cache.js +9 -1
- package/dist/protocols/utils/responses-checkpoint.js +2 -2
- package/dist/protocols/utils/responses-hosted-tools.js +1 -1
- package/dist/protocols/utils/tool-stream.d.ts +12 -0
- package/dist/protocols/utils/tool-stream.js +15 -7
- package/dist/protocols/venice-chat.d.ts +280 -0
- package/dist/protocols/venice-chat.js +283 -0
- package/dist/protocols/xai-responses.d.ts +18 -21
- package/dist/protocols/xai-responses.js +2 -8
- package/dist/protocols/zai-chat.d.ts +7 -3
- package/dist/protocols/zai-chat.js +6 -3
- package/dist/provider-error.js +27 -7
- package/dist/providers/alibaba.d.ts +17 -13
- package/dist/providers/amazon-bedrock/mantle/messages.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/messages.js +1 -0
- package/dist/providers/amazon-bedrock-mantle.d.ts +301 -9
- package/dist/providers/amazon-bedrock-mantle.js +28 -3
- package/dist/providers/azure.d.ts +13 -61
- package/dist/providers/baseten.d.ts +2 -2
- package/dist/providers/cerebras.d.ts +2 -2
- package/dist/providers/cloudflare-ai-gateway.d.ts +14 -62
- package/dist/providers/cloudflare-workers-ai.d.ts +2 -2
- package/dist/providers/cohere/chat.d.ts +4 -0
- package/dist/providers/cohere/chat.js +8 -0
- package/dist/providers/cohere.d.ts +395 -0
- package/dist/providers/cohere.js +48 -0
- package/dist/providers/deepinfra.d.ts +2 -2
- package/dist/providers/deepseek.d.ts +2 -2
- package/dist/providers/digitalocean.d.ts +469 -0
- package/dist/providers/digitalocean.js +53 -0
- package/dist/providers/fireworks.d.ts +2 -2
- package/dist/providers/google/interactions.d.ts +7 -0
- package/dist/providers/google/interactions.js +8 -0
- package/dist/providers/google-vertex-chat.d.ts +1 -1
- package/dist/providers/google-vertex-messages.js +1 -1
- package/dist/providers/google-vertex-responses.d.ts +16 -4
- package/dist/providers/google-vertex.d.ts +1 -1
- package/dist/providers/google.d.ts +95 -4
- package/dist/providers/google.js +8 -1
- package/dist/providers/groq.d.ts +8 -4
- package/dist/providers/groq.js +1 -1
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/meta.d.ts +15 -28
- package/dist/providers/minimax.d.ts +17 -5
- package/dist/providers/mistral.d.ts +10 -4
- package/dist/providers/moonshot.d.ts +17 -5
- package/dist/providers/openai-compatible-responses.d.ts +16 -4
- package/dist/providers/openai-compatible.d.ts +1 -1
- package/dist/providers/openai-options.d.ts +1 -1
- package/dist/providers/openai.d.ts +7 -31
- package/dist/providers/openrouter.d.ts +9 -5
- package/dist/providers/openrouter.js +1 -13
- package/dist/providers/togetherai.d.ts +2 -2
- package/dist/providers/venice.d.ts +183 -0
- package/dist/providers/venice.js +38 -0
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/zai-coding-plan.d.ts +17 -5
- package/dist/providers/zai.d.ts +1 -1
- package/dist/route/client.d.ts +4 -0
- package/dist/route/client.js +2 -5
- package/dist/route/executor.js +13 -6
- package/dist/route/framing.js +23 -23
- package/dist/route/transport/http.d.ts +2 -1
- package/dist/route/transport/http.js +28 -6
- package/dist/schema/events.d.ts +176 -0
- package/dist/schema/events.js +4 -0
- package/dist/schema/options.d.ts +11 -0
- package/dist/schema/options.js +15 -2
- package/dist/testing.d.ts +32 -0
- package/dist/tool-history.js +17 -3
- 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
|
|
package/dist/cache-policy.js
CHANGED
|
@@ -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
|
|
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
|
-
:
|
|
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.
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
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
|
|
682
|
-
if (
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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.
|
|
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
|
|
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 (
|
|
745
|
-
|
|
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
|
|
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
|
|
831
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|