@opencode/ai 2.0.26 → 2.0.28
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 +8 -2
- package/dist/cache-policy.js +20 -11
- package/dist/experimental/system-one.js +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/media.js +7 -6
- package/dist/protocols/alibaba-chat.d.ts +2 -2
- package/dist/protocols/alibaba-messages.d.ts +1 -1
- package/dist/protocols/anthropic-messages.js +1 -1
- package/dist/protocols/bedrock-converse.js +4 -3
- package/dist/protocols/bedrock-event-stream.js +4 -3
- package/dist/protocols/gemini.d.ts +73 -9
- package/dist/protocols/gemini.js +111 -17
- package/dist/protocols/meta-responses.js +3 -2
- package/dist/protocols/mistral-chat.d.ts +1 -1
- package/dist/protocols/mistral-chat.js +4 -2
- package/dist/protocols/open-responses-channel.d.ts +1 -1
- package/dist/protocols/open-responses-channel.js +7 -2
- package/dist/protocols/openai-chat.js +6 -2
- package/dist/protocols/openai-responses.d.ts +5 -5
- package/dist/protocols/openai-responses.js +4 -3
- package/dist/protocols/shared.d.ts +6 -2
- package/dist/protocols/shared.js +29 -3
- package/dist/protocols/utils/bedrock-auth.js +1 -1
- package/dist/protocols/utils/bedrock-media.js +4 -3
- package/dist/protocols/utils/fal-queue.d.ts +1 -1
- package/dist/protocols/utils/media-input.js +4 -3
- package/dist/protocols/utils/responses-compaction.js +6 -1
- package/dist/protocols/utils/tool-schema.js +8 -2
- package/dist/providers/alibaba.d.ts +5 -5
- package/dist/providers/amazon-bedrock-mantle.js +2 -2
- package/dist/providers/anthropic-compatible.js +2 -2
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/azure.d.ts +2 -2
- package/dist/providers/azure.js +3 -3
- package/dist/providers/baseten.js +2 -2
- package/dist/providers/cerebras.js +2 -2
- package/dist/providers/cloudflare-ai-gateway.d.ts +2 -2
- package/dist/providers/cloudflare-ai-gateway.js +2 -2
- package/dist/providers/cloudflare-workers-ai.js +2 -2
- package/dist/providers/cohere/chat.js +1 -1
- package/dist/providers/cohere.js +1 -1
- package/dist/providers/deepinfra.js +2 -2
- package/dist/providers/deepseek.js +2 -2
- package/dist/providers/fireworks.js +2 -2
- package/dist/providers/google/interactions.js +2 -2
- package/dist/providers/google-vertex/interactions.d.ts +2 -0
- package/dist/providers/google-vertex/interactions.js +1 -0
- package/dist/providers/google-vertex/mistral.d.ts +2 -0
- package/dist/providers/google-vertex/mistral.js +1 -0
- package/dist/providers/google-vertex-chat.js +3 -3
- package/dist/providers/google-vertex-interactions.d.ts +112 -0
- package/dist/providers/google-vertex-interactions.js +64 -0
- package/dist/providers/google-vertex-messages.js +3 -3
- package/dist/providers/google-vertex-mistral.d.ts +124 -0
- package/dist/providers/google-vertex-mistral.js +69 -0
- package/dist/providers/google-vertex-responses.js +3 -3
- package/dist/providers/google-vertex-shared.d.ts +3 -1
- package/dist/providers/google-vertex-shared.js +29 -3
- package/dist/providers/google-vertex.d.ts +2 -1
- package/dist/providers/google-vertex.js +4 -5
- package/dist/providers/google.d.ts +2 -1
- package/dist/providers/google.js +2 -2
- package/dist/providers/groq.js +2 -2
- package/dist/providers/index.d.ts +3 -1
- package/dist/providers/index.js +3 -1
- package/dist/providers/meta.js +1 -1
- package/dist/providers/minimax/chat.js +1 -1
- package/dist/providers/minimax/responses.js +1 -1
- package/dist/providers/minimax.js +1 -1
- package/dist/providers/mistral.js +2 -2
- package/dist/providers/moonshot/messages.js +1 -1
- package/dist/providers/moonshot/responses.js +1 -1
- package/dist/providers/moonshot.js +1 -1
- package/dist/providers/openai-compatible-responses.js +2 -2
- package/dist/providers/openai-compatible.js +2 -2
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +2 -2
- package/dist/providers/{opencode-zen.d.ts → opencode.d.ts} +1 -1
- package/dist/providers/{opencode-zen.js → opencode.js} +2 -2
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/togetherai.js +2 -2
- package/dist/providers/venice.js +1 -1
- package/dist/providers/vercel-ai-gateway.js +2 -2
- package/dist/providers/xai.js +1 -1
- package/dist/providers/zai-coding-plan/messages.js +1 -1
- package/dist/providers/zai-coding-plan/responses.js +1 -1
- package/dist/providers/zai-coding-plan.js +1 -1
- package/dist/providers/zai.js +1 -1
- package/dist/route/auth.d.ts +1 -1
- package/dist/route/auth.js +2 -2
- package/dist/route/executor-service.d.ts +1 -1
- package/dist/route/executor.d.ts +1 -1
- package/dist/route/executor.js +1 -1
- package/dist/route/framing.js +1 -1
- package/dist/route/media-protocol.d.ts +1 -1
- package/dist/route/media-protocol.js +1 -1
- package/dist/route/media.js +1 -1
- package/dist/route/transport/http.d.ts +1 -1
- package/dist/route/transport/http.js +5 -4
- package/dist/route/transport/websocket-channel.d.ts +1 -1
- package/dist/route/transport/websocket.d.ts +4 -4
- package/dist/route/transport/websocket.js +4 -9
- package/dist/schema/messages.js +9 -8
- package/dist/schema/options.d.ts +15 -1
- package/dist/schema/options.js +25 -5
- package/dist/tool.d.ts +10 -0
- package/dist/tool.js +21 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -32,6 +32,11 @@ Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployme
|
|
|
32
32
|
`Google.configure({ apiKey }).interactions(modelID)` selects the Interactions API; `.model(modelID)` still selects
|
|
33
33
|
GenerateContent. The package entrypoint is `@opencode/ai/providers/google/interactions`.
|
|
34
34
|
|
|
35
|
+
For Vertex AI, use `GoogleVertexInteractions.configure({ project, location: "global" }).model(modelID)` or
|
|
36
|
+
`@opencode/ai/providers/google-vertex/interactions`. It uses Application Default Credentials by default, accepts
|
|
37
|
+
an explicit `accessToken` or `auth` override, and supports Express mode with `apiKey`. Vertex metadata and raw usage
|
|
38
|
+
live under `providerMetadata.vertex`. The default is still full-history replay with `store: false`.
|
|
39
|
+
|
|
35
40
|
```ts
|
|
36
41
|
const model = Google.configure({ apiKey }).interactions("gemini-3.8-flash")
|
|
37
42
|
const response = yield* LLM.generate({
|
|
@@ -177,7 +182,7 @@ console.log(response.answers.department.choice)
|
|
|
177
182
|
console.log(response.answers.refund.probability)
|
|
178
183
|
```
|
|
179
184
|
|
|
180
|
-
`TypeSafeAI` reads `TYPESAFE_API_KEY`. `
|
|
185
|
+
`TypeSafeAI` reads `TYPESAFE_API_KEY`. `OpenCode` exposes the same selector and reads
|
|
181
186
|
`OPENCODE_API_KEY`. OpenRouter and Vercel AI Gateway use the same provider shape:
|
|
182
187
|
|
|
183
188
|
```ts
|
|
@@ -1316,13 +1321,14 @@ APIs have separate entrypoints:
|
|
|
1316
1321
|
- `@opencode/ai/providers/openai-compatible/responses`
|
|
1317
1322
|
- `@opencode/ai/providers/anthropic-compatible`
|
|
1318
1323
|
- `@opencode/ai/providers/google-vertex/gemini`
|
|
1324
|
+
- `@opencode/ai/providers/google-vertex/interactions`
|
|
1319
1325
|
- `@opencode/ai/providers/google-vertex/chat`
|
|
1320
1326
|
- `@opencode/ai/providers/google-vertex/responses`
|
|
1321
1327
|
- `@opencode/ai/providers/google-vertex/messages`
|
|
1322
1328
|
|
|
1323
1329
|
OpenAI Responses has one semantic route and uses HTTP by default. Advanced callers may supply a per-call WebSocket channel executor through `StreamOptions`; transport policy does not change provider settings, model identity, or route identity. The provider-neutral Open Responses implementation owns the reusable WebSocket request and event contract, while each provider opts in with its own handshake and connection policy. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; the Responses adapter at `providers/openai-compatible/responses` uses the provider-neutral Open Responses protocol. OpenAI Responses extends that baseline with OpenAI tools, event variants, metadata, and defaults. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
|
|
1324
1330
|
|
|
1325
|
-
Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead
|
|
1331
|
+
Vertex Gemini, Vertex Interactions, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini and Interactions instead select express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present (explicit `project` or `location` selects ADC over an ambient key). Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
|
|
1326
1332
|
|
|
1327
1333
|
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
|
|
1328
1334
|
|
package/dist/cache-policy.js
CHANGED
|
@@ -143,17 +143,7 @@ const countHints = (request) => countToolHints(request.tools) +
|
|
|
143
143
|
request.messages.reduce((count, message) => count +
|
|
144
144
|
message.content.reduce((contentCount, part) => contentCount + ("cache" in part && part.cache !== undefined ? 1 : 0), 0), 0);
|
|
145
145
|
export const applyCachePolicy = (request) => {
|
|
146
|
-
const
|
|
147
|
-
if (!RESPECTS_INLINE_HINTS.has(route))
|
|
148
|
-
return request;
|
|
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")
|
|
153
|
-
? request.model.id.toLowerCase().startsWith("qwen")
|
|
154
|
-
? QWEN
|
|
155
|
-
: NONE
|
|
156
|
-
: resolve(request.cache);
|
|
146
|
+
const policy = selectPolicy(request);
|
|
157
147
|
if (!policy.tools && !policy.system && !policy.messages)
|
|
158
148
|
return request;
|
|
159
149
|
const hint = makeHint(policy.ttlSeconds);
|
|
@@ -165,3 +155,22 @@ export const applyCachePolicy = (request) => {
|
|
|
165
155
|
return request;
|
|
166
156
|
return LLMRequest.update(request, { tools, system, messages });
|
|
167
157
|
};
|
|
158
|
+
// A declared cache format overrides the route allowlist and its model-ID heuristics.
|
|
159
|
+
const selectPolicy = (request) => {
|
|
160
|
+
const format = request.model.compatibility?.cacheFormat;
|
|
161
|
+
const route = request.model.route.id;
|
|
162
|
+
const auto = request.cache === undefined || request.cache === "auto";
|
|
163
|
+
if (format === "alibaba")
|
|
164
|
+
return auto ? QWEN : { ...resolve(request.cache), tools: false };
|
|
165
|
+
if (format === "anthropic")
|
|
166
|
+
return resolve(request.cache);
|
|
167
|
+
if (!RESPECTS_INLINE_HINTS.has(route))
|
|
168
|
+
return NONE;
|
|
169
|
+
if (!auto)
|
|
170
|
+
return resolve(request.cache);
|
|
171
|
+
if (route === "openrouter" || route === "vercel-ai-gateway-messages")
|
|
172
|
+
return gatewayPolicy(request.model.id);
|
|
173
|
+
if (route === "alibaba-chat")
|
|
174
|
+
return request.model.id.toLowerCase().startsWith("qwen") ? QWEN : NONE;
|
|
175
|
+
return AUTO;
|
|
176
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import { Headers, HttpClientRequest } from "effect/
|
|
2
|
+
import { Headers, HttpClientRequest } from "effect/http";
|
|
3
3
|
import { ChoiceQuestion, EvaluationInput, EvaluationModel, EvaluationResponse, EvaluationRounding, ScoreQuestion, } from "./evaluation.js";
|
|
4
4
|
import { Auth } from "../route/auth.js";
|
|
5
5
|
import { AIError, HttpContext, HttpOptions, InvalidProviderOutputError, InvalidRequestError, ModelID, Usage, mergeJsonRecords, } from "../schema/index.js";
|
package/dist/media.d.ts
CHANGED
|
@@ -100,12 +100,12 @@ export declare class Asset {
|
|
|
100
100
|
};
|
|
101
101
|
info: {
|
|
102
102
|
readonly format?: string | undefined;
|
|
103
|
+
readonly encoding?: string | undefined;
|
|
103
104
|
readonly width?: number | undefined;
|
|
104
105
|
readonly height?: number | undefined;
|
|
105
106
|
readonly durationSeconds?: number | undefined;
|
|
106
107
|
readonly sampleRate?: number | undefined;
|
|
107
108
|
readonly channels?: number | undefined;
|
|
108
|
-
readonly encoding?: string | undefined;
|
|
109
109
|
} | undefined;
|
|
110
110
|
providerMetadata: {
|
|
111
111
|
readonly [x: string]: {
|
package/dist/media.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * as Media from "./media.js";
|
|
2
|
-
import { Effect,
|
|
3
|
-
import {
|
|
2
|
+
import { Effect, FileSystem, Schema, SchemaGetter } from "effect";
|
|
3
|
+
import { Base64 } from "effect/encoding";
|
|
4
|
+
import { HttpClientRequest } from "effect/http";
|
|
4
5
|
import { ProviderID } from "./schema/ids.js";
|
|
5
6
|
import { AIError, HttpContext, InvalidProviderOutputError, InvalidRequestError } from "./schema/errors.js";
|
|
6
7
|
import { ProviderMetadata } from "./schema/options.js";
|
|
@@ -105,7 +106,7 @@ export class Asset {
|
|
|
105
106
|
const source = this.source;
|
|
106
107
|
if (source.type !== "bytes" && source.type !== "base64")
|
|
107
108
|
return undefined;
|
|
108
|
-
const base64 = source.type === "base64" ? source.data : (this.#base64 ??=
|
|
109
|
+
const base64 = source.type === "base64" ? source.data : (this.#base64 ??= Base64.encode(source.data));
|
|
109
110
|
const mime = this.mediaType.toLowerCase();
|
|
110
111
|
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` };
|
|
111
112
|
}
|
|
@@ -120,7 +121,7 @@ export class Asset {
|
|
|
120
121
|
if (source.type === "ref")
|
|
121
122
|
return Effect.fail(invalid(`Cannot materialize provider ref ${source.provider}:${source.id}`));
|
|
122
123
|
const decoded = source.type === "base64"
|
|
123
|
-
? Effect.fromResult(
|
|
124
|
+
? Effect.fromResult(Base64.decode(source.data)).pipe(Effect.mapError((cause) => invalid(`Media asset contains invalid base64 data`, cause)))
|
|
124
125
|
: download(source, this.headers);
|
|
125
126
|
return decoded.pipe(Effect.tap((data) => Effect.sync(() => (this.#bytes = data))));
|
|
126
127
|
});
|
|
@@ -132,7 +133,7 @@ export class Asset {
|
|
|
132
133
|
return Effect.succeed(source.data);
|
|
133
134
|
if (this.#base64 !== undefined)
|
|
134
135
|
return Effect.succeed(this.#base64);
|
|
135
|
-
return this.bytes().pipe(Effect.map((data) => (this.#base64 =
|
|
136
|
+
return this.bytes().pipe(Effect.map((data) => (this.#base64 = Base64.encode(data))));
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
dataUrl() {
|
|
@@ -145,7 +146,7 @@ export class Asset {
|
|
|
145
146
|
toJSON() {
|
|
146
147
|
const source = this.source;
|
|
147
148
|
return {
|
|
148
|
-
source: source.type === "bytes" ? { ...source, data:
|
|
149
|
+
source: source.type === "bytes" ? { ...source, data: Base64.encode(source.data) } : source,
|
|
149
150
|
info: this.info,
|
|
150
151
|
providerMetadata: this.providerMetadata,
|
|
151
152
|
};
|
|
@@ -10,7 +10,7 @@ declare const Options: Schema.Struct<{
|
|
|
10
10
|
readonly preserveThinking: Schema.optional<Schema.Boolean>;
|
|
11
11
|
readonly clearThinking: Schema.optional<Schema.Boolean>;
|
|
12
12
|
readonly thinking: Schema.optional<Schema.Struct<{
|
|
13
|
-
readonly type: Schema.declare<
|
|
13
|
+
readonly type: Schema.declare<(string & {}) | "disabled" | "adaptive", (string & {}) | "disabled" | "adaptive">;
|
|
14
14
|
}>>;
|
|
15
15
|
readonly toolStream: Schema.optional<Schema.Boolean>;
|
|
16
16
|
readonly parallelToolCalls: Schema.optional<Schema.Boolean>;
|
|
@@ -140,7 +140,7 @@ export declare const protocol: Protocol<{
|
|
|
140
140
|
readonly temperature?: number | undefined;
|
|
141
141
|
readonly seed?: number | undefined;
|
|
142
142
|
readonly thinking?: {
|
|
143
|
-
readonly type:
|
|
143
|
+
readonly type: (string & {}) | "disabled" | "adaptive";
|
|
144
144
|
} | undefined;
|
|
145
145
|
readonly tool_choice?: "required" | "auto" | "none" | {
|
|
146
146
|
readonly type: "function";
|
|
@@ -5,7 +5,7 @@ import { OpenResponsesOptions } from "./utils/open-responses-options.js";
|
|
|
5
5
|
declare const Options: Schema.Struct<{
|
|
6
6
|
readonly effort: Schema.optional<Schema.declare<OpenResponsesOptions.ReasoningEffort, OpenResponsesOptions.ReasoningEffort>>;
|
|
7
7
|
readonly thinking: Schema.optional<Schema.Struct<{
|
|
8
|
-
readonly type: Schema.declare<"enabled" | "disabled"
|
|
8
|
+
readonly type: Schema.declare<(string & {}) | "enabled" | "disabled", (string & {}) | "enabled" | "disabled">;
|
|
9
9
|
readonly budgetTokens: Schema.optional<Schema.Int>;
|
|
10
10
|
readonly budget_tokens: Schema.optional<Schema.Int>;
|
|
11
11
|
}>>;
|
|
@@ -6,7 +6,7 @@ import { Auth } from "../route/auth.js";
|
|
|
6
6
|
import { Endpoint } from "../route/endpoint.js";
|
|
7
7
|
import { Framing } from "../route/framing.js";
|
|
8
8
|
import { Protocol } from "../route/protocol.js";
|
|
9
|
-
import { Headers } from "effect/
|
|
9
|
+
import { Headers } from "effect/http";
|
|
10
10
|
import { HttpTransport } from "../route/transport/index.js";
|
|
11
11
|
import { AIError, HttpOptions, LLMRequest, LLMEvent, mergeJsonRecords, Usage, } from "../schema/index.js";
|
|
12
12
|
import { JsonObject, knownString, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Base64 } from "effect/encoding";
|
|
2
3
|
import { Route } from "../route/client.js";
|
|
3
4
|
import { Endpoint } from "../route/endpoint.js";
|
|
4
5
|
import { Protocol } from "../route/protocol.js";
|
|
@@ -489,7 +490,7 @@ const mapUsage = (usage, providerMetadataKey) => {
|
|
|
489
490
|
providerMetadata: { [providerMetadataKey]: usage },
|
|
490
491
|
});
|
|
491
492
|
};
|
|
492
|
-
const encodeRedactedContent = (chunks) =>
|
|
493
|
+
const encodeRedactedContent = (chunks) => Base64.encode(concatBytes(chunks));
|
|
493
494
|
const step = (state, event) => Effect.gen(function* () {
|
|
494
495
|
if (event.contentBlockStart?.start?.toolUse) {
|
|
495
496
|
const index = event.contentBlockStart.contentBlockIndex;
|
|
@@ -530,7 +531,7 @@ const step = (state, event) => Effect.gen(function* () {
|
|
|
530
531
|
const redactedChunk = yield* (() => {
|
|
531
532
|
if (reasoning.redactedContent === undefined)
|
|
532
533
|
return Effect.succeed(undefined);
|
|
533
|
-
return Effect.fromResult(
|
|
534
|
+
return Effect.fromResult(Base64.decode(reasoning.redactedContent)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Bedrock Converse reasoningContent.redactedContent contains invalid base64 data", undefined, cause)));
|
|
534
535
|
})();
|
|
535
536
|
const redactedChunks = state.reasoningRedactedContent[index] ?? [];
|
|
536
537
|
if (redactedChunk !== undefined)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventStreamCodec } from "@smithy/eventstream-codec";
|
|
2
2
|
import { fromUtf8, toUtf8 } from "@smithy/util-utf8";
|
|
3
|
-
import { Effect,
|
|
3
|
+
import { Effect, Stream } from "effect";
|
|
4
|
+
import { Base64 } from "effect/encoding";
|
|
4
5
|
import { AIError, AIErrorReason, InvalidProviderOutputError } from "../schema/index.js";
|
|
5
6
|
import { Framing } from "../route/framing.js";
|
|
6
7
|
import { ProviderShared } from "./shared.js";
|
|
@@ -31,7 +32,7 @@ const consumeFrames = (route) => (state, input) => Effect.gen(function* () {
|
|
|
31
32
|
route,
|
|
32
33
|
classification: "incomplete-stream",
|
|
33
34
|
message: `Incomplete Bedrock Converse event-stream frame: ${remaining.length} buffered bytes remain at end of stream`,
|
|
34
|
-
body:
|
|
35
|
+
body: Base64.encode(remaining),
|
|
35
36
|
}),
|
|
36
37
|
});
|
|
37
38
|
return [state, []];
|
|
@@ -45,7 +46,7 @@ const consumeFrames = (route) => (state, input) => Effect.gen(function* () {
|
|
|
45
46
|
break;
|
|
46
47
|
const decoded = yield* Effect.try({
|
|
47
48
|
try: () => eventCodec.decode(view.subarray(0, totalLength)),
|
|
48
|
-
catch: (error) => ProviderShared.eventError(route, `Failed to decode Bedrock Converse event-stream frame: ${error instanceof Error ? error.message : String(error)}`,
|
|
49
|
+
catch: (error) => ProviderShared.eventError(route, `Failed to decode Bedrock Converse event-stream frame: ${error instanceof Error ? error.message : String(error)}`, Base64.encode(view.subarray(0, totalLength)), error),
|
|
49
50
|
});
|
|
50
51
|
cursor = { buffer: cursor.buffer, offset: cursor.offset + totalLength };
|
|
51
52
|
const payload = utf8.decode(decoded.body);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { Route } from "../route/client.js";
|
|
3
3
|
import { Protocol } from "../route/protocol.js";
|
|
4
|
+
import { HttpTransport, type Transport, type TransportPrepareInput } from "../route/transport/index.js";
|
|
4
5
|
import { type ProviderOptions } from "../schema/index.js";
|
|
5
6
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
6
7
|
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
@@ -100,8 +101,54 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
100
101
|
readonly thinkingLevel: Schema.optional<Schema.declare<import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">, import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">>>;
|
|
101
102
|
}>>;
|
|
102
103
|
}>>;
|
|
104
|
+
continuationToken: Schema.optional<Schema.String>;
|
|
103
105
|
}>;
|
|
104
106
|
export type GeminiBody = Schema.Schema.Type<typeof GeminiBody>;
|
|
107
|
+
declare const GeminiUsage: Schema.Struct<{
|
|
108
|
+
readonly cachedContentTokenCount: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
109
|
+
readonly thoughtsTokenCount: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
110
|
+
readonly promptTokenCount: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
111
|
+
readonly candidatesTokenCount: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
112
|
+
readonly totalTokenCount: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
113
|
+
}>;
|
|
114
|
+
type GeminiUsage = Schema.Schema.Type<typeof GeminiUsage>;
|
|
115
|
+
declare const GeminiPromptFeedback: Schema.StructWithRest<Schema.Struct<{
|
|
116
|
+
readonly blockReason: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
117
|
+
readonly blockReasonMessage: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
118
|
+
readonly safetyRatings: Schema.optional<Schema.NullOr<Schema.Unknown>>;
|
|
119
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
|
|
120
|
+
type GeminiPromptFeedback = Schema.Schema.Type<typeof GeminiPromptFeedback>;
|
|
121
|
+
interface ParserState {
|
|
122
|
+
readonly route: string;
|
|
123
|
+
readonly providerMetadataKey: string;
|
|
124
|
+
readonly finishReason?: string;
|
|
125
|
+
readonly hasToolCalls: boolean;
|
|
126
|
+
readonly promptFeedback?: GeminiPromptFeedback;
|
|
127
|
+
readonly usage?: GeminiUsage;
|
|
128
|
+
/** Usage of the earlier requests of a response Gemini continued across requests. */
|
|
129
|
+
readonly continuedUsage?: ReadonlyArray<GeminiUsage>;
|
|
130
|
+
readonly lifecycle: Lifecycle.State;
|
|
131
|
+
readonly reasoningSignature?: string;
|
|
132
|
+
readonly textSignature?: string;
|
|
133
|
+
readonly reasoningId?: string;
|
|
134
|
+
readonly textId?: string;
|
|
135
|
+
readonly nextReasoningId: number;
|
|
136
|
+
readonly nextTextId: number;
|
|
137
|
+
readonly seenCallIds?: ReadonlySet<string>;
|
|
138
|
+
}
|
|
139
|
+
/** Marks where the transport starts another request for the same response; provider JSON can never match it. */
|
|
140
|
+
declare class NextRequest {
|
|
141
|
+
}
|
|
142
|
+
export interface Prepared {
|
|
143
|
+
readonly http: HttpTransport.HttpPrepared<string>;
|
|
144
|
+
readonly input: TransportPrepareInput<GeminiBody>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* SSE transport that continues responses Gemini splits across requests. Chunks carry `continuationToken` checkpoints,
|
|
148
|
+
* and a request that reaches its output cap finishes with `CONTINUATION`. The transport resends the original body with
|
|
149
|
+
* the latest token, which also resumes a request that ended or dropped before finishing.
|
|
150
|
+
*/
|
|
151
|
+
export declare const transport: Transport<GeminiBody, Prepared, unknown>;
|
|
105
152
|
/**
|
|
106
153
|
* The Gemini protocol — request body construction, body schema, and the
|
|
107
154
|
* streaming-event state machine shared by Google AI Studio and Vertex Gemini.
|
|
@@ -190,7 +237,8 @@ export declare const protocol: Protocol<{
|
|
|
190
237
|
} | undefined;
|
|
191
238
|
readonly maxOutputTokens?: number | undefined;
|
|
192
239
|
} | undefined;
|
|
193
|
-
|
|
240
|
+
readonly continuationToken?: string | undefined;
|
|
241
|
+
}, NextRequest | {
|
|
194
242
|
readonly error?: unknown;
|
|
195
243
|
readonly candidates?: readonly {
|
|
196
244
|
readonly content?: {
|
|
@@ -213,13 +261,28 @@ export declare const protocol: Protocol<{
|
|
|
213
261
|
readonly thoughtsTokenCount?: number | null | undefined;
|
|
214
262
|
} | null | undefined;
|
|
215
263
|
}, {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
264
|
+
readonly error?: unknown;
|
|
265
|
+
readonly candidates?: readonly {
|
|
266
|
+
readonly content?: {
|
|
267
|
+
readonly parts?: readonly unknown[] | null | undefined;
|
|
268
|
+
readonly role?: "model" | "user" | null | undefined;
|
|
269
|
+
} | null | undefined;
|
|
270
|
+
readonly finishReason?: string | null | undefined;
|
|
271
|
+
}[] | null | undefined;
|
|
272
|
+
readonly promptFeedback?: {
|
|
273
|
+
readonly [x: string]: unknown;
|
|
274
|
+
readonly blockReason?: string | null | undefined;
|
|
275
|
+
readonly blockReasonMessage?: string | null | undefined;
|
|
276
|
+
readonly safetyRatings?: unknown;
|
|
277
|
+
} | null | undefined;
|
|
278
|
+
readonly usageMetadata?: {
|
|
279
|
+
readonly promptTokenCount?: number | null | undefined;
|
|
280
|
+
readonly candidatesTokenCount?: number | null | undefined;
|
|
281
|
+
readonly totalTokenCount?: number | null | undefined;
|
|
282
|
+
readonly cachedContentTokenCount?: number | null | undefined;
|
|
283
|
+
readonly thoughtsTokenCount?: number | null | undefined;
|
|
284
|
+
} | null | undefined;
|
|
285
|
+
} | NextRequest, ParserState>;
|
|
223
286
|
export declare const route: Route<{
|
|
224
287
|
readonly contents: readonly {
|
|
225
288
|
readonly parts?: readonly ({
|
|
@@ -304,5 +367,6 @@ export declare const route: Route<{
|
|
|
304
367
|
} | undefined;
|
|
305
368
|
readonly maxOutputTokens?: number | undefined;
|
|
306
369
|
} | undefined;
|
|
307
|
-
|
|
370
|
+
readonly continuationToken?: string | undefined;
|
|
371
|
+
}, Prepared, import("../route/client.js").CompactionOperations | undefined>;
|
|
308
372
|
export * as Gemini from "./gemini.js";
|
package/dist/protocols/gemini.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Effect, Option, Schema } from "effect";
|
|
1
|
+
import { Effect, Option, Schema, Stream } from "effect";
|
|
2
2
|
import { Tool } from "@opencode/schema/tool";
|
|
3
3
|
import { Route } from "../route/client.js";
|
|
4
4
|
import { Auth } from "../route/auth.js";
|
|
5
5
|
import { Endpoint } from "../route/endpoint.js";
|
|
6
|
-
import { Framing } from "../route/framing.js";
|
|
7
6
|
import { Protocol } from "../route/protocol.js";
|
|
7
|
+
import { HttpTransport, } from "../route/transport/index.js";
|
|
8
8
|
import { AIError, LLMEvent, Usage, } from "../schema/index.js";
|
|
9
9
|
import { classifyProviderFailure } from "../provider-error.js";
|
|
10
10
|
import { Media } from "../media.js";
|
|
@@ -12,6 +12,7 @@ import { JsonObject, knownString, lenient, optionalArray, optionalNull, Provider
|
|
|
12
12
|
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
13
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
14
14
|
const ADAPTER = "gemini";
|
|
15
|
+
const CONTINUATION = "CONTINUATION";
|
|
15
16
|
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
|
16
17
|
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
|
|
17
18
|
// Gemini 2.5 rejects a budget under the model's minimum: 512 on Flash-Lite, the highest, and 128 on Pro.
|
|
@@ -162,6 +163,7 @@ const GeminiBodyFields = {
|
|
|
162
163
|
tools: optionalArray(GeminiTool),
|
|
163
164
|
toolConfig: Schema.optional(GeminiToolConfig),
|
|
164
165
|
generationConfig: Schema.optional(GeminiGenerationConfig),
|
|
166
|
+
continuationToken: Schema.optional(Schema.String),
|
|
165
167
|
};
|
|
166
168
|
const GeminiBody = Schema.Struct(GeminiBodyFields);
|
|
167
169
|
const GeminiUsage = Schema.Struct({
|
|
@@ -403,7 +405,7 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
|
|
|
403
405
|
// `cachedContentTokenCount` subset. `candidatesTokenCount` is *exclusive*
|
|
404
406
|
// of `thoughtsTokenCount` — visible-only, not a total — so we sum the two
|
|
405
407
|
// to produce the inclusive `outputTokens` the rest of the contract expects.
|
|
406
|
-
const mapUsage = (usage, metadataKey) => {
|
|
408
|
+
const mapUsage = (usage, metadataKey, metadata = usage ?? {}) => {
|
|
407
409
|
if (!usage)
|
|
408
410
|
return undefined;
|
|
409
411
|
// Explicit provider nulls decode as `null`; normalize to `undefined` so the
|
|
@@ -425,7 +427,26 @@ const mapUsage = (usage, metadataKey) => {
|
|
|
425
427
|
cacheReadInputTokens: cached,
|
|
426
428
|
reasoningTokens: thoughts,
|
|
427
429
|
totalTokens: ProviderShared.totalTokens(promptTokens, outputTokens, usage.totalTokenCount ?? undefined),
|
|
428
|
-
providerMetadata: providerMetadata(metadataKey,
|
|
430
|
+
providerMetadata: providerMetadata(metadataKey, metadata),
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
// Each continued request re-bills the output so far as input. Reporting the first request's input with all output keeps
|
|
434
|
+
// input plus output equal to the response's final context size, under-reporting billed input; the summed billed counts
|
|
435
|
+
// remain in provider metadata.
|
|
436
|
+
const mapContinuedUsage = (requests, metadataKey) => {
|
|
437
|
+
const sum = (field) => ProviderShared.sumTokens(...requests.map((usage) => usage[field] ?? undefined));
|
|
438
|
+
return mapUsage({
|
|
439
|
+
promptTokenCount: requests[0].promptTokenCount,
|
|
440
|
+
cachedContentTokenCount: requests[0].cachedContentTokenCount,
|
|
441
|
+
candidatesTokenCount: sum("candidatesTokenCount"),
|
|
442
|
+
thoughtsTokenCount: sum("thoughtsTokenCount"),
|
|
443
|
+
}, metadataKey, {
|
|
444
|
+
promptTokenCount: sum("promptTokenCount"),
|
|
445
|
+
cachedContentTokenCount: sum("cachedContentTokenCount"),
|
|
446
|
+
candidatesTokenCount: sum("candidatesTokenCount"),
|
|
447
|
+
thoughtsTokenCount: sum("thoughtsTokenCount"),
|
|
448
|
+
totalTokenCount: sum("totalTokenCount"),
|
|
449
|
+
requests: requests.length,
|
|
429
450
|
});
|
|
430
451
|
};
|
|
431
452
|
const mapFinishReason = (finishReason, hasToolCalls) => {
|
|
@@ -451,7 +472,7 @@ const finish = (state) => {
|
|
|
451
472
|
// the "nothing to finish" check below keeps its meaning.
|
|
452
473
|
const promptBlockReason = state.finishReason === undefined ? (state.promptFeedback?.blockReason ?? undefined) : undefined;
|
|
453
474
|
const finishReason = state.finishReason ?? promptBlockReason;
|
|
454
|
-
if (finishReason === undefined && state.usage === undefined)
|
|
475
|
+
if (finishReason === undefined && state.usage === undefined && state.continuedUsage === undefined)
|
|
455
476
|
return [];
|
|
456
477
|
const events = [];
|
|
457
478
|
let lifecycle = state.lifecycle;
|
|
@@ -468,7 +489,9 @@ const finish = (state) => {
|
|
|
468
489
|
normalized: promptBlockReason === undefined ? mapFinishReason(finishReason, state.hasToolCalls) : "content-filter",
|
|
469
490
|
raw: finishReason,
|
|
470
491
|
},
|
|
471
|
-
usage: state.
|
|
492
|
+
usage: state.continuedUsage === undefined
|
|
493
|
+
? mapUsage(state.usage, state.providerMetadataKey)
|
|
494
|
+
: mapContinuedUsage([...state.continuedUsage, state.usage ?? {}], state.providerMetadataKey),
|
|
472
495
|
providerMetadata: state.promptFeedback === undefined
|
|
473
496
|
? undefined
|
|
474
497
|
: providerMetadata(state.providerMetadataKey, { promptFeedback: state.promptFeedback }),
|
|
@@ -476,6 +499,18 @@ const finish = (state) => {
|
|
|
476
499
|
return events;
|
|
477
500
|
};
|
|
478
501
|
const step = (state, event) => {
|
|
502
|
+
// Usage is cumulative within one request, so the next request of the same response counts from zero.
|
|
503
|
+
if (event instanceof NextRequest)
|
|
504
|
+
return Effect.succeed([
|
|
505
|
+
{
|
|
506
|
+
...state,
|
|
507
|
+
finishReason: undefined,
|
|
508
|
+
promptFeedback: undefined,
|
|
509
|
+
usage: undefined,
|
|
510
|
+
continuedUsage: [...(state.continuedUsage ?? []), state.usage ?? {}],
|
|
511
|
+
},
|
|
512
|
+
[],
|
|
513
|
+
]);
|
|
479
514
|
if (ProviderShared.isRecord(event.error)) {
|
|
480
515
|
const body = ProviderShared.encodeJson(event);
|
|
481
516
|
return Effect.fail(new AIError({
|
|
@@ -492,21 +527,17 @@ const step = (state, event) => {
|
|
|
492
527
|
}
|
|
493
528
|
if ("error" in event)
|
|
494
529
|
return Effect.fail(ProviderShared.eventError(state.route, `Invalid ${state.route} stream event`, ProviderShared.encodeJson(event)));
|
|
530
|
+
const candidate = event.candidates?.[0];
|
|
495
531
|
const nextState = {
|
|
496
532
|
...state,
|
|
497
533
|
promptFeedback: event.promptFeedback ?? state.promptFeedback,
|
|
498
|
-
usage: event.usageMetadata
|
|
499
|
-
|
|
500
|
-
: state.usage,
|
|
534
|
+
usage: event.usageMetadata ?? state.usage,
|
|
535
|
+
finishReason: candidate?.finishReason ?? state.finishReason,
|
|
501
536
|
};
|
|
502
|
-
const candidate = event.candidates?.[0];
|
|
503
537
|
if (candidate?.finishReason && mapFinishReason(candidate.finishReason, state.hasToolCalls) === "error")
|
|
504
538
|
return Effect.fail(ProviderShared.eventError(state.route, `Gemini stopped with ${candidate.finishReason}`, ProviderShared.encodeJson(event)));
|
|
505
539
|
if (!candidate?.content)
|
|
506
|
-
return Effect.succeed([
|
|
507
|
-
{ ...nextState, finishReason: candidate?.finishReason ?? nextState.finishReason },
|
|
508
|
-
[],
|
|
509
|
-
]);
|
|
540
|
+
return Effect.succeed([nextState, []]);
|
|
510
541
|
const events = [];
|
|
511
542
|
let hasToolCalls = nextState.hasToolCalls;
|
|
512
543
|
let lifecycle = nextState.lifecycle;
|
|
@@ -625,12 +656,75 @@ const step = (state, event) => {
|
|
|
625
656
|
nextReasoningId,
|
|
626
657
|
nextTextId,
|
|
627
658
|
seenCallIds,
|
|
628
|
-
finishReason: candidate.finishReason ?? nextState.finishReason,
|
|
629
659
|
},
|
|
630
660
|
events,
|
|
631
661
|
]);
|
|
632
662
|
};
|
|
633
663
|
// =============================================================================
|
|
664
|
+
// Transport
|
|
665
|
+
// =============================================================================
|
|
666
|
+
const GeminiCheckpoint = Schema.Struct({
|
|
667
|
+
candidates: optionalNull(Schema.Array(Schema.Struct({
|
|
668
|
+
finishReason: optionalNull(Schema.String),
|
|
669
|
+
continuationToken: optionalNull(Schema.String),
|
|
670
|
+
}))),
|
|
671
|
+
});
|
|
672
|
+
const decodeCheckpoint = Schema.decodeUnknownOption(GeminiCheckpoint);
|
|
673
|
+
/** Marks where the transport starts another request for the same response; provider JSON can never match it. */
|
|
674
|
+
class NextRequest {
|
|
675
|
+
}
|
|
676
|
+
const sse = HttpTransport.sseJson.with();
|
|
677
|
+
/**
|
|
678
|
+
* SSE transport that continues responses Gemini splits across requests. Chunks carry `continuationToken` checkpoints,
|
|
679
|
+
* and a request that reaches its output cap finishes with `CONTINUATION`. The transport resends the original body with
|
|
680
|
+
* the latest token, which also resumes a request that ended or dropped before finishing.
|
|
681
|
+
*/
|
|
682
|
+
export const transport = {
|
|
683
|
+
id: sse.id,
|
|
684
|
+
prepare: (input) => sse.prepare(input).pipe(Effect.map((http) => ({ http, input }))),
|
|
685
|
+
execute: (prepared, request, runtime) => Effect.gen(function* () {
|
|
686
|
+
const route = request.model.route.id;
|
|
687
|
+
const label = `${request.model.provider}/${route}`;
|
|
688
|
+
// Frames are parsed once here, so errors recover each frame's raw text for their body.
|
|
689
|
+
const raw = new WeakMap();
|
|
690
|
+
const respond = (execution) => Stream.suspend(() => {
|
|
691
|
+
let token;
|
|
692
|
+
let finishReason;
|
|
693
|
+
let dropped;
|
|
694
|
+
return execution.frames.pipe(Stream.mapEffect((frame) => ProviderShared.parseJson(route, frame, `Invalid ${label} stream event`).pipe(Effect.map((event) => {
|
|
695
|
+
if (typeof event === "object" && event !== null)
|
|
696
|
+
raw.set(event, frame);
|
|
697
|
+
const candidate = Option.getOrUndefined(decodeCheckpoint(event))?.candidates?.[0];
|
|
698
|
+
token = candidate?.continuationToken ?? (candidate?.finishReason ? undefined : token);
|
|
699
|
+
finishReason = candidate?.finishReason ?? finishReason;
|
|
700
|
+
return event;
|
|
701
|
+
}))), Stream.catchIf(
|
|
702
|
+
// A connection failure may resume; a timeout keeps its meaning and ends the response.
|
|
703
|
+
(error) => error.reason._tag === "Transport" && error.reason.code !== "Timeout", (error) => {
|
|
704
|
+
dropped = error;
|
|
705
|
+
return Stream.empty;
|
|
706
|
+
}), Stream.concat(Stream.suspend(() => {
|
|
707
|
+
if (token !== undefined && (finishReason === undefined || finishReason === CONTINUATION))
|
|
708
|
+
return Stream.concat(Stream.succeed(new NextRequest()), next(token));
|
|
709
|
+
if (dropped)
|
|
710
|
+
return Stream.fail(dropped);
|
|
711
|
+
if (finishReason === CONTINUATION)
|
|
712
|
+
return Stream.fail(ProviderShared.eventError(label, "Gemini continued the response without a token"));
|
|
713
|
+
return Stream.empty;
|
|
714
|
+
})));
|
|
715
|
+
});
|
|
716
|
+
const next = (continuationToken) => Stream.unwrap(sse
|
|
717
|
+
.prepare({ ...prepared.input, body: { ...prepared.input.body, continuationToken } })
|
|
718
|
+
.pipe(Effect.flatMap((continued) => sse.execute(continued, request, runtime)), Effect.map(respond)));
|
|
719
|
+
const first = yield* sse.execute(prepared.http, request, runtime);
|
|
720
|
+
return {
|
|
721
|
+
frames: respond(first),
|
|
722
|
+
http: first.http,
|
|
723
|
+
body: (frame) => (typeof frame === "object" && frame !== null ? raw.get(frame) : undefined),
|
|
724
|
+
};
|
|
725
|
+
}),
|
|
726
|
+
};
|
|
727
|
+
// =============================================================================
|
|
634
728
|
// Protocol And Gemini Route
|
|
635
729
|
// =============================================================================
|
|
636
730
|
/**
|
|
@@ -646,7 +740,7 @@ export const protocol = Protocol.make({
|
|
|
646
740
|
// Gemini's schema rules are this API's default, including for tuned endpoints whose IDs do not name Gemini.
|
|
647
741
|
sanitizer: "gemini",
|
|
648
742
|
stream: {
|
|
649
|
-
event:
|
|
743
|
+
event: Schema.Union([Schema.instanceOf(NextRequest), GeminiEvent]),
|
|
650
744
|
initial: (request) => ({
|
|
651
745
|
route: `${request.model.provider}/${request.model.route.id}`,
|
|
652
746
|
providerMetadataKey: request.model.route.providerMetadataKey ?? String(request.model.provider),
|
|
@@ -669,6 +763,6 @@ export const route = Route.make({
|
|
|
669
763
|
baseURL: DEFAULT_BASE_URL,
|
|
670
764
|
}),
|
|
671
765
|
auth: Auth.none,
|
|
672
|
-
|
|
766
|
+
transport,
|
|
673
767
|
});
|
|
674
768
|
export * as Gemini from "./gemini.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Base64 } from "effect/encoding";
|
|
2
3
|
import { Protocol } from "../route/protocol.js";
|
|
3
4
|
import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.js";
|
|
4
5
|
import { OpenResponses } from "./open-responses.js";
|
|
@@ -80,7 +81,7 @@ const HOSTED_TOOLS = {
|
|
|
80
81
|
return { type: "error", value: item.error };
|
|
81
82
|
if (!item.result)
|
|
82
83
|
return yield* ProviderShared.eventError(ADAPTER, "Meta returned an image without data", ProviderShared.encodeJson(raw));
|
|
83
|
-
const data = yield* Effect.fromResult(
|
|
84
|
+
const data = yield* Effect.fromResult(Base64.decode(item.result)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Meta returned invalid image base64", ProviderShared.encodeJson(raw), cause)));
|
|
84
85
|
// Responses image items can omit output_format, including when PNG/JPEG was requested.
|
|
85
86
|
const mime = item.output_format === undefined
|
|
86
87
|
? (detectMediaType(data) ?? "application/octet-stream")
|
|
@@ -32,7 +32,7 @@ export type ProviderOptionsInput = {
|
|
|
32
32
|
readonly promptCacheKey?: string;
|
|
33
33
|
readonly [key: string]: unknown;
|
|
34
34
|
};
|
|
35
|
-
declare const MistralBody: Schema.Struct<{
|
|
35
|
+
export declare const MistralBody: Schema.Struct<{
|
|
36
36
|
readonly model: Schema.String;
|
|
37
37
|
readonly messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
|
|
38
38
|
readonly role: Schema.Literal<"system">;
|
|
@@ -78,7 +78,7 @@ const MistralOptions = Schema.Struct({
|
|
|
78
78
|
promptMode: Schema.optional(Schema.Literal("reasoning")),
|
|
79
79
|
promptCacheKey: Schema.optional(Schema.String),
|
|
80
80
|
});
|
|
81
|
-
const MistralBody = Schema.Struct({
|
|
81
|
+
export const MistralBody = Schema.Struct({
|
|
82
82
|
model: Schema.String,
|
|
83
83
|
messages: Schema.Array(MistralMessage),
|
|
84
84
|
tools: optionalArray(MistralTool),
|
|
@@ -317,7 +317,9 @@ export const fromRequest = Effect.fn("MistralChat.fromRequest")(function* (reque
|
|
|
317
317
|
frequency_penalty: request.generation?.frequencyPenalty,
|
|
318
318
|
presence_penalty: request.generation?.presencePenalty,
|
|
319
319
|
stop: request.generation?.stop,
|
|
320
|
-
prompt_cache_key:
|
|
320
|
+
prompt_cache_key: ProviderShared.sendsPromptCacheKey(request)
|
|
321
|
+
? (options.promptCacheKey ?? request.promptCacheKey)
|
|
322
|
+
: undefined,
|
|
321
323
|
safe_prompt: options.safePrompt,
|
|
322
324
|
document_image_limit: options.documentImageLimit,
|
|
323
325
|
document_page_limit: options.documentPageLimit,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Headers } from "effect/
|
|
1
|
+
import { Headers } from "effect/http";
|
|
2
2
|
import { HttpTransport, type Transport, type WebSocketChannelDriver } from "../route/transport/index.js";
|
|
3
3
|
import { OpenResponsesContinuation } from "./open-responses-continuation.js";
|
|
4
4
|
export interface Options {
|