mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(agents): strip stale gemini assistant prefill
This commit is contained in:
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/models: skip external OpenRouter and LiteLLM pricing refreshes for local/self-hosted model endpoints so startup does not wait on remote pricing catalogs for local-only Ollama, vLLM, and compatible providers. Thanks @codex.
|
||||
- CLI/plugins: stop security-blocked plugin installs from retrying as hook packs, so normal plugin packages report the scanner failure without a misleading "not a valid hook pack" follow-up. Fixes #61175; supersedes #64102. Thanks @KonsultDigital and @ziyincody.
|
||||
- Agents/Anthropic: strip stale trailing assistant prefill turns from outbound replay so context-engine short circuits cannot send unsupported assistant-prefill payloads to provider APIs. Fixes #72556. Thanks @Veda-openclaw.
|
||||
- Agents/Google: strip stale trailing assistant/model prefill turns from Gemini outbound replay so Google Generative AI requests end with a user turn or function response. Follow-up to #72556. Thanks @Veda-openclaw.
|
||||
- Control UI/Dreaming: require explicit confirmation before applying restart-impacting Dreaming mode changes, with restart warning copy and loading feedback. Fixes #63804. (#63807) Thanks @bbddbb1.
|
||||
- CLI/update: keep the automatic post-update completion refresh on the core-command tree so it no longer stages bundled plugin runtime deps before the Gateway restart path, avoiding `.24` update hangs and 1006 disconnect cascades. Fixes #72665. Thanks @sakalaboator and @He-Pin.
|
||||
- Agents/Bedrock: stop heartbeat runs from persisting blank user transcript turns and repair existing blank user text messages before replay, preventing AWS Bedrock `ContentBlock` blank-text validation failures. Fixes #72640 and #72622. Thanks @goldzulu.
|
||||
|
||||
@@ -1671,6 +1671,44 @@ describe("wrapStreamFnSanitizeMalformedToolCalls", () => {
|
||||
expect(seenContext.messages).not.toBe(messages);
|
||||
});
|
||||
|
||||
it("strips trailing assistant prefill turns for Gemini outbound replay", async () => {
|
||||
const messages = [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "earlier question" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "stale model answer" }],
|
||||
},
|
||||
];
|
||||
const baseFn = vi.fn((_model, _context) =>
|
||||
createFakeStream({ events: [], resultMessage: { role: "assistant", content: [] } }),
|
||||
);
|
||||
|
||||
const wrapped = wrapStreamFnSanitizeMalformedToolCalls(baseFn as never, new Set(["read"]), {
|
||||
validateGeminiTurns: true,
|
||||
preserveSignatures: true,
|
||||
dropThinkingBlocks: false,
|
||||
} as never);
|
||||
const stream = wrapped(
|
||||
{ api: "google-generative-ai" } as never,
|
||||
{ messages } as never,
|
||||
{} as never,
|
||||
) as FakeWrappedStream | Promise<FakeWrappedStream>;
|
||||
await Promise.resolve(stream);
|
||||
|
||||
expect(baseFn).toHaveBeenCalledTimes(1);
|
||||
const seenContext = baseFn.mock.calls[0]?.[1] as { messages: unknown[] };
|
||||
expect(seenContext.messages).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "earlier question" }],
|
||||
},
|
||||
]);
|
||||
expect(seenContext.messages).not.toBe(messages);
|
||||
});
|
||||
|
||||
it("drops signed thinking turns when sibling replay tool calls are not allowlisted", async () => {
|
||||
const messages = [
|
||||
{
|
||||
|
||||
@@ -895,16 +895,25 @@ export function wrapStreamFnSanitizeMalformedToolCalls(
|
||||
let nextMessages = replayInputsChanged
|
||||
? sanitizeToolUseResultPairing(sanitized.messages)
|
||||
: sanitized.messages;
|
||||
let strippedTrailingAssistantPrefill = false;
|
||||
if (transcriptPolicy?.validateAnthropicTurns) {
|
||||
nextMessages = sanitizeAnthropicReplayToolResults(nextMessages, {
|
||||
disallowEmbeddedUserToolResultsForSignedThinkingReplay: allowProviderOwnedThinkingReplay,
|
||||
});
|
||||
}
|
||||
if (transcriptPolicy?.validateAnthropicTurns || transcriptPolicy?.validateGeminiTurns) {
|
||||
const beforeStrip = nextMessages;
|
||||
nextMessages = stripTrailingAssistantPrefillTurns(nextMessages);
|
||||
strippedTrailingAssistantPrefill ||= nextMessages !== beforeStrip;
|
||||
}
|
||||
if (nextMessages === messages) {
|
||||
return baseFn(model, context, options);
|
||||
}
|
||||
if (sanitized.droppedAssistantMessages > 0 || transcriptPolicy?.validateAnthropicTurns) {
|
||||
if (
|
||||
sanitized.droppedAssistantMessages > 0 ||
|
||||
transcriptPolicy?.validateAnthropicTurns ||
|
||||
strippedTrailingAssistantPrefill
|
||||
) {
|
||||
if (transcriptPolicy?.validateGeminiTurns) {
|
||||
nextMessages = validateGeminiTurns(nextMessages);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user