@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
|
@@ -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),
|
|
@@ -236,13 +236,28 @@ const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (
|
|
|
236
236
|
},
|
|
237
237
|
};
|
|
238
238
|
});
|
|
239
|
-
|
|
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
|
-
|
|
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.
|
|
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);
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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: "
|
|
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: "
|
|
275
|
+
readonly mode: "NONE" | "AUTO" | "ANY";
|
|
276
276
|
readonly allowedFunctionNames?: readonly string[] | undefined;
|
|
277
277
|
};
|
|
278
278
|
} | undefined;
|
package/dist/protocols/gemini.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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:
|
|
385
|
+
systemInstruction: request.system.length === 0 ? undefined : { parts: request.system.map((part) => ({ text: part.text })) },
|
|
386
386
|
tools: hasTools
|
|
387
387
|
? [
|
|
388
388
|
{
|