diff --git a/packages/ai/src/protocols/utils/tool-stream.ts b/packages/ai/src/protocols/utils/tool-stream.ts index 667c5d39679..1bb271f8899 100644 --- a/packages/ai/src/protocols/utils/tool-stream.ts +++ b/packages/ai/src/protocols/utils/tool-stream.ts @@ -84,8 +84,6 @@ const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => { id: tool.id, name: tool.name, raw, - message: error.reason.message, - providerMetadata: tool.providerMetadata, }), ), ), diff --git a/packages/ai/src/schema/events.ts b/packages/ai/src/schema/events.ts index 74e2e10ea63..5be1c4cf900 100644 --- a/packages/ai/src/schema/events.ts +++ b/packages/ai/src/schema/events.ts @@ -150,14 +150,12 @@ export const ToolInputEnd = Schema.Struct({ }).annotate({ identifier: "LLM.Event.ToolInputEnd" }) export type ToolInputEnd = Schema.Schema.Type -/** A local tool call that could not be decoded. `raw` is diagnostic-only. */ +/** A local tool call whose final input could not be decoded. */ export const ToolInputError = Schema.Struct({ type: Schema.tag("tool-input-error"), id: ToolCallID, name: Schema.String, raw: Schema.String, - message: Schema.String, - providerMetadata: Schema.optional(ProviderMetadata), }).annotate({ identifier: "LLM.Event.ToolInputError" }) export type ToolInputError = Schema.Schema.Type diff --git a/packages/ai/test/provider/openai-responses.test.ts b/packages/ai/test/provider/openai-responses.test.ts index cde2a531dd0..3bfe72da146 100644 --- a/packages/ai/test/provider/openai-responses.test.ts +++ b/packages/ai/test/provider/openai-responses.test.ts @@ -1290,8 +1290,6 @@ describe("OpenAI Responses route", () => { id: "call_1", name: "lookup", raw: '{"query":"partial', - message: "Invalid JSON input for openai-responses tool call lookup", - providerMetadata: { openai: { itemId: "item_1" } }, }) expect(response.finishReason).toBe("tool-calls") expect(response.events.some(LLMEvent.is.toolCall)).toBeFalse() diff --git a/packages/ai/test/response.test.ts b/packages/ai/test/response.test.ts index f1b3c5db4f3..d3402be6c32 100644 --- a/packages/ai/test/response.test.ts +++ b/packages/ai/test/response.test.ts @@ -104,7 +104,6 @@ describe("LLMResponse reducer", () => { id: "call_1", name: "lookup", raw: '{"query":"partial', - message: "Invalid JSON input", }), ]) diff --git a/packages/ai/test/tool-stream.test.ts b/packages/ai/test/tool-stream.test.ts index 95c1bb7b671..c02b57c4862 100644 --- a/packages/ai/test/tool-stream.test.ts +++ b/packages/ai/test/tool-stream.test.ts @@ -81,7 +81,6 @@ describe("ToolStream", () => { id: "call_1", name: "lookup", raw: '{"query":"partial', - message: "Invalid JSON input for test-route tool call lookup", }, ], }) @@ -112,7 +111,6 @@ describe("ToolStream", () => { id: "call_invalid", name: "lookup", raw: '{"query":"partial', - message: "Invalid JSON input for test-route tool call lookup", }, ], }) diff --git a/packages/core/src/aisdk.ts b/packages/core/src/aisdk.ts index 2aacaa79395..f4f9b03f718 100644 --- a/packages/core/src/aisdk.ts +++ b/packages/core/src/aisdk.ts @@ -642,8 +642,6 @@ function streamPartEvents( id: event.toolCallId, name: event.toolName, raw: event.input, - message: error.reason.message, - providerMetadata: providerMetadata(event.providerMetadata), }), ]), ), diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 7f129ed0459..dfb5de04781 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -259,7 +259,7 @@ const layer = Layer.effect( step: currentStep, }) } - yield* serialized(publisher.failAssistant(error, true)) + yield* serialized(publisher.failAssistant(error)) } // Provider error events only arrive from the stream, so the flag is final here. const providerFailed = publisher.hasProviderError() @@ -280,7 +280,7 @@ const layer = Layer.effect( if (settled._tag === "Failure") yield* FiberSet.clear(toolFibers) if (userDeclined || streamInterrupted || toolsInterrupted) { yield* serialized(publisher.failUnsettledTools({ type: "aborted", message: "Tool execution interrupted" })) - yield* serialized(publisher.failAssistant({ type: "aborted", message: "Step interrupted" }, true)) + yield* serialized(publisher.failAssistant({ type: "aborted", message: "Step interrupted" })) } // A settled tool fiber failure is one of two things. A defect from a tool // implementation becomes a failed tool call the model can read, and the step still @@ -293,7 +293,7 @@ const layer = Layer.effect( const failure = infraError ?? Cause.squash(settledFailure) const error = toSessionError(failure) yield* serialized(publisher.failUnsettledTools(error)) - if (infraError !== undefined) yield* serialized(publisher.failAssistant(error, true)) + if (infraError !== undefined) yield* serialized(publisher.failAssistant(error)) } // Fail unresolved calls before the terminal step event. Local calls have joined, so @@ -321,13 +321,10 @@ const layer = Layer.effect( : false if (hostedResultMissing && !publisher.stepSettlement()) yield* serialized( - publisher.failAssistant( - { - type: "tool.result-missing", - message: "Provider did not return a tool result", - }, - true, - ), + publisher.failAssistant({ + type: "tool.result-missing", + message: "Provider did not return a tool result", + }), ) const stepFailure = publisher.stepFailure() diff --git a/packages/core/src/session/runner/publish-llm-event.ts b/packages/core/src/session/runner/publish-llm-event.ts index 96f4deb3fe1..1344d2627a7 100644 --- a/packages/core/src/session/runner/publish-llm-event.ts +++ b/packages/core/src/session/runner/publish-llm-event.ts @@ -262,11 +262,11 @@ export const createLLMEventPublisher = (events: Pick id: "call_1", name: "lookup", raw, - message: "Invalid JSON input for aisdk tool call lookup", }) expect(response.events.some(LLMEvent.is.toolInputEnd)).toBeTrue() expect(response.events.some(LLMEvent.is.toolCall)).toBeFalse() diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index c0361e8722b..e3297a54a12 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -4180,7 +4180,6 @@ describe("SessionRunnerLLM", () => { id: "call-malformed", name: "echo", raw, - message: "Invalid JSON input for test tool call echo", }), LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }), LLMEvent.finish({ reason: "tool-calls" }), @@ -4278,7 +4277,6 @@ describe("SessionRunnerLLM", () => { id: "call-malformed", name: "echo", raw: '{"text":"partial', - message: "Invalid JSON input for test tool call echo", }), LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }), LLMEvent.finish({ reason: "tool-calls" }), @@ -4321,7 +4319,6 @@ describe("SessionRunnerLLM", () => { id: "call-malformed", name: "echo", raw: '{"text":"partial', - message: "Invalid JSON input for test tool call echo", }), LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }), LLMEvent.finish({ reason: "tool-calls" }), @@ -4387,7 +4384,7 @@ describe("SessionRunnerLLM", () => { }), ) - it.effect("replaces malformed input diagnosis with a later provider failure", () => + it.effect("records a provider failure after malformed input", () => Effect.gen(function* () { const session = yield* setup yield* admit(session, "Fail after malformed input") @@ -4402,7 +4399,6 @@ describe("SessionRunnerLLM", () => { id: "call-malformed", name: "echo", raw: '{"text":"partial', - message: "Invalid JSON input for test tool call echo", }), ]).pipe(Stream.concat(Stream.fail(failure))) @@ -4432,7 +4428,6 @@ describe("SessionRunnerLLM", () => { id, name: "echo", raw: '{"text":"partial', - message: "Invalid JSON input for test tool call echo", }), LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }), LLMEvent.finish({ reason: "tool-calls" }), @@ -4468,7 +4463,6 @@ describe("SessionRunnerLLM", () => { id, name: "echo", raw: '{"text":"partial', - message: "Invalid JSON input for test tool call echo", }), LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }), LLMEvent.finish({ reason: "tool-calls" }), @@ -4575,6 +4569,24 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("preserves the provider failure when tool output persistence also fails", () => + Effect.gen(function* () { + const session = yield* setup + yield* admit(session, "Storage fails while provider fails") + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ id: "call-store-provider-error", name: "storefail", input: {} }), + LLMEvent.providerError({ message: "Provider unavailable" }), + ] + + expect(yield* session.resume(sessionID).pipe(Effect.exit)).toMatchObject({ _tag: "Failure" }) + + expect(requireAssistant(yield* session.context(sessionID))).toMatchObject({ + error: { type: "provider.unknown", message: "Provider unavailable" }, + }) + }), + ) + it.effect("durably fails a hosted tool left unresolved at normal provider EOF", () => Effect.gen(function* () { const session = yield* setup