@opencode-ai/ai 0.0.0-beta-17823 → 0.0.0-beta-17898
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.d.ts +0 -1
- package/dist/protocols/gemini.js +39 -13
- package/dist/protocols/openai-images.js +1 -1
- package/dist/protocols/utils/image-input.js +1 -1
- package/dist/protocols/utils/lifecycle.d.ts +1 -1
- package/dist/protocols/utils/lifecycle.js +2 -2
- package/package.json +3 -3
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",
|
|
@@ -450,23 +468,31 @@ const step = (state, event) => {
|
|
|
450
468
|
const events = [];
|
|
451
469
|
let hasToolCalls = nextState.hasToolCalls;
|
|
452
470
|
let lifecycle = nextState.lifecycle;
|
|
453
|
-
let nextToolCallId = nextState.nextToolCallId;
|
|
454
471
|
let reasoningSignature = nextState.reasoningSignature;
|
|
472
|
+
let textSignature = nextState.textSignature;
|
|
455
473
|
for (const part of candidate.content.parts) {
|
|
456
|
-
|
|
457
|
-
|
|
474
|
+
const signature = "thoughtSignature" in part && part.thoughtSignature ? part.thoughtSignature : undefined;
|
|
475
|
+
// Gemini attaches replay signatures to thought parts, visible text, or function calls;
|
|
476
|
+
// each block kind must retain the signature attached to its own parts.
|
|
477
|
+
if (signature !== undefined && "thought" in part && part.thought)
|
|
478
|
+
reasoningSignature = signature;
|
|
479
|
+
else if (signature !== undefined && "text" in part)
|
|
480
|
+
textSignature = signature;
|
|
458
481
|
if ("text" in part && part.text.length > 0) {
|
|
459
482
|
if (part.thought) {
|
|
460
|
-
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text,
|
|
483
|
+
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text, signature ? googleMetadata({ thoughtSignature: signature }) : undefined);
|
|
461
484
|
continue;
|
|
462
485
|
}
|
|
463
486
|
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined);
|
|
464
|
-
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text);
|
|
487
|
+
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text, textSignature ? googleMetadata({ thoughtSignature: textSignature }) : undefined);
|
|
488
|
+
textSignature = undefined;
|
|
465
489
|
continue;
|
|
466
490
|
}
|
|
467
491
|
if ("functionCall" in part) {
|
|
468
492
|
const input = part.functionCall.args === undefined ? {} : part.functionCall.args;
|
|
469
|
-
|
|
493
|
+
// Gemini 2.0+ and Vertex supply a unique function call ID on the part; when omitted (e.g. Gemini 1.5),
|
|
494
|
+
// generate a globally unique ID rather than a per-request counter to prevent cross-request collisions in downstream registries.
|
|
495
|
+
const id = part.functionCall.id ?? `tool_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
470
496
|
const metadata = {
|
|
471
497
|
...(part.functionCall.id === undefined ? {} : { functionCallId: part.functionCall.id }),
|
|
472
498
|
...(part.thoughtSignature === undefined ? {} : { thoughtSignature: part.thoughtSignature }),
|
|
@@ -487,8 +513,8 @@ const step = (state, event) => {
|
|
|
487
513
|
...nextState,
|
|
488
514
|
hasToolCalls,
|
|
489
515
|
lifecycle,
|
|
490
|
-
nextToolCallId,
|
|
491
516
|
reasoningSignature,
|
|
517
|
+
textSignature,
|
|
492
518
|
finishReason: candidate.finishReason ?? nextState.finishReason,
|
|
493
519
|
},
|
|
494
520
|
events,
|
|
@@ -509,7 +535,7 @@ export const protocol = Protocol.make({
|
|
|
509
535
|
},
|
|
510
536
|
stream: {
|
|
511
537
|
event: Protocol.jsonEvent(GeminiEvent),
|
|
512
|
-
initial: () => ({ hasToolCalls: false,
|
|
538
|
+
initial: () => ({ hasToolCalls: false, lifecycle: Lifecycle.initial() }),
|
|
513
539
|
step,
|
|
514
540
|
onHalt: finish,
|
|
515
541
|
},
|
|
@@ -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-17898",
|
|
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-17898",
|
|
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-17898",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-rc.110",
|
|
45
45
|
"google-auth-library": "10.5.0"
|