Error Handling
Octavus provides structured error handling across all transports. Errors are categorized by type and source, enabling you to build appropriate UI responses and monitoring.
Error Types
The onError callback receives an OctavusError with structured information:
import { useOctavusChat, type OctavusError } from '@octavus/react';
const { error, status } = useOctavusChat({
transport,
onError: (err: OctavusError) => {
console.error('Chat error:', {
type: err.errorType, // Error classification
message: err.message, // Human-readable message
source: err.source, // Where the error originated
retryable: err.retryable, // Can be retried
retryAfter: err.retryAfter, // Seconds to wait (rate limits)
code: err.code, // Machine-readable code
provider: err.provider, // Provider details (if applicable)
});
},
});Error Classification
Error Types
| Type | Description | Typical Response |
|---|---|---|
rate_limit_error | Too many requests | Show retry timer |
quota_exceeded_error | Usage quota exceeded | Show upgrade prompt |
authentication_error | Invalid API key | Check configuration |
permission_error | No access to resource | Check permissions |
validation_error | Invalid request | Fix request data |
provider_error | LLM provider issue | Retry or show error |
provider_overloaded | Provider at capacity | Retry with backoff |
provider_timeout | Provider timed out | Retry |
tool_error | Tool execution failed | Show tool error |
internal_error | Platform error | Show generic error |
Error Sources
| Source | Description |
|---|---|
platform | Octavus platform error |
provider | LLM provider error (OpenAI, Anthropic, etc.) |
tool | Tool execution error |
client | Client-side error (network, parsing) |
Type Guards
Use type guards to handle specific error types:
import {
useOctavusChat,
isRateLimitError,
isQuotaExceededError,
isAuthenticationError,
isProviderError,
isToolError,
isRetryableError,
} from '@octavus/react';
const { error } = useOctavusChat({
transport,
onError: (err) => {
if (isRateLimitError(err)) {
// Transient rate limit (429) - show a countdown and retry
showRetryTimer(err.retryAfter ?? 60);
return;
}
if (isQuotaExceededError(err)) {
// Terminal allowance block (402) - prompt an upgrade, do not retry
showUpgradePrompt(err.message);
return;
}
if (isAuthenticationError(err)) {
// Configuration issue - shouldn't happen in production
reportConfigError(err);
return;
}
if (isProviderError(err)) {
// LLM service issue
showProviderError(err.provider?.name ?? 'AI service');
return;
}
if (isToolError(err)) {
// Tool failed - already shown inline
return;
}
if (isRetryableError(err)) {
// Generic retryable error
showRetryButton();
return;
}
// Non-retryable error
showGenericError(err.message);
},
});Provider Error Details
When errors come from LLM providers, additional details are available:
if (isProviderError(error) && error.provider) {
console.log({
name: error.provider.name, // 'anthropic', 'openai', 'google'
model: error.provider.model, // Model that caused the error
statusCode: error.provider.statusCode, // HTTP status code
errorType: error.provider.errorType, // Provider's error type
requestId: error.provider.requestId, // For support tickets
});
}Retrying After Errors
Use retry() to re-execute the last trigger from the same starting point. Messages are rolled back, the user message is re-added (if any), and the agent re-executes. Files are reused without re-uploading.
const { error, canRetry, retry } = useOctavusChat({ transport });
// Retry after any error
if (canRetry) {
await retry();
}retry() also works after stopping (cancellation) or when the result is unsatisfactory - not just errors.
Building Error UI
import {
useOctavusChat,
isRateLimitError,
isQuotaExceededError,
isAuthenticationError,
isProviderError,
} from '@octavus/react';
function Chat() {
const { error, status, retry, canRetry } = useOctavusChat({ transport });
return (
<div>
{/* Error display */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="font-medium text-red-800">{getErrorTitle(error)}</div>
<p className="text-red-600 text-sm mt-1">{error.message}</p>
{isRateLimitError(error) && error.retryAfter && (
<p className="text-red-500 text-sm mt-2">
Please try again in {error.retryAfter} seconds
</p>
)}
{canRetry && (
<button className="mt-3 text-red-700 underline" onClick={() => void retry()}>
Retry
</button>
)}
</div>
)}
</div>
);
}
function getErrorTitle(error: OctavusError): string {
if (isRateLimitError(error)) return 'Service is busy';
if (isQuotaExceededError(error)) return 'Usage limit reached';
if (isAuthenticationError(error)) return 'Configuration error';
if (isProviderError(error)) return 'AI service unavailable';
return 'Something went wrong';
}Monitoring & Logging
Log errors for monitoring and debugging:
useOctavusChat({
transport,
onError: (err) => {
// Send to your monitoring service
analytics.track('octavus_error', {
errorType: err.errorType,
source: err.source,
retryable: err.retryable,
code: err.code,
provider: err.provider?.name,
});
// Log for debugging
console.error('[Octavus]', {
type: err.errorType,
message: err.message,
source: err.source,
provider: err.provider,
});
},
});Error State
The hook exposes error state directly:
const { error, status, retry, canRetry } = useOctavusChat({ transport });
// status is 'error' when an error occurred
// error contains the OctavusError object
// Option 1: Retry the same trigger (rolls back messages, re-executes)
if (canRetry) {
await retry();
}
// Option 2: Send a new message (clears the error)
await send('user-message', { USER_MESSAGE: 'Try again' });Rate Limit Handling
Rate limits include retry information:
if (isRateLimitError(error)) {
const waitTime = error.retryAfter ?? 60; // Default to 60 seconds
// Show countdown
setCountdown(waitTime);
const timer = setInterval(() => {
setCountdown((c) => {
if (c <= 1) {
clearInterval(timer);
return 0;
}
return c - 1;
});
}, 1000);
}Error Event Structure
For custom transports or direct event handling, errors follow this structure:
interface ErrorEvent {
type: 'error';
errorType: ErrorType;
message: string;
source: ErrorSource;
retryable: boolean;
retryAfter?: number;
code?: string;
provider?: {
name: string;
model?: string;
statusCode?: number;
errorType?: string;
requestId?: string;
};
tool?: {
name: string;
callId?: string;
};
}Tool Errors
Tool errors are handled differently - they appear inline on the tool call:
function ToolCallPart({ part }: { part: UIToolCallPart }) {
return (
<div>
<span>{part.toolName}</span>
{part.status === 'error' && <div className="text-red-500 text-sm mt-1">{part.error}</div>}
</div>
);
}Tool errors don't trigger onError - they're captured on the tool call part itself.