Skip to content
ai-effect
Esc
↑↓navigate↵open⌘Jpreview

Errors

The AiError type, its seven reasons, and how to handle and retry them.

A failed call fails its Effect with an AiError (tag ai-effect/AiError), instead of the SDK’s thrown errors. It has:

  • method: the function that failed: 'generateText', 'streamText', 'embed', 'embedMany', 'rerank', 'ToolLoopAgent.generate', 'ToolLoopAgent.stream', 'createUIMessageStream' or 'createAgentUIStream'
  • reason: one of the seven reasons below
  • message and isRetryable, both derived from the reason

Reasons

reason._tag What maps to it isRetryable
ai-effect/RateLimitError HTTP 429; retryAfter is a Duration parsed from the retry-after header when present yes
ai-effect/AuthenticationError HTTP 401 or 403 no
ai-effect/InvalidRequestError HTTP 400, 404, 409 or 422, or a bad prompt, argument, model or unsupported feature no
ai-effect/ProviderError HTTP 5xx, or a network failure with no status the SDK’s own isRetryable
ai-effect/InvalidOutputError the model returned no output, or output that failed parsing, validation or tool lookup yes
ai-effect/AbortedError the call was aborted, or timed out (see below) no
ai-effect/UnknownError anything else, including errors thrown by your own code and other HTTP statuses no

Every reason has a message, a cause holding the raw SDK error, and a statusCode when the provider returned one. If the SDK retried and gave up, cause is the last underlying error rather than the SDK’s retry wrapper.

Aborts

AbortedError covers a call stopped by an abortSignal you passed, an SDK timeout, or a provider abort. A stream that ends with an abort part fails with it too. It has no statusCode.

Interrupting the fiber is not an AbortedError: it stays an interrupt and never reaches the error channel.

Handle one reason

Effect.catchReason handles a single reason and leaves the others in the error channel:

import { generateText } from 'ai-effect';
import { Effect } from 'effect';

const reply = generateText({
	model: 'openai/gpt-5',
	prompt: 'Hello!',
}).pipe(
	Effect.map((result) => result.text),
	Effect.catchReason(
		'ai-effect/AiError',
		'ai-effect/RateLimitError',
		(reason) =>
			Effect.logWarning('rate limited', reason.retryAfter).pipe(
				Effect.as('Fallback response'),
			),
	),
);

Retry

Drive a retry policy with isRetryable:

import { generateText } from 'ai-effect';
import { Effect } from 'effect';

const resilient = generateText({
	model: 'openai/gpt-5',
	prompt: 'Hello!',
}).pipe(
	Effect.retry({
		while: (error) => error.isRetryable,
		times: 3,
	}),
);

The AI SDK also retries on its own (its maxRetries option); Effect.retry applies after those attempts are exhausted.

Streaming errors

streamText and ToolLoopAgent.stream return their result as soon as the stream is set up, so the returned Effect only fails for errors thrown at that point. Errors during streaming fail the stream you consume, or the field Effect you await, such as result.text, with an AiError that has the same method. The result’s stream field is the exception: it emits error and abort parts as data.

Tool failures

A failing tool execute does not produce an AiError, including inside a ToolLoopAgent; see Tools.

Was this page helpful?