@ai-matrx/agents 0.43.18 → 0.43.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/content-transfer/index.cjs +26 -10
- package/dist/content-transfer/index.cjs.map +1 -1
- package/dist/content-transfer/index.js +26 -10
- package/dist/content-transfer/index.js.map +1 -1
- package/dist/content-transfer/react/index.cjs +26 -10
- package/dist/content-transfer/react/index.cjs.map +1 -1
- package/dist/content-transfer/react/index.js +26 -10
- package/dist/content-transfer/react/index.js.map +1 -1
- package/dist/index.cjs +75 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +75 -74
- package/dist/index.js.map +1 -1
- package/dist/{keys.generated-BzkXY1sO.d.cts → keys.generated-DX8nj5xZ.d.cts} +4 -4
- package/dist/{keys.generated-BzkXY1sO.d.ts → keys.generated-DX8nj5xZ.d.ts} +4 -4
- package/dist/mandates/index.cjs +2 -2
- package/dist/mandates/index.cjs.map +1 -1
- package/dist/mandates/index.d.cts +2 -2
- package/dist/mandates/index.d.ts +2 -2
- package/dist/mandates/index.js +2 -2
- package/dist/mandates/index.js.map +1 -1
- package/dist/matrx/index.cjs +75 -74
- package/dist/matrx/index.cjs.map +1 -1
- package/dist/matrx/index.d.cts +1 -1
- package/dist/matrx/index.d.ts +1 -1
- package/dist/matrx/index.js +75 -74
- package/dist/matrx/index.js.map +1 -1
- package/dist/portable/index.cjs +26 -10
- package/dist/portable/index.cjs.map +1 -1
- package/dist/portable/index.js +26 -10
- package/dist/portable/index.js.map +1 -1
- package/dist/portable/mcp.cjs +24 -10
- package/dist/portable/mcp.cjs.map +1 -1
- package/dist/portable/mcp.js +24 -10
- package/dist/portable/mcp.js.map +1 -1
- package/dist/react/index.cjs +60 -46
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +60 -46
- package/dist/react/index.js.map +1 -1
- package/mandates/snapshots/keys.0.43.19.json +653 -0
- package/package.json +4 -4
package/dist/portable/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../matrx/client.ts","../../matrx/backend-errors.ts","../../matrx/org-context.ts","../../matrx/protocol.ts","../../matrx/transport.ts","../../matrx/call.ts","../../matrx/internal.ts","../../portable/mcp.ts"],"sourcesContent":["/**\n * The PRODUCTION `MatrxTransport` — the full connection pipeline every Matrx\n * host used to hand-roll, moved in under C22 (THE HARD PARTS LIVE IN THE\n * PACKAGE). A host constructs it from identity alone:\n *\n * ```ts\n * const transport = createMatrxTransport({\n * baseUrl: \"https://server.app.matrxserver.com\",\n * credentials, // CredentialsPort (@ai-matrx/data)\n * organizationId: () => activeOrgId, // omit for conversation-lane calls\n * });\n * ```\n *\n * Everything else is defaulted to the proven production values:\n *\n * - **timeouts** — connect 15s (for a non-streaming FastAPI handler this is\n * effectively time-to-response), total UNCAPPED (the port cannot tell a JSON\n * call from a long-lived NDJSON/SSE stream; the connect timeout is the JSON\n * guard), via `@ai-matrx/data/net`'s `resilientFetch`;\n * - **protocol** — the AI API version transform (`applyAiApiVersion`, default\n * v2) + the loud v2 → v1 transport fallback (`./protocol`);\n * - **credentials** — read fresh from the injected `CredentialsPort` on EVERY\n * call, so a token refresh mid-run is picked up; mapped to headers by the\n * ONE `credentialToHeaders`;\n * - **org context** — the fail-closed `X-Organization-Id` binding\n * (`./org-context`): configured lanes REQUIRE an org and refuse before the\n * wire; unconfigured lanes (conversation-scoped calls, which carry org in\n * the body) send none;\n * - **error classification** — every failure normalizes through\n * `normalizeMatrxError` into the ONE `MatrxCallError` envelope;\n * - **diagnostics** — a typed sink the host wires to its capture/telemetry\n * (`onRequest`, `onError`, `onProtocolDowngrade`); wiring it is optional,\n * the transport works silently without it.\n *\n * Header merge order is part of the port contract: wire headers first\n * (`Content-Type` / `Accept` / `Last-Event-ID` — the package's), then\n * credentials, then resolver policy headers, then the org header. Policy\n * headers never carry `Content-Type` — the wire owns it, and a policy\n * `Content-Type` merged over a GET SSE call would corrupt the wire, so the\n * factory strips it defensively.\n */\n\nimport { isNetError, type CredentialsPort } from \"@ai-matrx/data\";\nimport { credentialToHeaders } from \"@ai-matrx/data\";\nimport { extractErrorMessage } from \"@ai-matrx/data/net\";\nimport { BackendApiError } from \"./backend-errors\";\nimport {\n bareStatusSentence,\n honestTransportMessage,\n isBareTransportCode,\n} from \"./call\";\nimport {\n applyOrganizationContextHeader,\n OrganizationContextError,\n requireOrganizationContext,\n} from \"./org-context\";\nimport {\n applyAiApiVersion,\n fetchWithMatrxProtocolFallback,\n MATRX_AI_API_VERSION_DEFAULT,\n type MatrxAiApiVersion,\n type MatrxProtocolDowngrade,\n} from \"./protocol\";\nimport {\n extractMatrxErrorCode,\n extractMatrxErrorMessage,\n MatrxApiError,\n type MatrxTransport,\n type MatrxTransportRequest,\n} from \"./transport\";\n\n// ─── The normalized error envelope ──────────────────────────────────────────\n\n/**\n * The ONE classified error shape for Matrx client calls — the envelope hosts\n * branch on instead of string-matching exceptions. Structurally compatible\n * with matrx-frontend's `ApiCallError` by design.\n */\nexport interface MatrxCallError {\n type:\n | \"auth_error\"\n | \"network_error\"\n | \"http_error\"\n | \"validation_error\"\n | \"abort_error\"\n | \"unknown\";\n message: string;\n /** HTTP status code, if applicable. */\n status?: number;\n /** Raw error detail from the server (the parsed error body). */\n serverDetail?: unknown;\n /** Machine code preserved through normalization (server `code`, org-context code, …). */\n code?: string;\n /** Original exception identity for diagnostics. */\n name?: string;\n /** Original exception stack. */\n stack?: string;\n /** JSON-safe dump of the original thrown value. */\n raw?: unknown;\n}\n\nfunction classifyStatus(status: number): \"validation_error\" | \"http_error\" {\n return status >= 400 && status < 500 ? \"validation_error\" : \"http_error\";\n}\n\n/**\n * Classify any failure thrown by a Matrx client call — `MatrxApiError`, a\n * typed `BackendApiError` / `StreamTransportError`, a `NetError` from the\n * resilience layer, an org-context refusal, an abort — into the\n * `MatrxCallError` envelope. Never throws.\n *\n * Every message is laundered through `honestTransportMessage`: a bare status\n * line (\"HTTP 400\") never reaches a person — the status rides `status`.\n * A `BackendApiError` keeps its machine `code` (a dropped socket stays\n * `stream_transport_lost`, so a surface reattaches instead of dead-ending)\n * and its structured `details` ride `serverDetail` (the organization-hold\n * body carries the caller's membership choices there).\n */\nexport function normalizeMatrxError(err: unknown): MatrxCallError {\n if (err instanceof OrganizationContextError) {\n return {\n type: \"validation_error\",\n message: err.message,\n code: err.code,\n name: err.name,\n ...(err.stack !== undefined ? { stack: err.stack } : {}),\n };\n }\n\n if (err instanceof DOMException && err.name === \"AbortError\") {\n return { type: \"abort_error\", message: \"Request was cancelled.\" };\n }\n\n if (err instanceof MatrxApiError) {\n return {\n type: classifyStatus(err.status),\n message: honestTransportMessage(err.message, err.status),\n status: err.status,\n serverDetail: err.serverDetail,\n ...(err.code ? { code: err.code } : {}),\n name: err.name,\n };\n }\n\n if (err instanceof BackendApiError) {\n return {\n type: \"network_error\",\n message: honestTransportMessage(\n err.detail || err.userMessage,\n err.status ?? undefined,\n ),\n code: err.code,\n name: err.name,\n ...(err.status !== null ? { status: err.status } : {}),\n ...(err.details !== null ? { serverDetail: err.details } : {}),\n };\n }\n\n if (isNetError(err)) {\n if (err.code === \"aborted\") {\n return { type: \"abort_error\", message: err.message };\n }\n if (err.code === \"http\") {\n const status = err.status ?? 0;\n return {\n type: classifyStatus(status),\n message: honestTransportMessage(err.message, status),\n status,\n };\n }\n // connect-timeout / total-timeout / heartbeat-timeout / network / offline\n return { type: \"network_error\", message: err.message };\n }\n\n if (err instanceof Error) {\n const httpMatch = err.message.match(/HTTP (\\d+):\\s*(.*)/);\n if (httpMatch) {\n const status = parseInt(httpMatch[1] as string, 10);\n return {\n type: classifyStatus(status),\n message: honestTransportMessage(httpMatch[2] ?? err.message, status),\n status,\n };\n }\n // \"HTTP 400\" with no colon: the status line itself, never the sentence.\n if (isBareTransportCode(err.message)) {\n const status = Number.parseInt(err.message.replace(/\\D+/g, \"\"), 10);\n return {\n type: classifyStatus(status),\n message: bareStatusSentence(status),\n status,\n };\n }\n return { type: \"network_error\", message: err.message };\n }\n\n return { type: \"unknown\", message: extractErrorMessage(err) };\n}\n\n// ─── The factory ────────────────────────────────────────────────────────────\n\n/**\n * A fully-resolved connection target for one call: where to send it and which\n * policy headers ride ON TOP of the wire + credential headers.\n */\nexport interface MatrxTransportTarget {\n /** Fully-qualified base URL, no trailing slash. */\n baseUrl: string;\n /**\n * Extra policy headers for this target (e.g. a resolver that carries its own\n * credential headers). `Content-Type` is stripped — the wire owns it.\n */\n policyHeaders?: Record<string, string>;\n /** Routing channel label surfaced to `onRequest` telemetry. */\n channel?: string;\n}\n\n/** Context handed to every diagnostics callback. */\nexport interface MatrxRequestInfo {\n /** The final URL (base + version-transformed path). */\n url: string;\n method: \"GET\" | \"POST\";\n /** The server-relative path as the package requested it. */\n path: string;\n /** The resolved routing channel (default \"default\"). */\n channel: string;\n /** The transport's call-site label (default \"matrxTransport\"). */\n source: string;\n}\n\n/**\n * The typed diagnostics sink — the host wires these to its telemetry/capture\n * systems; all optional, and the transport is fully functional without them.\n */\nexport interface MatrxTransportDiagnostics {\n /** Fired at the last pre-fetch moment of every call. */\n onRequest?: (info: MatrxRequestInfo) => void;\n /**\n * Fired for every thrown network failure and every non-2xx response not\n * listed in `expectedErrorStatuses`, with the classified envelope.\n */\n onError?: (error: MatrxCallError, info: MatrxRequestInfo) => void;\n /** Fired on every v2 → v1 protocol downgrade. */\n onProtocolDowngrade?: (downgrade: MatrxProtocolDowngrade) => void;\n}\n\nexport interface CreateMatrxTransportOptions {\n /** Fixed base URL, no trailing slash. Exactly one of `baseUrl` / `resolveTarget`. */\n baseUrl?: string;\n /**\n * Per-call target resolver — for hosts whose base URL / policy headers are\n * dynamic (server toggles, sandbox overrides, conversation channels).\n * Resolution runs fresh on EVERY call. May throw to refuse a call loudly.\n */\n resolveTarget?: () => MatrxTransportTarget;\n /**\n * The credential source (`@ai-matrx/data`), read fresh per call so token\n * refreshes are picked up mid-run. Omit only when the resolver's\n * `policyHeaders` already carry the credentials.\n */\n credentials?: CredentialsPort;\n /**\n * The org-context binding. When configured, every call REQUIRES a valid org\n * (fail-closed `organization_context_required` before the wire) and carries\n * `X-Organization-Id`. Omit for conversation-lane transports, which send the\n * org in the body only.\n */\n organizationId?: string | (() => string | null | undefined);\n /** AI API protocol version (value or per-call getter). Default `\"v2\"`. */\n aiApiVersion?: MatrxAiApiVersion | (() => MatrxAiApiVersion);\n /** Max ms request start → response headers. Default 15_000. */\n connectTimeoutMs?: number;\n /**\n * Max ms for the whole handshake. Default `null` (uncapped) — the port\n * cannot tell a JSON call from a long-lived stream, and capping would kill\n * long streams; the connect timeout is the real JSON guard.\n */\n totalTimeoutMs?: number | null;\n /**\n * HTTP failures this transport's call class fully handles as expected\n * domain outcomes (e.g. 404 on runtime-operation identify). They still\n * reach the package client (which maps them to typed results) but are not\n * reported through `onError`.\n */\n expectedErrorStatuses?: readonly number[];\n /** Short call-site label surfaced on `MatrxRequestInfo` (default \"matrxTransport\"). */\n source?: string;\n diagnostics?: MatrxTransportDiagnostics;\n}\n\nconst PRODUCTION_CONNECT_TIMEOUT_MS = 15_000;\n\nfunction stripContentType(\n headers: Record<string, string>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(headers).filter(\n ([name]) => name.toLowerCase() !== \"content-type\",\n ),\n );\n}\n\n/**\n * Build the production `MatrxTransport`. See the module doc for the pipeline;\n * every knob defaults to the proven production value.\n */\nexport function createMatrxTransport(\n options: CreateMatrxTransportOptions,\n): MatrxTransport {\n const {\n baseUrl,\n resolveTarget,\n credentials,\n organizationId,\n aiApiVersion,\n connectTimeoutMs = PRODUCTION_CONNECT_TIMEOUT_MS,\n totalTimeoutMs = null,\n expectedErrorStatuses,\n diagnostics,\n source = \"matrxTransport\",\n } = options;\n\n if ((baseUrl === undefined) === (resolveTarget === undefined)) {\n throw new Error(\n \"createMatrxTransport: provide exactly one of `baseUrl` or `resolveTarget`.\",\n );\n }\n\n const resolve: () => MatrxTransportTarget =\n resolveTarget ?? (() => ({ baseUrl: baseUrl as string }));\n\n const resolveVersion = (): MatrxAiApiVersion => {\n if (aiApiVersion === undefined) return MATRX_AI_API_VERSION_DEFAULT;\n return typeof aiApiVersion === \"function\" ? aiApiVersion() : aiApiVersion;\n };\n\n const resolveOrganizationId = (): string | null => {\n if (organizationId === undefined) return null;\n const candidate =\n typeof organizationId === \"function\" ? organizationId() : organizationId;\n // Fail closed: a configured org lane with no org refuses before the wire.\n return requireOrganizationContext(candidate);\n };\n\n return {\n async fetch(path: string, init: MatrxTransportRequest): Promise<Response> {\n const target = resolve();\n\n // `path` is already interpolated (`/ai/agents/<uuid>`), so the version\n // transform is the concrete-path bridge, not a template registry.\n const versionedPath = applyAiApiVersion(path, resolveVersion());\n const url = `${target.baseUrl}${versionedPath}`;\n\n // Wire headers FIRST; credentials, then resolver policy headers, then\n // the org header on top — none of them may carry Content-Type.\n const credentialHeaders = credentials\n ? credentialToHeaders(await credentials.get())\n : {};\n let headers: Record<string, string> = {\n ...init.headers,\n ...stripContentType(credentialHeaders),\n ...stripContentType(target.policyHeaders ?? {}),\n };\n const orgId = resolveOrganizationId();\n if (orgId !== null) {\n headers = applyOrganizationContextHeader(headers, orgId);\n }\n\n const info: MatrxRequestInfo = {\n url,\n method: init.method,\n path,\n channel: target.channel ?? \"default\",\n source,\n };\n diagnostics?.onRequest?.(info);\n\n let response: Response;\n try {\n ({ response } = await fetchWithMatrxProtocolFallback(\n url,\n {\n method: init.method,\n headers,\n ...(init.body !== undefined ? { body: init.body } : {}),\n },\n {\n ...(init.signal ? { signal: init.signal } : {}),\n connectTimeoutMs,\n totalTimeoutMs,\n throwOnHttpError: false,\n ...(diagnostics?.onProtocolDowngrade\n ? { onDowngrade: diagnostics.onProtocolDowngrade }\n : {}),\n },\n ));\n } catch (err) {\n diagnostics?.onError?.(normalizeMatrxError(err), info);\n throw err;\n }\n\n if (!response.ok && !expectedErrorStatuses?.includes(response.status)) {\n // The package client consumes the original body for its\n // MatrxApiError; read the diagnostics copy from a clone so the two\n // never fight.\n const serverDetail: unknown = await response\n .clone()\n .json()\n .catch(() => undefined);\n const code = extractMatrxErrorCode(serverDetail);\n diagnostics?.onError?.(\n {\n type: classifyStatus(response.status),\n message:\n extractMatrxErrorMessage(serverDetail) ??\n `HTTP ${response.status}`,\n status: response.status,\n serverDetail,\n ...(code ? { code } : {}),\n },\n info,\n );\n }\n\n return response;\n },\n };\n}\n","/**\n * `@ai-matrx/agents/matrx` — the AI Matrx server's error model, as every\n * client reads it: `BackendApiError` (the server's `APIError` body),\n * `StreamTransportError` (the socket died mid-run; the run may still finish\n * and is reattachable), parsing of HTTP / stream / persisted errors, and the\n * ONE sentence a person sees (`getUserMessage`, `describeBackendFailure`).\n *\n * Moved from matrx-frontend `lib/api/errors.ts` (chat-package independence\n * P9); the app re-exports it from here. Pure: no host, no window, no env.\n */\n\n/**\n * Standardized error shape returned by all backend endpoints.\n * Matches the Python `APIError` Pydantic model.\n */\nexport interface BackendApiErrorData {\n /** Machine-readable error code (e.g. \"auth_required\", \"validation_error\") */\n error: string;\n /** Developer-facing detail for debugging */\n message: string;\n /** Safe to display directly in the UI */\n user_message: string;\n /** Extra info (validation errors, etc.) */\n details: unknown | null;\n /** Unique request ID for support/debugging */\n request_id: string;\n}\n\n/** Common backend error codes */\nexport type BackendErrorCode =\n | \"auth_required\"\n | \"token_required\"\n | \"admin_required\"\n | \"validation_error\"\n | \"not_found\"\n | \"internal_error\"\n | \"agent_error\"\n /** The stream socket died mid-run; the server run may still be completing\n * and is reattachable. See `StreamTransportError`. */\n | \"stream_transport_lost\"\n | (string & {});\n\n// ============================================================================\n// ERROR CLASS\n// ============================================================================\n\n/**\n * Typed error thrown by all backend API operations.\n * Contains structured fields matching the Python APIError model.\n *\n * Usage:\n * ```typescript\n * try {\n * await client.post(ENDPOINTS.ai.agentStart(agentId), body);\n * } catch (err) {\n * if (err instanceof BackendApiError) {\n * // Show err.userMessage to the user\n * // Log err.requestId for debugging\n * // Check err.code for programmatic handling\n * }\n * }\n * ```\n */\nexport class BackendApiError extends Error {\n /** Machine-readable error code */\n readonly code: BackendErrorCode;\n /** Developer-facing detail */\n readonly detail: string;\n /** Safe to display directly in the UI */\n readonly userMessage: string;\n /** Extra info (validation errors, etc.) */\n readonly details: unknown | null;\n /** Unique request ID for support/debugging */\n readonly requestId: string;\n /** HTTP status code (if from an HTTP response) */\n readonly status: number | null;\n\n constructor(data: {\n code: BackendErrorCode;\n detail: string;\n userMessage: string;\n details?: unknown | null;\n requestId?: string | undefined;\n status?: number | null | undefined;\n }) {\n super(data.userMessage);\n this.name = \"BackendApiError\";\n this.code = data.code;\n this.detail = data.detail;\n this.userMessage = data.userMessage;\n this.details = data.details ?? null;\n // MATRX-EXCEPTION: requestId is genuinely optional (constructor param);\n // \"\" means \"no request id available\" — a display/log field, not persisted.\n this.requestId = data.requestId ?? \"\";\n this.status = data.status ?? null;\n }\n\n /** Convert to the wire format for logging */\n toJSON(): BackendApiErrorData {\n return {\n error: this.code,\n message: this.detail,\n user_message: this.userMessage,\n details: this.details,\n request_id: this.requestId,\n };\n }\n}\n\n/**\n * The socket carrying a live NDJSON stream broke mid-run.\n *\n * THE DISTINCTION THIS EXISTS TO MAKE: a backend that blows up mid-stream does\n * NOT break the socket — it emits a typed `error` event and closes the body\n * cleanly. So an exception escaping the body reader means the *transport* died,\n * not the run. And aidream streams run `detach_on_disconnect=True`: the server\n * keeps executing and persisting the turn after our connection goes away.\n *\n * The client therefore cannot decide locally whether the answer is lost — it\n * must ASK THE SERVER. That is what `resumable` means here: \"reattach by\n * requestId / conversationId / durable run id and let server truth settle it\",\n * never \"this succeeded\". A server that genuinely died reports `failed` on\n * reattach and the honest record replaces the optimistic copy.\n *\n * Consumers: `run-ai-stream.ts` (chat → `reconnectServerOperation`) and\n * `adopt-foreign-stream.ts` (pipeline runs → the surface's own rejoin).\n */\nexport class StreamTransportError extends BackendApiError {\n /** Always true — reattach and let the server settle the outcome. */\n readonly resumable = true as const;\n\n constructor(data: {\n detail: string;\n details?: unknown | null;\n requestId?: string;\n }) {\n super({\n code: \"stream_transport_lost\",\n detail: data.detail,\n userMessage:\n \"The connection dropped. Your run is still going on the server — reconnecting to it now.\",\n details: data.details ?? null,\n ...(data.requestId !== undefined ? { requestId: data.requestId } : {}),\n });\n this.name = \"StreamTransportError\";\n }\n}\n\n/**\n * True when a failure is a dropped transport rather than a failed run. Use this\n * instead of `instanceof` at boundaries that re-wrap errors (thunk rejections,\n * `callApi` result errors), where the class identity is lost but the code\n * survives.\n */\nexport function isStreamTransportLost(error: unknown): boolean {\n if (error instanceof StreamTransportError) return true;\n if (error instanceof BackendApiError) {\n return error.code === \"stream_transport_lost\";\n }\n if (error && typeof error === \"object\") {\n const code = (error as { code?: unknown }).code;\n if (code === \"stream_transport_lost\") return true;\n const errorType = (error as { error_type?: unknown }).error_type;\n if (errorType === \"transport_lost\") return true;\n }\n return false;\n}\n\n// ============================================================================\n// HTTP ERROR PARSER\n// ============================================================================\n\n/**\n * Parse a non-OK HTTP response into a BackendApiError.\n *\n * Handles the standardized backend shape and falls back gracefully\n * when the response isn't JSON or uses a legacy format.\n */\nexport async function parseHttpError(\n response: Response,\n): Promise<BackendApiError> {\n const status = response.status;\n let text: string;\n\n try {\n // A Response body can only be read once. Capture it before deciding\n // whether the backend sent a structured error or plain text.\n text = await response.text();\n } catch {\n return new BackendApiError({\n code: statusToCode(status),\n detail: `HTTP ${status}`,\n userMessage: `Request failed (${status})`,\n status,\n });\n }\n\n let body: Record<string, unknown> | null;\n try {\n body = JSON.parse(text) as Record<string, unknown> | null;\n } catch {\n return new BackendApiError({\n code: statusToCode(status),\n detail: text || `HTTP ${status}`,\n userMessage: text || `Request failed (${status})`,\n status,\n });\n }\n return parseHttpErrorBody(body, status);\n}\n\n/**\n * Parse an already-decoded JSON error body into a BackendApiError.\n *\n * Exported because XHR callers (upload/download progress paths) have the\n * parsed body in hand and MUST NOT hand-roll a shallower read: a private\n * copy in `python-client.ts` looked only at top-level `error`/`message`, so\n * FastAPI's `{\"detail\": {...}}` envelope — what every matrx-files 500 uses —\n * degraded to the useless `code: \"internal\", detail: \"HTTP 500\"`. That is\n * exactly how an upload failure with a real server-side cause reached the\n * user as `Upload failed (500)` and nothing else. One parser, every transport.\n */\nexport function parseHttpErrorBody(\n body: Record<string, unknown> | null,\n status: number,\n): BackendApiError {\n if (!body) {\n return new BackendApiError({\n code: statusToCode(status),\n detail: `HTTP ${status}`,\n userMessage: `Request failed (${status})`,\n status,\n });\n }\n // Standard backend shape: { error, message, user_message, details, request_id }\n if (typeof body.error === \"string\" && typeof body.user_message === \"string\") {\n return new BackendApiError({\n code: body.error as BackendErrorCode,\n detail: (body.message as string) || `HTTP ${status}`,\n userMessage: body.user_message as string,\n details: body.details ?? null,\n requestId:\n typeof body.request_id === \"string\" ? body.request_id : undefined,\n status,\n });\n }\n\n // Legacy: nested error object with user_visible_message\n if (typeof body.error === \"object\" && body.error !== null) {\n const errorObj = body.error as Record<string, unknown>;\n return new BackendApiError({\n code:\n (errorObj.type as string) ||\n (errorObj.error as string) ||\n statusToCode(status),\n detail: (errorObj.message as string) || `HTTP ${status}`,\n userMessage:\n (errorObj.user_message as string) ||\n (errorObj.user_visible_message as string) ||\n (errorObj.message as string) ||\n `Request failed (${status})`,\n details: errorObj.details ?? null,\n requestId:\n typeof errorObj.request_id === \"string\"\n ? errorObj.request_id\n : undefined,\n status,\n });\n }\n\n // FastAPI 422 validation shape: { detail: [{ loc, msg, type }, ...] }\n if (Array.isArray(body.detail)) {\n const first = body.detail[0] as Record<string, unknown> | undefined;\n const firstMsg = first && typeof first.msg === \"string\" ? first.msg : null;\n return new BackendApiError({\n code: statusToCode(status),\n detail: firstMsg\n ? `Validation error: ${firstMsg}`\n : JSON.stringify(body.detail),\n userMessage: firstMsg || `Request failed (${status})`,\n details: body.detail,\n status,\n });\n }\n\n // FastAPI HTTPException with structured detail: { detail: { code?, error?, message?, user_message?, ... } }\n if (\n typeof body.detail === \"object\" &&\n body.detail !== null &&\n !Array.isArray(body.detail)\n ) {\n const d = body.detail as Record<string, unknown>;\n const code =\n (typeof d.code === \"string\" && d.code) ||\n (typeof d.error === \"string\" && d.error) ||\n statusToCode(status);\n const message =\n (typeof d.message === \"string\" && d.message) ||\n (typeof d.detail === \"string\" && d.detail) ||\n `HTTP ${status}`;\n return new BackendApiError({\n code: code as BackendErrorCode,\n detail: message,\n userMessage:\n (typeof d.user_message === \"string\" && d.user_message) ||\n (typeof d.user_visible_message === \"string\" &&\n d.user_visible_message) ||\n message,\n details: d.details ?? d,\n requestId: typeof d.request_id === \"string\" ? d.request_id : undefined,\n status,\n });\n }\n\n // Legacy: flat { error: string, message: string } or { detail: string }\n return new BackendApiError({\n code: typeof body.error === \"string\" ? body.error : statusToCode(status),\n detail:\n (body.message as string) ||\n (body.detail as string) ||\n (body.error as string) ||\n `HTTP ${status}`,\n userMessage:\n (body.user_message as string) ||\n (body.user_visible_message as string) ||\n (body.message as string) ||\n (body.detail as string) ||\n `Request failed (${status})`,\n details: body.details ?? null,\n requestId:\n typeof body.request_id === \"string\" ? body.request_id : undefined,\n status,\n });\n}\n\n/**\n * Adapt callApi's result-style error into the same canonical error used by\n * direct fetch and streaming consumers.\n *\n * callApi intentionally returns errors instead of throwing them, but its\n * `serverDetail` contains the complete FastAPI body. Sending only\n * `error.message` to a feature discards that body and turns a precise\n * configuration failure into \"HTTP 422\". This adapter keeps one parser and\n * one human-facing explanation path across both client styles.\n */\nexport function parseCallApiError(error: {\n message: string;\n status?: number;\n serverDetail?: unknown;\n}): BackendApiError {\n const status = error.status ?? 500;\n const body =\n error.serverDetail &&\n typeof error.serverDetail === \"object\" &&\n !Array.isArray(error.serverDetail)\n ? (error.serverDetail as Record<string, unknown>)\n : { message: error.message };\n return parseHttpErrorBody(body, status);\n}\n\n// ============================================================================\n// STREAMING ERROR PARSER\n// ============================================================================\n\n/**\n * Parse streaming error event data into a BackendApiError.\n *\n * Handles both new format (`user_message`) and legacy (`user_visible_message`).\n */\nexport function parseStreamError(data: unknown): BackendApiError {\n if (!data || typeof data !== \"object\") {\n return new BackendApiError({\n code: \"internal_error\",\n detail: typeof data === \"string\" ? data : \"Unknown streaming error\",\n userMessage: typeof data === \"string\" ? data : \"Something went wrong\",\n });\n }\n\n const obj = data as Record<string, unknown>;\n const details =\n typeof obj.details === \"object\" && obj.details !== null\n ? (obj.details as Record<string, unknown>)\n : null;\n return new BackendApiError({\n code:\n (obj.code as string) ||\n (obj.error_type as string) ||\n (obj.error as string) ||\n \"internal_error\",\n detail: (obj.message as string) || \"Streaming error\",\n userMessage:\n (obj.user_message as string) ||\n (obj.message as string) ||\n \"Something went wrong\",\n details,\n requestId:\n typeof obj.request_id === \"string\"\n ? obj.request_id\n : typeof details?.request_id === \"string\"\n ? details.request_id\n : undefined,\n });\n}\n\n/**\n * Restore the canonical error shape from a durable backend run row.\n *\n * Provider ledgers often keep an aggregate summary plus a more specific first\n * child failure. Prefer that child so a page refresh does not turn a precise\n * streamed failure back into \"1 request failed\".\n */\nexport function parsePersistedBackendError(\n data: unknown,\n requestId = \"\",\n): BackendApiError | null {\n if (!data || typeof data !== \"object\") return null;\n const error = data as Record<string, unknown>;\n const failures = Array.isArray(error.failures) ? error.failures : [];\n const firstSpecific = failures.find(\n (failure): failure is Record<string, unknown> =>\n typeof failure === \"object\" &&\n failure !== null &&\n typeof (failure as Record<string, unknown>).message === \"string\",\n );\n const summary =\n typeof error.message === \"string\"\n ? error.message\n : \"Backend operation failed\";\n const specific =\n typeof firstSpecific?.message === \"string\"\n ? firstSpecific.message\n : summary;\n const detail = specific === summary ? summary : `${summary}: ${specific}`;\n const code = typeof error.type === \"string\" ? error.type : \"internal_error\";\n // A persisted failure may carry a human-facing line the technical detail\n // cannot express — the load-bearing case being a paid result that survived\n // its persistence failure (`WritePreservedError`, D183): \"it broke\" is true\n // and \"your work is preserved and will be recovered\" is what the user needs.\n // `describeBackendFailure` still overrides a TEMPLATED one with the cause.\n const userMessage =\n typeof error.user_message === \"string\" && error.user_message\n ? error.user_message\n : detail;\n return new BackendApiError({\n code,\n detail,\n userMessage,\n details: error,\n requestId,\n });\n}\n\n// ============================================================================\n// FAILURE EXPLANATION — never let a templated non-answer be the whole story\n// ============================================================================\n\n/**\n * Server messages that carry ZERO diagnostic value. The streaming layer\n * (`matrx-connect/streaming/response.py`) emits the first one for every\n * unclassified crash — \"CanonicalGscSync failed unexpectedly. Please try\n * again or adjust your settings.\" — while the REAL cause travels in the same\n * payload's `message`. Treating those as the answer is what makes failures\n * feel secretive.\n */\nconst GENERIC_MESSAGE_PATTERNS: readonly RegExp[] = [\n /failed unexpectedly/i,\n /^\\s*something went wrong/i,\n /please try again(\\s+later)?\\.?\\s*$/i,\n /^\\s*request failed\\b/i,\n /^\\s*unknown (streaming )?error/i,\n /^\\s*internal server error\\.?\\s*$/i,\n];\n\n/** True when a message tells the reader nothing about what actually broke. */\nexport function isGenericUserMessage(\n message: string | null | undefined,\n): boolean {\n const value = (message ?? \"\").trim();\n if (!value) return true;\n return GENERIC_MESSAGE_PATTERNS.some((pattern) => pattern.test(value));\n}\n\nexport interface UpstreamErrorPayload {\n message: string;\n code: string | null;\n userMessage: string | null;\n requestId: string | null;\n status: number | null;\n}\n\n/**\n * Recover an upstream service's structured error that a downstream service\n * stringified into its own message.\n *\n * Real example (scraper wrapping aidream):\n * `aidream could not resolve GSC credential 7223…: HTTP 409 {\"error\":\"conflict\",\n * \"message\":\"Google connection 7223… has no vault credential — it needs\n * re-authentication\",\"user_message\":\"Something went wrong…\",\"request_id\":\"9002…\"}`\n *\n * Without this, the only actionable sentence on the whole hop is invisible.\n */\nexport function unwrapUpstreamError(\n message: string,\n): UpstreamErrorPayload | null {\n const start = message.indexOf(\"{\");\n const end = message.lastIndexOf(\"}\");\n if (start < 0 || end <= start) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(message.slice(start, end + 1));\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const body = parsed as Record<string, unknown>;\n const inner =\n (typeof body.message === \"string\" && body.message) ||\n (typeof body.detail === \"string\" && body.detail) ||\n \"\";\n if (!inner) return null;\n const statusMatch = /\\bHTTP (\\d{3})\\b/.exec(message.slice(0, start));\n return {\n message: inner,\n code:\n (typeof body.error_type === \"string\" && body.error_type) ||\n (typeof body.error === \"string\" && body.error) ||\n null,\n userMessage:\n typeof body.user_message === \"string\" ? body.user_message : null,\n requestId: typeof body.request_id === \"string\" ? body.request_id : null,\n status: statusMatch ? Number(statusMatch[1]) : null,\n };\n}\n\nexport interface BackendFailureExplanation {\n /** Machine code from the deepest layer that classified the failure. */\n code: string;\n /** The most specific human-readable cause available — never a template. */\n cause: string;\n /** What to headline in the UI: the cause when the server was generic. */\n headline: string;\n /** True when every user-facing message the server sent was a template. */\n headlineWasGeneric: boolean;\n /** Message chain, outermost (closest service) first. */\n chain: string[];\n /** Deepest request id available, for cross-service log correlation. */\n requestId: string;\n status: number | null;\n}\n\n/**\n * THE anti-secrecy primitive: turn any thrown backend/stream failure into the\n * most specific explanation the payload can support — unwrapping every nested\n * upstream error and refusing to let a templated `user_message` be the answer.\n *\n * Every surface that reports a backend failure to a human should headline\n * `explanation.headline` and always keep `cause` + `requestId` reachable.\n */\nexport function describeBackendFailure(\n error: unknown,\n): BackendFailureExplanation {\n const chain: string[] = [];\n let code = \"internal_error\";\n let requestId = \"\";\n let status: number | null = null;\n let userFacing: string | null = null;\n\n if (error instanceof BackendApiError) {\n code = error.code;\n requestId = error.requestId;\n status = error.status;\n userFacing = error.userMessage;\n if (error.detail) chain.push(error.detail);\n if (error.userMessage && error.userMessage !== error.detail) {\n chain.push(error.userMessage);\n }\n } else if (error instanceof Error) {\n chain.push(error.message);\n userFacing = error.message;\n } else if (typeof error === \"string\") {\n chain.push(error);\n userFacing = error;\n } else {\n chain.push(\"Unknown error\");\n }\n\n // Walk the nesting: ANY message in the chain may have stringified the\n // service above it (the technical `detail` usually does, the templated\n // `user_message` never does), so every layer gets unwrapped.\n for (let cursor = 0; cursor < chain.length && cursor < 12; cursor += 1) {\n const upstream = unwrapUpstreamError(chain[cursor] ?? \"\");\n if (!upstream || chain.includes(upstream.message)) continue;\n chain.push(upstream.message);\n if (upstream.code) code = upstream.code;\n if (upstream.requestId) requestId = upstream.requestId;\n if (upstream.status !== null) status = upstream.status;\n }\n\n const specific = [...chain]\n .reverse()\n .find((message) => !isGenericUserMessage(message));\n const cause = specific ?? chain[0] ?? \"Unknown error\";\n const headlineWasGeneric = isGenericUserMessage(userFacing);\n return {\n code,\n cause,\n headline: headlineWasGeneric ? cause : (userFacing ?? cause),\n headlineWasGeneric,\n chain,\n requestId,\n status,\n };\n}\n\n/**\n * Extract a user-visible message from any error object.\n * Utility for components that just need the display string.\n */\nexport function getUserMessage(error: unknown): string {\n if (error instanceof BackendApiError) {\n return error.userMessage;\n }\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n // A Redux thunk's `.unwrap()` rejects with a SerializedError — a plain\n // object, not an Error — whose `message` is the real reason.\n if (\n typeof error === \"object\" &&\n error !== null &&\n typeof (error as { message?: unknown }).message === \"string\" &&\n (error as { message: string }).message\n ) {\n return (error as { message: string }).message;\n }\n return \"Something went wrong\";\n}\n\n// ============================================================================\n// INTERNAL HELPERS\n// ============================================================================\n\nfunction statusToCode(status: number): BackendErrorCode {\n switch (status) {\n case 401:\n return \"auth_required\";\n case 403:\n return \"admin_required\";\n case 404:\n return \"not_found\";\n case 422:\n return \"validation_error\";\n default:\n return \"internal_error\";\n }\n}\n","import { isUuidShape } from \"@ai-matrx/kit/uuid\";\n\n/**\n * The fail-closed organization-context kernel — ONE implementation for every\n * Matrx client transport (moved in from matrx-frontend\n * `lib/api/organization-context.ts` verbatim under C22; the host module is now\n * a re-export of this one).\n *\n * Transports resolve their authoritative organization first, then use these\n * functions to bind that exact value without guessing or defaulting. The\n * server never manufactures an org (it 422s a blank one), so the client\n * refuses before the wire: a missing or malformed org id throws\n * `OrganizationContextError` instead of sending a request that cannot succeed.\n */\n\nexport type OrganizationContextErrorCode =\n | \"organization_context_required\"\n | \"organization_context_invalid\"\n | \"organization_context_mismatch\";\n\nexport class OrganizationContextError extends Error {\n readonly code: OrganizationContextErrorCode;\n\n constructor(code: OrganizationContextErrorCode, message: string) {\n super(message);\n this.name = \"OrganizationContextError\";\n this.code = code;\n }\n}\n\ndeclare const organizationOperationBrand: unique symbol;\n\n/** A validated organization operation; construct only through the factory. */\nexport type OrganizationOperation = Readonly<{\n organization_id: string;\n readonly [organizationOperationBrand]: true;\n}>;\n\nexport function createOrganizationOperation(\n organizationId: string,\n): OrganizationOperation {\n const organization_id = requireOrganizationContext(organizationId);\n return Object.freeze({ organization_id }) as OrganizationOperation;\n}\n\nexport function assertOrganizationMatchesOperation(\n operation: OrganizationOperation,\n organizationId: string,\n): void {\n if (operation.organization_id !== requireOrganizationContext(organizationId)) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"Organization ID must match the request operation.\",\n );\n }\n}\n\n/**\n * Normalize and validate the effective organization id for a request. The\n * override (an explicit per-call value) beats the selected context value.\n * Throws `organization_context_required` when neither is present and\n * `organization_context_invalid` when the candidate is not a UUID.\n */\nexport function requireOrganizationContext(\n selectedOrganizationId: string | null | undefined,\n overrideOrganizationId?: string,\n): string {\n const candidate = overrideOrganizationId ?? selectedOrganizationId;\n if (typeof candidate !== \"string\" || candidate.trim().length === 0) {\n throw new OrganizationContextError(\n \"organization_context_required\",\n \"Select an organization before sending this request.\",\n );\n }\n\n const normalized = candidate.trim();\n if (!isUuidShape(normalized)) {\n throw new OrganizationContextError(\n \"organization_context_invalid\",\n \"The selected organization ID is invalid.\",\n );\n }\n return normalized.toLowerCase();\n}\n\n/**\n * Bind `X-Organization-Id` onto a header bag. A pre-existing org header that\n * disagrees with the context org is a `organization_context_mismatch` error —\n * never silently overwritten in either direction.\n */\nexport function applyOrganizationContextHeader(\n headers: Record<string, string>,\n organizationId: string,\n): Record<string, string> {\n const normalizedOrganizationId = requireOrganizationContext(organizationId);\n for (const [name, value] of Object.entries(headers)) {\n if (\n name.toLowerCase() === \"x-organization-id\" &&\n value.trim().toLowerCase() !== normalizedOrganizationId\n ) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"X-Organization-Id must match the request context organization.\",\n );\n }\n }\n const withoutOrganizationHeader = Object.fromEntries(\n Object.entries(headers).filter(\n ([name]) => name.toLowerCase() !== \"x-organization-id\",\n ),\n );\n return {\n ...withoutOrganizationHeader,\n \"X-Organization-Id\": normalizedOrganizationId,\n };\n}\n\n/**\n * Assert that an `organization_id` query parameter (when present) matches the\n * request context organization — the query string must never smuggle a\n * different org past the header binding.\n */\nexport function assertQueryOrganizationMatchesContext(\n queryParams: Record<string, string | number | boolean> | undefined,\n organizationId: string,\n): void {\n if (!queryParams || queryParams.organization_id === undefined) return;\n const queryOrganizationId = requireOrganizationContext(\n String(queryParams.organization_id),\n );\n if (queryOrganizationId !== requireOrganizationContext(organizationId)) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"Query organization_id must match the request context organization.\",\n );\n }\n}\n","/**\n * The AI API protocol-version policy — which version of the AI runtime a\n * Matrx client talks to, and the v2 → v1 transport fallback (moved in from\n * matrx-frontend `lib/api/ai-api-version.ts` + `call-api.ts::\n * fetchWithV2Fallback` under C22; server truth\n * `aidream/docs/runtime/V2_FRONTEND_MIGRATION.md`).\n *\n * Background: the Python backend exposes a `/v2` runtime-spine namespace that\n * wraps the core AI request surfaces in a request-tracking envelope. Body,\n * headers, and streaming response are BYTE-IDENTICAL to v1 — only the URL\n * differs.\n *\n * ─── The one rule that keeps burning us ─────────────────────────────────────\n * `/v2` is inserted at the FRONT of the in-app path, right before `/ai`:\n *\n * /ai/agents/{id} → /v2/ai/agents/{id} ✅ correct\n * /ai/v2/agents/… ❌ WRONG (nested) — 404s\n *\n * `toV2Path` below is the ONLY place this transform is spelled out.\n *\n * ─── Scope: ONLY the covered surfaces have a v2 form ────────────────────────\n * chat, manual, agents/{id}, conversations/{id} (+ singular aliases),\n * prompts/{id}, mandates/{key}. EVERYTHING ELSE — cancel, warm, resume,\n * fork-and-run, runtime operations, files … — has NO v2 route, and the server\n * does NOT auto-downgrade (a `/v2` request to an uncovered surface is a plain\n * 404). So the transform is a scoped allowlist, never a blanket prefix.\n */\n\nimport {\n isNetError,\n resilientFetch,\n type ResilientFetchOptions,\n} from \"@ai-matrx/data/net\";\n\nexport type MatrxAiApiVersion = \"v1\" | \"v2\";\n\n/**\n * The package-wide default AI API version — the production value every Matrx\n * client runs unless its host deliberately overrides\n * (`createMatrxTransport({ aiApiVersion })`).\n */\nexport const MATRX_AI_API_VERSION_DEFAULT: MatrxAiApiVersion = \"v2\";\n\n/**\n * The canonical v1 path TEMPLATES the v2 spine covers, `{param}` placeholders\n * intact — for hosts that route through a template registry.\n */\nexport const V2_COVERED_AI_PATH_TEMPLATES = [\n \"/ai/manual\",\n \"/ai/chat\",\n \"/ai/agents/{agent_id}\",\n \"/ai/agent/{agent_id}\", // singular alias — behaves identically\n \"/ai/conversations/{conversation_id}\",\n \"/ai/conversation/{conversation_id}\", // singular alias\n \"/ai/prompts/{prompt_id}\", // saved-prompt execution\n \"/ai/mandates/{mandate_key}\", // THE MANDATE DOOR — `/v2` sibling exists (routers/v2.py)\n] as const;\n\n/**\n * Insert `/v2` at the front of an in-app path. Idempotent, and prefix-aware:\n * a legacy `/api/` compatibility prefix (stripped server-side) is preserved so\n * `/api/ai/chat` → `/api/v2/ai/chat`.\n */\nexport function toV2Path(path: string): string {\n const withLead = path.startsWith(\"/\") ? path : `/${path}`;\n if (withLead.startsWith(\"/api/\")) {\n const rest = withLead.slice(\"/api\".length); // \"/ai/chat\"\n return rest.startsWith(\"/v2/\") ? withLead : `/api/v2${rest}`;\n }\n return withLead.startsWith(\"/v2/\") ? withLead : `/v2${withLead}`;\n}\n\n// Interpolated (concrete) forms of the covered surfaces — used to guard\n// callers that pass a real path (id already substituted). Anchored so ONLY the\n// exact shapes match: sub-paths like `/ai/agents/{id}/warm`,\n// `/ai/conversations/{id}/resume`, and `/ai/cancel/{id}` deliberately do NOT\n// match and stay on v1. The optional `/api` prefix mirrors `toV2Path`.\nconst COVERED_INTERPOLATED_PATH = [\n /^(?:\\/api)?\\/ai\\/manual$/,\n /^(?:\\/api)?\\/ai\\/chat$/,\n /^(?:\\/api)?\\/ai\\/agents\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/agent\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/conversations\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/conversation\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/prompts\\/[^/]+$/, // `/warm` etc. deliberately don't match\n /^(?:\\/api)?\\/ai\\/mandates\\/[^/]+$/, // the mandate door\n];\n\n/** Whether `path` (already interpolated) is one of the covered surfaces. */\nexport function isCoveredAiPath(path: string): boolean {\n return COVERED_INTERPOLATED_PATH.some((re) => re.test(path));\n}\n\n/**\n * Whether an in-app path (or a full URL whose path was built by `toV2Path`)\n * targets the v2 namespace — the guard the downgrade fallback keys off.\n */\nexport function isV2Path(pathOrUrl: string): boolean {\n return /\\/v2\\/ai\\//.test(pathOrUrl);\n}\n\n/**\n * The inverse of `toV2Path` — strip the `/v2` version segment so a transport\n * failure of the v2 endpoint (network error, 404/405, 5xx BEFORE any stream\n * content) can retry the identical request on the v1 route. Works on a bare\n * path, an `/api`-prefixed path, or a full URL (the `/v2/ai/` shape is unique\n * to the version namespace). Idempotent on non-v2 input.\n */\nexport function toV1FallbackUrl(url: string): string {\n return url.replace(/\\/v2\\/ai\\//, \"/ai/\");\n}\n\n/**\n * Apply the active AI API version to an ALREADY-INTERPOLATED in-app path.\n *\n * - Covered surface + v2 → `/v2` prefix inserted.\n * - Anything else (or v1) → returned unchanged.\n *\n * Pass the in-app PATH only (no scheme/host); prepend the base URL afterward.\n */\nexport function applyAiApiVersion(\n path: string,\n version: MatrxAiApiVersion,\n): string {\n if (version !== \"v2\") return path;\n return isCoveredAiPath(path) ? toV2Path(path) : path;\n}\n\n/** One v2 → v1 downgrade, surfaced to the host's diagnostics sink. */\nexport interface MatrxProtocolDowngrade {\n /** The failed v2 URL. */\n url: string;\n /** Why the downgrade fired (thrown error text, or `HTTP <status>`). */\n reason: string;\n /** HTTP status when the trigger was a response (404/405/5xx). */\n status?: number;\n}\n\nexport interface MatrxProtocolFallbackOptions extends ResilientFetchOptions {\n /**\n * Fired on every downgrade — a sustained stream of these means a v2 surface\n * is unhealthy. The fallback also `console.warn`s unconditionally (parity\n * with the original host pipeline) so the signal never disappears silently.\n */\n onDowngrade?: (downgrade: MatrxProtocolDowngrade) => void;\n}\n\n/**\n * `resilientFetch` with the v2 → v1 transport fallback. Fires ONLY when a\n * `/v2/ai/...` ENDPOINT itself fails — a network-layer throw (non-abort), a\n * 404/405 (surface not on v2), or a 5xx — always BEFORE any stream content is\n * consumed. Never on an application error (those fail identically on v1), and\n * never on a caller abort (a user cancel must not be logged as a downgrade —\n * that poisons the exact telemetry the rollout reads to judge v2 health).\n */\nexport async function fetchWithMatrxProtocolFallback(\n url: string,\n init: RequestInit,\n opts: MatrxProtocolFallbackOptions = {},\n): Promise<{ response: Response }> {\n const { onDowngrade, ...fetchOpts } = opts;\n const logDowngrade = (reason: string, status?: number) => {\n console.warn(\n `[matrx] ai_v2_downgrade → retrying on v1. v2=${url} reason=${reason}`,\n );\n onDowngrade?.({ url, reason, ...(status !== undefined ? { status } : {}) });\n };\n if (!isV2Path(url)) return resilientFetch(url, init, fetchOpts);\n let result: { response: Response };\n try {\n result = await resilientFetch(url, init, fetchOpts);\n } catch (err) {\n // resilientFetch normalizes a caller-signal abort to NetError code\n // \"aborted\" (AbortedError); a raw AbortError is checked too for safety.\n const isAbort =\n (isNetError(err) && err.code === \"aborted\") ||\n (err instanceof Error && err.name === \"AbortError\");\n if (isAbort) throw err;\n logDowngrade(String(err));\n return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);\n }\n const status = result.response.status;\n if (status === 404 || status === 405 || status >= 500) {\n logDowngrade(`HTTP ${status}`, status);\n return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);\n }\n return result;\n}\n\n/**\n * The exact-match `pathOverrides` map for `resolveEndpointPath` — keyed on the\n * canonical templates, valued at their `/v2` siblings. Empty for v1. Every call\n * that flows through the endpoint-override registry picks up v2 for the covered\n * surfaces — and ONLY the covered surfaces. (Moved from matrx-frontend\n * `lib/api/ai-api-version.ts`, chat-package independence P9.)\n */\nexport function aiVersionPathOverrides(\n version: MatrxAiApiVersion,\n): Record<string, string> {\n if (version !== \"v2\") return {};\n const map: Record<string, string> = {};\n for (const template of V2_COVERED_AI_PATH_TEMPLATES) {\n map[template] = toV2Path(template);\n }\n return map;\n}\n","/**\n * `@ai-matrx/agents/matrx` — the Matrx transport port.\n *\n * The ONE seam between this package's wire semantics and a host's connection\n * policy. This package owns WHAT is said to the AI Matrx server — paths,\n * methods, JSON bodies, streaming Accept headers, the `Last-Event-ID` cursor\n * header — and the host owns HOW the connection is made:\n *\n * - base-URL / backend-channel resolution (global, sandbox override, local\n * engine, EC2-dedicated — whatever ladder the host runs);\n * - credentials (Supabase JWT `Authorization: Bearer`, guest\n * `X-Fingerprint-ID`, or the API-key lane) — NEVER implemented here;\n * - the `X-Organization-Id` context header;\n * - retry policy, network-level timeouts, and diagnostics capture.\n *\n * A host implements the port in a few lines:\n *\n * ```ts\n * const transport: MatrxTransport = {\n * fetch: (path, init) =>\n * fetch(`${baseUrl}${path}`, {\n * ...init,\n * headers: { ...init.headers, ...authHeaders() },\n * }),\n * };\n * ```\n *\n * The port is deliberately structural and `fetch`-shaped so `@ai-matrx/data`'s\n * `/api` transport can implement it without importing this package.\n */\n\n/**\n * The request this package hands the port. A strict subset of `RequestInit`,\n * so a host can spread it straight into `fetch`.\n */\nexport interface MatrxTransportRequest {\n method: \"GET\" | \"POST\";\n /**\n * Wire-semantic headers the CALL requires (`Content-Type`, `Accept`,\n * `Last-Event-ID`). The host merges its policy headers (auth, org) on top;\n * it must not drop these.\n */\n headers: Record<string, string>;\n /** Pre-serialized JSON body, present on POST calls that carry one. */\n body?: string;\n /** Caller cancellation. The host must wire it to the underlying fetch. */\n signal?: AbortSignal;\n}\n\n/**\n * The transport port. `path` is server-relative and always starts with `/`\n * (`/ai/...`, `/runtime/...`); the host prepends its resolved base URL.\n */\nexport interface MatrxTransport {\n fetch(path: string, init: MatrxTransportRequest): Promise<Response>;\n}\n\n/**\n * A non-2xx response from the Matrx API, with the server's structured error\n * body preserved and its richest human-readable message extracted.\n */\nexport class MatrxApiError extends Error {\n override readonly name = \"MatrxApiError\";\n /** HTTP status of the failed response. */\n readonly status: number;\n /** Machine code from the server body (`code`, or `detail.code`), when present. */\n readonly code: string | null;\n /** The parsed server error body, verbatim (undefined when unparsable). */\n readonly serverDetail: unknown;\n /** The request path the failure came from (server-relative). */\n readonly path: string;\n\n constructor(args: {\n status: number;\n path: string;\n serverDetail?: unknown;\n message?: string;\n }) {\n super(\n args.message ??\n extractMatrxErrorMessage(args.serverDetail) ??\n `HTTP ${args.status}`,\n );\n this.status = args.status;\n this.path = args.path;\n this.serverDetail = args.serverDetail;\n this.code = extractMatrxErrorCode(args.serverDetail);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction nonBlankString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\n/**\n * Extract the richest human-readable message from a Matrx/FastAPI error body.\n *\n * aidream 4xx validation errors look like\n * `{ error, user_message, details: [{ field, message, help }] }`; hand-raised\n * HTTPExceptions carry `{ detail: { code, message } }`; FastAPI's defaults are\n * `{ detail: string | [{ msg }] }`. Preference order: `user_message` →\n * `message` → joined `details[].message` → `detail.message` →\n * `detail` string → joined `detail[].msg`. Returns undefined for\n * unrecognized bodies so callers fall back to the bare status line.\n */\nexport function extractMatrxErrorMessage(\n serverDetail: unknown,\n): string | undefined {\n if (!isRecord(serverDetail)) return undefined;\n\n const userMessage = nonBlankString(serverDetail.user_message);\n if (userMessage) return userMessage;\n const message = nonBlankString(serverDetail.message);\n if (message) return message;\n\n if (Array.isArray(serverDetail.details)) {\n const messages = serverDetail.details\n .map((entry: unknown) => {\n if (!isRecord(entry)) return undefined;\n const detailMessage = nonBlankString(entry.message);\n if (!detailMessage) return undefined;\n const field = nonBlankString(entry.field);\n return field ? `${field}: ${detailMessage}` : detailMessage;\n })\n .filter((m): m is string => typeof m === \"string\");\n if (messages.length > 0) return messages.join(\"; \");\n }\n\n const detail = serverDetail.detail;\n if (isRecord(detail)) {\n const detailMessage =\n nonBlankString(detail.message) ?? nonBlankString(detail.user_message);\n if (detailMessage) return detailMessage;\n }\n if (typeof detail === \"string\" && detail.trim()) return detail;\n if (Array.isArray(detail)) {\n const messages = detail\n .map((entry: unknown) =>\n isRecord(entry) ? nonBlankString(entry.msg) : undefined,\n )\n .filter((m): m is string => typeof m === \"string\");\n if (messages.length > 0) return messages.join(\"; \");\n }\n return undefined;\n}\n\n/**\n * Extract the machine error code from a Matrx error body: top-level `code`,\n * else `detail.code` (the hand-raised HTTPException shape). Null when absent.\n */\nexport function extractMatrxErrorCode(serverDetail: unknown): string | null {\n if (!isRecord(serverDetail)) return null;\n const topLevel = nonBlankString(serverDetail.code);\n if (topLevel) return topLevel;\n const detail = serverDetail.detail;\n if (isRecord(detail)) {\n const nested = nonBlankString(detail.code);\n if (nested) return nested;\n }\n return null;\n}\n","/**\n * `@ai-matrx/agents/matrx` — THE request pipeline every typed Matrx server call\n * rides (chat package independence P9b). One implementation for every client:\n * matrx-frontend's `lib/api` `callApi` and the chat package's bare-host default\n * both build on it, and supply only their host facts — where the server is,\n * which credential and organization ride, and where diagnostics go.\n *\n * What lives here (pure, no host/window/env read):\n *\n * - `buildMatrxRequestUrl` — path params, the legacy `/api` strip, the query;\n * - `buildMatrxRequestBody` — scope injection (`organization_id` /\n * `project_id` / `task_id`), the UI-only field strip, and the fail-closed\n * body-vs-context organization check;\n * - `bareStatusSentence` / `isBareTransportCode` / `honestTransportMessage` —\n * a bare status code is never a sentence at a person;\n * - `parseMatrxNdjsonResponse` — a response body as typed envelopes, a broken\n * body reader classified as a resumable `StreamTransportError`;\n * - `executeMatrxCall` — the JSON or NDJSON execution over the v2 → v1\n * protocol fallback, with the stream callbacks and `consumeStream`;\n * - `sendMatrxRequest` / `readMatrxJsonResponse` — the raw-`Response` lane\n * for a host's imperative client (multipart bodies, byte downloads,\n * response-aware callers): one deadline, a caller abort kept as itself, and\n * the ONE HTTP error parser;\n * - `buildSafeRequestLog` / `redactUrlForRequestLog` /\n * `shouldReportMatrxCallError` — the log and capture policy.\n *\n * The error envelope (`MatrxCallError`) and its ONE classifier\n * (`normalizeMatrxError`) live in `./client`.\n */\n\nimport {\n readMatrxNdjsonStream,\n type MatrxNdjsonIssue,\n type MatrxStreamEnvelope,\n} from \"../stream/ndjson\";\nimport {\n BackendApiError,\n parseHttpError,\n StreamTransportError,\n} from \"./backend-errors\";\nimport type { MatrxCallError } from \"./client\";\nimport { OrganizationContextError } from \"./org-context\";\nimport {\n fetchWithMatrxProtocolFallback,\n type MatrxProtocolDowngrade,\n} from \"./protocol\";\nimport { extractMatrxErrorMessage } from \"./transport\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type MatrxHttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n\n/**\n * Every context dimension a call may carry. `organization_id`, `project_id`\n * and `task_id` are injected into the body; `user_id` rides the credential and\n * `conversation_id` the path or an explicit body field — never injected.\n */\nexport interface MatrxCallScope {\n user_id?: string;\n organization_id?: string;\n project_id?: string;\n task_id?: string;\n conversation_id?: string;\n}\n\nexport interface MatrxCallResult<T = unknown> {\n /** Parsed JSON response body (non-streaming calls only). */\n data?: T;\n /** Server-assigned request id (response header). */\n requestId?: string;\n /** Server-assigned conversation id (response header). */\n conversationId?: string;\n /** Set when the call failed with an HTTP error response. */\n error?: MatrxCallError;\n}\n\ntype MatrxQueryScalar = string | number | boolean;\n\n/**\n * Query values: a scalar is sent as itself, an array repeats the key (the\n * FastAPI repeatable-parameter convention), `null` / `undefined` are omitted.\n */\nexport type MatrxQueryParams = Record<\n string,\n MatrxQueryScalar | readonly MatrxQueryScalar[] | null | undefined\n>;\n\n// ─── URL ────────────────────────────────────────────────────────────────────\n\n/**\n * The full URL for one call: `{param}` segments substituted (encoded), the\n * legacy `/api` prefix stripped (server routes no longer live under it), and\n * the query appended.\n */\nexport function buildMatrxRequestUrl(\n baseUrl: string,\n pathTemplate: string,\n pathParams?: Record<string, string>,\n queryParams?: MatrxQueryParams,\n): string {\n let resolvedPath = pathTemplate;\n if (pathParams) {\n for (const [key, value] of Object.entries(pathParams)) {\n resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(value));\n }\n }\n const fullPath = resolvedPath.startsWith(\"/api/\")\n ? resolvedPath.slice(4)\n : resolvedPath;\n const url = `${baseUrl}${fullPath}`;\n if (queryParams) {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(queryParams)) {\n if (value === null || value === undefined) continue;\n const values: readonly MatrxQueryScalar[] = Array.isArray(value)\n ? value\n : [value as MatrxQueryScalar];\n for (const entry of values) search.append(key, String(entry));\n }\n const qs = search.toString();\n if (qs) return `${url}${url.includes(\"?\") ? \"&\" : \"?\"}${qs}`;\n }\n return url;\n}\n\n// ─── Body ───────────────────────────────────────────────────────────────────\n\n/**\n * Client capability flags that must never reach the server — the server's\n * request schemas reject them.\n */\nexport const MATRX_UI_ONLY_BODY_FIELDS: ReadonlySet<string> = new Set([\n \"youtube_videos\",\n \"file_urls\",\n \"image_urls\",\n]);\n\n/**\n * The final request body: UI-only fields stripped, scope fields injected.\n *\n * A caller's `organization_id: null` (or blank) means \"I have none of my own\",\n * never \"send this for a different organization\" — it is dropped and the\n * scope's organization injected. A real value that disagrees with the scope\n * is refused (`organization_context_mismatch`). With no scope organization\n * (the org-less guest lane, an org-free read) nothing is injected for it.\n * Other scope fields keep caller-wins behaviour.\n */\nexport function buildMatrxRequestBody(\n body: unknown,\n scope: MatrxCallScope,\n): Record<string, unknown> {\n // MATRX-EXCEPTION: `body` is optional by design — a caller with no body\n // still gets scope fields injected, so `{}` is the correct start.\n const raw = (body ?? {}) as Record<string, unknown>;\n const base: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!MATRX_UI_ONLY_BODY_FIELDS.has(key)) base[key] = value;\n }\n\n if (\n base.organization_id === null ||\n (typeof base.organization_id === \"string\" &&\n base.organization_id.trim() === \"\")\n ) {\n delete base.organization_id;\n }\n\n const bodyOrganizationId = base.organization_id;\n if (\n scope.organization_id !== undefined &&\n bodyOrganizationId !== undefined &&\n (typeof bodyOrganizationId !== \"string\" ||\n bodyOrganizationId.trim() !== scope.organization_id)\n ) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"Request body organization_id must match the request context organization.\",\n );\n }\n if (bodyOrganizationId !== undefined && scope.organization_id !== undefined) {\n base.organization_id = scope.organization_id;\n }\n\n const scopeFields: Record<string, unknown> = {\n ...(scope.organization_id !== undefined\n ? { organization_id: scope.organization_id }\n : {}),\n };\n if (scope.project_id !== undefined) scopeFields.project_id = scope.project_id;\n if (scope.task_id !== undefined) scopeFields.task_id = scope.task_id;\n return { ...scopeFields, ...base };\n}\n\n// ─── Honest status sentences ────────────────────────────────────────────────\n\nconst BARE_TRANSPORT_CODE =\n /^\\s*(?:HTTP|HTTP\\s*Error|Status(?:\\s*Code)?)?\\s*[:\\-]?\\s*\\d{3}\\s*[.:!]?\\s*$/i;\n\n/** True when a candidate sentence is really just the status line wearing words. */\nexport function isBareTransportCode(text: string | null | undefined): boolean {\n if (!text) return false;\n return BARE_TRANSPORT_CODE.test(text);\n}\n\n/**\n * A BARE STATUS CODE IS NEVER A SENTENCE. What a person reads when the server\n * answered an error with no readable message. The status itself rides\n * `error.status`, which is what code branches on — this is only the words.\n */\nexport function bareStatusSentence(status: number): string {\n if (status === 401 || status === 403) {\n return \"The server would not let this request through — your session may have expired, or this account may not have access here. Sign in again, and if it repeats, ask an administrator.\";\n }\n if (status === 404) {\n return \"The server has nothing at that address. Reload the page; if it repeats, report it — a client asking for something that no longer exists is a defect, not your mistake.\";\n }\n if (status === 429) {\n return \"The server is rate-limiting this request. Wait a few seconds and try again.\";\n }\n if (status >= 500) {\n return \"The server failed while answering this, and sent no explanation. Try again in a moment; if it repeats, report it with the request id above.\";\n }\n return \"The server refused this request and sent no reason with it — the missing reason is itself a defect worth reporting. Reload the page and try once more; if it repeats, report it with the request id above.\";\n}\n\n/** `raw` unless it is empty or only a status line; then the status sentence. */\nexport function honestTransportMessage(\n raw: string,\n status: number | undefined,\n): string {\n if (raw && !isBareTransportCode(raw)) return raw;\n return bareStatusSentence(status ?? 0);\n}\n\n// ─── Log + capture policy ───────────────────────────────────────────────────\n\nconst SENSITIVE_HEADER_NAME = /authorization|cookie|token|api[-_]?key/i;\n\n/** Request metadata safe to log: secret headers redacted, the body as its shape only. */\nexport function buildSafeRequestLog(\n headers: Record<string, string>,\n body: unknown,\n): { headers: Record<string, string>; body: Record<string, unknown> } {\n const safeHeaders = Object.fromEntries(\n Object.entries(headers).map(([name, value]) => [\n name,\n SENSITIVE_HEADER_NAME.test(name) ? \"[REDACTED]\" : value,\n ]),\n );\n const bodyMetadata: Record<string, unknown> = Array.isArray(body)\n ? { type: \"array\", itemCount: body.length }\n : body && typeof body === \"object\"\n ? { type: \"object\", keys: Object.keys(body as Record<string, unknown>) }\n : { type: body === null ? \"null\" : typeof body };\n return { headers: safeHeaders, body: bodyMetadata };\n}\n\n/** The URL with every query value redacted. */\nexport function redactUrlForRequestLog(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of parsed.searchParams.keys()) {\n parsed.searchParams.set(key, \"[REDACTED]\");\n }\n return parsed.toString();\n } catch {\n const queryIndex = url.indexOf(\"?\");\n return queryIndex === -1 ? url : `${url.slice(0, queryIndex)}?[REDACTED]`;\n }\n}\n\n/** False only for an HTTP status the call site declared an expected outcome. */\nexport function shouldReportMatrxCallError(\n status: number | null | undefined,\n expectedErrorStatuses: readonly number[] | undefined,\n): boolean {\n return status == null || !expectedErrorStatuses?.includes(status);\n}\n\n// ─── Stream parsing ─────────────────────────────────────────────────────────\n\nexport interface MatrxStreamIds {\n requestId: string | null;\n conversationId: string | null;\n}\n\nexport interface MatrxStreamParse<E = MatrxStreamEnvelope> extends MatrxStreamIds {\n events: AsyncGenerator<E, void, undefined>;\n}\n\nexport interface ParseMatrxNdjsonResponseHooks {\n /** Every envelope, before the consumer sees it. */\n onEvent?: (event: MatrxStreamEnvelope, ids: MatrxStreamIds) => void;\n /** A broken body reader, already classified, before it is thrown. */\n onTransportError?: (error: BackendApiError, ids: MatrxStreamIds) => void;\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n onUnknownEnvelope?: (value: unknown) => void;\n}\n\n/**\n * A Matrx NDJSON response as typed envelopes. The ids come from headers, so\n * they are available before any event. A body that breaks mid-run is a\n * TRANSPORT loss (the run may still finish server-side and is reattachable) —\n * thrown as `StreamTransportError`, never a failed run; an abort ends quietly.\n */\nexport function parseMatrxNdjsonResponse(\n response: Response,\n signal?: AbortSignal,\n hooks: ParseMatrxNdjsonResponseHooks = {},\n): MatrxStreamParse {\n const ids: MatrxStreamIds = {\n requestId: response.headers.get(\"X-Request-ID\"),\n conversationId: response.headers.get(\"X-Conversation-ID\"),\n };\n async function* events(): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n if (!response.body) {\n throw new BackendApiError({\n code: \"internal_error\",\n detail: \"Response has no body\",\n userMessage: \"No response received from server\",\n });\n }\n try {\n for await (const envelope of readMatrxNdjsonStream(response.body, {\n ...(signal ? { signal } : {}),\n ...(hooks.onMalformedLine ? { onMalformedLine: hooks.onMalformedLine } : {}),\n ...(hooks.onUnknownEnvelope ? { onUnknownEnvelope: hooks.onUnknownEnvelope } : {}),\n })) {\n hooks.onEvent?.(envelope, ids);\n yield envelope;\n }\n } catch (error) {\n if (signal?.aborted || (error instanceof Error && error.name === \"AbortError\")) {\n return;\n }\n const transportError =\n error instanceof BackendApiError\n ? error\n : new StreamTransportError({\n detail:\n error instanceof Error\n ? error.message\n : \"The response stream ended unexpectedly.\",\n details: error,\n ...(ids.requestId ? { requestId: ids.requestId } : {}),\n });\n hooks.onTransportError?.(transportError, ids);\n throw transportError;\n }\n }\n return { events: events(), ...ids };\n}\n\n// ─── Execution ──────────────────────────────────────────────────────────────\n\nexport interface ExecuteMatrxCallRequest<E = MatrxStreamEnvelope> {\n /** The final URL (`buildMatrxRequestUrl`). */\n url: string;\n method: string;\n /** Every header the host binds (credential, organization, Content-Type). */\n headers: Record<string, string>;\n /** The assembled body (`buildMatrxRequestBody`); never sent on GET/HEAD. */\n body: unknown;\n /** NDJSON streaming call. */\n stream?: boolean;\n signal?: AbortSignal;\n /** Time to response headers. Default 15_000. */\n connectTimeoutMs?: number;\n /** Whole-request cap for JSON calls. Default 30_000; `null` uncaps. Streams are uncapped. */\n totalTimeoutMs?: number | null;\n /** Fires when headers arrive, before any event. */\n onStreamStart?: (requestId: string | null, conversationId: string | null) => void;\n onStreamEvent?: (event: E) => void;\n /**\n * Take ownership of the body instead of the executor draining it (a body is\n * consumed once). `onStreamEvent` is then not called; start / complete /\n * error still fire.\n */\n consumeStream?: (response: Response, ids: MatrxStreamIds) => Promise<void>;\n onStreamComplete?: (requestId: string | null, conversationId: string | null) => void;\n /** Fires for an HTTP error response on a stream (thrown failures are the caller's). */\n onStreamError?: (error: MatrxCallError) => void;\n}\n\nexport interface ExecuteMatrxCallHooks<E = MatrxStreamEnvelope> {\n /** Every v2 → v1 protocol downgrade. */\n onProtocolDowngrade?: (downgrade: MatrxProtocolDowngrade) => void;\n /** The host's stream parser (default `parseMatrxNdjsonResponse`). */\n parseStream?: (response: Response, signal?: AbortSignal) => MatrxStreamParse<E>;\n}\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 15_000;\nconst DEFAULT_JSON_TOTAL_TIMEOUT_MS = 30_000;\n\nasync function httpErrorFrom(response: Response): Promise<MatrxCallError> {\n const serverDetail: unknown = await response.json().catch(() => undefined);\n return {\n type:\n response.status >= 400 && response.status < 500\n ? \"validation_error\"\n : \"http_error\",\n message:\n extractMatrxErrorMessage(serverDetail) ?? bareStatusSentence(response.status),\n status: response.status,\n serverDetail,\n };\n}\n\n/**\n * Execute one call over the v2 → v1 protocol fallback. An HTTP error\n * response resolves as `{ error }`; a thrown failure (network, timeout,\n * abort, a broken stream) propagates — the caller normalizes it with\n * `normalizeMatrxError` and decides what to capture.\n */\nexport async function executeMatrxCall<T = unknown, E = MatrxStreamEnvelope>(\n request: ExecuteMatrxCallRequest<E>,\n hooks: ExecuteMatrxCallHooks<E> = {},\n): Promise<MatrxCallResult<T>> {\n const upper = request.method.toUpperCase();\n const sendsBody = request.stream || (upper !== \"GET\" && upper !== \"HEAD\");\n const { response } = await fetchWithMatrxProtocolFallback(\n request.url,\n {\n method: request.method,\n headers: request.headers,\n ...(sendsBody ? { body: JSON.stringify(request.body) } : {}),\n },\n {\n ...(request.signal ? { signal: request.signal } : {}),\n connectTimeoutMs: request.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n totalTimeoutMs: request.stream\n ? null\n : request.totalTimeoutMs === undefined\n ? DEFAULT_JSON_TOTAL_TIMEOUT_MS\n : request.totalTimeoutMs,\n throwOnHttpError: false,\n ...(hooks.onProtocolDowngrade ? { onDowngrade: hooks.onProtocolDowngrade } : {}),\n },\n );\n\n const requestId = response.headers.get(\"X-Request-ID\");\n const conversationId = response.headers.get(\"X-Conversation-ID\");\n const idFields = {\n ...(requestId !== null ? { requestId } : {}),\n ...(conversationId !== null ? { conversationId } : {}),\n };\n\n if (!response.ok) {\n const error = await httpErrorFrom(response);\n if (request.stream) request.onStreamError?.(error);\n return { ...idFields, error };\n }\n\n if (!request.stream) {\n const data = (response.status === 204 ? undefined : await response.json()) as T;\n return { data, ...idFields };\n }\n\n if (request.consumeStream) {\n request.onStreamStart?.(requestId, conversationId);\n await request.consumeStream(response, { requestId, conversationId });\n request.onStreamComplete?.(requestId, conversationId);\n return idFields;\n }\n\n const parse =\n hooks.parseStream ??\n ((res: Response, signal?: AbortSignal) =>\n parseMatrxNdjsonResponse(res, signal) as unknown as MatrxStreamParse<E>);\n const parsed = parse(response, request.signal);\n request.onStreamStart?.(parsed.requestId, parsed.conversationId);\n for await (const event of parsed.events) {\n request.onStreamEvent?.(event);\n }\n request.onStreamComplete?.(parsed.requestId, parsed.conversationId);\n return {\n ...(parsed.requestId !== null ? { requestId: parsed.requestId } : {}),\n ...(parsed.conversationId !== null ? { conversationId: parsed.conversationId } : {}),\n };\n}\n\n// ─── Raw send ───────────────────────────────────────────────────────────────\n\nexport interface SendMatrxRequestOptions {\n /** Caller cancellation; an abort propagates as the caller's own AbortError. */\n signal?: AbortSignal;\n /**\n * Deadline (ms) from send to response headers. On expiry the call rejects\n * with a `BackendApiError` coded `request_timeout` (status 504) — never a\n * fake caller cancellation. Omit for no deadline.\n */\n timeoutMs?: number;\n}\n\n/**\n * Send one request and resolve with the raw `Response`, whatever its status.\n * The lane for an imperative host client whose bodies are not JSON (a\n * `FormData` upload), whose answers are bytes, or which reads the response\n * itself. A caller abort and a network failure propagate unchanged; only this\n * call's own deadline is classified (`request_timeout`).\n */\nexport async function sendMatrxRequest(\n url: string,\n init: RequestInit = {},\n options: SendMatrxRequestOptions = {},\n): Promise<Response> {\n const callerSignal = options.signal ?? init.signal ?? undefined;\n if (options.timeoutMs === undefined) {\n return fetch(url, { ...init, ...(callerSignal ? { signal: callerSignal } : {}) });\n }\n const controller = new AbortController();\n const onCallerAbort = (): void => controller.abort(callerSignal?.reason);\n if (callerSignal) {\n if (callerSignal.aborted) controller.abort(callerSignal.reason);\n else callerSignal.addEventListener(\"abort\", onCallerAbort, { once: true });\n }\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, options.timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (error) {\n if (timedOut) {\n throw new BackendApiError({\n code: \"request_timeout\",\n detail: `${init.method ?? \"GET\"} ${url} exceeded ${options.timeoutMs}ms`,\n userMessage: \"The request timed out — please retry.\",\n status: 504,\n });\n }\n throw error;\n } finally {\n clearTimeout(timer);\n callerSignal?.removeEventListener(\"abort\", onCallerAbort);\n }\n}\n\n/**\n * A response's JSON body, or the ONE classified `BackendApiError`\n * (`parseHttpError`) for a non-2xx answer. A 204 resolves `null`.\n */\nexport async function readMatrxJsonResponse<T>(response: Response): Promise<T> {\n if (!response.ok) throw await parseHttpError(response);\n if (response.status === 204) return null as T;\n return (await response.json()) as T;\n}\n","/**\n * Internal request plumbing for `@ai-matrx/agents/matrx`. Not part of the\n * public surface — `matrx/index.ts` deliberately does not re-export this\n * module. Everything here is pure: no globals, no work at import time.\n */\n\nimport {\n readMatrxNdjsonStream,\n type MatrxNdjsonIssue,\n type MatrxStreamEnvelope,\n type MatrxStreamEnvelopeObservation,\n} from \"../stream/ndjson\";\nimport { MatrxApiError, type MatrxTransport } from \"./transport\";\n\n/** Encode one path segment (an id) safely into a server-relative path. */\nexport function encodePathSegment(value: string): string {\n return encodeURIComponent(value);\n}\n\nexport type QueryValue =\n | string\n | number\n | boolean\n | readonly string[]\n | undefined;\n\n/**\n * Build a query string. Array values repeat the key (`kind=a&kind=b` — the\n * FastAPI repeatable-parameter convention); undefined values are omitted.\n * Returns \"\" or a string starting with \"?\".\n */\nexport function buildQuery(params: Record<string, QueryValue>): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const entry of value) search.append(key, entry);\n } else {\n search.append(key, String(value));\n }\n }\n const encoded = search.toString();\n return encoded ? `?${encoded}` : \"\";\n}\n\nasync function readServerDetail(response: Response): Promise<unknown> {\n try {\n return (await response.json()) as unknown;\n } catch {\n return undefined;\n }\n}\n\nasync function throwApiError(path: string, response: Response): Promise<never> {\n throw new MatrxApiError({\n status: response.status,\n path,\n serverDetail: await readServerDetail(response),\n });\n}\n\nexport interface JsonRequestOptions {\n method: \"GET\" | \"POST\";\n body?: unknown;\n signal?: AbortSignal;\n}\n\n/**\n * Execute a JSON request through the transport. Throws `MatrxApiError` on a\n * non-2xx response; resolves with the parsed JSON body otherwise.\n */\nexport async function requestJson<T>(\n transport: MatrxTransport,\n path: string,\n options: JsonRequestOptions,\n): Promise<T> {\n const hasBody = options.method !== \"GET\" && options.body !== undefined;\n const response = await transport.fetch(path, {\n method: options.method,\n headers: hasBody ? { \"Content-Type\": \"application/json\" } : {},\n ...(hasBody ? { body: JSON.stringify(options.body) } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) return throwApiError(path, response);\n return (await response.json()) as T;\n}\n\n/**\n * Options for every streaming call, riding the NDJSON kernel's contract.\n * Public via `./run`'s re-export.\n */\nexport interface MatrxStreamCallOptions {\n /** Abort the fetch and end the events iterator. */\n signal?: AbortSignal;\n /** Bounded background read-ahead (see `stream/ndjson`). */\n maxReadAhead?: number;\n /** Malformed NDJSON is non-fatal but must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx envelope. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope in its exact wire form. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\n/**\n * A live agent run: the server-assigned ids (from response headers, available\n * BEFORE any event) and the normalized event stream. Public via `./run`.\n */\nexport interface MatrxRunHandle {\n /** `X-Request-ID` — the ONLY id `POST /ai/cancel/{request_id}` accepts. */\n requestId: string | null;\n /** `X-Conversation-ID` — the server's conversation identity. */\n conversationId: string | null;\n /** Normalized `{event, data}` envelopes through the ONE wire kernel. */\n events: AsyncGenerator<MatrxStreamEnvelope, void, undefined>;\n /** The raw response, for hosts that need headers/status beyond the ids. */\n response: Response;\n}\n\n/**\n * Wrap a validated streaming Response into the run handle — the ONE place\n * the id headers are read and the NDJSON kernel is attached (`./run` and\n * `./operations`' rejoin share it).\n */\nexport function toRunHandle(\n response: Response,\n options: MatrxStreamCallOptions,\n): MatrxRunHandle {\n return {\n requestId: response.headers.get(\"X-Request-ID\"),\n conversationId: response.headers.get(\"X-Conversation-ID\"),\n events: readMatrxNdjsonStream(response.body as ReadableStream<Uint8Array>, {\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.maxReadAhead !== undefined\n ? { maxReadAhead: options.maxReadAhead }\n : {}),\n ...(options.onMalformedLine\n ? { onMalformedLine: options.onMalformedLine }\n : {}),\n ...(options.onUnknownEnvelope\n ? { onUnknownEnvelope: options.onUnknownEnvelope }\n : {}),\n ...(options.onValidEnvelope\n ? { onValidEnvelope: options.onValidEnvelope }\n : {}),\n }),\n response,\n };\n}\n\nexport interface StreamRequestOptions {\n method: \"GET\" | \"POST\";\n body?: unknown;\n /** Extra wire-semantic headers (`Accept`, `Last-Event-ID`). */\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\n/**\n * Execute a streaming request. Throws `MatrxApiError` on a non-2xx response\n * (reading the error body as JSON when possible) or when a 2xx response\n * carries no body; resolves with the validated `Response` otherwise.\n */\nexport async function requestStream(\n transport: MatrxTransport,\n path: string,\n options: StreamRequestOptions,\n): Promise<Response> {\n const hasBody = options.method !== \"GET\" && options.body !== undefined;\n const response = await transport.fetch(path, {\n method: options.method,\n headers: {\n ...(hasBody ? { \"Content-Type\": \"application/json\" } : {}),\n ...options.headers,\n },\n ...(hasBody ? { body: JSON.stringify(options.body) } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) return throwApiError(path, response);\n if (!response.body) {\n throw new MatrxApiError({\n status: response.status,\n path,\n serverDetail: { code: \"missing_response_body\" },\n message: \"The streaming response carried no body.\",\n });\n }\n return response;\n}\n","/**\n * `@ai-matrx/agents/portable/mcp` — an agent's server tools as an MCP server.\n *\n * `createAgentToolsMcpHandler` answers MCP JSON-RPC (`initialize`,\n * `tools/list`, `tools/call`, `ping`) for ONE agent. `tools/list` is the\n * bundle's server tools (resolved by the realtime-tools funnel on the server);\n * `tools/call` runs the tool through `POST /ai/tools/execute`, which\n * re-resolves the agent's set and refuses anything outside it, and writes the\n * same `cx_tool_call` row a turn-based call writes.\n *\n * Every call reads the credential FRESH from the `CredentialsPort` and carries\n * `X-Organization-Id` — nothing is cached, and the token never leaves this\n * process (the CLI only ever sees a URL). Signed out, a call answers one plain\n * sentence instead of failing obscurely.\n *\n * Transport-neutral: the host feeds it the parsed JSON body of each HTTP POST\n * (MCP \"streamable HTTP\") and writes back what it returns. Pure: no Node, no\n * React, no I/O at import.\n */\n\nimport type { CredentialsPort } from \"@ai-matrx/data\";\n\nimport { createMatrxTransport } from \"../matrx/client\";\nimport { requestJson } from \"../matrx/internal\";\nimport type { MatrxTransport } from \"../matrx/transport\";\nimport { MatrxApiError } from \"../matrx/transport\";\nimport type { JsonValue, PortableAgentBundle, PortableTool } from \"./types.generated\";\n\n/** The MCP protocol revisions this handler speaks, newest first. */\nexport const AGENT_TOOLS_MCP_PROTOCOL_VERSIONS = [\"2025-06-18\", \"2025-03-26\", \"2024-11-05\"] as const;\n\n/** The server name the CLIs see (`mcp__matrx-agent__<tool>` in Claude Code). */\nexport const AGENT_TOOLS_MCP_SERVER_NAME = \"matrx-agent\";\n\n/** What a signed-out tool call answers. */\nexport const SIGNED_OUT_TOOL_MESSAGE = \"Sign in to AI Matrx to use this tool.\";\n/** What a call with no organization answers. */\nexport const NO_ORGANIZATION_TOOL_MESSAGE = \"Pick an organization in AI Matrx to use this tool.\";\n\nexport interface AgentToolsMcpOptions {\n /** The bundle the session loaded — its `tools`, agent/version identity and `surface`. */\n bundle: Pick<PortableAgentBundle, \"agent_id\" | \"version_id\" | \"name\" | \"tools\" | \"surface\">;\n /** The credential source, read fresh per call. `null`/absent = signed out. */\n credentials?: CredentialsPort | null;\n /** The organization every call runs in (value, or a per-call getter that may be async). */\n organizationId?: string | null | (() => string | null | undefined | Promise<string | null | undefined>);\n /** AI Matrx server base URL, no trailing slash. */\n baseUrl: string;\n /**\n * One conversation id for the whole coding session, so its tool calls group\n * under one `cx_conversation`. Default: a fresh id per handler.\n */\n conversationId?: string;\n /** Test seam: replaces the transport built from the options above. */\n transport?: MatrxTransport;\n /** Every tool call's outcome, for the host's transcript or logs. */\n onToolCall?: (event: { name: string; ok: boolean; callId: string }) => void;\n}\n\n/** A JSON-RPC message, structurally. */\nexport interface McpJsonRpcMessage {\n jsonrpc?: string;\n id?: string | number | null;\n method?: string;\n params?: unknown;\n}\n\n/** What the host writes back: HTTP status, plus a JSON body when there is one. */\nexport interface McpHttpReply {\n status: number;\n body?: unknown;\n}\n\nexport interface AgentToolsMcpHandler {\n /** Handle one parsed HTTP POST body (a message or a batch). */\n handle(body: unknown): Promise<McpHttpReply>;\n /** The tools `tools/list` returns, in MCP shape. */\n listTools(): McpToolDescriptor[];\n}\n\nexport interface McpToolDescriptor {\n name: string;\n description: string;\n inputSchema: Record<string, JsonValue>;\n}\n\ninterface ToolExecuteResponse {\n call_id: string;\n ok: boolean;\n output: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction newId(): string {\n return globalThis.crypto.randomUUID();\n}\n\nfunction toDescriptor(tool: PortableTool): McpToolDescriptor {\n const schema = isRecord(tool.parameters) && tool.parameters.type === \"object\"\n ? tool.parameters\n : { type: \"object\", properties: {} };\n return { name: tool.name, description: tool.description || tool.canonical_name, inputSchema: schema };\n}\n\nfunction rpcResult(id: McpJsonRpcMessage[\"id\"], result: unknown): unknown {\n return { jsonrpc: \"2.0\", id: id ?? null, result };\n}\n\nfunction rpcError(id: McpJsonRpcMessage[\"id\"], code: number, message: string): unknown {\n return { jsonrpc: \"2.0\", id: id ?? null, error: { code, message } };\n}\n\nfunction textResult(text: string, isError: boolean): unknown {\n return { content: [{ type: \"text\", text }], isError };\n}\n\nexport function createAgentToolsMcpHandler(options: AgentToolsMcpOptions): AgentToolsMcpHandler {\n const { bundle } = options;\n const conversationId = options.conversationId ?? newId();\n const descriptors = bundle.tools.map(toDescriptor);\n const known = new Set(bundle.tools.map((tool) => tool.name));\n\n const resolveOrganization = async (): Promise<string | null> => {\n const raw = typeof options.organizationId === \"function\"\n ? await options.organizationId()\n : options.organizationId;\n return raw && raw.trim() ? raw.trim() : null;\n };\n\n const transportFor = (organizationId: string): MatrxTransport =>\n options.transport ??\n createMatrxTransport({\n baseUrl: options.baseUrl,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n organizationId,\n source: \"agentToolsMcp\",\n });\n\n async function callTool(id: McpJsonRpcMessage[\"id\"], params: unknown): Promise<unknown> {\n if (!isRecord(params) || typeof params.name !== \"string\") {\n return rpcError(id, -32602, \"tools/call needs a tool name.\");\n }\n const name = params.name;\n if (!known.has(name)) return rpcError(id, -32602, `Unknown tool: ${name}`);\n const args = isRecord(params.arguments) ? params.arguments : {};\n\n const credential = options.credentials ? await options.credentials.get() : null;\n if (!credential) return rpcResult(id, textResult(SIGNED_OUT_TOOL_MESSAGE, true));\n const organizationId = await resolveOrganization();\n if (!organizationId) return rpcResult(id, textResult(NO_ORGANIZATION_TOOL_MESSAGE, true));\n\n const callId = newId();\n const pinned = typeof bundle.version_id === \"string\" && bundle.version_id.length > 0;\n try {\n const response = await requestJson<ToolExecuteResponse>(\n transportFor(organizationId),\n \"/ai/tools/execute\",\n {\n method: \"POST\",\n body: {\n agent_id: pinned ? bundle.version_id : bundle.agent_id,\n is_version: pinned,\n conversation_id: conversationId,\n tool_name: name,\n arguments: args,\n call_id: callId,\n surface: bundle.surface,\n },\n },\n );\n options.onToolCall?.({ name, ok: response.ok, callId });\n return rpcResult(id, textResult(response.output, !response.ok));\n } catch (error) {\n options.onToolCall?.({ name, ok: false, callId });\n if (error instanceof MatrxApiError && error.status === 401) {\n return rpcResult(id, textResult(SIGNED_OUT_TOOL_MESSAGE, true));\n }\n const message = error instanceof Error ? error.message : String(error);\n return rpcResult(id, textResult(`AI Matrx could not run ${name}: ${message}`, true));\n }\n }\n\n async function handleOne(message: unknown): Promise<unknown | null> {\n if (!isRecord(message) || typeof message.method !== \"string\") {\n return rpcError(null, -32600, \"Invalid request.\");\n }\n const { id, params } = message as unknown as McpJsonRpcMessage;\n const method = message.method;\n const isNotification = id === undefined;\n switch (method) {\n case \"initialize\": {\n const requested = isRecord(params) ? params.protocolVersion : undefined;\n const protocolVersion =\n typeof requested === \"string\" &&\n (AGENT_TOOLS_MCP_PROTOCOL_VERSIONS as readonly string[]).includes(requested)\n ? requested\n : AGENT_TOOLS_MCP_PROTOCOL_VERSIONS[0];\n return rpcResult(id, {\n protocolVersion,\n capabilities: { tools: { listChanged: false } },\n serverInfo: { name: AGENT_TOOLS_MCP_SERVER_NAME, version: \"1\" },\n instructions: `Tools of the AI Matrx agent \"${bundle.name}\". They run on AI Matrx as the signed-in person.`,\n });\n }\n case \"ping\":\n return isNotification ? null : rpcResult(id, {});\n case \"tools/list\":\n return rpcResult(id, { tools: descriptors });\n case \"tools/call\":\n return callTool(id, params);\n default:\n if (isNotification) return null; // notifications/initialized and friends\n return rpcError(id, -32601, `Method not found: ${method}`);\n }\n }\n\n return {\n listTools: () => descriptors,\n async handle(body) {\n if (Array.isArray(body)) {\n const replies = (await Promise.all(body.map(handleOne))).filter((r) => r !== null);\n return replies.length ? { status: 200, body: replies } : { status: 202 };\n }\n const reply = await handleOne(body);\n return reply === null ? { status: 202 } : { status: 200, body: reply };\n },\n };\n}\n"],"mappings":"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","names":["isNetError","status","isNetError","isRecord"]}
|
|
1
|
+
{"version":3,"sources":["../../matrx/client.ts","../../matrx/backend-errors.ts","../../matrx/org-context.ts","../../matrx/protocol.ts","../../matrx/transport.ts","../../matrx/call.ts","../../matrx/internal.ts","../../portable/mcp.ts"],"sourcesContent":["/**\n * The PRODUCTION `MatrxTransport` — the full connection pipeline every Matrx\n * host used to hand-roll, moved in under C22 (THE HARD PARTS LIVE IN THE\n * PACKAGE). A host constructs it from identity alone:\n *\n * ```ts\n * const transport = createMatrxTransport({\n * baseUrl: \"https://server.app.matrxserver.com\",\n * credentials, // CredentialsPort (@ai-matrx/data)\n * organizationId: () => activeOrgId, // omit for conversation-lane calls\n * });\n * ```\n *\n * Everything else is defaulted to the proven production values:\n *\n * - **timeouts** — connect 15s (for a non-streaming FastAPI handler this is\n * effectively time-to-response), total UNCAPPED (the port cannot tell a JSON\n * call from a long-lived NDJSON/SSE stream; the connect timeout is the JSON\n * guard), via `@ai-matrx/data/net`'s `resilientFetch`;\n * - **protocol** — the AI API version transform (`applyAiApiVersion`, default\n * v2) + the loud v2 → v1 transport fallback (`./protocol`);\n * - **credentials** — read fresh from the injected `CredentialsPort` on EVERY\n * call, so a token refresh mid-run is picked up; mapped to headers by the\n * ONE `credentialToHeaders`;\n * - **org context** — the fail-closed `X-Organization-Id` binding\n * (`./org-context`): configured lanes REQUIRE an org and refuse before the\n * wire; unconfigured lanes (conversation-scoped calls, which carry org in\n * the body) send none;\n * - **error classification** — every failure normalizes through\n * `normalizeMatrxError` into the ONE `MatrxCallError` envelope;\n * - **diagnostics** — a typed sink the host wires to its capture/telemetry\n * (`onRequest`, `onError`, `onProtocolDowngrade`); wiring it is optional,\n * the transport works silently without it.\n *\n * Header merge order is part of the port contract: wire headers first\n * (`Content-Type` / `Accept` / `Last-Event-ID` — the package's), then\n * credentials, then resolver policy headers, then the org header. Policy\n * headers never carry `Content-Type` — the wire owns it, and a policy\n * `Content-Type` merged over a GET SSE call would corrupt the wire, so the\n * factory strips it defensively.\n */\n\nimport { isNetError, type CredentialsPort } from \"@ai-matrx/data\";\nimport { credentialToHeaders } from \"@ai-matrx/data\";\nimport { extractErrorMessage } from \"@ai-matrx/data/net\";\nimport { BackendApiError } from \"./backend-errors\";\nimport {\n bareStatusSentence,\n honestTransportMessage,\n isBareTransportCode,\n} from \"./call\";\nimport {\n applyOrganizationContextHeader,\n OrganizationContextError,\n requireOrganizationContext,\n} from \"./org-context\";\nimport {\n applyAiApiVersion,\n fetchWithMatrxProtocolFallback,\n MATRX_AI_API_VERSION_DEFAULT,\n type MatrxAiApiVersion,\n type MatrxProtocolDowngrade,\n} from \"./protocol\";\nimport {\n extractMatrxErrorCode,\n extractMatrxErrorMessage,\n MatrxApiError,\n type MatrxTransport,\n type MatrxTransportRequest,\n} from \"./transport\";\n\n// ─── The normalized error envelope ──────────────────────────────────────────\n\n/**\n * The ONE classified error shape for Matrx client calls — the envelope hosts\n * branch on instead of string-matching exceptions. Structurally compatible\n * with matrx-frontend's `ApiCallError` by design.\n */\nexport interface MatrxCallError {\n type:\n | \"auth_error\"\n | \"network_error\"\n | \"http_error\"\n | \"validation_error\"\n | \"abort_error\"\n | \"unknown\";\n message: string;\n /** HTTP status code, if applicable. */\n status?: number;\n /** Raw error detail from the server (the parsed error body). */\n serverDetail?: unknown;\n /** Machine code preserved through normalization (server `code`, org-context code, …). */\n code?: string;\n /** Original exception identity for diagnostics. */\n name?: string;\n /** Original exception stack. */\n stack?: string;\n /** JSON-safe dump of the original thrown value. */\n raw?: unknown;\n}\n\nfunction classifyStatus(status: number): \"validation_error\" | \"http_error\" {\n return status >= 400 && status < 500 ? \"validation_error\" : \"http_error\";\n}\n\n/**\n * Classify any failure thrown by a Matrx client call — `MatrxApiError`, a\n * typed `BackendApiError` / `StreamTransportError`, a `NetError` from the\n * resilience layer, an org-context refusal, an abort — into the\n * `MatrxCallError` envelope. Never throws.\n *\n * Every message is laundered through `honestTransportMessage`: a bare status\n * line (\"HTTP 400\") never reaches a person — the status rides `status`.\n * A `BackendApiError` keeps its machine `code` (a dropped socket stays\n * `stream_transport_lost`, so a surface reattaches instead of dead-ending)\n * and its structured `details` ride `serverDetail` (the organization-hold\n * body carries the caller's membership choices there).\n */\nexport function normalizeMatrxError(err: unknown): MatrxCallError {\n if (err instanceof OrganizationContextError) {\n return {\n type: \"validation_error\",\n message: err.message,\n code: err.code,\n name: err.name,\n ...(err.stack !== undefined ? { stack: err.stack } : {}),\n };\n }\n\n if (err instanceof DOMException && err.name === \"AbortError\") {\n return { type: \"abort_error\", message: \"Request was cancelled.\" };\n }\n\n if (err instanceof MatrxApiError) {\n return {\n type: classifyStatus(err.status),\n message: honestTransportMessage(err.message, err.status),\n status: err.status,\n serverDetail: err.serverDetail,\n ...(err.code ? { code: err.code } : {}),\n name: err.name,\n };\n }\n\n if (err instanceof BackendApiError) {\n return {\n type: \"network_error\",\n message: honestTransportMessage(\n err.detail || err.userMessage,\n err.status ?? undefined,\n ),\n code: err.code,\n name: err.name,\n ...(err.status !== null ? { status: err.status } : {}),\n ...(err.details !== null ? { serverDetail: err.details } : {}),\n };\n }\n\n if (isNetError(err)) {\n if (err.code === \"aborted\") {\n return { type: \"abort_error\", message: err.message };\n }\n if (err.code === \"http\") {\n const status = err.status ?? 0;\n return {\n type: classifyStatus(status),\n message: honestTransportMessage(err.message, status),\n status,\n };\n }\n // connect-timeout / total-timeout / heartbeat-timeout / network / offline\n return { type: \"network_error\", message: err.message };\n }\n\n if (err instanceof Error) {\n const httpMatch = err.message.match(/HTTP (\\d+):\\s*(.*)/);\n if (httpMatch) {\n const status = parseInt(httpMatch[1] as string, 10);\n return {\n type: classifyStatus(status),\n message: honestTransportMessage(httpMatch[2] ?? err.message, status),\n status,\n };\n }\n // \"HTTP 400\" with no colon: the status line itself, never the sentence.\n if (isBareTransportCode(err.message)) {\n const status = Number.parseInt(err.message.replace(/\\D+/g, \"\"), 10);\n return {\n type: classifyStatus(status),\n message: bareStatusSentence(status),\n status,\n };\n }\n return { type: \"network_error\", message: err.message };\n }\n\n return { type: \"unknown\", message: extractErrorMessage(err) };\n}\n\n// ─── The factory ────────────────────────────────────────────────────────────\n\n/**\n * A fully-resolved connection target for one call: where to send it and which\n * policy headers ride ON TOP of the wire + credential headers.\n */\nexport interface MatrxTransportTarget {\n /** Fully-qualified base URL, no trailing slash. */\n baseUrl: string;\n /**\n * Extra policy headers for this target (e.g. a resolver that carries its own\n * credential headers). `Content-Type` is stripped — the wire owns it.\n */\n policyHeaders?: Record<string, string>;\n /** Routing channel label surfaced to `onRequest` telemetry. */\n channel?: string;\n}\n\n/** Context handed to every diagnostics callback. */\nexport interface MatrxRequestInfo {\n /** The final URL (base + version-transformed path). */\n url: string;\n method: \"GET\" | \"POST\";\n /** The server-relative path as the package requested it. */\n path: string;\n /** The resolved routing channel (default \"default\"). */\n channel: string;\n /** The transport's call-site label (default \"matrxTransport\"). */\n source: string;\n}\n\n/**\n * The typed diagnostics sink — the host wires these to its telemetry/capture\n * systems; all optional, and the transport is fully functional without them.\n */\nexport interface MatrxTransportDiagnostics {\n /** Fired at the last pre-fetch moment of every call. */\n onRequest?: (info: MatrxRequestInfo) => void;\n /**\n * Fired for every thrown network failure and every non-2xx response not\n * listed in `expectedErrorStatuses`, with the classified envelope.\n */\n onError?: (error: MatrxCallError, info: MatrxRequestInfo) => void;\n /** Fired on every v2 → v1 protocol downgrade. */\n onProtocolDowngrade?: (downgrade: MatrxProtocolDowngrade) => void;\n}\n\nexport interface CreateMatrxTransportOptions {\n /** Fixed base URL, no trailing slash. Exactly one of `baseUrl` / `resolveTarget`. */\n baseUrl?: string;\n /**\n * Per-call target resolver — for hosts whose base URL / policy headers are\n * dynamic (server toggles, sandbox overrides, conversation channels).\n * Resolution runs fresh on EVERY call. May throw to refuse a call loudly.\n */\n resolveTarget?: () => MatrxTransportTarget;\n /**\n * The credential source (`@ai-matrx/data`), read fresh per call so token\n * refreshes are picked up mid-run. Omit only when the resolver's\n * `policyHeaders` already carry the credentials.\n */\n credentials?: CredentialsPort;\n /**\n * The org-context binding. When configured, every call REQUIRES a valid org\n * (fail-closed `organization_context_required` before the wire) and carries\n * `X-Organization-Id`. Omit for conversation-lane transports, which send the\n * org in the body only.\n */\n organizationId?: string | (() => string | null | undefined);\n /** AI API protocol version (value or per-call getter). Default `\"v2\"`. */\n aiApiVersion?: MatrxAiApiVersion | (() => MatrxAiApiVersion);\n /** Max ms request start → response headers. Default 15_000. */\n connectTimeoutMs?: number;\n /**\n * Max ms for the whole handshake. Default `null` (uncapped) — the port\n * cannot tell a JSON call from a long-lived stream, and capping would kill\n * long streams; the connect timeout is the real JSON guard.\n */\n totalTimeoutMs?: number | null;\n /**\n * HTTP failures this transport's call class fully handles as expected\n * domain outcomes (e.g. 404 on runtime-operation identify). They still\n * reach the package client (which maps them to typed results) but are not\n * reported through `onError`.\n */\n expectedErrorStatuses?: readonly number[];\n /** Short call-site label surfaced on `MatrxRequestInfo` (default \"matrxTransport\"). */\n source?: string;\n diagnostics?: MatrxTransportDiagnostics;\n}\n\nconst PRODUCTION_CONNECT_TIMEOUT_MS = 15_000;\n\nfunction stripContentType(\n headers: Record<string, string>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(headers).filter(\n ([name]) => name.toLowerCase() !== \"content-type\",\n ),\n );\n}\n\n/**\n * Build the production `MatrxTransport`. See the module doc for the pipeline;\n * every knob defaults to the proven production value.\n */\nexport function createMatrxTransport(\n options: CreateMatrxTransportOptions,\n): MatrxTransport {\n const {\n baseUrl,\n resolveTarget,\n credentials,\n organizationId,\n aiApiVersion,\n connectTimeoutMs = PRODUCTION_CONNECT_TIMEOUT_MS,\n totalTimeoutMs = null,\n expectedErrorStatuses,\n diagnostics,\n source = \"matrxTransport\",\n } = options;\n\n if ((baseUrl === undefined) === (resolveTarget === undefined)) {\n throw new Error(\n \"createMatrxTransport: provide exactly one of `baseUrl` or `resolveTarget`.\",\n );\n }\n\n const resolve: () => MatrxTransportTarget =\n resolveTarget ?? (() => ({ baseUrl: baseUrl as string }));\n\n const resolveVersion = (): MatrxAiApiVersion => {\n if (aiApiVersion === undefined) return MATRX_AI_API_VERSION_DEFAULT;\n return typeof aiApiVersion === \"function\" ? aiApiVersion() : aiApiVersion;\n };\n\n const resolveOrganizationId = (): string | null => {\n if (organizationId === undefined) return null;\n const candidate =\n typeof organizationId === \"function\" ? organizationId() : organizationId;\n // Fail closed: a configured org lane with no org refuses before the wire.\n return requireOrganizationContext(candidate);\n };\n\n return {\n async fetch(path: string, init: MatrxTransportRequest): Promise<Response> {\n const target = resolve();\n\n // `path` is already interpolated (`/ai/agents/<uuid>`), so the version\n // transform is the concrete-path bridge, not a template registry.\n const versionedPath = applyAiApiVersion(path, resolveVersion());\n const url = `${target.baseUrl}${versionedPath}`;\n\n // Wire headers FIRST; credentials, then resolver policy headers, then\n // the org header on top — none of them may carry Content-Type.\n const credentialHeaders = credentials\n ? credentialToHeaders(await credentials.get())\n : {};\n let headers: Record<string, string> = {\n ...init.headers,\n ...stripContentType(credentialHeaders),\n ...stripContentType(target.policyHeaders ?? {}),\n };\n const orgId = resolveOrganizationId();\n if (orgId !== null) {\n headers = applyOrganizationContextHeader(headers, orgId);\n }\n\n const info: MatrxRequestInfo = {\n url,\n method: init.method,\n path,\n channel: target.channel ?? \"default\",\n source,\n };\n diagnostics?.onRequest?.(info);\n\n let response: Response;\n try {\n ({ response } = await fetchWithMatrxProtocolFallback(\n url,\n {\n method: init.method,\n headers,\n ...(init.body !== undefined ? { body: init.body } : {}),\n },\n {\n ...(init.signal ? { signal: init.signal } : {}),\n connectTimeoutMs,\n totalTimeoutMs,\n throwOnHttpError: false,\n ...(diagnostics?.onProtocolDowngrade\n ? { onDowngrade: diagnostics.onProtocolDowngrade }\n : {}),\n },\n ));\n } catch (err) {\n diagnostics?.onError?.(normalizeMatrxError(err), info);\n throw err;\n }\n\n if (!response.ok && !expectedErrorStatuses?.includes(response.status)) {\n // The package client consumes the original body for its\n // MatrxApiError; read the diagnostics copy from a clone so the two\n // never fight.\n const serverDetail: unknown = await response\n .clone()\n .json()\n .catch(() => undefined);\n const code = extractMatrxErrorCode(serverDetail);\n diagnostics?.onError?.(\n {\n type: classifyStatus(response.status),\n message:\n extractMatrxErrorMessage(serverDetail) ??\n `HTTP ${response.status}`,\n status: response.status,\n serverDetail,\n ...(code ? { code } : {}),\n },\n info,\n );\n }\n\n return response;\n },\n };\n}\n","/**\n * `@ai-matrx/agents/matrx` — the AI Matrx server's error model, as every\n * client reads it: `BackendApiError` (the server's `APIError` body),\n * `StreamTransportError` (the socket died mid-run; the run may still finish\n * and is reattachable), parsing of HTTP / stream / persisted errors, and the\n * ONE sentence a person sees (`getUserMessage`, `describeBackendFailure`).\n *\n * Moved from matrx-frontend `lib/api/errors.ts` (chat-package independence\n * P9); the app re-exports it from here. Pure: no host, no window, no env.\n */\n\n/**\n * Standardized error shape returned by all backend endpoints.\n * Matches the Python `APIError` Pydantic model.\n */\nexport interface BackendApiErrorData {\n /** Machine-readable error code (e.g. \"auth_required\", \"validation_error\") */\n error: string;\n /** Developer-facing detail for debugging */\n message: string;\n /** Safe to display directly in the UI */\n user_message: string;\n /** Extra info (validation errors, etc.) */\n details: unknown | null;\n /** Unique request ID for support/debugging */\n request_id: string;\n}\n\n/** Common backend error codes */\nexport type BackendErrorCode =\n | \"auth_required\"\n | \"token_required\"\n | \"admin_required\"\n | \"validation_error\"\n | \"not_found\"\n | \"internal_error\"\n | \"agent_error\"\n /** The stream socket died mid-run; the server run may still be completing\n * and is reattachable. See `StreamTransportError`. */\n | \"stream_transport_lost\"\n | (string & {});\n\n// ============================================================================\n// ERROR CLASS\n// ============================================================================\n\n/**\n * Typed error thrown by all backend API operations.\n * Contains structured fields matching the Python APIError model.\n *\n * Usage:\n * ```typescript\n * try {\n * await client.post(ENDPOINTS.ai.agentStart(agentId), body);\n * } catch (err) {\n * if (err instanceof BackendApiError) {\n * // Show err.userMessage to the user\n * // Log err.requestId for debugging\n * // Check err.code for programmatic handling\n * }\n * }\n * ```\n */\nexport class BackendApiError extends Error {\n /** Machine-readable error code */\n readonly code: BackendErrorCode;\n /** Developer-facing detail */\n readonly detail: string;\n /** Safe to display directly in the UI */\n readonly userMessage: string;\n /** Extra info (validation errors, etc.) */\n readonly details: unknown | null;\n /** Unique request ID for support/debugging */\n readonly requestId: string;\n /** HTTP status code (if from an HTTP response) */\n readonly status: number | null;\n\n constructor(data: {\n code: BackendErrorCode;\n detail: string;\n userMessage: string;\n details?: unknown | null;\n requestId?: string | undefined;\n status?: number | null | undefined;\n }) {\n super(data.userMessage);\n this.name = \"BackendApiError\";\n this.code = data.code;\n this.detail = data.detail;\n this.userMessage = data.userMessage;\n this.details = data.details ?? null;\n // MATRX-EXCEPTION: requestId is genuinely optional (constructor param);\n // \"\" means \"no request id available\" — a display/log field, not persisted.\n this.requestId = data.requestId ?? \"\";\n this.status = data.status ?? null;\n }\n\n /** Convert to the wire format for logging */\n toJSON(): BackendApiErrorData {\n return {\n error: this.code,\n message: this.detail,\n user_message: this.userMessage,\n details: this.details,\n request_id: this.requestId,\n };\n }\n}\n\n/**\n * The socket carrying a live NDJSON stream broke mid-run.\n *\n * THE DISTINCTION THIS EXISTS TO MAKE: a backend that blows up mid-stream does\n * NOT break the socket — it emits a typed `error` event and closes the body\n * cleanly. So an exception escaping the body reader means the *transport* died,\n * not the run. And aidream streams run `detach_on_disconnect=True`: the server\n * keeps executing and persisting the turn after our connection goes away.\n *\n * The client therefore cannot decide locally whether the answer is lost — it\n * must ASK THE SERVER. That is what `resumable` means here: \"reattach by\n * requestId / conversationId / durable run id and let server truth settle it\",\n * never \"this succeeded\". A server that genuinely died reports `failed` on\n * reattach and the honest record replaces the optimistic copy.\n *\n * Consumers: `run-ai-stream.ts` (chat → `reconnectServerOperation`) and\n * `adopt-foreign-stream.ts` (pipeline runs → the surface's own rejoin).\n */\nexport class StreamTransportError extends BackendApiError {\n /** Always true — reattach and let the server settle the outcome. */\n readonly resumable = true as const;\n\n constructor(data: {\n detail: string;\n details?: unknown | null;\n requestId?: string;\n }) {\n super({\n code: \"stream_transport_lost\",\n detail: data.detail,\n userMessage:\n \"The connection dropped. Your run is still going on the server — reconnecting to it now.\",\n details: data.details ?? null,\n ...(data.requestId !== undefined ? { requestId: data.requestId } : {}),\n });\n this.name = \"StreamTransportError\";\n }\n}\n\n/**\n * True when a failure is a dropped transport rather than a failed run. Use this\n * instead of `instanceof` at boundaries that re-wrap errors (thunk rejections,\n * `callApi` result errors), where the class identity is lost but the code\n * survives.\n */\nexport function isStreamTransportLost(error: unknown): boolean {\n if (error instanceof StreamTransportError) return true;\n if (error instanceof BackendApiError) {\n return error.code === \"stream_transport_lost\";\n }\n if (error && typeof error === \"object\") {\n const code = (error as { code?: unknown }).code;\n if (code === \"stream_transport_lost\") return true;\n const errorType = (error as { error_type?: unknown }).error_type;\n if (errorType === \"transport_lost\") return true;\n }\n return false;\n}\n\n// ============================================================================\n// HTTP ERROR PARSER\n// ============================================================================\n\n/**\n * Parse a non-OK HTTP response into a BackendApiError.\n *\n * Handles the standardized backend shape and falls back gracefully\n * when the response isn't JSON or uses a legacy format.\n */\nexport async function parseHttpError(\n response: Response,\n): Promise<BackendApiError> {\n const status = response.status;\n let text: string;\n\n try {\n // A Response body can only be read once. Capture it before deciding\n // whether the backend sent a structured error or plain text.\n text = await response.text();\n } catch {\n return new BackendApiError({\n code: statusToCode(status),\n detail: `HTTP ${status}`,\n userMessage: `Request failed (${status})`,\n status,\n });\n }\n\n let body: Record<string, unknown> | null;\n try {\n body = JSON.parse(text) as Record<string, unknown> | null;\n } catch {\n return new BackendApiError({\n code: statusToCode(status),\n detail: text || `HTTP ${status}`,\n userMessage: text || `Request failed (${status})`,\n status,\n });\n }\n return parseHttpErrorBody(body, status);\n}\n\n/**\n * Parse an already-decoded JSON error body into a BackendApiError.\n *\n * Exported because XHR callers (upload/download progress paths) have the\n * parsed body in hand and MUST NOT hand-roll a shallower read: a private\n * copy in `python-client.ts` looked only at top-level `error`/`message`, so\n * FastAPI's `{\"detail\": {...}}` envelope — what every matrx-files 500 uses —\n * degraded to the useless `code: \"internal\", detail: \"HTTP 500\"`. That is\n * exactly how an upload failure with a real server-side cause reached the\n * user as `Upload failed (500)` and nothing else. One parser, every transport.\n */\nexport function parseHttpErrorBody(\n body: Record<string, unknown> | null,\n status: number,\n): BackendApiError {\n if (!body) {\n return new BackendApiError({\n code: statusToCode(status),\n detail: `HTTP ${status}`,\n userMessage: `Request failed (${status})`,\n status,\n });\n }\n // Standard backend shape: { error, message, user_message, details, request_id }\n if (typeof body.error === \"string\" && typeof body.user_message === \"string\") {\n return new BackendApiError({\n code: body.error as BackendErrorCode,\n detail: (body.message as string) || `HTTP ${status}`,\n userMessage: body.user_message as string,\n details: body.details ?? null,\n requestId:\n typeof body.request_id === \"string\" ? body.request_id : undefined,\n status,\n });\n }\n\n // Legacy: nested error object with user_visible_message\n if (typeof body.error === \"object\" && body.error !== null) {\n const errorObj = body.error as Record<string, unknown>;\n return new BackendApiError({\n code:\n (errorObj.type as string) ||\n (errorObj.error as string) ||\n statusToCode(status),\n detail: (errorObj.message as string) || `HTTP ${status}`,\n userMessage:\n (errorObj.user_message as string) ||\n (errorObj.user_visible_message as string) ||\n (errorObj.message as string) ||\n `Request failed (${status})`,\n details: errorObj.details ?? null,\n requestId:\n typeof errorObj.request_id === \"string\"\n ? errorObj.request_id\n : undefined,\n status,\n });\n }\n\n // FastAPI 422 validation shape: { detail: [{ loc, msg, type }, ...] }\n if (Array.isArray(body.detail)) {\n const first = body.detail[0] as Record<string, unknown> | undefined;\n const firstMsg = first && typeof first.msg === \"string\" ? first.msg : null;\n return new BackendApiError({\n code: statusToCode(status),\n detail: firstMsg\n ? `Validation error: ${firstMsg}`\n : JSON.stringify(body.detail),\n userMessage: firstMsg || `Request failed (${status})`,\n details: body.detail,\n status,\n });\n }\n\n // FastAPI HTTPException with structured detail: { detail: { code?, error?, message?, user_message?, ... } }\n if (\n typeof body.detail === \"object\" &&\n body.detail !== null &&\n !Array.isArray(body.detail)\n ) {\n const d = body.detail as Record<string, unknown>;\n const code =\n (typeof d.code === \"string\" && d.code) ||\n (typeof d.error === \"string\" && d.error) ||\n statusToCode(status);\n const message =\n (typeof d.message === \"string\" && d.message) ||\n (typeof d.detail === \"string\" && d.detail) ||\n `HTTP ${status}`;\n return new BackendApiError({\n code: code as BackendErrorCode,\n detail: message,\n userMessage:\n (typeof d.user_message === \"string\" && d.user_message) ||\n (typeof d.user_visible_message === \"string\" &&\n d.user_visible_message) ||\n message,\n details: d.details ?? d,\n requestId: typeof d.request_id === \"string\" ? d.request_id : undefined,\n status,\n });\n }\n\n // Legacy: flat { error: string, message: string } or { detail: string }\n return new BackendApiError({\n code: typeof body.error === \"string\" ? body.error : statusToCode(status),\n detail:\n (body.message as string) ||\n (body.detail as string) ||\n (body.error as string) ||\n `HTTP ${status}`,\n userMessage:\n (body.user_message as string) ||\n (body.user_visible_message as string) ||\n (body.message as string) ||\n (body.detail as string) ||\n `Request failed (${status})`,\n details: body.details ?? null,\n requestId:\n typeof body.request_id === \"string\" ? body.request_id : undefined,\n status,\n });\n}\n\n/**\n * Adapt callApi's result-style error into the same canonical error used by\n * direct fetch and streaming consumers.\n *\n * callApi intentionally returns errors instead of throwing them, but its\n * `serverDetail` contains the complete FastAPI body. Sending only\n * `error.message` to a feature discards that body and turns a precise\n * configuration failure into \"HTTP 422\". This adapter keeps one parser and\n * one human-facing explanation path across both client styles.\n */\nexport function parseCallApiError(error: {\n message: string;\n status?: number;\n serverDetail?: unknown;\n}): BackendApiError {\n const status = error.status ?? 500;\n const body =\n error.serverDetail &&\n typeof error.serverDetail === \"object\" &&\n !Array.isArray(error.serverDetail)\n ? (error.serverDetail as Record<string, unknown>)\n : { message: error.message };\n return parseHttpErrorBody(body, status);\n}\n\n// ============================================================================\n// STREAMING ERROR PARSER\n// ============================================================================\n\n/**\n * Parse streaming error event data into a BackendApiError.\n *\n * Handles both new format (`user_message`) and legacy (`user_visible_message`).\n */\nexport function parseStreamError(data: unknown): BackendApiError {\n if (!data || typeof data !== \"object\") {\n return new BackendApiError({\n code: \"internal_error\",\n detail: typeof data === \"string\" ? data : \"Unknown streaming error\",\n userMessage: typeof data === \"string\" ? data : \"Something went wrong\",\n });\n }\n\n const obj = data as Record<string, unknown>;\n const details =\n typeof obj.details === \"object\" && obj.details !== null\n ? (obj.details as Record<string, unknown>)\n : null;\n return new BackendApiError({\n code:\n (obj.code as string) ||\n (obj.error_type as string) ||\n (obj.error as string) ||\n \"internal_error\",\n detail: (obj.message as string) || \"Streaming error\",\n userMessage:\n (obj.user_message as string) ||\n (obj.message as string) ||\n \"Something went wrong\",\n details,\n requestId:\n typeof obj.request_id === \"string\"\n ? obj.request_id\n : typeof details?.request_id === \"string\"\n ? details.request_id\n : undefined,\n });\n}\n\n/**\n * Restore the canonical error shape from a durable backend run row.\n *\n * Provider ledgers often keep an aggregate summary plus a more specific first\n * child failure. Prefer that child so a page refresh does not turn a precise\n * streamed failure back into \"1 request failed\".\n */\nexport function parsePersistedBackendError(\n data: unknown,\n requestId = \"\",\n): BackendApiError | null {\n if (!data || typeof data !== \"object\") return null;\n const error = data as Record<string, unknown>;\n const failures = Array.isArray(error.failures) ? error.failures : [];\n const firstSpecific = failures.find(\n (failure): failure is Record<string, unknown> =>\n typeof failure === \"object\" &&\n failure !== null &&\n typeof (failure as Record<string, unknown>).message === \"string\",\n );\n const summary =\n typeof error.message === \"string\"\n ? error.message\n : \"Backend operation failed\";\n const specific =\n typeof firstSpecific?.message === \"string\"\n ? firstSpecific.message\n : summary;\n const detail = specific === summary ? summary : `${summary}: ${specific}`;\n const code = typeof error.type === \"string\" ? error.type : \"internal_error\";\n // A persisted failure may carry a human-facing line the technical detail\n // cannot express — the load-bearing case being a paid result that survived\n // its persistence failure (`WritePreservedError`, D183): \"it broke\" is true\n // and \"your work is preserved and will be recovered\" is what the user needs.\n // `describeBackendFailure` still overrides a TEMPLATED one with the cause.\n const userMessage =\n typeof error.user_message === \"string\" && error.user_message\n ? error.user_message\n : detail;\n return new BackendApiError({\n code,\n detail,\n userMessage,\n details: error,\n requestId,\n });\n}\n\n// ============================================================================\n// FAILURE EXPLANATION — never let a templated non-answer be the whole story\n// ============================================================================\n\n/**\n * Server messages that carry ZERO diagnostic value. The streaming layer\n * (`matrx-connect/streaming/response.py`) emits the first one for every\n * unclassified crash — \"CanonicalGscSync failed unexpectedly. Please try\n * again or adjust your settings.\" — while the REAL cause travels in the same\n * payload's `message`. Treating those as the answer is what makes failures\n * feel secretive.\n */\nconst GENERIC_MESSAGE_PATTERNS: readonly RegExp[] = [\n /failed unexpectedly/i,\n /^\\s*something went wrong/i,\n /please try again(\\s+later)?\\.?\\s*$/i,\n /^\\s*request failed\\b/i,\n /^\\s*unknown (streaming )?error/i,\n /^\\s*internal server error\\.?\\s*$/i,\n];\n\n/** True when a message tells the reader nothing about what actually broke. */\nexport function isGenericUserMessage(\n message: string | null | undefined,\n): boolean {\n const value = (message ?? \"\").trim();\n if (!value) return true;\n return GENERIC_MESSAGE_PATTERNS.some((pattern) => pattern.test(value));\n}\n\nexport interface UpstreamErrorPayload {\n message: string;\n code: string | null;\n userMessage: string | null;\n requestId: string | null;\n status: number | null;\n}\n\n/**\n * Recover an upstream service's structured error that a downstream service\n * stringified into its own message.\n *\n * Real example (scraper wrapping aidream):\n * `aidream could not resolve GSC credential 7223…: HTTP 409 {\"error\":\"conflict\",\n * \"message\":\"Google connection 7223… has no vault credential — it needs\n * re-authentication\",\"user_message\":\"Something went wrong…\",\"request_id\":\"9002…\"}`\n *\n * Without this, the only actionable sentence on the whole hop is invisible.\n */\nexport function unwrapUpstreamError(\n message: string,\n): UpstreamErrorPayload | null {\n const start = message.indexOf(\"{\");\n const end = message.lastIndexOf(\"}\");\n if (start < 0 || end <= start) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(message.slice(start, end + 1));\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const body = parsed as Record<string, unknown>;\n const inner =\n (typeof body.message === \"string\" && body.message) ||\n (typeof body.detail === \"string\" && body.detail) ||\n \"\";\n if (!inner) return null;\n const statusMatch = /\\bHTTP (\\d{3})\\b/.exec(message.slice(0, start));\n return {\n message: inner,\n code:\n (typeof body.error_type === \"string\" && body.error_type) ||\n (typeof body.error === \"string\" && body.error) ||\n null,\n userMessage:\n typeof body.user_message === \"string\" ? body.user_message : null,\n requestId: typeof body.request_id === \"string\" ? body.request_id : null,\n status: statusMatch ? Number(statusMatch[1]) : null,\n };\n}\n\nexport interface BackendFailureExplanation {\n /** Machine code from the deepest layer that classified the failure. */\n code: string;\n /** The most specific human-readable cause available — never a template. */\n cause: string;\n /** What to headline in the UI: the cause when the server was generic. */\n headline: string;\n /** True when every user-facing message the server sent was a template. */\n headlineWasGeneric: boolean;\n /** Message chain, outermost (closest service) first. */\n chain: string[];\n /** Deepest request id available, for cross-service log correlation. */\n requestId: string;\n status: number | null;\n}\n\n/**\n * THE anti-secrecy primitive: turn any thrown backend/stream failure into the\n * most specific explanation the payload can support — unwrapping every nested\n * upstream error and refusing to let a templated `user_message` be the answer.\n *\n * Every surface that reports a backend failure to a human should headline\n * `explanation.headline` and always keep `cause` + `requestId` reachable.\n */\nexport function describeBackendFailure(\n error: unknown,\n): BackendFailureExplanation {\n const chain: string[] = [];\n let code = \"internal_error\";\n let requestId = \"\";\n let status: number | null = null;\n let userFacing: string | null = null;\n\n if (error instanceof BackendApiError) {\n code = error.code;\n requestId = error.requestId;\n status = error.status;\n userFacing = error.userMessage;\n if (error.detail) chain.push(error.detail);\n if (error.userMessage && error.userMessage !== error.detail) {\n chain.push(error.userMessage);\n }\n } else if (error instanceof Error) {\n chain.push(error.message);\n userFacing = error.message;\n } else if (typeof error === \"string\") {\n chain.push(error);\n userFacing = error;\n } else {\n chain.push(\"Unknown error\");\n }\n\n // Walk the nesting: ANY message in the chain may have stringified the\n // service above it (the technical `detail` usually does, the templated\n // `user_message` never does), so every layer gets unwrapped.\n for (let cursor = 0; cursor < chain.length && cursor < 12; cursor += 1) {\n const upstream = unwrapUpstreamError(chain[cursor] ?? \"\");\n if (!upstream || chain.includes(upstream.message)) continue;\n chain.push(upstream.message);\n if (upstream.code) code = upstream.code;\n if (upstream.requestId) requestId = upstream.requestId;\n if (upstream.status !== null) status = upstream.status;\n }\n\n const specific = [...chain]\n .reverse()\n .find((message) => !isGenericUserMessage(message));\n const cause = specific ?? chain[0] ?? \"Unknown error\";\n const headlineWasGeneric = isGenericUserMessage(userFacing);\n return {\n code,\n cause,\n headline: headlineWasGeneric ? cause : (userFacing ?? cause),\n headlineWasGeneric,\n chain,\n requestId,\n status,\n };\n}\n\n/**\n * Extract a user-visible message from any error object.\n * Utility for components that just need the display string.\n */\nexport function getUserMessage(error: unknown): string {\n if (error instanceof BackendApiError) {\n return error.userMessage;\n }\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n // A Redux thunk's `.unwrap()` rejects with a SerializedError — a plain\n // object, not an Error — whose `message` is the real reason.\n if (\n typeof error === \"object\" &&\n error !== null &&\n typeof (error as { message?: unknown }).message === \"string\" &&\n (error as { message: string }).message\n ) {\n return (error as { message: string }).message;\n }\n return \"Something went wrong\";\n}\n\n// ============================================================================\n// INTERNAL HELPERS\n// ============================================================================\n\nfunction statusToCode(status: number): BackendErrorCode {\n switch (status) {\n case 401:\n return \"auth_required\";\n case 403:\n return \"admin_required\";\n case 404:\n return \"not_found\";\n case 422:\n return \"validation_error\";\n default:\n return \"internal_error\";\n }\n}\n","import { isUuidShape } from \"@ai-matrx/kit/uuid\";\n\n/**\n * The fail-closed organization-context kernel — ONE implementation for every\n * Matrx client transport (moved in from matrx-frontend\n * `lib/api/organization-context.ts` verbatim under C22; the host module is now\n * a re-export of this one).\n *\n * Transports resolve their authoritative organization first, then use these\n * functions to bind that exact value without guessing or defaulting. The\n * server never manufactures an org (it 422s a blank one), so the client\n * refuses before the wire: a missing or malformed org id throws\n * `OrganizationContextError` instead of sending a request that cannot succeed.\n */\n\nexport type OrganizationContextErrorCode =\n | \"organization_context_required\"\n | \"organization_context_invalid\"\n | \"organization_context_mismatch\";\n\nexport class OrganizationContextError extends Error {\n readonly code: OrganizationContextErrorCode;\n\n constructor(code: OrganizationContextErrorCode, message: string) {\n super(message);\n this.name = \"OrganizationContextError\";\n this.code = code;\n }\n}\n\ndeclare const organizationOperationBrand: unique symbol;\n\n/** A validated organization operation; construct only through the factory. */\nexport type OrganizationOperation = Readonly<{\n organization_id: string;\n readonly [organizationOperationBrand]: true;\n}>;\n\nexport function createOrganizationOperation(\n organizationId: string,\n): OrganizationOperation {\n const organization_id = requireOrganizationContext(organizationId);\n return Object.freeze({ organization_id }) as OrganizationOperation;\n}\n\nexport function assertOrganizationMatchesOperation(\n operation: OrganizationOperation,\n organizationId: string,\n): void {\n if (operation.organization_id !== requireOrganizationContext(organizationId)) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"Organization ID must match the request operation.\",\n );\n }\n}\n\n/**\n * Normalize and validate the effective organization id for a request. The\n * override (an explicit per-call value) beats the selected context value.\n * Throws `organization_context_required` when neither is present and\n * `organization_context_invalid` when the candidate is not a UUID.\n */\nexport function requireOrganizationContext(\n selectedOrganizationId: string | null | undefined,\n overrideOrganizationId?: string,\n): string {\n const candidate = overrideOrganizationId ?? selectedOrganizationId;\n if (typeof candidate !== \"string\" || candidate.trim().length === 0) {\n throw new OrganizationContextError(\n \"organization_context_required\",\n \"Select an organization before sending this request.\",\n );\n }\n\n const normalized = candidate.trim();\n if (!isUuidShape(normalized)) {\n throw new OrganizationContextError(\n \"organization_context_invalid\",\n \"The selected organization ID is invalid.\",\n );\n }\n return normalized.toLowerCase();\n}\n\n/**\n * Bind `X-Organization-Id` onto a header bag. A pre-existing org header that\n * disagrees with the context org is a `organization_context_mismatch` error —\n * never silently overwritten in either direction.\n */\nexport function applyOrganizationContextHeader(\n headers: Record<string, string>,\n organizationId: string,\n): Record<string, string> {\n const normalizedOrganizationId = requireOrganizationContext(organizationId);\n for (const [name, value] of Object.entries(headers)) {\n if (\n name.toLowerCase() === \"x-organization-id\" &&\n value.trim().toLowerCase() !== normalizedOrganizationId\n ) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"X-Organization-Id must match the request context organization.\",\n );\n }\n }\n const withoutOrganizationHeader = Object.fromEntries(\n Object.entries(headers).filter(\n ([name]) => name.toLowerCase() !== \"x-organization-id\",\n ),\n );\n return {\n ...withoutOrganizationHeader,\n \"X-Organization-Id\": normalizedOrganizationId,\n };\n}\n\n/**\n * Assert that an `organization_id` query parameter (when present) matches the\n * request context organization — the query string must never smuggle a\n * different org past the header binding.\n */\nexport function assertQueryOrganizationMatchesContext(\n queryParams: Record<string, string | number | boolean> | undefined,\n organizationId: string,\n): void {\n if (!queryParams || queryParams.organization_id === undefined) return;\n const queryOrganizationId = requireOrganizationContext(\n String(queryParams.organization_id),\n );\n if (queryOrganizationId !== requireOrganizationContext(organizationId)) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"Query organization_id must match the request context organization.\",\n );\n }\n}\n","/**\n * The AI API protocol-version policy — which version of the AI runtime a\n * Matrx client talks to, and the v2 → v1 transport fallback (moved in from\n * matrx-frontend `lib/api/ai-api-version.ts` + `call-api.ts::\n * fetchWithV2Fallback` under C22; server truth\n * `aidream/docs/runtime/V2_FRONTEND_MIGRATION.md`).\n *\n * Background: the Python backend exposes a `/v2` runtime-spine namespace that\n * wraps the core AI request surfaces in a request-tracking envelope. Body,\n * headers, and streaming response are BYTE-IDENTICAL to v1 — only the URL\n * differs.\n *\n * ─── The one rule that keeps burning us ─────────────────────────────────────\n * `/v2` is inserted at the FRONT of the in-app path, right before `/ai`:\n *\n * /ai/agents/{id} → /v2/ai/agents/{id} ✅ correct\n * /ai/v2/agents/… ❌ WRONG (nested) — 404s\n *\n * `toV2Path` below is the ONLY place this transform is spelled out.\n *\n * ─── Scope: ONLY the covered surfaces have a v2 form ────────────────────────\n * chat, manual, agents/{id}, conversations/{id} (+ singular aliases),\n * prompts/{id}, mandates/{key}. EVERYTHING ELSE — cancel, warm, resume,\n * fork-and-run, runtime operations, files … — has NO v2 route, and the server\n * does NOT auto-downgrade (a `/v2` request to an uncovered surface is a plain\n * 404). So the transform is a scoped allowlist, never a blanket prefix.\n */\n\nimport {\n isNetError,\n resilientFetch,\n type ResilientFetchOptions,\n} from \"@ai-matrx/data/net\";\n\nexport type MatrxAiApiVersion = \"v1\" | \"v2\";\n\n/**\n * The package-wide default AI API version — the production value every Matrx\n * client runs unless its host deliberately overrides\n * (`createMatrxTransport({ aiApiVersion })`).\n */\nexport const MATRX_AI_API_VERSION_DEFAULT: MatrxAiApiVersion = \"v2\";\n\n/**\n * The canonical v1 path TEMPLATES the v2 spine covers, `{param}` placeholders\n * intact — for hosts that route through a template registry.\n */\nexport const V2_COVERED_AI_PATH_TEMPLATES = [\n \"/ai/manual\",\n \"/ai/chat\",\n \"/ai/agents/{agent_id}\",\n \"/ai/agent/{agent_id}\", // singular alias — behaves identically\n \"/ai/conversations/{conversation_id}\",\n \"/ai/conversation/{conversation_id}\", // singular alias\n \"/ai/prompts/{prompt_id}\", // saved-prompt execution\n \"/ai/mandates/{mandate_key}\", // THE MANDATE DOOR — `/v2` sibling exists (routers/v2.py)\n] as const;\n\n/**\n * Insert `/v2` at the front of an in-app path. Idempotent, and prefix-aware:\n * a legacy `/api/` compatibility prefix (stripped server-side) is preserved so\n * `/api/ai/chat` → `/api/v2/ai/chat`.\n */\nexport function toV2Path(path: string): string {\n const withLead = path.startsWith(\"/\") ? path : `/${path}`;\n if (withLead.startsWith(\"/api/\")) {\n const rest = withLead.slice(\"/api\".length); // \"/ai/chat\"\n return rest.startsWith(\"/v2/\") ? withLead : `/api/v2${rest}`;\n }\n return withLead.startsWith(\"/v2/\") ? withLead : `/v2${withLead}`;\n}\n\n// Interpolated (concrete) forms of the covered surfaces — used to guard\n// callers that pass a real path (id already substituted). Anchored so ONLY the\n// exact shapes match: sub-paths like `/ai/agents/{id}/warm`,\n// `/ai/conversations/{id}/resume`, and `/ai/cancel/{id}` deliberately do NOT\n// match and stay on v1. The optional `/api` prefix mirrors `toV2Path`.\nconst COVERED_INTERPOLATED_PATH = [\n /^(?:\\/api)?\\/ai\\/manual$/,\n /^(?:\\/api)?\\/ai\\/chat$/,\n /^(?:\\/api)?\\/ai\\/agents\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/agent\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/conversations\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/conversation\\/[^/]+$/,\n /^(?:\\/api)?\\/ai\\/prompts\\/[^/]+$/, // `/warm` etc. deliberately don't match\n /^(?:\\/api)?\\/ai\\/mandates\\/[^/]+$/, // the mandate door\n];\n\n/** Whether `path` (already interpolated) is one of the covered surfaces. */\nexport function isCoveredAiPath(path: string): boolean {\n return COVERED_INTERPOLATED_PATH.some((re) => re.test(path));\n}\n\n/**\n * Whether an in-app path (or a full URL whose path was built by `toV2Path`)\n * targets the v2 namespace — the guard the downgrade fallback keys off.\n */\nexport function isV2Path(pathOrUrl: string): boolean {\n return /\\/v2\\/ai\\//.test(pathOrUrl);\n}\n\n/**\n * The inverse of `toV2Path` — strip the `/v2` version segment so a transport\n * failure of the v2 endpoint (network error, 404/405, 5xx BEFORE any stream\n * content) can retry the identical request on the v1 route. Works on a bare\n * path, an `/api`-prefixed path, or a full URL (the `/v2/ai/` shape is unique\n * to the version namespace). Idempotent on non-v2 input.\n */\nexport function toV1FallbackUrl(url: string): string {\n return url.replace(/\\/v2\\/ai\\//, \"/ai/\");\n}\n\n/**\n * Apply the active AI API version to an ALREADY-INTERPOLATED in-app path.\n *\n * - Covered surface + v2 → `/v2` prefix inserted.\n * - Anything else (or v1) → returned unchanged.\n *\n * Pass the in-app PATH only (no scheme/host); prepend the base URL afterward.\n */\nexport function applyAiApiVersion(\n path: string,\n version: MatrxAiApiVersion,\n): string {\n if (version !== \"v2\") return path;\n return isCoveredAiPath(path) ? toV2Path(path) : path;\n}\n\n/** One v2 → v1 downgrade, surfaced to the host's diagnostics sink. */\nexport interface MatrxProtocolDowngrade {\n /** The failed v2 URL. */\n url: string;\n /** Why the downgrade fired (thrown error text, or `HTTP <status>`). */\n reason: string;\n /** HTTP status when the trigger was a response (404/405/5xx). */\n status?: number;\n}\n\nexport interface MatrxProtocolFallbackOptions extends ResilientFetchOptions {\n /**\n * Fired on every downgrade — a sustained stream of these means a v2 surface\n * is unhealthy. The fallback also `console.warn`s unconditionally (parity\n * with the original host pipeline) so the signal never disappears silently.\n */\n onDowngrade?: (downgrade: MatrxProtocolDowngrade) => void;\n}\n\n/**\n * `resilientFetch` with the v2 → v1 transport fallback. Fires ONLY when a\n * `/v2/ai/...` ENDPOINT itself fails — a network-layer throw (non-abort), a\n * 404/405 (surface not on v2), or a 5xx — always BEFORE any stream content is\n * consumed. Never on an application error (those fail identically on v1), and\n * never on a caller abort (a user cancel must not be logged as a downgrade —\n * that poisons the exact telemetry the rollout reads to judge v2 health).\n */\nexport async function fetchWithMatrxProtocolFallback(\n url: string,\n init: RequestInit,\n opts: MatrxProtocolFallbackOptions = {},\n): Promise<{ response: Response }> {\n const { onDowngrade, ...fetchOpts } = opts;\n const logDowngrade = (reason: string, status?: number) => {\n console.warn(\n `[matrx] ai_v2_downgrade → retrying on v1. v2=${url} reason=${reason}`,\n );\n onDowngrade?.({ url, reason, ...(status !== undefined ? { status } : {}) });\n };\n if (!isV2Path(url)) return resilientFetch(url, init, fetchOpts);\n let result: { response: Response };\n try {\n result = await resilientFetch(url, init, fetchOpts);\n } catch (err) {\n // resilientFetch normalizes a caller-signal abort to NetError code\n // \"aborted\" (AbortedError); a raw AbortError is checked too for safety.\n const isAbort =\n (isNetError(err) && err.code === \"aborted\") ||\n (err instanceof Error && err.name === \"AbortError\");\n if (isAbort) throw err;\n logDowngrade(String(err));\n return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);\n }\n const status = result.response.status;\n if (status === 404 || status === 405 || status >= 500) {\n logDowngrade(`HTTP ${status}`, status);\n return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);\n }\n return result;\n}\n\n/**\n * The exact-match `pathOverrides` map for `resolveEndpointPath` — keyed on the\n * canonical templates, valued at their `/v2` siblings. Empty for v1. Every call\n * that flows through the endpoint-override registry picks up v2 for the covered\n * surfaces — and ONLY the covered surfaces. (Moved from matrx-frontend\n * `lib/api/ai-api-version.ts`, chat-package independence P9.)\n */\nexport function aiVersionPathOverrides(\n version: MatrxAiApiVersion,\n): Record<string, string> {\n if (version !== \"v2\") return {};\n const map: Record<string, string> = {};\n for (const template of V2_COVERED_AI_PATH_TEMPLATES) {\n map[template] = toV2Path(template);\n }\n return map;\n}\n","/**\n * `@ai-matrx/agents/matrx` — the Matrx transport port.\n *\n * The ONE seam between this package's wire semantics and a host's connection\n * policy. This package owns WHAT is said to the AI Matrx server — paths,\n * methods, JSON bodies, streaming Accept headers, the `Last-Event-ID` cursor\n * header — and the host owns HOW the connection is made:\n *\n * - base-URL / backend-channel resolution (global, sandbox override, local\n * engine, EC2-dedicated — whatever ladder the host runs);\n * - credentials (Supabase JWT `Authorization: Bearer`, guest\n * `X-Fingerprint-ID`, or the API-key lane) — NEVER implemented here;\n * - the `X-Organization-Id` context header;\n * - retry policy, network-level timeouts, and diagnostics capture.\n *\n * A host implements the port in a few lines:\n *\n * ```ts\n * const transport: MatrxTransport = {\n * fetch: (path, init) =>\n * fetch(`${baseUrl}${path}`, {\n * ...init,\n * headers: { ...init.headers, ...authHeaders() },\n * }),\n * };\n * ```\n *\n * The port is deliberately structural and `fetch`-shaped so `@ai-matrx/data`'s\n * `/api` transport can implement it without importing this package.\n */\n\nimport { isGenericUserMessage } from \"./backend-errors\";\n\n/**\n * The request this package hands the port. A strict subset of `RequestInit`,\n * so a host can spread it straight into `fetch`.\n */\nexport interface MatrxTransportRequest {\n method: \"GET\" | \"POST\";\n /**\n * Wire-semantic headers the CALL requires (`Content-Type`, `Accept`,\n * `Last-Event-ID`). The host merges its policy headers (auth, org) on top;\n * it must not drop these.\n */\n headers: Record<string, string>;\n /** Pre-serialized JSON body, present on POST calls that carry one. */\n body?: string;\n /** Caller cancellation. The host must wire it to the underlying fetch. */\n signal?: AbortSignal;\n}\n\n/**\n * The transport port. `path` is server-relative and always starts with `/`\n * (`/ai/...`, `/runtime/...`); the host prepends its resolved base URL.\n */\nexport interface MatrxTransport {\n fetch(path: string, init: MatrxTransportRequest): Promise<Response>;\n}\n\n/**\n * A non-2xx response from the Matrx API, with the server's structured error\n * body preserved and its richest human-readable message extracted.\n */\nexport class MatrxApiError extends Error {\n override readonly name = \"MatrxApiError\";\n /** HTTP status of the failed response. */\n readonly status: number;\n /** Machine code from the server body (`code`, or `detail.code`), when present. */\n readonly code: string | null;\n /** The parsed server error body, verbatim (undefined when unparsable). */\n readonly serverDetail: unknown;\n /** The request path the failure came from (server-relative). */\n readonly path: string;\n\n constructor(args: {\n status: number;\n path: string;\n serverDetail?: unknown;\n message?: string;\n }) {\n super(\n args.message ??\n extractMatrxErrorMessage(args.serverDetail) ??\n `HTTP ${args.status}`,\n );\n this.status = args.status;\n this.path = args.path;\n this.serverDetail = args.serverDetail;\n this.code = extractMatrxErrorCode(args.serverDetail);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction nonBlankString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\n/**\n * Extract the richest human-readable message from a Matrx/FastAPI error body.\n *\n * aidream 4xx validation errors look like\n * `{ error, user_message, details: [{ field, message, help }] }`; hand-raised\n * HTTPExceptions carry `{ detail: { code, message } }`; FastAPI's defaults are\n * `{ detail: string | [{ msg }] }`. Preference order: `user_message` →\n * `message` → joined `details[].message` → `detail.message` →\n * `detail` string → joined `detail[].msg`. Returns undefined for\n * unrecognized bodies so callers fall back to the bare status line.\n */\nexport function extractMatrxErrorMessage(\n serverDetail: unknown,\n): string | undefined {\n if (!isRecord(serverDetail)) return undefined;\n\n // Candidates in preference order. A generic `user_message` (\"Something went\n // wrong\") only wins when nothing more precise exists in the body.\n const candidates: string[] = [];\n const push = (value: string | undefined) => {\n if (value) candidates.push(value);\n };\n push(nonBlankString(serverDetail.user_message));\n push(nonBlankString(serverDetail.message));\n\n if (Array.isArray(serverDetail.details)) {\n const messages = serverDetail.details\n .map((entry: unknown) => {\n if (!isRecord(entry)) return undefined;\n const detailMessage = nonBlankString(entry.message);\n if (!detailMessage) return undefined;\n const field = nonBlankString(entry.field);\n return field ? `${field}: ${detailMessage}` : detailMessage;\n })\n .filter((m): m is string => typeof m === \"string\");\n if (messages.length > 0) push(messages.join(\"; \"));\n }\n\n const detail = serverDetail.detail;\n if (isRecord(detail)) {\n push(nonBlankString(detail.message) ?? nonBlankString(detail.user_message));\n }\n if (typeof detail === \"string\" && detail.trim()) push(detail);\n if (Array.isArray(detail)) {\n const messages = detail\n .map((entry: unknown) =>\n isRecord(entry) ? nonBlankString(entry.msg) : undefined,\n )\n .filter((m): m is string => typeof m === \"string\");\n if (messages.length > 0) push(messages.join(\"; \"));\n }\n return candidates.find((c) => !isGenericUserMessage(c)) ?? candidates[0];\n}\n\n/**\n * Extract the machine error code from a Matrx error body: top-level `code`,\n * else `detail.code` (the hand-raised HTTPException shape). Null when absent.\n */\nexport function extractMatrxErrorCode(serverDetail: unknown): string | null {\n if (!isRecord(serverDetail)) return null;\n const topLevel = nonBlankString(serverDetail.code);\n if (topLevel) return topLevel;\n const detail = serverDetail.detail;\n if (isRecord(detail)) {\n const nested = nonBlankString(detail.code);\n if (nested) return nested;\n }\n return null;\n}\n","/**\n * `@ai-matrx/agents/matrx` — THE request pipeline every typed Matrx server call\n * rides (chat package independence P9b). One implementation for every client:\n * matrx-frontend's `lib/api` `callApi` and the chat package's bare-host default\n * both build on it, and supply only their host facts — where the server is,\n * which credential and organization ride, and where diagnostics go.\n *\n * What lives here (pure, no host/window/env read):\n *\n * - `buildMatrxRequestUrl` — path params, the legacy `/api` strip, the query;\n * - `buildMatrxRequestBody` — scope injection (`organization_id` /\n * `project_id` / `task_id`), the UI-only field strip, and the fail-closed\n * body-vs-context organization check;\n * - `bareStatusSentence` / `isBareTransportCode` / `honestTransportMessage` —\n * a bare status code is never a sentence at a person;\n * - `parseMatrxNdjsonResponse` — a response body as typed envelopes, a broken\n * body reader classified as a resumable `StreamTransportError`;\n * - `executeMatrxCall` — the JSON or NDJSON execution over the v2 → v1\n * protocol fallback, with the stream callbacks and `consumeStream`;\n * - `sendMatrxRequest` / `readMatrxJsonResponse` — the raw-`Response` lane\n * for a host's imperative client (multipart bodies, byte downloads,\n * response-aware callers): one deadline, a caller abort kept as itself, and\n * the ONE HTTP error parser;\n * - `buildSafeRequestLog` / `redactUrlForRequestLog` /\n * `shouldReportMatrxCallError` — the log and capture policy.\n *\n * The error envelope (`MatrxCallError`) and its ONE classifier\n * (`normalizeMatrxError`) live in `./client`.\n */\n\nimport {\n readMatrxNdjsonStream,\n type MatrxNdjsonIssue,\n type MatrxStreamEnvelope,\n} from \"../stream/ndjson\";\nimport {\n BackendApiError,\n parseHttpError,\n StreamTransportError,\n} from \"./backend-errors\";\nimport type { MatrxCallError } from \"./client\";\nimport { OrganizationContextError } from \"./org-context\";\nimport {\n fetchWithMatrxProtocolFallback,\n type MatrxProtocolDowngrade,\n} from \"./protocol\";\nimport { extractMatrxErrorMessage } from \"./transport\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type MatrxHttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n\n/**\n * Every context dimension a call may carry. `organization_id`, `project_id`\n * and `task_id` are injected into the body; `user_id` rides the credential and\n * `conversation_id` the path or an explicit body field — never injected.\n */\nexport interface MatrxCallScope {\n user_id?: string;\n organization_id?: string;\n project_id?: string;\n task_id?: string;\n conversation_id?: string;\n}\n\nexport interface MatrxCallResult<T = unknown> {\n /** Parsed JSON response body (non-streaming calls only). */\n data?: T;\n /** Server-assigned request id (response header). */\n requestId?: string;\n /** Server-assigned conversation id (response header). */\n conversationId?: string;\n /** Set when the call failed with an HTTP error response. */\n error?: MatrxCallError;\n}\n\ntype MatrxQueryScalar = string | number | boolean;\n\n/**\n * Query values: a scalar is sent as itself, an array repeats the key (the\n * FastAPI repeatable-parameter convention), `null` / `undefined` are omitted.\n */\nexport type MatrxQueryParams = Record<\n string,\n MatrxQueryScalar | readonly MatrxQueryScalar[] | null | undefined\n>;\n\n// ─── URL ────────────────────────────────────────────────────────────────────\n\n/**\n * The full URL for one call: `{param}` segments substituted (encoded), the\n * legacy `/api` prefix stripped (server routes no longer live under it), and\n * the query appended.\n */\nexport function buildMatrxRequestUrl(\n baseUrl: string,\n pathTemplate: string,\n pathParams?: Record<string, string>,\n queryParams?: MatrxQueryParams,\n): string {\n let resolvedPath = pathTemplate;\n if (pathParams) {\n for (const [key, value] of Object.entries(pathParams)) {\n resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(value));\n }\n }\n const fullPath = resolvedPath.startsWith(\"/api/\")\n ? resolvedPath.slice(4)\n : resolvedPath;\n const url = `${baseUrl}${fullPath}`;\n if (queryParams) {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(queryParams)) {\n if (value === null || value === undefined) continue;\n const values: readonly MatrxQueryScalar[] = Array.isArray(value)\n ? value\n : [value as MatrxQueryScalar];\n for (const entry of values) search.append(key, String(entry));\n }\n const qs = search.toString();\n if (qs) return `${url}${url.includes(\"?\") ? \"&\" : \"?\"}${qs}`;\n }\n return url;\n}\n\n// ─── Body ───────────────────────────────────────────────────────────────────\n\n/**\n * Client capability flags that must never reach the server — the server's\n * request schemas reject them.\n */\nexport const MATRX_UI_ONLY_BODY_FIELDS: ReadonlySet<string> = new Set([\n \"youtube_videos\",\n \"file_urls\",\n \"image_urls\",\n]);\n\n/**\n * The final request body: UI-only fields stripped, scope fields injected.\n *\n * A caller's `organization_id: null` (or blank) means \"I have none of my own\",\n * never \"send this for a different organization\" — it is dropped and the\n * scope's organization injected. A real value that disagrees with the scope\n * is refused (`organization_context_mismatch`). With no scope organization\n * (the org-less guest lane, an org-free read) nothing is injected for it.\n * Other scope fields keep caller-wins behaviour.\n */\nexport function buildMatrxRequestBody(\n body: unknown,\n scope: MatrxCallScope,\n): Record<string, unknown> {\n // MATRX-EXCEPTION: `body` is optional by design — a caller with no body\n // still gets scope fields injected, so `{}` is the correct start.\n const raw = (body ?? {}) as Record<string, unknown>;\n const base: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!MATRX_UI_ONLY_BODY_FIELDS.has(key)) base[key] = value;\n }\n\n if (\n base.organization_id === null ||\n (typeof base.organization_id === \"string\" &&\n base.organization_id.trim() === \"\")\n ) {\n delete base.organization_id;\n }\n\n const bodyOrganizationId = base.organization_id;\n if (\n scope.organization_id !== undefined &&\n bodyOrganizationId !== undefined &&\n (typeof bodyOrganizationId !== \"string\" ||\n bodyOrganizationId.trim() !== scope.organization_id)\n ) {\n throw new OrganizationContextError(\n \"organization_context_mismatch\",\n \"Request body organization_id must match the request context organization.\",\n );\n }\n if (bodyOrganizationId !== undefined && scope.organization_id !== undefined) {\n base.organization_id = scope.organization_id;\n }\n\n const scopeFields: Record<string, unknown> = {\n ...(scope.organization_id !== undefined\n ? { organization_id: scope.organization_id }\n : {}),\n };\n if (scope.project_id !== undefined) scopeFields.project_id = scope.project_id;\n if (scope.task_id !== undefined) scopeFields.task_id = scope.task_id;\n return { ...scopeFields, ...base };\n}\n\n// ─── Honest status sentences ────────────────────────────────────────────────\n\nconst BARE_TRANSPORT_CODE =\n /^\\s*(?:HTTP|HTTP\\s*Error|Status(?:\\s*Code)?)?\\s*[:\\-]?\\s*\\d{3}\\s*[.:!]?\\s*$/i;\n\n/** True when a candidate sentence is really just the status line wearing words. */\nexport function isBareTransportCode(text: string | null | undefined): boolean {\n if (!text) return false;\n return BARE_TRANSPORT_CODE.test(text);\n}\n\n/**\n * A BARE STATUS CODE IS NEVER A SENTENCE. What a person reads when the server\n * answered an error with no readable message. The status itself rides\n * `error.status`, which is what code branches on — this is only the words.\n */\nexport function bareStatusSentence(status: number): string {\n if (status === 401 || status === 403) {\n return \"The server would not let this request through — your session may have expired, or this account may not have access here. Sign in again, and if it repeats, ask an administrator.\";\n }\n if (status === 404) {\n return \"The server has nothing at that address. Reload the page; if it repeats, report it — a client asking for something that no longer exists is a defect, not your mistake.\";\n }\n if (status === 429) {\n return \"The server is rate-limiting this request. Wait a few seconds and try again.\";\n }\n if (status >= 500) {\n return \"The server failed while answering this, and sent no explanation. Try again in a moment; if it repeats, report it with the request id above.\";\n }\n return \"The server refused this request and sent no reason with it — the missing reason is itself a defect worth reporting. Reload the page and try once more; if it repeats, report it with the request id above.\";\n}\n\n/** `raw` unless it is empty or only a status line; then the status sentence. */\nexport function honestTransportMessage(\n raw: string,\n status: number | undefined,\n): string {\n if (raw && !isBareTransportCode(raw)) return raw;\n return bareStatusSentence(status ?? 0);\n}\n\n// ─── Log + capture policy ───────────────────────────────────────────────────\n\nconst SENSITIVE_HEADER_NAME = /authorization|cookie|token|api[-_]?key/i;\n\n/** Request metadata safe to log: secret headers redacted, the body as its shape only. */\nexport function buildSafeRequestLog(\n headers: Record<string, string>,\n body: unknown,\n): { headers: Record<string, string>; body: Record<string, unknown> } {\n const safeHeaders = Object.fromEntries(\n Object.entries(headers).map(([name, value]) => [\n name,\n SENSITIVE_HEADER_NAME.test(name) ? \"[REDACTED]\" : value,\n ]),\n );\n const bodyMetadata: Record<string, unknown> = Array.isArray(body)\n ? { type: \"array\", itemCount: body.length }\n : body && typeof body === \"object\"\n ? { type: \"object\", keys: Object.keys(body as Record<string, unknown>) }\n : { type: body === null ? \"null\" : typeof body };\n return { headers: safeHeaders, body: bodyMetadata };\n}\n\n/** The URL with every query value redacted. */\nexport function redactUrlForRequestLog(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of parsed.searchParams.keys()) {\n parsed.searchParams.set(key, \"[REDACTED]\");\n }\n return parsed.toString();\n } catch {\n const queryIndex = url.indexOf(\"?\");\n return queryIndex === -1 ? url : `${url.slice(0, queryIndex)}?[REDACTED]`;\n }\n}\n\n/** False only for an HTTP status the call site declared an expected outcome. */\nexport function shouldReportMatrxCallError(\n status: number | null | undefined,\n expectedErrorStatuses: readonly number[] | undefined,\n): boolean {\n return status == null || !expectedErrorStatuses?.includes(status);\n}\n\n// ─── Stream parsing ─────────────────────────────────────────────────────────\n\nexport interface MatrxStreamIds {\n requestId: string | null;\n conversationId: string | null;\n}\n\nexport interface MatrxStreamParse<E = MatrxStreamEnvelope> extends MatrxStreamIds {\n events: AsyncGenerator<E, void, undefined>;\n}\n\nexport interface ParseMatrxNdjsonResponseHooks {\n /** Every envelope, before the consumer sees it. */\n onEvent?: (event: MatrxStreamEnvelope, ids: MatrxStreamIds) => void;\n /** A broken body reader, already classified, before it is thrown. */\n onTransportError?: (error: BackendApiError, ids: MatrxStreamIds) => void;\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n onUnknownEnvelope?: (value: unknown) => void;\n}\n\n/**\n * A Matrx NDJSON response as typed envelopes. The ids come from headers, so\n * they are available before any event. A body that breaks mid-run is a\n * TRANSPORT loss (the run may still finish server-side and is reattachable) —\n * thrown as `StreamTransportError`, never a failed run; an abort ends quietly.\n */\nexport function parseMatrxNdjsonResponse(\n response: Response,\n signal?: AbortSignal,\n hooks: ParseMatrxNdjsonResponseHooks = {},\n): MatrxStreamParse {\n const ids: MatrxStreamIds = {\n requestId: response.headers.get(\"X-Request-ID\"),\n conversationId: response.headers.get(\"X-Conversation-ID\"),\n };\n async function* events(): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n if (!response.body) {\n throw new BackendApiError({\n code: \"internal_error\",\n detail: \"Response has no body\",\n userMessage: \"No response received from server\",\n });\n }\n try {\n for await (const envelope of readMatrxNdjsonStream(response.body, {\n ...(signal ? { signal } : {}),\n ...(hooks.onMalformedLine ? { onMalformedLine: hooks.onMalformedLine } : {}),\n ...(hooks.onUnknownEnvelope ? { onUnknownEnvelope: hooks.onUnknownEnvelope } : {}),\n })) {\n hooks.onEvent?.(envelope, ids);\n yield envelope;\n }\n } catch (error) {\n if (signal?.aborted || (error instanceof Error && error.name === \"AbortError\")) {\n return;\n }\n const transportError =\n error instanceof BackendApiError\n ? error\n : new StreamTransportError({\n detail:\n error instanceof Error\n ? error.message\n : \"The response stream ended unexpectedly.\",\n details: error,\n ...(ids.requestId ? { requestId: ids.requestId } : {}),\n });\n hooks.onTransportError?.(transportError, ids);\n throw transportError;\n }\n }\n return { events: events(), ...ids };\n}\n\n// ─── Execution ──────────────────────────────────────────────────────────────\n\nexport interface ExecuteMatrxCallRequest<E = MatrxStreamEnvelope> {\n /** The final URL (`buildMatrxRequestUrl`). */\n url: string;\n method: string;\n /** Every header the host binds (credential, organization, Content-Type). */\n headers: Record<string, string>;\n /** The assembled body (`buildMatrxRequestBody`); never sent on GET/HEAD. */\n body: unknown;\n /** NDJSON streaming call. */\n stream?: boolean;\n signal?: AbortSignal;\n /** Time to response headers. Default 15_000. */\n connectTimeoutMs?: number;\n /** Whole-request cap for JSON calls. Default 30_000; `null` uncaps. Streams are uncapped. */\n totalTimeoutMs?: number | null;\n /** Fires when headers arrive, before any event. */\n onStreamStart?: (requestId: string | null, conversationId: string | null) => void;\n onStreamEvent?: (event: E) => void;\n /**\n * Take ownership of the body instead of the executor draining it (a body is\n * consumed once). `onStreamEvent` is then not called; start / complete /\n * error still fire.\n */\n consumeStream?: (response: Response, ids: MatrxStreamIds) => Promise<void>;\n onStreamComplete?: (requestId: string | null, conversationId: string | null) => void;\n /** Fires for an HTTP error response on a stream (thrown failures are the caller's). */\n onStreamError?: (error: MatrxCallError) => void;\n}\n\nexport interface ExecuteMatrxCallHooks<E = MatrxStreamEnvelope> {\n /** Every v2 → v1 protocol downgrade. */\n onProtocolDowngrade?: (downgrade: MatrxProtocolDowngrade) => void;\n /** The host's stream parser (default `parseMatrxNdjsonResponse`). */\n parseStream?: (response: Response, signal?: AbortSignal) => MatrxStreamParse<E>;\n}\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 15_000;\nconst DEFAULT_JSON_TOTAL_TIMEOUT_MS = 30_000;\n\nasync function httpErrorFrom(response: Response): Promise<MatrxCallError> {\n const serverDetail: unknown = await response.json().catch(() => undefined);\n return {\n type:\n response.status >= 400 && response.status < 500\n ? \"validation_error\"\n : \"http_error\",\n message:\n extractMatrxErrorMessage(serverDetail) ?? bareStatusSentence(response.status),\n status: response.status,\n serverDetail,\n };\n}\n\n/**\n * Execute one call over the v2 → v1 protocol fallback. An HTTP error\n * response resolves as `{ error }`; a thrown failure (network, timeout,\n * abort, a broken stream) propagates — the caller normalizes it with\n * `normalizeMatrxError` and decides what to capture.\n */\nexport async function executeMatrxCall<T = unknown, E = MatrxStreamEnvelope>(\n request: ExecuteMatrxCallRequest<E>,\n hooks: ExecuteMatrxCallHooks<E> = {},\n): Promise<MatrxCallResult<T>> {\n const upper = request.method.toUpperCase();\n const sendsBody = request.stream || (upper !== \"GET\" && upper !== \"HEAD\");\n const { response } = await fetchWithMatrxProtocolFallback(\n request.url,\n {\n method: request.method,\n headers: request.headers,\n ...(sendsBody ? { body: JSON.stringify(request.body) } : {}),\n },\n {\n ...(request.signal ? { signal: request.signal } : {}),\n connectTimeoutMs: request.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n totalTimeoutMs: request.stream\n ? null\n : request.totalTimeoutMs === undefined\n ? DEFAULT_JSON_TOTAL_TIMEOUT_MS\n : request.totalTimeoutMs,\n throwOnHttpError: false,\n ...(hooks.onProtocolDowngrade ? { onDowngrade: hooks.onProtocolDowngrade } : {}),\n },\n );\n\n const requestId = response.headers.get(\"X-Request-ID\");\n const conversationId = response.headers.get(\"X-Conversation-ID\");\n const idFields = {\n ...(requestId !== null ? { requestId } : {}),\n ...(conversationId !== null ? { conversationId } : {}),\n };\n\n if (!response.ok) {\n const error = await httpErrorFrom(response);\n if (request.stream) request.onStreamError?.(error);\n return { ...idFields, error };\n }\n\n if (!request.stream) {\n const data = (response.status === 204 ? undefined : await response.json()) as T;\n return { data, ...idFields };\n }\n\n if (request.consumeStream) {\n request.onStreamStart?.(requestId, conversationId);\n await request.consumeStream(response, { requestId, conversationId });\n request.onStreamComplete?.(requestId, conversationId);\n return idFields;\n }\n\n const parse =\n hooks.parseStream ??\n ((res: Response, signal?: AbortSignal) =>\n parseMatrxNdjsonResponse(res, signal) as unknown as MatrxStreamParse<E>);\n const parsed = parse(response, request.signal);\n request.onStreamStart?.(parsed.requestId, parsed.conversationId);\n for await (const event of parsed.events) {\n request.onStreamEvent?.(event);\n }\n request.onStreamComplete?.(parsed.requestId, parsed.conversationId);\n return {\n ...(parsed.requestId !== null ? { requestId: parsed.requestId } : {}),\n ...(parsed.conversationId !== null ? { conversationId: parsed.conversationId } : {}),\n };\n}\n\n// ─── Raw send ───────────────────────────────────────────────────────────────\n\nexport interface SendMatrxRequestOptions {\n /** Caller cancellation; an abort propagates as the caller's own AbortError. */\n signal?: AbortSignal;\n /**\n * Deadline (ms) from send to response headers. On expiry the call rejects\n * with a `BackendApiError` coded `request_timeout` (status 504) — never a\n * fake caller cancellation. Omit for no deadline.\n */\n timeoutMs?: number;\n}\n\n/**\n * Send one request and resolve with the raw `Response`, whatever its status.\n * The lane for an imperative host client whose bodies are not JSON (a\n * `FormData` upload), whose answers are bytes, or which reads the response\n * itself. A caller abort and a network failure propagate unchanged; only this\n * call's own deadline is classified (`request_timeout`).\n */\nexport async function sendMatrxRequest(\n url: string,\n init: RequestInit = {},\n options: SendMatrxRequestOptions = {},\n): Promise<Response> {\n const callerSignal = options.signal ?? init.signal ?? undefined;\n if (options.timeoutMs === undefined) {\n return fetch(url, { ...init, ...(callerSignal ? { signal: callerSignal } : {}) });\n }\n const controller = new AbortController();\n const onCallerAbort = (): void => controller.abort(callerSignal?.reason);\n if (callerSignal) {\n if (callerSignal.aborted) controller.abort(callerSignal.reason);\n else callerSignal.addEventListener(\"abort\", onCallerAbort, { once: true });\n }\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, options.timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (error) {\n if (timedOut) {\n throw new BackendApiError({\n code: \"request_timeout\",\n detail: `${init.method ?? \"GET\"} ${url} exceeded ${options.timeoutMs}ms`,\n userMessage: \"The request timed out — please retry.\",\n status: 504,\n });\n }\n throw error;\n } finally {\n clearTimeout(timer);\n callerSignal?.removeEventListener(\"abort\", onCallerAbort);\n }\n}\n\n/**\n * A response's JSON body, or the ONE classified `BackendApiError`\n * (`parseHttpError`) for a non-2xx answer. A 204 resolves `null`.\n */\nexport async function readMatrxJsonResponse<T>(response: Response): Promise<T> {\n if (!response.ok) throw await parseHttpError(response);\n if (response.status === 204) return null as T;\n return (await response.json()) as T;\n}\n","/**\n * Internal request plumbing for `@ai-matrx/agents/matrx`. Not part of the\n * public surface — `matrx/index.ts` deliberately does not re-export this\n * module. Everything here is pure: no globals, no work at import time.\n */\n\nimport {\n readMatrxNdjsonStream,\n type MatrxNdjsonIssue,\n type MatrxStreamEnvelope,\n type MatrxStreamEnvelopeObservation,\n} from \"../stream/ndjson\";\nimport { MatrxApiError, type MatrxTransport } from \"./transport\";\n\n/** Encode one path segment (an id) safely into a server-relative path. */\nexport function encodePathSegment(value: string): string {\n return encodeURIComponent(value);\n}\n\nexport type QueryValue =\n | string\n | number\n | boolean\n | readonly string[]\n | undefined;\n\n/**\n * Build a query string. Array values repeat the key (`kind=a&kind=b` — the\n * FastAPI repeatable-parameter convention); undefined values are omitted.\n * Returns \"\" or a string starting with \"?\".\n */\nexport function buildQuery(params: Record<string, QueryValue>): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const entry of value) search.append(key, entry);\n } else {\n search.append(key, String(value));\n }\n }\n const encoded = search.toString();\n return encoded ? `?${encoded}` : \"\";\n}\n\nasync function readServerDetail(response: Response): Promise<unknown> {\n try {\n return (await response.json()) as unknown;\n } catch {\n return undefined;\n }\n}\n\nasync function throwApiError(path: string, response: Response): Promise<never> {\n throw new MatrxApiError({\n status: response.status,\n path,\n serverDetail: await readServerDetail(response),\n });\n}\n\nexport interface JsonRequestOptions {\n method: \"GET\" | \"POST\";\n body?: unknown;\n signal?: AbortSignal;\n}\n\n/**\n * Execute a JSON request through the transport. Throws `MatrxApiError` on a\n * non-2xx response; resolves with the parsed JSON body otherwise.\n */\nexport async function requestJson<T>(\n transport: MatrxTransport,\n path: string,\n options: JsonRequestOptions,\n): Promise<T> {\n const hasBody = options.method !== \"GET\" && options.body !== undefined;\n const response = await transport.fetch(path, {\n method: options.method,\n headers: hasBody ? { \"Content-Type\": \"application/json\" } : {},\n ...(hasBody ? { body: JSON.stringify(options.body) } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) return throwApiError(path, response);\n return (await response.json()) as T;\n}\n\n/**\n * Options for every streaming call, riding the NDJSON kernel's contract.\n * Public via `./run`'s re-export.\n */\nexport interface MatrxStreamCallOptions {\n /** Abort the fetch and end the events iterator. */\n signal?: AbortSignal;\n /** Bounded background read-ahead (see `stream/ndjson`). */\n maxReadAhead?: number;\n /** Malformed NDJSON is non-fatal but must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx envelope. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope in its exact wire form. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\n/**\n * A live agent run: the server-assigned ids (from response headers, available\n * BEFORE any event) and the normalized event stream. Public via `./run`.\n */\nexport interface MatrxRunHandle {\n /** `X-Request-ID` — the ONLY id `POST /ai/cancel/{request_id}` accepts. */\n requestId: string | null;\n /** `X-Conversation-ID` — the server's conversation identity. */\n conversationId: string | null;\n /** Normalized `{event, data}` envelopes through the ONE wire kernel. */\n events: AsyncGenerator<MatrxStreamEnvelope, void, undefined>;\n /** The raw response, for hosts that need headers/status beyond the ids. */\n response: Response;\n}\n\n/**\n * Wrap a validated streaming Response into the run handle — the ONE place\n * the id headers are read and the NDJSON kernel is attached (`./run` and\n * `./operations`' rejoin share it).\n */\nexport function toRunHandle(\n response: Response,\n options: MatrxStreamCallOptions,\n): MatrxRunHandle {\n return {\n requestId: response.headers.get(\"X-Request-ID\"),\n conversationId: response.headers.get(\"X-Conversation-ID\"),\n events: readMatrxNdjsonStream(response.body as ReadableStream<Uint8Array>, {\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.maxReadAhead !== undefined\n ? { maxReadAhead: options.maxReadAhead }\n : {}),\n ...(options.onMalformedLine\n ? { onMalformedLine: options.onMalformedLine }\n : {}),\n ...(options.onUnknownEnvelope\n ? { onUnknownEnvelope: options.onUnknownEnvelope }\n : {}),\n ...(options.onValidEnvelope\n ? { onValidEnvelope: options.onValidEnvelope }\n : {}),\n }),\n response,\n };\n}\n\nexport interface StreamRequestOptions {\n method: \"GET\" | \"POST\";\n body?: unknown;\n /** Extra wire-semantic headers (`Accept`, `Last-Event-ID`). */\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\n/**\n * Execute a streaming request. Throws `MatrxApiError` on a non-2xx response\n * (reading the error body as JSON when possible) or when a 2xx response\n * carries no body; resolves with the validated `Response` otherwise.\n */\nexport async function requestStream(\n transport: MatrxTransport,\n path: string,\n options: StreamRequestOptions,\n): Promise<Response> {\n const hasBody = options.method !== \"GET\" && options.body !== undefined;\n const response = await transport.fetch(path, {\n method: options.method,\n headers: {\n ...(hasBody ? { \"Content-Type\": \"application/json\" } : {}),\n ...options.headers,\n },\n ...(hasBody ? { body: JSON.stringify(options.body) } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) return throwApiError(path, response);\n if (!response.body) {\n throw new MatrxApiError({\n status: response.status,\n path,\n serverDetail: { code: \"missing_response_body\" },\n message: \"The streaming response carried no body.\",\n });\n }\n return response;\n}\n","/**\n * `@ai-matrx/agents/portable/mcp` — an agent's server tools as an MCP server.\n *\n * `createAgentToolsMcpHandler` answers MCP JSON-RPC (`initialize`,\n * `tools/list`, `tools/call`, `ping`) for ONE agent. `tools/list` is the\n * bundle's server tools (resolved by the realtime-tools funnel on the server);\n * `tools/call` runs the tool through `POST /ai/tools/execute`, which\n * re-resolves the agent's set and refuses anything outside it, and writes the\n * same `cx_tool_call` row a turn-based call writes.\n *\n * Every call reads the credential FRESH from the `CredentialsPort` and carries\n * `X-Organization-Id` — nothing is cached, and the token never leaves this\n * process (the CLI only ever sees a URL). Signed out, a call answers one plain\n * sentence instead of failing obscurely.\n *\n * Transport-neutral: the host feeds it the parsed JSON body of each HTTP POST\n * (MCP \"streamable HTTP\") and writes back what it returns. Pure: no Node, no\n * React, no I/O at import.\n */\n\nimport type { CredentialsPort } from \"@ai-matrx/data\";\n\nimport { createMatrxTransport } from \"../matrx/client\";\nimport { requestJson } from \"../matrx/internal\";\nimport type { MatrxTransport } from \"../matrx/transport\";\nimport { MatrxApiError } from \"../matrx/transport\";\nimport type { JsonValue, PortableAgentBundle, PortableTool } from \"./types.generated\";\n\n/** The MCP protocol revisions this handler speaks, newest first. */\nexport const AGENT_TOOLS_MCP_PROTOCOL_VERSIONS = [\"2025-06-18\", \"2025-03-26\", \"2024-11-05\"] as const;\n\n/** The server name the CLIs see (`mcp__matrx-agent__<tool>` in Claude Code). */\nexport const AGENT_TOOLS_MCP_SERVER_NAME = \"matrx-agent\";\n\n/** What a signed-out tool call answers. */\nexport const SIGNED_OUT_TOOL_MESSAGE = \"Sign in to AI Matrx to use this tool.\";\n/** What a call with no organization answers. */\nexport const NO_ORGANIZATION_TOOL_MESSAGE = \"Pick an organization in AI Matrx to use this tool.\";\n\nexport interface AgentToolsMcpOptions {\n /** The bundle the session loaded — its `tools`, agent/version identity and `surface`. */\n bundle: Pick<PortableAgentBundle, \"agent_id\" | \"version_id\" | \"name\" | \"tools\" | \"surface\">;\n /** The credential source, read fresh per call. `null`/absent = signed out. */\n credentials?: CredentialsPort | null;\n /** The organization every call runs in (value, or a per-call getter that may be async). */\n organizationId?: string | null | (() => string | null | undefined | Promise<string | null | undefined>);\n /** AI Matrx server base URL, no trailing slash. */\n baseUrl: string;\n /**\n * One conversation id for the whole coding session, so its tool calls group\n * under one `cx_conversation`. Default: a fresh id per handler.\n */\n conversationId?: string;\n /** Test seam: replaces the transport built from the options above. */\n transport?: MatrxTransport;\n /** Every tool call's outcome, for the host's transcript or logs. */\n onToolCall?: (event: { name: string; ok: boolean; callId: string }) => void;\n}\n\n/** A JSON-RPC message, structurally. */\nexport interface McpJsonRpcMessage {\n jsonrpc?: string;\n id?: string | number | null;\n method?: string;\n params?: unknown;\n}\n\n/** What the host writes back: HTTP status, plus a JSON body when there is one. */\nexport interface McpHttpReply {\n status: number;\n body?: unknown;\n}\n\nexport interface AgentToolsMcpHandler {\n /** Handle one parsed HTTP POST body (a message or a batch). */\n handle(body: unknown): Promise<McpHttpReply>;\n /** The tools `tools/list` returns, in MCP shape. */\n listTools(): McpToolDescriptor[];\n}\n\nexport interface McpToolDescriptor {\n name: string;\n description: string;\n inputSchema: Record<string, JsonValue>;\n}\n\ninterface ToolExecuteResponse {\n call_id: string;\n ok: boolean;\n output: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction newId(): string {\n return globalThis.crypto.randomUUID();\n}\n\nfunction toDescriptor(tool: PortableTool): McpToolDescriptor {\n const schema = isRecord(tool.parameters) && tool.parameters.type === \"object\"\n ? tool.parameters\n : { type: \"object\", properties: {} };\n return { name: tool.name, description: tool.description || tool.canonical_name, inputSchema: schema };\n}\n\nfunction rpcResult(id: McpJsonRpcMessage[\"id\"], result: unknown): unknown {\n return { jsonrpc: \"2.0\", id: id ?? null, result };\n}\n\nfunction rpcError(id: McpJsonRpcMessage[\"id\"], code: number, message: string): unknown {\n return { jsonrpc: \"2.0\", id: id ?? null, error: { code, message } };\n}\n\nfunction textResult(text: string, isError: boolean): unknown {\n return { content: [{ type: \"text\", text }], isError };\n}\n\nexport function createAgentToolsMcpHandler(options: AgentToolsMcpOptions): AgentToolsMcpHandler {\n const { bundle } = options;\n const conversationId = options.conversationId ?? newId();\n const descriptors = bundle.tools.map(toDescriptor);\n const known = new Set(bundle.tools.map((tool) => tool.name));\n\n const resolveOrganization = async (): Promise<string | null> => {\n const raw = typeof options.organizationId === \"function\"\n ? await options.organizationId()\n : options.organizationId;\n return raw && raw.trim() ? raw.trim() : null;\n };\n\n const transportFor = (organizationId: string): MatrxTransport =>\n options.transport ??\n createMatrxTransport({\n baseUrl: options.baseUrl,\n ...(options.credentials ? { credentials: options.credentials } : {}),\n organizationId,\n source: \"agentToolsMcp\",\n });\n\n async function callTool(id: McpJsonRpcMessage[\"id\"], params: unknown): Promise<unknown> {\n if (!isRecord(params) || typeof params.name !== \"string\") {\n return rpcError(id, -32602, \"tools/call needs a tool name.\");\n }\n const name = params.name;\n if (!known.has(name)) return rpcError(id, -32602, `Unknown tool: ${name}`);\n const args = isRecord(params.arguments) ? params.arguments : {};\n\n const credential = options.credentials ? await options.credentials.get() : null;\n if (!credential) return rpcResult(id, textResult(SIGNED_OUT_TOOL_MESSAGE, true));\n const organizationId = await resolveOrganization();\n if (!organizationId) return rpcResult(id, textResult(NO_ORGANIZATION_TOOL_MESSAGE, true));\n\n const callId = newId();\n const pinned = typeof bundle.version_id === \"string\" && bundle.version_id.length > 0;\n try {\n const response = await requestJson<ToolExecuteResponse>(\n transportFor(organizationId),\n \"/ai/tools/execute\",\n {\n method: \"POST\",\n body: {\n agent_id: pinned ? bundle.version_id : bundle.agent_id,\n is_version: pinned,\n conversation_id: conversationId,\n tool_name: name,\n arguments: args,\n call_id: callId,\n surface: bundle.surface,\n },\n },\n );\n options.onToolCall?.({ name, ok: response.ok, callId });\n return rpcResult(id, textResult(response.output, !response.ok));\n } catch (error) {\n options.onToolCall?.({ name, ok: false, callId });\n if (error instanceof MatrxApiError && error.status === 401) {\n return rpcResult(id, textResult(SIGNED_OUT_TOOL_MESSAGE, true));\n }\n const message = error instanceof Error ? error.message : String(error);\n return rpcResult(id, textResult(`AI Matrx could not run ${name}: ${message}`, true));\n }\n }\n\n async function handleOne(message: unknown): Promise<unknown | null> {\n if (!isRecord(message) || typeof message.method !== \"string\") {\n return rpcError(null, -32600, \"Invalid request.\");\n }\n const { id, params } = message as unknown as McpJsonRpcMessage;\n const method = message.method;\n const isNotification = id === undefined;\n switch (method) {\n case \"initialize\": {\n const requested = isRecord(params) ? params.protocolVersion : undefined;\n const protocolVersion =\n typeof requested === \"string\" &&\n (AGENT_TOOLS_MCP_PROTOCOL_VERSIONS as readonly string[]).includes(requested)\n ? requested\n : AGENT_TOOLS_MCP_PROTOCOL_VERSIONS[0];\n return rpcResult(id, {\n protocolVersion,\n capabilities: { tools: { listChanged: false } },\n serverInfo: { name: AGENT_TOOLS_MCP_SERVER_NAME, version: \"1\" },\n instructions: `Tools of the AI Matrx agent \"${bundle.name}\". They run on AI Matrx as the signed-in person.`,\n });\n }\n case \"ping\":\n return isNotification ? null : rpcResult(id, {});\n case \"tools/list\":\n return rpcResult(id, { tools: descriptors });\n case \"tools/call\":\n return callTool(id, params);\n default:\n if (isNotification) return null; // notifications/initialized and friends\n return rpcError(id, -32601, `Method not found: ${method}`);\n }\n }\n\n return {\n listTools: () => descriptors,\n async handle(body) {\n if (Array.isArray(body)) {\n const replies = (await Promise.all(body.map(handleOne))).filter((r) => r !== null);\n return replies.length ? { status: 200, body: replies } : { status: 202 };\n }\n const reply = await handleOne(body);\n return reply === null ? { status: 202 } : { status: 200, body: reply };\n },\n };\n}\n"],"mappings":"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","names":["isNetError","status","isNetError","isRecord"]}
|