Emit LLM stream lifecycle events (#26971)

This commit is contained in:
Kit Langton
2026-05-11 21:31:48 -04:00
committed by GitHub
parent e5aa5161f2
commit 8030a6c187
14 changed files with 560 additions and 196 deletions
+14 -4
View File
@@ -16,6 +16,7 @@ import {
} from "../schema"
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
import { OpenAIOptions } from "./utils/openai-options"
import { Lifecycle } from "./utils/lifecycle"
import { ToolStream } from "./utils/tool-stream"
const ADAPTER = "openai-chat"
@@ -147,6 +148,7 @@ interface ParserState {
readonly toolCallEvents: ReadonlyArray<LLMEvent>
readonly usage?: Usage
readonly finishReason?: FinishReason
readonly lifecycle: Lifecycle.State
}
const invalid = ProviderShared.invalidRequest
@@ -321,7 +323,9 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
const toolDeltas = delta?.tool_calls ?? []
let tools = state.tools
if (delta?.content) events.push(LLMEvent.textDelta({ id: "text-0", text: delta.content }))
let lifecycle = state.lifecycle
if (delta?.content) lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content)
for (const tool of toolDeltas) {
const result = ToolStream.appendOrStart(
@@ -333,7 +337,8 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
)
if (ToolStream.isError(result)) return yield* result
tools = result.tools
if (result.event) events.push(result.event)
if (result.events.length) lifecycle = Lifecycle.stepStart(lifecycle, events)
events.push(...result.events)
}
// Finalize accumulated tool inputs eagerly when finish_reason arrives so
@@ -349,15 +354,20 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
toolCallEvents: finished?.events ?? state.toolCallEvents,
usage,
finishReason,
lifecycle,
},
events,
] as const
})
const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
const events: LLMEvent[] = []
const hasToolCalls = state.toolCallEvents.length > 0
const reason = state.finishReason === "stop" && hasToolCalls ? "tool-calls" : state.finishReason
return [...state.toolCallEvents, ...(reason ? [LLMEvent.requestFinish({ reason, usage: state.usage })] : [])]
const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle
events.push(...state.toolCallEvents)
if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
return events
}
// =============================================================================
@@ -377,7 +387,7 @@ export const protocol = Protocol.make({
},
stream: {
event: Protocol.jsonEvent(OpenAIChatEvent),
initial: () => ({ tools: ToolStream.empty<number>(), toolCallEvents: [] }),
initial: () => ({ tools: ToolStream.empty<number>(), toolCallEvents: [], lifecycle: Lifecycle.initial() }),
step,
onHalt: finishEvents,
},