fix(ai): preserve response message phases (#38777)

This commit is contained in:
Aiden Cline 2026-07-25 14:06:00 -05:00 committed by GitHub
commit c5bf4edb10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 416 additions and 54 deletions

View file

@ -767,4 +767,35 @@ Recent work
},
])
})
test("preserves assistant text provider state across same-provider model changes and failures", () => {
const messages = toLLMMessages(
[
SessionMessage.Assistant.make({
id: id("assistant-phase"),
type: "assistant",
agent: build,
model: { id: ModelV2.ID.make("old"), providerID: ProviderV2.ID.make("provider") },
content: [
SessionMessage.AssistantText.make({
type: "text",
text: "Checking.",
state: { phase: "commentary" },
}),
],
error: { type: "provider.unknown", message: "Interrupted after commentary" },
time: { created, completed: created },
}),
],
ModelV2.Ref.make({ id: ModelV2.ID.make("new"), providerID: ProviderV2.ID.make("provider") }),
)
expect(messages[0]?.content).toEqual([
{
type: "text",
text: "Checking.",
providerMetadata: { provider: { phase: "commentary" } },
},
])
})
})

View file

@ -2672,6 +2672,47 @@ describe("SessionRunnerLLM", () => {
}),
)
it.effect("restores durable text provider metadata in the next request", () =>
Effect.gen(function* () {
const session = yield* setup
yield* admit(session, "Check first")
response = [
LLMEvent.stepStart({ index: 0 }),
LLMEvent.textStart({ id: "commentary", providerMetadata: { openai: { phase: "commentary" } } }),
LLMEvent.textDelta({ id: "commentary", text: "Checking." }),
LLMEvent.textEnd({
id: "commentary",
providerMetadata: { openai: { phase: "commentary" }, anthropic: { ignored: true } },
}),
LLMEvent.stepFinish({ index: 0, reason: { normalized: "stop" } }),
LLMEvent.finish({ reason: { normalized: "stop" } }),
]
yield* session.resume(sessionID)
yield* replaySessionProjection(sessionID)
expect(yield* session.context(sessionID)).toMatchObject([
{ type: "user", text: "Check first" },
{
type: "assistant",
content: [{ type: "text", text: "Checking.", state: { phase: "commentary" } }],
},
])
yield* admit(session, "Continue")
response = []
yield* session.resume(sessionID)
expect(requests[1]?.messages[1]?.content).toEqual([
{
type: "text",
text: "Checking.",
providerMetadata: { openai: { phase: "commentary" } },
},
])
}),
)
it.effect("replays durable provider-executed tool results inline in the next request", () =>
Effect.gen(function* () {
const session = yield* setup