export interface PeerInfo { id: string; model: string; identity: string; } export interface AgentInfo { id: string; state: string; model: string; identity: string; uptime: number; messageCount: number; restartCount: number; scheduleCount?: number; } export type MessageRouter = (fromId: string, toId: string, message: string) => Promise; export interface AgentManager { listAgents(): Promise; stopAgent(id: string): Promise; startAgent(id: string): Promise; removeAgent(id: string): Promise; spawnAgent(name: string, content: string, apiKey: string): Promise; getApiKey(agentId: string): string | undefined; } export interface CronJob { id: string; name: string; schedule: string; task: string; enabled: boolean; lastRun?: Date; nextRun?: Date; } export interface StreamEvent { type: "text" | "thinking" | "tool_start" | "tool_use" | "tool_delta" | "tool_result" | "done" | "error"; text?: string; toolUse?: { id: string; name: string; input: unknown }; toolCallId?: string; toolName?: string; error?: string; errorCode?: string; } export interface ClassifiedError { message: string; code: string; retryable: boolean; } export function toErrorMessage(err: unknown): string { return err instanceof Error ? err.message : String(err); } /** * Unwrap wrapper errors (NoOutputGeneratedError) to reach the root cause, * then classify by API status code into a user-friendly message. */ export function classifyError(err: unknown): ClassifiedError { const unwrapped = unwrapCause(err); if (isNamedError(unwrapped, "NoOutputGeneratedError")) return { code: "no_output", message: "The model produced no response. Try again.", retryable: true }; if (isNamedError(unwrapped, "MissingToolResultsError")) return { code: "context_corrupt", message: "Conversation history has missing tool results. Try starting a new conversation.", retryable: false }; const statusCode = getStatusCode(unwrapped); if (statusCode !== undefined) { if (statusCode === 402) return { code: "credits_exhausted", message: "Your API provider account has run out of credits. Add credits to continue.", retryable: false }; if (statusCode === 401 || statusCode === 403) return { code: "auth_error", message: "API key is invalid or unauthorized.", retryable: false }; if (statusCode === 429) return { code: "rate_limited", message: "Rate limited by the API provider. Try again in a moment.", retryable: true }; if (statusCode === 408) return { code: "timeout", message: "Request timed out. Try again.", retryable: true }; if (statusCode >= 500) return { code: "provider_error", message: "The API provider is experiencing issues. Try again shortly.", retryable: true }; } return { code: "unknown", message: toErrorMessage(unwrapped), retryable: false }; } function unwrapCause(err: unknown): unknown { if (err instanceof Error && err.cause instanceof Error) { if (isNamedError(err, "NoOutputGeneratedError")) { return err.cause; } } return err; } function isNamedError(err: unknown, name: string): boolean { if (!(err instanceof Error)) return false; const marker = Symbol.for(`vercel.ai.error.AI_${name}`); if (marker in err) return true; return err.constructor?.name === name; } function getStatusCode(err: unknown): number | undefined { if (typeof err === "object" && err !== null && "statusCode" in err) { const code = (err as { statusCode: unknown }).statusCode; return typeof code === "number" ? code : undefined; } return undefined; }