diff --git a/packages/llm/src/schema/events.ts b/packages/llm/src/schema/events.ts index b2109f3693..212a01f6a3 100644 --- a/packages/llm/src/schema/events.ts +++ b/packages/llm/src/schema/events.ts @@ -256,9 +256,15 @@ export const LLMEvent = Object.assign(llmEventTagged, { toolResult: (input: WithID) => ToolResult.make({ ...input, id: toolCallID(input.id) }), toolError: (input: WithID) => ToolError.make({ ...input, id: toolCallID(input.id) }), stepFinish: (input: WithUsage) => - StepFinish.make({ ...input, usage: input.usage instanceof Usage ? input.usage : new Usage(input.usage ?? {}) }), + StepFinish.make({ + ...input, + usage: input.usage === undefined ? undefined : input.usage instanceof Usage ? input.usage : new Usage(input.usage), + }), requestFinish: (input: WithUsage) => - RequestFinish.make({ ...input, usage: input.usage instanceof Usage ? input.usage : new Usage(input.usage ?? {}) }), + RequestFinish.make({ + ...input, + usage: input.usage === undefined ? undefined : input.usage instanceof Usage ? input.usage : new Usage(input.usage), + }), providerError: ProviderErrorEvent.make, is: { requestStart: llmEventTagged.guards["request-start"], diff --git a/packages/opencode/src/session/llm-ai-sdk.ts b/packages/opencode/src/session/llm-ai-sdk.ts index 9d5407f975..bba31861e1 100644 --- a/packages/opencode/src/session/llm-ai-sdk.ts +++ b/packages/opencode/src/session/llm-ai-sdk.ts @@ -1,4 +1,4 @@ -import { ContentBlockID, FinishReason, LLMEvent, ProviderMetadata, ToolCallID, ToolResultValue, Usage } from "@opencode-ai/llm" +import { FinishReason, LLMEvent, ProviderMetadata, ToolResultValue } from "@opencode-ai/llm" import { Effect, Schema } from "effect" import { type streamText } from "ai" import { errorMessage } from "@/util/error" @@ -11,15 +11,12 @@ export function adapterState() { step: 0, text: 0, reasoning: 0, - currentTextID: undefined as ContentBlockID | undefined, - currentReasoningID: undefined as ContentBlockID | undefined, + currentTextID: undefined as string | undefined, + currentReasoningID: undefined as string | undefined, toolNames: {} as Record, } } -const contentBlockID = (value: string) => ContentBlockID.make(value) -const toolCallID = (value: string) => ToolCallID.make(value) - function finishReason(value: string | undefined): FinishReason { return Schema.is(FinishReason)(value) ? value : "unknown" } @@ -28,7 +25,7 @@ function providerMetadata(value: unknown): ProviderMetadata | undefined { return Schema.is(ProviderMetadata)(value) ? value : undefined } -function usage(value: unknown): Usage | undefined { +function usage(value: unknown) { if (!value || typeof value !== "object") return undefined const item = value as { inputTokens?: number @@ -49,7 +46,17 @@ function usage(value: unknown): Usage | undefined { cacheWriteInputTokens: item.inputTokenDetails?.cacheWriteTokens, }).filter((entry) => entry[1] !== undefined), ) - return new Usage(result) + return result +} + +function currentTextID(state: ReturnType, id: string | undefined) { + state.currentTextID = id ?? state.currentTextID ?? `text-${state.text++}` + return state.currentTextID +} + +function currentReasoningID(state: ReturnType, id: string | undefined) { + state.currentReasoningID = id ?? state.currentReasoningID ?? `reasoning-${state.reasoning++}` + return state.currentReasoningID } export function toLLMEvents( @@ -86,7 +93,7 @@ export function toLLMEvents( case "text-start": return Effect.sync(() => { - state.currentTextID = contentBlockID(event.id ?? `text-${state.text++}`) + state.currentTextID = currentTextID(state, event.id) return [ LLMEvent.textStart({ id: state.currentTextID, @@ -98,7 +105,7 @@ export function toLLMEvents( case "text-delta": return Effect.succeed([ LLMEvent.textDelta({ - id: event.id ? contentBlockID(event.id) : (state.currentTextID ?? contentBlockID(`text-${state.text++}`)), + id: currentTextID(state, event.id), text: event.text, }), ]) @@ -106,14 +113,14 @@ export function toLLMEvents( case "text-end": return Effect.succeed([ LLMEvent.textEnd({ - id: event.id ? contentBlockID(event.id) : (state.currentTextID ?? contentBlockID(`text-${state.text++}`)), + id: currentTextID(state, event.id), providerMetadata: providerMetadata(event.providerMetadata), }), ]) case "reasoning-start": return Effect.sync(() => { - state.currentReasoningID = contentBlockID(event.id) + state.currentReasoningID = currentReasoningID(state, event.id) return [ LLMEvent.reasoningStart({ id: state.currentReasoningID, @@ -125,14 +132,14 @@ export function toLLMEvents( case "reasoning-delta": return Effect.succeed([ LLMEvent.reasoningDelta({ - id: event.id ? contentBlockID(event.id) : (state.currentReasoningID ?? contentBlockID(`reasoning-${state.reasoning++}`)), + id: currentReasoningID(state, event.id), text: event.text, }), ]) case "reasoning-end": return Effect.sync(() => { - const id = contentBlockID(event.id) + const id = currentReasoningID(state, event.id) state.currentReasoningID = undefined return [ LLMEvent.reasoningEnd({ @@ -147,7 +154,7 @@ export function toLLMEvents( state.toolNames[event.id] = event.toolName return [ LLMEvent.toolInputStart({ - id: toolCallID(event.id), + id: event.id, name: event.toolName, providerMetadata: providerMetadata(event.providerMetadata), }), @@ -157,7 +164,7 @@ export function toLLMEvents( case "tool-input-delta": return Effect.succeed([ LLMEvent.toolInputDelta({ - id: toolCallID(event.id), + id: event.id, name: state.toolNames[event.id] ?? "unknown", text: event.delta ?? "", }), @@ -166,7 +173,7 @@ export function toLLMEvents( case "tool-input-end": return Effect.succeed([ LLMEvent.toolInputEnd({ - id: toolCallID(event.id), + id: event.id, name: state.toolNames[event.id] ?? "unknown", }), ]) @@ -176,7 +183,7 @@ export function toLLMEvents( state.toolNames[event.toolCallId] = event.toolName return [ LLMEvent.toolCall({ - id: toolCallID(event.toolCallId), + id: event.toolCallId, name: event.toolName, input: event.input, providerExecuted: "providerExecuted" in event ? event.providerExecuted : undefined, @@ -191,7 +198,7 @@ export function toLLMEvents( delete state.toolNames[event.toolCallId] return [ LLMEvent.toolResult({ - id: toolCallID(event.toolCallId), + id: event.toolCallId, name, result: ToolResultValue.make(event.output), providerExecuted: "providerExecuted" in event ? event.providerExecuted : undefined, @@ -205,7 +212,7 @@ export function toLLMEvents( delete state.toolNames[event.toolCallId] return [ LLMEvent.toolError({ - id: toolCallID(event.toolCallId), + id: event.toolCallId, name, message: errorMessage(event.error), }),