fix(gemini): gate thought-signature replay trust · openclaw/openclaw@a428568
steipete
·
2026-05-07
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -542,6 +542,42 @@ describe("google transport stream", () => {
|
542 | 542 | }); |
543 | 543 | }); |
544 | 544 | |
| 545 | +it("does not trust cross-provider tool-call thought signatures for non-Gemini-3 models", () => { |
| 546 | +const model = buildGeminiModel({ |
| 547 | +id: "gemini-2.5-pro", |
| 548 | +name: "Gemini 2.5 Pro", |
| 549 | +}); |
| 550 | + |
| 551 | +const params = buildGoogleGenerativeAiParams(model, { |
| 552 | +messages: [ |
| 553 | +{ |
| 554 | +role: "assistant", |
| 555 | +provider: "anthropic", |
| 556 | +api: "anthropic-messages", |
| 557 | +model: "claude-opus-4-7", |
| 558 | +stopReason: "toolUse", |
| 559 | +timestamp: 0, |
| 560 | +content: [ |
| 561 | +{ |
| 562 | +type: "toolCall", |
| 563 | +id: "call_1", |
| 564 | +name: "lookup", |
| 565 | +arguments: { q: "hello" }, |
| 566 | +thoughtSignature: "foreign_sig", |
| 567 | +}, |
| 568 | +], |
| 569 | +}, |
| 570 | +], |
| 571 | +} as never); |
| 572 | + |
| 573 | +expect(params.contents[0]).toMatchObject({ |
| 574 | +role: "model", |
| 575 | +parts: [{ functionCall: { name: "lookup", args: { q: "hello" } } }], |
| 576 | +}); |
| 577 | +expect(JSON.stringify(params.contents)).not.toContain("foreign_sig"); |
| 578 | +expect(JSON.stringify(params.contents)).not.toContain("skip_thought_signature_validator"); |
| 579 | +}); |
| 580 | + |
545 | 581 | it("builds direct Gemini payloads without negative fallback thinking budgets", () => { |
546 | 582 | const model = { |
547 | 583 | id: "custom-gemini-model", |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -386,7 +386,9 @@ function convertGoogleMessages(model: GoogleTransportModel, context: Context) {
|
386 | 386 | context.messages, |
387 | 387 | model, |
388 | 388 | (id) => (requiresToolCallId(model.id) ? normalizeToolCallId(id) : id), |
389 | | -{ preserveCrossModelToolCallThoughtSignature: true }, |
| 389 | +{ |
| 390 | +preserveCrossModelToolCallThoughtSignature: requiresToolCallThoughtSignature(model.id), |
| 391 | +}, |
390 | 392 | ); |
391 | 393 | for (const msg of transformedMessages) { |
392 | 394 | if (msg.role === "user") { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -2980,6 +2980,53 @@ describe("openai transport stream", () => {
|
2980 | 2980 | "skip_thought_signature_validator", |
2981 | 2981 | ); |
2982 | 2982 | }); |
| 2983 | + |
| 2984 | +it("does not trust cross-route thought_signature for non-Gemini-3 Google compat models", () => { |
| 2985 | +const nonGemini3Model = { |
| 2986 | + ...geminiModel, |
| 2987 | +id: "gemini-2.5-pro", |
| 2988 | +name: "Gemini 2.5 Pro", |
| 2989 | +}; |
| 2990 | +const params = buildOpenAICompletionsParams( |
| 2991 | +nonGemini3Model, |
| 2992 | +{ |
| 2993 | +messages: [ |
| 2994 | +{ |
| 2995 | +role: "assistant", |
| 2996 | +api: "google-generative-ai", |
| 2997 | +provider: nonGemini3Model.provider, |
| 2998 | +model: nonGemini3Model.id, |
| 2999 | +usage: { |
| 3000 | +input: 0, |
| 3001 | +output: 0, |
| 3002 | +cacheRead: 0, |
| 3003 | +cacheWrite: 0, |
| 3004 | +totalTokens: 0, |
| 3005 | +cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, |
| 3006 | +}, |
| 3007 | +stopReason: "toolUse", |
| 3008 | +timestamp: 1, |
| 3009 | +content: [ |
| 3010 | +{ |
| 3011 | +type: "toolCall", |
| 3012 | +id: "call_abc", |
| 3013 | +name: "echo_value", |
| 3014 | +arguments: { value: "repro" }, |
| 3015 | +thoughtSignature: "SIG-OPAQUE-ABC==", |
| 3016 | +}, |
| 3017 | +], |
| 3018 | +}, |
| 3019 | +], |
| 3020 | +tools: [], |
| 3021 | +} as never, |
| 3022 | +undefined, |
| 3023 | +) as { messages: Array<Record<string, unknown>> }; |
| 3024 | + |
| 3025 | +const assistant = params.messages.find((message) => message.role === "assistant") as |
| 3026 | +| { tool_calls?: Array<{ extra_content?: unknown }> } |
| 3027 | +| undefined; |
| 3028 | +expect(assistant?.tool_calls?.[0]?.extra_content).toBeUndefined(); |
| 3029 | +}); |
2983 | 3030 | }); |
2984 | 3031 | |
2985 | 3032 | it("uses Mistral compat defaults for direct Mistral completions providers", () => { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1836,6 +1836,9 @@ function injectToolCallThoughtSignatures(
|
1836 | 1836 | source.api === model.api && |
1837 | 1837 | source.provider === model.provider && |
1838 | 1838 | source.model === model.id; |
| 1839 | +if (!isSameRoute && !fallbackSig) { |
| 1840 | +continue; |
| 1841 | +} |
1839 | 1842 | sigById.set(id, isSameRoute ? sig : (fallbackSig ?? sig)); |
1840 | 1843 | } |
1841 | 1844 | } |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。