@opencode/ai 2.0.22 → 2.0.24
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 +13 -9
- package/dist/protocols/alibaba-chat.d.ts +3 -3
- package/dist/protocols/alibaba-messages.d.ts +3 -0
- package/dist/protocols/alibaba-responses.d.ts +19 -12
- package/dist/protocols/alibaba-responses.js +3 -5
- package/dist/protocols/anthropic-messages.d.ts +3 -0
- package/dist/protocols/anthropic-messages.js +53 -48
- package/dist/protocols/bedrock-converse.js +3 -3
- 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-messages.d.ts +3 -0
- package/dist/protocols/meta-responses.d.ts +34 -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 +424 -103
- package/dist/protocols/open-responses.js +30 -20
- package/dist/protocols/openai-chat.d.ts +401 -5
- package/dist/protocols/openai-chat.js +18 -21
- 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 +90 -223
- package/dist/protocols/openai-responses.js +6 -13
- 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/gateway-protocol.d.ts +17 -0
- package/dist/protocols/utils/gateway-protocol.js +57 -0
- 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.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 +21 -21
- package/dist/protocols/xai-responses.js +2 -8
- package/dist/protocols/zai-chat.d.ts +3 -3
- package/dist/protocols/zai-messages.d.ts +3 -0
- package/dist/provider-error.js +7 -1
- 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 +5 -5
- 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 -0
- 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 +4 -4
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -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 +5 -5
- 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/vercel-ai-gateway/chat.d.ts +2 -0
- package/dist/providers/vercel-ai-gateway/chat.js +1 -0
- package/dist/providers/vercel-ai-gateway/messages.d.ts +2 -0
- package/dist/providers/vercel-ai-gateway/messages.js +1 -0
- package/dist/providers/vercel-ai-gateway/responses.d.ts +2 -0
- package/dist/providers/vercel-ai-gateway/responses.js +1 -0
- package/dist/providers/vercel-ai-gateway.d.ts +91 -9
- package/dist/providers/vercel-ai-gateway.js +161 -21
- 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.js +2 -5
- package/dist/route/framing.js +23 -23
- 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, Vercel AI Gateway, 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
|
@@ -40,26 +40,28 @@ 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",
|
|
46
47
|
"minimax-messages",
|
|
47
48
|
"moonshot-messages",
|
|
49
|
+
"vercel-ai-gateway-messages",
|
|
48
50
|
"zai-coding-messages",
|
|
49
51
|
"bedrock-converse",
|
|
50
52
|
"openrouter",
|
|
51
53
|
"digitalocean",
|
|
52
54
|
]);
|
|
53
|
-
// OpenRouter upstreams other than Anthropic and Alibaba Qwen cache without breakpoints.
|
|
54
|
-
// breakpoint, so a conversation-tail breakpoint writes a new cache every step and costs
|
|
55
|
-
// breakpoints on tool definitions and caches tools with the system prompt.
|
|
55
|
+
// OpenRouter and Vercel AI Gateway upstreams other than Anthropic and Alibaba Qwen cache without breakpoints.
|
|
56
|
+
// Gemini uses only the last breakpoint, so a conversation-tail breakpoint writes a new cache every step and costs
|
|
57
|
+
// more than none. Qwen ignores breakpoints on tool definitions and caches tools with the system prompt.
|
|
56
58
|
const QWEN = { system: true, messages: { tail: 1 } };
|
|
57
|
-
const
|
|
59
|
+
const gatewayPolicy = (modelID) => {
|
|
58
60
|
// `~anthropic/claude-sonnet-latest` style IDs are OpenRouter aliases for the latest model in a family.
|
|
59
61
|
const id = modelID.replace(/^~/, "");
|
|
60
62
|
if (id.startsWith("anthropic/"))
|
|
61
63
|
return AUTO;
|
|
62
|
-
if (id.startsWith("qwen/"))
|
|
64
|
+
if (id.startsWith("qwen/") || id.startsWith("alibaba/qwen"))
|
|
63
65
|
return QWEN;
|
|
64
66
|
return NONE;
|
|
65
67
|
};
|
|
@@ -141,11 +143,13 @@ const countHints = (request) => countToolHints(request.tools) +
|
|
|
141
143
|
request.messages.reduce((count, message) => count +
|
|
142
144
|
message.content.reduce((contentCount, part) => contentCount + ("cache" in part && part.cache !== undefined ? 1 : 0), 0), 0);
|
|
143
145
|
export const applyCachePolicy = (request) => {
|
|
144
|
-
|
|
146
|
+
const route = request.model.route.id;
|
|
147
|
+
if (!RESPECTS_INLINE_HINTS.has(route))
|
|
145
148
|
return request;
|
|
146
|
-
const policy =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
const policy = (route === "openrouter" || route === "vercel-ai-gateway-messages") &&
|
|
150
|
+
(request.cache === undefined || request.cache === "auto")
|
|
151
|
+
? gatewayPolicy(request.model.id)
|
|
152
|
+
: route === "alibaba-chat" && (request.cache === undefined || request.cache === "auto")
|
|
149
153
|
? request.model.id.toLowerCase().startsWith("qwen")
|
|
150
154
|
? QWEN
|
|
151
155
|
: NONE
|
|
@@ -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;
|
|
@@ -272,8 +272,10 @@ export declare const protocol: Protocol<{
|
|
|
272
272
|
readonly top_k?: number | undefined;
|
|
273
273
|
readonly stop_sequences?: readonly string[] | undefined;
|
|
274
274
|
}, string, {
|
|
275
|
+
readonly [x: string]: unknown;
|
|
275
276
|
readonly type: string;
|
|
276
277
|
readonly message?: {
|
|
278
|
+
readonly [x: string]: unknown;
|
|
277
279
|
readonly usage?: {
|
|
278
280
|
readonly [x: string]: unknown;
|
|
279
281
|
readonly input_tokens?: number | null | undefined;
|
|
@@ -306,6 +308,7 @@ export declare const protocol: Protocol<{
|
|
|
306
308
|
} | undefined;
|
|
307
309
|
} | undefined;
|
|
308
310
|
readonly error?: {
|
|
311
|
+
readonly [x: string]: unknown;
|
|
309
312
|
readonly type?: string | undefined;
|
|
310
313
|
readonly message?: string | undefined;
|
|
311
314
|
} | 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,18 +208,21 @@ 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?: {
|
|
216
|
+
readonly [x: string]: unknown;
|
|
212
217
|
readonly input_tokens?: number | undefined;
|
|
213
218
|
readonly output_tokens?: number | undefined;
|
|
214
219
|
readonly output_tokens_details?: {
|
|
220
|
+
readonly [x: string]: unknown;
|
|
215
221
|
readonly reasoning_tokens?: number | undefined;
|
|
216
222
|
} | null | undefined;
|
|
217
223
|
readonly total_tokens?: number | undefined;
|
|
218
224
|
readonly input_tokens_details?: {
|
|
225
|
+
readonly [x: string]: unknown;
|
|
219
226
|
readonly cached_tokens?: number | undefined;
|
|
220
227
|
readonly cache_write_tokens?: number | undefined;
|
|
221
228
|
} | null | undefined;
|
|
@@ -232,8 +239,8 @@ export declare const protocol: Protocol<{
|
|
|
232
239
|
readonly name?: string | undefined;
|
|
233
240
|
readonly namespace?: string | undefined;
|
|
234
241
|
readonly arguments?: string | undefined;
|
|
235
|
-
readonly encrypted_content?: string | null | undefined;
|
|
236
242
|
readonly call_id?: string | undefined;
|
|
243
|
+
readonly encrypted_content?: string | null | undefined;
|
|
237
244
|
} | null | undefined;
|
|
238
245
|
readonly delta?: string | undefined;
|
|
239
246
|
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: {
|
|
@@ -684,8 +684,10 @@ export declare const protocol: Protocol<{
|
|
|
684
684
|
readonly top_k?: number | undefined;
|
|
685
685
|
readonly stop_sequences?: readonly string[] | undefined;
|
|
686
686
|
}, string, {
|
|
687
|
+
readonly [x: string]: unknown;
|
|
687
688
|
readonly type: string;
|
|
688
689
|
readonly message?: {
|
|
690
|
+
readonly [x: string]: unknown;
|
|
689
691
|
readonly usage?: {
|
|
690
692
|
readonly [x: string]: unknown;
|
|
691
693
|
readonly input_tokens?: number | null | undefined;
|
|
@@ -718,6 +720,7 @@ export declare const protocol: Protocol<{
|
|
|
718
720
|
} | undefined;
|
|
719
721
|
} | undefined;
|
|
720
722
|
readonly error?: {
|
|
723
|
+
readonly [x: string]: unknown;
|
|
721
724
|
readonly type?: string | undefined;
|
|
722
725
|
readonly message?: string | undefined;
|
|
723
726
|
} | undefined;
|
|
@@ -332,7 +332,7 @@ const AnthropicUsage = Schema.StructWithRest(Schema.Struct({
|
|
|
332
332
|
...AnthropicIterationUsage.schema.fields,
|
|
333
333
|
iterations: Schema.optional(Schema.Array(AnthropicIterationUsage)),
|
|
334
334
|
}), [JsonObject]);
|
|
335
|
-
const AnthropicStreamBlock = Schema.Struct({
|
|
335
|
+
const AnthropicStreamBlock = Schema.StructWithRest(Schema.Struct({
|
|
336
336
|
type: Schema.String,
|
|
337
337
|
id: Schema.optional(Schema.String),
|
|
338
338
|
name: Schema.optional(Schema.String),
|
|
@@ -348,9 +348,9 @@ const AnthropicStreamBlock = Schema.Struct({
|
|
|
348
348
|
// server_tool_use id in `tool_use_id`.
|
|
349
349
|
tool_use_id: Schema.optional(Schema.String),
|
|
350
350
|
content: Schema.optional(Schema.Unknown),
|
|
351
|
-
});
|
|
351
|
+
}), [JsonObject]);
|
|
352
352
|
const decodeAnthropicStreamBlock = Schema.decodeUnknownOption(AnthropicStreamBlock);
|
|
353
|
-
const AnthropicStreamDelta = Schema.Struct({
|
|
353
|
+
const AnthropicStreamDelta = Schema.StructWithRest(Schema.Struct({
|
|
354
354
|
content: optionalNull(Schema.String),
|
|
355
355
|
type: Schema.optional(Schema.String),
|
|
356
356
|
text: Schema.optional(Schema.String),
|
|
@@ -359,13 +359,13 @@ 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) })),
|
|
363
|
-
});
|
|
362
|
+
stop_details: optionalNull(Schema.StructWithRest(Schema.Struct({ category: optionalNull(Schema.String), explanation: optionalNull(Schema.String) }), [JsonObject])),
|
|
363
|
+
}), [JsonObject]);
|
|
364
364
|
const decodeAnthropicStreamDelta = Schema.decodeUnknownOption(AnthropicStreamDelta);
|
|
365
|
-
const AnthropicEvent = Schema.Struct({
|
|
365
|
+
const AnthropicEvent = Schema.StructWithRest(Schema.Struct({
|
|
366
366
|
type: Schema.String,
|
|
367
367
|
index: Schema.optional(Schema.Number),
|
|
368
|
-
message: Schema.optional(Schema.Struct({ usage: Schema.optional(AnthropicUsage) })),
|
|
368
|
+
message: Schema.optional(Schema.StructWithRest(Schema.Struct({ usage: Schema.optional(AnthropicUsage) }), [JsonObject])),
|
|
369
369
|
content_block: Schema.optional(Schema.Unknown),
|
|
370
370
|
delta: Schema.optional(Schema.Unknown),
|
|
371
371
|
usage: Schema.optional(AnthropicUsage),
|
|
@@ -373,8 +373,8 @@ const AnthropicEvent = Schema.Struct({
|
|
|
373
373
|
// OpenAI-compatible proxies and gateway translations occasionally drop one
|
|
374
374
|
// or the other; mark them optional so a partial payload still parses and
|
|
375
375
|
// the parser can fall back to whichever field is populated.
|
|
376
|
-
error: Schema.optional(Schema.Struct({ type: Schema.optional(Schema.String), message: Schema.optional(Schema.String) })),
|
|
377
|
-
});
|
|
376
|
+
error: Schema.optional(Schema.StructWithRest(Schema.Struct({ type: Schema.optional(Schema.String), message: Schema.optional(Schema.String) }), [JsonObject])),
|
|
377
|
+
}), [JsonObject]);
|
|
378
378
|
const invalid = ProviderShared.invalidRequest;
|
|
379
379
|
// =============================================================================
|
|
380
380
|
// Request Lowering
|
|
@@ -462,7 +462,7 @@ const serverToolResultType = (name) => {
|
|
|
462
462
|
return "web_fetch_tool_result";
|
|
463
463
|
return undefined;
|
|
464
464
|
};
|
|
465
|
-
const lowerServerToolResult = Effect.
|
|
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.
|
|
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
|
-
|
|
691
|
-
|
|
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.
|
|
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
|
|
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 (
|
|
744
|
-
|
|
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
|
|
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
|
|
830
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|