@opencode/ai 2.0.16 → 2.0.17
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 +187 -129
- package/dist/ai-client.d.ts +8 -0
- package/dist/ai-client.js +12 -0
- package/dist/experimental/evaluation-client.d.ts +3 -3
- package/dist/experimental/evaluation-client.js +1 -1
- package/dist/experimental/evaluation.js +1 -1
- package/dist/generation.d.ts +6 -10
- package/dist/generation.js +17 -20
- package/dist/image-client.d.ts +59 -24
- package/dist/image-client.js +16 -40
- package/dist/image.d.ts +15 -27
- package/dist/image.js +4 -15
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/llm.d.ts +7 -5
- package/dist/llm.js +10 -4
- package/dist/media-client.d.ts +30 -0
- package/dist/media-client.js +51 -0
- package/dist/media-model.d.ts +9 -10
- package/dist/media-model.js +10 -12
- package/dist/media.js +2 -2
- package/dist/promise.d.ts +68 -30
- package/dist/promise.js +52 -31
- package/dist/protocols/alibaba-chat.js +4 -1
- package/dist/protocols/alibaba-messages.d.ts +1 -1
- package/dist/protocols/alibaba-messages.js +6 -4
- package/dist/protocols/anthropic-messages.d.ts +34 -34
- package/dist/protocols/anthropic-messages.js +14 -8
- package/dist/protocols/assemblyai-transcription.d.ts +2 -1
- package/dist/protocols/assemblyai-transcription.js +14 -16
- package/dist/protocols/bedrock-converse.d.ts +7 -0
- package/dist/protocols/bedrock-converse.js +37 -11
- package/dist/protocols/bfl-images.d.ts +7 -1
- package/dist/protocols/bfl-images.js +36 -36
- package/dist/protocols/cartesia-speech.d.ts +18 -2
- package/dist/protocols/cartesia-speech.js +10 -16
- package/dist/protocols/deepgram-speech.d.ts +19 -3
- package/dist/protocols/deepgram-speech.js +11 -12
- package/dist/protocols/deepgram-transcription.d.ts +2 -1
- package/dist/protocols/deepgram-transcription.js +9 -13
- package/dist/protocols/elevenlabs-speech.d.ts +19 -3
- package/dist/protocols/elevenlabs-speech.js +9 -13
- package/dist/protocols/fal-images.d.ts +2 -1
- package/dist/protocols/fal-images.js +30 -40
- package/dist/protocols/fal-video.d.ts +2 -2
- package/dist/protocols/fal-video.js +9 -24
- package/dist/protocols/gemini.d.ts +13 -13
- package/dist/protocols/gemini.js +16 -7
- package/dist/protocols/google-images.d.ts +3 -3
- package/dist/protocols/google-images.js +11 -21
- package/dist/protocols/google-speech.d.ts +16 -0
- package/dist/protocols/google-speech.js +20 -16
- package/dist/protocols/google-transcription.d.ts +2 -1
- package/dist/protocols/google-transcription.js +8 -20
- package/dist/protocols/google-video.d.ts +2 -2
- package/dist/protocols/google-video.js +14 -30
- package/dist/protocols/meta-images.d.ts +3 -4
- package/dist/protocols/meta-images.js +12 -21
- package/dist/protocols/meta-messages.d.ts +5 -5
- package/dist/protocols/meta-responses.js +6 -4
- package/dist/protocols/open-responses.d.ts +4 -3
- package/dist/protocols/open-responses.js +4 -8
- package/dist/protocols/openai-chat.js +3 -4
- package/dist/protocols/openai-images.d.ts +7 -5
- package/dist/protocols/openai-images.js +65 -66
- package/dist/protocols/openai-responses.d.ts +10 -10
- package/dist/protocols/openai-responses.js +31 -30
- package/dist/protocols/openai-speech.d.ts +21 -1
- package/dist/protocols/openai-speech.js +11 -12
- package/dist/protocols/openai-transcription.d.ts +4 -0
- package/dist/protocols/openai-transcription.js +44 -32
- package/dist/protocols/replicate-images.js +11 -17
- package/dist/protocols/runway-video.d.ts +3 -3
- package/dist/protocols/runway-video.js +11 -17
- package/dist/protocols/shared.d.ts +11 -17
- package/dist/protocols/shared.js +11 -40
- package/dist/protocols/stability-images.d.ts +2 -1
- package/dist/protocols/stability-images.js +24 -36
- package/dist/protocols/utils/fal-queue.d.ts +1 -3
- package/dist/protocols/utils/fal-queue.js +4 -6
- package/dist/protocols/utils/media-input.d.ts +15 -1
- package/dist/protocols/utils/media-input.js +22 -0
- package/dist/protocols/utils/responses-checkpoint.js +3 -7
- package/dist/protocols/utils/responses-compaction.d.ts +3 -1
- package/dist/protocols/utils/responses-compaction.js +16 -3
- package/dist/protocols/utils/speech-stream.d.ts +3 -3
- package/dist/protocols/utils/speech-stream.js +1 -4
- package/dist/protocols/utils/tool-schema.d.ts +2 -2
- package/dist/protocols/utils/tool-schema.js +29 -9
- package/dist/protocols/xai-images.d.ts +4 -4
- package/dist/protocols/xai-images.js +14 -29
- package/dist/protocols/xai-responses.js +1 -1
- package/dist/protocols/xai-video.js +12 -18
- package/dist/protocols/zai-images.d.ts +2 -2
- package/dist/protocols/zai-images.js +11 -14
- package/dist/protocols/zai-messages.d.ts +1 -1
- package/dist/provider-error.js +7 -1
- package/dist/providers/alibaba.d.ts +1 -1
- package/dist/providers/amazon-bedrock.d.ts +2 -0
- package/dist/providers/amazon-bedrock.js +1 -0
- package/dist/providers/anthropic-compatible.d.ts +5 -5
- package/dist/providers/anthropic.d.ts +5 -5
- package/dist/providers/assemblyai.d.ts +1 -1
- package/dist/providers/assemblyai.js +5 -10
- package/dist/providers/azure.d.ts +4 -4
- package/dist/providers/azure.js +2 -2
- package/dist/providers/black-forest-labs.d.ts +1 -1
- package/dist/providers/black-forest-labs.js +5 -10
- package/dist/providers/cartesia.d.ts +1 -1
- package/dist/providers/cartesia.js +5 -10
- package/dist/providers/cloudflare-ai-gateway.d.ts +14 -14
- package/dist/providers/deepgram.d.ts +1 -1
- package/dist/providers/deepgram.js +6 -12
- package/dist/providers/elevenlabs.d.ts +1 -1
- package/dist/providers/elevenlabs.js +5 -10
- package/dist/providers/fal.d.ts +1 -1
- package/dist/providers/fal.js +5 -12
- package/dist/providers/google-vertex-messages.d.ts +5 -5
- package/dist/providers/google-vertex.d.ts +3 -3
- package/dist/providers/google.d.ts +3 -3
- package/dist/providers/google.js +7 -12
- package/dist/providers/meta.d.ts +8 -8
- package/dist/providers/meta.js +4 -8
- package/dist/providers/minimax.d.ts +5 -5
- package/dist/providers/moonshot.d.ts +5 -5
- package/dist/providers/openai-options.d.ts +3 -9
- package/dist/providers/openai-options.js +4 -7
- package/dist/providers/openai.d.ts +9 -10
- package/dist/providers/openai.js +11 -12
- package/dist/providers/opencode-zen.js +1 -1
- package/dist/providers/openrouter.d.ts +6 -7
- package/dist/providers/openrouter.js +10 -5
- package/dist/providers/replicate.d.ts +1 -1
- package/dist/providers/replicate.js +5 -10
- package/dist/providers/runway.d.ts +1 -1
- package/dist/providers/runway.js +5 -10
- package/dist/providers/stability.d.ts +1 -1
- package/dist/providers/stability.js +6 -11
- package/dist/providers/typesafe-ai.js +1 -1
- package/dist/providers/vercel-ai-gateway.js +1 -1
- package/dist/providers/xai.js +5 -10
- package/dist/providers/zai-coding-plan.d.ts +1 -1
- package/dist/providers/zai.js +4 -8
- package/dist/route/auth.d.ts +1 -1
- package/dist/route/auth.js +14 -13
- package/dist/route/client.d.ts +9 -7
- package/dist/route/client.js +6 -8
- package/dist/route/endpoint.d.ts +1 -0
- package/dist/route/endpoint.js +2 -2
- package/dist/route/executor-service.d.ts +4 -2
- package/dist/route/executor-service.js +2 -1
- package/dist/route/executor.d.ts +3 -1
- package/dist/route/executor.js +7 -0
- package/dist/route/framing.d.ts +10 -1
- package/dist/route/framing.js +45 -5
- package/dist/route/index.d.ts +1 -1
- package/dist/route/media-protocol.d.ts +49 -35
- package/dist/route/media-protocol.js +77 -65
- package/dist/route/media.d.ts +10 -16
- package/dist/route/media.js +46 -62
- package/dist/route/protocol.d.ts +3 -1
- package/dist/schema/options.d.ts +6 -3
- package/dist/schema/options.js +7 -3
- package/dist/speech-client.d.ts +62 -17
- package/dist/speech-client.js +17 -21
- package/dist/speech.d.ts +178 -21
- package/dist/speech.js +13 -8
- package/dist/testing.d.ts +2 -2
- package/dist/transcription-client.d.ts +78 -24
- package/dist/transcription-client.js +9 -40
- package/dist/transcription.d.ts +15 -27
- package/dist/transcription.js +4 -10
- package/dist/utils/json.d.ts +4 -0
- package/dist/utils/json.js +4 -0
- package/dist/utils/media-type.d.ts +2 -1
- package/dist/utils/media-type.js +3 -1
- package/dist/video-client.d.ts +55 -24
- package/dist/video-client.js +16 -36
- package/dist/video.d.ts +18 -26
- package/dist/video.js +15 -16
- package/package.json +3 -3
- package/dist/protocols/utils/meta-image.d.ts +0 -2
- package/dist/protocols/utils/meta-image.js +0 -13
- package/dist/protocols/utils/openai-image.d.ts +0 -5
- package/dist/protocols/utils/openai-image.js +0 -18
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { Effect, Schema, SchemaGetter } from "effect";
|
|
2
2
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
3
|
import { MediaRoute } from "../route/media.js";
|
|
4
|
-
import {
|
|
4
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
5
5
|
import { TranscriptionFinishEvent, TranscriptionModel, TranscriptionSegmentEvent, TranscriptionTextDeltaEvent, } from "../transcription.js";
|
|
6
|
-
import { ProviderShared } from "./shared.js";
|
|
7
6
|
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
8
|
-
const
|
|
9
|
-
const NAME = "Google Transcription";
|
|
10
|
-
const PROVIDER = ProviderID.make("google");
|
|
7
|
+
const route = MediaProtocol.identity({ id: "google-transcription", name: "Google Transcription", provider: "google" });
|
|
11
8
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
12
9
|
// ---------------------------------------------------------------------------
|
|
13
10
|
// 3. Streaming event schema
|
|
@@ -26,21 +23,16 @@ const AudioTranscription = Schema.Struct({
|
|
|
26
23
|
speakerLabel: Schema.optional(Schema.String),
|
|
27
24
|
}))),
|
|
28
25
|
});
|
|
29
|
-
const decodeChunk =
|
|
26
|
+
const decodeChunk = route.decodeFrame(GeminiGenerateContent.chunk(Schema.Struct({ audioTranscription: Schema.optional(AudioTranscription) })));
|
|
30
27
|
// ---------------------------------------------------------------------------
|
|
31
28
|
// 5. Request body construction
|
|
32
29
|
// ---------------------------------------------------------------------------
|
|
33
30
|
const fromRequest = Effect.fn("GoogleTranscription.fromRequest")(function* (request) {
|
|
34
31
|
// General Gemini models ignore `audioTranscriptionConfig` and answer the audio conversationally.
|
|
35
32
|
if (!request.model.id.includes("transcribe"))
|
|
36
|
-
return yield*
|
|
37
|
-
operation: "transcription.model",
|
|
38
|
-
provider: PROVIDER,
|
|
39
|
-
route: ADAPTER,
|
|
40
|
-
message: `${request.model.id} is not a transcription model; use a transcribe model such as gemini-3.5-transcribe`,
|
|
41
|
-
});
|
|
33
|
+
return yield* route.unsupported("transcription.model", `${request.model.id} is not a transcription model; use a transcribe model such as gemini-3.5-transcribe`);
|
|
42
34
|
return MediaProtocol.json(mergeJsonRecords({
|
|
43
|
-
contents: [{ role: "user", parts: [yield* GeminiGenerateContent.mediaPart(
|
|
35
|
+
contents: [{ role: "user", parts: [yield* GeminiGenerateContent.mediaPart(route.id, request.audio)] }],
|
|
44
36
|
generationConfig: mergeJsonRecords({
|
|
45
37
|
audioTranscriptionConfig: {
|
|
46
38
|
languageCodes: request.language === undefined ? undefined : [request.language],
|
|
@@ -80,7 +72,7 @@ const turn = (part) => {
|
|
|
80
72
|
};
|
|
81
73
|
const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
|
|
82
74
|
const chunk = yield* decodeChunk(frame);
|
|
83
|
-
const blocked = GeminiGenerateContent.blocked(
|
|
75
|
+
const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
|
|
84
76
|
if (blocked !== undefined)
|
|
85
77
|
return yield* blocked;
|
|
86
78
|
const turns = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.audioTranscription === undefined ? [] : [turn(part.audioTranscription)]);
|
|
@@ -101,7 +93,7 @@ const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
|
|
|
101
93
|
});
|
|
102
94
|
const finish = (state) => {
|
|
103
95
|
if (state.finishReason === undefined)
|
|
104
|
-
return Effect.fail(
|
|
96
|
+
return Effect.fail(route.incomplete());
|
|
105
97
|
return Effect.succeed([
|
|
106
98
|
TranscriptionFinishEvent.make({
|
|
107
99
|
text: state.text,
|
|
@@ -115,9 +107,7 @@ const finish = (state) => {
|
|
|
115
107
|
// ---------------------------------------------------------------------------
|
|
116
108
|
// 7. Protocol and route
|
|
117
109
|
// ---------------------------------------------------------------------------
|
|
118
|
-
export const protocol = MediaProtocol.stream({
|
|
119
|
-
id: ADAPTER,
|
|
120
|
-
name: NAME,
|
|
110
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
121
111
|
unsupported: ["prompt", "speakers"],
|
|
122
112
|
body: { from: fromRequest },
|
|
123
113
|
frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
|
|
@@ -126,8 +116,6 @@ export const protocol = MediaProtocol.stream({
|
|
|
126
116
|
finish,
|
|
127
117
|
});
|
|
128
118
|
export const model = (input) => TranscriptionModel.fromRoute({
|
|
129
|
-
id: ADAPTER,
|
|
130
|
-
provider: PROVIDER,
|
|
131
119
|
protocol,
|
|
132
120
|
baseURL: DEFAULT_BASE_URL,
|
|
133
121
|
path: ({ request }) => GeminiGenerateContent.path(request.model.id, request.mode),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
3
|
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { type OpenString } from "../schema/index.js";
|
|
4
5
|
import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
|
|
5
6
|
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
6
|
-
export type GoogleVideoString<Known extends string> = Known | (string & {});
|
|
7
7
|
/** Provider-native `parameters`. Common fields (`aspectRatio`, `resolution`, `durationSeconds`, `seed`) live on the request. */
|
|
8
8
|
export type GoogleVideoOptions = {
|
|
9
|
-
readonly personGeneration?:
|
|
9
|
+
readonly personGeneration?: OpenString<"allow_all" | "allow_adult" | "dont_allow">;
|
|
10
10
|
} & Record<string, unknown>;
|
|
11
11
|
export type Request = VideoRequestFor<GoogleVideoOptions>;
|
|
12
12
|
/** The long-running operation name, e.g. `models/veo-3.1-generate-preview/operations/abc123`. */
|
|
@@ -2,12 +2,10 @@ import { Duration, Effect, Schema } from "effect";
|
|
|
2
2
|
import { Media } from "../media.js";
|
|
3
3
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
4
|
import { MediaRoute } from "../route/media.js";
|
|
5
|
-
import {
|
|
5
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
6
6
|
import { VideoModel, VideoResponse } from "../video.js";
|
|
7
7
|
import { ProviderShared, optionalArray } from "./shared.js";
|
|
8
|
-
const
|
|
9
|
-
const NAME = "Google Veo";
|
|
10
|
-
const PROVIDER = ProviderID.make("google");
|
|
8
|
+
const route = MediaProtocol.identity({ id: "google-video", name: "Google Veo", provider: "google" });
|
|
11
9
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
12
10
|
/** Veo keeps generated files for two days; the asset carries that deadline so callers materialize in time. */
|
|
13
11
|
const FILE_RETENTION = Duration.days(2);
|
|
@@ -38,24 +36,14 @@ const Operation = Schema.Struct({
|
|
|
38
36
|
// 5. Request body construction
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
40
38
|
// Veo takes inline media only; a prior Veo output is `Media.url` with transient auth, so materialize it first.
|
|
41
|
-
const inlineMedia = (asset) => ProviderShared.requireInlineMedia(
|
|
39
|
+
const inlineMedia = (asset) => ProviderShared.requireInlineMedia(route.name, asset).pipe(Effect.map((inline) => ({ inlineData: { mimeType: inline.mime, data: inline.base64 } })));
|
|
42
40
|
const fromRequest = Effect.fn("GoogleVideo.fromRequest")(function* (request) {
|
|
43
41
|
if (request.n !== undefined && request.n > 1)
|
|
44
|
-
return yield*
|
|
45
|
-
operation: "video.n",
|
|
46
|
-
provider: PROVIDER,
|
|
47
|
-
route: ADAPTER,
|
|
48
|
-
message: `${NAME} generates one video per request; call it once per video instead of n=${request.n}`,
|
|
49
|
-
});
|
|
42
|
+
return yield* route.unsupported("video.n", `${route.name} generates one video per request; call it once per video instead of n=${request.n}`);
|
|
50
43
|
if (request.audio === false)
|
|
51
|
-
return yield*
|
|
52
|
-
operation: "video.audio",
|
|
53
|
-
provider: PROVIDER,
|
|
54
|
-
route: ADAPTER,
|
|
55
|
-
message: `${NAME} always generates audio; audio: false cannot be honored`,
|
|
56
|
-
});
|
|
44
|
+
return yield* route.unsupported("video.audio", `${route.name} always generates audio; audio: false cannot be honored`);
|
|
57
45
|
if (request.frames?.last !== undefined && request.frames.first === undefined)
|
|
58
|
-
return yield* ProviderShared.invalidRequest(`${
|
|
46
|
+
return yield* ProviderShared.invalidRequest(`${route.name} requires frames.first when frames.last is set`);
|
|
59
47
|
const image = request.frames?.first === undefined ? undefined : yield* inlineMedia(request.frames.first);
|
|
60
48
|
const lastFrame = request.frames?.last === undefined ? undefined : yield* inlineMedia(request.frames.last);
|
|
61
49
|
const video = request.video === undefined ? undefined : yield* inlineMedia(request.video);
|
|
@@ -82,7 +70,7 @@ const fromRequest = Effect.fn("GoogleVideo.fromRequest")(function* (request) {
|
|
|
82
70
|
// ---------------------------------------------------------------------------
|
|
83
71
|
// 6. Response decoding
|
|
84
72
|
// ---------------------------------------------------------------------------
|
|
85
|
-
const decodeStart =
|
|
73
|
+
const decodeStart = route.decodeStarted(StartResponse, (value) => ({
|
|
86
74
|
token: { operation: value.name },
|
|
87
75
|
snapshot: { id: value.name, status: "running" },
|
|
88
76
|
}));
|
|
@@ -92,7 +80,7 @@ const statusOf = (operation) => {
|
|
|
92
80
|
return "running";
|
|
93
81
|
return operation.error === undefined ? "completed" : "failed";
|
|
94
82
|
};
|
|
95
|
-
const decodeOperation =
|
|
83
|
+
const decodeOperation = route.decodeJson(Operation);
|
|
96
84
|
const decodeStatus = Effect.fn("GoogleVideo.decodeStatus")(function* (response, context) {
|
|
97
85
|
const output = yield* decodeOperation(response);
|
|
98
86
|
return { id: context.token.operation, status: statusOf(output.value) };
|
|
@@ -102,9 +90,9 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
|
|
|
102
90
|
const operation = output.value;
|
|
103
91
|
const status = statusOf(operation);
|
|
104
92
|
if (status === "running")
|
|
105
|
-
return yield* output.
|
|
93
|
+
return yield* output.pending(context.token.operation);
|
|
106
94
|
if (status === "failed")
|
|
107
|
-
return yield* output.ended("failed", `${
|
|
95
|
+
return yield* output.ended("failed", `${route.name} operation failed${operation.error?.message === undefined ? "" : `: ${operation.error.message}`}`);
|
|
108
96
|
const generated = operation.response?.generateVideoResponse;
|
|
109
97
|
// Downloads require the same API key as the poll; the asset carries it transiently and follows the redirect.
|
|
110
98
|
const videos = yield* Effect.forEach((generated?.generatedSamples ?? []).flatMap((sample) => sample.video?.uri === undefined ? [] : [{ uri: sample.video.uri, mimeType: sample.video.mimeType }]), (video) => MediaProtocol.expiringUrl(video.uri, FILE_RETENTION, {
|
|
@@ -114,13 +102,13 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
|
|
|
114
102
|
const reasons = generated?.raiMediaFilteredReasons ?? [];
|
|
115
103
|
const notices = reasons.map((reason) => ({
|
|
116
104
|
type: "filtered",
|
|
117
|
-
message: `${
|
|
105
|
+
message: `${route.name} filtered media: ${reason}`,
|
|
118
106
|
providerMetadata: { google: { raiMediaFilteredReason: reason } },
|
|
119
107
|
}));
|
|
120
108
|
if (videos.length === 0 && (reasons.length > 0 || (generated?.raiMediaFilteredCount ?? 0) > 0))
|
|
121
|
-
return yield* output.contentPolicy(`${
|
|
109
|
+
return yield* output.contentPolicy(`${route.name} filtered every video${reasons.length === 0 ? "" : `: ${reasons.join("; ")}`}`);
|
|
122
110
|
if (videos.length === 0)
|
|
123
|
-
return yield* output.invalid(`${
|
|
111
|
+
return yield* output.invalid(`${route.name} operation completed without any video`);
|
|
124
112
|
return new VideoResponse({
|
|
125
113
|
videos,
|
|
126
114
|
notices: notices.length === 0 ? undefined : notices,
|
|
@@ -137,17 +125,13 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
|
|
|
137
125
|
// 7. Protocol and route
|
|
138
126
|
// ---------------------------------------------------------------------------
|
|
139
127
|
const operationPath = (token) => `/${token.operation}`;
|
|
140
|
-
export const protocol = MediaProtocol.queued({
|
|
141
|
-
id: ADAPTER,
|
|
142
|
-
name: NAME,
|
|
128
|
+
export const protocol = MediaProtocol.queued(route, {
|
|
143
129
|
token: Token,
|
|
144
130
|
start: { body: { from: fromRequest }, decode: decodeStart },
|
|
145
131
|
status: { path: operationPath, decode: decodeStatus },
|
|
146
132
|
result: { path: operationPath, decode: decodeResult },
|
|
147
133
|
});
|
|
148
134
|
export const model = (input) => VideoModel.fromRoute({
|
|
149
|
-
id: ADAPTER,
|
|
150
|
-
provider: PROVIDER,
|
|
151
135
|
protocol,
|
|
152
136
|
baseURL: DEFAULT_BASE_URL,
|
|
153
137
|
path: ({ request }) => `/models/${request.model.id}:predictLongRunning`,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
2
2
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
3
|
import { MediaRoute } from "../route/media.js";
|
|
4
|
-
type OpenString
|
|
4
|
+
import { type OpenString } from "../schema/index.js";
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://api.meta.ai/v1";
|
|
5
6
|
/** Provider-native options. Common fields (`n`, `size`, `format`, `images`) live on the request. */
|
|
6
7
|
export type ImageOptions = {
|
|
7
8
|
readonly responseFormat?: OpenString<"b64_json" | "url">;
|
|
@@ -15,7 +16,5 @@ export type ImageOptions = {
|
|
|
15
16
|
};
|
|
16
17
|
export type Request = ImageRequestFor<ImageOptions>;
|
|
17
18
|
export declare const protocol: MediaProtocol.Inline<Request, ImageResponse>;
|
|
18
|
-
export declare const model: (input: MediaRoute.ModelInput
|
|
19
|
-
readonly baseURL: string;
|
|
20
|
-
}) => ImageModel<ImageOptions>;
|
|
19
|
+
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<ImageOptions>;
|
|
21
20
|
export * as MetaImages from "./meta-images.js";
|
|
@@ -3,12 +3,11 @@ import { ImageModel, ImageResponse } from "../image.js";
|
|
|
3
3
|
import { Media } from "../media.js";
|
|
4
4
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
5
|
import { MediaRoute } from "../route/media.js";
|
|
6
|
-
import {
|
|
6
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
7
7
|
import { JsonObject, ProviderShared, optionalNull } from "./shared.js";
|
|
8
8
|
import { MediaInput } from "./utils/media-input.js";
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const PROVIDER = ProviderID.make("meta");
|
|
9
|
+
const route = MediaProtocol.identity({ id: "meta-images", name: "Meta Images", provider: "meta" });
|
|
10
|
+
export const DEFAULT_BASE_URL = "https://api.meta.ai/v1";
|
|
12
11
|
// ---------------------------------------------------------------------------
|
|
13
12
|
// 2. Request body and response schemas
|
|
14
13
|
// ---------------------------------------------------------------------------
|
|
@@ -38,7 +37,7 @@ const Response = Schema.Struct({
|
|
|
38
37
|
// ---------------------------------------------------------------------------
|
|
39
38
|
const isEdit = (request) => (request.images?.length ?? 0) > 0;
|
|
40
39
|
// Meta has no file handles: refs are rejected even when they name this provider.
|
|
41
|
-
const reference = (asset) => ProviderShared.mediaReference(asset, undefined,
|
|
40
|
+
const reference = (asset) => ProviderShared.mediaReference(asset, undefined, route.name).pipe(Effect.map((item) => ({ image_url: item.value })));
|
|
42
41
|
const fromRequest = Effect.fn("MetaImages.fromRequest")(function* (request) {
|
|
43
42
|
const images = yield* Effect.forEach(request.images ?? [], reference);
|
|
44
43
|
const { responseFormat, reasoningStrength, toolEnablement, ...native } = request.providerOptions ?? {};
|
|
@@ -58,23 +57,18 @@ const fromRequest = Effect.fn("MetaImages.fromRequest")(function* (request) {
|
|
|
58
57
|
// ---------------------------------------------------------------------------
|
|
59
58
|
// 6. Response decoding
|
|
60
59
|
// ---------------------------------------------------------------------------
|
|
60
|
+
const decodeDocument = route.decodeJson(Response);
|
|
61
61
|
const decodeResponse = Effect.fn("MetaImages.decodeResponse")(function* (response, context) {
|
|
62
|
-
const output = yield*
|
|
62
|
+
const output = yield* decodeDocument(response);
|
|
63
63
|
const decoded = output.value;
|
|
64
64
|
const requested = context.body.type === "json" ? context.body.value.output_format : undefined;
|
|
65
65
|
const format = decoded.output_format ?? (typeof requested === "string" ? requested : "webp");
|
|
66
66
|
const mediaType = `image/${format}`;
|
|
67
|
-
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
info: { format },
|
|
71
|
-
});
|
|
72
|
-
if (item.url)
|
|
73
|
-
return Effect.succeed(Media.url(item.url, { mediaType, info: { format } }));
|
|
74
|
-
return Effect.fail(output.invalid(`${NAME} result ${index} has neither image data nor a URL`));
|
|
75
|
-
});
|
|
67
|
+
const images = yield* Effect.forEach(decoded.data, (item, index) => MediaInput.imageOutput(output.invalid, `${route.name} result ${index}`, item, mediaType, {
|
|
68
|
+
info: { format },
|
|
69
|
+
}));
|
|
76
70
|
if (images.length === 0)
|
|
77
|
-
return yield* output.invalid(`${
|
|
71
|
+
return yield* output.invalid(`${route.name} returned no images`);
|
|
78
72
|
return new ImageResponse({
|
|
79
73
|
images,
|
|
80
74
|
usage: decoded.usage === undefined
|
|
@@ -92,17 +86,14 @@ const decodeResponse = Effect.fn("MetaImages.decodeResponse")(function* (respons
|
|
|
92
86
|
// ---------------------------------------------------------------------------
|
|
93
87
|
// 7. Protocol and route
|
|
94
88
|
// ---------------------------------------------------------------------------
|
|
95
|
-
export const protocol = MediaProtocol.inline({
|
|
96
|
-
id: ADAPTER,
|
|
97
|
-
name: NAME,
|
|
89
|
+
export const protocol = MediaProtocol.inline(route, {
|
|
98
90
|
unsupported: ["mask", "aspectRatio", "seed"],
|
|
99
91
|
body: { from: fromRequest },
|
|
100
92
|
response: { decode: decodeResponse },
|
|
101
93
|
});
|
|
102
94
|
export const model = (input) => ImageModel.fromRoute({
|
|
103
|
-
id: ADAPTER,
|
|
104
|
-
provider: PROVIDER,
|
|
105
95
|
protocol,
|
|
96
|
+
baseURL: DEFAULT_BASE_URL,
|
|
106
97
|
path: ({ request }) => `/images/${isEdit(request) ? "edits" : "generations"}`,
|
|
107
98
|
}, input);
|
|
108
99
|
export * as MetaImages from "./meta-images.js";
|
|
@@ -221,20 +221,20 @@ export declare const protocol: Protocol<{
|
|
|
221
221
|
readonly thinking?: {
|
|
222
222
|
readonly type: "enabled";
|
|
223
223
|
readonly budget_tokens: number;
|
|
224
|
-
readonly display?: (
|
|
224
|
+
readonly display?: import("../schema/options.js").OpenString<"summarized" | "omitted"> | undefined;
|
|
225
225
|
readonly block_binding?: {
|
|
226
|
-
readonly prefix_mismatch_behavior?: (
|
|
226
|
+
readonly prefix_mismatch_behavior?: import("../schema/options.js").OpenString<"error" | "drop_block"> | undefined;
|
|
227
227
|
} | undefined;
|
|
228
228
|
} | {
|
|
229
229
|
readonly type: "adaptive";
|
|
230
|
-
readonly display?: (
|
|
230
|
+
readonly display?: import("../schema/options.js").OpenString<"summarized" | "omitted"> | undefined;
|
|
231
231
|
readonly block_binding?: {
|
|
232
|
-
readonly prefix_mismatch_behavior?: (
|
|
232
|
+
readonly prefix_mismatch_behavior?: import("../schema/options.js").OpenString<"error" | "drop_block"> | undefined;
|
|
233
233
|
} | undefined;
|
|
234
234
|
} | {
|
|
235
235
|
readonly type: "disabled";
|
|
236
236
|
} | undefined;
|
|
237
|
-
readonly service_tier?: "auto" |
|
|
237
|
+
readonly service_tier?: import("../schema/options.js").OpenString<"auto" | "standard_only"> | undefined;
|
|
238
238
|
readonly container?: string | {
|
|
239
239
|
readonly id?: string | null | undefined;
|
|
240
240
|
readonly skills?: readonly {
|
|
@@ -5,8 +5,7 @@ import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.j
|
|
|
5
5
|
import { OpenResponses } from "./open-responses.js";
|
|
6
6
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
7
7
|
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js";
|
|
8
|
-
import {
|
|
9
|
-
import { MetaImage } from "./utils/meta-image.js";
|
|
8
|
+
import { detectMediaType } from "../utils/media-type.js";
|
|
10
9
|
const ADAPTER = "meta-responses";
|
|
11
10
|
const NAME = "Meta Responses";
|
|
12
11
|
export const WebSearch = Schema.Struct({
|
|
@@ -82,7 +81,7 @@ const fromRequest = Effect.fn("MetaResponses.fromRequest")(function* (request) {
|
|
|
82
81
|
? undefined
|
|
83
82
|
: yield* Effect.forEach(projected.tools, (tool) => Effect.gen(function* () {
|
|
84
83
|
if (tool.native === undefined)
|
|
85
|
-
return yield* OpenResponses.lowerTool(NAME, tool
|
|
84
|
+
return yield* OpenResponses.lowerTool(NAME, tool);
|
|
86
85
|
return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(NativeTool))(tool.native.meta);
|
|
87
86
|
})),
|
|
88
87
|
tool_choice: OpenResponses.allowedToolChoice(request) ??
|
|
@@ -101,7 +100,10 @@ const HOSTED_TOOLS = {
|
|
|
101
100
|
if (!item.result)
|
|
102
101
|
return yield* ProviderShared.eventError(ADAPTER, "Meta returned an image without data", ProviderShared.encodeJson(raw));
|
|
103
102
|
const data = yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Meta returned invalid image base64", ProviderShared.encodeJson(raw), cause)));
|
|
104
|
-
|
|
103
|
+
// Responses image items can omit output_format, including when PNG/JPEG was requested.
|
|
104
|
+
const mime = item.output_format === undefined
|
|
105
|
+
? (detectMediaType(data) ?? "application/octet-stream")
|
|
106
|
+
: `image/${item.output_format}`;
|
|
105
107
|
return {
|
|
106
108
|
type: "content",
|
|
107
109
|
value: [{ type: "file", uri: `data:${mime};base64,${item.result}`, mime }],
|
|
@@ -716,9 +716,7 @@ interface ReasoningStreamItem {
|
|
|
716
716
|
readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>;
|
|
717
717
|
readonly deltaIndexes: ReadonlySet<number>;
|
|
718
718
|
}
|
|
719
|
-
export declare const lowerTool: (protocolName: string, tool: ToolDefinition
|
|
720
|
-
readonly [x: string]: unknown;
|
|
721
|
-
}) => Effect.Effect<{
|
|
719
|
+
export declare const lowerTool: (protocolName: string, tool: ToolDefinition) => Effect.Effect<{
|
|
722
720
|
type: "function";
|
|
723
721
|
name: string;
|
|
724
722
|
description: string;
|
|
@@ -727,6 +725,9 @@ export declare const lowerTool: (protocolName: string, tool: ToolDefinition, inp
|
|
|
727
725
|
};
|
|
728
726
|
strict: boolean;
|
|
729
727
|
}, AIError, never>;
|
|
728
|
+
export declare const lowerTools: (tools: ReadonlyArray<ToolDefinition>, adapter: ProviderAdapter) => Effect.Effect<{
|
|
729
|
+
readonly type: string;
|
|
730
|
+
}[], AIError, never>;
|
|
730
731
|
export declare const lowerToolChoice: (protocolName: string, toolChoice: NonNullable<LLMRequest["toolChoice"]>) => Effect.Effect<"required" | "auto" | "none" | {
|
|
731
732
|
type: "function";
|
|
732
733
|
name: string;
|
|
@@ -7,7 +7,6 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
7
7
|
import { effortUpdate } from "../effort-updates.js";
|
|
8
8
|
import { OpenResponsesOptions } from "./utils/open-responses-options.js";
|
|
9
9
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
10
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
11
10
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
12
11
|
const ADAPTER = "open-responses";
|
|
13
12
|
const NAME = "Open Responses";
|
|
@@ -281,18 +280,19 @@ const BASE_ADAPTER = { id: ADAPTER, name: NAME };
|
|
|
281
280
|
// =============================================================================
|
|
282
281
|
// Request Lowering
|
|
283
282
|
// =============================================================================
|
|
284
|
-
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool
|
|
283
|
+
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool) {
|
|
285
284
|
if (tool.native !== undefined)
|
|
286
285
|
return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`);
|
|
287
286
|
return {
|
|
288
287
|
type: "function",
|
|
289
288
|
name: tool.name,
|
|
290
289
|
description: tool.description,
|
|
291
|
-
parameters: inputSchema,
|
|
290
|
+
parameters: tool.inputSchema,
|
|
292
291
|
// The common tool definition does not currently express Responses strict-schema policy.
|
|
293
292
|
strict: false,
|
|
294
293
|
};
|
|
295
294
|
});
|
|
295
|
+
export const lowerTools = (tools, adapter) => Effect.forEach(tools, (tool) => tool.native !== undefined && adapter.nativeTool ? adapter.nativeTool(tool.native) : lowerTool(adapter.name, tool));
|
|
296
296
|
export const lowerToolChoice = (protocolName, toolChoice) => ProviderShared.matchToolChoice(protocolName, toolChoice, {
|
|
297
297
|
auto: () => "auto",
|
|
298
298
|
none: () => "none",
|
|
@@ -606,11 +606,7 @@ export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAd
|
|
|
606
606
|
return {
|
|
607
607
|
...(yield* lowerConversation(projected.request, adapter)),
|
|
608
608
|
...lowerGeneration(request),
|
|
609
|
-
tools: projected.tools.length === 0
|
|
610
|
-
? undefined
|
|
611
|
-
: yield* Effect.forEach(projected.tools, (tool) => tool.native !== undefined && adapter.nativeTool
|
|
612
|
-
? adapter.nativeTool(tool.native)
|
|
613
|
-
: lowerTool(adapter.name, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model))),
|
|
609
|
+
tools: projected.tools.length === 0 ? undefined : yield* lowerTools(projected.tools, adapter),
|
|
614
610
|
tool_choice: allowedToolChoice(request) ??
|
|
615
611
|
(request.toolChoice ? yield* lowerToolChoice(adapter.name, request.toolChoice) : undefined),
|
|
616
612
|
};
|
|
@@ -11,7 +11,6 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
11
11
|
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
12
12
|
import { OpenAIOptions } from "./utils/openai-options.js";
|
|
13
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
14
|
-
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
15
14
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
16
15
|
const ADAPTER = "openai-chat";
|
|
17
16
|
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "refusal", "tool_calls"]);
|
|
@@ -209,12 +208,12 @@ export const OpenAIChatEvent = Schema.StructWithRest(Schema.Struct({
|
|
|
209
208
|
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
210
209
|
const DONE = "[DONE]";
|
|
211
210
|
const OpenAIChatStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(OpenAIChatEvent)]);
|
|
212
|
-
const lowerTool = (tool,
|
|
211
|
+
const lowerTool = (tool, options, supportsStrictMode) => ({
|
|
213
212
|
type: "function",
|
|
214
213
|
function: {
|
|
215
214
|
name: tool.name,
|
|
216
215
|
description: tool.description,
|
|
217
|
-
parameters: inputSchema,
|
|
216
|
+
parameters: tool.inputSchema,
|
|
218
217
|
...(supportsStrictMode ? { strict: false } : {}),
|
|
219
218
|
},
|
|
220
219
|
cache_control: options.cacheControl?.(tool.cache),
|
|
@@ -649,7 +648,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
|
|
|
649
648
|
? hasHistory
|
|
650
649
|
? []
|
|
651
650
|
: undefined
|
|
652
|
-
: flattened.tools.map((tool) => lowerTool(tool,
|
|
651
|
+
: flattened.tools.map((tool) => lowerTool(tool, options, supportsStrictMode)),
|
|
653
652
|
tool_choice: hasActiveTools && request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
|
|
654
653
|
stream: true,
|
|
655
654
|
...(supportsUsageInStreaming ? { stream_options: { include_usage: true } } : {}),
|
|
@@ -2,16 +2,15 @@ import { ImageModel, type ImageRequestFor } from "../image.js";
|
|
|
2
2
|
import { Media } from "../media.js";
|
|
3
3
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
4
|
import { MediaRoute } from "../route/media.js";
|
|
5
|
-
import { type MediaUsage } from "../schema/index.js";
|
|
5
|
+
import { type MediaUsage, type OpenString } from "../schema/index.js";
|
|
6
6
|
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
7
7
|
export declare const PATH = "/images/generations";
|
|
8
8
|
export declare const EDIT_PATH = "/images/edits";
|
|
9
|
-
export type OpenAIImageString<Known extends string> = Known | (string & {});
|
|
10
9
|
/** Provider-native options. Common fields (`n`, `size`, `format`, `images`, `mask`) live on the request. */
|
|
11
10
|
export type OpenAIImageOptions = {
|
|
12
|
-
readonly quality?:
|
|
13
|
-
readonly background?:
|
|
14
|
-
readonly moderation?:
|
|
11
|
+
readonly quality?: OpenString<"auto" | "low" | "medium" | "high" | "standard" | "hd">;
|
|
12
|
+
readonly background?: OpenString<"auto" | "opaque" | "transparent">;
|
|
13
|
+
readonly moderation?: OpenString<"auto" | "low">;
|
|
15
14
|
readonly outputCompression?: number;
|
|
16
15
|
/** Previews sent before the final image when streaming (default 2); ignored by `Image.generate`. */
|
|
17
16
|
readonly partialImages?: number;
|
|
@@ -25,6 +24,9 @@ type Frame = string | {
|
|
|
25
24
|
interface State {
|
|
26
25
|
readonly completed: number;
|
|
27
26
|
readonly format?: string;
|
|
27
|
+
readonly size?: string;
|
|
28
|
+
readonly quality?: string;
|
|
29
|
+
readonly background?: string;
|
|
28
30
|
readonly usage?: MediaUsage;
|
|
29
31
|
}
|
|
30
32
|
export declare const protocol: MediaProtocol.Streamed<Request, {
|