@opencode/ai 2.0.14 → 2.0.15
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 +115 -56
- package/dist/experimental/evaluation-client.d.ts +1 -1
- package/dist/experimental/evaluation-client.js +39 -3
- package/dist/experimental/evaluation.d.ts +4 -4
- package/dist/experimental/evaluation.js +2 -2
- package/dist/experimental/system-one.d.ts +3 -3
- package/dist/experimental/system-one.js +40 -51
- package/dist/generation.d.ts +69 -0
- package/dist/generation.js +84 -0
- package/dist/image-client.d.ts +6 -4
- package/dist/image-client.js +11 -11
- package/dist/image.d.ts +1040 -62
- package/dist/image.js +80 -52
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- package/dist/llm.d.ts +9 -1
- package/dist/media.d.ts +212 -0
- package/dist/media.js +228 -0
- package/dist/promise.d.ts +554 -0
- package/dist/promise.js +44 -0
- package/dist/protocols/anthropic-messages.js +7 -17
- package/dist/protocols/bedrock-converse.d.ts +4 -4
- package/dist/protocols/bedrock-converse.js +1 -6
- package/dist/protocols/gemini.d.ts +21 -0
- package/dist/protocols/gemini.js +42 -6
- package/dist/protocols/google-images.d.ts +9 -21
- package/dist/protocols/google-images.js +169 -132
- package/dist/protocols/meta-images.d.ts +7 -12
- package/dist/protocols/meta-images.js +92 -66
- package/dist/protocols/mistral-chat.js +7 -6
- package/dist/protocols/open-responses.d.ts +11 -3
- package/dist/protocols/open-responses.js +23 -12
- package/dist/protocols/openai-chat.d.ts +34 -1
- package/dist/protocols/openai-chat.js +100 -31
- package/dist/protocols/openai-images.d.ts +9 -20
- package/dist/protocols/openai-images.js +112 -142
- package/dist/protocols/shared.d.ts +17 -17
- package/dist/protocols/shared.js +32 -35
- package/dist/protocols/utils/bedrock-media.d.ts +2 -3
- package/dist/protocols/utils/bedrock-media.js +4 -4
- package/dist/protocols/utils/media-input.d.ts +10 -0
- package/dist/protocols/utils/media-input.js +17 -0
- package/dist/protocols/utils/responses-compaction.js +6 -5
- package/dist/protocols/utils/tool-stream.d.ts +27 -3
- package/dist/protocols/xai-images.d.ts +9 -15
- package/dist/protocols/xai-images.js +85 -83
- package/dist/protocols/zai-images.d.ts +9 -13
- package/dist/protocols/zai-images.js +59 -57
- package/dist/providers/amazon-bedrock.d.ts +2 -2
- package/dist/providers/cerebras.js +6 -1
- package/dist/providers/deepinfra.js +6 -1
- package/dist/providers/google-vertex.d.ts +7 -0
- package/dist/providers/google.d.ts +7 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/openrouter.d.ts +19 -0
- package/dist/providers/openrouter.js +13 -1
- package/dist/providers/vercel-ai-gateway.d.ts +41 -0
- package/dist/providers/vercel-ai-gateway.js +85 -0
- package/dist/route/client.d.ts +9 -1
- package/dist/route/endpoint.d.ts +10 -10
- package/dist/route/executor-service.d.ts +12 -0
- package/dist/route/executor-service.js +3 -0
- package/dist/route/executor.d.ts +4 -9
- package/dist/route/executor.js +3 -3
- package/dist/route/index.d.ts +2 -0
- package/dist/route/index.js +2 -0
- package/dist/route/media-protocol.d.ts +45 -0
- package/dist/route/media-protocol.js +40 -0
- package/dist/route/media.d.ts +46 -0
- package/dist/route/media.js +64 -0
- package/dist/schema/errors.d.ts +13 -3
- package/dist/schema/errors.js +7 -0
- package/dist/schema/events.d.ts +563 -40
- package/dist/schema/events.js +35 -2
- package/dist/schema/messages.d.ts +98 -8
- package/dist/schema/messages.js +8 -6
- package/dist/schema/options.d.ts +2 -0
- package/dist/schema/options.js +3 -0
- package/dist/testing.d.ts +72 -8
- package/dist/utils/media-type.d.ts +6 -0
- package/dist/utils/media-type.js +49 -0
- package/dist/utils/sanitize.js +3 -1
- package/package.json +7 -3
- package/dist/protocols/utils/image-input.d.ts +0 -21
- package/dist/protocols/utils/image-input.js +0 -20
|
@@ -157,7 +157,7 @@ export declare const protocol: Protocol<{
|
|
|
157
157
|
};
|
|
158
158
|
} | {
|
|
159
159
|
readonly document: {
|
|
160
|
-
readonly format: "pdf" | "
|
|
160
|
+
readonly format: "pdf" | "txt" | "md" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html";
|
|
161
161
|
readonly name: string;
|
|
162
162
|
readonly source: {
|
|
163
163
|
readonly bytes: string;
|
|
@@ -176,7 +176,7 @@ export declare const protocol: Protocol<{
|
|
|
176
176
|
};
|
|
177
177
|
} | {
|
|
178
178
|
readonly document: {
|
|
179
|
-
readonly format: "pdf" | "
|
|
179
|
+
readonly format: "pdf" | "txt" | "md" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html";
|
|
180
180
|
readonly name: string;
|
|
181
181
|
readonly source: {
|
|
182
182
|
readonly bytes: string;
|
|
@@ -386,7 +386,7 @@ export declare const route: Route<{
|
|
|
386
386
|
};
|
|
387
387
|
} | {
|
|
388
388
|
readonly document: {
|
|
389
|
-
readonly format: "pdf" | "
|
|
389
|
+
readonly format: "pdf" | "txt" | "md" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html";
|
|
390
390
|
readonly name: string;
|
|
391
391
|
readonly source: {
|
|
392
392
|
readonly bytes: string;
|
|
@@ -405,7 +405,7 @@ export declare const route: Route<{
|
|
|
405
405
|
};
|
|
406
406
|
} | {
|
|
407
407
|
readonly document: {
|
|
408
|
-
readonly format: "pdf" | "
|
|
408
|
+
readonly format: "pdf" | "txt" | "md" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html";
|
|
409
409
|
readonly name: string;
|
|
410
410
|
readonly source: {
|
|
411
411
|
readonly bytes: string;
|
|
@@ -221,12 +221,7 @@ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent
|
|
|
221
221
|
content.push({ text: item.text });
|
|
222
222
|
continue;
|
|
223
223
|
}
|
|
224
|
-
const media = yield* BedrockMedia.lower(
|
|
225
|
-
type: "media",
|
|
226
|
-
mediaType: item.mime,
|
|
227
|
-
data: item.uri,
|
|
228
|
-
filename: item.name,
|
|
229
|
-
}, documentNames);
|
|
224
|
+
const media = yield* BedrockMedia.lower(ProviderShared.toolFileMedia(item), documentNames);
|
|
230
225
|
content.push(...media);
|
|
231
226
|
}
|
|
232
227
|
return content;
|
|
@@ -33,6 +33,12 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
33
33
|
readonly mimeType: Schema.String;
|
|
34
34
|
readonly data: Schema.String;
|
|
35
35
|
}>;
|
|
36
|
+
readonly thoughtSignature: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
37
|
+
}>, Schema.Struct<{
|
|
38
|
+
readonly fileData: Schema.Struct<{
|
|
39
|
+
readonly mimeType: Schema.String;
|
|
40
|
+
readonly fileUri: Schema.String;
|
|
41
|
+
}>;
|
|
36
42
|
}>, Schema.Struct<{
|
|
37
43
|
readonly functionCall: Schema.Struct<{
|
|
38
44
|
readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
@@ -50,6 +56,7 @@ declare const GeminiBody: Schema.Struct<{
|
|
|
50
56
|
readonly mimeType: Schema.String;
|
|
51
57
|
readonly data: Schema.String;
|
|
52
58
|
}>;
|
|
59
|
+
readonly thoughtSignature: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
53
60
|
}>>>;
|
|
54
61
|
}>;
|
|
55
62
|
}>]>>>>;
|
|
@@ -106,10 +113,16 @@ export declare const protocol: Protocol<{
|
|
|
106
113
|
readonly mimeType: string;
|
|
107
114
|
readonly data: string;
|
|
108
115
|
};
|
|
116
|
+
readonly thoughtSignature?: string | null | undefined;
|
|
109
117
|
} | {
|
|
110
118
|
readonly text: string;
|
|
111
119
|
readonly thought?: boolean | null | undefined;
|
|
112
120
|
readonly thoughtSignature?: string | null | undefined;
|
|
121
|
+
} | {
|
|
122
|
+
readonly fileData: {
|
|
123
|
+
readonly mimeType: string;
|
|
124
|
+
readonly fileUri: string;
|
|
125
|
+
};
|
|
113
126
|
} | {
|
|
114
127
|
readonly functionCall: {
|
|
115
128
|
readonly name: string;
|
|
@@ -127,6 +140,7 @@ export declare const protocol: Protocol<{
|
|
|
127
140
|
readonly mimeType: string;
|
|
128
141
|
readonly data: string;
|
|
129
142
|
};
|
|
143
|
+
readonly thoughtSignature?: string | null | undefined;
|
|
130
144
|
}[] | undefined;
|
|
131
145
|
};
|
|
132
146
|
})[] | null | undefined;
|
|
@@ -213,10 +227,16 @@ export declare const route: Route<{
|
|
|
213
227
|
readonly mimeType: string;
|
|
214
228
|
readonly data: string;
|
|
215
229
|
};
|
|
230
|
+
readonly thoughtSignature?: string | null | undefined;
|
|
216
231
|
} | {
|
|
217
232
|
readonly text: string;
|
|
218
233
|
readonly thought?: boolean | null | undefined;
|
|
219
234
|
readonly thoughtSignature?: string | null | undefined;
|
|
235
|
+
} | {
|
|
236
|
+
readonly fileData: {
|
|
237
|
+
readonly mimeType: string;
|
|
238
|
+
readonly fileUri: string;
|
|
239
|
+
};
|
|
220
240
|
} | {
|
|
221
241
|
readonly functionCall: {
|
|
222
242
|
readonly name: string;
|
|
@@ -234,6 +254,7 @@ export declare const route: Route<{
|
|
|
234
254
|
readonly mimeType: string;
|
|
235
255
|
readonly data: string;
|
|
236
256
|
};
|
|
257
|
+
readonly thoughtSignature?: string | null | undefined;
|
|
237
258
|
}[] | undefined;
|
|
238
259
|
};
|
|
239
260
|
})[] | null | undefined;
|
package/dist/protocols/gemini.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Framing } from "../route/framing.js";
|
|
|
7
7
|
import { Protocol } from "../route/protocol.js";
|
|
8
8
|
import { AIError, LLMEvent, Usage, } from "../schema/index.js";
|
|
9
9
|
import { classifyProviderFailure } from "../provider-error.js";
|
|
10
|
+
import { Media } from "../media.js";
|
|
10
11
|
import { JsonObject, knownString, lenient, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
11
12
|
import { GeminiToolSchema } from "./utils/gemini-tool-schema.js";
|
|
12
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
@@ -54,6 +55,14 @@ const GeminiInlineDataPart = Schema.Struct({
|
|
|
54
55
|
mimeType: Schema.String,
|
|
55
56
|
data: Schema.String,
|
|
56
57
|
}),
|
|
58
|
+
thoughtSignature: optionalNull(Schema.String),
|
|
59
|
+
});
|
|
60
|
+
/** Gemini Files API reference; the only remote input Gemini accepts. */
|
|
61
|
+
const GeminiFileDataPart = Schema.Struct({
|
|
62
|
+
fileData: Schema.Struct({
|
|
63
|
+
mimeType: Schema.String,
|
|
64
|
+
fileUri: Schema.String,
|
|
65
|
+
}),
|
|
57
66
|
});
|
|
58
67
|
const GeminiFunctionCallPart = Schema.Struct({
|
|
59
68
|
functionCall: Schema.Struct({
|
|
@@ -74,6 +83,7 @@ const GeminiFunctionResponsePart = Schema.Struct({
|
|
|
74
83
|
const GeminiContentPart = Schema.Union([
|
|
75
84
|
GeminiTextPart,
|
|
76
85
|
GeminiInlineDataPart,
|
|
86
|
+
GeminiFileDataPart,
|
|
77
87
|
GeminiFunctionCallPart,
|
|
78
88
|
GeminiFunctionResponsePart,
|
|
79
89
|
]);
|
|
@@ -211,10 +221,14 @@ const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini",
|
|
|
211
221
|
required: () => ({ functionCallingConfig: { mode: "ANY" } }),
|
|
212
222
|
tool: (name) => ({ functionCallingConfig: { mode: "ANY", allowedFunctionNames: [name] } }),
|
|
213
223
|
});
|
|
214
|
-
|
|
224
|
+
// Gemini does not fetch public URLs; inline payloads and Gemini Files references are the accepted inputs.
|
|
225
|
+
const lowerContentPart = Effect.fn("Gemini.lowerContentPart")(function* (part) {
|
|
215
226
|
if (part.type === "text")
|
|
216
227
|
return { text: part.text };
|
|
217
|
-
const
|
|
228
|
+
const source = part.media.source;
|
|
229
|
+
if (source.type === "ref" && source.provider === "google")
|
|
230
|
+
return { fileData: { mimeType: part.media.mediaType, fileUri: source.id } };
|
|
231
|
+
const media = yield* ProviderShared.requireInlineMedia("Gemini", part.media);
|
|
218
232
|
return { inlineData: { mimeType: media.mime, data: media.base64 } };
|
|
219
233
|
});
|
|
220
234
|
const providerMetadata = (key, metadata) => ({ [key]: metadata });
|
|
@@ -259,7 +273,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
259
273
|
for (const part of message.content) {
|
|
260
274
|
if (!ProviderShared.supportsContent(part, ["text", "media"]))
|
|
261
275
|
return yield* ProviderShared.unsupportedContent("Gemini", "user", ["text", "media"]);
|
|
262
|
-
parts.push(yield*
|
|
276
|
+
parts.push(yield* lowerContentPart(part));
|
|
263
277
|
}
|
|
264
278
|
contents.push({ role: "user", parts });
|
|
265
279
|
continue;
|
|
@@ -269,12 +283,23 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
269
283
|
// Parallel Gemini 3 calls may carry one signature on the first call; unsigned sibling calls are valid.
|
|
270
284
|
let hasSignedToolCall = false;
|
|
271
285
|
for (const part of message.content) {
|
|
272
|
-
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
|
|
273
|
-
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", [
|
|
286
|
+
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call", "media"]))
|
|
287
|
+
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", [
|
|
288
|
+
"text",
|
|
289
|
+
"reasoning",
|
|
290
|
+
"tool-call",
|
|
291
|
+
"media",
|
|
292
|
+
]);
|
|
274
293
|
if (part.type === "text") {
|
|
275
294
|
parts.push({ text: part.text, thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey) });
|
|
276
295
|
continue;
|
|
277
296
|
}
|
|
297
|
+
// Generated images replay as model-role inline data so multi-turn image editing keeps the prior output.
|
|
298
|
+
if (part.type === "media") {
|
|
299
|
+
const lowered = yield* lowerContentPart(part);
|
|
300
|
+
parts.push({ ...lowered, thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey) });
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
278
303
|
if (part.type === "reasoning") {
|
|
279
304
|
parts.push({
|
|
280
305
|
text: part.text,
|
|
@@ -324,7 +349,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
324
349
|
for (const item of content) {
|
|
325
350
|
if (item.type === "text")
|
|
326
351
|
continue;
|
|
327
|
-
const value = ProviderShared.
|
|
352
|
+
const value = yield* ProviderShared.requireInlineMedia("Gemini", ProviderShared.toolFileMedia(item).media);
|
|
328
353
|
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } });
|
|
329
354
|
}
|
|
330
355
|
if (legacyToolMedia && media.length > 0)
|
|
@@ -539,6 +564,17 @@ const step = (state, event) => {
|
|
|
539
564
|
reasoningSignature = signature;
|
|
540
565
|
else if (signature !== undefined && "text" in part)
|
|
541
566
|
textSignature = signature;
|
|
567
|
+
// Image-capable Gemini models return generated images as inline data parts; surface them as first-class output.
|
|
568
|
+
if ("inlineData" in part) {
|
|
569
|
+
lifecycle = Lifecycle.stepStart(lifecycle, events);
|
|
570
|
+
events.push(LLMEvent.media({
|
|
571
|
+
media: Media.base64(part.inlineData.data, part.inlineData.mimeType),
|
|
572
|
+
providerMetadata: signature
|
|
573
|
+
? providerMetadata(state.providerMetadataKey, { thoughtSignature: signature })
|
|
574
|
+
: undefined,
|
|
575
|
+
}));
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
542
578
|
if ("text" in part && part.text.length > 0) {
|
|
543
579
|
if (part.thought) {
|
|
544
580
|
if (textId !== undefined) {
|
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
import { ImageModel } from "../image.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
4
|
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
5
5
|
export type GoogleImageString<Known extends string> = Known | (string & {});
|
|
6
|
+
/** Provider-native options. Common fields (`aspectRatio`, `seed`, `images`) live on the request. */
|
|
6
7
|
export type GoogleImageOptions = {
|
|
7
|
-
readonly aspectRatio?: GoogleImageString<"1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "16:9" | "21:9">;
|
|
8
8
|
readonly imageSize?: GoogleImageString<"1K" | "2K" | "4K">;
|
|
9
|
-
readonly seed?: number;
|
|
10
9
|
readonly thinkingLevel?: GoogleImageString<"MINIMAL" | "LOW" | "MEDIUM" | "HIGH">;
|
|
11
10
|
readonly includeThoughts?: boolean;
|
|
12
11
|
} & Record<string, unknown>;
|
|
13
|
-
export type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
readonly parts: ReadonlyArray<Record<string, unknown>>;
|
|
17
|
-
}>;
|
|
18
|
-
readonly generationConfig: Record<string, unknown>;
|
|
19
|
-
};
|
|
20
|
-
export interface ModelInput {
|
|
21
|
-
readonly id: string;
|
|
22
|
-
readonly auth: AuthDefinition;
|
|
23
|
-
readonly baseURL?: string;
|
|
24
|
-
readonly headers?: Record<string, string>;
|
|
25
|
-
readonly http?: HttpOptions;
|
|
26
|
-
}
|
|
27
|
-
export declare const model: (input: ModelInput) => ImageModel<GoogleImageOptions>;
|
|
12
|
+
export type Request = ImageRequestFor<GoogleImageOptions>;
|
|
13
|
+
export declare const protocol: MediaProtocol.Inline<Request, ImageResponse>;
|
|
14
|
+
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<GoogleImageOptions>;
|
|
28
15
|
export declare const GoogleImages: {
|
|
29
|
-
readonly
|
|
16
|
+
readonly protocol: MediaProtocol.Inline<Request, ImageResponse>;
|
|
17
|
+
readonly model: (input: MediaRoute.ModelInput) => ImageModel<GoogleImageOptions>;
|
|
30
18
|
};
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { Effect,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { ImageModel, ImageResponse } from "../image.js";
|
|
3
|
+
import { Media } from "../media.js";
|
|
4
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
|
+
import { MediaRoute } from "../route/media.js";
|
|
6
|
+
import { ProviderID, mergeJsonRecords } from "../schema/index.js";
|
|
6
7
|
import { ProviderShared } from "./shared.js";
|
|
7
|
-
import {
|
|
8
|
+
import { MediaInput } from "./utils/media-input.js";
|
|
8
9
|
const ADAPTER = "google-images";
|
|
10
|
+
const NAME = "Google Images";
|
|
11
|
+
const PROVIDER = ProviderID.make("google");
|
|
9
12
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// 2. Response schema
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
10
16
|
const GoogleUsage = Schema.StructWithRest(Schema.Struct({
|
|
11
17
|
cachedContentTokenCount: Schema.optional(Schema.Number),
|
|
12
18
|
thoughtsTokenCount: Schema.optional(Schema.Number),
|
|
@@ -41,141 +47,172 @@ const GoogleImageResponse = Schema.Struct({
|
|
|
41
47
|
responseId: Schema.optional(Schema.String),
|
|
42
48
|
promptFeedback: Schema.optional(Schema.Unknown),
|
|
43
49
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
const thinkingConfig = {
|
|
51
|
-
thinkingLevel,
|
|
52
|
-
includeThoughts,
|
|
53
|
-
};
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// 5. Request body construction
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
const generationConfig = (request) => {
|
|
54
|
+
const { imageSize, thinkingLevel, includeThoughts, ...native } = request.providerOptions ?? {};
|
|
55
|
+
const imageConfig = { aspectRatio: request.aspectRatio, imageSize };
|
|
56
|
+
const thinkingConfig = { thinkingLevel, includeThoughts };
|
|
54
57
|
return (mergeJsonRecords({
|
|
55
58
|
responseModalities: ["IMAGE"],
|
|
56
|
-
imageConfig: Object.values(
|
|
57
|
-
seed,
|
|
59
|
+
imageConfig: Object.values(imageConfig).some((value) => value !== undefined) ? imageConfig : undefined,
|
|
60
|
+
seed: request.seed,
|
|
58
61
|
thinkingConfig: Object.values(thinkingConfig).some((value) => value !== undefined) ? thinkingConfig : undefined,
|
|
59
62
|
}, native) ?? { responseModalities: ["IMAGE"] });
|
|
60
63
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Gemini does not fetch public URLs; inline payloads or Gemini Files references are the only accepted inputs.
|
|
65
|
+
const imagePart = (asset) => {
|
|
66
|
+
const inline = asset.inline();
|
|
67
|
+
if (inline)
|
|
68
|
+
return Effect.succeed({ inlineData: { mimeType: inline.mime, data: inline.base64 } });
|
|
69
|
+
const id = MediaInput.refID(asset, PROVIDER);
|
|
70
|
+
if (id)
|
|
71
|
+
return Effect.succeed({ fileData: { mimeType: asset.mediaType, fileUri: id } });
|
|
72
|
+
if (asset.source.type === "ref")
|
|
73
|
+
return Effect.fail(ProviderShared.invalidRequest("Google generateContent requires Gemini file references rather than other providers' file IDs"));
|
|
74
|
+
return Effect.fail(ProviderShared.invalidRequest("Google generateContent does not fetch public image URLs; use bytes, a data URL, or a Gemini file reference"));
|
|
67
75
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
76
|
+
const fromRequest = Effect.fn("GoogleImages.fromRequest")(function* (request) {
|
|
77
|
+
if (request.n !== undefined && request.n > 1)
|
|
78
|
+
return yield* ProviderShared.unsupportedOperation({
|
|
79
|
+
operation: "image.n",
|
|
80
|
+
provider: PROVIDER,
|
|
81
|
+
route: ADAPTER,
|
|
82
|
+
message: `${NAME} generates one image per request; call it once per image instead of n=${request.n}`,
|
|
83
|
+
});
|
|
84
|
+
const parts = yield* Effect.forEach(request.images ?? [], imagePart);
|
|
85
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
86
|
+
contents: [{ role: "user", parts: [{ text: request.prompt }, ...parts] }],
|
|
87
|
+
generationConfig: generationConfig(request),
|
|
88
|
+
}, request.http?.body) ?? {});
|
|
89
|
+
});
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// 6. Response decoding
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (response) {
|
|
94
|
+
const output = yield* MediaProtocol.decodeJson(ADAPTER, NAME, GoogleImageResponse)(response);
|
|
95
|
+
const decoded = output.value;
|
|
96
|
+
const candidates = decoded.candidates ?? [];
|
|
97
|
+
const candidateMetadata = candidates.map((candidate, candidateIndex) => ({
|
|
98
|
+
index: candidate.index ?? candidateIndex,
|
|
99
|
+
finishReason: candidate.finishReason,
|
|
100
|
+
finishMessage: candidate.finishMessage,
|
|
101
|
+
safetyRatings: candidate.safetyRatings,
|
|
102
|
+
citationMetadata: candidate.citationMetadata,
|
|
103
|
+
groundingMetadata: candidate.groundingMetadata,
|
|
104
|
+
parts: (candidate.content?.parts ?? []).map((part) => part.inlineData === undefined
|
|
105
|
+
? { type: "text", text: part.text, thought: part.thought, thoughtSignature: part.thoughtSignature }
|
|
106
|
+
: {
|
|
107
|
+
type: "inlineData",
|
|
108
|
+
mediaType: part.inlineData.mimeType,
|
|
109
|
+
thought: part.thought,
|
|
110
|
+
thoughtSignature: part.thoughtSignature,
|
|
111
|
+
}),
|
|
112
|
+
}));
|
|
113
|
+
// Thought parts are drafts; only non-thought inline data is a final image.
|
|
114
|
+
const encoded = candidates.flatMap((candidate, candidateIndex) => (candidate.content?.parts ?? []).flatMap((part, partIndex) => part.inlineData === undefined || part.thought === true
|
|
115
|
+
? []
|
|
116
|
+
: [
|
|
117
|
+
{
|
|
118
|
+
candidate,
|
|
119
|
+
candidateIndex,
|
|
120
|
+
partIndex,
|
|
121
|
+
inlineData: part.inlineData,
|
|
122
|
+
thoughtSignature: part.thoughtSignature,
|
|
123
|
+
},
|
|
124
|
+
]));
|
|
125
|
+
const images = yield* Effect.forEach(encoded, (item) => MediaInput.decodedAsset(output.invalid, `${NAME} candidate ${item.candidateIndex} part ${item.partIndex}`, item.inlineData.data, item.inlineData.mimeType, {
|
|
126
|
+
providerMetadata: {
|
|
127
|
+
google: {
|
|
128
|
+
candidateIndex: item.candidate.index ?? item.candidateIndex,
|
|
129
|
+
partIndex: item.partIndex,
|
|
130
|
+
finishReason: item.candidate.finishReason,
|
|
131
|
+
safetyRatings: item.candidate.safetyRatings,
|
|
132
|
+
citationMetadata: item.candidate.citationMetadata,
|
|
133
|
+
groundingMetadata: item.candidate.groundingMetadata,
|
|
134
|
+
thoughtSignature: item.thoughtSignature,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
if (images.length === 0) {
|
|
139
|
+
const finishReasons = candidates.flatMap((candidate) => candidate.finishReason === undefined ? [] : [candidate.finishReason]);
|
|
140
|
+
return yield* output.invalid(`${NAME} returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect body for prompt feedback and candidate details`);
|
|
141
|
+
}
|
|
142
|
+
// Candidates that stopped for a safety or policy reason are partial results, not a silent drop.
|
|
143
|
+
const notices = [
|
|
144
|
+
...(decoded.promptFeedback === undefined
|
|
145
|
+
? []
|
|
146
|
+
: [
|
|
147
|
+
{
|
|
148
|
+
type: "filtered",
|
|
149
|
+
message: `${NAME} reported prompt feedback`,
|
|
150
|
+
providerMetadata: { google: { promptFeedback: decoded.promptFeedback } },
|
|
128
151
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
inputTokens: usage.promptTokenCount,
|
|
144
|
-
outputTokens,
|
|
145
|
-
nonCachedInputTokens: ProviderShared.subtractTokens(usage.promptTokenCount, usage.cachedContentTokenCount),
|
|
146
|
-
cacheReadInputTokens: usage.cachedContentTokenCount,
|
|
147
|
-
reasoningTokens: usage.thoughtsTokenCount,
|
|
148
|
-
totalTokens: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount),
|
|
149
|
-
providerMetadata: { google: usage },
|
|
150
|
-
}),
|
|
151
|
-
providerMetadata: {
|
|
152
|
-
google: {
|
|
153
|
-
modelVersion: decoded.modelVersion,
|
|
154
|
-
responseId: decoded.responseId,
|
|
155
|
-
promptFeedback: decoded.promptFeedback,
|
|
156
|
-
candidates: candidateMetadata,
|
|
152
|
+
]),
|
|
153
|
+
...candidates.flatMap((candidate, index) => candidate.finishReason === undefined || candidate.finishReason === "STOP"
|
|
154
|
+
? []
|
|
155
|
+
: [
|
|
156
|
+
{
|
|
157
|
+
type: "filtered",
|
|
158
|
+
message: `${NAME} candidate ${candidate.index ?? index} finished with ${candidate.finishReason}${candidate.finishMessage === undefined ? "" : `: ${candidate.finishMessage}`}`,
|
|
159
|
+
providerMetadata: {
|
|
160
|
+
google: {
|
|
161
|
+
candidateIndex: candidate.index ?? index,
|
|
162
|
+
finishReason: candidate.finishReason,
|
|
163
|
+
finishMessage: candidate.finishMessage,
|
|
164
|
+
safetyRatings: candidate.safetyRatings,
|
|
165
|
+
},
|
|
157
166
|
},
|
|
158
167
|
},
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
168
|
+
]),
|
|
169
|
+
];
|
|
170
|
+
const usage = decoded.usageMetadata;
|
|
171
|
+
const outputTokens = usage?.candidatesTokenCount === undefined ? undefined : usage.candidatesTokenCount + (usage.thoughtsTokenCount ?? 0);
|
|
172
|
+
return new ImageResponse({
|
|
173
|
+
images,
|
|
174
|
+
notices: notices.length === 0 ? undefined : notices,
|
|
175
|
+
usage: usage === undefined
|
|
176
|
+
? undefined
|
|
177
|
+
: {
|
|
178
|
+
type: "tokens",
|
|
179
|
+
input: usage.promptTokenCount,
|
|
180
|
+
output: outputTokens,
|
|
181
|
+
total: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount),
|
|
182
|
+
details: {
|
|
183
|
+
reasoningTokens: usage.thoughtsTokenCount,
|
|
184
|
+
cacheReadInputTokens: usage.cachedContentTokenCount,
|
|
185
|
+
google: usage,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
providerMetadata: {
|
|
189
|
+
google: {
|
|
190
|
+
modelVersion: decoded.modelVersion,
|
|
191
|
+
responseId: decoded.responseId,
|
|
192
|
+
promptFeedback: decoded.promptFeedback,
|
|
193
|
+
candidates: candidateMetadata,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// 7. Protocol and route
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
export const protocol = MediaProtocol.inline({
|
|
202
|
+
id: ADAPTER,
|
|
203
|
+
name: NAME,
|
|
204
|
+
unsupported: ["mask", "size", "format"],
|
|
205
|
+
body: { from: fromRequest },
|
|
206
|
+
response: { decode: decodeResponse },
|
|
207
|
+
});
|
|
208
|
+
export const model = (input) => ImageModel.fromRoute({
|
|
209
|
+
id: ADAPTER,
|
|
210
|
+
provider: PROVIDER,
|
|
211
|
+
protocol,
|
|
212
|
+
baseURL: DEFAULT_BASE_URL,
|
|
213
|
+
path: ({ request }) => `/models/${request.model.id}:generateContent`,
|
|
214
|
+
}, input);
|
|
179
215
|
export const GoogleImages = {
|
|
216
|
+
protocol,
|
|
180
217
|
model,
|
|
181
218
|
};
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { ImageModel } from "../image.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
4
|
type OpenString<Known extends string> = Known | (string & {});
|
|
5
|
+
/** Provider-native options. Common fields (`n`, `size`, `format`, `images`) live on the request. */
|
|
5
6
|
export type ImageOptions = {
|
|
6
|
-
readonly n?: number;
|
|
7
|
-
/** Aspect ratio hint, not an exact output resolution. */
|
|
8
|
-
readonly size?: string;
|
|
9
|
-
readonly outputFormat?: OpenString<"webp" | "png" | "jpeg">;
|
|
10
7
|
readonly responseFormat?: OpenString<"b64_json" | "url">;
|
|
11
8
|
readonly reasoningStrength?: OpenString<"low" | "high">;
|
|
12
9
|
readonly toolEnablement?: {
|
|
@@ -16,11 +13,9 @@ export type ImageOptions = {
|
|
|
16
13
|
};
|
|
17
14
|
readonly [key: string]: unknown;
|
|
18
15
|
};
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
export type Request = ImageRequestFor<ImageOptions>;
|
|
17
|
+
export declare const protocol: MediaProtocol.Inline<Request, ImageResponse>;
|
|
18
|
+
export declare const model: (input: MediaRoute.ModelInput & {
|
|
22
19
|
readonly baseURL: string;
|
|
23
|
-
readonly headers?: Record<string, string>;
|
|
24
|
-
readonly http?: HttpOptions;
|
|
25
20
|
}) => ImageModel<ImageOptions>;
|
|
26
21
|
export * as MetaImages from "./meta-images.js";
|