fix(ai): populate RUN_ERROR rawEvent with provider error body (#672)#673
Conversation
Streaming chat adapters previously collapsed provider errors to an opaque
`{ message, code }` headline and never populated AG-UI's purpose-built
`rawEvent` field, so the upstream provider detail was unrecoverable.
Add a `toRunErrorRawEvent` helper that extracts only known provider-body
fields (`error.rawEvent` -> object-valued `error.error` -> `error.metadata`),
never the raw SDK exception (which can carry auth headers). Wire it into every
fatal-catch RUN_ERROR emission across openai-base (chat-completions +
responses), ai-openrouter (text + responses, incl. the mid-stream `chunk.error`
rethrow), ai-anthropic, and ai-gemini. The `{ message, code }` contract of
`toRunErrorPayload` is unchanged.
The StreamProcessor now also attaches `code` and `rawEvent` to the Error it
surfaces via `onError` / `useChat`'s `error`, so the upstream cause is
recoverable in application code.
Note: the OpenRouter SDK parses each in-band stream chunk's `error` through a
strict schema (`{ code, message }`), so provider `metadata` survives only on
pre-stream HTTP errors, whose typed error class exposes the full body via
`.error`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR extends ChangesError Enrichment with Provider Metadata
Sequence Diagram(s)sequenceDiagram
participant Adapter
participant Extract
participant StreamProc
participant Events
Adapter->>Extract: compute rawEvent from caught error
Adapter->>StreamProc: emit RUN_ERROR {message, code, rawEvent?}
StreamProc->>StreamProc: build Error(message) and attach code/rawEvent
StreamProc->>Events: call onError(Error with code/rawEvent)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🚀 Changeset Version Preview13 package(s) bumped directly, 17 bumped as dependents. 🟥 Major bumps
🟨 Minor bumps
🟩 Patch bumps
|
|
View your CI Pipeline Execution ↗ for commit ffa91a9
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-devtools-core
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-utils
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/ai-anthropic/src/adapters/text.ts (1)
183-203:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSanitize the Anthropic logger payloads before attaching
rawEvent.Both catch blocks still send the raw SDK exception to
logger.errors(...). That can leak request metadata to user loggers and undercuts the security boundary this PR is adding aroundrawEvent. Please switch these paths totoRunErrorPayload(...)and only spreadcodewhen defined.🛡️ Suggested pattern
-import { toRunErrorRawEvent } from '`@tanstack/ai/adapter-internals`' +import { + toRunErrorPayload, + toRunErrorRawEvent, +} from '`@tanstack/ai/adapter-internals`' } catch (error: unknown) { - const err = error as Error & { status?: number; code?: string } + const errorPayload = toRunErrorPayload( + error, + 'anthropic.chatStream failed', + ) const rawEvent = toRunErrorRawEvent(error) logger.errors('anthropic.chatStream fatal', { - error, + error: errorPayload, source: 'anthropic.chatStream', }) yield { type: EventType.RUN_ERROR, model: options.model, timestamp: Date.now(), - message: err.message || 'Unknown error occurred', - code: err.code || String(err.status), + message: errorPayload.message, + ...(errorPayload.code !== undefined && { code: errorPayload.code }), ...(rawEvent !== undefined && { rawEvent }), error: { - message: err.message || 'Unknown error occurred', - code: err.code || String(err.status), + message: errorPayload.message, + ...(errorPayload.code !== undefined && { code: errorPayload.code }), }, }Apply the same pattern in
processAnthropicStream.Also applies to: 1155-1174
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ai-anthropic/src/adapters/text.ts` around lines 183 - 203, The catch block is currently logging the raw SDK exception and attaching rawEvent via toRunErrorRawEvent, which may leak sensitive request metadata; replace logger.errors(..., { error, ... }) with a sanitized payload using toRunErrorPayload(error) and when building the yielded object use the sanitized payload (e.g., payload = toRunErrorPayload(error)) instead of rawEvent, and only spread code into the event/error objects when payload.code is defined; apply the same change in processAnthropicStream and any other catch blocks that call toRunErrorRawEvent or pass the raw error to logger.errors so all logs and emitted RUN_ERROR events use toRunErrorPayload(...) and conditional spreading of code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/ai-anthropic/src/adapters/text.ts`:
- Around line 183-203: The catch block is currently logging the raw SDK
exception and attaching rawEvent via toRunErrorRawEvent, which may leak
sensitive request metadata; replace logger.errors(..., { error, ... }) with a
sanitized payload using toRunErrorPayload(error) and when building the yielded
object use the sanitized payload (e.g., payload = toRunErrorPayload(error))
instead of rawEvent, and only spread code into the event/error objects when
payload.code is defined; apply the same change in processAnthropicStream and any
other catch blocks that call toRunErrorRawEvent or pass the raw error to
logger.errors so all logs and emitted RUN_ERROR events use
toRunErrorPayload(...) and conditional spreading of code.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ec50406a-df0a-4696-8a9f-b709740552a5
📒 Files selected for processing (17)
.changeset/runerror-raw-event.mdpackages/ai-anthropic/src/adapters/text.tspackages/ai-gemini/src/adapters/text.tspackages/ai-openrouter/src/adapters/responses-text.tspackages/ai-openrouter/src/adapters/text.tspackages/ai-openrouter/tests/openrouter-adapter.test.tspackages/ai/src/activities/chat/stream/processor.tspackages/ai/src/activities/error-payload.tspackages/ai/src/adapter-internals.tspackages/ai/tests/error-payload.test.tspackages/openai-base/src/adapters/chat-completions-text.tspackages/openai-base/src/adapters/responses-text.tspackages/openai-base/tests/chat-completions-text.test.tstesting/e2e/src/routes/api.tools-test.tstesting/e2e/src/routes/tools-test.tsxtesting/e2e/tests/error-handling.spec.tstesting/e2e/tests/tools-test/helpers.ts
Add tests verifying provider error bodies are forwarded as RUN_ERROR.rawEvent for Anthropic, Gemini, OpenRouter, and openai-base adapters, and that StreamProcessor attaches code/rawEvent to onError. Replace an `any` cast in the processor with a typed runId guard, and ignore the local scheduled_tasks.lock file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #672.
Problem
When a streaming chat call failed, the
RUN_ERRORevent reaching consumers carried only an opaque{ message, code }headline (e.g."Provider returned error"). Adapters discarded the provider's structured error body, and no adapter populated AG-UI's purpose-builtrawEventfield — so the upstream cause (provider name, the upstream model's error body, rate-limit/overload/BYOK detail) was unrecoverable.Changes
toRunErrorRawEvent(error)(packages/ai/src/activities/error-payload.ts, exported via@tanstack/ai/adapter-internals). Extracts only known provider-body fields —error.rawEvent→ object-valuederror.error→error.metadata— and never the raw SDK exception object (which can carry request metadata such as auth headers). Returnsundefinedso the field is omitted, not nulled. The{ message, code }contract oftoRunErrorPayloadis untouched.rawEventpopulation on every fatal-catchRUN_ERRORemission via a conditional spread:packages/openai-base—chat-completions-text.ts,responses-text.tspackages/ai-openrouter—text.ts(incl. preserving the mid-streamchunk.erroron the rethrow) andresponses-text.ts(incl.chunk.response.errorsites)packages/ai-anthropic/src/adapters/text.ts,packages/ai-gemini/src/adapters/text.tsStreamProcessornow attachescodeandrawEventto theErrorit surfaces viaonError/useChat'serror, so the upstream cause is recoverable in application code. Backward-compatible (messageunchanged).Tests
toRunErrorRawEventcoverage (priority order, string-errorrejection, no header leakage); adapter emission tests inopenai-base(forwards.errorbody / omits when absent) andai-openrouter(mid-stream body →rawEvent).error-handling.spec.tsassertsrawEventsurvives SSE transport + the strip-to-spec middleware and reaches the consumer.test:types,test:eslint(0 errors), unit suites (ai,openai-base,ai-anthropic,ai-gemini,ai-openrouter,ai-client), and the error-handling E2E spec — all green.The original repro (recovering OpenRouter's
error.metadatafrom a mid-stream error) is not achievable on@openrouter/sdk@0.12.35: the SDK parses each in-band stream chunk'serrorthrough a strict Zod schema ({ code, message }), strippingmetadatabefore the adapter seeschunk.error. The metadata survives only on pre-stream HTTP errors (rate-limit / overload / BYOK rejection), whose typed error class exposes the full body via.error— which the new helper forwards. Documented in code comments and the changeset.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
rawEventfield, alongside existing error codes — improving visibility of upstream error details without changing existing error contracts.Tests
rawEventis correctly surfaced and preserved through streaming and SSE paths.