import { Schema } from "effect" import { Tool } from "@opencode-ai/schema/tool" import { ModelID, ProviderID, ProviderMetadata, RouteID } from "./ids.js" export const ProviderFailureClassification = Schema.Literals(["context-overflow", "payload-too-large"]) export type ProviderFailureClassification = typeof ProviderFailureClassification.Type export class HttpRequestDetails extends Schema.Class("AI.HttpRequestDetails")({ method: Schema.String, url: Schema.String, headers: Schema.Record(Schema.String, Schema.String), }) {} export class HttpResponseDetails extends Schema.Class("AI.HttpResponseDetails")({ status: Schema.Number, headers: Schema.Record(Schema.String, Schema.String), }) {} export class HttpRateLimitDetails extends Schema.Class("AI.HttpRateLimitDetails")({ retryAfterMs: Schema.optional(Schema.Number), limit: Schema.optional(Schema.Record(Schema.String, Schema.String)), remaining: Schema.optional(Schema.Record(Schema.String, Schema.String)), reset: Schema.optional(Schema.Record(Schema.String, Schema.String)), }) {} export class HttpContext extends Schema.Class("AI.HttpContext")({ request: HttpRequestDetails, response: Schema.optional(HttpResponseDetails), body: Schema.optional(Schema.String), bodyTruncated: Schema.optional(Schema.Boolean), requestId: Schema.optional(Schema.String), rateLimit: Schema.optional(HttpRateLimitDetails), }) {} export class InvalidRequestReason extends Schema.Class("AI.Error.InvalidRequest")({ _tag: Schema.tag("InvalidRequest"), message: Schema.String, parameter: Schema.optional(Schema.String), classification: Schema.optional(ProviderFailureClassification), providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export class NoRouteReason extends Schema.Class("AI.Error.NoRoute")({ _tag: Schema.tag("NoRoute"), route: RouteID, provider: ProviderID, model: ModelID, }) { get message() { return `No AI route for ${this.provider}/${this.model} using ${this.route}` } } export class AuthenticationReason extends Schema.Class("AI.Error.Authentication")({ _tag: Schema.tag("Authentication"), message: Schema.String, kind: Schema.Literals(["missing", "invalid", "expired", "insufficient-permissions", "unknown"]), providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export class RateLimitReason extends Schema.Class("AI.Error.RateLimit")({ _tag: Schema.tag("RateLimit"), message: Schema.String, retryAfterMs: Schema.optional(Schema.Number), rateLimit: Schema.optional(HttpRateLimitDetails), providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export class QuotaExceededReason extends Schema.Class("AI.Error.QuotaExceeded")({ _tag: Schema.tag("QuotaExceeded"), message: Schema.String, providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export class ContentPolicyReason extends Schema.Class("AI.Error.ContentPolicy")({ _tag: Schema.tag("ContentPolicy"), message: Schema.String, providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export class ProviderInternalReason extends Schema.Class("AI.Error.ProviderInternal")({ _tag: Schema.tag("ProviderInternal"), message: Schema.String, status: Schema.optional(Schema.Number), retryAfterMs: Schema.optional(Schema.Number), providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export const TransportType = Schema.Literals(["http", "websocket"]) export type TransportType = typeof TransportType.Type export const TransportOperation = Schema.Literals(["request", "read", "write"]) export type TransportOperation = typeof TransportOperation.Type export class TransportReason extends Schema.Class("AI.Error.Transport")({ _tag: Schema.tag("Transport"), message: Schema.String, transport: TransportType, operation: TransportOperation, code: Schema.optional(Schema.String), url: Schema.optional(Schema.String), http: Schema.optional(HttpContext), }) {} export class InvalidProviderOutputReason extends Schema.Class( "AI.Error.InvalidProviderOutput", )({ _tag: Schema.tag("InvalidProviderOutput"), message: Schema.String, classification: Schema.optional(Schema.Literals(["incomplete-stream"])), route: Schema.optional(Schema.String), raw: Schema.optional(Schema.String), providerMetadata: Schema.optional(ProviderMetadata), }) {} export class UnknownProviderReason extends Schema.Class("AI.Error.UnknownProvider")({ _tag: Schema.tag("UnknownProvider"), message: Schema.String, status: Schema.optional(Schema.Number), providerMetadata: Schema.optional(ProviderMetadata), http: Schema.optional(HttpContext), }) {} export const AIErrorReason = Schema.Union([ InvalidRequestReason, NoRouteReason, AuthenticationReason, RateLimitReason, QuotaExceededReason, ContentPolicyReason, ProviderInternalReason, TransportReason, InvalidProviderOutputReason, UnknownProviderReason, ]).pipe(Schema.toTaggedUnion("_tag")) export type AIErrorReason = Schema.Schema.Type export class AIError extends Schema.TaggedErrorClass()("AI.Error", { module: Schema.String, method: Schema.String, reason: AIErrorReason, }) { override readonly cause = this.reason override get message() { return `${this.module}.${this.method}: ${this.reason.message}` } } /** * Failure type for tool execute handlers. Handlers must map their internal * errors to this shape; the runtime catches `ToolFailure`s and surfaces them * as `tool-error` events plus a `tool-result` of `type: "error"` so the model * can self-correct. * * Anything thrown or yielded by a handler that is not a `ToolFailure` is * treated as a defect and fails the stream. */ export class ToolFailure extends Tool.Error {}