From 4da0afa5ca87c21950866f49964cf00ee5b3063e Mon Sep 17 00:00:00 2001 From: Major Hayden Date: Wed, 19 Aug 2026 14:12:36 -0500 Subject: [PATCH] fix(ai): preserve Vertex Anthropic tool continuations - fold terminal system updates into Vertex local tool-result turns - preserve native system updates for direct Anthropic and ordinary Vertex requests Refs #43478 Signed-off-by: Major Hayden --- .../ai/src/protocols/anthropic-messages.ts | 11 +- .../test/provider/anthropic-messages.test.ts | 107 ++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/protocols/anthropic-messages.ts b/packages/ai/src/protocols/anthropic-messages.ts index 3c31699694bd..65ce4be0ed6b 100644 --- a/packages/ai/src/protocols/anthropic-messages.ts +++ b/packages/ai/src/protocols/anthropic-messages.ts @@ -502,7 +502,16 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* ( if (message.role === "system") { if (splitsLocalToolResults(request.messages, index)) return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result") - if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request.messages, index)) { + // Vertex currently returns 404 for a terminal system message after local tool results. + const vertexToolResultUpdate = + request.model.provider === "google-vertex" && + request.messages[index - 1]?.role === "tool" && + request.messages[index + 1] === undefined + if ( + supportsNativeSystemUpdates(request) && + !vertexToolResultUpdate && + canUseNativeSystemUpdate(request.messages, index) + ) { messages.push(yield* lowerNativeSystemUpdate(message, breakpoints)) continue } diff --git a/packages/ai/test/provider/anthropic-messages.test.ts b/packages/ai/test/provider/anthropic-messages.test.ts index 8eb9ef31394b..b178a558d532 100644 --- a/packages/ai/test/provider/anthropic-messages.test.ts +++ b/packages/ai/test/provider/anthropic-messages.test.ts @@ -18,6 +18,14 @@ const opus48 = AnthropicMessages.route .with({ endpoint: { baseURL: "https://api.anthropic.test/v1/" }, auth: Auth.header("x-api-key", "test") }) .model({ id: "claude-opus-4-8" }) +const vertexOpus48 = AnthropicMessages.route + .with({ + provider: "google-vertex", + endpoint: { baseURL: "https://vertex.test/v1/" }, + auth: Auth.header("authorization", "Bearer test"), + }) + .model({ id: "claude-opus-4-8" }) + const request = LLM.request({ id: "req_1", model, @@ -277,6 +285,105 @@ describe("Anthropic Messages route", () => { }), ) + it.effect("keeps a terminal Vertex system update in the tool-result turn", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ + model: vertexOpus48, + messages: [ + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]), + Message.tool({ id: "call_1", name: "lookup", result: "Done." }), + Message.system("Operator update."), + ], + cache: "none", + }), + ) + + expect(prepared.body.messages).toEqual([ + { + role: "assistant", + content: [{ type: "tool_use", id: "call_1", name: "lookup", input: {} }], + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "call_1", + content: '"Done."', + is_error: undefined, + cache_control: undefined, + }, + { + type: "text", + text: "\nOperator update.\n", + cache_control: undefined, + }, + ], + }, + ]) + }), + ) + + it.effect("keeps a terminal direct Anthropic system update native", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ + model: opus48, + messages: [ + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]), + Message.tool({ id: "call_1", name: "lookup", result: "Done." }), + Message.system("Operator update."), + ], + cache: "none", + }), + ) + + expect(prepared.body.messages).toEqual([ + { + role: "assistant", + content: [{ type: "tool_use", id: "call_1", name: "lookup", input: {} }], + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "call_1", + content: '"Done."', + is_error: undefined, + cache_control: undefined, + }, + ], + }, + { + role: "system", + content: [{ type: "text", text: "Operator update.", cache_control: undefined }], + }, + ]) + }), + ) + + it.effect("keeps an ordinary terminal Vertex system update native", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ + model: vertexOpus48, + messages: [Message.user("Before."), Message.system("Operator update.")], + cache: "none", + }), + ) + + expect(prepared.body.messages).toEqual([ + { role: "user", content: [{ type: "text", text: "Before." }] }, + { + role: "system", + content: [{ type: "text", text: "Operator update.", cache_control: undefined }], + }, + ]) + }), + ) + it.effect("rejects a system update between a local tool call and its result", () => Effect.gen(function* () { const error = yield* compileRequest(