fix(core): fence v2 context placement

This commit is contained in:
Kit Langton 2026-06-04 22:57:20 -04:00
commit 91bd2f2c37
7 changed files with 97 additions and 7 deletions

View file

@ -363,6 +363,18 @@ const canUseNativeSystemUpdate = (messages: LLMRequest["messages"], index: numbe
)
}
const splitsLocalToolResults = (messages: LLMRequest["messages"], index: number) => {
const pending = new Set<string>()
for (const message of messages.slice(0, index)) {
for (const part of message.content) {
if (message.role === "assistant" && part.type === "tool-call" && part.providerExecuted !== true)
pending.add(part.id)
if (message.role === "tool" && part.type === "tool-result") pending.delete(part.id)
}
}
return pending.size > 0
}
const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (
message: LLMRequest["messages"][number],
breakpoints: Cache.Breakpoints,
@ -386,6 +398,8 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
for (const [index, message] of request.messages.entries()) {
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)) {
messages.push(yield* lowerNativeSystemUpdate(message, breakpoints))
continue

View file

@ -168,6 +168,25 @@ describe("Anthropic Messages route", () => {
}),
)
it.effect("rejects a system update between a local tool call and its result", () =>
Effect.gen(function* () {
const error = yield* LLMClient.prepare(
LLM.request({
model: opus48,
messages: [
Message.user("Use the tool."),
Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]),
Message.system("Too early."),
Message.tool({ id: "call_1", name: "lookup", result: "Done." }),
],
cache: "none",
}),
).pipe(Effect.flip)
expect(error.message).toContain("system updates cannot split a local tool call from its tool result")
}),
)
it.effect("prepares tool call and tool result messages", () =>
Effect.gen(function* () {
const prepared = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(