fix(agent): report model refusals as typed MODEL_REFUSED instead of MODEL_PROVIDER_FAILED
Fixes #945.
What
A model refusal (finish reason content-filter) on any adapter model call — agent.extract included — is now reported as the typed, non-retryable MODEL_REFUSED code (category configuration, exit 2) instead of MODEL_PROVIDER_FAILED: model provider failed: No output generated. (exit 3). The message names the step, the model, and the raw finish reason, so the report reads as a refusal instead of a provider outage, and retry policies no longer burn a second call on a prompt that is refused by construction.
Also fixed on the text path (no schema), where a refusal previously surfaced as MODEL_OUTPUT_INVALID — which invocation.ask would spend a repair round retrying.
Drive-by fix in the same seam: the SDK's no-output error was renamed in AI SDK v7 (NoOutputGeneratedError), so the old NoObjectGeneratedError.isInstance check in translateModelError never matched and empty non-refusal output fell through to MODEL_PROVIDER_FAILED. It now recognizes both names and maps back to MODEL_OUTPUT_INVALID as originally intended.
Changes
packages/e2e/src/types.ts,packages/e2e/src/agent/error.ts: newMODEL_REFUSEDcode,configurationcategory — deliberately nottest, so the runner's retry policy (which only retriestest) never retries a refusal.packages/e2e/src/agent/model/sdk.ts:throwIfModelRefusedchecksresult.finishReasonright aftergenerateText, before parsing / touchingresult.output(whose getter throws the SDK's no-output error); both the structured and text branches covered.docs/reference/errors.mdx,skills/e2e/references/debugging.md: documented (node scripts/check-error-codes.tsgreen)..changeset/model-refused-typed-error.md: patch changeset.
Verified
- New
packages/e2e/tests/unit/model-refusal.test.ts(3 tests) against a local stub provider returningfinish_reason: content_filter:- pre-fix: structured path →
MODEL_PROVIDER_FAILED(model provider failed: No output generated.— the exact report from #945); text path →MODEL_OUTPUT_INVALID. Both fail as expected. - post-fix: both →
MODEL_REFUSEDwith step/model/reason in the message;serializeError(...).retryable === falseandisRetryEligible(...) === false.
- pre-fix: structured path →
- Full unit suite: 185 files / 2737 tests pass.
oxlintclean,tsc --noEmitclean apart from pre-existing@e2e-dev/webresolution errors in integration tests (package not installed in this env),node scripts/check-error-codes.tsgreen.