import { Cli, Errors } from 'incur'; /** * Error-layer enum. Per PROTOCOL §4.2 consumers MUST treat unknown values * as `transport` (forward-compat). The runtime tuple {@link ERROR_LAYERS} * mirrors this and is the source of truth for membership checks. */ type ErrorLayer = 'application' | 'escalation' | 'transport' | 'client'; /** * Call-to-action. Mirrors incur's command-aware Cta so registered command * names are validated at the type level when CTAs are constructed. */ type Cta = Cli.Cta; /** * Wire-format shape of `cta` on success and error envelopes. Mirrors what * incur's `c.ok` / `c.error` emit at the wire. */ type CtaBlock = { /** One or more suggested follow-up commands. */ commands: Cta[]; /** Human-readable label. Defaults to "Suggested command(s):" if omitted. */ description?: string; }; /** * Public registry of CLI-emitted error codes. Pre-v1, this should still be * treated carefully: adding a code is cheap, but do not pre-register future * codes until a reachable path can emit them. */ declare const ErrorCodes: { /** Business profile fetch failed (HTTP 4xx/5xx, network, DNS, etc.). */ readonly PROFILE_FETCH_FAILED: "PROFILE_FETCH_FAILED"; /** Business profile body could not be parsed as JSON. */ readonly PROFILE_INVALID_JSON: "PROFILE_INVALID_JSON"; /** * BUSINESS profile body parsed as JSON but failed schema validation. Our * own hosted document has its own code (`AGENT_PROFILE_SCHEMA_INVALID`); * never use this one for it. */ readonly PROFILE_SCHEMA_INVALID: "PROFILE_SCHEMA_INVALID"; /** * The Business offer has no exact release in common with the active runtime * Profile's eligible rendering set. A DIY or URL-only ad-hoc Profile * contributes one rendering; managed contributes every installed rendering. * Body provenance and explicit URL precedence are independent: any active * URL override must be removed/fixed before Profile switching can help. */ readonly PROTOCOL_VERSION_INCOMPATIBLE: "PROTOCOL_VERSION_INCOMPATIBLE"; /** * The BUSINESS's document contradicts itself about its version. Merchant * defect; there is nothing to fix client-side, which is why both cases * share one code and one (null) remedy. `context.kind` distinguishes them * for anyone holding the `UcpError` in-process: * - `'supported_versions'`: a `supported_versions` document declares a * different `ucp.version` than the key that linked to it. The spec says * the platform MUST NOT use such a document. * - `'service-entries'`: the negotiated business rendering advertises a * `dev.ucp.*` service with no entry at the rendering's own * `ucp.version`. * * Our own profile's equivalent defect is `AGENT_PROFILE_VERSION_MISMATCH` * — different document, different layer, different remedy. */ readonly PROFILE_VERSION_MISMATCH: "PROFILE_VERSION_MISMATCH"; /** * The business offers the negotiated protocol version, but a requested * SERVICE's entries do not intersect the versions the agent profile * declares for it (third-party services carry their own version lines). * Named `SERVICE_*` because the check is against `ucp.services`, not the * adjacent `ucp.capabilities` feature-flag registry. */ readonly SERVICE_VERSION_INCOMPATIBLE: "SERVICE_VERSION_INCOMPATIBLE"; /** Business profile parses cleanly but does not advertise the requested capability. */ readonly CAPABILITY_NOT_OFFERED: "CAPABILITY_NOT_OFFERED"; /** Business negotiated a capability but did not expose the requested operation/tool. */ readonly OPERATION_NOT_OFFERED: "OPERATION_NOT_OFFERED"; /** * Business profile passed schema validation but a negotiated mcp/rest service * entry is missing its `endpoint` (which dispatch needs). The canonical UCP * profile schema requires endpoint for these transports via per-transport * anyOf branches; the generated zod type widens that constraint back to * `optional()`, so this is the runtime backstop. */ readonly SERVICE_ENDPOINT_MISSING: "SERVICE_ENDPOINT_MISSING"; /** Business offers no transport that satisfies our requirements. */ readonly NO_COMPATIBLE_TRANSPORT: "NO_COMPATIBLE_TRANSPORT"; /** Endpoint returned a non-2xx HTTP response (full http_status carried in error.http_status). */ readonly TRANSPORT_HTTP_ERROR: "TRANSPORT_HTTP_ERROR"; /** Underlying fetch failed (DNS, connection refused, TLS, abort, etc.). */ readonly TRANSPORT_NETWORK_ERROR: "TRANSPORT_NETWORK_ERROR"; /** Endpoint returned 2xx but the body was not parseable as JSON. */ readonly TRANSPORT_INVALID_JSON: "TRANSPORT_INVALID_JSON"; /** * A URL answered a redirect status — `301`, `302`, `303`, `307`, `308` — and * `ucpFetch` declined to follow it. On the documents an exchange * dereferences — profile, identity (`jwks_uri` / CIMD), and `schema` * URLs — that enforces UCP's prohibition. On a negotiated service * endpoint the prohibition does not reach, and the refusal is ucp-cli's own * policy: a hop is an out-of-band replacement for the endpoint the profile * declared. One code for every outbound path because the remedy is the same * wherever it lands: serve the requested resource at the URL that was asked * for, or put the final https URL in the declaration that supplied the * refused one. `context.location` carries the refused target, `http_status` * the redirect status. */ readonly TRANSPORT_REDIRECT_REFUSED: "TRANSPORT_REDIRECT_REFUSED"; /** Body parses as JSON but isn't a JSON-RPC 2.0 envelope (missing jsonrpc/result/error). */ readonly MCP_INVALID_RESPONSE: "MCP_INVALID_RESPONSE"; /** * JSON-RPC `error` member returned, with no usable HTTP status hint. Numeric * `code` and `data` are carried in `error.context`. Fallback for protocol- * level errors that don't fit one of the spec-aligned codes below. */ readonly MCP_RPC_ERROR: "MCP_RPC_ERROR"; /** * Business requires authentication (HTTP 401). Spec: "Authentication required * or credentials invalid" (docs/specification/overview.md, Protocol Errors). * Recovery is buyer handoff to the business's flow — this CLI does not yet * implement business-specific auth schemes (JWT, OAuth, API key, etc.). */ readonly AUTH_REQUIRED: "AUTH_REQUIRED"; /** * Authenticated but lacks required scope/permissions (HTTP 403). Spec: * "Authenticated but insufficient permissions". Same recovery as AUTH_REQUIRED: * hand the buyer off, this CLI cannot escalate scope. */ readonly INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS"; /** * Idempotency key reused with a different payload (HTTP 409). Spec: * "Idempotency key reused with different payload". Recovery: re-issue with * a fresh key, or omit and retry. */ readonly IDEMPOTENCY_CONFLICT: "IDEMPOTENCY_CONFLICT"; /** * Business rate-limited the request (HTTP 429). Spec: "Too many requests". * Retry-After parsing is not yet wired; treat as transient and back off. */ readonly RATE_LIMITED: "RATE_LIMITED"; /** * Business 5xx — unexpected server error (HTTP 500-599 except 503). Spec: * "Unexpected server error". Likely transient. */ readonly BUSINESS_SERVER_ERROR: "BUSINESS_SERVER_ERROR"; /** * Business temporarily unable to handle requests (HTTP 503). Spec: "Server * temporarily unable to handle requests". Distinct from BUSINESS_SERVER_ERROR * because the business is signaling intent to recover; retry is preferred. */ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE"; /** Caller passed input that fails resolved-schema validation. */ readonly SCHEMA_VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED"; /** Caller passed structurally invalid input (bad JSON, missing required flag). */ readonly INVALID_INPUT: "INVALID_INPUT"; /** * No target business resolved from invocation input, UCP_BUSINESS, or the * session sources available in the current mode. Carries a context-appropriate * recovery CTA. */ readonly BUSINESS_NOT_RESOLVED: "BUSINESS_NOT_RESOLVED"; /** Caller referenced a profile name that doesn't exist on disk. */ readonly PROFILE_NOT_FOUND: "PROFILE_NOT_FOUND"; /** Non-interactive profile init omitted the required profile name. */ readonly PROFILE_INIT_REQUIRES_NAME: "PROFILE_INIT_REQUIRES_NAME"; /** Profile creation lost an existence race and refused to overwrite. */ readonly PROFILE_ALREADY_EXISTS: "PROFILE_ALREADY_EXISTS"; /** Profile name violates the filesystem-safe naming rule or reserved-name rule. */ readonly PROFILE_INVALID_NAME: "PROFILE_INVALID_NAME"; /** * OUR hosted document could not be fetched. The business fetches the same * URL on every call and would fail too — fix hosting first. * `context.reason` says which sub-case: * - `'network'` DNS/TLS/connection/abort * - `'http_status'` reached, non-2xx * - `'not_json'` 200 with a body that is not JSON (an origin * serving an HTML error page is the single most * common failure here, and it is not really * "unreachable") * - `'redirect'` answered a redirect status (301/302/303/307/308); * UCP forbids redirects on profile URLs, and * `ucp doctor`'s own fetch of the URL (the only one * ucp-cli makes) refuses to follow it * - `'business_reported'` the business told us: JSON-RPC -32001 with * `data.code: 'profile_unreachable'` */ readonly AGENT_PROFILE_UNREACHABLE: "AGENT_PROFILE_UNREACHABLE"; /** * OUR profile declares a `ucp.version` ucp-cli does not support. * Re-init at a supported version, switch profiles, or upgrade the CLI. */ readonly AGENT_PROFILE_VERSION_UNSUPPORTED: "AGENT_PROFILE_VERSION_UNSUPPORTED"; /** * OUR selected rendering is internally inconsistent: a `dev.ucp.*` entry at * a version other than its own `ucp.version` (the snapshot rule we hold * merchants to, applied to ourselves). This is always fatal; bundled * renderings are regression-tested never to contain the defect. */ readonly AGENT_PROFILE_VERSION_MISMATCH: "AGENT_PROFILE_VERSION_MISMATCH"; /** OUR hosted document failed its release's platform-profile schema. */ readonly AGENT_PROFILE_SCHEMA_INVALID: "AGENT_PROFILE_SCHEMA_INVALID"; /** * A service was explicitly requested (`--capability`, operation dispatch), * the BUSINESS offers it, and OUR selected rendering does not declare it — * so there is no platform side to negotiate with. A DIY document can be * edited and published; managed/URL renderings require leaving that source. * * When neither side has the id the answer is `CAPABILITY_NOT_OFFERED` * instead: a typo is not a "go edit your profile" problem. */ readonly AGENT_PROFILE_SERVICE_UNDECLARED: "AGENT_PROFILE_SERVICE_UNDECLARED"; }; /** * STABLE — registered codes give autocomplete; arbitrary strings remain valid * so plugins, future codes, and business-emitted application errors flow * through without a registry update. */ type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes] | (string & {}); interface UcpErrorOptions { /** STABLE — one of the four PROTOCOL §4.2 layers. Required at every throw site. */ layer: ErrorLayer; /** Code from {@link ErrorCodes}, or any string for forward-compat. */ code: ErrorCode; /** Human-readable error message. */ message: string; /** Actionable hint surfaced in TTY mode. */ hint?: string; /** Whether the operation can be retried as-is. */ retryable?: boolean; /** Underlying cause for the cause chain. */ cause?: Error; /** * HTTP status when the error originated from an HTTP response. Only * meaningful for `layer: 'transport'`. IN-PROCESS DIAGNOSTIC ONLY: the * current CLI middleware does not serialize it, so anything a caller must * see has to be folded into `message` (or `cta`) at the throw site. */ http_status?: number; /** * Diagnostic context — response body, validation field-paths, anything * that helps in-process code act on the error (the CLI middleware reads * `context.kind` to pick a recovery cta, for instance). IN-PROCESS ONLY: * it is never serialized onto the emitted error, which is why * `src/cli-errors.test.ts` pins remedies to `code`/`message`/`cta`. * Distinct from incur `BaseError.details: string`, which is the * cause-chain message extraction; ours is structured payload. */ context?: unknown; /** * Recovery hint — what the agent should do next. First-class field * (not nested in `context`) so agents can reliably destructure the flat * `cta` without spelunking diagnostic blobs. Wire shape matches * {@link CtaBlock} on error envelopes (PROTOCOL §4.3): `description` * plus an ordered `commands[]`, each with `command` + optional * `description`. */ cta?: CtaBlock; } /** * Throwable error carrying a {@link ErrorLayer}. Extends incur's IncurError * so it flows through `cli.serve()`'s existing catch path. `.layer`, * `.http_status`, and `.context` are in-process fields, never serialized: * `cli.ts`'s error middleware reads `.context` (and `.cta`) to choose what to * re-emit and never reads `.layer`, which is the per-site classification that * the throw sites and `error-layers.test.ts` enforce. Only `code`, `message`, * `retryable`, and `cta` are emitted on the error the caller sees. * * Throw at the call site that knows the layer, not at a centralized * wrapper. A required field at construction can't be skipped. */ declare class UcpError extends Errors.IncurError { name: string; readonly layer: ErrorLayer; readonly http_status?: number; readonly context?: unknown; readonly cta?: CtaBlock; constructor(options: UcpErrorOptions); } declare function isUcpError(err: unknown): err is UcpError; export { type ErrorCode, ErrorCodes, UcpError, type UcpErrorOptions, isUcpError };