@opencode-ai/ai 0.0.0-beta-17823 → 0.0.0-beta-17887
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/protocols/gemini.js
CHANGED
|
@@ -26,6 +26,9 @@ const requiresThoughtSignatureFallback = (modelID) => {
|
|
|
26
26
|
return false;
|
|
27
27
|
return !/(^|\/)gemini-robotics-er-1\.5(?:[.-]|$)/i.test(modelID);
|
|
28
28
|
};
|
|
29
|
+
// Gemini 3 accepts media nested inside function responses; matched Gemini 2.5 variants reject it,
|
|
30
|
+
// so their tool-result attachments lower as a separate user turn instead.
|
|
31
|
+
const routesLegacyToolMedia = (modelID) => /gemini-2[.-]5(?:[.-]|$)/i.test(modelID);
|
|
29
32
|
// =============================================================================
|
|
30
33
|
// Request Body Schema
|
|
31
34
|
// =============================================================================
|
|
@@ -197,7 +200,17 @@ const lowerToolCall = (part) => ({
|
|
|
197
200
|
});
|
|
198
201
|
const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
199
202
|
const contents = [];
|
|
203
|
+
const legacyToolMedia = routesLegacyToolMedia(request.model.id);
|
|
204
|
+
let pendingMedia;
|
|
205
|
+
const flushMedia = () => {
|
|
206
|
+
if (!pendingMedia)
|
|
207
|
+
return;
|
|
208
|
+
contents.push({ role: "user", parts: [{ text: "Attached media from tool result:" }, ...pendingMedia] });
|
|
209
|
+
pendingMedia = undefined;
|
|
210
|
+
};
|
|
200
211
|
for (const message of request.messages) {
|
|
212
|
+
if (message.role !== "tool")
|
|
213
|
+
flushMedia();
|
|
201
214
|
if (message.role === "system") {
|
|
202
215
|
const part = yield* ProviderShared.wrappedSystemUpdate("Gemini", message);
|
|
203
216
|
const previous = contents.at(-1);
|
|
@@ -227,7 +240,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
227
240
|
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
|
|
228
241
|
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", ["text", "reasoning", "tool-call"]);
|
|
229
242
|
if (part.type === "text") {
|
|
230
|
-
parts.push({ text: part.text });
|
|
243
|
+
parts.push({ text: part.text, thoughtSignature: thoughtSignature(part.providerMetadata) });
|
|
231
244
|
continue;
|
|
232
245
|
}
|
|
233
246
|
if (part.type === "reasoning") {
|
|
@@ -278,6 +291,8 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
278
291
|
const value = ProviderShared.normalizeToolFile(item);
|
|
279
292
|
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } });
|
|
280
293
|
}
|
|
294
|
+
if (legacyToolMedia && media.length > 0)
|
|
295
|
+
(pendingMedia ??= []).push(...media);
|
|
281
296
|
parts.push({
|
|
282
297
|
functionResponse: {
|
|
283
298
|
id: functionCallId(part.providerMetadata),
|
|
@@ -286,7 +301,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
286
301
|
name: part.name,
|
|
287
302
|
content: text.join("\n"),
|
|
288
303
|
},
|
|
289
|
-
parts: media.length
|
|
304
|
+
parts: legacyToolMedia || media.length === 0 ? undefined : media,
|
|
290
305
|
},
|
|
291
306
|
});
|
|
292
307
|
}
|
|
@@ -298,6 +313,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
298
313
|
else
|
|
299
314
|
contents.push({ role: "user", parts });
|
|
300
315
|
}
|
|
316
|
+
flushMedia();
|
|
301
317
|
return contents;
|
|
302
318
|
});
|
|
303
319
|
const resolveOptions = (request) => {
|
|
@@ -422,9 +438,11 @@ const finish = (state) => {
|
|
|
422
438
|
if (finishReason === undefined && state.usage === undefined)
|
|
423
439
|
return [];
|
|
424
440
|
const events = [];
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
: state.
|
|
441
|
+
let lifecycle = state.lifecycle;
|
|
442
|
+
if (state.reasoningSignature !== undefined)
|
|
443
|
+
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", googleMetadata({ thoughtSignature: state.reasoningSignature }));
|
|
444
|
+
if (state.textSignature !== undefined)
|
|
445
|
+
lifecycle = Lifecycle.textEnd(lifecycle, events, "text-0", googleMetadata({ thoughtSignature: state.textSignature }));
|
|
428
446
|
Lifecycle.finish(lifecycle, events, {
|
|
429
447
|
reason: {
|
|
430
448
|
normalized: promptBlockReason === undefined ? mapFinishReason(finishReason, state.hasToolCalls) : "content-filter",
|
|
@@ -452,16 +470,23 @@ const step = (state, event) => {
|
|
|
452
470
|
let lifecycle = nextState.lifecycle;
|
|
453
471
|
let nextToolCallId = nextState.nextToolCallId;
|
|
454
472
|
let reasoningSignature = nextState.reasoningSignature;
|
|
473
|
+
let textSignature = nextState.textSignature;
|
|
455
474
|
for (const part of candidate.content.parts) {
|
|
456
|
-
|
|
457
|
-
|
|
475
|
+
const signature = "thoughtSignature" in part && part.thoughtSignature ? part.thoughtSignature : undefined;
|
|
476
|
+
// Gemini attaches replay signatures to thought parts, visible text, or function calls;
|
|
477
|
+
// each block kind must retain the signature attached to its own parts.
|
|
478
|
+
if (signature !== undefined && "thought" in part && part.thought)
|
|
479
|
+
reasoningSignature = signature;
|
|
480
|
+
else if (signature !== undefined && "text" in part)
|
|
481
|
+
textSignature = signature;
|
|
458
482
|
if ("text" in part && part.text.length > 0) {
|
|
459
483
|
if (part.thought) {
|
|
460
|
-
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text,
|
|
484
|
+
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text, signature ? googleMetadata({ thoughtSignature: signature }) : undefined);
|
|
461
485
|
continue;
|
|
462
486
|
}
|
|
463
487
|
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined);
|
|
464
|
-
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text);
|
|
488
|
+
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text, textSignature ? googleMetadata({ thoughtSignature: textSignature }) : undefined);
|
|
489
|
+
textSignature = undefined;
|
|
465
490
|
continue;
|
|
466
491
|
}
|
|
467
492
|
if ("functionCall" in part) {
|
|
@@ -489,6 +514,7 @@ const step = (state, event) => {
|
|
|
489
514
|
lifecycle,
|
|
490
515
|
nextToolCallId,
|
|
491
516
|
reasoningSignature,
|
|
517
|
+
textSignature,
|
|
492
518
|
finishReason: candidate.finishReason ?? nextState.finishReason,
|
|
493
519
|
},
|
|
494
520
|
events,
|
|
@@ -61,7 +61,7 @@ export const model = (input) => {
|
|
|
61
61
|
return Effect.succeed({ data: image.data, mediaType: image.mediaType });
|
|
62
62
|
if (image.type === "url")
|
|
63
63
|
return ImageInputs.decodeDataUrl(image.url, ADAPTER);
|
|
64
|
-
return Effect.
|
|
64
|
+
return Effect.undefined;
|
|
65
65
|
});
|
|
66
66
|
const multipartMask = mask === undefined
|
|
67
67
|
? undefined
|
|
@@ -8,7 +8,7 @@ const invalid = (module, message) => new AIError({
|
|
|
8
8
|
export const dataUrl = (input) => `data:${input.mediaType};base64,${Encoding.encodeBase64(input.data)}`;
|
|
9
9
|
export const decodeDataUrl = (url, module) => {
|
|
10
10
|
if (!url.startsWith("data:"))
|
|
11
|
-
return Effect.
|
|
11
|
+
return Effect.undefined;
|
|
12
12
|
const match = /^data:([^;,]+);base64,(.*)$/s.exec(url);
|
|
13
13
|
if (!match)
|
|
14
14
|
return Effect.fail(invalid(module, "Image data URLs must contain a MIME type and base64 data"));
|
|
@@ -7,7 +7,7 @@ export interface State {
|
|
|
7
7
|
export declare const initial: () => State;
|
|
8
8
|
export declare const stepStart: (state: State, events: LLMEvent[]) => State;
|
|
9
9
|
export declare const textStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
10
|
-
export declare const textDelta: (state: State, events: LLMEvent[], id: string, text: string) => State;
|
|
10
|
+
export declare const textDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
|
|
11
11
|
export declare const reasoningStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
12
12
|
export declare const reasoningDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
|
|
13
13
|
export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
@@ -13,9 +13,9 @@ export const textStart = (state, events, id, providerMetadata) => {
|
|
|
13
13
|
events.push(LLMEvent.textStart({ id, providerMetadata }));
|
|
14
14
|
return { ...stepped, text: new Set([...stepped.text, id]) };
|
|
15
15
|
};
|
|
16
|
-
export const textDelta = (state, events, id, text) => {
|
|
16
|
+
export const textDelta = (state, events, id, text, providerMetadata) => {
|
|
17
17
|
const started = textStart(state, events, id);
|
|
18
|
-
events.push(LLMEvent.textDelta({ id, text }));
|
|
18
|
+
events.push(LLMEvent.textDelta({ id, text, providerMetadata }));
|
|
19
19
|
return started;
|
|
20
20
|
};
|
|
21
21
|
export const reasoningStart = (state, events, id, providerMetadata) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-17887",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
32
|
"@effect/platform-node": "4.0.0-rc.110",
|
|
33
|
-
"@opencode-ai/http-recorder": "0.0.0-beta-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-beta-17887",
|
|
34
34
|
"@tsconfig/bun": "1.0.9",
|
|
35
35
|
"@types/bun": "1.3.13",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@smithy/eventstream-codec": "4.2.14",
|
|
41
41
|
"@smithy/util-utf8": "4.2.2",
|
|
42
|
-
"@opencode-ai/schema": "0.0.0-beta-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-beta-17887",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-rc.110",
|
|
45
45
|
"google-auth-library": "10.5.0"
|