{"version":3,"file":"agent-surface.mjs","names":[],"sources":["../src/agent-surface.ts"],"sourcesContent":["/**\n * The agent-facing surface contract shared by the CLI (`@stll/anonymize-cli`)\n * and the MCP server (`@stll/anonymize-mcp`).\n *\n * Both surfaces are driven mostly by AI agents. To stay legible to them, every\n * tool/command failure carries one of a closed set of machine-readable `code`s\n * alongside a human `message` and an actionable `hint`; the MCP server returns\n * the `{ error: { code, message, hint, retryable } }` envelope with `isError`,\n * and the CLI maps the same `code` to a distinct process exit code. The two\n * surfaces do not talk to each other (the CLI drives the WASM engine directly),\n * so they share the taxonomy through this runtime-free module rather than a\n * call path.\n *\n * The set is closed: a new failure mode must reuse a code here or add one\n * deliberately (and pick a fresh exit code). This module must stay runtime-free\n * (no wasm, no node built-ins) so any consumer can import it cheaply.\n */\n\nexport const ANONYMIZE_ERROR_CODES = [\n  /** Input failed validation at the boundary (shape, type, size, arguments). */\n  \"validation_error\",\n  /** A path resolved outside the configured roots, or was not absolute. */\n  \"path_not_allowed\",\n  /** The named input path or session key does not exist. */\n  \"not_found\",\n  /** The input's extension or content is not a supported document type. */\n  \"unsupported_format\",\n  /** The output path already exists; anonymize never overwrites. */\n  \"output_exists\",\n  /** A restore needs a durable session store that is not configured. */\n  \"session_unavailable\",\n  /** An external tool (pdftoppm, tesseract) was missing or not executable. */\n  \"dependency_missing\",\n  /** An unexpected internal failure; detail is not leaked to the caller. */\n  \"internal_error\",\n] as const;\n\nexport type AnonymizeErrorCode = (typeof ANONYMIZE_ERROR_CODES)[number];\n\n/**\n * The structured tool-error envelope the MCP surface returns (alongside\n * `isError: true`). `hint` states the next step for the agent; `retryable`\n * says whether retrying the same call unchanged could plausibly succeed.\n */\nexport type AnonymizeErrorEnvelope = {\n  error: {\n    code: AnonymizeErrorCode;\n    message: string;\n    hint: string;\n    retryable: boolean;\n  };\n};\n\n/**\n * Process exit codes for the CLI. `ok`/`unexpected`/`usage` are the pre-existing\n * classes (0/1/2); the remaining classes are keyed off the error codes above so\n * an agent can branch on the exit code without parsing stderr. Every error code\n * maps to a distinct exit code (asserted in `agent-surface.test.ts`).\n */\nexport const EXIT_CODES = {\n  ok: 0,\n  unexpected: 1,\n  usage: 2,\n  pathNotAllowed: 3,\n  notFound: 4,\n  unsupportedFormat: 5,\n  outputExists: 6,\n  sessionUnavailable: 7,\n  dependencyMissing: 8,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\n/**\n * Map every error code to its CLI exit class. `validation_error` shares the\n * `usage` class (2) with the CLI's own `UsageError`: both mean \"the invocation\n * was malformed, fix the input\". `internal_error` maps to the generic\n * `unexpected` class (1).\n */\nexport const ERROR_CODE_EXIT_MAP: Readonly<\n  Record<AnonymizeErrorCode, ExitCode>\n> = {\n  validation_error: EXIT_CODES.usage,\n  path_not_allowed: EXIT_CODES.pathNotAllowed,\n  not_found: EXIT_CODES.notFound,\n  unsupported_format: EXIT_CODES.unsupportedFormat,\n  output_exists: EXIT_CODES.outputExists,\n  session_unavailable: EXIT_CODES.sessionUnavailable,\n  dependency_missing: EXIT_CODES.dependencyMissing,\n  internal_error: EXIT_CODES.unexpected,\n};\n\ntype SurfaceErrorOptions = {\n  hint: string;\n  retryable?: boolean;\n  cause?: unknown;\n};\n\n/**\n * A failure carrying a stable agent-surface `code`, a `hint`, and a `retryable`\n * flag. Service and engine code throws this instead of a plain `Error` so both\n * surfaces can classify it: the MCP boundary renders it as the error envelope,\n * the CLI maps it to an exit code. Messages must stay content-free (never echo\n * raw input text or detected entities).\n */\nexport class AnonymizeSurfaceError extends Error {\n  readonly code: AnonymizeErrorCode;\n  readonly hint: string;\n  readonly retryable: boolean;\n\n  constructor(\n    code: AnonymizeErrorCode,\n    message: string,\n    { hint, retryable = false, cause }: SurfaceErrorOptions,\n  ) {\n    super(message, cause === undefined ? undefined : { cause });\n    this.name = \"AnonymizeSurfaceError\";\n    this.code = code;\n    this.hint = hint;\n    this.retryable = retryable;\n  }\n}\n\nexport const isAnonymizeSurfaceError = (\n  value: unknown,\n): value is AnonymizeSurfaceError => value instanceof AnonymizeSurfaceError;\n\n/** Build the wire error envelope from a surface error. */\nexport const toErrorEnvelope = (\n  error: AnonymizeSurfaceError,\n): AnonymizeErrorEnvelope => ({\n  error: {\n    code: error.code,\n    message: error.message,\n    hint: error.hint,\n    retryable: error.retryable,\n  },\n});\n\n/**\n * Classify an arbitrary thrown value into the error envelope. A\n * `AnonymizeSurfaceError` keeps its code; anything else collapses to\n * `internal_error` with its detail withheld, so unexpected failures never leak\n * raw text or stack detail to the caller.\n */\nexport const classifyToEnvelope = (error: unknown): AnonymizeErrorEnvelope => {\n  if (isAnonymizeSurfaceError(error)) {\n    return toErrorEnvelope(error);\n  }\n  return {\n    error: {\n      code: \"internal_error\",\n      message: \"The operation failed unexpectedly.\",\n      hint: \"Retry; if it persists, file it with the send_feedback tool.\",\n      retryable: true,\n    },\n  };\n};\n\n/** The CLI exit code for a thrown value (non-surface errors are `unexpected`). */\nexport const exitCodeForError = (error: unknown): ExitCode =>\n  isAnonymizeSurfaceError(error)\n    ? ERROR_CODE_EXIT_MAP[error.code]\n    : EXIT_CODES.unexpected;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,wBAAwB;CAEnC;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;AACF;;;;;;;AAwBA,MAAa,aAAa;CACxB,IAAI;CACJ,YAAY;CACZ,OAAO;CACP,gBAAgB;CAChB,UAAU;CACV,mBAAmB;CACnB,cAAc;CACd,oBAAoB;CACpB,mBAAmB;AACrB;;;;;;;AAUA,MAAa,sBAET;CACF,kBAAkB,WAAW;CAC7B,kBAAkB,WAAW;CAC7B,WAAW,WAAW;CACtB,oBAAoB,WAAW;CAC/B,eAAe,WAAW;CAC1B,qBAAqB,WAAW;CAChC,oBAAoB,WAAW;CAC/B,gBAAgB,WAAW;AAC7B;;;;;;;;AAeA,IAAa,wBAAb,cAA2C,MAAM;CAC/C;CACA;CACA;CAEA,YACE,MACA,SACA,EAAE,MAAM,YAAY,OAAO,SAC3B;EACA,MAAM,SAAS,UAAU,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,CAAC;EAC1D,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,YAAY;CACnB;AACF;AAEA,MAAa,2BACX,UACmC,iBAAiB;;AAGtD,MAAa,mBACX,WAC4B,EAC5B,OAAO;CACL,MAAM,MAAM;CACZ,SAAS,MAAM;CACf,MAAM,MAAM;CACZ,WAAW,MAAM;AACnB,EACF;;;;;;;AAQA,MAAa,sBAAsB,UAA2C;CAC5E,IAAI,wBAAwB,KAAK,GAC/B,OAAO,gBAAgB,KAAK;CAE9B,OAAO,EACL,OAAO;EACL,MAAM;EACN,SAAS;EACT,MAAM;EACN,WAAW;CACb,EACF;AACF;;AAGA,MAAa,oBAAoB,UAC/B,wBAAwB,KAAK,IACzB,oBAAoB,MAAM,QAC1B,WAAW"}