@opencode/ai 2.0.21 → 2.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +61 -1
  2. package/dist/cache-policy.js +10 -2
  3. package/dist/llm.d.ts +4 -0
  4. package/dist/promise.d.ts +8 -0
  5. package/dist/protocols/alibaba-chat.d.ts +7 -3
  6. package/dist/protocols/alibaba-chat.js +2 -1
  7. package/dist/protocols/alibaba-responses.d.ts +16 -12
  8. package/dist/protocols/alibaba-responses.js +3 -5
  9. package/dist/protocols/anthropic-messages.js +58 -51
  10. package/dist/protocols/bedrock-converse.js +33 -4
  11. package/dist/protocols/cartesia-speech.js +3 -3
  12. package/dist/protocols/cohere-chat.d.ts +178 -0
  13. package/dist/protocols/cohere-chat.js +299 -0
  14. package/dist/protocols/elevenlabs-speech.js +3 -3
  15. package/dist/protocols/gemini.d.ts +2 -2
  16. package/dist/protocols/gemini.js +3 -3
  17. package/dist/protocols/google-interactions.d.ts +236 -0
  18. package/dist/protocols/google-interactions.js +478 -0
  19. package/dist/protocols/google-speech.js +1 -1
  20. package/dist/protocols/google-transcription.js +1 -1
  21. package/dist/protocols/index.d.ts +2 -0
  22. package/dist/protocols/index.js +2 -0
  23. package/dist/protocols/meta-responses.d.ts +31 -58
  24. package/dist/protocols/meta-responses.js +8 -27
  25. package/dist/protocols/mistral-chat.d.ts +24 -9
  26. package/dist/protocols/mistral-chat.js +22 -10
  27. package/dist/protocols/open-responses.d.ts +410 -91
  28. package/dist/protocols/open-responses.js +25 -15
  29. package/dist/protocols/openai-chat.d.ts +409 -5
  30. package/dist/protocols/openai-chat.js +27 -26
  31. package/dist/protocols/openai-compatible-chat.d.ts +1 -1
  32. package/dist/protocols/openai-compatible-responses.d.ts +16 -4
  33. package/dist/protocols/openai-images.js +2 -2
  34. package/dist/protocols/openai-responses.d.ts +87 -223
  35. package/dist/protocols/openai-responses.js +10 -16
  36. package/dist/protocols/openai-speech.js +1 -1
  37. package/dist/protocols/openai-transcription.js +1 -1
  38. package/dist/protocols/shared.js +2 -2
  39. package/dist/protocols/stability-images.js +1 -1
  40. package/dist/protocols/utils/bedrock-media.js +2 -2
  41. package/dist/protocols/utils/cache.d.ts +5 -0
  42. package/dist/protocols/utils/cache.js +9 -1
  43. package/dist/protocols/utils/responses-checkpoint.js +2 -2
  44. package/dist/protocols/utils/responses-hosted-tools.js +1 -1
  45. package/dist/protocols/utils/tool-stream.d.ts +12 -0
  46. package/dist/protocols/utils/tool-stream.js +15 -7
  47. package/dist/protocols/venice-chat.d.ts +280 -0
  48. package/dist/protocols/venice-chat.js +283 -0
  49. package/dist/protocols/xai-responses.d.ts +18 -21
  50. package/dist/protocols/xai-responses.js +2 -8
  51. package/dist/protocols/zai-chat.d.ts +7 -3
  52. package/dist/protocols/zai-chat.js +6 -3
  53. package/dist/provider-error.js +27 -7
  54. package/dist/providers/alibaba.d.ts +17 -13
  55. package/dist/providers/amazon-bedrock/mantle/messages.d.ts +2 -0
  56. package/dist/providers/amazon-bedrock/mantle/messages.js +1 -0
  57. package/dist/providers/amazon-bedrock-mantle.d.ts +301 -9
  58. package/dist/providers/amazon-bedrock-mantle.js +28 -3
  59. package/dist/providers/azure.d.ts +13 -61
  60. package/dist/providers/baseten.d.ts +2 -2
  61. package/dist/providers/cerebras.d.ts +2 -2
  62. package/dist/providers/cloudflare-ai-gateway.d.ts +14 -62
  63. package/dist/providers/cloudflare-workers-ai.d.ts +2 -2
  64. package/dist/providers/cohere/chat.d.ts +4 -0
  65. package/dist/providers/cohere/chat.js +8 -0
  66. package/dist/providers/cohere.d.ts +395 -0
  67. package/dist/providers/cohere.js +48 -0
  68. package/dist/providers/deepinfra.d.ts +2 -2
  69. package/dist/providers/deepseek.d.ts +2 -2
  70. package/dist/providers/digitalocean.d.ts +469 -0
  71. package/dist/providers/digitalocean.js +53 -0
  72. package/dist/providers/fireworks.d.ts +2 -2
  73. package/dist/providers/google/interactions.d.ts +7 -0
  74. package/dist/providers/google/interactions.js +8 -0
  75. package/dist/providers/google-vertex-chat.d.ts +1 -1
  76. package/dist/providers/google-vertex-messages.js +1 -1
  77. package/dist/providers/google-vertex-responses.d.ts +16 -4
  78. package/dist/providers/google-vertex.d.ts +1 -1
  79. package/dist/providers/google.d.ts +95 -4
  80. package/dist/providers/google.js +8 -1
  81. package/dist/providers/groq.d.ts +8 -4
  82. package/dist/providers/groq.js +1 -1
  83. package/dist/providers/index.d.ts +3 -0
  84. package/dist/providers/index.js +3 -0
  85. package/dist/providers/meta.d.ts +15 -28
  86. package/dist/providers/minimax.d.ts +17 -5
  87. package/dist/providers/mistral.d.ts +10 -4
  88. package/dist/providers/moonshot.d.ts +17 -5
  89. package/dist/providers/openai-compatible-responses.d.ts +16 -4
  90. package/dist/providers/openai-compatible.d.ts +1 -1
  91. package/dist/providers/openai-options.d.ts +1 -1
  92. package/dist/providers/openai.d.ts +7 -31
  93. package/dist/providers/openrouter.d.ts +9 -5
  94. package/dist/providers/openrouter.js +1 -13
  95. package/dist/providers/togetherai.d.ts +2 -2
  96. package/dist/providers/venice.d.ts +183 -0
  97. package/dist/providers/venice.js +38 -0
  98. package/dist/providers/xai.d.ts +1 -1
  99. package/dist/providers/zai-coding-plan.d.ts +17 -5
  100. package/dist/providers/zai.d.ts +1 -1
  101. package/dist/route/client.d.ts +4 -0
  102. package/dist/route/client.js +2 -5
  103. package/dist/route/executor.js +13 -6
  104. package/dist/route/framing.js +23 -23
  105. package/dist/route/transport/http.d.ts +2 -1
  106. package/dist/route/transport/http.js +28 -6
  107. package/dist/schema/events.d.ts +176 -0
  108. package/dist/schema/events.js +4 -0
  109. package/dist/schema/options.d.ts +11 -0
  110. package/dist/schema/options.js +15 -2
  111. package/dist/testing.d.ts +32 -0
  112. package/dist/tool-history.js +17 -3
  113. package/package.json +3 -3
@@ -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),
@@ -236,13 +236,28 @@ const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (
236
236
  },
237
237
  };
238
238
  });
239
- const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (request, breakpoints) {
239
+ // Keep Claude and Nova tool-result images inline; put other models' images beside the result.
240
+ const keepToolImagesInline = (id) => id.includes("anthropic.claude-") || id.includes("amazon.nova-");
241
+ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
240
242
  const messages = [];
241
243
  const documentNames = new Set();
242
244
  // Mistral can reject replay IDs even when they satisfy Converse's broader ID syntax.
243
245
  const normalizeID = request.model.id.includes("mistral.") ? MistralToolID.normalizer(request) : (id) => id;
244
246
  const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
247
+ const hoistImages = !keepToolImagesInline(request.model.id);
248
+ // Bedrock expects parallel tool results before any images hoisted beside them.
249
+ const pendingImages = [];
250
+ const flushImages = () => {
251
+ if (pendingImages.length === 0)
252
+ return;
253
+ const previous = messages.at(-1);
254
+ if (previous?.role === "user")
255
+ messages[messages.length - 1] = { role: "user", content: [...previous.content, ...pendingImages] };
256
+ pendingImages.length = 0;
257
+ };
245
258
  for (const message of request.messages) {
259
+ if (message.role !== "tool")
260
+ flushImages();
246
261
  if (message.role === "system") {
247
262
  const part = yield* ProviderShared.wrappedSystemUpdate("Bedrock Converse", message);
248
263
  const content = textWithCache(breakpoints, part.text, part.cache);
@@ -317,7 +332,20 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
317
332
  for (const part of message.content) {
318
333
  if (!ProviderShared.supportsContent(part, ["tool-result"]))
319
334
  return yield* ProviderShared.unsupportedContent("Bedrock Converse", "tool", ["tool-result"]);
320
- content.push(yield* lowerToolResult(part, documentNames, normalizeID));
335
+ const result = yield* lowerToolResult(part, documentNames, normalizeID);
336
+ const images = hoistImages
337
+ ? result.toolResult.content.filter((item) => "image" in item)
338
+ : [];
339
+ const nonImageContent = result.toolResult.content.filter((item) => !("image" in item));
340
+ content.push(images.length === 0
341
+ ? result
342
+ : {
343
+ toolResult: {
344
+ ...result.toolResult,
345
+ content: nonImageContent.length > 0 ? nonImageContent : [{ text: "See attached image." }],
346
+ },
347
+ });
348
+ pendingImages.push(...images);
321
349
  const cachePoint = BedrockCache.block(breakpoints, part.cache);
322
350
  if (cachePoint)
323
351
  content.push(cachePoint);
@@ -328,6 +356,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
328
356
  else
329
357
  messages.push({ role: "user", content });
330
358
  }
359
+ flushImages();
331
360
  return messages;
332
361
  });
333
362
  // System prompts share the cache-point convention: emit the text block, then
@@ -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";
@@ -0,0 +1,299 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { Route } from "../route/client.js";
3
+ import { Endpoint } from "../route/endpoint.js";
4
+ import { Framing } from "../route/framing.js";
5
+ import { Protocol } from "../route/protocol.js";
6
+ import { LLMEvent, Usage } from "../schema/index.js";
7
+ import { ProviderShared } from "./shared.js";
8
+ import { Lifecycle } from "./utils/lifecycle.js";
9
+ import { ToolStream } from "./utils/tool-stream.js";
10
+ const ADAPTER = "cohere-chat";
11
+ export const DEFAULT_BASE_URL = "https://api.cohere.com/v2";
12
+ const Options = Schema.Struct({
13
+ thinking: Schema.optional(Schema.Struct({
14
+ type: Schema.optional(Schema.Literals(["enabled", "disabled"])),
15
+ tokenBudget: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
16
+ })),
17
+ });
18
+ const Content = Schema.Union([
19
+ Schema.Struct({ type: Schema.Literal("text"), text: Schema.String }),
20
+ Schema.Struct({ type: Schema.Literal("thinking"), thinking: Schema.String }),
21
+ Schema.Struct({ type: Schema.Literal("image_url"), image_url: Schema.Struct({ url: Schema.String }) }),
22
+ ]);
23
+ const ToolCall = Schema.Struct({
24
+ id: Schema.String,
25
+ type: Schema.Literal("function"),
26
+ function: Schema.Struct({ name: Schema.String, arguments: Schema.String }),
27
+ });
28
+ const Message = Schema.Struct({
29
+ role: Schema.Literals(["system", "user", "assistant", "tool"]),
30
+ content: Schema.optional(Schema.Union([Schema.String, Schema.Array(Content)])),
31
+ tool_calls: Schema.optional(Schema.Array(ToolCall)),
32
+ tool_call_id: Schema.optional(Schema.String),
33
+ tool_plan: Schema.optional(Schema.String),
34
+ });
35
+ const Body = Schema.Struct({
36
+ model: Schema.String,
37
+ messages: Schema.Array(Message),
38
+ stream: Schema.Literal(true),
39
+ tools: Schema.optional(Schema.Array(Schema.Struct({
40
+ type: Schema.Literal("function"),
41
+ function: Schema.Struct({
42
+ name: Schema.String,
43
+ description: Schema.optional(Schema.String),
44
+ parameters: Schema.Unknown,
45
+ }),
46
+ }))),
47
+ tool_choice: Schema.optional(Schema.Literals(["NONE", "REQUIRED"])),
48
+ thinking: Schema.optional(Schema.Struct({ type: Schema.String, token_budget: Schema.optional(Schema.Number) })),
49
+ max_tokens: Schema.optional(Schema.Number),
50
+ temperature: Schema.optional(Schema.Number),
51
+ p: Schema.optional(Schema.Number),
52
+ k: Schema.optional(Schema.Number),
53
+ seed: Schema.optional(Schema.Number),
54
+ stop_sequences: Schema.optional(Schema.Array(Schema.String)),
55
+ frequency_penalty: Schema.optional(Schema.Number),
56
+ presence_penalty: Schema.optional(Schema.Number),
57
+ });
58
+ const TokenCounts = Schema.Struct({
59
+ input_tokens: Schema.optional(Schema.Number),
60
+ output_tokens: Schema.optional(Schema.Number),
61
+ reasoning_tokens: Schema.optional(Schema.Number),
62
+ });
63
+ const NativeUsage = Schema.Struct({
64
+ tokens: Schema.optional(TokenCounts),
65
+ billed_units: Schema.optional(TokenCounts),
66
+ cached_tokens: Schema.optional(Schema.Number),
67
+ });
68
+ const Event = Schema.Union([
69
+ Schema.Struct({ type: Schema.Literal("message-start") }),
70
+ Schema.Struct({
71
+ type: Schema.Literals(["content-start", "content-delta"]),
72
+ index: Schema.Number,
73
+ delta: Schema.Struct({
74
+ message: Schema.Struct({
75
+ content: Schema.Struct({ text: Schema.optional(Schema.String), thinking: Schema.optional(Schema.String) }),
76
+ }),
77
+ }),
78
+ }),
79
+ Schema.Struct({ type: Schema.Literal("content-end"), index: Schema.Number }),
80
+ Schema.Struct({
81
+ type: Schema.Literal("tool-plan-delta"),
82
+ delta: Schema.Struct({ message: Schema.Struct({ tool_plan: Schema.String }) }),
83
+ }),
84
+ Schema.Struct({
85
+ type: Schema.Literals(["tool-call-start", "tool-call-delta"]),
86
+ index: Schema.Number,
87
+ delta: Schema.Struct({
88
+ message: Schema.Struct({
89
+ tool_calls: Schema.Struct({
90
+ id: Schema.optional(Schema.String),
91
+ function: Schema.Struct({ name: Schema.optional(Schema.String), arguments: Schema.optional(Schema.String) }),
92
+ }),
93
+ }),
94
+ }),
95
+ }),
96
+ Schema.Struct({ type: Schema.Literal("tool-call-end"), index: Schema.Number }),
97
+ Schema.Struct({
98
+ type: Schema.Literal("message-end"),
99
+ delta: Schema.Struct({ finish_reason: Schema.String, usage: Schema.optional(NativeUsage) }),
100
+ }),
101
+ // Citation output is outside this basic chat surface.
102
+ Schema.Struct({ type: Schema.Literals(["citation-start", "citation-end"]) }),
103
+ ]);
104
+ const TOOL_CHOICE = { auto: undefined, none: "NONE", required: "REQUIRED", tool: "REQUIRED" };
105
+ const fromRequest = Effect.fn("CohereChat.fromRequest")(function* (request) {
106
+ const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(request.providerOptions ?? {});
107
+ const flattened = ProviderShared.flattenToolRequest(request);
108
+ const messages = request.system.length
109
+ ? [
110
+ {
111
+ role: "system",
112
+ content: request.system.length === 1
113
+ ? request.system[0].text
114
+ : request.system.map((part) => ({ type: "text", text: part.text })),
115
+ },
116
+ ]
117
+ : [];
118
+ for (const message of flattened.request.messages) {
119
+ if (message.role === "system") {
120
+ messages.push({ role: "user", content: (yield* ProviderShared.wrappedSystemUpdate("Cohere Chat", message)).text });
121
+ continue;
122
+ }
123
+ if (message.role === "tool") {
124
+ for (const part of message.content) {
125
+ if (part.type !== "tool-result")
126
+ return yield* ProviderShared.unsupportedContent("Cohere Chat", "tool", ["tool-result"]);
127
+ if (part.result.type === "content" && part.result.value.some((item) => item.type === "file"))
128
+ return yield* ProviderShared.invalidRequest("Cohere Chat does not support file content in tool results");
129
+ messages.push({ role: "tool", tool_call_id: part.id, content: ProviderShared.toolResultText(part) });
130
+ }
131
+ continue;
132
+ }
133
+ const content = [];
134
+ const calls = [];
135
+ const plans = [];
136
+ for (const part of message.content) {
137
+ if (part.type === "text") {
138
+ content.push({ type: "text", text: part.text });
139
+ continue;
140
+ }
141
+ if (message.role === "assistant" && part.type === "reasoning") {
142
+ if (part.providerMetadata?.cohere?.toolPlan === true)
143
+ plans.push(part.text);
144
+ else
145
+ content.push({ type: "thinking", thinking: part.text });
146
+ continue;
147
+ }
148
+ if (message.role === "assistant" && part.type === "tool-call") {
149
+ const args = ProviderShared.encodeJson(part.input);
150
+ calls.push({ id: part.id, type: "function", function: { name: part.name, arguments: args } });
151
+ continue;
152
+ }
153
+ if (message.role === "user" && part.type === "media" && part.media.mediaType.startsWith("image/")) {
154
+ const url = ProviderShared.mediaUrl(part.media) ??
155
+ (yield* ProviderShared.requireInlineMedia("Cohere Chat", part.media)).dataUrl;
156
+ content.push({ type: "image_url", image_url: { url } });
157
+ continue;
158
+ }
159
+ return yield* ProviderShared.unsupportedContent("Cohere Chat", message.role, message.role === "user" ? ["text", "media"] : ["text", "reasoning", "tool-call"]);
160
+ }
161
+ messages.push({
162
+ role: message.role,
163
+ content: content.length ? content : undefined,
164
+ tool_calls: calls.length ? calls : undefined,
165
+ tool_plan: plans.length ? plans.join("") : undefined,
166
+ });
167
+ }
168
+ const selected = request.toolChoice?.type === "tool" ? request.toolChoice.name : undefined;
169
+ const tools = selected === undefined ? flattened.tools : flattened.tools.filter((tool) => tool.name === selected);
170
+ if (selected !== undefined && tools.length === 0)
171
+ return yield* ProviderShared.invalidRequest("Cohere Chat tool choice must name an available tool");
172
+ if (tools.some((tool) => tool.native !== undefined))
173
+ return yield* ProviderShared.invalidRequest("Cohere Chat does not support provider-defined tools");
174
+ return {
175
+ model: request.model.id,
176
+ messages,
177
+ stream: true,
178
+ tools: tools.length
179
+ ? tools.map((tool) => ({
180
+ type: "function",
181
+ function: { name: tool.name, description: tool.description, parameters: tool.inputSchema },
182
+ }))
183
+ : undefined,
184
+ tool_choice: TOOL_CHOICE[request.toolChoice?.type ?? "auto"],
185
+ thinking: options.thinking && {
186
+ type: options.thinking.type ?? "enabled",
187
+ // Cohere rejects budgets above max_tokens; fitting also leaves room for the answer.
188
+ token_budget: options.thinking.tokenBudget === undefined
189
+ ? undefined
190
+ : ProviderShared.fitThinkingBudget(options.thinking.tokenBudget, request.generation?.maxTokens),
191
+ },
192
+ max_tokens: request.generation?.maxTokens,
193
+ temperature: request.generation?.temperature,
194
+ p: request.generation?.topP,
195
+ k: request.generation?.topK,
196
+ seed: request.generation?.seed,
197
+ stop_sequences: request.generation?.stop,
198
+ frequency_penalty: request.generation?.frequencyPenalty,
199
+ presence_penalty: request.generation?.presencePenalty,
200
+ };
201
+ });
202
+ const finishReason = (raw) => {
203
+ switch (raw) {
204
+ case "COMPLETE":
205
+ case "STOP_SEQUENCE":
206
+ return { normalized: "stop", raw };
207
+ case "MAX_TOKENS":
208
+ return { normalized: "length", raw };
209
+ case "TOOL_CALL":
210
+ return { normalized: "tool-calls", raw };
211
+ case "ERROR":
212
+ case "TIMEOUT":
213
+ return { normalized: "error", raw };
214
+ default:
215
+ return { normalized: "unknown", raw };
216
+ }
217
+ };
218
+ const mapUsage = (usage) => new Usage({
219
+ inputTokens: usage.tokens?.input_tokens,
220
+ outputTokens: usage.tokens?.output_tokens,
221
+ nonCachedInputTokens: ProviderShared.subtractTokens(usage.tokens?.input_tokens, usage.cached_tokens),
222
+ cacheReadInputTokens: usage.cached_tokens,
223
+ reasoningTokens: usage.tokens?.reasoning_tokens,
224
+ totalTokens: ProviderShared.totalTokens(usage.tokens?.input_tokens, usage.tokens?.output_tokens, undefined),
225
+ providerMetadata: { cohere: usage },
226
+ });
227
+ // Lifecycle deltas open blocks on demand and ends are no-ops for closed blocks, so content-start needs no handling.
228
+ const step = Effect.fnUntraced(function* (state, event) {
229
+ const events = [];
230
+ switch (event.type) {
231
+ case "message-start":
232
+ return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, events];
233
+ case "content-delta": {
234
+ const id = String(event.index);
235
+ const content = event.delta.message.content;
236
+ const lifecycle = content.thinking !== undefined
237
+ ? Lifecycle.reasoningDelta(state.lifecycle, events, id, content.thinking)
238
+ : Lifecycle.textDelta(state.lifecycle, events, id, content.text ?? "");
239
+ return [{ ...state, lifecycle }, events];
240
+ }
241
+ case "content-end": {
242
+ const id = String(event.index);
243
+ const lifecycle = Lifecycle.textEnd(Lifecycle.reasoningEnd(state.lifecycle, events, id), events, id);
244
+ return [{ ...state, lifecycle }, events];
245
+ }
246
+ case "tool-plan-delta": {
247
+ const plan = event.delta.message.tool_plan;
248
+ const lifecycle = Lifecycle.reasoningDelta(state.lifecycle, events, "tool-plan", plan, {
249
+ cohere: { toolPlan: true },
250
+ });
251
+ return [{ ...state, lifecycle }, events];
252
+ }
253
+ case "tool-call-start":
254
+ case "tool-call-delta": {
255
+ const call = event.delta.message.tool_calls;
256
+ const result = ToolStream.appendOrStart(ADAPTER, state.tools, event.index, { id: call.id, name: call.function.name, text: call.function.arguments ?? "" }, "Cohere tool call is missing id or name");
257
+ if (ToolStream.isError(result))
258
+ return yield* result;
259
+ return [{ ...state, tools: result.tools }, result.events];
260
+ }
261
+ case "tool-call-end": {
262
+ const result = yield* ToolStream.finish(ADAPTER, state.tools, event.index);
263
+ return [{ ...state, tools: result.tools }, result.events ?? []];
264
+ }
265
+ case "message-end": {
266
+ const pending = yield* ToolStream.finishAll(ADAPTER, state.tools);
267
+ events.push(...pending.events);
268
+ const lifecycle = Lifecycle.finish(state.lifecycle, events, {
269
+ reason: finishReason(event.delta.finish_reason),
270
+ usage: event.delta.usage && mapUsage(event.delta.usage),
271
+ });
272
+ return [{ tools: pending.tools, lifecycle, finished: true }, events];
273
+ }
274
+ default:
275
+ return [state, events];
276
+ }
277
+ });
278
+ export const protocol = Protocol.make({
279
+ id: ADAPTER,
280
+ body: { schema: Body, from: fromRequest },
281
+ stream: {
282
+ event: Protocol.jsonEvent(Event),
283
+ initial: () => ({ lifecycle: Lifecycle.initial(), tools: ToolStream.empty(), finished: false }),
284
+ step,
285
+ terminal: (event) => event.type === "message-end",
286
+ onHalt: (state) => state.finished
287
+ ? Effect.succeed([])
288
+ : Effect.fail(ProviderShared.eventError(ADAPTER, "Cohere stream ended without message-end")),
289
+ },
290
+ });
291
+ export const route = Route.make({
292
+ id: ADAPTER,
293
+ provider: "cohere",
294
+ providerMetadataKey: "cohere",
295
+ protocol,
296
+ endpoint: Endpoint.path("/chat", { baseURL: DEFAULT_BASE_URL }),
297
+ framing: Framing.sse,
298
+ });
299
+ export * as CohereChat from "./cohere-chat.js";
@@ -32,7 +32,7 @@ const OUTPUT_FORMATS = {
32
32
  opus: "opus_48000_64",
33
33
  };
34
34
  /** WAV is served only by the non-streaming endpoints. */
35
- const outputFormat = Effect.fn("ElevenLabsSpeech.outputFormat")(function* (request) {
35
+ const outputFormat = Effect.fnUntraced(function* (request) {
36
36
  const format = request.providerOptions?.outputFormat ?? OUTPUT_FORMATS[request.format ?? "mp3"];
37
37
  if (format === undefined)
38
38
  return yield* route.unsupported("media.format", `${route.name} has no default output format for "${request.format}"; pass providerOptions.outputFormat`);
@@ -55,7 +55,7 @@ const path = (request) => `${PATH}/${encodeURIComponent(SpeechStream.voiceID(req
55
55
  // ---------------------------------------------------------------------------
56
56
  // 6. Stream parsing
57
57
  // ---------------------------------------------------------------------------
58
- const onRecord = Effect.fn("ElevenLabsSpeech.onRecord")(function* (state, frame) {
58
+ const onRecord = Effect.fnUntraced(function* (state, frame) {
59
59
  const record = yield* decodeRecord(frame);
60
60
  const [next, events] = SpeechStream.delta(state, record.audio_base64);
61
61
  const alignment = record.alignment;
@@ -80,7 +80,7 @@ const describeOutput = (format) => {
80
80
  const encoding = PCM_CODECS[codec];
81
81
  return encoding === undefined ? SpeechStream.container(codec, sampleRate) : SpeechStream.pcm(encoding, sampleRate);
82
82
  };
83
- const finish = Effect.fn("ElevenLabsSpeech.finish")(function* (state, context) {
83
+ const finish = Effect.fnUntraced(function* (state, context) {
84
84
  const requestID = context.http.headers["request-id"];
85
85
  return yield* SpeechStream.finish(route, state, {
86
86
  ...describeOutput(yield* outputFormat(context.request)),
@@ -158,7 +158,7 @@ export declare const protocol: Protocol<{
158
158
  readonly serviceTier?: string | undefined;
159
159
  readonly toolConfig?: {
160
160
  readonly functionCallingConfig: {
161
- readonly mode: "AUTO" | "NONE" | "ANY";
161
+ readonly mode: "NONE" | "AUTO" | "ANY";
162
162
  readonly allowedFunctionNames?: readonly string[] | undefined;
163
163
  };
164
164
  } | undefined;
@@ -272,7 +272,7 @@ export declare const route: Route<{
272
272
  readonly serviceTier?: string | undefined;
273
273
  readonly toolConfig?: {
274
274
  readonly functionCallingConfig: {
275
- readonly mode: "AUTO" | "NONE" | "ANY";
275
+ readonly mode: "NONE" | "AUTO" | "ANY";
276
276
  readonly allowedFunctionNames?: readonly string[] | undefined;
277
277
  };
278
278
  } | undefined;
@@ -201,7 +201,7 @@ const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini",
201
201
  required: () => ({ functionCallingConfig: { mode: "ANY" } }),
202
202
  tool: (name) => ({ functionCallingConfig: { mode: "ANY", allowedFunctionNames: [name] } }),
203
203
  });
204
- const lowerContentPart = Effect.fn("Gemini.lowerContentPart")(function* (part) {
204
+ const lowerContentPart = Effect.fnUntraced(function* (part) {
205
205
  if (part.type === "text")
206
206
  return { text: part.text };
207
207
  return yield* GeminiGenerateContent.mediaPart("Gemini", part.media);
@@ -217,7 +217,7 @@ const lowerToolCall = (part, omitIds, metadataKey) => ({
217
217
  functionCall: { ...(omitIds ? {} : { id: part.id }), name: part.name, args: part.input },
218
218
  thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey),
219
219
  });
220
- const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
220
+ const lowerMessages = Effect.fnUntraced(function* (request) {
221
221
  const contents = [];
222
222
  const metadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
223
223
  const omitCallIds = omitsFunctionCallIds(request.model.id);
@@ -382,7 +382,7 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
382
382
  contents: yield* lowerMessages(flattened.request),
383
383
  safetySettings: options.safetySettings,
384
384
  serviceTier: options.serviceTier,
385
- systemInstruction: request.system.length === 0 ? undefined : { parts: [{ text: ProviderShared.joinText(request.system) }] },
385
+ systemInstruction: request.system.length === 0 ? undefined : { parts: request.system.map((part) => ({ text: part.text })) },
386
386
  tools: hasTools
387
387
  ? [
388
388
  {