Sign In

@opencode-ai/ai

Package Overview
Dependencies
Maintainers
2
Versions
617
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opencode-ai/ai - npm Package Compare versions

Comparing version
0.0.0-dev-17491
to
0.0.0-dev-17503
+3
-2
dist/protocols/anthropic-messages.js

@@ -16,3 +16,2 @@ import { Effect, Schema } from "effect";

const ADAPTER = "anthropic-messages";
const MEDIA_MIMES = new Set([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES]);
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";

@@ -297,3 +296,3 @@ export const PATH = "/messages";

const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part) {
const media = yield* ProviderShared.validateMedia("Anthropic Messages", part, MEDIA_MIMES);
const media = ProviderShared.normalizeMedia(part);
if (media.mime === "application/pdf")

@@ -308,2 +307,4 @@ return {

};
if (!media.mime.startsWith("image/"))
return yield* invalid(`Anthropic Messages does not support media type ${part.mediaType}`);
return {

@@ -310,0 +311,0 @@ type: "image",

@@ -14,3 +14,2 @@ import { Effect, Schema } from "effect";

const ADAPTER = "gemini";
const MEDIA_MIMES = new Set(ProviderShared.MEDIA_MIMES);
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.

@@ -173,3 +172,3 @@ const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";

return { text: part.text };
const media = yield* ProviderShared.validateMedia("Gemini", part, MEDIA_MIMES);
const media = ProviderShared.normalizeMedia(part);
return { inlineData: { mimeType: media.mime, data: media.base64 } };

@@ -272,3 +271,3 @@ });

continue;
const value = yield* ProviderShared.validateToolFile("Gemini", item, MEDIA_MIMES);
const value = ProviderShared.normalizeToolFile(item);
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } });

@@ -275,0 +274,0 @@ }

+1
-1

@@ -369,3 +369,3 @@ import { Effect, Schema } from "effect";

readonly part: MediaPart;
readonly media: ProviderShared.ValidatedMedia;
readonly media: ProviderShared.NormalizedMedia;
readonly request: LLMRequest;

@@ -372,0 +372,0 @@ }) => MediaInput | undefined;

@@ -13,3 +13,2 @@ import { Effect, Schema } from "effect";

const NAME = "Open Responses";
const MEDIA_MIMES = new Set([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES]);
export const PATH = "/responses";

@@ -247,10 +246,10 @@ // =============================================================================

const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, request, extension) {
const media = yield* ProviderShared.validateMedia(extension.name, part, MEDIA_MIMES);
const media = ProviderShared.normalizeMedia(part);
const extended = extension.lowerMedia?.({ part, media, request });
if (extended)
return extended;
if (media.mime === "application/pdf") {
if (!media.mime.startsWith("image/")) {
return {
type: "input_file",
filename: part.filename ?? "document.pdf",
filename: part.filename ?? (media.mime === "application/pdf" ? "document.pdf" : "file"),
file_data: media.dataUrl,

@@ -257,0 +256,0 @@ };

@@ -16,3 +16,2 @@ import { Effect, Schema } from "effect";

const ADAPTER = "openai-chat";
const IMAGE_MIMES = new Set(ProviderShared.IMAGE_MIMES);
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "tool_calls"]);

@@ -193,3 +192,5 @@ export const DEFAULT_BASE_URL = "https://api.openai.com/v1";

const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part) {
const media = yield* ProviderShared.validateMedia("OpenAI Chat", part, IMAGE_MIMES);
const media = ProviderShared.normalizeMedia(part);
if (!media.mime.startsWith("image/"))
return yield* ProviderShared.invalidRequest(`OpenAI Chat does not support media type ${part.mediaType}`);
return { type: "image_url", image_url: { url: media.dataUrl } };

@@ -196,0 +197,0 @@ });

@@ -1,6 +0,5 @@

import { Buffer } from "node:buffer";
import { Tool } from "@opencode-ai/schema/tool";
import { Effect, Schema, Stream } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { AIError, type ContentPart, type LLMRequest, type ToolResultPart } from "../schema/index.js";
import { AIError, type ContentPart, type LLMRequest, type MediaPart, type ToolResultPart } from "../schema/index.js";
import { isRecord } from "../utils/record.js";

@@ -109,35 +108,9 @@ export { isRecord };

export declare const parseToolInput: (route: string, name: string, raw: string) => Effect.Effect<unknown, AIError, never>;
export declare const IMAGE_MIMES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp"];
export declare const VIDEO_MIMES: readonly ["video/mp4", "video/webm", "video/quicktime"];
export declare const AUDIO_MIMES: readonly ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"];
export declare const PDF_MIMES: readonly ["application/pdf"];
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"];
export declare const MAX_MEDIA_ENCODED_BYTES: number;
export declare const MAX_MEDIA_DECODED_BYTES: number;
export interface ValidatedMedia {
export interface NormalizedMedia {
readonly mime: string;
readonly base64: string;
readonly dataUrl: string;
readonly bytes: Uint8Array;
}
export declare const validateMedia: (route: string, part: {
readonly data: string | Uint8Array<ArrayBufferLike>;
readonly type: "media";
readonly mediaType: string;
readonly metadata?: {
readonly [x: string]: unknown;
} | undefined;
readonly filename?: string | undefined;
}, supportedMimes: ReadonlySet<string>) => Effect.Effect<{
mime: string;
base64: string;
dataUrl: string;
bytes: Buffer<ArrayBuffer>;
}, AIError, never>;
export declare const validateToolFile: (route: string, part: Tool.FileContent, supportedMimes: ReadonlySet<string>) => Effect.Effect<{
mime: string;
base64: string;
dataUrl: string;
bytes: Buffer<ArrayBuffer>;
}, AIError, never>;
export declare const normalizeMedia: (part: MediaPart) => NormalizedMedia;
export declare const normalizeToolFile: (part: Tool.FileContent) => NormalizedMedia;
export declare const trimBaseUrl: (value: string) => string;

@@ -144,0 +117,0 @@ export declare const toolResultText: (part: ToolResultPart) => string;

@@ -116,43 +116,13 @@ import { Buffer } from "node:buffer";

export const parseToolInput = (route, name, raw) => parseJson(route, raw || "{}", `Invalid JSON input for ${route} tool call ${name}`);
export const IMAGE_MIMES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
export const VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
export const AUDIO_MIMES = ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"];
export const PDF_MIMES = ["application/pdf"];
export const MEDIA_MIMES = [...IMAGE_MIMES, ...VIDEO_MIMES, ...AUDIO_MIMES, ...PDF_MIMES];
export const MAX_MEDIA_ENCODED_BYTES = 28 * 1024 * 1024;
export const MAX_MEDIA_DECODED_BYTES = 20 * 1024 * 1024;
const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
export const validateMedia = Effect.fn("ProviderShared.validateMedia")(function* (route, part, supportedMimes) {
export const normalizeMedia = (part) => {
const mime = part.mediaType.toLowerCase();
if (!supportedMimes.has(mime))
return yield* invalidRequest(`${route} does not support media type ${part.mediaType}`);
let base64;
if (typeof part.data !== "string") {
if (part.data.byteLength > MAX_MEDIA_DECODED_BYTES)
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`);
base64 = Buffer.from(part.data).toString("base64");
const base64 = Buffer.from(part.data).toString("base64");
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` };
}
else if (part.data.startsWith("data:")) {
const match = /^data:([^;,]+);base64,([A-Za-z0-9+/]*={0,2})$/s.exec(part.data);
if (!match)
return yield* invalidRequest(`${route} media data URL must contain valid base64`);
if (match[1].toLowerCase() !== mime)
return yield* invalidRequest(`${route} media type ${part.mediaType} does not match data URL type ${match[1]}`);
base64 = match[2];
}
else {
base64 = part.data;
}
if (Buffer.byteLength(base64, "utf8") > MAX_MEDIA_ENCODED_BYTES)
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_ENCODED_BYTES} byte encoded limit`);
if (!base64 || base64.length % 4 !== 0 || !base64Pattern.test(base64))
return yield* invalidRequest(`${route} media must contain valid base64`);
const bytes = Buffer.from(base64, "base64");
if (bytes.byteLength > MAX_MEDIA_DECODED_BYTES)
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`);
if (bytes.toString("base64") !== base64)
return yield* invalidRequest(`${route} media must contain canonical base64`);
return { mime, base64, dataUrl: `data:${mime};base64,${base64}`, bytes };
});
export const validateToolFile = (route, part, supportedMimes) => validateMedia(route, { type: "media", mediaType: part.mime, data: part.uri, filename: part.name }, supportedMimes);
if (!part.data.startsWith("data:"))
return { mime, base64: part.data, dataUrl: `data:${mime};base64,${part.data}` };
return { mime, base64: part.data.slice(part.data.indexOf(",") + 1), dataUrl: part.data };
};
export const normalizeToolFile = (part) => normalizeMedia({ type: "media", mediaType: part.mime, data: part.uri, filename: part.name });
export const trimBaseUrl = (value) => value.replace(/\/+$/, "");

@@ -159,0 +129,0 @@ export const toolResultText = (part) => {

@@ -56,3 +56,3 @@ import { Effect, Schema } from "effect";

if (imageFormat) {
const media = yield* ProviderShared.validateMedia("Bedrock Converse", part, new Set(Object.keys(IMAGE_FORMATS)));
const media = ProviderShared.normalizeMedia(part);
return { image: { format: imageFormat, source: { bytes: media.base64 } } };

@@ -66,3 +66,3 @@ }

return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename");
const media = yield* ProviderShared.validateMedia("Bedrock Converse", part, new Set(Object.keys(DOCUMENT_FORMATS)));
const media = ProviderShared.normalizeMedia(part);
return documentBlock(part.filename, documentFormat, media.base64);

@@ -69,0 +69,0 @@ }

{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.0.0-dev-17491",
"version": "0.0.0-dev-17503",
"name": "@opencode-ai/ai",

@@ -33,3 +33,3 @@ "type": "module",

"@effect/platform-node": "4.0.0-beta.101",
"@opencode-ai/http-recorder": "0.0.0-dev-17491",
"@opencode-ai/http-recorder": "0.0.0-dev-17503",
"@tsconfig/bun": "1.0.9",

@@ -43,3 +43,3 @@ "@types/bun": "1.3.13",

"@smithy/util-utf8": "4.2.2",
"@opencode-ai/schema": "0.0.0-dev-17491",
"@opencode-ai/schema": "0.0.0-dev-17503",
"aws4fetch": "1.0.20",

@@ -46,0 +46,0 @@ "effect": "4.0.0-beta.101",