feat(llm): add LLMEvent.is.* camelCase narrowing helpers
Schema.toTaggedUnion('type') already provides LLMEvent.guards but uses
kebab-case bracket access (LLMEvent.guards['tool-call']). Adds an LLMEvent.is
namespace with camelCase aliases that delegate to the same guards, so
consumers can write events.filter(LLMEvent.is.toolCall) instead of
events.filter(LLMEvent.guards['tool-call']).
Migrated all callsites in src/llm.ts and the two test files for consistency.
LLMEvent.guards / .match / .cases / .isAnyOf remain available for callers
who want the Effect-canonical API.
This commit is contained in:
@@ -191,7 +191,7 @@ export const updateRequest = (input: LLMRequest, patch: Partial<RequestInput>) =
|
||||
|
||||
export const outputText = (response: LLMResponse | { readonly events: ReadonlyArray<LLMEvent> }) =>
|
||||
response.events
|
||||
.filter(LLMEvent.guards["text-delta"])
|
||||
.filter(LLMEvent.is.textDelta)
|
||||
.map((event) => event.text)
|
||||
.join("")
|
||||
|
||||
@@ -204,10 +204,10 @@ export const outputUsage = (response: LLMResponse | { readonly events: ReadonlyA
|
||||
}
|
||||
|
||||
export const outputToolCalls = (response: LLMResponse | { readonly events: ReadonlyArray<LLMEvent> }) =>
|
||||
response.events.filter(LLMEvent.guards["tool-call"])
|
||||
response.events.filter(LLMEvent.is.toolCall)
|
||||
|
||||
export const outputReasoning = (response: LLMResponse | { readonly events: ReadonlyArray<LLMEvent> }) =>
|
||||
response.events
|
||||
.filter(LLMEvent.guards["reasoning-delta"])
|
||||
.filter(LLMEvent.is.reasoningDelta)
|
||||
.map((event) => event.text)
|
||||
.join("")
|
||||
|
||||
@@ -338,7 +338,7 @@ export const ProviderErrorEvent = Schema.Struct({
|
||||
}).annotate({ identifier: "LLM.Event.ProviderError" })
|
||||
export type ProviderErrorEvent = Schema.Schema.Type<typeof ProviderErrorEvent>
|
||||
|
||||
export const LLMEvent = Schema.Union([
|
||||
const llmEventTagged = Schema.Union([
|
||||
RequestStart,
|
||||
StepStart,
|
||||
TextStart,
|
||||
@@ -353,7 +353,30 @@ export const LLMEvent = Schema.Union([
|
||||
RequestFinish,
|
||||
ProviderErrorEvent,
|
||||
]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type LLMEvent = Schema.Schema.Type<typeof LLMEvent>
|
||||
|
||||
/**
|
||||
* camelCase aliases for `LLMEvent.guards` (provided by `Schema.toTaggedUnion`).
|
||||
* Lets consumers write `events.filter(LLMEvent.is.toolCall)` instead of
|
||||
* `events.filter(LLMEvent.guards["tool-call"])`.
|
||||
*/
|
||||
const llmEventIs = {
|
||||
requestStart: llmEventTagged.guards["request-start"],
|
||||
stepStart: llmEventTagged.guards["step-start"],
|
||||
textStart: llmEventTagged.guards["text-start"],
|
||||
textDelta: llmEventTagged.guards["text-delta"],
|
||||
textEnd: llmEventTagged.guards["text-end"],
|
||||
reasoningDelta: llmEventTagged.guards["reasoning-delta"],
|
||||
toolInputDelta: llmEventTagged.guards["tool-input-delta"],
|
||||
toolCall: llmEventTagged.guards["tool-call"],
|
||||
toolResult: llmEventTagged.guards["tool-result"],
|
||||
toolError: llmEventTagged.guards["tool-error"],
|
||||
stepFinish: llmEventTagged.guards["step-finish"],
|
||||
requestFinish: llmEventTagged.guards["request-finish"],
|
||||
providerError: llmEventTagged.guards["provider-error"],
|
||||
} as const
|
||||
|
||||
export const LLMEvent = Object.assign(llmEventTagged, { is: llmEventIs })
|
||||
export type LLMEvent = Schema.Schema.Type<typeof llmEventTagged>
|
||||
|
||||
export class PatchTrace extends Schema.Class<PatchTrace>("LLM.PatchTrace")({
|
||||
id: Schema.String,
|
||||
|
||||
@@ -44,12 +44,12 @@ describe("OpenAI Chat tool-loop recorded", () => {
|
||||
// Two model rounds: tool-call + tool-result + final answer. Two
|
||||
// `request-finish` events confirm both interactions in the cassette
|
||||
// were dispatched in order.
|
||||
const finishes = events.filter(LLMEvent.guards["request-finish"])
|
||||
const finishes = events.filter(LLMEvent.is.requestFinish)
|
||||
expect(finishes).toHaveLength(2)
|
||||
expect(finishes[0]?.reason).toBe("tool-calls")
|
||||
expect(finishes.at(-1)?.reason).toBe("stop")
|
||||
|
||||
const toolResult = events.find(LLMEvent.guards["tool-result"])
|
||||
const toolResult = events.find(LLMEvent.is.toolResult)
|
||||
expect(toolResult).toMatchObject({
|
||||
type: "tool-result",
|
||||
name: "get_weather",
|
||||
|
||||
@@ -52,7 +52,7 @@ describe("ToolRuntime", () => {
|
||||
),
|
||||
)
|
||||
|
||||
const result = events.find(LLMEvent.guards["tool-result"])
|
||||
const result = events.find(LLMEvent.is.toolResult)
|
||||
expect(result).toMatchObject({
|
||||
type: "tool-result",
|
||||
id: "call_1",
|
||||
@@ -79,10 +79,10 @@ describe("ToolRuntime", () => {
|
||||
),
|
||||
)
|
||||
|
||||
const toolError = events.find(LLMEvent.guards["tool-error"])
|
||||
const toolError = events.find(LLMEvent.is.toolError)
|
||||
expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "missing_tool" })
|
||||
expect(toolError?.message).toContain("Unknown tool")
|
||||
expect(events.find(LLMEvent.guards["tool-result"])).toMatchObject({
|
||||
expect(events.find(LLMEvent.is.toolResult)).toMatchObject({
|
||||
type: "tool-result",
|
||||
id: "call_1",
|
||||
name: "missing_tool",
|
||||
@@ -106,7 +106,7 @@ describe("ToolRuntime", () => {
|
||||
),
|
||||
)
|
||||
|
||||
const toolError = events.find(LLMEvent.guards["tool-error"])
|
||||
const toolError = events.find(LLMEvent.is.toolError)
|
||||
expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "get_weather" })
|
||||
expect(toolError?.message).toContain("Invalid tool input")
|
||||
}),
|
||||
@@ -127,7 +127,7 @@ describe("ToolRuntime", () => {
|
||||
),
|
||||
)
|
||||
|
||||
const toolError = events.find(LLMEvent.guards["tool-error"])
|
||||
const toolError = events.find(LLMEvent.is.toolError)
|
||||
expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "get_weather" })
|
||||
expect(toolError?.message).toBe("Weather lookup failed for FAIL")
|
||||
}),
|
||||
@@ -166,7 +166,7 @@ describe("ToolRuntime", () => {
|
||||
),
|
||||
)
|
||||
|
||||
expect(events.filter(LLMEvent.guards["request-finish"])).toHaveLength(2)
|
||||
expect(events.filter(LLMEvent.is.requestFinish)).toHaveLength(2)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -186,8 +186,8 @@ describe("ToolRuntime", () => {
|
||||
}).pipe(Stream.runCollect, Effect.provide(layer)),
|
||||
)
|
||||
|
||||
expect(events.filter(LLMEvent.guards["request-finish"])).toHaveLength(1)
|
||||
expect(events.find(LLMEvent.guards["tool-result"])).toBeUndefined()
|
||||
expect(events.filter(LLMEvent.is.requestFinish)).toHaveLength(1)
|
||||
expect(events.find(LLMEvent.is.toolResult)).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -238,8 +238,8 @@ describe("ToolRuntime", () => {
|
||||
)
|
||||
|
||||
expect(streams).toBe(1)
|
||||
expect(events.find(LLMEvent.guards["tool-error"])).toBeUndefined()
|
||||
expect(events.filter(LLMEvent.guards["tool-call"])).toEqual([
|
||||
expect(events.find(LLMEvent.is.toolError)).toBeUndefined()
|
||||
expect(events.filter(LLMEvent.is.toolCall)).toEqual([
|
||||
{
|
||||
type: "tool-call",
|
||||
id: "srvtoolu_abc",
|
||||
@@ -276,7 +276,7 @@ describe("ToolRuntime", () => {
|
||||
),
|
||||
)
|
||||
|
||||
const results = events.filter(LLMEvent.guards["tool-result"])
|
||||
const results = events.filter(LLMEvent.is.toolResult)
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results.map((event) => event.id).toSorted()).toEqual(["c1", "c2"])
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user