@opencode-ai/ai 0.0.0-dev-17491 → 0.0.0-dev-17503
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.js +2 -3
- 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 +4 -31
- package/dist/protocols/shared.js +8 -38
- package/dist/protocols/utils/bedrock-media.js +2 -2
- package/package.json +3 -3
|
@@ -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: {
|
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";
|
|
@@ -169,7 +168,7 @@ const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini",
|
|
|
169
168
|
const lowerUserPart = Effect.fn("Gemini.lowerUserPart")(function* (part) {
|
|
170
169
|
if (part.type === "text")
|
|
171
170
|
return { text: part.text };
|
|
172
|
-
const media =
|
|
171
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
173
172
|
return { inlineData: { mimeType: media.mime, data: media.base64 } };
|
|
174
173
|
});
|
|
175
174
|
const googleMetadata = (metadata) => ({ google: metadata });
|
|
@@ -267,7 +266,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
267
266
|
for (const item of content) {
|
|
268
267
|
if (item.type === "text")
|
|
269
268
|
continue;
|
|
270
|
-
const value =
|
|
269
|
+
const value = ProviderShared.normalizeToolFile(item);
|
|
271
270
|
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } });
|
|
272
271
|
}
|
|
273
272
|
parts.push({
|
|
@@ -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;
|
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")
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-17503",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
32
|
"@effect/platform-node": "4.0.0-beta.101",
|
|
33
|
-
"@opencode-ai/http-recorder": "0.0.0-dev-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-dev-17503",
|
|
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,7 +39,7 @@
|
|
|
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-dev-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-dev-17503",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-beta.101",
|
|
45
45
|
"google-auth-library": "10.5.0"
|