opencode/packages/core/src/session/to-session-error.ts
Aiden Cline bd51cdba12 refactor(llm): redesign error model as flat tagged union
- Replace LLMError { module, method, reason } wrapper with a flat tagged
  union: BadRequest, Authentication, PermissionDenied, NotFound, RateLimit,
  QuotaExceeded, ContentPolicy, ContextOverflow, ServerError, APIError,
  ConnectionError, TimeoutError, MalformedResponse, NoRoute.
- Delete the provider-error LLMEvent: streams carry output only and every
  failure exits through the typed error channel.
- Add one shared classifyApiFailure classifier used by the HTTP executor,
  protocol stream errors, and the AI SDK adapter so all routes classify
  identically (including OpenAI in-stream rate_limit_exceeded and
  internal_error codes).
- Enforce a terminal contract in LLMClient.stream for every route: EOF
  without finish and output after finish fail as MalformedResponse.
- Classify AI SDK failures properly in core/aisdk.ts instead of collapsing
  to UnknownProvider; preserve status, headers, body, and retry-after.
- Simplify the session runner: drop held-back overflow events, key overflow
  recovery off LLM.ContextOverflow, retry RateLimit | ServerError |
  ConnectionError | TimeoutError.
- Map new tags in toSessionError (provider.context-overflow,
  provider.timeout, provider.not-found).
2026-07-13 00:47:32 -05:00

65 lines
3.4 KiB
TypeScript

import { isLLMError, ToolFailure } from "@opencode-ai/llm"
import { Tool } from "@opencode-ai/plugin/v2/effect/tool"
import { SessionError } from "@opencode-ai/schema/session-error"
import { PermissionV2 } from "../permission"
import { QuestionV2 } from "../question"
import { Integration } from "../integration"
import { ToolOutputStore } from "../tool-output-store"
import { AgentNotFoundError, StepFailedError, UserInterruptedError } from "./error"
import { SessionRunnerModel } from "./runner/model"
export function toSessionError(cause: unknown): SessionError.Error {
if (isLLMError(cause)) {
switch (cause._tag) {
case "LLM.RateLimit":
return { type: "provider.rate-limit", message: cause.message }
case "LLM.Authentication":
return { type: "provider.auth", message: cause.message }
case "LLM.PermissionDenied":
return { type: "provider.auth", message: cause.message }
case "LLM.NotFound":
return { type: "provider.not-found", message: cause.message }
case "LLM.QuotaExceeded":
return { type: "provider.quota", message: cause.message }
case "LLM.ContentPolicy":
return { type: "provider.content-filter", message: cause.message }
case "LLM.ContextOverflow":
return { type: "provider.context-overflow", message: cause.message }
case "LLM.ConnectionError":
return { type: "provider.transport", message: cause.message }
case "LLM.TimeoutError":
return { type: "provider.timeout", message: cause.message }
case "LLM.ServerError":
return { type: "provider.internal", message: cause.message }
case "LLM.MalformedResponse":
return { type: "provider.invalid-output", message: cause.message }
case "LLM.BadRequest":
return { type: "provider.invalid-request", message: cause.message }
case "LLM.NoRoute":
return { type: "provider.no-route", message: cause.message }
case "LLM.APIError":
return { type: "provider.unknown", message: cause.message }
default: {
const exhaustive: never = cause
return exhaustive
}
}
}
if (cause instanceof PermissionV2.BlockedError) return { type: "permission.rejected", message: cause.message }
if (cause instanceof QuestionV2.RejectedError) return { type: "aborted", message: cause.message }
if (cause instanceof ToolFailure || cause instanceof Tool.Failure)
return cause.error === undefined ? { type: "tool.execution", message: cause.message } : toSessionError(cause.error)
if (cause instanceof StepFailedError) return cause.error
if (cause instanceof AgentNotFoundError) return { type: "unknown", message: cause.message }
if (cause instanceof UserInterruptedError) return { type: "aborted", message: cause.message }
if (
cause instanceof SessionRunnerModel.ModelNotSelectedError ||
cause instanceof SessionRunnerModel.ModelUnavailableError ||
cause instanceof SessionRunnerModel.VariantUnavailableError ||
cause instanceof SessionRunnerModel.UnsupportedPackageError
)
return { type: "provider.no-route", message: cause.message }
if (cause instanceof Integration.AuthorizationError) return { type: "provider.auth", message: cause.message }
if (cause instanceof ToolOutputStore.StorageError) return { type: "unknown", message: cause.message }
return { type: "unknown", message: cause instanceof Error ? cause.message : String(cause) }
}