@opencode-ai/ai 0.0.0-dev-17987 → 0.0.0-dev-18000
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 +23 -18
- package/dist/providers/google-vertex.js +13 -1
- package/package.json +3 -3
package/dist/protocols/gemini.js
CHANGED
|
@@ -29,6 +29,12 @@ const requiresThoughtSignatureFallback = (modelID) => {
|
|
|
29
29
|
// Gemini 3 accepts media nested inside function responses; matched Gemini 2.5 variants reject it,
|
|
30
30
|
// so their tool-result attachments lower as a separate user turn instead.
|
|
31
31
|
const routesLegacyToolMedia = (modelID) => /gemini-2[.-]5(?:[.-]|$)/i.test(modelID);
|
|
32
|
+
// Blacklist: Gemini 1.x/2.x ignore or reject explicit function call ids.
|
|
33
|
+
// Every other model id (Gemini 3+, gemma, anything unrecognized) gets them.
|
|
34
|
+
const omitsFunctionCallIds = (modelID) => {
|
|
35
|
+
const match = /^gemini(?:-live)?-(\d+)/i.exec(modelID);
|
|
36
|
+
return match !== null && Number(match[1]) < 3;
|
|
37
|
+
};
|
|
32
38
|
// =============================================================================
|
|
33
39
|
// Request Body Schema
|
|
34
40
|
// =============================================================================
|
|
@@ -188,18 +194,13 @@ const thoughtSignature = (providerMetadata) => {
|
|
|
188
194
|
? google.thoughtSignature
|
|
189
195
|
: undefined;
|
|
190
196
|
};
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
return ProviderShared.isRecord(google) && typeof google.functionCallId === "string"
|
|
194
|
-
? google.functionCallId
|
|
195
|
-
: undefined;
|
|
196
|
-
};
|
|
197
|
-
const lowerToolCall = (part) => ({
|
|
198
|
-
functionCall: { id: functionCallId(part.providerMetadata), name: part.name, args: part.input },
|
|
197
|
+
const lowerToolCall = (part, omitIds) => ({
|
|
198
|
+
functionCall: { ...(omitIds ? {} : { id: part.id }), name: part.name, args: part.input },
|
|
199
199
|
thoughtSignature: thoughtSignature(part.providerMetadata),
|
|
200
200
|
});
|
|
201
201
|
const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
202
202
|
const contents = [];
|
|
203
|
+
const omitCallIds = omitsFunctionCallIds(request.model.id);
|
|
203
204
|
const legacyToolMedia = routesLegacyToolMedia(request.model.id);
|
|
204
205
|
let pendingMedia;
|
|
205
206
|
const flushMedia = () => {
|
|
@@ -248,7 +249,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
248
249
|
continue;
|
|
249
250
|
}
|
|
250
251
|
if (part.type === "tool-call") {
|
|
251
|
-
const lowered = lowerToolCall(part);
|
|
252
|
+
const lowered = lowerToolCall(part, omitCallIds);
|
|
252
253
|
const signature = lowered.thoughtSignature;
|
|
253
254
|
parts.push({
|
|
254
255
|
...lowered,
|
|
@@ -272,7 +273,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
272
273
|
if (part.result.type !== "content") {
|
|
273
274
|
parts.push({
|
|
274
275
|
functionResponse: {
|
|
275
|
-
id:
|
|
276
|
+
...(omitCallIds ? {} : { id: part.id }),
|
|
276
277
|
name: part.name,
|
|
277
278
|
response: {
|
|
278
279
|
name: part.name,
|
|
@@ -295,7 +296,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
|
|
|
295
296
|
(pendingMedia ??= []).push(...media);
|
|
296
297
|
parts.push({
|
|
297
298
|
functionResponse: {
|
|
298
|
-
id:
|
|
299
|
+
...(omitCallIds ? {} : { id: part.id }),
|
|
299
300
|
name: part.name,
|
|
300
301
|
response: {
|
|
301
302
|
name: part.name,
|
|
@@ -470,6 +471,8 @@ const step = (state, event) => {
|
|
|
470
471
|
let lifecycle = nextState.lifecycle;
|
|
471
472
|
let reasoningSignature = nextState.reasoningSignature;
|
|
472
473
|
let textSignature = nextState.textSignature;
|
|
474
|
+
// Supplier ids must be tracked across chunks of the same response, not just within one event's parts.
|
|
475
|
+
const seenCallIds = new Set(nextState.seenCallIds);
|
|
473
476
|
for (const part of candidate.content.parts) {
|
|
474
477
|
const signature = "thoughtSignature" in part && part.thoughtSignature ? part.thoughtSignature : undefined;
|
|
475
478
|
// Gemini attaches replay signatures to thought parts, visible text, or function calls;
|
|
@@ -490,20 +493,21 @@ const step = (state, event) => {
|
|
|
490
493
|
}
|
|
491
494
|
if ("functionCall" in part) {
|
|
492
495
|
const input = part.functionCall.args === undefined ? {} : part.functionCall.args;
|
|
493
|
-
// Gemini 2.0+
|
|
496
|
+
// Gemini 2.0+ supplies a unique function call ID on the part; when omitted (e.g. Gemini 1.5),
|
|
494
497
|
// generate a globally unique ID rather than a per-request counter to prevent cross-request collisions in downstream registries.
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
498
|
+
// A repeated supplier id would replay as two identical calls, so only the first occurrence keeps it.
|
|
499
|
+
const supplied = part.functionCall.id;
|
|
500
|
+
const duplicate = supplied !== undefined && seenCallIds.has(supplied);
|
|
501
|
+
if (supplied !== undefined)
|
|
502
|
+
seenCallIds.add(supplied);
|
|
503
|
+
const id = supplied !== undefined && !duplicate ? supplied : `tool_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
500
504
|
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined);
|
|
501
505
|
lifecycle = Lifecycle.stepStart(lifecycle, events);
|
|
502
506
|
events.push(LLMEvent.toolCall({
|
|
503
507
|
id,
|
|
504
508
|
name: part.functionCall.name,
|
|
505
509
|
input,
|
|
506
|
-
providerMetadata:
|
|
510
|
+
providerMetadata: part.thoughtSignature === undefined ? undefined : googleMetadata({ thoughtSignature: part.thoughtSignature }),
|
|
507
511
|
}));
|
|
508
512
|
hasToolCalls = true;
|
|
509
513
|
}
|
|
@@ -515,6 +519,7 @@ const step = (state, event) => {
|
|
|
515
519
|
lifecycle,
|
|
516
520
|
reasoningSignature,
|
|
517
521
|
textSignature,
|
|
522
|
+
seenCallIds,
|
|
518
523
|
finishReason: candidate.finishReason ?? nextState.finishReason,
|
|
519
524
|
},
|
|
520
525
|
events,
|
|
@@ -10,11 +10,23 @@ import { GoogleVertexShared } from "./google-vertex-shared.js";
|
|
|
10
10
|
export const id = ProviderID.make("google-vertex");
|
|
11
11
|
const fromRequest = Effect.fn("GoogleVertex.fromRequest")(function* (request) {
|
|
12
12
|
const body = yield* Gemini.protocol.body.from(request);
|
|
13
|
+
// Vertex's native REST schema rejects `id` on FunctionCall/FunctionResponse parts with HTTP 400,
|
|
14
|
+
// unlike AI Studio, so history minted there cannot be lowered verbatim.
|
|
15
|
+
const contents = body.contents.map((content) => ({
|
|
16
|
+
...content,
|
|
17
|
+
parts: content.parts.map((part) => {
|
|
18
|
+
if ("functionCall" in part)
|
|
19
|
+
return { ...part, functionCall: { ...part.functionCall, id: undefined } };
|
|
20
|
+
if ("functionResponse" in part)
|
|
21
|
+
return { ...part, functionResponse: { ...part.functionResponse, id: undefined } };
|
|
22
|
+
return part;
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
13
25
|
const value = request.providerOptions?.labels;
|
|
14
26
|
const labels = ProviderShared.isRecord(value)
|
|
15
27
|
? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"))
|
|
16
28
|
: undefined;
|
|
17
|
-
return { ...body, labels };
|
|
29
|
+
return { ...body, contents, labels };
|
|
18
30
|
});
|
|
19
31
|
const protocol = {
|
|
20
32
|
...Gemini.protocol,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-18000",
|
|
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-dev-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-dev-18000",
|
|
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-dev-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-dev-18000",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-rc.110",
|
|
45
45
|
"google-auth-library": "10.5.0"
|