@opencode/ai 0.0.0-dev-20120 → 0.0.0-dev-20123
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/experimental/evaluation.js +1 -1
- package/dist/generation.d.ts +1 -7
- package/dist/generation.js +1 -11
- package/dist/image.d.ts +2 -2
- package/dist/image.js +2 -2
- package/dist/llm.js +1 -1
- package/dist/media-model.d.ts +0 -2
- package/dist/media-model.js +0 -2
- package/dist/protocols/alibaba-messages.d.ts +1 -1
- package/dist/protocols/anthropic-messages.d.ts +34 -34
- package/dist/protocols/assemblyai-transcription.d.ts +2 -1
- package/dist/protocols/assemblyai-transcription.js +10 -14
- package/dist/protocols/bfl-images.js +15 -31
- package/dist/protocols/cartesia-speech.d.ts +2 -2
- package/dist/protocols/cartesia-speech.js +10 -16
- package/dist/protocols/deepgram-speech.d.ts +3 -3
- package/dist/protocols/deepgram-speech.js +7 -11
- package/dist/protocols/deepgram-transcription.d.ts +2 -1
- package/dist/protocols/deepgram-transcription.js +9 -13
- package/dist/protocols/elevenlabs-speech.d.ts +3 -3
- package/dist/protocols/elevenlabs-speech.js +9 -13
- package/dist/protocols/fal-images.d.ts +2 -1
- package/dist/protocols/fal-images.js +16 -29
- 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/google-images.d.ts +3 -3
- package/dist/protocols/google-images.js +11 -21
- package/dist/protocols/google-speech.js +7 -13
- 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 +5 -2
- package/dist/protocols/mistral-chat.js +6 -3
- package/dist/protocols/openai-images.d.ts +4 -5
- package/dist/protocols/openai-images.js +24 -55
- package/dist/protocols/openai-responses.js +8 -2
- package/dist/protocols/openai-speech.js +7 -11
- package/dist/protocols/openai-transcription.js +21 -29
- package/dist/protocols/replicate-images.js +11 -17
- package/dist/protocols/runway-video.d.ts +3 -3
- package/dist/protocols/runway-video.js +10 -16
- package/dist/protocols/shared.d.ts +5 -17
- package/dist/protocols/shared.js +3 -39
- 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/speech-stream.d.ts +3 -3
- package/dist/protocols/utils/speech-stream.js +1 -4
- package/dist/protocols/utils/tool-schema.js +19 -6
- package/dist/protocols/xai-images.d.ts +4 -4
- package/dist/protocols/xai-images.js +12 -28
- package/dist/protocols/xai-video.js +12 -18
- package/dist/protocols/zai-images.d.ts +2 -2
- package/dist/protocols/zai-images.js +8 -11
- package/dist/protocols/zai-messages.d.ts +1 -1
- package/dist/providers/alibaba.d.ts +1 -1
- 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.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 +10 -10
- 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.d.ts +7 -8
- package/dist/providers/openai.js +10 -11
- package/dist/providers/opencode-zen.js +1 -1
- package/dist/providers/openrouter.d.ts +6 -7
- package/dist/providers/openrouter.js +1 -1
- 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/client.d.ts +0 -1
- package/dist/route/client.js +1 -6
- package/dist/route/endpoint.d.ts +1 -0
- package/dist/route/endpoint.js +2 -2
- package/dist/route/framing.d.ts +10 -1
- package/dist/route/framing.js +45 -5
- package/dist/route/media-protocol.d.ts +42 -35
- package/dist/route/media-protocol.js +67 -62
- package/dist/route/media.d.ts +6 -2
- package/dist/route/media.js +40 -29
- package/dist/schema/options.d.ts +6 -3
- package/dist/schema/options.js +7 -3
- package/dist/speech.d.ts +2 -2
- package/dist/speech.js +2 -2
- package/dist/transcription.js +2 -2
- package/dist/utils/json.d.ts +4 -0
- package/dist/utils/json.js +4 -0
- package/dist/video.d.ts +2 -2
- package/dist/video.js +2 -2
- 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
|
@@ -10,14 +10,14 @@ export type ProviderOptionsInput = OptionsInput;
|
|
|
10
10
|
declare const Options: Schema.Struct<{
|
|
11
11
|
readonly cachedContent: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.String>, never>>;
|
|
12
12
|
readonly safetySettings: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.$Array<Schema.Struct<{
|
|
13
|
-
readonly category: Schema.declare<(
|
|
14
|
-
readonly threshold: Schema.declare<(
|
|
13
|
+
readonly category: Schema.declare<import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">, import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">>;
|
|
14
|
+
readonly threshold: Schema.declare<import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">, import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">>;
|
|
15
15
|
}>>>, never>>;
|
|
16
|
-
readonly serviceTier: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<(
|
|
16
|
+
readonly serviceTier: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<import("../schema/options.js").OpenString<"standard" | "flex" | "priority">, import("../schema/options.js").OpenString<"standard" | "flex" | "priority">>>, never>>;
|
|
17
17
|
readonly thinkingConfig: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.Struct<{
|
|
18
18
|
readonly thinkingBudget: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.Number>, never>>;
|
|
19
19
|
readonly includeThoughts: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.Boolean>, never>>;
|
|
20
|
-
readonly thinkingLevel: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<(
|
|
20
|
+
readonly thinkingLevel: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">, import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">>>, never>>;
|
|
21
21
|
}>>, never>>;
|
|
22
22
|
}>;
|
|
23
23
|
declare const GeminiBody: Schema.Struct<{
|
|
@@ -63,8 +63,8 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
63
63
|
}>>;
|
|
64
64
|
labels: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
65
65
|
safetySettings: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
66
|
-
readonly category: Schema.declare<(
|
|
67
|
-
readonly threshold: Schema.declare<(
|
|
66
|
+
readonly category: Schema.declare<import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">, import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">>;
|
|
67
|
+
readonly threshold: Schema.declare<import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">, import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">>;
|
|
68
68
|
}>>>;
|
|
69
69
|
serviceTier: Schema.optional<Schema.String>;
|
|
70
70
|
systemInstruction: Schema.optional<Schema.Struct<{
|
|
@@ -97,7 +97,7 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
97
97
|
readonly thinkingConfig: Schema.optional<Schema.Struct<{
|
|
98
98
|
readonly thinkingBudget: Schema.optional<Schema.Number>;
|
|
99
99
|
readonly includeThoughts: Schema.optional<Schema.Boolean>;
|
|
100
|
-
readonly thinkingLevel: Schema.optional<Schema.declare<(
|
|
100
|
+
readonly thinkingLevel: Schema.optional<Schema.declare<import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">, import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">>>;
|
|
101
101
|
}>>;
|
|
102
102
|
}>>;
|
|
103
103
|
}>;
|
|
@@ -164,8 +164,8 @@ export declare const protocol: Protocol<{
|
|
|
164
164
|
} | undefined;
|
|
165
165
|
readonly cachedContent?: string | undefined;
|
|
166
166
|
readonly safetySettings?: readonly {
|
|
167
|
-
readonly category: (
|
|
168
|
-
readonly threshold: (
|
|
167
|
+
readonly category: import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">;
|
|
168
|
+
readonly threshold: import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">;
|
|
169
169
|
}[] | undefined;
|
|
170
170
|
readonly labels?: {
|
|
171
171
|
readonly [x: string]: string;
|
|
@@ -186,7 +186,7 @@ export declare const protocol: Protocol<{
|
|
|
186
186
|
readonly thinkingConfig?: {
|
|
187
187
|
readonly thinkingBudget?: number | undefined;
|
|
188
188
|
readonly includeThoughts?: boolean | undefined;
|
|
189
|
-
readonly thinkingLevel?: (
|
|
189
|
+
readonly thinkingLevel?: import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high"> | undefined;
|
|
190
190
|
} | undefined;
|
|
191
191
|
readonly maxOutputTokens?: number | undefined;
|
|
192
192
|
} | undefined;
|
|
@@ -278,8 +278,8 @@ export declare const route: Route<{
|
|
|
278
278
|
} | undefined;
|
|
279
279
|
readonly cachedContent?: string | undefined;
|
|
280
280
|
readonly safetySettings?: readonly {
|
|
281
|
-
readonly category: (
|
|
282
|
-
readonly threshold: (
|
|
281
|
+
readonly category: import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">;
|
|
282
|
+
readonly threshold: import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">;
|
|
283
283
|
}[] | undefined;
|
|
284
284
|
readonly labels?: {
|
|
285
285
|
readonly [x: string]: string;
|
|
@@ -300,7 +300,7 @@ export declare const route: Route<{
|
|
|
300
300
|
readonly thinkingConfig?: {
|
|
301
301
|
readonly thinkingBudget?: number | undefined;
|
|
302
302
|
readonly includeThoughts?: boolean | undefined;
|
|
303
|
-
readonly thinkingLevel?: (
|
|
303
|
+
readonly thinkingLevel?: import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high"> | undefined;
|
|
304
304
|
} | undefined;
|
|
305
305
|
readonly maxOutputTokens?: number | undefined;
|
|
306
306
|
} | undefined;
|
|
@@ -1,12 +1,12 @@
|
|
|
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
|
+
import { type OpenString } from "../schema/index.js";
|
|
4
5
|
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
5
|
-
export type GoogleImageString<Known extends string> = Known | (string & {});
|
|
6
6
|
/** Provider-native options. Common fields (`aspectRatio`, `seed`, `images`) live on the request. */
|
|
7
7
|
export type GoogleImageOptions = {
|
|
8
|
-
readonly imageSize?:
|
|
9
|
-
readonly thinkingLevel?:
|
|
8
|
+
readonly imageSize?: OpenString<"1K" | "2K" | "4K">;
|
|
9
|
+
readonly thinkingLevel?: OpenString<"MINIMAL" | "LOW" | "MEDIUM" | "HIGH">;
|
|
10
10
|
readonly includeThoughts?: boolean;
|
|
11
11
|
} & Record<string, unknown>;
|
|
12
12
|
export type Request = ImageRequestFor<GoogleImageOptions>;
|
|
@@ -2,13 +2,11 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import { ImageModel, ImageResponse } from "../image.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 { ProviderShared } from "./shared.js";
|
|
7
7
|
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
8
8
|
import { MediaInput } from "./utils/media-input.js";
|
|
9
|
-
const
|
|
10
|
-
const NAME = "Google Images";
|
|
11
|
-
const PROVIDER = ProviderID.make("google");
|
|
9
|
+
const route = MediaProtocol.identity({ id: "google-images", name: "Google Images", provider: "google" });
|
|
12
10
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
13
11
|
// ---------------------------------------------------------------------------
|
|
14
12
|
// 2. Response schema
|
|
@@ -63,13 +61,8 @@ const generationConfig = (request) => {
|
|
|
63
61
|
};
|
|
64
62
|
const fromRequest = Effect.fn("GoogleImages.fromRequest")(function* (request) {
|
|
65
63
|
if (request.n !== undefined && request.n > 1)
|
|
66
|
-
return yield*
|
|
67
|
-
|
|
68
|
-
provider: PROVIDER,
|
|
69
|
-
route: ADAPTER,
|
|
70
|
-
message: `${NAME} generates one image per request; call it once per image instead of n=${request.n}`,
|
|
71
|
-
});
|
|
72
|
-
const parts = yield* Effect.forEach(request.images ?? [], (image) => GeminiGenerateContent.mediaPart(NAME, image));
|
|
64
|
+
return yield* route.unsupported("image.n", `${route.name} generates one image per request; call it once per image instead of n=${request.n}`);
|
|
65
|
+
const parts = yield* Effect.forEach(request.images ?? [], (image) => GeminiGenerateContent.mediaPart(route.name, image));
|
|
73
66
|
return MediaProtocol.json(mergeJsonRecords({
|
|
74
67
|
contents: [{ role: "user", parts: [{ text: request.prompt }, ...parts] }],
|
|
75
68
|
generationConfig: generationConfig(request),
|
|
@@ -78,8 +71,9 @@ const fromRequest = Effect.fn("GoogleImages.fromRequest")(function* (request) {
|
|
|
78
71
|
// ---------------------------------------------------------------------------
|
|
79
72
|
// 6. Response decoding
|
|
80
73
|
// ---------------------------------------------------------------------------
|
|
74
|
+
const decodeDocument = route.decodeJson(GoogleImageResponse);
|
|
81
75
|
const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (response) {
|
|
82
|
-
const output = yield*
|
|
76
|
+
const output = yield* decodeDocument(response);
|
|
83
77
|
const decoded = output.value;
|
|
84
78
|
const candidates = decoded.candidates ?? [];
|
|
85
79
|
const candidateMetadata = candidates.map((candidate, candidateIndex) => ({
|
|
@@ -110,7 +104,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
|
|
|
110
104
|
thoughtSignature: part.thoughtSignature,
|
|
111
105
|
},
|
|
112
106
|
]));
|
|
113
|
-
const images = yield* Effect.forEach(encoded, (item) => MediaInput.decodedAsset(output.invalid, `${
|
|
107
|
+
const images = yield* Effect.forEach(encoded, (item) => MediaInput.decodedAsset(output.invalid, `${route.name} candidate ${item.candidateIndex} part ${item.partIndex}`, item.inlineData.data, item.inlineData.mimeType, {
|
|
114
108
|
providerMetadata: {
|
|
115
109
|
google: {
|
|
116
110
|
candidateIndex: item.candidate.index ?? item.candidateIndex,
|
|
@@ -125,7 +119,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
|
|
|
125
119
|
}));
|
|
126
120
|
if (images.length === 0) {
|
|
127
121
|
const finishReasons = candidates.flatMap((candidate) => candidate.finishReason === undefined ? [] : [candidate.finishReason]);
|
|
128
|
-
return yield* output.invalid(`${
|
|
122
|
+
return yield* output.invalid(`${route.name} returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect body for prompt feedback and candidate details`);
|
|
129
123
|
}
|
|
130
124
|
// Candidates that stopped for a safety or policy reason are partial results, not a silent drop.
|
|
131
125
|
const notices = [
|
|
@@ -134,7 +128,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
|
|
|
134
128
|
: [
|
|
135
129
|
{
|
|
136
130
|
type: "filtered",
|
|
137
|
-
message: `${
|
|
131
|
+
message: `${route.name} reported prompt feedback`,
|
|
138
132
|
providerMetadata: { google: { promptFeedback: decoded.promptFeedback } },
|
|
139
133
|
},
|
|
140
134
|
]),
|
|
@@ -143,7 +137,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
|
|
|
143
137
|
: [
|
|
144
138
|
{
|
|
145
139
|
type: "filtered",
|
|
146
|
-
message: `${
|
|
140
|
+
message: `${route.name} candidate ${candidate.index ?? index} finished with ${candidate.finishReason}${candidate.finishMessage === undefined ? "" : `: ${candidate.finishMessage}`}`,
|
|
147
141
|
providerMetadata: {
|
|
148
142
|
google: {
|
|
149
143
|
candidateIndex: candidate.index ?? index,
|
|
@@ -186,16 +180,12 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
|
|
|
186
180
|
// ---------------------------------------------------------------------------
|
|
187
181
|
// 7. Protocol and route
|
|
188
182
|
// ---------------------------------------------------------------------------
|
|
189
|
-
export const protocol = MediaProtocol.inline({
|
|
190
|
-
id: ADAPTER,
|
|
191
|
-
name: NAME,
|
|
183
|
+
export const protocol = MediaProtocol.inline(route, {
|
|
192
184
|
unsupported: ["mask", "size", "format"],
|
|
193
185
|
body: { from: fromRequest },
|
|
194
186
|
response: { decode: decodeResponse },
|
|
195
187
|
});
|
|
196
188
|
export const model = (input) => ImageModel.fromRoute({
|
|
197
|
-
id: ADAPTER,
|
|
198
|
-
provider: PROVIDER,
|
|
199
189
|
protocol,
|
|
200
190
|
baseURL: DEFAULT_BASE_URL,
|
|
201
191
|
path: ({ request }) => `/models/${request.model.id}:generateContent`,
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Effect, Schema } 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 { SpeechModel } from "../speech.js";
|
|
6
6
|
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
7
7
|
import { SpeechStream } from "./utils/speech-stream.js";
|
|
8
|
-
const
|
|
9
|
-
const NAME = "Google Speech";
|
|
10
|
-
const PROVIDER = ProviderID.make("google");
|
|
8
|
+
const route = MediaProtocol.identity({ id: "google-speech", name: "Google Speech", provider: "google" });
|
|
11
9
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
12
10
|
const DEFAULT_SAMPLE_RATE = 24000;
|
|
13
11
|
// ---------------------------------------------------------------------------
|
|
@@ -17,13 +15,13 @@ const GenerateContentChunk = GeminiGenerateContent.chunk(Schema.Struct({
|
|
|
17
15
|
text: Schema.optional(Schema.String),
|
|
18
16
|
inlineData: Schema.optional(Schema.Struct({ mimeType: Schema.String, data: Schema.Uint8ArrayFromBase64 })),
|
|
19
17
|
}));
|
|
20
|
-
const decodeChunk =
|
|
18
|
+
const decodeChunk = route.decodeFrame(GenerateContentChunk);
|
|
21
19
|
// ---------------------------------------------------------------------------
|
|
22
20
|
// 5. Request body construction
|
|
23
21
|
// ---------------------------------------------------------------------------
|
|
24
22
|
const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
|
|
25
23
|
if (request.format !== undefined && request.format !== "pcm")
|
|
26
|
-
return yield*
|
|
24
|
+
return yield* route.unsupported("media.format", `${route.name} only returns raw PCM; request format "pcm" or omit it, then wrap the samples yourself`);
|
|
27
25
|
const voiceName = SpeechStream.voiceID(request.voice);
|
|
28
26
|
return MediaProtocol.json(mergeJsonRecords({
|
|
29
27
|
contents: [{ role: "user", parts: [{ text: request.text }] }],
|
|
@@ -41,7 +39,7 @@ const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
|
|
|
41
39
|
// ---------------------------------------------------------------------------
|
|
42
40
|
const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
|
|
43
41
|
const chunk = yield* decodeChunk(frame);
|
|
44
|
-
const blocked = GeminiGenerateContent.blocked(
|
|
42
|
+
const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
|
|
45
43
|
if (blocked !== undefined)
|
|
46
44
|
return yield* blocked;
|
|
47
45
|
const audio = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.inlineData === undefined ? [] : [part.inlineData]);
|
|
@@ -50,7 +48,7 @@ const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
|
|
|
50
48
|
});
|
|
51
49
|
const finish = (state) => {
|
|
52
50
|
const sampleRate = SpeechStream.sampleRate(state.mimeType) ?? DEFAULT_SAMPLE_RATE;
|
|
53
|
-
return SpeechStream.finish(
|
|
51
|
+
return SpeechStream.finish(route, state, {
|
|
54
52
|
...SpeechStream.pcm("pcm_s16le", sampleRate, state.mimeType ?? `audio/L16;codec=pcm;rate=${sampleRate}`),
|
|
55
53
|
usage: GeminiGenerateContent.usage(state.usage),
|
|
56
54
|
providerMetadata: GeminiGenerateContent.providerMetadata(state),
|
|
@@ -60,9 +58,7 @@ const finish = (state) => {
|
|
|
60
58
|
// ---------------------------------------------------------------------------
|
|
61
59
|
// 7. Protocol and route
|
|
62
60
|
// ---------------------------------------------------------------------------
|
|
63
|
-
export const protocol = MediaProtocol.stream({
|
|
64
|
-
id: ADAPTER,
|
|
65
|
-
name: NAME,
|
|
61
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
66
62
|
unsupported: ["instructions", "speed", "timestamps"],
|
|
67
63
|
body: { from: fromRequest },
|
|
68
64
|
frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
|
|
@@ -71,8 +67,6 @@ export const protocol = MediaProtocol.stream({
|
|
|
71
67
|
finish,
|
|
72
68
|
});
|
|
73
69
|
export const model = (input) => SpeechModel.fromRoute({
|
|
74
|
-
id: ADAPTER,
|
|
75
|
-
provider: PROVIDER,
|
|
76
70
|
protocol,
|
|
77
71
|
baseURL: DEFAULT_BASE_URL,
|
|
78
72
|
// Only `gemini-3.1-flash-tts-preview` and later stream; earlier TTS models reject `streamGenerateContent`.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
2
|
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type OpenString } from "../schema/index.js";
|
|
3
4
|
import { TranscriptionModel, type TranscriptionRequestFor, type TranscriptionSegment, type TranscriptionWord } from "../transcription.js";
|
|
4
5
|
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
5
6
|
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
@@ -9,7 +10,7 @@ export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.c
|
|
|
9
10
|
*/
|
|
10
11
|
export type GoogleTranscriptionOptions = {
|
|
11
12
|
readonly audioTranscriptionConfig?: {
|
|
12
|
-
readonly mode?: "VERBATIM" | "SMART"
|
|
13
|
+
readonly mode?: OpenString<"VERBATIM" | "SMART">;
|
|
13
14
|
readonly customVocabulary?: ReadonlyArray<string>;
|
|
14
15
|
readonly languageCodes?: ReadonlyArray<string>;
|
|
15
16
|
};
|
|
@@ -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.invalid(`${
|
|
93
|
+
return yield* output.invalid(`${route.name} operation ${context.token.operation} has not finished`);
|
|
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 {
|