@opencode-ai/ai 0.0.0-beta-17498 → 0.0.0-beta-17570
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/dist/protocols/anthropic-messages.js +3 -2
- package/dist/protocols/gemini.d.ts +17 -4
- package/dist/protocols/gemini.js +31 -21
- package/dist/protocols/open-responses.d.ts +1 -1
- package/dist/protocols/open-responses.js +3 -4
- package/dist/protocols/openai-chat.js +3 -2
- package/dist/protocols/shared.d.ts +6 -33
- package/dist/protocols/shared.js +11 -41
- package/dist/protocols/utils/bedrock-media.js +2 -2
- package/dist/provider-error.js +1 -1
- package/dist/providers/google-vertex.d.ts +16 -9
- package/dist/providers/google-vertex.js +18 -1
- package/dist/providers/google.d.ts +4 -1
- package/dist/schema/errors.js +1 -1
- package/package.json +5 -5
|
@@ -13,7 +13,6 @@ import { Lifecycle } from "./utils/lifecycle.js";
|
|
|
13
13
|
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
14
14
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
15
15
|
const ADAPTER = "anthropic-messages";
|
|
16
|
-
const MEDIA_MIMES = new Set([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES]);
|
|
17
16
|
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
|
|
18
17
|
export const PATH = "/messages";
|
|
19
18
|
// =============================================================================
|
|
@@ -293,7 +292,7 @@ const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult
|
|
|
293
292
|
return { type: wireType, tool_use_id: part.id, content: payload };
|
|
294
293
|
});
|
|
295
294
|
const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part) {
|
|
296
|
-
const media =
|
|
295
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
297
296
|
if (media.mime === "application/pdf")
|
|
298
297
|
return {
|
|
299
298
|
type: "document",
|
|
@@ -303,6 +302,8 @@ const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part) {
|
|
|
303
302
|
data: media.base64,
|
|
304
303
|
},
|
|
305
304
|
};
|
|
305
|
+
if (!media.mime.startsWith("image/"))
|
|
306
|
+
return yield* invalid(`Anthropic Messages does not support media type ${part.mediaType}`);
|
|
306
307
|
return {
|
|
307
308
|
type: "image",
|
|
308
309
|
source: {
|
|
@@ -38,7 +38,7 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
38
38
|
readonly functionCall: Schema.Struct<{
|
|
39
39
|
readonly id: Schema.optional<Schema.String>;
|
|
40
40
|
readonly name: Schema.String;
|
|
41
|
-
readonly args: Schema.Unknown
|
|
41
|
+
readonly args: Schema.optional<Schema.Unknown>;
|
|
42
42
|
}>;
|
|
43
43
|
readonly thoughtSignature: Schema.optional<Schema.String>;
|
|
44
44
|
}>, Schema.Struct<{
|
|
@@ -55,6 +55,7 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
55
55
|
}>;
|
|
56
56
|
}>]>>;
|
|
57
57
|
}>>;
|
|
58
|
+
labels: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
58
59
|
safetySettings: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
59
60
|
readonly category: Schema.String;
|
|
60
61
|
readonly threshold: Schema.String;
|
|
@@ -115,8 +116,8 @@ export declare const protocol: Protocol<{
|
|
|
115
116
|
} | {
|
|
116
117
|
readonly functionCall: {
|
|
117
118
|
readonly name: string;
|
|
118
|
-
readonly args: unknown;
|
|
119
119
|
readonly id?: string | undefined;
|
|
120
|
+
readonly args?: unknown;
|
|
120
121
|
};
|
|
121
122
|
readonly thoughtSignature?: string | undefined;
|
|
122
123
|
} | {
|
|
@@ -154,6 +155,9 @@ export declare const protocol: Protocol<{
|
|
|
154
155
|
readonly threshold: string;
|
|
155
156
|
}[] | undefined;
|
|
156
157
|
readonly serviceTier?: string | undefined;
|
|
158
|
+
readonly labels?: {
|
|
159
|
+
readonly [x: string]: string;
|
|
160
|
+
} | undefined;
|
|
157
161
|
readonly systemInstruction?: {
|
|
158
162
|
readonly parts: readonly {
|
|
159
163
|
readonly text: string;
|
|
@@ -190,8 +194,8 @@ export declare const protocol: Protocol<{
|
|
|
190
194
|
} | {
|
|
191
195
|
readonly functionCall: {
|
|
192
196
|
readonly name: string;
|
|
193
|
-
readonly args: unknown;
|
|
194
197
|
readonly id?: string | undefined;
|
|
198
|
+
readonly args?: unknown;
|
|
195
199
|
};
|
|
196
200
|
readonly thoughtSignature?: string | undefined;
|
|
197
201
|
} | {
|
|
@@ -210,6 +214,12 @@ export declare const protocol: Protocol<{
|
|
|
210
214
|
} | undefined;
|
|
211
215
|
readonly finishReason?: string | undefined;
|
|
212
216
|
}[] | undefined;
|
|
217
|
+
readonly promptFeedback?: {
|
|
218
|
+
readonly [x: string]: unknown;
|
|
219
|
+
readonly blockReason?: string | undefined;
|
|
220
|
+
readonly blockReasonMessage?: string | undefined;
|
|
221
|
+
readonly safetyRatings?: unknown;
|
|
222
|
+
} | undefined;
|
|
213
223
|
readonly usageMetadata?: {
|
|
214
224
|
readonly cachedContentTokenCount?: number | undefined;
|
|
215
225
|
readonly thoughtsTokenCount?: number | undefined;
|
|
@@ -237,8 +247,8 @@ export declare const route: Route<{
|
|
|
237
247
|
} | {
|
|
238
248
|
readonly functionCall: {
|
|
239
249
|
readonly name: string;
|
|
240
|
-
readonly args: unknown;
|
|
241
250
|
readonly id?: string | undefined;
|
|
251
|
+
readonly args?: unknown;
|
|
242
252
|
};
|
|
243
253
|
readonly thoughtSignature?: string | undefined;
|
|
244
254
|
} | {
|
|
@@ -276,6 +286,9 @@ export declare const route: Route<{
|
|
|
276
286
|
readonly threshold: string;
|
|
277
287
|
}[] | undefined;
|
|
278
288
|
readonly serviceTier?: string | undefined;
|
|
289
|
+
readonly labels?: {
|
|
290
|
+
readonly [x: string]: string;
|
|
291
|
+
} | undefined;
|
|
279
292
|
readonly systemInstruction?: {
|
|
280
293
|
readonly parts: readonly {
|
|
281
294
|
readonly text: string;
|
package/dist/protocols/gemini.js
CHANGED
|
@@ -11,7 +11,6 @@ import { GeminiToolSchema } from "./utils/gemini-tool-schema.js";
|
|
|
11
11
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
12
12
|
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
13
13
|
const ADAPTER = "gemini";
|
|
14
|
-
const MEDIA_MIMES = new Set(ProviderShared.MEDIA_MIMES);
|
|
15
14
|
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
|
16
15
|
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
|
|
17
16
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
@@ -45,7 +44,7 @@ const GeminiFunctionCallPart = Schema.Struct({
|
|
|
45
44
|
functionCall: Schema.Struct({
|
|
46
45
|
id: Schema.optional(Schema.String),
|
|
47
46
|
name: Schema.String,
|
|
48
|
-
args: Schema.Unknown,
|
|
47
|
+
args: Schema.optional(Schema.Unknown),
|
|
49
48
|
}),
|
|
50
49
|
thoughtSignature: Schema.optional(Schema.String),
|
|
51
50
|
});
|
|
@@ -107,6 +106,7 @@ const GeminiGenerationConfig = Schema.Struct({
|
|
|
107
106
|
const GeminiBodyFields = {
|
|
108
107
|
cachedContent: Schema.optional(Schema.String),
|
|
109
108
|
contents: Schema.Array(GeminiContent),
|
|
109
|
+
labels: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
110
110
|
safetySettings: optionalArray(GeminiSafetySetting),
|
|
111
111
|
serviceTier: Schema.optional(Schema.String),
|
|
112
112
|
systemInstruction: Schema.optional(GeminiSystemInstruction),
|
|
@@ -126,8 +126,14 @@ const GeminiCandidate = Schema.Struct({
|
|
|
126
126
|
content: Schema.optional(GeminiContent),
|
|
127
127
|
finishReason: Schema.optional(Schema.String),
|
|
128
128
|
});
|
|
129
|
+
const GeminiPromptFeedback = Schema.StructWithRest(Schema.Struct({
|
|
130
|
+
blockReason: Schema.optional(Schema.String),
|
|
131
|
+
blockReasonMessage: Schema.optional(Schema.String),
|
|
132
|
+
safetyRatings: Schema.optional(Schema.Unknown),
|
|
133
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
129
134
|
const GeminiEvent = Schema.Struct({
|
|
130
135
|
candidates: optionalArray(GeminiCandidate),
|
|
136
|
+
promptFeedback: Schema.optional(GeminiPromptFeedback),
|
|
131
137
|
usageMetadata: Schema.optional(GeminiUsage),
|
|
132
138
|
});
|
|
133
139
|
// =============================================================================
|
|
@@ -169,7 +175,7 @@ const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini",
|
|
|
169
175
|
const lowerUserPart = Effect.fn("Gemini.lowerUserPart")(function* (part) {
|
|
170
176
|
if (part.type === "text")
|
|
171
177
|
return { text: part.text };
|
|
172
|
-
const media =
|
|
178
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
173
179
|
return { inlineData: { mimeType: media.mime, data: media.base64 } };
|
|
174
180
|
});
|
|
175
181
|
const googleMetadata = (metadata) => ({ google: metadata });
|
|
@@ -267,7 +273,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
267
273
|
for (const item of content) {
|
|
268
274
|
if (item.type === "text")
|
|
269
275
|
continue;
|
|
270
|
-
const value =
|
|
276
|
+
const value = ProviderShared.normalizeToolFile(item);
|
|
271
277
|
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } });
|
|
272
278
|
}
|
|
273
279
|
parts.push({
|
|
@@ -402,25 +408,29 @@ const mapFinishReason = (finishReason, hasToolCalls) => {
|
|
|
402
408
|
return "error";
|
|
403
409
|
return "unknown";
|
|
404
410
|
};
|
|
405
|
-
const finish = (state) =>
|
|
406
|
-
?
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
411
|
+
const finish = (state) => {
|
|
412
|
+
const promptBlockReason = state.finishReason === undefined ? state.promptFeedback?.blockReason : undefined;
|
|
413
|
+
const finishReason = state.finishReason ?? promptBlockReason;
|
|
414
|
+
if (finishReason === undefined && state.usage === undefined)
|
|
415
|
+
return [];
|
|
416
|
+
const events = [];
|
|
417
|
+
const lifecycle = state.reasoningSignature
|
|
418
|
+
? Lifecycle.reasoningEnd(state.lifecycle, events, "reasoning-0", googleMetadata({ thoughtSignature: state.reasoningSignature }))
|
|
419
|
+
: state.lifecycle;
|
|
420
|
+
Lifecycle.finish(lifecycle, events, {
|
|
421
|
+
reason: {
|
|
422
|
+
normalized: promptBlockReason === undefined ? mapFinishReason(finishReason, state.hasToolCalls) : "content-filter",
|
|
423
|
+
raw: finishReason,
|
|
424
|
+
},
|
|
425
|
+
usage: state.usage,
|
|
426
|
+
providerMetadata: state.promptFeedback === undefined ? undefined : googleMetadata({ promptFeedback: state.promptFeedback }),
|
|
427
|
+
});
|
|
428
|
+
return events;
|
|
429
|
+
};
|
|
421
430
|
const step = (state, event) => {
|
|
422
431
|
const nextState = {
|
|
423
432
|
...state,
|
|
433
|
+
promptFeedback: event.promptFeedback ?? state.promptFeedback,
|
|
424
434
|
usage: event.usageMetadata ? (mapUsage(event.usageMetadata) ?? state.usage) : state.usage,
|
|
425
435
|
};
|
|
426
436
|
const candidate = event.candidates?.[0];
|
|
@@ -447,7 +457,7 @@ const step = (state, event) => {
|
|
|
447
457
|
continue;
|
|
448
458
|
}
|
|
449
459
|
if ("functionCall" in part) {
|
|
450
|
-
const input = part.functionCall.args;
|
|
460
|
+
const input = part.functionCall.args === undefined ? {} : part.functionCall.args;
|
|
451
461
|
const id = `tool_${nextToolCallId++}`;
|
|
452
462
|
const metadata = {
|
|
453
463
|
...(part.functionCall.id === undefined ? {} : { functionCallId: part.functionCall.id }),
|
|
@@ -366,7 +366,7 @@ export interface Extension {
|
|
|
366
366
|
readonly name: string;
|
|
367
367
|
readonly lowerMedia?: (input: {
|
|
368
368
|
readonly part: MediaPart;
|
|
369
|
-
readonly media: ProviderShared.
|
|
369
|
+
readonly media: ProviderShared.NormalizedMedia;
|
|
370
370
|
readonly request: LLMRequest;
|
|
371
371
|
}) => MediaInput | undefined;
|
|
372
372
|
readonly messagePhase?: (value: unknown) => MessagePhase | null | undefined;
|
|
@@ -10,7 +10,6 @@ import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
|
10
10
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
11
11
|
const ADAPTER = "open-responses";
|
|
12
12
|
const NAME = "Open Responses";
|
|
13
|
-
const MEDIA_MIMES = new Set([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES]);
|
|
14
13
|
export const PATH = "/responses";
|
|
15
14
|
// =============================================================================
|
|
16
15
|
// Request Body Schema
|
|
@@ -243,14 +242,14 @@ const hostedToolItemID = (part, providerMetadataKey) => {
|
|
|
243
242
|
: undefined;
|
|
244
243
|
};
|
|
245
244
|
const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, request, extension) {
|
|
246
|
-
const media =
|
|
245
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
247
246
|
const extended = extension.lowerMedia?.({ part, media, request });
|
|
248
247
|
if (extended)
|
|
249
248
|
return extended;
|
|
250
|
-
if (media.mime
|
|
249
|
+
if (!media.mime.startsWith("image/")) {
|
|
251
250
|
return {
|
|
252
251
|
type: "input_file",
|
|
253
|
-
filename: part.filename ?? "document.pdf",
|
|
252
|
+
filename: part.filename ?? (media.mime === "application/pdf" ? "document.pdf" : "file"),
|
|
254
253
|
file_data: media.dataUrl,
|
|
255
254
|
};
|
|
256
255
|
}
|
|
@@ -13,7 +13,6 @@ import { Lifecycle } from "./utils/lifecycle.js";
|
|
|
13
13
|
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
14
14
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
15
15
|
const ADAPTER = "openai-chat";
|
|
16
|
-
const IMAGE_MIMES = new Set(ProviderShared.IMAGE_MIMES);
|
|
17
16
|
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "tool_calls"]);
|
|
18
17
|
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
19
18
|
export const PATH = "/chat/completions";
|
|
@@ -189,7 +188,9 @@ const lowerToolCall = (part) => ({
|
|
|
189
188
|
},
|
|
190
189
|
});
|
|
191
190
|
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part) {
|
|
192
|
-
const media =
|
|
191
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
192
|
+
if (!media.mime.startsWith("image/"))
|
|
193
|
+
return yield* ProviderShared.invalidRequest(`OpenAI Chat does not support media type ${part.mediaType}`);
|
|
193
194
|
return { type: "image_url", image_url: { url: media.dataUrl } };
|
|
194
195
|
});
|
|
195
196
|
const openAICompatibleReasoningContent = (native) => isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
1
|
import { Tool } from "@opencode-ai/schema/tool";
|
|
3
2
|
import { Effect, Schema, Stream } from "effect";
|
|
4
3
|
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
5
|
-
import { AIError, type ContentPart, type LLMRequest, type ToolResultPart } from "../schema/index.js";
|
|
4
|
+
import { AIError, type ContentPart, type LLMRequest, type MediaPart, type ToolResultPart } from "../schema/index.js";
|
|
6
5
|
import { isRecord } from "../utils/record.js";
|
|
7
6
|
export { isRecord };
|
|
8
7
|
export declare const Json: Schema.fromJsonString<Schema.Unknown>;
|
|
@@ -106,39 +105,13 @@ export declare const wrappedSystemUpdate: (route: string, message: import("../sc
|
|
|
106
105
|
* routes: `Invalid JSON input for <route> tool call <name>`.
|
|
107
106
|
*/
|
|
108
107
|
export declare const parseToolInput: (route: string, name: string, raw: string) => Effect.Effect<unknown, AIError, never>;
|
|
109
|
-
export
|
|
110
|
-
export declare const VIDEO_MIMES: readonly ["video/mp4", "video/webm", "video/quicktime"];
|
|
111
|
-
export declare const AUDIO_MIMES: readonly ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"];
|
|
112
|
-
export declare const PDF_MIMES: readonly ["application/pdf"];
|
|
113
|
-
export declare const MEDIA_MIMES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp", "video/mp4", "video/webm", "video/quicktime", "audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac", "application/pdf"];
|
|
114
|
-
export declare const MAX_MEDIA_ENCODED_BYTES: number;
|
|
115
|
-
export declare const MAX_MEDIA_DECODED_BYTES: number;
|
|
116
|
-
export interface ValidatedMedia {
|
|
108
|
+
export interface NormalizedMedia {
|
|
117
109
|
readonly mime: string;
|
|
118
110
|
readonly base64: string;
|
|
119
111
|
readonly dataUrl: string;
|
|
120
|
-
readonly bytes: Uint8Array;
|
|
121
112
|
}
|
|
122
|
-
export declare const
|
|
123
|
-
|
|
124
|
-
readonly type: "media";
|
|
125
|
-
readonly mediaType: string;
|
|
126
|
-
readonly metadata?: {
|
|
127
|
-
readonly [x: string]: unknown;
|
|
128
|
-
} | undefined;
|
|
129
|
-
readonly filename?: string | undefined;
|
|
130
|
-
}, supportedMimes: ReadonlySet<string>) => Effect.Effect<{
|
|
131
|
-
mime: string;
|
|
132
|
-
base64: string;
|
|
133
|
-
dataUrl: string;
|
|
134
|
-
bytes: Buffer<ArrayBuffer>;
|
|
135
|
-
}, AIError, never>;
|
|
136
|
-
export declare const validateToolFile: (route: string, part: Tool.FileContent, supportedMimes: ReadonlySet<string>) => Effect.Effect<{
|
|
137
|
-
mime: string;
|
|
138
|
-
base64: string;
|
|
139
|
-
dataUrl: string;
|
|
140
|
-
bytes: Buffer<ArrayBuffer>;
|
|
141
|
-
}, AIError, never>;
|
|
113
|
+
export declare const normalizeMedia: (part: MediaPart) => NormalizedMedia;
|
|
114
|
+
export declare const normalizeToolFile: (part: Tool.FileContent) => NormalizedMedia;
|
|
142
115
|
export declare const trimBaseUrl: (value: string) => string;
|
|
143
116
|
export declare const toolResultText: (part: ToolResultPart) => string;
|
|
144
117
|
export declare const errorText: (error: unknown) => string;
|
|
@@ -147,8 +120,8 @@ export declare const errorText: (error: unknown) => string;
|
|
|
147
120
|
* decoder, and drops empty / `[DONE]` keep-alive events so the downstream
|
|
148
121
|
* `decodeChunk` sees one JSON string per element. The SSE channel emits a
|
|
149
122
|
* `Retry` control event on its error channel; we drop it here (we don't
|
|
150
|
-
* implement client-driven retries)
|
|
151
|
-
* `AIError`.
|
|
123
|
+
* implement client-driven retries). Decoder failures become provider output
|
|
124
|
+
* errors so the public error channel stays `AIError`.
|
|
152
125
|
*/
|
|
153
126
|
export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>) => Stream.Stream<string, AIError>;
|
|
154
127
|
/**
|
package/dist/protocols/shared.js
CHANGED
|
@@ -113,47 +113,17 @@ export const wrappedSystemUpdate = Effect.fn("ProviderShared.wrappedSystemUpdate
|
|
|
113
113
|
* routes: `Invalid JSON input for <route> tool call <name>`.
|
|
114
114
|
*/
|
|
115
115
|
export const parseToolInput = (route, name, raw) => parseJson(route, raw || "{}", `Invalid JSON input for ${route} tool call ${name}`);
|
|
116
|
-
export const
|
|
117
|
-
export const VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
118
|
-
export const AUDIO_MIMES = ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"];
|
|
119
|
-
export const PDF_MIMES = ["application/pdf"];
|
|
120
|
-
export const MEDIA_MIMES = [...IMAGE_MIMES, ...VIDEO_MIMES, ...AUDIO_MIMES, ...PDF_MIMES];
|
|
121
|
-
export const MAX_MEDIA_ENCODED_BYTES = 28 * 1024 * 1024;
|
|
122
|
-
export const MAX_MEDIA_DECODED_BYTES = 20 * 1024 * 1024;
|
|
123
|
-
const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
124
|
-
export const validateMedia = Effect.fn("ProviderShared.validateMedia")(function* (route, part, supportedMimes) {
|
|
116
|
+
export const normalizeMedia = (part) => {
|
|
125
117
|
const mime = part.mediaType.toLowerCase();
|
|
126
|
-
if (!supportedMimes.has(mime))
|
|
127
|
-
return yield* invalidRequest(`${route} does not support media type ${part.mediaType}`);
|
|
128
|
-
let base64;
|
|
129
118
|
if (typeof part.data !== "string") {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
base64 = Buffer.from(part.data).toString("base64");
|
|
119
|
+
const base64 = Buffer.from(part.data).toString("base64");
|
|
120
|
+
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` };
|
|
133
121
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return yield* invalidRequest(`${route} media type ${part.mediaType} does not match data URL type ${match[1]}`);
|
|
140
|
-
base64 = match[2];
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
base64 = part.data;
|
|
144
|
-
}
|
|
145
|
-
if (Buffer.byteLength(base64, "utf8") > MAX_MEDIA_ENCODED_BYTES)
|
|
146
|
-
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_ENCODED_BYTES} byte encoded limit`);
|
|
147
|
-
if (!base64 || base64.length % 4 !== 0 || !base64Pattern.test(base64))
|
|
148
|
-
return yield* invalidRequest(`${route} media must contain valid base64`);
|
|
149
|
-
const bytes = Buffer.from(base64, "base64");
|
|
150
|
-
if (bytes.byteLength > MAX_MEDIA_DECODED_BYTES)
|
|
151
|
-
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`);
|
|
152
|
-
if (bytes.toString("base64") !== base64)
|
|
153
|
-
return yield* invalidRequest(`${route} media must contain canonical base64`);
|
|
154
|
-
return { mime, base64, dataUrl: `data:${mime};base64,${base64}`, bytes };
|
|
155
|
-
});
|
|
156
|
-
export const validateToolFile = (route, part, supportedMimes) => validateMedia(route, { type: "media", mediaType: part.mime, data: part.uri, filename: part.name }, supportedMimes);
|
|
122
|
+
if (!part.data.startsWith("data:"))
|
|
123
|
+
return { mime, base64: part.data, dataUrl: `data:${mime};base64,${part.data}` };
|
|
124
|
+
return { mime, base64: part.data.slice(part.data.indexOf(",") + 1), dataUrl: part.data };
|
|
125
|
+
};
|
|
126
|
+
export const normalizeToolFile = (part) => normalizeMedia({ type: "media", mediaType: part.mime, data: part.uri, filename: part.name });
|
|
157
127
|
export const trimBaseUrl = (value) => value.replace(/\/+$/, "");
|
|
158
128
|
export const toolResultText = (part) => {
|
|
159
129
|
if (part.result.type === "text")
|
|
@@ -184,10 +154,10 @@ export const errorText = (error) => {
|
|
|
184
154
|
* decoder, and drops empty / `[DONE]` keep-alive events so the downstream
|
|
185
155
|
* `decodeChunk` sees one JSON string per element. The SSE channel emits a
|
|
186
156
|
* `Retry` control event on its error channel; we drop it here (we don't
|
|
187
|
-
* implement client-driven retries)
|
|
188
|
-
* `AIError`.
|
|
157
|
+
* implement client-driven retries). Decoder failures become provider output
|
|
158
|
+
* errors so the public error channel stays `AIError`.
|
|
189
159
|
*/
|
|
190
|
-
export const sseFraming = (bytes) => bytes.pipe(Stream.decodeText(), Stream.pipeThroughChannel(Sse.decode()), Stream.catchTag("Retry", () => Stream.empty), Stream.filter((event) => event.data.length > 0 && event.data !== "[DONE]"), Stream.map((event) => event.data));
|
|
160
|
+
export const sseFraming = (bytes) => bytes.pipe(Stream.decodeText(), Stream.pipeThroughChannel(Sse.decode()), Stream.catchTag("Retry", () => Stream.empty), Stream.catchTag("SseError", (error) => Stream.fail(eventError("sse", error.message))), Stream.filter((event) => event.data.length > 0 && event.data !== "[DONE]"), Stream.map((event) => event.data));
|
|
191
161
|
/**
|
|
192
162
|
* Canonical invalid-request constructor. Lift one-line `const invalid =
|
|
193
163
|
* (message) => invalidRequest(message)` aliases out of every
|
|
@@ -53,7 +53,7 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part) {
|
|
|
53
53
|
const mime = part.mediaType.toLowerCase();
|
|
54
54
|
const imageFormat = IMAGE_FORMATS[mime];
|
|
55
55
|
if (imageFormat) {
|
|
56
|
-
const media =
|
|
56
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
57
57
|
return { image: { format: imageFormat, source: { bytes: media.base64 } } };
|
|
58
58
|
}
|
|
59
59
|
if (mime.startsWith("image/"))
|
|
@@ -62,7 +62,7 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part) {
|
|
|
62
62
|
if (documentFormat) {
|
|
63
63
|
if (!part.filename)
|
|
64
64
|
return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename");
|
|
65
|
-
const media =
|
|
65
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
66
66
|
return documentBlock(part.filename, documentFormat, media.base64);
|
|
67
67
|
}
|
|
68
68
|
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`);
|
package/dist/provider-error.js
CHANGED
|
@@ -35,7 +35,7 @@ export const isPayloadTooLarge = (message) => payloadPatterns.some((pattern) =>
|
|
|
35
35
|
export const isContextOverflowFailure = (failure) => failure instanceof AIError
|
|
36
36
|
? failure.reason._tag === "InvalidRequest" && failure.reason.classification === "context-overflow"
|
|
37
37
|
: Schema.is(ProviderErrorEvent)(failure) && failure.classification === "context-overflow";
|
|
38
|
-
const decodeJson = Schema.decodeUnknownOption(Schema.
|
|
38
|
+
const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
|
|
39
39
|
const QUOTA_CODES = new Set(["insufficient_quota", "usage_not_included", "billing_error"]);
|
|
40
40
|
const SERVER_CODES = new Set([
|
|
41
41
|
"api_error",
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { ProviderPackage } from "../provider-package.js";
|
|
2
2
|
import { Gemini } from "../protocols/gemini.js";
|
|
3
3
|
import { Route, type RouteDefaultsInput } from "../route/client.js";
|
|
4
|
-
import { type ModelID } from "../schema/index.js";
|
|
4
|
+
import { type ModelID, type ProviderOptions } from "../schema/index.js";
|
|
5
5
|
import { GoogleVertexShared } from "./google-vertex-shared.js";
|
|
6
|
-
export
|
|
7
|
-
|
|
6
|
+
export interface GeminiOptionsInput extends Gemini.OptionsInput {
|
|
7
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
8
|
+
}
|
|
9
|
+
export type GeminiProviderOptionsInput = ProviderOptions & {
|
|
10
|
+
readonly gemini?: GeminiOptionsInput;
|
|
11
|
+
};
|
|
8
12
|
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
9
13
|
export type Config = RouteDefaultsInput & GoogleVertexShared.ApiKeyOptions & {
|
|
10
14
|
readonly baseURL?: string;
|
|
11
15
|
readonly location?: string;
|
|
12
16
|
readonly project?: string;
|
|
13
|
-
readonly providerOptions?:
|
|
17
|
+
readonly providerOptions?: GeminiProviderOptionsInput;
|
|
14
18
|
};
|
|
15
19
|
export type Settings = ProviderPackage.Settings & ({
|
|
16
20
|
readonly accessToken?: string;
|
|
@@ -22,7 +26,7 @@ export type Settings = ProviderPackage.Settings & ({
|
|
|
22
26
|
readonly baseURL?: string;
|
|
23
27
|
readonly location?: string;
|
|
24
28
|
readonly project?: string;
|
|
25
|
-
readonly providerOptions?:
|
|
29
|
+
readonly providerOptions?: GeminiProviderOptionsInput;
|
|
26
30
|
};
|
|
27
31
|
export declare const routes: Route<{
|
|
28
32
|
readonly contents: readonly {
|
|
@@ -39,8 +43,8 @@ export declare const routes: Route<{
|
|
|
39
43
|
} | {
|
|
40
44
|
readonly functionCall: {
|
|
41
45
|
readonly name: string;
|
|
42
|
-
readonly args: unknown;
|
|
43
46
|
readonly id?: string | undefined;
|
|
47
|
+
readonly args?: unknown;
|
|
44
48
|
};
|
|
45
49
|
readonly thoughtSignature?: string | undefined;
|
|
46
50
|
} | {
|
|
@@ -78,6 +82,9 @@ export declare const routes: Route<{
|
|
|
78
82
|
readonly threshold: string;
|
|
79
83
|
}[] | undefined;
|
|
80
84
|
readonly serviceTier?: string | undefined;
|
|
85
|
+
readonly labels?: {
|
|
86
|
+
readonly [x: string]: string;
|
|
87
|
+
} | undefined;
|
|
81
88
|
readonly systemInstruction?: {
|
|
82
89
|
readonly parts: readonly {
|
|
83
90
|
readonly text: string;
|
|
@@ -101,15 +108,15 @@ export declare const routes: Route<{
|
|
|
101
108
|
}, import("../route/transport/http.js").HttpPrepared<string>>[];
|
|
102
109
|
export declare const configure: (input?: Config) => {
|
|
103
110
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
104
|
-
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<
|
|
111
|
+
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<GeminiProviderOptionsInput>;
|
|
105
112
|
configure: (input?: Config) => /*elided*/ any;
|
|
106
113
|
};
|
|
107
114
|
export declare const provider: {
|
|
108
115
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
109
116
|
configure: (input?: Config) => {
|
|
110
117
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
111
|
-
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<
|
|
118
|
+
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<GeminiProviderOptionsInput>;
|
|
112
119
|
configure: /*elided*/ any;
|
|
113
120
|
};
|
|
114
121
|
};
|
|
115
|
-
export declare const model: ProviderPackage.Definition<Settings,
|
|
122
|
+
export declare const model: ProviderPackage.Definition<Settings, GeminiProviderOptionsInput>["model"];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
1
2
|
import { Gemini } from "../protocols/gemini.js";
|
|
3
|
+
import { ProviderShared } from "../protocols/shared.js";
|
|
2
4
|
import { Auth } from "../route/auth.js";
|
|
3
5
|
import { Route } from "../route/client.js";
|
|
4
6
|
import { Endpoint } from "../route/endpoint.js";
|
|
@@ -6,11 +8,26 @@ import { Framing } from "../route/framing.js";
|
|
|
6
8
|
import { ProviderID } from "../schema/index.js";
|
|
7
9
|
import { GoogleVertexShared } from "./google-vertex-shared.js";
|
|
8
10
|
export const id = ProviderID.make("google-vertex");
|
|
11
|
+
const fromRequest = Effect.fn("GoogleVertex.fromRequest")(function* (request) {
|
|
12
|
+
const body = yield* Gemini.protocol.body.from(request);
|
|
13
|
+
const value = request.providerOptions?.gemini?.labels;
|
|
14
|
+
const labels = ProviderShared.isRecord(value)
|
|
15
|
+
? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"))
|
|
16
|
+
: undefined;
|
|
17
|
+
return { ...body, labels };
|
|
18
|
+
});
|
|
19
|
+
const protocol = {
|
|
20
|
+
...Gemini.protocol,
|
|
21
|
+
body: {
|
|
22
|
+
...Gemini.protocol.body,
|
|
23
|
+
from: fromRequest,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
9
26
|
const route = Route.make({
|
|
10
27
|
id: "google-vertex-gemini",
|
|
11
28
|
provider: id,
|
|
12
29
|
providerMetadataKey: "google",
|
|
13
|
-
protocol
|
|
30
|
+
protocol,
|
|
14
31
|
endpoint: Endpoint.path(({ request }) => {
|
|
15
32
|
const model = String(request.model.id);
|
|
16
33
|
return `/${model.startsWith("endpoints/") ? model : `models/${model}`}:streamGenerateContent?alt=sse`;
|
|
@@ -22,8 +22,8 @@ export declare const routes: import("../route/client.js").Route<{
|
|
|
22
22
|
} | {
|
|
23
23
|
readonly functionCall: {
|
|
24
24
|
readonly name: string;
|
|
25
|
-
readonly args: unknown;
|
|
26
25
|
readonly id?: string | undefined;
|
|
26
|
+
readonly args?: unknown;
|
|
27
27
|
};
|
|
28
28
|
readonly thoughtSignature?: string | undefined;
|
|
29
29
|
} | {
|
|
@@ -61,6 +61,9 @@ export declare const routes: import("../route/client.js").Route<{
|
|
|
61
61
|
readonly threshold: string;
|
|
62
62
|
}[] | undefined;
|
|
63
63
|
readonly serviceTier?: string | undefined;
|
|
64
|
+
readonly labels?: {
|
|
65
|
+
readonly [x: string]: string;
|
|
66
|
+
} | undefined;
|
|
64
67
|
readonly systemInstruction?: {
|
|
65
68
|
readonly parts: readonly {
|
|
66
69
|
readonly text: string;
|
package/dist/schema/errors.js
CHANGED
|
@@ -132,7 +132,7 @@ export const AIErrorReason = Schema.Union([
|
|
|
132
132
|
InvalidProviderOutputReason,
|
|
133
133
|
UnknownProviderReason,
|
|
134
134
|
]).pipe(Schema.toTaggedUnion("_tag"));
|
|
135
|
-
export class AIError extends Schema.
|
|
135
|
+
export class AIError extends Schema.TaggedError()("AI.Error", {
|
|
136
136
|
module: Schema.String,
|
|
137
137
|
method: Schema.String,
|
|
138
138
|
reason: AIErrorReason,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-17570",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
|
-
"@effect/platform-node": "4.0.0-beta.
|
|
33
|
-
"@opencode-ai/http-recorder": "0.0.0-beta-
|
|
32
|
+
"@effect/platform-node": "4.0.0-beta.107",
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-beta-17570",
|
|
34
34
|
"@tsconfig/bun": "1.0.9",
|
|
35
35
|
"@types/bun": "1.3.13",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@smithy/eventstream-codec": "4.2.14",
|
|
41
41
|
"@smithy/util-utf8": "4.2.2",
|
|
42
|
-
"@opencode-ai/schema": "0.0.0-beta-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-beta-17570",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
|
-
"effect": "4.0.0-beta.
|
|
44
|
+
"effect": "4.0.0-beta.107",
|
|
45
45
|
"google-auth-library": "10.5.0"
|
|
46
46
|
}
|
|
47
47
|
}
|