@oxy-hq/sdk 2.0.1 → 2.1.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/customer-app/logger.ts","../src/customer-app/debug.ts","../src/customer-app/errors.ts","../src/customer-app/inject.ts","../src/customer-app/manifest.ts","../src/customer-app/interpolate.ts","../src/customer-app/markdown.ts","../src/customer-app/react.tsx"],"sourcesContent":["// Diagnostic logger for customer-app bundles.\n//\n// Customer apps are built by our internal team; \"open DevTools, read\n// the logs\" is a real debugging workflow. The SDK logs every fetch\n// lifecycle (start, success, error) with structured context so an\n// internal dev can correlate UI behavior with what hit the wire\n// without needing server logs.\n//\n// Defaults to console at info level with a `[oxy-app]` prefix.\n// Override via `setOxyAppLogger(...)` for tests or production silence.\n//\n// Log lines are formatted as:\n// [oxy-app] <event> { …structured ctx… }\n// so DevTools' object inspector unfolds them.\n\nexport type OxyAppLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface OxyAppLogger {\n log(level: OxyAppLogLevel, msg: string, ctx?: Record<string, unknown>): void;\n}\n\nlet activeLogger: OxyAppLogger = createConsoleLogger();\n\n/** Replace the global logger. Pass `null` to silence everything. */\nexport function setOxyAppLogger(logger: OxyAppLogger | null): void {\n activeLogger = logger ?? silentLogger();\n}\n\n/** Used by the SDK internals; not part of the public surface. */\nexport function getOxyAppLogger(): OxyAppLogger {\n return activeLogger;\n}\n\nfunction createConsoleLogger(): OxyAppLogger {\n return {\n log(level, msg, ctx) {\n if (typeof console === \"undefined\") return;\n const prefix = \"[oxy-app]\";\n const args: unknown[] = ctx ? [prefix, msg, ctx] : [prefix, msg];\n switch (level) {\n case \"debug\":\n console.debug(...args);\n break;\n case \"info\":\n console.info(...args);\n break;\n case \"warn\":\n console.warn(...args);\n break;\n case \"error\":\n console.error(...args);\n break;\n }\n }\n };\n}\n\nfunction silentLogger(): OxyAppLogger {\n return { log() {} };\n}\n","// Bundle-side accessor for the server's diagnostic snapshot.\n//\n// `GET /api/customer-apps/<org>/<app>/debug` returns a structured\n// snapshot of what oxy currently sees about a registered customer\n// app: the app row, bundle dir resolution, and parsed manifest (or\n// parse error). Useful when a bundle isn't loading what you expected\n// and you want to verify what the server actually sees — without\n// needing terminal access.\n//\n// The `products` field on the snapshot is a legacy artifact carried\n// for server-side compatibility; in v2 the bundle owns its queries\n// via `useQuery` and the field is always empty for v2 manifests.\n\nimport { getOxyAppLogger } from \"./logger\";\nimport type { ResolvedCustomerAppManifest } from \"./manifest\";\n\n/** Untyped at the boundary — keep it loose so server-side schema\n * additions don't break older bundles. Stable enough for inspection\n * but not a contract clients should depend on field-by-field. */\nexport interface CustomerAppDebugSnapshot {\n org_slug: string;\n app_slug: string;\n app: {\n id: string;\n slug: string;\n name: string;\n status: string;\n source_type: string;\n project_id: string;\n branch: string;\n };\n bundle_dir: string | null;\n bundle_dir_exists: boolean;\n /** Raw parsed manifest from the server — kept loose so schema additions don't break older bundles. */\n manifest: Record<string, unknown> | null;\n manifest_error: string | null;\n products: Array<{ name: string; producer: string }>;\n}\n\n/**\n * Fetch the server-side diagnostic snapshot for this bundle. Pair with\n * `loadCustomerAppManifest()` — pass its result here. Logs the\n * snapshot through the SDK logger so it appears in the bundle's\n * console at info level.\n */\nexport async function getCustomerAppDebug(\n resolved: ResolvedCustomerAppManifest\n): Promise<CustomerAppDebugSnapshot> {\n const log = getOxyAppLogger();\n const { apiBaseUrl, orgSlug, appSlug } = resolved;\n const url =\n `${apiBaseUrl}/api/customer-apps/` +\n `${encodeURIComponent(orgSlug)}/${encodeURIComponent(appSlug)}/debug`;\n\n log.log(\"debug\", \"fetching debug snapshot\", { url });\n const res = await fetch(url, { credentials: \"same-origin\" });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch debug snapshot (HTTP ${res.status}): ${detail || res.statusText}`\n );\n }\n const snapshot = (await res.json()) as CustomerAppDebugSnapshot;\n log.log(\"info\", \"debug snapshot\", snapshot as unknown as Record<string, unknown>);\n return snapshot;\n}\n","// Friendly interpretation of the errors a customer-app bundle can hit\n// at startup. The bundle catches an `Error` thrown by\n// `loadCustomerAppManifest` or `useQuery`, hands it to\n// `interpretCustomerAppError`, and renders the returned struct as a\n// proper error page — instead of dumping a raw exception that asks\n// the developer to learn the internal contract from a stack trace.\n//\n// Every interpretation includes:\n// - `title`: short headline (\"Manifest not found\")\n// - `message`: the underlying technical message (the raw err.message)\n// - `hint`: an actionable next step (\"commit public/oxy-app.json and rebuild\")\n// - `docs`: a pointer to the relevant section of the architecture doc\n//\n// Add cases as we hit new failure modes in the wild — the catch-all\n// keeps the surface safe in the meantime.\n\nexport interface CustomerAppErrorReport {\n title: string;\n message: string;\n hint: string;\n docs?: string;\n}\n\nconst ARCH_DOC = \"internal-docs/customer-apps.md\";\n\n/** Interpret a thrown error as a structured report for UI display. */\nexport function interpretCustomerAppError(err: unknown): CustomerAppErrorReport {\n const message = err instanceof Error ? err.message : String(err);\n\n // Order matters — earlier matches take priority. Use specific\n // substrings that the loader / fetcher actually emit so this stays\n // grep-discoverable from both ends.\n\n if (/Failed to load oxy-app\\.json.*HTTP 404/.test(message)) {\n return {\n title: \"Manifest not found\",\n message,\n hint:\n \"The bundle is being served, but oxy-app.json was not. Check that \" +\n \"public/oxy-app.json is committed in the customer-app repo and \" +\n \"that the build copied it into the static output. If you're using \" +\n \"Next.js, anything under public/ is auto-copied to out/.\",\n docs: ARCH_DOC\n };\n }\n\n if (/Failed to load oxy-app\\.json/.test(message)) {\n return {\n title: \"Manifest could not be loaded\",\n message,\n hint:\n \"Network error fetching the manifest. Confirm the bundle is being \" +\n \"served from a path that matches OXY_APP_BASE_PATH at \" +\n \"build time — a mismatch causes assets and the manifest to 404.\",\n docs: ARCH_DOC\n };\n }\n\n if (/schemaVersion/i.test(message)) {\n return {\n title: \"Manifest schema mismatch\",\n message,\n hint:\n \"This bundle was built against a different version of the \" +\n \"data-product contract than the SDK it ships. Rebuild the bundle \" +\n \"with a compatible @oxy-hq/sdk version.\",\n docs: ARCH_DOC\n };\n }\n\n // Query proxy responses. The `${status}: ${body}` shape comes from\n // useQuery — body is the server's `{ \"message\": \"...\" }` JSON for\n // structured errors, or raw text for unstructured ones.\n\n if (/^401:/m.test(message)) {\n return {\n title: \"Session expired\",\n message,\n hint: \"Reload the page to re-authenticate via oxy's session cookie.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*origin not allowed/im.test(message)) {\n return {\n title: \"Request origin not allowed\",\n message,\n hint:\n \"The bundle's host isn't in oxy's OXY_ALLOWED_ORIGINS. \" +\n \"Production: add the bundle's serving origin to the env var. \" +\n \"Local dev: oxy auto-allows http://localhost:5173 and :5174.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*not a member/im.test(message)) {\n return {\n title: \"Access denied\",\n message,\n hint:\n \"Your account isn't a member of the org that owns this project. \" +\n \"Ask an org owner to add you.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*SELECT.*WITH/im.test(message)) {\n return {\n title: \"Query rejected — read-only endpoint\",\n message,\n hint:\n \"This proxy only runs SELECT or WITH queries. Mutations \" +\n \"(INSERT/UPDATE/DELETE/DROP) are not allowed from customer-app \" +\n \"bundles.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:/m.test(message)) {\n return {\n title: \"Access denied\",\n message,\n hint: \"The request was rejected by the server. Check the oxy server \" + \"logs for details.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^404:/m.test(message) && /project/i.test(message)) {\n return {\n title: \"Project not found\",\n message,\n hint:\n \"The projectId in oxy-app.json doesn't match any registered \" +\n \"project. Confirm the manifest's projectId is a real UUID for \" +\n \"this deployment.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^400:.*sql.*must be non-empty/im.test(message)) {\n return {\n title: \"Empty SQL\",\n message,\n hint:\n \"useQuery was called with an empty or whitespace-only `sql`. \" +\n \"Pass a real query, or set `enabled: false` to skip the call.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^400:/m.test(message) && /query failed/i.test(message)) {\n return {\n title: \"Query failed\",\n message,\n hint:\n \"The SQL ran but the warehouse rejected it. Full error in the \" +\n \"oxy server logs (look for the projects::query span).\",\n docs: ARCH_DOC\n };\n }\n\n if (/^502:/m.test(message)) {\n return {\n title: \"Warehouse unreachable\",\n message,\n hint:\n \"Oxy couldn't reach the configured database. Check connector \" +\n \"config + warehouse health.\",\n docs: ARCH_DOC\n };\n }\n\n // \"Unexpected token '<', '<!doctype '...\" — the bundle asked for JSON\n // at a path that oxy resolved to its SPA-fallback HTML.\n //\n // In v2 the single most likely cause is: the built bundle is stale.\n // It was built against an older SDK whose useQuery / fetchers pointed\n // at endpoints that no longer exist (e.g. the deleted /products/...\n // route), so the request resolved to the SPA fallback and returned\n // index.html where the bundle expected a JSON body.\n if (/Unexpected token '<'|<!doctype/i.test(message)) {\n return {\n title: \"Fetched HTML where JSON was expected\",\n message,\n hint:\n \"Most likely the built bundle is stale — built against an old \" +\n \"SDK whose endpoints no longer exist on the server. Rebuild the \" +\n \"bundle (vite build) with @oxy-hq/sdk@^2.0.0 and reload. If \" +\n \"the bundle is current, check that OXY_APP_BASE_PATH matches \" +\n \"the path the customer-app row is served at.\",\n docs: ARCH_DOC\n };\n }\n\n // Catch-all — surfaces the raw message but with a generic next step.\n return {\n title: \"Unexpected error loading the dashboard\",\n message,\n hint:\n \"Check the browser console for the full stack trace, and the oxy \" +\n \"server logs for the corresponding request.\",\n docs: ARCH_DOC\n };\n}\n","// Runtime app-config injected into the browser by oxy when it serves\n// a customer-app bundle's HTML. Lets a single bundle serve any\n// registered app without having `(orgId, projectId)` baked in at\n// build time — see\n// `crates/app/src/server/api/customer_apps_serve.rs::inject_app_config`\n// on the server side.\n\n/**\n * Shape of `window.__OXY_APP__` written by oxy at serve time.\n * Consumed by `loadCustomerAppManifest` as the authoritative identity\n * source (overrides any hints in `oxy-app.json`).\n */\nexport interface OxyInjectedAppConfig {\n appId: string;\n slug: string;\n orgId: string;\n orgSlug: string;\n projectId: string;\n branch: string;\n /** Empty string means same-origin (the default for v2). */\n apiBaseUrl: string;\n}\n\ndeclare global {\n interface Window {\n __OXY_APP__?: OxyInjectedAppConfig;\n }\n}\n\n/**\n * Read the runtime app-config oxy injected at serve time. Returns\n * `undefined` outside the browser or when the global isn't set\n * (`pnpm dev` against a non-oxy server, etc. — manifest hints are\n * the fallback).\n */\nexport function readInjectedAppConfig(): OxyInjectedAppConfig | undefined {\n if (typeof window === \"undefined\") return undefined;\n return window.__OXY_APP__;\n}\n","// Manifest loader for customer-app bundles served by oxy at\n// `app.oxy.tech/customer-apps/<org_slug>/<app_slug>/`.\n//\n// The bundle commits a `public/oxy-app.json` declaring its identity\n// (slug, orgSlug, projectId). This module:\n// 1. Fetches that manifest at startup (cached after the first call).\n// 2. Validates the schema with clear errors (v2 only — v1 is rejected).\n// 3. Joins it with the runtime identity oxy injects via\n// `<script>window.__OXY_APP__=...</script>`.\n//\n// Bundles call `useQuery` directly for data access — there are no\n// `products` or `writers` declarations in v2 manifests.\n\nimport { type OxyInjectedAppConfig, readInjectedAppConfig } from \"./inject\";\nimport { getOxyAppLogger } from \"./logger\";\n\n// ── Manifest types ──────────────────────────────────────────────────────────\n\n/** Wire shape of `oxy-app.json` (v2 only). */\nexport interface OxyAppManifest {\n /** Must be 2. v1 manifests are no longer supported. */\n schemaVersion: 2;\n /**\n * Optional display name. The admin \"Link existing\" dialog prefills\n * its Name field from this. Omit to let oxy fall back to the\n * folder basename.\n */\n name?: string;\n /**\n * URL slug. **Required.** The canonical source of truth — the\n * dialog locks the slug field to this value, and\n * `OXY_APP_BASE_PATH=/customer-apps/<org>/<slug>/` baked into the\n * build must match.\n */\n slug: string;\n /**\n * Optional org slug. Prefills the dialog's org picker; operator\n * can still override. Carries no security weight — the actual\n * access check is on the linked row.\n */\n orgSlug?: string;\n /**\n * Optional project (workspace) uuid the bundle expects to read\n * from. Used by `useQuery` to construct the\n * `/api/projects/:id/query` URL.\n */\n projectId?: string;\n}\n\n// ── Resolved manifest ───────────────────────────────────────────────────────\n\n/**\n * Manifest + runtime-injected identity needed to call oxy. Callers\n * should treat this as the only source of truth for \"which org/app\n * does this bundle belong to.\"\n */\nexport interface ResolvedCustomerAppManifest {\n manifest: OxyAppManifest;\n /**\n * Always an empty array for v2 manifests. Kept for API compatibility;\n * callers that previously iterated product names should switch to\n * explicit `useQuery` calls.\n * @deprecated Will be removed in a future version.\n */\n productNames: string[];\n /** Org slug injected by oxy. */\n orgSlug: string;\n /** App slug injected by oxy. */\n appSlug: string;\n /**\n * The oxy server's API base URL. Empty string when oxy serves the\n * bundle itself (same-origin, the common case); a full URL only\n * when the bundle is running under a dev server proxy.\n */\n apiBaseUrl: string;\n /** App UUID; informational. */\n appId?: string;\n /**\n * Project (workspace) UUID. Injection (`window.__OXY_APP__.projectId`)\n * wins over the manifest's `projectId` field — the admin row is\n * authoritative. Manifest `projectId` is a dev-time hint used only\n * when running without a server. Used by `useQuery` to construct the\n * `/api/projects/:id/query` URL.\n */\n projectId?: string;\n}\n\nexport interface LoadManifestOptions {\n /**\n * Override the URL the manifest is fetched from. Default:\n * `<injected_base>/oxy-app.json` or `/oxy-app.json`.\n * Useful for non-Next bundlers — set explicitly to wherever your\n * bundler emits static assets.\n */\n manifestUrl?: string;\n}\n\nlet cached: Promise<ResolvedCustomerAppManifest> | null = null;\n\n/**\n * Load + validate the manifest. Cached after the first call so callers\n * can invoke this from every component without coordinating.\n */\nexport function loadCustomerAppManifest(\n options: LoadManifestOptions = {}\n): Promise<ResolvedCustomerAppManifest> {\n if (!cached) {\n cached = fetchAndValidate(options);\n }\n return cached;\n}\n\n/** For tests: reset the cache between runs. */\nexport function _resetCustomerAppManifestCacheForTest(): void {\n cached = null;\n}\n\nasync function fetchAndValidate(\n options: LoadManifestOptions\n): Promise<ResolvedCustomerAppManifest> {\n const log = getOxyAppLogger();\n const injected = readInjectedAppConfig();\n const manifestUrl = options.manifestUrl ?? defaultManifestUrl(injected);\n\n log.log(\"info\", \"loading manifest\", {\n manifestUrl,\n injectionPresent: !!injected,\n orgSlug: injected?.orgSlug,\n appSlug: injected?.slug,\n appId: injected?.appId\n });\n\n const startedAt = Date.now();\n const res = await fetch(manifestUrl, { credentials: \"same-origin\" });\n if (!res.ok) {\n log.log(\"error\", \"manifest fetch failed\", {\n manifestUrl,\n status: res.status,\n statusText: res.statusText\n });\n throw new Error(\n `Failed to load oxy-app.json from ${manifestUrl} (HTTP ${res.status}). ` +\n `The customer-app repo must commit this file alongside the bundle.`\n );\n }\n const raw = (await res.json()) as unknown;\n const manifest = validateManifest(raw, manifestUrl);\n\n const resolved: ResolvedCustomerAppManifest = {\n manifest,\n productNames: [],\n orgSlug: injected?.orgSlug ?? \"\",\n appSlug: injected?.slug ?? \"\",\n apiBaseUrl: injected?.apiBaseUrl || \"\",\n appId: injected?.appId,\n projectId: injected?.projectId ?? manifest.projectId\n };\n log.log(\"info\", \"manifest ready\", {\n durationMs: Date.now() - startedAt,\n schemaVersion: manifest.schemaVersion,\n slug: manifest.slug\n });\n return resolved;\n}\n\n/**\n * Default manifest URL.\n *\n * Resolution order (bundler-agnostic):\n * 1. `window.__OXY_APP__.orgSlug`/`slug` injection → the canonical\n * `/customer-apps/<org>/<app>/oxy-app.json`. Works for every\n * bundle oxy serves regardless of how it was built.\n * 2. `NEXT_PUBLIC_APP_BASE_PATH` env var — kept for backward compat\n * with Next.js bundles that bake basePath at build time.\n * 3. Empty basePath → `/oxy-app.json` (only matches when running in\n * a `vite dev` / `next dev` root mount; will 404 under oxy).\n */\nfunction defaultManifestUrl(injected: OxyInjectedAppConfig | undefined): string {\n if (injected?.orgSlug && injected?.slug) {\n const org = encodeURIComponent(injected.orgSlug);\n const app = encodeURIComponent(injected.slug);\n return `/customer-apps/${org}/${app}/oxy-app.json`;\n }\n // No injection → bundle is running outside oxy (`pnpm dev` against\n // a local Vite, an iframe preview, etc.). Look up `/oxy-app.json`\n // at the document root; the vite-plugin's dev shim and the\n // standard `public/` convention both serve it there.\n return \"/oxy-app.json\";\n}\n\n// ── Validation ──────────────────────────────────────────────────────────────\n\n/**\n * Validate a v2 manifest. Required: schemaVersion === 2, slug (non-empty).\n * Optional: name (display), orgSlug (dev-time hint for the admin dialog),\n * projectId (dev-time hint when there's no server-side injection).\n *\n * At serve time, oxy's identity injection (window.__OXY_APP__) overrides\n * the manifest's orgSlug/projectId — the manifest fields are advisory.\n */\nfunction validateManifest(raw: unknown, url: string): OxyAppManifest {\n if (!isRecord(raw)) {\n throw new Error(`Manifest at ${url} is not a JSON object`);\n }\n if (raw.schemaVersion !== 2) {\n throw new Error(\n `oxy-app.json: schemaVersion must be 2 (got ${JSON.stringify(raw.schemaVersion)}). ` +\n `v1 manifests are no longer supported — upgrade to the identity-only shape.`\n );\n }\n if (raw.products !== undefined || raw.writers !== undefined) {\n throw new Error(\n `oxy-app.json is schemaVersion 2 (identity-only); \\`products\\` and \\`writers\\` are no longer supported`\n );\n }\n if (typeof raw.slug !== \"string\" || !raw.slug.trim()) {\n throw new Error(\"oxy-app.json: `slug` is required and must be a non-empty string\");\n }\n\n const name = typeof raw.name === \"string\" ? raw.name : undefined;\n const slug = raw.slug;\n const orgSlug = typeof raw.orgSlug === \"string\" ? raw.orgSlug : undefined;\n const projectId = typeof raw.projectId === \"string\" ? raw.projectId : undefined;\n\n return { schemaVersion: 2, name, slug, orgSlug, projectId };\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","/**\n * Interpolate `{{ params.X }}` and `{{ params.X | sqlquote }}` placeholders\n * in a SQL template.\n *\n * - `{{ params.X | sqlquote }}` — quote strings ('foo'), pass numbers and\n * booleans raw, nullish becomes NULL. Mirrors the server's Jinja sqlquote\n * filter.\n * - `{{ params.X }}` — raw pass-through. Used for already-trusted values\n * (numbers, identifiers the caller has validated). Caller is responsible\n * for safety.\n *\n * Not a security boundary. The server still gates SQL execution by\n * project membership. Bundles that accept untrusted user input should\n * use `| sqlquote` or validate/coerce before passing.\n */\nexport function interpolateSqlParams(\n sql: string,\n params: Record<string, string | number | boolean | null | undefined>\n): string {\n return sql.replace(\n /\\{\\{\\s*params\\.([a-zA-Z0-9_]+)(\\s*\\|\\s*sqlquote)?\\s*\\}\\}/g,\n (_match, key: string, sqlquote: string | undefined) => {\n const v = params[key];\n if (v === null || v === undefined) return \"NULL\";\n if (sqlquote) {\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n return `'${String(v).replace(/'/g, \"''\")}'`;\n }\n // No filter — raw pass-through. Caller is responsible.\n return String(v);\n }\n );\n}\n","// Markdown helpers used by `<OxyAnswer>`.\n//\n// Extracted from `react.tsx` so they're testable without spinning up\n// a React renderer in unit tests. The renderer itself stays in\n// `react.tsx` because it's JSX-heavy.\n\n/**\n * Allowlist for `[text](url)` href values in agent-emitted markdown.\n * Markdown comes from an LLM, which sits across an external trust\n * boundary — without this filter, a `javascript:` URL produced by\n * the model would render as a clickable XSS in the bundle's origin.\n *\n * Accepts:\n * - http(s):// absolute URLs\n * - mailto: addresses\n * - root-relative paths (`/foo`)\n * - same-page fragments (`#section`)\n *\n * Rejects everything else, including `javascript:`, `data:`,\n * protocol-relative `//evil.com`, and any other scheme. Comparison\n * is case-insensitive after stripping leading whitespace + ASCII\n * control bytes (browsers strip these before scheme resolution, so\n * `java\\tscript:` would otherwise slip past a naive prefix check).\n */\nexport function isSafeLinkHref(raw: string): boolean {\n // Built char-by-char rather than via regex to avoid embedding\n // actual control bytes in source (linters/IDEs mangle them).\n let cleaned = \"\";\n for (let i = 0; i < raw.length; i++) {\n const cc = raw.charCodeAt(i);\n if (cc > 0x20 && cc !== 0x7f) cleaned += raw[i];\n }\n if (cleaned === \"\") return false;\n if (cleaned.startsWith(\"#\") || cleaned.startsWith(\"/\")) {\n // Reject protocol-relative (`//host/...`) — resolves against\n // current scheme + host, a classic open-redirect vector.\n if (cleaned.startsWith(\"//\")) return false;\n return true;\n }\n const lower = cleaned.toLowerCase();\n return lower.startsWith(\"http://\") || lower.startsWith(\"https://\") || lower.startsWith(\"mailto:\");\n}\n","// React provider + hooks for customer-app bundles.\n//\n// Every customer app does the same dance: load the manifest once at\n// boot, then fire queries as components mount. The bundle developer\n// shouldn't have to thread the resolved manifest through every prop\n// or wire a custom context per app — that's what this file is for.\n//\n// Usage:\n//\n// import { OxyAppProvider, useQuery } from \"@oxy-hq/sdk\";\n//\n// function App() {\n// return <OxyAppProvider><Dashboard /></OxyAppProvider>;\n// }\n// function Dashboard() {\n// const { rows, error, loading } = useQuery({ sql: \"SELECT 1\" });\n// ...\n// }\n//\n// Loading + error states are per-query so the bundle can render\n// per-widget skeletons.\n\nimport * as React from \"react\";\nimport { type CustomerAppErrorReport, interpretCustomerAppError } from \"./errors\";\nimport { interpolateSqlParams } from \"./interpolate\";\nimport {\n type LoadManifestOptions,\n loadCustomerAppManifest,\n type ResolvedCustomerAppManifest\n} from \"./manifest\";\nimport { isSafeLinkHref } from \"./markdown\";\n\n// ── Context ─────────────────────────────────────────────────────────────────\n\n/**\n * Credentialed fetch wrapper stored in context so `useQuery` can share\n * the same request mechanism without coupling it to the global `fetch`.\n *\n * Sends `credentials: \"include\"` so the session cookie rides along when\n * the app is served by oxy (in-workspace / admin preview) — that cookie\n * authorizes data calls. For local dev (cross-origin), the\n * `@oxy-hq/vite-plugin` proxy attaches the developer's token. Bundles may\n * override the fetcher for test/proxy environments.\n */\nexport type AppFetcher = typeof fetch;\n\nfunction defaultFetcher(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n return fetch(input, { credentials: \"include\", ...init });\n}\n\ninterface OxyAppContextValue {\n status: \"loading\" | \"ready\" | \"error\";\n resolved?: ResolvedCustomerAppManifest;\n error?: CustomerAppErrorReport;\n /** Credentialed fetch implementation shared by all hooks. */\n fetcher: AppFetcher;\n}\n\nconst OxyAppContext = React.createContext<OxyAppContextValue | undefined>(undefined);\n\nexport interface OxyAppProviderProps {\n /** Optional manifest load options. Same shape as `loadCustomerAppManifest`. */\n manifestOptions?: LoadManifestOptions;\n /**\n * Rendered while the manifest is loading. Defaults to nothing; pass a\n * spinner if you want one.\n */\n fallback?: React.ReactNode;\n /**\n * Rendered on manifest load failure. Receives the structured error\n * report so the bundle can show its own branded error card. Defaults\n * to a minimal text-only fallback (better than a blank page).\n */\n errorFallback?: (err: CustomerAppErrorReport) => React.ReactNode;\n /**\n * Override the fetch implementation used by all hooks (`useQuery`).\n * Useful for test environments or proxy setups. Defaults to a wrapper\n * that sets `credentials: \"include\"` on every request.\n */\n fetcher?: AppFetcher;\n children: React.ReactNode;\n}\n\n/**\n * Top-level provider. Loads the manifest once on mount; children only\n * render after the manifest is ready (or the error fallback fires).\n */\nexport function OxyAppProvider(props: OxyAppProviderProps): React.JSX.Element {\n const { manifestOptions, fallback, errorFallback, fetcher: fetcherProp, children } = props;\n // Stable fetcher reference: caller-supplied or the module-level default.\n // We don't put this in state because it should never change after mount\n // (same reasoning as manifestOptions).\n const fetcher = fetcherProp ?? defaultFetcher;\n const [state, setState] = React.useState<OxyAppContextValue>({ status: \"loading\", fetcher });\n\n React.useEffect(() => {\n let cancelled = false;\n loadCustomerAppManifest(manifestOptions)\n .then((resolved) => {\n if (!cancelled) setState({ status: \"ready\", resolved, fetcher });\n })\n .catch((e: unknown) => {\n if (!cancelled) setState({ status: \"error\", error: interpretCustomerAppError(e), fetcher });\n });\n return () => {\n cancelled = true;\n };\n // `manifestOptions` is treated as stable — changing it mid-flight\n // wouldn't make sense for a manifest load (the URL is baked at\n // build time). `fetcher` is derived from props but also stable;\n // it's included here so biome is satisfied and so the effect does\n // update if a test swaps the fetcher between renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [manifestOptions, fetcher]);\n\n if (state.status === \"error\" && state.error) {\n const err = state.error;\n return (\n <OxyAppContext.Provider value={state}>\n {errorFallback ? errorFallback(err) : defaultErrorFallback(err)}\n </OxyAppContext.Provider>\n );\n }\n if (state.status === \"loading\") {\n return <OxyAppContext.Provider value={state}>{fallback ?? null}</OxyAppContext.Provider>;\n }\n return <OxyAppContext.Provider value={state}>{children}</OxyAppContext.Provider>;\n}\n\nfunction defaultErrorFallback(err: CustomerAppErrorReport): React.ReactNode {\n // Intentionally inline-styled with system fonts so it works in any\n // bundle without depending on Tailwind / CSS-in-JS / etc.\n return (\n <div\n style={{\n margin: \"2rem auto\",\n maxWidth: \"640px\",\n padding: \"1rem\",\n border: \"1px solid #fca5a5\",\n background: \"#fee2e2\",\n color: \"#991b1b\",\n borderRadius: \"8px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n fontSize: \"14px\"\n }}\n >\n <div style={{ fontWeight: 600 }}>{err.title}</div>\n <pre style={{ fontSize: \"12px\", marginTop: \"4px\" }}>{err.message}</pre>\n <div style={{ marginTop: \"12px\" }}>\n <strong>What to try:</strong> {err.hint}\n </div>\n </div>\n );\n}\n\n// ── Beta-warning helper ─────────────────────────────────────────────────────\n\n/**\n * Emit a one-time `console.warn` the first time a beta hook is\n * used in a given page load. Bundles upgrading from a future GA\n * release won't see the warning; the message lets us flag rough\n * edges without breaking the build.\n */\nconst _warnedBeta = new Set<string>();\nfunction warnBetaOnce(name: string): void {\n if (_warnedBeta.has(name)) return;\n _warnedBeta.add(name);\n if (typeof console !== \"undefined\" && typeof console.warn === \"function\") {\n console.warn(\n `[@oxy-hq/sdk] \\`${name}\\` is in beta — interface and behavior may change. ` +\n `See https://github.com/oxy-hq/customer-apps for caveats and the migration guide.`\n );\n }\n}\n\n// ── Error helpers ───────────────────────────────────────────────────────────\n\n/**\n * Error thrown by all customer-app hooks when an API call returns a\n * non-2xx response. Carries the structured `code` + `hint` the server\n * emits so bundle UIs can render an actionable message instead of\n * \"404: { ...json... }\".\n *\n * The server contract is documented in\n * `crates/app/src/server/api/projects/agent_ask.rs` and\n * `procedure_run.rs` — both emit `{ message, code?, hint? }` as JSON.\n * Hooks that previously wrapped the raw text in `new Error()` now\n * throw this type instead.\n */\nexport class OxyApiError extends Error {\n readonly status: number;\n readonly code: string | null;\n readonly hint: string | null;\n constructor(opts: {\n status: number;\n message: string;\n code?: string | null;\n hint?: string | null;\n }) {\n // Build a single multi-line `message` so consumers that just\n // render `.message` still see the hint. Code is included so\n // logs / dev tools can grep for it.\n const base = opts.message || `HTTP ${opts.status}`;\n const code = opts.code ? ` [${opts.code}]` : \"\";\n const hint = opts.hint ? `\\n\\n${opts.hint}` : \"\";\n super(`${base}${code}${hint}`);\n this.name = \"OxyApiError\";\n this.status = opts.status;\n this.code = opts.code ?? null;\n this.hint = opts.hint ?? null;\n }\n}\n\n/**\n * Read a non-2xx response from oxy and return an `OxyApiError`.\n * Parses the JSON envelope when present; falls back to raw text\n * (truncated to 240 chars so a runaway HTML error page doesn't\n * dominate the bundle UI).\n */\nasync function apiErrorFromResponse(resp: Response): Promise<OxyApiError> {\n let body: unknown;\n let raw = \"\";\n try {\n raw = await resp.text();\n body = raw ? JSON.parse(raw) : undefined;\n } catch {\n // Non-JSON body — keep `raw` for the fallback path.\n }\n if (body && typeof body === \"object\") {\n const b = body as { message?: unknown; code?: unknown; hint?: unknown };\n return new OxyApiError({\n status: resp.status,\n message: typeof b.message === \"string\" ? b.message : `HTTP ${resp.status}`,\n code: typeof b.code === \"string\" ? b.code : null,\n hint: typeof b.hint === \"string\" ? b.hint : null\n });\n }\n const snippet = raw.length > 240 ? `${raw.slice(0, 237)}…` : raw;\n return new OxyApiError({\n status: resp.status,\n message: snippet || `HTTP ${resp.status}`\n });\n}\n\n// ── Hooks ───────────────────────────────────────────────────────────────────\n\n/**\n * Read the resolved manifest from context. Throws if called outside\n * `<OxyAppProvider>` — that's a programmer error worth surfacing\n * loudly, not silently swallowing.\n */\nexport function useResolvedManifest(): ResolvedCustomerAppManifest {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useResolvedManifest must be called inside <OxyAppProvider>\");\n }\n if (ctx.status !== \"ready\" || !ctx.resolved) {\n throw new Error(\n \"useResolvedManifest called before manifest finished loading. \" +\n \"Use the provider's `fallback` prop to render while loading.\"\n );\n }\n return ctx.resolved;\n}\n\n/**\n * Low-level hook that returns the raw context value (including the\n * fetcher). Prefer `useResolvedManifest` for manifest access; use\n * this only when you need the fetcher or projectId without requiring\n * the manifest to be ready (e.g. inside `useQuery`).\n */\nfunction useOxyApp(): { projectId: string | undefined; fetcher: AppFetcher } {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useOxyApp must be called inside <OxyAppProvider>\");\n }\n return {\n projectId: ctx.resolved?.projectId,\n fetcher: ctx.fetcher\n };\n}\n\n// ── useQuery ────────────────────────────────────────────────────────────────\n\nexport interface UseQueryInput {\n sql: string;\n database?: string;\n}\n\nexport interface UseQueryOpts {\n params?: Record<string, string | number | boolean | null | undefined>;\n /** Set false to skip the request (e.g., waiting on user input). */\n enabled?: boolean;\n}\n\nexport interface UseQueryResult<Row = Record<string, unknown>> {\n rows: Row[];\n columns: string[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/**\n * Execute an ad-hoc SQL query against the project linked to this\n * customer app. The query is specified inline by the caller; no\n * manifest declaration is involved.\n *\n * Re-runs whenever `input` or enabled `params` change. Use the\n * `enabled` option to defer the first fetch until required data is\n * available (e.g. a user-supplied filter value).\n */\nexport function useQuery<Row = Record<string, unknown>>(\n input: UseQueryInput,\n opts: UseQueryOpts = {}\n): UseQueryResult<Row> {\n const { projectId, fetcher } = useOxyApp();\n const enabled = opts.enabled !== false;\n // Serialise opts.params so `useMemo` fires only when the values\n // change, not on every render when callers pass a new object literal.\n const paramsKey = JSON.stringify(opts.params);\n // biome-ignore lint/correctness/useExhaustiveDependencies: paramsKey replaces opts.params as the dep\n const sqlWithParams = React.useMemo(\n () => interpolateSqlParams(input.sql, opts.params ?? {}),\n [input.sql, paramsKey]\n );\n\n const [state, setState] = React.useState<{\n rows: Row[];\n columns: string[];\n loading: boolean;\n error: Error | null;\n }>({\n rows: [],\n columns: [],\n loading: enabled && !!projectId,\n error: null\n });\n const [nonce, setNonce] = React.useState(0);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: nonce forces refetch\n React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n const ctrl = new AbortController();\n let cancelled = false;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n const body = JSON.stringify({\n sql: sqlWithParams,\n ...(input.database ? { database: input.database } : {})\n });\n\n fetcher(`/api/projects/${projectId}/query`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n })\n .then(async (resp) => {\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n return resp.json() as Promise<{ columns: string[]; rows: unknown[][] }>;\n })\n .then(({ columns, rows }) => {\n if (cancelled) return;\n const objects = rows.map(\n (r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row\n );\n setState({ rows: objects, columns, loading: false, error: null });\n })\n .catch((err) => {\n if (cancelled) return;\n // AbortError is not a real failure — the cleanup cancelled the request.\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setState((s) => ({\n ...s,\n loading: false,\n error: err instanceof Error ? err : new Error(String(err))\n }));\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n }, [enabled, projectId, sqlWithParams, input.database, nonce, fetcher]);\n\n return {\n rows: state.rows,\n columns: state.columns,\n loading: state.loading,\n error: state.error,\n refetch: () => setNonce((n) => n + 1)\n };\n}\n\n// ── useSemanticQuery ────────────────────────────────────────────────────────\n//\n// Bundles reference the project's semantic layer by topic + dimensions\n// + measures + filters. The server compiles to dialect-specific SQL via\n// airlayer and executes through the same connector path as useQuery —\n// when the data team refactors the SQL behind a measure, the bundle\n// picks up the change without an edit.\n\n/** Scalar filter operators (compared against a single value). */\nexport type SemanticScalarOp = \"eq\" | \"neq\" | \"lt\" | \"lte\" | \"gt\" | \"gte\";\n\n/** Array filter operators (compared against a list). */\nexport type SemanticArrayOp = \"in\" | \"not_in\";\n\n/** Date-range filter operators. `from` / `to` accept ISO date strings. */\nexport type SemanticDateRangeOp = \"in_date_range\" | \"not_in_date_range\";\n\n/**\n * One filter clause. The `field` references a dimension name within\n * the topic; the `op` discriminator picks which other fields are\n * meaningful. Wire shape matches `agentic_semantic::SemanticFilter`\n * verbatim — the bundle's request body is forwarded to airlayer's\n * compiler with no translation.\n */\nexport type SemanticFilter =\n | { field: string; op: SemanticScalarOp; value: string | number | boolean | null }\n | { field: string; op: SemanticArrayOp; values: Array<string | number | boolean | null> }\n | { field: string; op: SemanticDateRangeOp; from: string; to: string };\n\n/** Time dimensions with optional granularity (e.g. \"day\", \"month\"). */\nexport interface SemanticTimeDimension {\n dimension: string;\n granularity?: \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n}\n\nexport interface UseSemanticQueryInput {\n topic: string;\n dimensions?: string[];\n measures?: string[];\n time_dimensions?: SemanticTimeDimension[];\n filters?: SemanticFilter[];\n limit?: number;\n}\n\nexport interface UseSemanticQueryOpts {\n /** Set false to skip the request (e.g., waiting on user input). */\n enabled?: boolean;\n /**\n * When true, the response includes the compiled SQL string at\n * `sql`. Off by default — production callers shouldn't bake the\n * warehouse SQL into their UI. Bundle authors flip this on while\n * debugging.\n */\n debug?: boolean;\n}\n\nexport interface UseSemanticQueryResult<Row = Record<string, unknown>> {\n rows: Row[];\n columns: string[];\n /** True when the result was capped at the server's row limit. */\n truncated: boolean;\n /** Compiled SQL — populated only when `opts.debug` is true. */\n sql: string | null;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/**\n * Run a semantic-layer query against the project's `.view.yml` /\n * `.topic.yml` definitions. The server compiles to SQL and executes\n * through the same connector path as `useQuery`, so result shape\n * matches.\n *\n * Re-runs whenever the input shape changes (deep-compared via JSON).\n * Use `opts.enabled = false` to defer the first fetch until required\n * inputs (e.g. a user-picked filter value) are available.\n */\nexport function useSemanticQuery<Row = Record<string, unknown>>(\n input: UseSemanticQueryInput,\n opts: UseSemanticQueryOpts = {}\n): UseSemanticQueryResult<Row> {\n const { projectId, fetcher } = useOxyApp();\n const enabled = opts.enabled !== false;\n const debug = opts.debug === true;\n\n // Stable key for the effect dep — re-running on every render when\n // callers pass a new object literal would mean re-fetching on every\n // parent render.\n const inputKey = React.useMemo(() => JSON.stringify(input), [input]);\n\n const [state, setState] = React.useState<{\n rows: Row[];\n columns: string[];\n truncated: boolean;\n sql: string | null;\n loading: boolean;\n error: Error | null;\n }>({\n rows: [],\n columns: [],\n truncated: false,\n sql: null,\n loading: enabled && !!projectId,\n error: null\n });\n const [nonce, setNonce] = React.useState(0);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: inputKey serializes input; nonce forces refetch\n React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n const ctrl = new AbortController();\n let cancelled = false;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n // `v: 1` is the customer-apps-platform body versioning convention\n // (see customer_apps_gates.rs::parse_versioned_body). Pinning it\n // here means a future v2 server can reject this stale client\n // cleanly instead of silently misinterpreting it.\n const body = JSON.stringify({\n v: 1,\n topic: input.topic,\n dimensions: input.dimensions ?? [],\n measures: input.measures ?? [],\n time_dimensions: input.time_dimensions ?? [],\n filters: input.filters ?? [],\n ...(input.limit != null ? { limit: input.limit } : {})\n });\n\n const url = `/api/projects/${projectId}/semantic-query${debug ? \"?debug=1\" : \"\"}`;\n fetcher(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n })\n .then(async (resp) => {\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n return resp.json() as Promise<{\n columns: string[];\n rows: unknown[][];\n truncated: boolean;\n sql?: string;\n }>;\n })\n .then(({ columns, rows, truncated, sql }) => {\n if (cancelled) return;\n const objects = rows.map(\n (r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row\n );\n setState({\n rows: objects,\n columns,\n truncated,\n sql: sql ?? null,\n loading: false,\n error: null\n });\n })\n .catch((err) => {\n if (cancelled) return;\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setState((s) => ({\n ...s,\n loading: false,\n error: err instanceof Error ? err : new Error(String(err))\n }));\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n }, [enabled, projectId, inputKey, debug, nonce, fetcher]);\n\n return {\n rows: state.rows,\n columns: state.columns,\n truncated: state.truncated,\n sql: state.sql,\n loading: state.loading,\n error: state.error,\n refetch: () => setNonce((n) => n + 1)\n };\n}\n\n// ── useProcedureRun ─────────────────────────────────────────────────────────\n//\n// Trigger a long-running procedure (`.procedure.yml`) from the\n// bundle. Returns state + progress + structured outputs. Use for\n// \"Generate report\" / \"Recompute\" buttons.\n\nexport type ProcedureRunState = \"idle\" | \"running\" | \"done\" | \"failed\";\n\nexport interface UseProcedureRunInput {\n procedureId: string;\n}\n\nexport interface UseProcedureRunOpts {\n /** Polling cadence in ms while running. Default: 2000 (procedures\n * are typically minutes-long; tighter cadence wastes resources). */\n pollIntervalMs?: number;\n pollIntervalBackoffMs?: number;\n /** Max client-side wait in ms. Default: 1 hour. */\n maxWaitMs?: number;\n}\n\nexport interface ProcedureProgress {\n step: string;\n percent: number;\n}\n\nexport interface ProcedureResult {\n summary: string;\n outputs: Record<string, unknown>;\n}\n\nexport interface UseProcedureRunResult {\n state: ProcedureRunState;\n run: (params?: Record<string, unknown>) => void;\n /** Cancel the in-flight run. Idempotent. */\n cancel: () => void;\n progress: ProcedureProgress | null;\n result: ProcedureResult | null;\n error: Error | null;\n}\n\nconst PROCEDURE_POLL_MS = 2000;\nconst PROCEDURE_BACKOFF_MS = 5000;\nconst PROCEDURE_MAX_WAIT_MS = 60 * 60 * 1000;\n\n/**\n * @beta Long-running procedure runner. The wire shape works end-to-end\n * (start → poll → cancel; runs survive server restarts via the\n * `customer_app_procedure_runs` table) but a few rough edges remain\n * before this is GA-ready:\n *\n * - Hint surfaces for `procedure_not_found` are correct but the\n * procedure-discovery rules (which directories the server scans,\n * case-sensitivity, branch awareness) aren't documented yet.\n * - Cancellation across multi-instance deployments leans on a\n * periodic sweep — fine for now, but expect occasional latency\n * between `cancel()` and the run actually stopping.\n * - Progress reporting requires the procedure to emit named\n * steps; bundles get `progress: null` until that lands.\n *\n * The API surface is stable; expect breaking changes only if the\n * server-side `customer_app_procedure_runs` schema changes.\n */\nexport function useProcedureRun(\n input: UseProcedureRunInput,\n opts: UseProcedureRunOpts = {}\n): UseProcedureRunResult {\n warnBetaOnce(\"useProcedureRun\");\n const { projectId, fetcher } = useOxyApp();\n const pollMs = opts.pollIntervalMs ?? PROCEDURE_POLL_MS;\n const backoffMs = opts.pollIntervalBackoffMs ?? PROCEDURE_BACKOFF_MS;\n const maxWaitMs = opts.maxWaitMs ?? PROCEDURE_MAX_WAIT_MS;\n\n const [state, setState] = React.useState<{\n state: ProcedureRunState;\n progress: ProcedureProgress | null;\n result: ProcedureResult | null;\n error: Error | null;\n }>({\n state: \"idle\",\n progress: null,\n result: null,\n error: null\n });\n const inflight = React.useRef<{\n abort?: AbortController;\n runId?: string;\n }>({});\n\n // cancel is idempotent and a no-op once the run has reached a\n // terminal state. inflight.current.runId is cleared in the same\n // setState calls that flip state out of \"running\"; that's the\n // single source of truth so a slow click after `done` can't\n // overwrite the result with a phantom \"failed\".\n const cancel = React.useCallback(() => {\n const runId = inflight.current.runId;\n if (!projectId || !runId) return;\n inflight.current.abort?.abort();\n inflight.current.runId = undefined;\n void fetcher(`/api/projects/${projectId}/procedures/runs/${encodeURIComponent(runId)}/cancel`, {\n method: \"POST\"\n }).catch(() => {});\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(\"procedure cancelled by user\")\n });\n }, [projectId, fetcher]);\n\n const run = React.useCallback(\n (params?: Record<string, unknown>) => {\n if (!projectId) {\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"project not configured\")\n }));\n return;\n }\n inflight.current.abort?.abort();\n const ctrl = new AbortController();\n inflight.current = { abort: ctrl };\n setState({ state: \"running\", progress: null, result: null, error: null });\n\n void (async () => {\n try {\n const body = JSON.stringify({\n v: 1,\n ...(params ? { params } : {})\n });\n const startResp = await fetcher(\n `/api/projects/${projectId}/procedures/${encodeURIComponent(input.procedureId)}/runs`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n }\n );\n if (!startResp.ok) {\n throw await apiErrorFromResponse(startResp);\n }\n const { run_id } = (await startResp.json()) as { run_id: string };\n inflight.current.runId = run_id;\n\n const startedAt = Date.now();\n let pollCount = 0;\n // biome-ignore lint/correctness/noConstantCondition: terminated by return/throw\n while (true) {\n if (ctrl.signal.aborted) return;\n if (Date.now() - startedAt > maxWaitMs) {\n throw new Error(\"procedure run timed out client-side\");\n }\n const interval = pollCount < 6 ? pollMs : backoffMs;\n await sleep(interval, ctrl.signal);\n if (ctrl.signal.aborted) return;\n pollCount += 1;\n\n const pollResp = await fetcher(\n `/api/projects/${projectId}/procedures/runs/${encodeURIComponent(run_id)}`,\n { method: \"GET\", signal: ctrl.signal }\n );\n if (!pollResp.ok) {\n throw await apiErrorFromResponse(pollResp);\n }\n const poll = (await pollResp.json()) as\n | { status: \"running\"; progress?: ProcedureProgress }\n | { status: \"done\"; result: ProcedureResult }\n | { status: \"cancelled\" }\n | {\n status: \"failed\";\n error: { message: string; code?: string };\n };\n if (poll.status === \"running\") {\n if (poll.progress) {\n setState((s) => ({ ...s, progress: poll.progress ?? null }));\n }\n continue;\n }\n if (poll.status === \"done\") {\n inflight.current.runId = undefined;\n setState({\n state: \"done\",\n progress: null,\n result: poll.result,\n error: null\n });\n return;\n }\n if (poll.status === \"cancelled\") {\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(\"procedure cancelled\")\n });\n return;\n }\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(poll.error.message)\n });\n return;\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: e instanceof Error ? e : new Error(String(e))\n });\n }\n })();\n },\n [projectId, fetcher, input.procedureId, pollMs, backoffMs, maxWaitMs]\n );\n\n React.useEffect(() => {\n return () => {\n inflight.current.abort?.abort();\n };\n }, []);\n\n return {\n state: state.state,\n run,\n cancel,\n progress: state.progress,\n result: state.result,\n error: state.error\n };\n}\n\n// ── useAgentRun (SSE streaming) ─────────────────────────────────────────────\n//\n// Real-time chat surface. Agentic pipeline emits events as they\n// happen — token-by-token answer text, mid-run SQL artifacts, ask-\n// user clarifications. Bundles use this for any chat / Q&A UI; the\n// drop-in `<OxyChat>` and `<OxyAnswer>` components wrap it for the\n// common cases.\n\nexport type AgentRunState = \"idle\" | \"running\" | \"needs_clarification\" | \"done\" | \"failed\";\n\nexport interface AgentRunEvent {\n type: string;\n data: unknown;\n}\n\n/** SQL produced and (optionally) executed by the agent. Extracted\n * from `query_generated` / `query_executed` / `verified_sql` /\n * `semantic_query` / `omni_query` SSE events so callers don't have\n * to scan the raw event stream themselves. */\nexport interface AgentSqlArtifact {\n type: \"sql\";\n /** Stable id derived from the SSE event id so React keys stay\n * stable across re-renders / reconnects. */\n id: string;\n /** Originating UI event type — preserves the verified/semantic/etc.\n * flavor in case the renderer wants a badge. */\n source: string;\n sql: string;\n /** Present when the SQL was executed and rows came back. */\n results?: {\n columns: string[];\n rows: unknown[][];\n rowCount: number;\n };\n /** Present when execution failed — surface it so the bundle UI can\n * show the failure inline next to the SQL instead of swallowing\n * it inside the agent's final answer. */\n error?: string;\n}\n\nexport type AgentArtifact = AgentSqlArtifact;\n\nexport interface UseAgentRunInput {\n agentId: string;\n}\n\nexport interface UseAgentRunResult {\n state: AgentRunState;\n /** Submit a question and open the SSE stream. */\n ask: (question: string, opts?: { threadId?: string }) => void;\n /** Cancel the in-flight stream + the server-side run. Idempotent. */\n cancel: () => void;\n /** Accumulated raw events for advanced consumers. */\n events: AgentRunEvent[];\n /** SQL artifacts extracted from the event stream — convenience\n * view over `events` so renderers don't have to know which event\n * types carry SQL. */\n artifacts: AgentArtifact[];\n /** Final answer once a `done` event arrives. Markdown. */\n answer: string | null;\n /** Clarification text once a suspension event arrives. */\n clarification: string | null;\n /** Thread id used by the active run (stable across follow-ups). */\n threadId: string | null;\n /**\n * @beta Relative path to the full thread view in oxy (e.g.\n * `/threads/<id>` for local mode, or\n * `/<org_slug>/workspaces/<ws_id>/threads/<id>` in cloud). Set\n * once the run starts so a bundle can render a \"Continue in Oxy\"\n * link without constructing the URL itself.\n *\n * Caveats while in beta:\n * - The bundle's origin and the oxy app shell's origin can\n * differ in cloud deployments. If they do, this relative URL\n * resolves against the bundle's origin and 404s. A future\n * release will expose the oxy app origin via the manifest;\n * for now, prefix at the call site if you know your\n * deployment topology, or hide the link entirely.\n * - The thread row may not be queryable until the run produces\n * its first event — clicking the link immediately after\n * `ask()` can land on a \"thread not found\" page.\n */\n threadUrl: string | null;\n error: Error | null;\n}\n\nexport function useAgentRun(input: UseAgentRunInput): UseAgentRunResult {\n const { projectId, fetcher } = useOxyApp();\n const [state, setState] = React.useState<{\n state: AgentRunState;\n events: AgentRunEvent[];\n artifacts: AgentArtifact[];\n answer: string | null;\n clarification: string | null;\n threadId: string | null;\n threadUrl: string | null;\n error: Error | null;\n }>({\n state: \"idle\",\n events: [],\n artifacts: [],\n answer: null,\n clarification: null,\n threadId: null,\n threadUrl: null,\n error: null\n });\n // Track the latest abort controller + run_id so cancel() can fire\n // the right server endpoint AND tear down the in-flight stream.\n // `terminated` is a local-only flag the consume loop reads to\n // decide whether to reconnect after a clean stream close. We\n // can't read React state directly (closure capture is stale) and\n // the prior stateRef approach was racy because setState is async\n // — by the time we read the ref it might not have flushed yet.\n // The flag is set synchronously inside the onEvent handler right\n // before setState fires, so the consume loop sees it on the next\n // iteration.\n const inflight = React.useRef<{\n abort?: AbortController;\n runId?: string;\n }>({});\n\n // cancel is idempotent + no-op once the run is terminal. We\n // clear inflight.current.runId in the same setState calls that\n // flip state out of \"running\"; a late click then can't overwrite\n // a done answer with a phantom \"failed\".\n const cancel = React.useCallback(() => {\n const runId = inflight.current.runId;\n if (!projectId || !runId) return;\n inflight.current.abort?.abort();\n inflight.current.runId = undefined;\n void fetcher(`/api/projects/${projectId}/agents/asks/${encodeURIComponent(runId)}/cancel`, {\n method: \"POST\"\n }).catch(() => {});\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"agent run cancelled by user\")\n }));\n }, [projectId, fetcher]);\n\n const ask = React.useCallback(\n (question: string, opts: { threadId?: string } = {}) => {\n if (!projectId) {\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"project not configured\")\n }));\n return;\n }\n inflight.current.abort?.abort();\n const ctrl = new AbortController();\n inflight.current = { abort: ctrl };\n setState({\n state: \"running\",\n events: [],\n artifacts: [],\n answer: null,\n clarification: null,\n threadId: opts.threadId ?? null,\n threadUrl: opts.threadId ? `/threads/${opts.threadId}` : null,\n error: null\n });\n\n void (async () => {\n try {\n // 1. POST to start the run.\n const body = JSON.stringify({\n v: 1,\n question,\n ...(opts.threadId ? { thread_id: opts.threadId } : {})\n });\n const startResp = await fetcher(\n `/api/projects/${projectId}/agents/${encodeURIComponent(input.agentId)}/asks`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n }\n );\n if (!startResp.ok) {\n throw await apiErrorFromResponse(startResp);\n }\n const { run_id, thread_id, thread_url } = (await startResp.json()) as {\n run_id: string;\n thread_id: string;\n thread_url?: string;\n };\n inflight.current.runId = run_id;\n setState((s) => ({\n ...s,\n threadId: thread_id,\n threadUrl: thread_url ?? `/threads/${thread_id}`\n }));\n\n // 2. Open the SSE stream via fetch (not EventSource) so we\n // can pass `Last-Event-ID` on reconnect. EventSource has\n // no API to set that header — the browser exposes the\n // server-set id internally but doesn't surface it for\n // custom auth flows or `withCredentials` + custom\n // headers together. Hand-rolled SSE + ReadableStream\n // gives us both.\n //\n // Reconnect strategy: on disconnect (network error,\n // server close before terminal event), wait 1s and\n // re-open with the last-seen sequence id. Max 5\n // attempts so a permanently-broken stream eventually\n // surfaces as `failed`. The `terminated` flag is set\n // synchronously when a terminal event arrives — once\n // set, the loop exits on the next iteration without\n // reconnecting.\n let lastEventId = \"\";\n let attempts = 0;\n let terminated = false;\n // biome-ignore lint/correctness/noConstantCondition: terminated by return/break\n while (true) {\n if (ctrl.signal.aborted) return;\n if (terminated) return;\n attempts += 1;\n\n try {\n await consumeSseStream({\n url: `/api/projects/${projectId}/agents/runs/${encodeURIComponent(run_id)}/events`,\n fetcher,\n signal: ctrl.signal,\n lastEventId,\n onEvent: (ev) => {\n // Track last event id for reconnect resumption.\n if (ev.id) lastEventId = ev.id;\n const data = parseSseData(ev.data);\n const eventType = ev.event || \"message\";\n const artifact = extractSqlArtifact(eventType, ev.id, data);\n\n // `text_delta` carries the streaming answer token-by-\n // token (CoreEvent::LlmToken → UiBlock::TextDelta).\n // The terminal `done` event has an empty payload — it\n // signals the run is over but doesn't restate the\n // answer text. So we accumulate tokens here.\n const token =\n eventType === \"text_delta\" &&\n typeof data === \"object\" &&\n data !== null &&\n \"token\" in data\n ? String((data as { token: unknown }).token)\n : null;\n\n setState((s) => ({\n ...s,\n events: [...s.events, { type: eventType, data }],\n artifacts: artifact ? [...s.artifacts, artifact] : s.artifacts,\n answer: token !== null ? (s.answer ?? \"\") + token : s.answer\n }));\n\n if (ev.event === \"done\") {\n terminated = true;\n inflight.current.runId = undefined;\n setState((s) => ({ ...s, state: \"done\" }));\n } else if (\n ev.event === \"failed\" ||\n ev.event === \"error\" ||\n ev.event === \"cancelled\"\n ) {\n // Mirrors `is_terminal_event` in\n // crates/app/.../agent_run_stream.rs — keep in sync.\n terminated = true;\n inflight.current.runId = undefined;\n const message =\n typeof data === \"object\" && data !== null && \"message\" in data\n ? String((data as { message: unknown }).message)\n : `agent run ${ev.event}`;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(message)\n }));\n } else if (ev.event === \"ask_user\") {\n // Not terminal in the cancel-sense — the user\n // can resume by calling ask() again with the\n // same threadId. Keep runId set so cancel()\n // still works if the user prefers to abort.\n terminated = true;\n const clarification =\n typeof data === \"object\" && data !== null && \"question\" in data\n ? String((data as { question: unknown }).question)\n : \"Agent needs clarification.\";\n setState((s) => ({\n ...s,\n state: \"needs_clarification\",\n clarification\n }));\n }\n }\n });\n // Stream closed cleanly. `terminated` was flipped by\n // the event handler iff a terminal event arrived —\n // check it at loop head; otherwise fall through to\n // reconnect.\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n // Network / parse error. Reconnect up to 5x with\n // 1s sleep before giving up.\n if (attempts >= 5) {\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: err instanceof Error ? err : new Error(String(err))\n }));\n return;\n }\n }\n if (terminated) return;\n await sleep(1000, ctrl.signal);\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: e instanceof Error ? e : new Error(String(e))\n }));\n }\n })();\n },\n [projectId, fetcher, input.agentId]\n );\n\n React.useEffect(() => {\n return () => {\n inflight.current.abort?.abort();\n };\n }, []);\n\n return {\n state: state.state,\n ask,\n cancel,\n events: state.events,\n artifacts: state.artifacts,\n answer: state.answer,\n clarification: state.clarification,\n threadId: state.threadId,\n threadUrl: state.threadUrl,\n error: state.error\n };\n}\n\n/** Parsed JSON payload of an SSE `data:` line, falling back to raw\n * string on parse failure. */\nfunction parseSseData(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** UI event types in the analytics taxonomy that carry SQL the bundle\n * may want to render alongside the answer. Each carries the same\n * shape (`query` / `columns` / `rows` / `success`) so we can parse\n * uniformly. New types added upstream don't surface as artifacts\n * until added here — that's intentional, the renderer needs to\n * know how to display each. */\nconst SQL_EVENT_TYPES = new Set([\n \"query_executed\",\n \"query_generated\",\n \"verified_sql\",\n \"semantic_query\",\n \"omni_query\"\n]);\n\n/** Extract a SQL artifact from a single SSE event when the type +\n * payload shape match. Returns `null` for events that aren't SQL\n * carriers or whose payload doesn't include the expected fields. */\nfunction extractSqlArtifact(\n eventType: string,\n eventId: string,\n data: unknown\n): AgentSqlArtifact | null {\n if (!SQL_EVENT_TYPES.has(eventType)) return null;\n if (typeof data !== \"object\" || data === null) return null;\n const obj = data as Record<string, unknown>;\n const sql =\n typeof obj.query === \"string\" ? obj.query : typeof obj.sql === \"string\" ? obj.sql : null;\n if (!sql) return null;\n\n const columns = Array.isArray(obj.columns) ? (obj.columns as unknown[]).map(String) : undefined;\n const rows = Array.isArray(obj.rows) ? (obj.rows as unknown[][]) : undefined;\n const rowCount =\n typeof obj.row_count === \"number\"\n ? obj.row_count\n : typeof obj.rowCount === \"number\"\n ? obj.rowCount\n : rows?.length;\n\n const artifact: AgentSqlArtifact = {\n type: \"sql\",\n id: eventId || `${eventType}-${sql.slice(0, 32)}`,\n source: eventType,\n sql\n };\n if (columns && rows) {\n artifact.results = { columns, rows, rowCount: rowCount ?? rows.length };\n }\n const errMsg =\n typeof obj.error === \"string\"\n ? obj.error\n : obj.success === false && typeof obj.message === \"string\"\n ? (obj.message as string)\n : undefined;\n if (errMsg) artifact.error = errMsg;\n return artifact;\n}\n\n/** One delivered SSE event. Matches what the spec calls a \"message\"\n * block — id + event-type + accumulated data. */\ninterface ParsedSseEvent {\n id: string;\n event: string;\n data: string;\n}\n\n/**\n * Hand-rolled SSE consumer using `fetch` + `ReadableStream`. We use\n * this in place of `EventSource` because:\n * 1. EventSource doesn't expose the connection's `Last-Event-ID`\n * header in a way you can control. The browser tracks it\n * internally but you can't pass a starting value, so a hook\n * that wants to resume after a tab switch / network blip has\n * no way to ask the server to replay from a known point.\n * 2. EventSource can't pass `Authorization` / other custom\n * headers — only `withCredentials` for cookies. Fine today,\n * but couples us to cookie auth forever.\n *\n * The parser handles the message-block model from the SSE spec\n * verbatim: lines split by `\\n` (or `\\r\\n` / `\\r`), event blocks\n * separated by blank lines, `id:` / `event:` / `data:` fields\n * accumulated per block. Multiple `data:` lines concatenate with\n * `\\n` (per spec) — we honor that even though the server emits\n * single-line data today.\n *\n * Throws on network error or non-2xx. Returns when the stream ends\n * normally (server closed connection cleanly).\n */\nasync function consumeSseStream(opts: {\n url: string;\n fetcher: AppFetcher;\n signal: AbortSignal;\n lastEventId: string;\n onEvent: (ev: ParsedSseEvent) => void;\n}): Promise<void> {\n const headers: Record<string, string> = {\n accept: \"text/event-stream\",\n \"cache-control\": \"no-cache\"\n };\n if (opts.lastEventId) {\n headers[\"Last-Event-ID\"] = opts.lastEventId;\n }\n const resp = await opts.fetcher(opts.url, {\n method: \"GET\",\n headers,\n signal: opts.signal\n });\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n if (!resp.body) {\n throw new Error(\"SSE response has no body\");\n }\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentId = \"\";\n let currentEvent = \"message\";\n let currentData: string[] = [];\n\n const dispatch = () => {\n if (currentData.length === 0 && currentEvent === \"message\" && !currentId) {\n return; // empty block\n }\n opts.onEvent({\n id: currentId,\n event: currentEvent,\n data: currentData.join(\"\\n\")\n });\n // `id` persists across events per spec; `event` and `data` reset.\n currentEvent = \"message\";\n currentData = [];\n };\n\n // biome-ignore lint/correctness/noConstantCondition: terminated by reader.read()\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let nlIndex: number;\n // Process complete lines. Handle \\n, \\r\\n, and bare \\r.\n // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line-buffer drain\n while ((nlIndex = buffer.search(/\\r\\n|\\r|\\n/)) !== -1) {\n // \\r at the very end of the buffer is ambiguous — it could be\n // a lone CR (Mac line ending) or the first half of a CRLF\n // whose LF hasn't arrived yet. Wait for the next read before\n // committing. Without this, a CRLF straddling a chunk boundary\n // gets parsed as two empty lines, which fires `dispatch()`\n // twice and breaks event framing for any future writer that\n // emits CRLF (Axum uses LF today, so latent — but bug-class\n // fix is cheap.)\n if (nlIndex === buffer.length - 1 && buffer[nlIndex] === \"\\r\") {\n break;\n }\n const line = buffer.slice(0, nlIndex);\n // Skip the matched line break (one or two chars).\n const sep = buffer.slice(nlIndex, nlIndex + 2);\n buffer = buffer.slice(nlIndex + (sep === \"\\r\\n\" ? 2 : 1));\n\n if (line === \"\") {\n // Empty line ⇒ end of event block.\n dispatch();\n continue;\n }\n if (line.startsWith(\":\")) {\n // Comment — keep-alive heartbeats. Skip.\n continue;\n }\n const colonAt = line.indexOf(\":\");\n const field = colonAt === -1 ? line : line.slice(0, colonAt);\n let value = colonAt === -1 ? \"\" : line.slice(colonAt + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"id\":\n currentId = value;\n break;\n case \"event\":\n currentEvent = value;\n break;\n case \"data\":\n currentData.push(value);\n break;\n // `retry:` and unknown fields ignored.\n }\n }\n }\n // Stream ended — dispatch any final buffered event without a\n // trailing blank line (server didn't write one before close).\n if (currentData.length > 0) {\n dispatch();\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(t);\n reject(new DOMException(\"aborted\", \"AbortError\"));\n },\n { once: true }\n );\n });\n}\n\n// ── <OxyAnswer> + <OxyChat> drop-in components ──────────────────────────────\n//\n// Bundles that want a chat surface without rolling their own UI use\n// `<OxyChat agentId=\"...\">`. Bundles that already have a question\n// input but want oxy to render the answer use `<OxyAnswer>` with the\n// values from `useAgentRun()`.\n//\n// Both components ship with default markdown rendering, SQL artifact\n// display, and a \"Continue in Oxy\" link — the three things every\n// bundle author needs and nobody wants to rebuild.\n//\n// Styling: inline `style={}` only. Bundles inevitably have their\n// own design system (Tailwind, Mantine, CSS-in-JS, etc.) and we\n// don't want the SDK to fight it. Caller can pass `className` to\n// override layout entirely.\n\nexport interface OxyAnswerProps {\n /** Markdown answer text from `useAgentRun().answer`. */\n answer: string | null;\n /** SQL artifacts from `useAgentRun().artifacts`. */\n artifacts?: AgentArtifact[];\n /** Lifecycle state — drives the placeholder, spinner, error UI. */\n state: AgentRunState;\n /** Clarification text when `state === \"needs_clarification\"`. */\n clarification?: string | null;\n /** Failure reason when `state === \"failed\"`. */\n error?: Error | null;\n /**\n * @beta Relative URL to the thread view in oxy — renders a\n * \"Continue in Oxy (beta)\" link when set. Pass `null` to suppress\n * the link entirely; the link is marked beta because the resolved\n * URL may not reach a live thread in every deployment topology\n * (see `UseAgentRunResult.threadUrl`).\n */\n threadUrl?: string | null;\n /** Override the link label. Default: \"Continue this thread in Oxy\". */\n threadLinkLabel?: string;\n /** Maximum number of SQL result rows to render per artifact. Older\n * rows truncated with a \"+N more\" note. Default: 10. */\n maxArtifactRows?: number;\n /** Class on the outer container — for callers using utility CSS. */\n className?: string;\n}\n\n/**\n * Renders an agent run's answer + artifacts + thread link as a\n * single block. The default styling is intentionally neutral\n * (system fonts, gray surfaces) so it blends into any bundle.\n *\n * Designed to be paired with `useAgentRun`:\n *\n * ```tsx\n * const run = useAgentRun({ agentId: \"analyst\" });\n * return (\n * <>\n * <button onClick={() => run.ask(\"how many users last week?\")}>Ask</button>\n * <OxyAnswer {...run} />\n * </>\n * );\n * ```\n */\nexport function OxyAnswer(props: OxyAnswerProps): React.JSX.Element {\n const {\n answer,\n artifacts = [],\n state,\n clarification,\n error,\n threadUrl,\n threadLinkLabel = \"Continue this thread in Oxy\",\n maxArtifactRows = 10,\n className\n } = props;\n\n const isRunning = state === \"running\";\n const isFailed = state === \"failed\";\n const needsClarification = state === \"needs_clarification\";\n\n return (\n <div className={className} style={styles.answerWrap}>\n {isRunning && answer === null ? (\n <div style={styles.statusRow}>\n <span style={styles.spinner} aria-hidden='true' />\n <span style={styles.statusText}>Thinking…</span>\n </div>\n ) : null}\n\n {artifacts.length > 0 ? (\n <div style={styles.artifactList}>\n {artifacts.map((a) => (\n <SqlArtifactBlock key={a.id} artifact={a} maxRows={maxArtifactRows} />\n ))}\n </div>\n ) : null}\n\n {answer ? (\n <div style={styles.markdown}>\n <MarkdownText text={answer} />\n </div>\n ) : null}\n\n {needsClarification && clarification ? (\n <div style={styles.clarification}>\n <strong>Agent needs clarification:</strong>\n <div style={{ marginTop: 4 }}>{clarification}</div>\n </div>\n ) : null}\n\n {isFailed && error ? <ErrorBlock error={error} /> : null}\n\n {threadUrl && (answer || artifacts.length > 0) ? (\n <div style={styles.threadLinkRow}>\n <a href={threadUrl} target='_blank' rel='noreferrer noopener' style={styles.threadLink}>\n {threadLinkLabel} →\n </a>\n <span style={styles.betaBadge} title='Thread linking is in beta — see docs'>\n beta\n </span>\n </div>\n ) : null}\n </div>\n );\n}\n\nexport interface OxyChatProps {\n /** Agent id (matches `<id>.agentic.yml` in the project). */\n agentId: string;\n /** Placeholder for the question input. */\n placeholder?: string;\n /** Button label. Default: \"Ask\". */\n submitLabel?: string;\n /** Rendered when the user hasn't asked anything yet. */\n emptyState?: React.ReactNode;\n /** Forwarded to the inner `<OxyAnswer>`. */\n maxArtifactRows?: number;\n /** Class on the outer container. */\n className?: string;\n}\n\n/**\n * Complete drop-in chat surface. One agent, one input, one answer\n * view. The chat is single-turn by default — each new question\n * cancels the previous run and clears the answer. Bundles that\n * want a multi-turn conversation history compose their own UI\n * using `useAgentRun` directly.\n *\n * Single-turn keeps the surface dead simple: bundles use this for\n * the \"ask anything about your data\" widget that sits next to\n * structured panels. Multi-turn is rare in those contexts and\n * better expressed by the bundle.\n */\nexport function OxyChat(props: OxyChatProps): React.JSX.Element {\n const {\n agentId,\n placeholder = \"Ask a question about your data…\",\n submitLabel = \"Ask\",\n emptyState,\n maxArtifactRows,\n className\n } = props;\n\n const run = useAgentRun({ agentId });\n const [question, setQuestion] = React.useState(\"\");\n\n const submit = React.useCallback(\n (e?: React.FormEvent) => {\n e?.preventDefault();\n const q = question.trim();\n if (!q || run.state === \"running\") return;\n run.ask(q);\n },\n [question, run]\n );\n\n return (\n <div className={className} style={styles.chatWrap}>\n <form onSubmit={submit} style={styles.chatForm}>\n <input\n type='text'\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder={placeholder}\n disabled={run.state === \"running\"}\n style={styles.chatInput}\n aria-label='Question'\n />\n <button\n type='submit'\n disabled={run.state === \"running\" || question.trim() === \"\"}\n style={styles.chatSubmit}\n >\n {run.state === \"running\" ? \"…\" : submitLabel}\n </button>\n {run.state === \"running\" ? (\n <button type='button' onClick={run.cancel} style={styles.chatCancel}>\n Stop\n </button>\n ) : null}\n </form>\n\n {run.state === \"idle\" ? (\n (emptyState ?? <div style={styles.emptyState}>Ask a question to get started.</div>)\n ) : (\n <OxyAnswer\n answer={run.answer}\n artifacts={run.artifacts}\n state={run.state}\n clarification={run.clarification}\n error={run.error}\n threadUrl={run.threadUrl}\n maxArtifactRows={maxArtifactRows}\n />\n )}\n </div>\n );\n}\n\nfunction SqlArtifactBlock(props: {\n artifact: AgentSqlArtifact;\n maxRows: number;\n}): React.JSX.Element {\n const { artifact, maxRows } = props;\n const [open, setOpen] = React.useState(false);\n const results = artifact.results;\n const truncated = results ? results.rows.length > maxRows : false;\n const visibleRows = results ? results.rows.slice(0, maxRows) : [];\n const sourceLabel =\n artifact.source === \"verified_sql\"\n ? \"Verified query\"\n : artifact.source === \"semantic_query\"\n ? \"Semantic query\"\n : artifact.source === \"omni_query\"\n ? \"Omni query\"\n : \"Query\";\n\n return (\n <div style={styles.artifact}>\n <button type='button' onClick={() => setOpen((o) => !o)} style={styles.artifactHeader}>\n <span style={styles.artifactBadge}>{sourceLabel}</span>\n <span style={styles.artifactSummary}>\n {results\n ? `${results.rowCount} row${results.rowCount === 1 ? \"\" : \"s\"}`\n : artifact.error\n ? \"execution failed\"\n : \"SQL only\"}\n </span>\n <span style={styles.artifactToggle}>{open ? \"Hide\" : \"Show\"}</span>\n </button>\n {open ? (\n <div>\n <pre style={styles.sqlBlock}>{artifact.sql}</pre>\n {artifact.error ? (\n <div style={styles.error}>{artifact.error}</div>\n ) : results ? (\n <div style={styles.resultsWrap}>\n <table style={styles.resultsTable}>\n <thead>\n <tr>\n {results.columns.map((c) => (\n <th key={c} style={styles.resultsTh}>\n {c}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {visibleRows.map((row, i) => (\n <tr key={i}>\n {row.map((cell, j) => (\n <td key={j} style={styles.resultsTd}>\n {formatCell(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {truncated ? (\n <div style={styles.truncatedNote}>\n +{results.rows.length - maxRows} more rows. Open the thread in Oxy to see all.\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction formatCell(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Renders a thrown Error with the server's `hint` line broken out\n * if the error is an `OxyApiError`. Falls back to `error.message`\n * for plain Errors. Whitespace-preserving so multi-line hints from\n * the server land readably.\n */\nfunction ErrorBlock(props: { error: Error }): React.JSX.Element {\n const { error } = props;\n if (error instanceof OxyApiError) {\n return (\n <div style={styles.error}>\n <div>\n <strong>Run failed:</strong> {error.message.split(\"\\n\\n\")[0]}\n </div>\n {error.hint ? (\n <div style={{ marginTop: 6, fontWeight: 400, whiteSpace: \"pre-wrap\" }}>\n <strong>Hint:</strong> {error.hint}\n </div>\n ) : null}\n </div>\n );\n }\n return (\n <div style={styles.error}>\n <strong>Run failed:</strong> {error.message}\n </div>\n );\n}\n\n// ── Minimal markdown renderer ───────────────────────────────────────────────\n//\n// Agent answers are simple markdown — paragraphs, headings (h1-h3),\n// fenced code blocks, inline code, bold, italic, links, unordered\n// lists. A 100-line renderer covers it without adding a runtime dep\n// (~30 KB) that bundles may already have in a different version.\n//\n// Out of scope intentionally: tables (use a SQL artifact), images\n// (agents don't emit them), nested lists (rare in answers), HTML\n// passthrough (no XSS surface).\n\nfunction MarkdownText(props: { text: string }): React.JSX.Element {\n const blocks = React.useMemo(() => parseMarkdown(props.text), [props.text]);\n return <>{blocks}</>;\n}\n\ntype MdBlock =\n | { kind: \"h\"; level: 1 | 2 | 3; text: string }\n | { kind: \"p\"; text: string }\n | { kind: \"code\"; lang: string; code: string }\n | { kind: \"list\"; items: string[] };\n\nfunction parseMarkdown(text: string): React.JSX.Element[] {\n const lines = text.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n const blocks: MdBlock[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n if (line === undefined) {\n i++;\n continue;\n }\n // Fenced code block\n const fence = line.match(/^```(\\w*)\\s*$/);\n if (fence) {\n const lang = fence[1] ?? \"\";\n const buf: string[] = [];\n i++;\n while (i < lines.length && !/^```\\s*$/.test(lines[i] ?? \"\")) {\n buf.push(lines[i] ?? \"\");\n i++;\n }\n i++; // skip closing fence\n blocks.push({ kind: \"code\", lang, code: buf.join(\"\\n\") });\n continue;\n }\n // Heading\n const h = line.match(/^(#{1,3})\\s+(.+)$/);\n if (h) {\n blocks.push({\n kind: \"h\",\n level: h[1]?.length as 1 | 2 | 3,\n text: h[2]!\n });\n i++;\n continue;\n }\n // List\n if (/^\\s*[-*]\\s+/.test(line)) {\n const items: string[] = [];\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").replace(/^\\s*[-*]\\s+/, \"\"));\n i++;\n }\n blocks.push({ kind: \"list\", items });\n continue;\n }\n // Blank line\n if (line.trim() === \"\") {\n i++;\n continue;\n }\n // Paragraph — collect consecutive non-empty, non-special lines.\n const buf: string[] = [line];\n i++;\n while (i < lines.length) {\n const next = lines[i] ?? \"\";\n if (\n next.trim() === \"\" ||\n /^#{1,3}\\s+/.test(next) ||\n /^```/.test(next) ||\n /^\\s*[-*]\\s+/.test(next)\n ) {\n break;\n }\n buf.push(next);\n i++;\n }\n blocks.push({ kind: \"p\", text: buf.join(\" \") });\n }\n\n return blocks.map((b, idx) => {\n switch (b.kind) {\n case \"h\": {\n const Tag = `h${b.level}` as unknown as keyof React.JSX.IntrinsicElements;\n const headingStyle = b.level === 1 ? styles.h1 : b.level === 2 ? styles.h2 : styles.h3;\n return (\n <Tag key={idx} style={headingStyle}>\n {renderInline(b.text)}\n </Tag>\n );\n }\n case \"code\":\n return (\n <pre key={idx} style={styles.codeBlock} data-lang={b.lang || undefined}>\n <code>{b.code}</code>\n </pre>\n );\n case \"list\":\n return (\n <ul key={idx} style={styles.list}>\n {b.items.map((item, i) => (\n <li key={i}>{renderInline(item)}</li>\n ))}\n </ul>\n );\n case \"p\":\n return (\n <p key={idx} style={styles.paragraph}>\n {renderInline(b.text)}\n </p>\n );\n }\n });\n}\n\n/**\n * Inline tokenizer for **bold**, *italic*, `code`, and [text](url).\n * Lazy: scan once, split into segments. The patterns are matched in\n * priority order (code first so backticks don't get eaten by bold).\n */\nfunction renderInline(text: string): React.ReactNode[] {\n const segments: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n // Patterns in priority order — code first since `**` inside backticks\n // shouldn't be parsed.\n const patterns: Array<{\n re: RegExp;\n render: (m: RegExpExecArray) => React.ReactNode;\n }> = [\n { re: /`([^`]+)`/, render: (m) => <code style={styles.inlineCode}>{m[1]}</code> },\n {\n re: /\\[([^\\]]+)\\]\\(([^)]+)\\)/,\n render: (m) => {\n // Allowlist URL schemes. Agent answers cross the LLM trust\n // boundary — a malicious prompt fragment reflected into the\n // answer could otherwise emit\n // `[click](javascript:alert(document.cookie))` and execute\n // in the bundle's origin. Accept only http(s), mailto,\n // root-relative paths, and same-page fragments; everything\n // else renders as plain text.\n if (isSafeLinkHref(m[2])) {\n return (\n <a href={m[2]} target='_blank' rel='noreferrer noopener' style={styles.link}>\n {m[1]}\n </a>\n );\n }\n return <>{m[1]}</>;\n }\n },\n { re: /\\*\\*([^*]+)\\*\\*/, render: (m) => <strong>{m[1]}</strong> },\n { re: /\\*([^*]+)\\*/, render: (m) => <em>{m[1]}</em> }\n ];\n\n while (remaining.length > 0) {\n let earliest: { idx: number; len: number; node: React.ReactNode } | null = null;\n for (const { re, render } of patterns) {\n const m = re.exec(remaining);\n if (m && (earliest === null || m.index < earliest.idx)) {\n earliest = { idx: m.index, len: m[0].length, node: render(m) };\n }\n }\n if (earliest === null) {\n segments.push(remaining);\n break;\n }\n if (earliest.idx > 0) segments.push(remaining.slice(0, earliest.idx));\n segments.push(<React.Fragment key={key++}>{earliest.node}</React.Fragment>);\n remaining = remaining.slice(earliest.idx + earliest.len);\n }\n return segments;\n}\n\n// ── Styles ──────────────────────────────────────────────────────────────────\n//\n// All inline. No CSS file, no class names, no global side effects.\n// Bundles that want custom styling pass `className` and override\n// with their own selectors, or skip the drop-in entirely and build\n// on `useAgentRun`.\n\nconst SANS =\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif';\nconst MONO = 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace';\n\nconst styles: Record<string, React.CSSProperties> = {\n answerWrap: {\n fontFamily: SANS,\n fontSize: 14,\n lineHeight: 1.5,\n color: \"#1f2937\"\n },\n statusRow: { display: \"flex\", alignItems: \"center\", gap: 8, padding: \"8px 0\" },\n spinner: {\n display: \"inline-block\",\n width: 12,\n height: 12,\n borderRadius: \"50%\",\n border: \"2px solid #d1d5db\",\n borderTopColor: \"#6b7280\",\n animation: \"oxy-spin 0.8s linear infinite\"\n },\n statusText: { color: \"#6b7280\" },\n markdown: { marginTop: 8 },\n h1: { fontSize: 20, fontWeight: 600, margin: \"16px 0 8px\" },\n h2: { fontSize: 17, fontWeight: 600, margin: \"14px 0 6px\" },\n h3: { fontSize: 15, fontWeight: 600, margin: \"12px 0 4px\" },\n paragraph: { margin: \"0 0 8px\" },\n list: { margin: \"0 0 8px\", paddingLeft: 20 },\n codeBlock: {\n fontFamily: MONO,\n fontSize: 12,\n background: \"#f3f4f6\",\n border: \"1px solid #e5e7eb\",\n borderRadius: 6,\n padding: \"8px 10px\",\n overflowX: \"auto\",\n margin: \"8px 0\"\n },\n inlineCode: {\n fontFamily: MONO,\n fontSize: \"0.92em\",\n background: \"#f3f4f6\",\n padding: \"1px 4px\",\n borderRadius: 3\n },\n link: { color: \"#2563eb\", textDecoration: \"underline\" },\n clarification: {\n marginTop: 12,\n padding: \"10px 12px\",\n background: \"#fef3c7\",\n border: \"1px solid #fcd34d\",\n borderRadius: 6,\n color: \"#78350f\"\n },\n error: {\n marginTop: 12,\n padding: \"10px 12px\",\n background: \"#fee2e2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 6,\n color: \"#991b1b\"\n },\n threadLinkRow: {\n marginTop: 12,\n textAlign: \"right\",\n display: \"flex\",\n justifyContent: \"flex-end\",\n alignItems: \"center\",\n gap: 6\n },\n threadLink: { fontSize: 12, color: \"#6b7280\", textDecoration: \"none\" },\n betaBadge: {\n fontSize: 9,\n fontWeight: 600,\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n padding: \"1px 5px\",\n borderRadius: 3,\n background: \"#fef3c7\",\n color: \"#92400e\",\n border: \"1px solid #fcd34d\"\n },\n artifactList: { display: \"flex\", flexDirection: \"column\", gap: 8, marginBottom: 8 },\n artifact: {\n border: \"1px solid #e5e7eb\",\n borderRadius: 6,\n background: \"#fafafa\",\n overflow: \"hidden\"\n },\n artifactHeader: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n width: \"100%\",\n padding: \"6px 10px\",\n background: \"transparent\",\n border: \"none\",\n borderBottom: \"1px solid transparent\",\n cursor: \"pointer\",\n fontFamily: SANS,\n fontSize: 12,\n color: \"#374151\"\n },\n artifactBadge: {\n fontWeight: 600,\n fontSize: 11,\n textTransform: \"uppercase\",\n letterSpacing: 0.4,\n color: \"#4b5563\"\n },\n artifactSummary: { color: \"#6b7280\", flex: 1 },\n artifactToggle: { color: \"#2563eb\" },\n sqlBlock: {\n fontFamily: MONO,\n fontSize: 12,\n margin: 0,\n padding: \"8px 10px\",\n background: \"#0f172a\",\n color: \"#e2e8f0\",\n overflowX: \"auto\"\n },\n resultsWrap: { padding: 8, overflowX: \"auto\" },\n resultsTable: { width: \"100%\", borderCollapse: \"collapse\", fontSize: 12 },\n resultsTh: {\n textAlign: \"left\",\n padding: \"4px 8px\",\n borderBottom: \"1px solid #e5e7eb\",\n fontWeight: 600,\n color: \"#374151\"\n },\n resultsTd: { padding: \"4px 8px\", borderBottom: \"1px solid #f3f4f6\", color: \"#1f2937\" },\n truncatedNote: { fontSize: 11, color: \"#6b7280\", padding: \"6px 8px\" },\n chatWrap: { fontFamily: SANS, fontSize: 14, color: \"#1f2937\" },\n chatForm: { display: \"flex\", gap: 8, marginBottom: 12 },\n chatInput: {\n flex: 1,\n padding: \"8px 12px\",\n border: \"1px solid #d1d5db\",\n borderRadius: 6,\n fontSize: 14,\n fontFamily: SANS\n },\n chatSubmit: {\n padding: \"8px 16px\",\n border: \"none\",\n borderRadius: 6,\n background: \"#2563eb\",\n color: \"#ffffff\",\n fontSize: 14,\n fontWeight: 500,\n cursor: \"pointer\"\n },\n chatCancel: {\n padding: \"8px 12px\",\n border: \"1px solid #d1d5db\",\n borderRadius: 6,\n background: \"#ffffff\",\n color: \"#374151\",\n fontSize: 14,\n cursor: \"pointer\"\n },\n emptyState: { padding: \"12px 0\", color: \"#9ca3af\", fontStyle: \"italic\" }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/anomalies.ts","../src/customer-app/logger.ts","../src/customer-app/debug.ts","../src/customer-app/errors.ts","../src/customer-app/inject.ts","../src/customer-app/manifest.ts","../src/customer-app/function-invoke.ts","../src/customer-app/function-sse.ts","../src/customer-app/interpolate.ts","../src/customer-app/markdown.ts","../src/customer-app/react.tsx","../src/metricTree.ts"],"sourcesContent":["// Anomaly inbox types + client. Surfaces the `/semantic/anomalies*`\n// endpoints — list, scan, status, explain — so SDK consumers can render\n// the same inbox the Oxy IDE uses.\n\nimport type { OxyConfig } from \"./config\";\nimport type { ExplainResult } from \"./metricTree\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnomalyStatus = \"new\" | \"acknowledged\" | \"dismissed\";\nexport type AnomalySeverity = \"low\" | \"medium\" | \"high\";\n\n/**\n * One row in the anomaly inbox. Detected by `oxy-metric-monitoring` per\n * `.monitor.yml` entry; upserted by repeat scans so unresolved anomalies\n * stay visible without piling up duplicates.\n */\nexport interface Anomaly {\n id: string;\n workspace_id: string;\n measure: string;\n time_dimension: string;\n granularity: string;\n period_start: string;\n period_end: string;\n observed: number;\n expected: number;\n lower_bound: number;\n upper_bound: number;\n z_score: number;\n severity: AnomalySeverity | string;\n status: AnomalyStatus | string;\n label?: string | null;\n /** Cached ExplainResult — populated by `POST /anomalies/:id/explain`. */\n explain_cache?: ExplainResult | null;\n explain_cached_at?: string | null;\n detected_at: string;\n updated_at: string;\n}\n\nexport interface ListAnomaliesOptions {\n status?: AnomalyStatus | string;\n /** Max rows (server caps at 500, defaults to 100). */\n limit?: number;\n}\n\nexport interface ListAnomaliesResponse {\n anomalies: Anomaly[];\n}\n\nexport interface ScanOptions {\n /** Override the reference \"now\" date (YYYY-MM-DD) — useful for demos. */\n as_of?: string;\n}\n\nexport interface ScanResponse {\n monitors_scanned: number;\n monitors_failed: number;\n anomalies_persisted: number;\n}\n\n// ── Client ───────────────────────────────────────────────────────────────────\n\nexport type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;\n\n/**\n * Client for `/semantic/anomalies*`. Construct via `OxyClient.anomalies`\n * rather than instantiating directly — the getter wires the request helper\n * so auth, timeout, and branch propagation come along for free.\n *\n * @example\n * ```typescript\n * const { anomalies } = await client.anomalies.list({ status: \"new\" });\n * for (const a of anomalies) {\n * console.log(a.label ?? a.measure, a.severity, a.z_score.toFixed(2));\n * }\n * ```\n */\nexport class AnomaliesClient {\n private readonly request: RequestFn;\n private readonly config: OxyConfig;\n\n constructor(config: OxyConfig, request: RequestFn) {\n this.config = config;\n this.request = request;\n }\n\n private path(suffix: string): string {\n return `/${this.config.projectId}/semantic/anomalies${suffix}`;\n }\n\n private buildQuery(extra: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...extra };\n if (this.config.branch) params.branch = this.config.branch;\n const qs = new URLSearchParams(params).toString();\n return qs ? `?${qs}` : \"\";\n }\n\n /**\n * List anomalies in the inbox, newest first.\n *\n * @example\n * ```typescript\n * // Open / unresolved anomalies only\n * const { anomalies } = await client.anomalies.list({ status: \"new\" });\n * ```\n */\n async list(options: ListAnomaliesOptions = {}): Promise<ListAnomaliesResponse> {\n const extra: Record<string, string> = {};\n if (options.status) extra.status = options.status;\n if (options.limit) extra.limit = String(options.limit);\n // No trailing slash before the query — axum 307-redirects \"/anomalies/\"\n // to \"/anomalies\", and the redirect fails CORS preflight in browsers.\n return this.request<ListAnomaliesResponse>(this.path(this.buildQuery(extra)));\n }\n\n /**\n * Trigger a full scan. Iterates every `.monitor.yml` entry in the\n * workspace, runs the detector, and upserts matching rows into the\n * inbox. Returns counts of scanned / failed / persisted.\n *\n * @example\n * ```typescript\n * // Scan against a known-good reference date (matches the seed dataset)\n * const result = await client.anomalies.scan({ as_of: \"2025-12-15\" });\n * console.log(`${result.anomalies_persisted} anomalies detected`);\n * ```\n */\n async scan(options: ScanOptions = {}): Promise<ScanResponse> {\n const extra: Record<string, string> = {};\n if (options.as_of) extra.as_of = options.as_of;\n return this.request<ScanResponse>(this.path(`/scan${this.buildQuery(extra)}`), {\n method: \"POST\"\n });\n }\n\n /**\n * Update an anomaly's status (acknowledge / dismiss / re-open).\n */\n async updateStatus(anomalyId: string, status: AnomalyStatus): Promise<Anomaly> {\n const query = this.buildQuery();\n return this.request<Anomaly>(this.path(`/${encodeURIComponent(anomalyId)}/status${query}`), {\n method: \"POST\",\n body: JSON.stringify({ status })\n });\n }\n\n /**\n * Run the metric-tree `explain` for an anomaly and cache the result on\n * the row. Subsequent calls return the cached `ExplainResult` instantly.\n */\n async explain(anomalyId: string): Promise<ExplainResult> {\n const query = this.buildQuery();\n return this.request<ExplainResult>(\n this.path(`/${encodeURIComponent(anomalyId)}/explain${query}`),\n { method: \"POST\" }\n );\n }\n}\n","// Diagnostic logger for customer-app bundles.\n//\n// Customer apps are built by our internal team; \"open DevTools, read\n// the logs\" is a real debugging workflow. The SDK logs every fetch\n// lifecycle (start, success, error) with structured context so an\n// internal dev can correlate UI behavior with what hit the wire\n// without needing server logs.\n//\n// Defaults to console at info level with a `[oxy-app]` prefix.\n// Override via `setOxyAppLogger(...)` for tests or production silence.\n//\n// Log lines are formatted as:\n// [oxy-app] <event> { …structured ctx… }\n// so DevTools' object inspector unfolds them.\n\nexport type OxyAppLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface OxyAppLogger {\n log(level: OxyAppLogLevel, msg: string, ctx?: Record<string, unknown>): void;\n}\n\nlet activeLogger: OxyAppLogger = createConsoleLogger();\n\n/** Replace the global logger. Pass `null` to silence everything. */\nexport function setOxyAppLogger(logger: OxyAppLogger | null): void {\n activeLogger = logger ?? silentLogger();\n}\n\n/** Used by the SDK internals; not part of the public surface. */\nexport function getOxyAppLogger(): OxyAppLogger {\n return activeLogger;\n}\n\nfunction createConsoleLogger(): OxyAppLogger {\n return {\n log(level, msg, ctx) {\n if (typeof console === \"undefined\") return;\n const prefix = \"[oxy-app]\";\n const args: unknown[] = ctx ? [prefix, msg, ctx] : [prefix, msg];\n switch (level) {\n case \"debug\":\n console.debug(...args);\n break;\n case \"info\":\n console.info(...args);\n break;\n case \"warn\":\n console.warn(...args);\n break;\n case \"error\":\n console.error(...args);\n break;\n }\n }\n };\n}\n\nfunction silentLogger(): OxyAppLogger {\n return { log() {} };\n}\n","// Bundle-side accessor for the server's diagnostic snapshot.\n//\n// `GET /api/customer-apps/<org>/<app>/debug` returns a structured\n// snapshot of what oxy currently sees about a registered customer\n// app: the app row, bundle dir resolution, and parsed manifest (or\n// parse error). Useful when a bundle isn't loading what you expected\n// and you want to verify what the server actually sees — without\n// needing terminal access.\n//\n// The `products` field on the snapshot is a legacy artifact carried\n// for server-side compatibility; in v2 the bundle owns its queries\n// via `useQuery` and the field is always empty for v2 manifests.\n\nimport { getOxyAppLogger } from \"./logger\";\nimport type { ResolvedCustomerAppManifest } from \"./manifest\";\n\n/** Untyped at the boundary — keep it loose so server-side schema\n * additions don't break older bundles. Stable enough for inspection\n * but not a contract clients should depend on field-by-field. */\nexport interface CustomerAppDebugSnapshot {\n org_slug: string;\n app_slug: string;\n app: {\n id: string;\n slug: string;\n name: string;\n status: string;\n source_type: string;\n project_id: string;\n branch: string;\n };\n bundle_dir: string | null;\n bundle_dir_exists: boolean;\n /** Raw parsed manifest from the server — kept loose so schema additions don't break older bundles. */\n manifest: Record<string, unknown> | null;\n manifest_error: string | null;\n products: Array<{ name: string; producer: string }>;\n}\n\n/**\n * Fetch the server-side diagnostic snapshot for this bundle. Pair with\n * `loadCustomerAppManifest()` — pass its result here. Logs the\n * snapshot through the SDK logger so it appears in the bundle's\n * console at info level.\n */\nexport async function getCustomerAppDebug(\n resolved: ResolvedCustomerAppManifest\n): Promise<CustomerAppDebugSnapshot> {\n const log = getOxyAppLogger();\n const { apiBaseUrl, orgSlug, appSlug } = resolved;\n const url =\n `${apiBaseUrl}/api/customer-apps/` +\n `${encodeURIComponent(orgSlug)}/${encodeURIComponent(appSlug)}/debug`;\n\n log.log(\"debug\", \"fetching debug snapshot\", { url });\n const res = await fetch(url, { credentials: \"same-origin\" });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch debug snapshot (HTTP ${res.status}): ${detail || res.statusText}`\n );\n }\n const snapshot = (await res.json()) as CustomerAppDebugSnapshot;\n log.log(\"info\", \"debug snapshot\", snapshot as unknown as Record<string, unknown>);\n return snapshot;\n}\n","// Friendly interpretation of the errors a customer-app bundle can hit\n// at startup. The bundle catches an `Error` thrown by\n// `loadCustomerAppManifest` or `useQuery`, hands it to\n// `interpretCustomerAppError`, and renders the returned struct as a\n// proper error page — instead of dumping a raw exception that asks\n// the developer to learn the internal contract from a stack trace.\n//\n// Every interpretation includes:\n// - `title`: short headline (\"Manifest not found\")\n// - `message`: the underlying technical message (the raw err.message)\n// - `hint`: an actionable next step (\"commit public/oxy-app.json and rebuild\")\n// - `docs`: a pointer to the relevant section of the architecture doc\n//\n// Add cases as we hit new failure modes in the wild — the catch-all\n// keeps the surface safe in the meantime.\n\nexport interface CustomerAppErrorReport {\n title: string;\n message: string;\n hint: string;\n docs?: string;\n}\n\nconst ARCH_DOC = \"internal-docs/customer-apps.md\";\n\n/** Interpret a thrown error as a structured report for UI display. */\nexport function interpretCustomerAppError(err: unknown): CustomerAppErrorReport {\n const message = err instanceof Error ? err.message : String(err);\n\n // Order matters — earlier matches take priority. Use specific\n // substrings that the loader / fetcher actually emit so this stays\n // grep-discoverable from both ends.\n\n if (/Failed to load oxy-app\\.json.*HTTP 404/.test(message)) {\n return {\n title: \"Manifest not found\",\n message,\n hint:\n \"The bundle is being served, but oxy-app.json was not. Check that \" +\n \"public/oxy-app.json is committed in the customer-app repo and \" +\n \"that the build copied it into the static output. If you're using \" +\n \"Next.js, anything under public/ is auto-copied to out/.\",\n docs: ARCH_DOC\n };\n }\n\n if (/Failed to load oxy-app\\.json/.test(message)) {\n return {\n title: \"Manifest could not be loaded\",\n message,\n hint:\n \"Network error fetching the manifest. Confirm the bundle is being \" +\n \"served from a path that matches OXY_APP_BASE_PATH at \" +\n \"build time — a mismatch causes assets and the manifest to 404.\",\n docs: ARCH_DOC\n };\n }\n\n if (/schemaVersion/i.test(message)) {\n return {\n title: \"Manifest schema mismatch\",\n message,\n hint:\n \"This bundle was built against a different version of the \" +\n \"data-product contract than the SDK it ships. Rebuild the bundle \" +\n \"with a compatible @oxy-hq/sdk version.\",\n docs: ARCH_DOC\n };\n }\n\n // Query proxy responses. The `${status}: ${body}` shape comes from\n // useQuery — body is the server's `{ \"message\": \"...\" }` JSON for\n // structured errors, or raw text for unstructured ones.\n\n if (/^401:/m.test(message)) {\n return {\n title: \"Session expired\",\n message,\n hint: \"Reload the page to re-authenticate via oxy's session cookie.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*origin not allowed/im.test(message)) {\n return {\n title: \"Request origin not allowed\",\n message,\n hint:\n \"The bundle's host isn't in oxy's OXY_ALLOWED_ORIGINS. \" +\n \"Production: add the bundle's serving origin to the env var. \" +\n \"Local dev: oxy auto-allows http://localhost:5173 and :5174.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*not a member/im.test(message)) {\n return {\n title: \"Access denied\",\n message,\n hint:\n \"Your account isn't a member of the org that owns this project. \" +\n \"Ask an org owner to add you.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*SELECT.*WITH/im.test(message)) {\n return {\n title: \"Query rejected — read-only endpoint\",\n message,\n hint:\n \"This proxy only runs SELECT or WITH queries. Mutations \" +\n \"(INSERT/UPDATE/DELETE/DROP) are not allowed from customer-app \" +\n \"bundles.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:/m.test(message)) {\n return {\n title: \"Access denied\",\n message,\n hint: \"The request was rejected by the server. Check the oxy server \" + \"logs for details.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^404:/m.test(message) && /project/i.test(message)) {\n return {\n title: \"Project not found\",\n message,\n hint:\n \"The projectId in oxy-app.json doesn't match any registered \" +\n \"project. Confirm the manifest's projectId is a real UUID for \" +\n \"this deployment.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^400:.*sql.*must be non-empty/im.test(message)) {\n return {\n title: \"Empty SQL\",\n message,\n hint:\n \"useQuery was called with an empty or whitespace-only `sql`. \" +\n \"Pass a real query, or set `enabled: false` to skip the call.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^400:/m.test(message) && /query failed/i.test(message)) {\n return {\n title: \"Query failed\",\n message,\n hint:\n \"The SQL ran but the warehouse rejected it. Full error in the \" +\n \"oxy server logs (look for the projects::query span).\",\n docs: ARCH_DOC\n };\n }\n\n if (/^502:/m.test(message)) {\n return {\n title: \"Warehouse unreachable\",\n message,\n hint:\n \"Oxy couldn't reach the configured database. Check connector \" +\n \"config + warehouse health.\",\n docs: ARCH_DOC\n };\n }\n\n // \"Unexpected token '<', '<!doctype '...\" — the bundle asked for JSON\n // at a path that oxy resolved to its SPA-fallback HTML.\n //\n // In v2 the single most likely cause is: the built bundle is stale.\n // It was built against an older SDK whose useQuery / fetchers pointed\n // at endpoints that no longer exist (e.g. the deleted /products/...\n // route), so the request resolved to the SPA fallback and returned\n // index.html where the bundle expected a JSON body.\n if (/Unexpected token '<'|<!doctype/i.test(message)) {\n return {\n title: \"Fetched HTML where JSON was expected\",\n message,\n hint:\n \"Most likely the built bundle is stale — built against an old \" +\n \"SDK whose endpoints no longer exist on the server. Rebuild the \" +\n \"bundle (vite build) with @oxy-hq/sdk@^2.0.0 and reload. If \" +\n \"the bundle is current, check that OXY_APP_BASE_PATH matches \" +\n \"the path the customer-app row is served at.\",\n docs: ARCH_DOC\n };\n }\n\n // Catch-all — surfaces the raw message but with a generic next step.\n return {\n title: \"Unexpected error loading the dashboard\",\n message,\n hint:\n \"Check the browser console for the full stack trace, and the oxy \" +\n \"server logs for the corresponding request.\",\n docs: ARCH_DOC\n };\n}\n","// Runtime app-config injected into the browser by oxy when it serves\n// a customer-app bundle's HTML. Lets a single bundle serve any\n// registered app without having `(orgId, projectId)` baked in at\n// build time — see\n// `crates/app/src/server/api/customer_apps_serve.rs::inject_app_config`\n// on the server side.\n\n/**\n * Shape of `window.__OXY_APP__` written by oxy at serve time.\n * Consumed by `loadCustomerAppManifest` as the authoritative identity\n * source (overrides any hints in `oxy-app.json`).\n */\nexport interface OxyInjectedAppConfig {\n appId: string;\n slug: string;\n orgId: string;\n orgSlug: string;\n projectId: string;\n branch: string;\n /** Empty string means same-origin (the default for v2). */\n apiBaseUrl: string;\n}\n\ndeclare global {\n interface Window {\n __OXY_APP__?: OxyInjectedAppConfig;\n }\n}\n\n/**\n * Read the runtime app-config oxy injected at serve time. Returns\n * `undefined` outside the browser or when the global isn't set\n * (`pnpm dev` against a non-oxy server, etc. — manifest hints are\n * the fallback).\n */\nexport function readInjectedAppConfig(): OxyInjectedAppConfig | undefined {\n if (typeof window === \"undefined\") return undefined;\n return window.__OXY_APP__;\n}\n","// Manifest loader for customer-app bundles served by oxy at\n// `app.oxygen-hq.com/customer-apps/<org_slug>/<app_slug>/`.\n//\n// The bundle commits a `public/oxy-app.json` declaring its identity\n// (slug, orgSlug, projectId). This module:\n// 1. Fetches that manifest at startup (cached after the first call).\n// 2. Validates the schema with clear errors (v2 only — v1 is rejected).\n// 3. Joins it with the runtime identity oxy injects via\n// `<script>window.__OXY_APP__=...</script>`.\n//\n// Bundles call `useQuery` directly for data access — there are no\n// `products` or `writers` declarations in v2 manifests.\n\nimport { type OxyInjectedAppConfig, readInjectedAppConfig } from \"./inject\";\nimport { getOxyAppLogger } from \"./logger\";\n\n// ── Manifest types ──────────────────────────────────────────────────────────\n\n/**\n * Declaration of a single Oxy Function shipped in the bundle's\n * `functions/` dir. See `internal-docs/2026-06-12-customer-apps-functions-design.md`.\n *\n * All fields optional except that at least one invocation surface\n * (`route`, `schedule`, or `airwayStep`) must be active. Absent =\n * `route: true` (HTTP-invocable via `useFunction`).\n */\nexport interface OxyAppFunctionManifest {\n /** Source entry, relative to the app dir. Default: `functions/<name>.ts`. */\n entry?: string;\n /** Cron expression. When set, the function fires on this schedule. */\n schedule?: string;\n /** IANA timezone for `schedule`. Default: `UTC`. */\n timezone?: string;\n /** Expose `POST .../fn/<name>` (called via `useFunction`). Default: true. */\n route?: boolean;\n /** Wire the function in as an Airway pipeline transform step. */\n airwayStep?: { pipeline: string; resource: string };\n /** Wall-clock timeout. Default 30, max 300. */\n timeoutSeconds?: number;\n /**\n * Opt-in result caching for route invocations. Omit (the default) to never\n * cache — the safe choice for a side-effectful function (writes, external\n * POSTs, ELT). Set `ttlSeconds` ONLY for read-only / idempotent functions:\n * results are then cached per (build, function, user, request body) for that\n * window, and a repeat `useFunction().invoke(sameBody)` returns the cached\n * result without re-running. A `?refresh` query bypasses it.\n */\n cache?: { ttlSeconds?: number };\n /**\n * Databases this function's `ctx.warehouse.*` writes may target. Omit (or\n * leave empty) and the function may NOT write to any database — writes are\n * fail-closed and rejected before any connection is opened. Declare a\n * destination here ONLY for a function that legitimately writes to it; a\n * read-only function omits it. This scopes writes away from the project's\n * source warehouse.\n */\n destinations?: string[];\n}\n\n/** Wire shape of `oxy-app.json` (v2 only). */\nexport interface OxyAppManifest {\n /** Must be 2. v1 manifests are no longer supported. */\n schemaVersion: 2;\n /**\n * Optional display name. The admin \"Link existing\" dialog prefills\n * its Name field from this. Omit to let oxy fall back to the\n * folder basename.\n */\n name?: string;\n /**\n * URL slug. **Required.** The canonical source of truth — the\n * dialog locks the slug field to this value, and\n * `OXY_APP_BASE_PATH=/customer-apps/<org>/<slug>/` baked into the\n * build must match.\n */\n slug: string;\n /**\n * Optional org slug. Prefills the dialog's org picker; operator\n * can still override. Carries no security weight — the actual\n * access check is on the linked row.\n */\n orgSlug?: string;\n /**\n * Optional project (workspace) uuid the bundle expects to read\n * from. Used by `useQuery` to construct the\n * `/api/projects/:id/query` URL.\n */\n projectId?: string;\n /**\n * Optional map of Oxy Functions (server-side handlers) shipped in the\n * bundle's `functions/` dir, keyed by function name. Omit for a pure\n * static bundle (today's default). See the functions design doc.\n */\n functions?: Record<string, OxyAppFunctionManifest>;\n}\n\n// ── Resolved manifest ───────────────────────────────────────────────────────\n\n/**\n * Manifest + runtime-injected identity needed to call oxy. Callers\n * should treat this as the only source of truth for \"which org/app\n * does this bundle belong to.\"\n */\nexport interface ResolvedCustomerAppManifest {\n manifest: OxyAppManifest;\n /**\n * Always an empty array for v2 manifests. Kept for API compatibility;\n * callers that previously iterated product names should switch to\n * explicit `useQuery` calls.\n * @deprecated Will be removed in a future version.\n */\n productNames: string[];\n /** Org slug injected by oxy. */\n orgSlug: string;\n /** App slug injected by oxy. */\n appSlug: string;\n /**\n * The oxy server's API base URL. Empty string when oxy serves the\n * bundle itself (same-origin, the common case); a full URL only\n * when the bundle is running under a dev server proxy.\n */\n apiBaseUrl: string;\n /** App UUID; informational. */\n appId?: string;\n /**\n * Project (workspace) UUID. Injection (`window.__OXY_APP__.projectId`)\n * wins over the manifest's `projectId` field — the admin row is\n * authoritative. Manifest `projectId` is a dev-time hint used only\n * when running without a server. Used by `useQuery` to construct the\n * `/api/projects/:id/query` URL.\n */\n projectId?: string;\n}\n\nexport interface LoadManifestOptions {\n /**\n * Override the URL the manifest is fetched from. Default:\n * `<injected_base>/oxy-app.json` or `/oxy-app.json`.\n * Useful for non-Next bundlers — set explicitly to wherever your\n * bundler emits static assets.\n */\n manifestUrl?: string;\n}\n\nlet cached: Promise<ResolvedCustomerAppManifest> | null = null;\n\n/**\n * Load + validate the manifest. Cached after the first call so callers\n * can invoke this from every component without coordinating.\n */\nexport function loadCustomerAppManifest(\n options: LoadManifestOptions = {}\n): Promise<ResolvedCustomerAppManifest> {\n if (!cached) {\n cached = fetchAndValidate(options);\n }\n return cached;\n}\n\n/** For tests: reset the cache between runs. */\nexport function _resetCustomerAppManifestCacheForTest(): void {\n cached = null;\n}\n\nasync function fetchAndValidate(\n options: LoadManifestOptions\n): Promise<ResolvedCustomerAppManifest> {\n const log = getOxyAppLogger();\n const injected = readInjectedAppConfig();\n const manifestUrl = options.manifestUrl ?? defaultManifestUrl(injected);\n\n log.log(\"info\", \"loading manifest\", {\n manifestUrl,\n injectionPresent: !!injected,\n orgSlug: injected?.orgSlug,\n appSlug: injected?.slug,\n appId: injected?.appId\n });\n\n const startedAt = Date.now();\n const res = await fetch(manifestUrl, { credentials: \"same-origin\" });\n if (!res.ok) {\n log.log(\"error\", \"manifest fetch failed\", {\n manifestUrl,\n status: res.status,\n statusText: res.statusText\n });\n throw new Error(\n `Failed to load oxy-app.json from ${manifestUrl} (HTTP ${res.status}). ` +\n `The customer-app repo must commit this file alongside the bundle.`\n );\n }\n const raw = (await res.json()) as unknown;\n const manifest = validateManifest(raw, manifestUrl);\n\n const resolved: ResolvedCustomerAppManifest = {\n manifest,\n productNames: [],\n orgSlug: injected?.orgSlug ?? \"\",\n appSlug: injected?.slug ?? \"\",\n apiBaseUrl: injected?.apiBaseUrl || \"\",\n appId: injected?.appId,\n projectId: injected?.projectId ?? manifest.projectId\n };\n log.log(\"info\", \"manifest ready\", {\n durationMs: Date.now() - startedAt,\n schemaVersion: manifest.schemaVersion,\n slug: manifest.slug\n });\n return resolved;\n}\n\n/**\n * Default manifest URL.\n *\n * Resolution order (bundler-agnostic):\n * 1. `window.__OXY_APP__.orgSlug`/`slug` injection → the canonical\n * `/customer-apps/<org>/<app>/oxy-app.json`. Works for every\n * bundle oxy serves regardless of how it was built.\n * 2. `NEXT_PUBLIC_APP_BASE_PATH` env var — kept for backward compat\n * with Next.js bundles that bake basePath at build time.\n * 3. Empty basePath → `/oxy-app.json` (only matches when running in\n * a `vite dev` / `next dev` root mount; will 404 under oxy).\n */\nfunction defaultManifestUrl(injected: OxyInjectedAppConfig | undefined): string {\n if (injected?.orgSlug && injected?.slug) {\n const org = encodeURIComponent(injected.orgSlug);\n const app = encodeURIComponent(injected.slug);\n return `/customer-apps/${org}/${app}/oxy-app.json`;\n }\n // No injection → bundle is running outside oxy (`pnpm dev` against\n // a local Vite, an iframe preview, etc.). Look up `/oxy-app.json`\n // at the document root; the vite-plugin's dev shim and the\n // standard `public/` convention both serve it there.\n return \"/oxy-app.json\";\n}\n\n// ── Validation ──────────────────────────────────────────────────────────────\n\n/**\n * Validate a v2 manifest. Required: schemaVersion === 2, slug (non-empty).\n * Optional: name (display), orgSlug (dev-time hint for the admin dialog),\n * projectId (dev-time hint when there's no server-side injection).\n *\n * At serve time, oxy's identity injection (window.__OXY_APP__) overrides\n * the manifest's orgSlug/projectId — the manifest fields are advisory.\n */\nfunction validateManifest(raw: unknown, url: string): OxyAppManifest {\n if (!isRecord(raw)) {\n throw new Error(`Manifest at ${url} is not a JSON object`);\n }\n if (raw.schemaVersion !== 2) {\n throw new Error(\n `oxy-app.json: schemaVersion must be 2 (got ${JSON.stringify(raw.schemaVersion)}). ` +\n `v1 manifests are no longer supported — upgrade to the identity-only shape.`\n );\n }\n if (raw.products !== undefined || raw.writers !== undefined) {\n throw new Error(\n `oxy-app.json is schemaVersion 2 (identity-only); \\`products\\` and \\`writers\\` are no longer supported`\n );\n }\n if (typeof raw.slug !== \"string\" || !raw.slug.trim()) {\n throw new Error(\"oxy-app.json: `slug` is required and must be a non-empty string\");\n }\n\n const name = typeof raw.name === \"string\" ? raw.name : undefined;\n const slug = raw.slug;\n const orgSlug = typeof raw.orgSlug === \"string\" ? raw.orgSlug : undefined;\n const projectId = typeof raw.projectId === \"string\" ? raw.projectId : undefined;\n const functions = raw.functions !== undefined ? validateFunctions(raw.functions) : undefined;\n\n return { schemaVersion: 2, name, slug, orgSlug, projectId, functions };\n}\n\nconst FUNCTION_NAME_RE = /^[a-z][a-z0-9-]{0,63}$/;\n\n/**\n * Validate the optional `functions` map. Each key is a function name;\n * each value declares how the function is invoked. Mirrors the\n * server-side validation in `customer_apps_publish.rs` so a bad\n * manifest fails at build, not at publish.\n */\nfunction validateFunctions(raw: unknown): Record<string, OxyAppFunctionManifest> {\n if (!isRecord(raw)) {\n throw new Error(\"oxy-app.json: `functions` must be an object keyed by function name\");\n }\n const out: Record<string, OxyAppFunctionManifest> = {};\n for (const [fnName, value] of Object.entries(raw)) {\n if (!FUNCTION_NAME_RE.test(fnName)) {\n throw new Error(`oxy-app.json: function name \"${fnName}\" must match ^[a-z][a-z0-9-]{0,63}$`);\n }\n if (!isRecord(value)) {\n throw new Error(`oxy-app.json: function \"${fnName}\" must be an object`);\n }\n const fn: OxyAppFunctionManifest = {};\n if (value.entry !== undefined) {\n if (typeof value.entry !== \"string\" || !value.entry.trim()) {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`entry\\` must be a non-empty string`);\n }\n fn.entry = value.entry;\n }\n if (value.schedule !== undefined) {\n if (typeof value.schedule !== \"string\" || !value.schedule.trim()) {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`schedule\\` must be a cron string`);\n }\n fn.schedule = value.schedule;\n }\n if (value.timezone !== undefined) {\n if (typeof value.timezone !== \"string\") {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`timezone\\` must be a string`);\n }\n fn.timezone = value.timezone;\n }\n if (value.route !== undefined) {\n if (typeof value.route !== \"boolean\") {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`route\\` must be a boolean`);\n }\n fn.route = value.route;\n }\n if (value.airwayStep !== undefined) {\n const step = value.airwayStep;\n if (\n !isRecord(step) ||\n typeof step.pipeline !== \"string\" ||\n typeof step.resource !== \"string\"\n ) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" \\`airwayStep\\` must be { pipeline, resource }`\n );\n }\n fn.airwayStep = { pipeline: step.pipeline, resource: step.resource };\n }\n if (value.timeoutSeconds !== undefined) {\n const t = value.timeoutSeconds;\n if (typeof t !== \"number\" || !Number.isInteger(t) || t < 1 || t > 300) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" \\`timeoutSeconds\\` must be an integer in [1, 300]`\n );\n }\n fn.timeoutSeconds = t;\n }\n if (value.cache !== undefined) {\n const c = value.cache;\n if (!isRecord(c)) {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`cache\\` must be an object`);\n }\n if (c.ttlSeconds !== undefined) {\n const ttl = c.ttlSeconds;\n if (typeof ttl !== \"number\" || !Number.isInteger(ttl) || ttl < 1) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" \\`cache.ttlSeconds\\` must be a positive integer`\n );\n }\n fn.cache = { ttlSeconds: ttl };\n }\n }\n // At least one invocation surface must be active. `route` defaults\n // to true only when no other surface is declared, matching the doc.\n const hasSchedule = fn.schedule !== undefined;\n const hasAirway = fn.airwayStep !== undefined;\n const routeActive = fn.route ?? !(hasSchedule || hasAirway);\n if (!routeActive && !hasSchedule && !hasAirway) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" must enable at least one of route/schedule/airwayStep`\n );\n }\n out[fnName] = fn;\n }\n return out;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","// In-flight deduplication for `useFunction().invoke()`.\n//\n// A function is arbitrary server-side logic and is frequently SIDE-EFFECTFUL\n// (warehouse writes, external POSTs, ELT kick-offs), so we must NOT cache\n// completed results — a fresh invoke after the first settles has to run again.\n//\n// What IS safe (and desirable) is collapsing *concurrent* identical invokes\n// into ONE request: a double-click, or two components invoking the same\n// function with the same body at the same time, should not fire two POSTs\n// (which would, e.g., post a journal entry twice). Once the in-flight request\n// settles, the entry is dropped, so the next invoke runs fresh.\n//\n// Result *caching* is a separate, opt-in, server-side feature (a function\n// declares `cache: { ttlSeconds }` in oxy-app.json) — never a client default.\n\nconst inflight = new Map<string, Promise<unknown>>();\n\n/**\n * Dedup key for an invocation: function name + its (stable-serialized) body,\n * joined by a newline. Function names are `[a-z][a-z0-9-]*` (no newline), so\n * the separator can never collide with a name.\n */\nexport function functionInvokeKey(name: string, body: unknown): string {\n return `${name}\\n${JSON.stringify(body ?? {})}`;\n}\n\n/**\n * Run `run()` unless an identical invocation is already in flight, in which\n * case share its promise. The entry is removed once the promise settles — so\n * this dedups concurrency only, it does NOT memoize the result.\n */\nexport function sharedFunctionInvoke<Data>(key: string, run: () => Promise<Data>): Promise<Data> {\n const existing = inflight.get(key) as Promise<Data> | undefined;\n if (existing) return existing;\n const p = run().finally(() => {\n inflight.delete(key);\n });\n inflight.set(key, p);\n return p;\n}\n\n/** Test-only: reset in-flight state between tests. */\nexport function __clearInflightFunctions(): void {\n inflight.clear();\n}\n","// SSE reader for `/fn/<name>` responses (design doc §11.2).\n//\n// The route emits zero or more `event: log` frames (the function's\n// `console.*` / `ctx.log` output — collected during the run and sent with the\n// response, not live-tailed — so a developer doesn't have to open the oxy server\n// logs), then terminates with either an `event: done` frame (whose accumulated\n// `data` carries the JSON-encoded function result) or an `event: error` frame\n// (structured `{ error, message }`).\n// Extracted from the React hook so it can be unit-tested without a DOM/render.\n\n/** A captured `console.*` / `ctx.log` line from a function run. */\nexport interface FunctionLog {\n level: string;\n message: string;\n}\n\n/** A successful function result plus the logs captured during the run. */\nexport interface FunctionResult<Data> {\n value: Data;\n logs: FunctionLog[];\n}\n\n/** An error carries the logs captured before the throw, so the app can show them. */\nexport type FunctionError = Error & { logs?: FunctionLog[] };\n\n/**\n * Read a `text/event-stream` function response to completion. Resolves with the\n * decoded result + captured logs, or rejects (with `.logs` attached) on an\n * `event: error` frame / a stream that ends without a terminal event.\n */\nexport async function readFunctionSseStream<Data>(resp: Response): Promise<FunctionResult<Data>> {\n const reader = resp.body?.getReader();\n if (!reader) {\n throw new Error(\"function response has no body stream\");\n }\n const decoder = new TextDecoder();\n let buffer = \"\";\n let dataPayload = \"\";\n const logs: FunctionLog[] = [];\n\n const handleFrame = (frame: string): { done: true; value: Data } | undefined => {\n let event = \"message\";\n let data = \"\";\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\"event:\")) event = line.slice(6).trim();\n else if (line.startsWith(\"data:\")) data += line.slice(5).trim();\n }\n if (event === \"log\") {\n try {\n const l = JSON.parse(data);\n logs.push({ level: String(l.level ?? \"info\"), message: String(l.message ?? \"\") });\n } catch {\n // Ignore a malformed log frame rather than fail the whole invocation.\n }\n } else if (event === \"data\") {\n dataPayload = data;\n } else if (event === \"done\") {\n const parsed = dataPayload ? (JSON.parse(dataPayload) as unknown) : null;\n return { done: true, value: parsed as Data };\n } else if (event === \"error\") {\n const payload = data ? JSON.parse(data) : {};\n const err = new Error(\n payload.message || payload.error || \"function invocation failed\"\n ) as FunctionError;\n err.name = payload.error || \"FunctionError\";\n err.logs = logs;\n throw err;\n }\n return undefined;\n };\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let sep: number;\n while ((sep = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n const result = handleFrame(frame);\n if (result) return { value: result.value, logs };\n }\n }\n throw new Error(\"function stream ended without a terminal event\");\n}\n","/**\n * Interpolate `{{ params.X }}` and `{{ params.X | sqlquote }}` placeholders\n * in a SQL template.\n *\n * - `{{ params.X | sqlquote }}` — quote strings ('foo'), pass numbers and\n * booleans raw, nullish becomes NULL. Mirrors the server's Jinja sqlquote\n * filter.\n * - `{{ params.X }}` — raw pass-through. Used for already-trusted values\n * (numbers, identifiers the caller has validated). Caller is responsible\n * for safety.\n *\n * Not a security boundary. The server still gates SQL execution by\n * project membership. Bundles that accept untrusted user input should\n * use `| sqlquote` or validate/coerce before passing.\n */\nexport function interpolateSqlParams(\n sql: string,\n params: Record<string, string | number | boolean | null | undefined>\n): string {\n return sql.replace(\n /\\{\\{\\s*params\\.([a-zA-Z0-9_]+)(\\s*\\|\\s*sqlquote)?\\s*\\}\\}/g,\n (_match, key: string, sqlquote: string | undefined) => {\n const v = params[key];\n if (v === null || v === undefined) return \"NULL\";\n if (sqlquote) {\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n return `'${String(v).replace(/'/g, \"''\")}'`;\n }\n // No filter — raw pass-through. Caller is responsible.\n return String(v);\n }\n );\n}\n","// Markdown helpers used by `<OxyAnswer>`.\n//\n// Extracted from `react.tsx` so they're testable without spinning up\n// a React renderer in unit tests. The renderer itself stays in\n// `react.tsx` because it's JSX-heavy.\n\n/**\n * Allowlist for `[text](url)` href values in agent-emitted markdown.\n * Markdown comes from an LLM, which sits across an external trust\n * boundary — without this filter, a `javascript:` URL produced by\n * the model would render as a clickable XSS in the bundle's origin.\n *\n * Accepts:\n * - http(s):// absolute URLs\n * - mailto: addresses\n * - root-relative paths (`/foo`)\n * - same-page fragments (`#section`)\n *\n * Rejects everything else, including `javascript:`, `data:`,\n * protocol-relative `//evil.com`, and any other scheme. Comparison\n * is case-insensitive after stripping leading whitespace + ASCII\n * control bytes (browsers strip these before scheme resolution, so\n * `java\\tscript:` would otherwise slip past a naive prefix check).\n */\nexport function isSafeLinkHref(raw: string): boolean {\n // Built char-by-char rather than via regex to avoid embedding\n // actual control bytes in source (linters/IDEs mangle them).\n let cleaned = \"\";\n for (let i = 0; i < raw.length; i++) {\n const cc = raw.charCodeAt(i);\n if (cc > 0x20 && cc !== 0x7f) cleaned += raw[i];\n }\n if (cleaned === \"\") return false;\n if (cleaned.startsWith(\"#\") || cleaned.startsWith(\"/\")) {\n // Reject protocol-relative (`//host/...`) — resolves against\n // current scheme + host, a classic open-redirect vector.\n if (cleaned.startsWith(\"//\")) return false;\n return true;\n }\n const lower = cleaned.toLowerCase();\n return lower.startsWith(\"http://\") || lower.startsWith(\"https://\") || lower.startsWith(\"mailto:\");\n}\n","// React provider + hooks for customer-app bundles.\n//\n// Every customer app does the same dance: load the manifest once at\n// boot, then fire queries as components mount. The bundle developer\n// shouldn't have to thread the resolved manifest through every prop\n// or wire a custom context per app — that's what this file is for.\n//\n// Usage:\n//\n// import { OxyAppProvider, useQuery } from \"@oxy-hq/sdk\";\n//\n// function App() {\n// return <OxyAppProvider><Dashboard /></OxyAppProvider>;\n// }\n// function Dashboard() {\n// const { rows, error, loading } = useQuery({ sql: \"SELECT 1\" });\n// ...\n// }\n//\n// Loading + error states are per-query so the bundle can render\n// per-widget skeletons.\n\nimport * as React from \"react\";\nimport { type CustomerAppErrorReport, interpretCustomerAppError } from \"./errors\";\nimport { functionInvokeKey, sharedFunctionInvoke } from \"./function-invoke\";\nimport {\n type FunctionError,\n type FunctionLog,\n type FunctionResult,\n readFunctionSseStream\n} from \"./function-sse\";\nimport { interpolateSqlParams } from \"./interpolate\";\nimport {\n type LoadManifestOptions,\n loadCustomerAppManifest,\n type ResolvedCustomerAppManifest\n} from \"./manifest\";\nimport { isSafeLinkHref } from \"./markdown\";\n\n// ── Context ─────────────────────────────────────────────────────────────────\n\n/**\n * Credentialed fetch wrapper stored in context so `useQuery` can share\n * the same request mechanism without coupling it to the global `fetch`.\n *\n * Sends `credentials: \"include\"` so the session cookie rides along when\n * the app is served by oxy (in-workspace / admin preview) — that cookie\n * authorizes data calls. For local dev (cross-origin), the\n * `@oxy-hq/vite-plugin` proxy attaches the developer's token. Bundles may\n * override the fetcher for test/proxy environments.\n */\nexport type AppFetcher = typeof fetch;\n\nfunction defaultFetcher(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n return fetch(input, { credentials: \"include\", ...init });\n}\n\ninterface OxyAppContextValue {\n status: \"loading\" | \"ready\" | \"error\";\n resolved?: ResolvedCustomerAppManifest;\n error?: CustomerAppErrorReport;\n /** Credentialed fetch implementation shared by all hooks. */\n fetcher: AppFetcher;\n}\n\nconst OxyAppContext = React.createContext<OxyAppContextValue | undefined>(undefined);\n\nexport interface OxyAppProviderProps {\n /** Optional manifest load options. Same shape as `loadCustomerAppManifest`. */\n manifestOptions?: LoadManifestOptions;\n /**\n * Rendered while the manifest is loading. Defaults to nothing; pass a\n * spinner if you want one.\n */\n fallback?: React.ReactNode;\n /**\n * Rendered on manifest load failure. Receives the structured error\n * report so the bundle can show its own branded error card. Defaults\n * to a minimal text-only fallback (better than a blank page).\n */\n errorFallback?: (err: CustomerAppErrorReport) => React.ReactNode;\n /**\n * Override the fetch implementation used by all hooks (`useQuery`).\n * Useful for test environments or proxy setups. Defaults to a wrapper\n * that sets `credentials: \"include\"` on every request.\n */\n fetcher?: AppFetcher;\n children: React.ReactNode;\n}\n\n/**\n * Top-level provider. Loads the manifest once on mount; children only\n * render after the manifest is ready (or the error fallback fires).\n */\nexport function OxyAppProvider(props: OxyAppProviderProps): React.JSX.Element {\n const { manifestOptions, fallback, errorFallback, fetcher: fetcherProp, children } = props;\n // Stable fetcher reference: caller-supplied or the module-level default.\n // We don't put this in state because it should never change after mount\n // (same reasoning as manifestOptions).\n const fetcher = fetcherProp ?? defaultFetcher;\n const [state, setState] = React.useState<OxyAppContextValue>({ status: \"loading\", fetcher });\n\n React.useEffect(() => {\n let cancelled = false;\n loadCustomerAppManifest(manifestOptions)\n .then((resolved) => {\n if (!cancelled) setState({ status: \"ready\", resolved, fetcher });\n })\n .catch((e: unknown) => {\n if (!cancelled) setState({ status: \"error\", error: interpretCustomerAppError(e), fetcher });\n });\n return () => {\n cancelled = true;\n };\n // `manifestOptions` is treated as stable — changing it mid-flight\n // wouldn't make sense for a manifest load (the URL is baked at\n // build time). `fetcher` is derived from props but also stable;\n // it's included here so biome is satisfied and so the effect does\n // update if a test swaps the fetcher between renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [manifestOptions, fetcher]);\n\n if (state.status === \"error\" && state.error) {\n const err = state.error;\n return (\n <OxyAppContext.Provider value={state}>\n {errorFallback ? errorFallback(err) : defaultErrorFallback(err)}\n </OxyAppContext.Provider>\n );\n }\n if (state.status === \"loading\") {\n return <OxyAppContext.Provider value={state}>{fallback ?? null}</OxyAppContext.Provider>;\n }\n return <OxyAppContext.Provider value={state}>{children}</OxyAppContext.Provider>;\n}\n\nfunction defaultErrorFallback(err: CustomerAppErrorReport): React.ReactNode {\n // Intentionally inline-styled with system fonts so it works in any\n // bundle without depending on Tailwind / CSS-in-JS / etc.\n return (\n <div\n style={{\n margin: \"2rem auto\",\n maxWidth: \"640px\",\n padding: \"1rem\",\n border: \"1px solid #fca5a5\",\n background: \"#fee2e2\",\n color: \"#991b1b\",\n borderRadius: \"8px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n fontSize: \"14px\"\n }}\n >\n <div style={{ fontWeight: 600 }}>{err.title}</div>\n <pre style={{ fontSize: \"12px\", marginTop: \"4px\" }}>{err.message}</pre>\n <div style={{ marginTop: \"12px\" }}>\n <strong>What to try:</strong> {err.hint}\n </div>\n </div>\n );\n}\n\n// ── Beta-warning helper ─────────────────────────────────────────────────────\n\n/**\n * Emit a one-time `console.warn` the first time a beta hook is\n * used in a given page load. Bundles upgrading from a future GA\n * release won't see the warning; the message lets us flag rough\n * edges without breaking the build.\n */\nconst _warnedBeta = new Set<string>();\nfunction warnBetaOnce(name: string): void {\n if (_warnedBeta.has(name)) return;\n _warnedBeta.add(name);\n if (typeof console !== \"undefined\" && typeof console.warn === \"function\") {\n console.warn(\n `[@oxy-hq/sdk] \\`${name}\\` is in beta — interface and behavior may change. ` +\n `See https://github.com/oxy-hq/customer-apps for caveats and the migration guide.`\n );\n }\n}\n\n// ── Error helpers ───────────────────────────────────────────────────────────\n\n/**\n * Error thrown by all customer-app hooks when an API call returns a\n * non-2xx response. Carries the structured `code` + `hint` the server\n * emits so bundle UIs can render an actionable message instead of\n * \"404: { ...json... }\".\n *\n * The server contract is documented in\n * `crates/app/src/server/api/projects/agent_ask.rs` and\n * `procedure_run.rs` — both emit `{ message, code?, hint? }` as JSON.\n * Hooks that previously wrapped the raw text in `new Error()` now\n * throw this type instead.\n */\nexport class OxyApiError extends Error {\n readonly status: number;\n readonly code: string | null;\n readonly hint: string | null;\n constructor(opts: {\n status: number;\n message: string;\n code?: string | null;\n hint?: string | null;\n }) {\n // Build a single multi-line `message` so consumers that just\n // render `.message` still see the hint. Code is included so\n // logs / dev tools can grep for it.\n const base = opts.message || `HTTP ${opts.status}`;\n const code = opts.code ? ` [${opts.code}]` : \"\";\n const hint = opts.hint ? `\\n\\n${opts.hint}` : \"\";\n super(`${base}${code}${hint}`);\n this.name = \"OxyApiError\";\n this.status = opts.status;\n this.code = opts.code ?? null;\n this.hint = opts.hint ?? null;\n }\n}\n\n/**\n * Read a non-2xx response from oxy and return an `OxyApiError`.\n * Parses the JSON envelope when present; falls back to raw text\n * (truncated to 240 chars so a runaway HTML error page doesn't\n * dominate the bundle UI).\n */\nasync function apiErrorFromResponse(resp: Response): Promise<OxyApiError> {\n let body: unknown;\n let raw = \"\";\n try {\n raw = await resp.text();\n body = raw ? JSON.parse(raw) : undefined;\n } catch {\n // Non-JSON body — keep `raw` for the fallback path.\n }\n if (body && typeof body === \"object\") {\n const b = body as { message?: unknown; code?: unknown; hint?: unknown };\n return new OxyApiError({\n status: resp.status,\n message: typeof b.message === \"string\" ? b.message : `HTTP ${resp.status}`,\n code: typeof b.code === \"string\" ? b.code : null,\n hint: typeof b.hint === \"string\" ? b.hint : null\n });\n }\n const snippet = raw.length > 240 ? `${raw.slice(0, 237)}…` : raw;\n return new OxyApiError({\n status: resp.status,\n message: snippet || `HTTP ${resp.status}`\n });\n}\n\n// ── Hooks ───────────────────────────────────────────────────────────────────\n\n/**\n * Read the resolved manifest from context. Throws if called outside\n * `<OxyAppProvider>` — that's a programmer error worth surfacing\n * loudly, not silently swallowing.\n */\nexport function useResolvedManifest(): ResolvedCustomerAppManifest {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useResolvedManifest must be called inside <OxyAppProvider>\");\n }\n if (ctx.status !== \"ready\" || !ctx.resolved) {\n throw new Error(\n \"useResolvedManifest called before manifest finished loading. \" +\n \"Use the provider's `fallback` prop to render while loading.\"\n );\n }\n return ctx.resolved;\n}\n\n/**\n * Low-level hook that returns the raw context value (including the\n * fetcher). Prefer `useResolvedManifest` for manifest access; use\n * this only when you need the fetcher or projectId without requiring\n * the manifest to be ready (e.g. inside `useQuery`).\n */\nfunction useOxyApp(): { projectId: string | undefined; fetcher: AppFetcher } {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useOxyApp must be called inside <OxyAppProvider>\");\n }\n return {\n projectId: ctx.resolved?.projectId,\n fetcher: ctx.fetcher\n };\n}\n\n// ── useQuery ────────────────────────────────────────────────────────────────\n\nexport interface UseQueryInput {\n sql: string;\n database?: string;\n}\n\nexport interface UseQueryOpts {\n params?: Record<string, string | number | boolean | null | undefined>;\n /** Set false to skip the request (e.g., waiting on user input). */\n enabled?: boolean;\n}\n\nexport interface UseQueryResult<Row = Record<string, unknown>> {\n rows: Row[];\n columns: string[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/**\n * Execute an ad-hoc SQL query against the project linked to this\n * customer app. The query is specified inline by the caller; no\n * manifest declaration is involved.\n *\n * Re-runs whenever `input` or enabled `params` change. Use the\n * `enabled` option to defer the first fetch until required data is\n * available (e.g. a user-supplied filter value).\n */\nexport function useQuery<Row = Record<string, unknown>>(\n input: UseQueryInput,\n opts: UseQueryOpts = {}\n): UseQueryResult<Row> {\n const { projectId, fetcher } = useOxyApp();\n const enabled = opts.enabled !== false;\n // Serialise opts.params so `useMemo` fires only when the values\n // change, not on every render when callers pass a new object literal.\n const paramsKey = JSON.stringify(opts.params);\n // biome-ignore lint/correctness/useExhaustiveDependencies: paramsKey replaces opts.params as the dep\n const sqlWithParams = React.useMemo(\n () => interpolateSqlParams(input.sql, opts.params ?? {}),\n [input.sql, paramsKey]\n );\n\n const [state, setState] = React.useState<{\n rows: Row[];\n columns: string[];\n loading: boolean;\n error: Error | null;\n }>({\n rows: [],\n columns: [],\n loading: enabled && !!projectId,\n error: null\n });\n const [nonce, setNonce] = React.useState(0);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: nonce forces refetch\n React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n const ctrl = new AbortController();\n let cancelled = false;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n const body = JSON.stringify({\n sql: sqlWithParams,\n ...(input.database ? { database: input.database } : {})\n });\n\n fetcher(`/api/projects/${projectId}/query`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n })\n .then(async (resp) => {\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n return resp.json() as Promise<{ columns: string[]; rows: unknown[][] }>;\n })\n .then(({ columns, rows }) => {\n if (cancelled) return;\n const objects = rows.map(\n (r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row\n );\n setState({ rows: objects, columns, loading: false, error: null });\n })\n .catch((err) => {\n if (cancelled) return;\n // AbortError is not a real failure — the cleanup cancelled the request.\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setState((s) => ({\n ...s,\n loading: false,\n error: err instanceof Error ? err : new Error(String(err))\n }));\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n }, [enabled, projectId, sqlWithParams, input.database, nonce, fetcher]);\n\n return {\n rows: state.rows,\n columns: state.columns,\n loading: state.loading,\n error: state.error,\n refetch: () => setNonce((n) => n + 1)\n };\n}\n\n// ── useFunction ─────────────────────────────────────────────────────────────\n//\n// Invoke a server-side Oxy Function shipped in the bundle's `functions/`\n// dir and declared in `oxy-app.json`. Unlike `useQuery` (which fires on\n// mount), a function is invoked imperatively via `invoke(body?)` —\n// functions do side-effectful work (ETL, writes, external calls), so the\n// caller decides when to run them (a button click, a form submit).\n//\n// The request lands on `POST <base>/customer-apps/<org>/<slug>/fn/<name>`\n// with the session cookie attached (same same-origin auth as useQuery),\n// runs in an isolated runtime against a data-plane-native `ctx`, and\n// returns whatever JSON the function's `Response` carried.\n\n// `readFunctionSseStream` lives in ./function-sse (React-free, unit-tested).\n\nexport interface UseFunctionResult<Data = unknown> {\n /**\n * Invoke the function with an optional JSON body. Resolves to the parsed\n * result. Pass `{ idempotencyKey }` to make a side-effectful invocation\n * exactly-once: a retry with the same key replays the stored result instead\n * of re-executing. Send a fresh key per logical action (e.g. a UUID per\n * journal entry).\n */\n invoke: (body?: unknown, opts?: { idempotencyKey?: string }) => Promise<Data>;\n /** Last successful result, or null before the first invoke. */\n data: Data | null;\n /** True while an invocation is in flight. */\n isLoading: boolean;\n /** Last invocation error, or null. On error this carries `.logs` too. */\n error: Error | null;\n /**\n * `console.*` / `ctx.log` output from the last invoke (success or error), so\n * a developer can see what the function printed without opening the oxy\n * server logs. Empty for a cache hit or idempotent replay — no run happened,\n * so there is nothing to log.\n */\n logs: FunctionLog[];\n}\n\n/**\n * Imperative hook for invoking an Oxy Function by name.\n *\n * ```tsx\n * const refresh = useFunction(\"refresh-sales\");\n * <button disabled={refresh.isLoading} onClick={() => refresh.invoke({ full: true })}>\n * Refresh\n * </button>\n * ```\n */\nexport function useFunction<Data = unknown>(name: string): UseFunctionResult<Data> {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useFunction must be called inside <OxyAppProvider>\");\n }\n const fetcher = ctx.fetcher;\n const resolved = ctx.resolved;\n\n const [state, setState] = React.useState<{\n data: Data | null;\n isLoading: boolean;\n error: Error | null;\n logs: FunctionLog[];\n }>({ data: null, isLoading: false, error: null, logs: [] });\n\n const invoke = React.useCallback(\n async (body?: unknown, opts?: { idempotencyKey?: string }): Promise<Data> => {\n if (!resolved) {\n throw new Error(\n \"useFunction.invoke called before the manifest finished loading. \" +\n \"Render behind the provider's `fallback` until ready.\"\n );\n }\n const { orgSlug, appSlug, apiBaseUrl } = resolved;\n const base = apiBaseUrl || \"\";\n const url = `${base}/customer-apps/${encodeURIComponent(orgSlug)}/${encodeURIComponent(\n appSlug\n )}/fn/${encodeURIComponent(name)}`;\n setState((s) => ({ ...s, isLoading: true, error: null }));\n try {\n // In-flight dedup: concurrent identical invokes (a double-click, or two\n // components) share ONE request — never a memoized result, since a\n // function may be side-effectful.\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n accept: \"text/event-stream\"\n };\n if (opts?.idempotencyKey) headers[\"idempotency-key\"] = opts.idempotencyKey;\n const result = await sharedFunctionInvoke<FunctionResult<Data>>(\n functionInvokeKey(name, body),\n async () => {\n const resp = await fetcher(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body ?? {})\n });\n if (!resp.ok && resp.status !== 200) {\n throw await apiErrorFromResponse(resp);\n }\n return readFunctionSseStream<Data>(resp);\n }\n );\n setState({ data: result.value, isLoading: false, error: null, logs: result.logs });\n return result.value;\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n // A function throw carries the logs it printed before failing — surface\n // them so the developer sees context without opening the oxy logs.\n const logs = (e as FunctionError).logs ?? [];\n setState((s) => ({ ...s, isLoading: false, error: e, logs }));\n throw e;\n }\n },\n [resolved, fetcher, name]\n );\n\n return {\n invoke,\n data: state.data,\n isLoading: state.isLoading,\n error: state.error,\n logs: state.logs\n };\n}\n\n// ── useSemanticQuery ────────────────────────────────────────────────────────\n//\n// Bundles reference the project's semantic layer by topic + dimensions\n// + measures + filters. The server compiles to dialect-specific SQL via\n// airlayer and executes through the same connector path as useQuery —\n// when the data team refactors the SQL behind a measure, the bundle\n// picks up the change without an edit.\n\n/** Scalar filter operators (compared against a single value). */\nexport type SemanticScalarOp = \"eq\" | \"neq\" | \"lt\" | \"lte\" | \"gt\" | \"gte\";\n\n/** Array filter operators (compared against a list). */\nexport type SemanticArrayOp = \"in\" | \"not_in\";\n\n/** Date-range filter operators. `from` / `to` accept ISO date strings. */\nexport type SemanticDateRangeOp = \"in_date_range\" | \"not_in_date_range\";\n\n/**\n * One filter clause. The `field` references a dimension name within\n * the topic; the `op` discriminator picks which other fields are\n * meaningful. Wire shape matches `agentic_semantic::SemanticFilter`\n * verbatim — the bundle's request body is forwarded to airlayer's\n * compiler with no translation.\n */\nexport type SemanticFilter =\n | { field: string; op: SemanticScalarOp; value: string | number | boolean | null }\n | { field: string; op: SemanticArrayOp; values: Array<string | number | boolean | null> }\n | { field: string; op: SemanticDateRangeOp; from: string; to: string };\n\n/** Time dimensions with optional granularity (e.g. \"day\", \"month\"). */\nexport interface SemanticTimeDimension {\n dimension: string;\n granularity?: \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n}\n\nexport interface UseSemanticQueryInput {\n topic: string;\n dimensions?: string[];\n measures?: string[];\n time_dimensions?: SemanticTimeDimension[];\n filters?: SemanticFilter[];\n limit?: number;\n}\n\nexport interface UseSemanticQueryOpts {\n /** Set false to skip the request (e.g., waiting on user input). */\n enabled?: boolean;\n /**\n * When true, the response includes the compiled SQL string at\n * `sql`. Off by default — production callers shouldn't bake the\n * warehouse SQL into their UI. Bundle authors flip this on while\n * debugging.\n */\n debug?: boolean;\n}\n\nexport interface UseSemanticQueryResult<Row = Record<string, unknown>> {\n rows: Row[];\n columns: string[];\n /** True when the result was capped at the server's row limit. */\n truncated: boolean;\n /** Compiled SQL — populated only when `opts.debug` is true. */\n sql: string | null;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/**\n * Run a semantic-layer query against the project's `.view.yml` /\n * `.topic.yml` definitions. The server compiles to SQL and executes\n * through the same connector path as `useQuery`, so result shape\n * matches.\n *\n * Re-runs whenever the input shape changes (deep-compared via JSON).\n * Use `opts.enabled = false` to defer the first fetch until required\n * inputs (e.g. a user-picked filter value) are available.\n */\nexport function useSemanticQuery<Row = Record<string, unknown>>(\n input: UseSemanticQueryInput,\n opts: UseSemanticQueryOpts = {}\n): UseSemanticQueryResult<Row> {\n const { projectId, fetcher } = useOxyApp();\n const enabled = opts.enabled !== false;\n const debug = opts.debug === true;\n\n // Stable key for the effect dep — re-running on every render when\n // callers pass a new object literal would mean re-fetching on every\n // parent render.\n const inputKey = React.useMemo(() => JSON.stringify(input), [input]);\n\n const [state, setState] = React.useState<{\n rows: Row[];\n columns: string[];\n truncated: boolean;\n sql: string | null;\n loading: boolean;\n error: Error | null;\n }>({\n rows: [],\n columns: [],\n truncated: false,\n sql: null,\n loading: enabled && !!projectId,\n error: null\n });\n const [nonce, setNonce] = React.useState(0);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: inputKey serializes input; nonce forces refetch\n React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n const ctrl = new AbortController();\n let cancelled = false;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n // `v: 1` is the customer-apps-platform body versioning convention\n // (see customer_apps_gates.rs::parse_versioned_body). Pinning it\n // here means a future v2 server can reject this stale client\n // cleanly instead of silently misinterpreting it.\n const body = JSON.stringify({\n v: 1,\n topic: input.topic,\n dimensions: input.dimensions ?? [],\n measures: input.measures ?? [],\n time_dimensions: input.time_dimensions ?? [],\n filters: input.filters ?? [],\n ...(input.limit != null ? { limit: input.limit } : {})\n });\n\n const url = `/api/projects/${projectId}/semantic-query${debug ? \"?debug=1\" : \"\"}`;\n fetcher(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n })\n .then(async (resp) => {\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n return resp.json() as Promise<{\n columns: string[];\n rows: unknown[][];\n truncated: boolean;\n sql?: string;\n }>;\n })\n .then(({ columns, rows, truncated, sql }) => {\n if (cancelled) return;\n const objects = rows.map(\n (r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row\n );\n setState({\n rows: objects,\n columns,\n truncated,\n sql: sql ?? null,\n loading: false,\n error: null\n });\n })\n .catch((err) => {\n if (cancelled) return;\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setState((s) => ({\n ...s,\n loading: false,\n error: err instanceof Error ? err : new Error(String(err))\n }));\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n }, [enabled, projectId, inputKey, debug, nonce, fetcher]);\n\n return {\n rows: state.rows,\n columns: state.columns,\n truncated: state.truncated,\n sql: state.sql,\n loading: state.loading,\n error: state.error,\n refetch: () => setNonce((n) => n + 1)\n };\n}\n\n// ── useProcedureRun ─────────────────────────────────────────────────────────\n//\n// Trigger a long-running procedure (`.procedure.yml`) from the\n// bundle. Returns state + progress + structured outputs. Use for\n// \"Generate report\" / \"Recompute\" buttons.\n\nexport type ProcedureRunState = \"idle\" | \"running\" | \"done\" | \"failed\";\n\nexport interface UseProcedureRunInput {\n procedureId: string;\n}\n\nexport interface UseProcedureRunOpts {\n /** Polling cadence in ms while running. Default: 2000 (procedures\n * are typically minutes-long; tighter cadence wastes resources). */\n pollIntervalMs?: number;\n pollIntervalBackoffMs?: number;\n /** Max client-side wait in ms. Default: 1 hour. */\n maxWaitMs?: number;\n}\n\nexport interface ProcedureProgress {\n step: string;\n percent: number;\n}\n\nexport interface ProcedureResult {\n summary: string;\n outputs: Record<string, unknown>;\n}\n\nexport interface UseProcedureRunResult {\n state: ProcedureRunState;\n run: (params?: Record<string, unknown>) => void;\n /** Cancel the in-flight run. Idempotent. */\n cancel: () => void;\n progress: ProcedureProgress | null;\n result: ProcedureResult | null;\n error: Error | null;\n}\n\nconst PROCEDURE_POLL_MS = 2000;\nconst PROCEDURE_BACKOFF_MS = 5000;\nconst PROCEDURE_MAX_WAIT_MS = 60 * 60 * 1000;\n\n/**\n * @beta Long-running procedure runner. The wire shape works end-to-end\n * (start → poll → cancel; runs survive server restarts via the\n * `customer_app_procedure_runs` table) but a few rough edges remain\n * before this is GA-ready:\n *\n * - Hint surfaces for `procedure_not_found` are correct but the\n * procedure-discovery rules (which directories the server scans,\n * case-sensitivity, branch awareness) aren't documented yet.\n * - Cancellation across multi-instance deployments leans on a\n * periodic sweep — fine for now, but expect occasional latency\n * between `cancel()` and the run actually stopping.\n * - Progress reporting requires the procedure to emit named\n * steps; bundles get `progress: null` until that lands.\n *\n * The API surface is stable; expect breaking changes only if the\n * server-side `customer_app_procedure_runs` schema changes.\n */\nexport function useProcedureRun(\n input: UseProcedureRunInput,\n opts: UseProcedureRunOpts = {}\n): UseProcedureRunResult {\n warnBetaOnce(\"useProcedureRun\");\n const { projectId, fetcher } = useOxyApp();\n const pollMs = opts.pollIntervalMs ?? PROCEDURE_POLL_MS;\n const backoffMs = opts.pollIntervalBackoffMs ?? PROCEDURE_BACKOFF_MS;\n const maxWaitMs = opts.maxWaitMs ?? PROCEDURE_MAX_WAIT_MS;\n\n const [state, setState] = React.useState<{\n state: ProcedureRunState;\n progress: ProcedureProgress | null;\n result: ProcedureResult | null;\n error: Error | null;\n }>({\n state: \"idle\",\n progress: null,\n result: null,\n error: null\n });\n const inflight = React.useRef<{\n abort?: AbortController;\n runId?: string;\n }>({});\n\n // cancel is idempotent and a no-op once the run has reached a\n // terminal state. inflight.current.runId is cleared in the same\n // setState calls that flip state out of \"running\"; that's the\n // single source of truth so a slow click after `done` can't\n // overwrite the result with a phantom \"failed\".\n const cancel = React.useCallback(() => {\n const runId = inflight.current.runId;\n if (!projectId || !runId) return;\n inflight.current.abort?.abort();\n inflight.current.runId = undefined;\n void fetcher(`/api/projects/${projectId}/procedures/runs/${encodeURIComponent(runId)}/cancel`, {\n method: \"POST\"\n }).catch(() => {});\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(\"procedure cancelled by user\")\n });\n }, [projectId, fetcher]);\n\n const run = React.useCallback(\n (params?: Record<string, unknown>) => {\n if (!projectId) {\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"project not configured\")\n }));\n return;\n }\n inflight.current.abort?.abort();\n const ctrl = new AbortController();\n inflight.current = { abort: ctrl };\n setState({ state: \"running\", progress: null, result: null, error: null });\n\n void (async () => {\n try {\n const body = JSON.stringify({\n v: 1,\n ...(params ? { params } : {})\n });\n const startResp = await fetcher(\n `/api/projects/${projectId}/procedures/${encodeURIComponent(input.procedureId)}/runs`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n }\n );\n if (!startResp.ok) {\n throw await apiErrorFromResponse(startResp);\n }\n const { run_id } = (await startResp.json()) as { run_id: string };\n inflight.current.runId = run_id;\n\n const startedAt = Date.now();\n let pollCount = 0;\n // biome-ignore lint/correctness/noConstantCondition: terminated by return/throw\n while (true) {\n if (ctrl.signal.aborted) return;\n if (Date.now() - startedAt > maxWaitMs) {\n throw new Error(\"procedure run timed out client-side\");\n }\n const interval = pollCount < 6 ? pollMs : backoffMs;\n await sleep(interval, ctrl.signal);\n if (ctrl.signal.aborted) return;\n pollCount += 1;\n\n const pollResp = await fetcher(\n `/api/projects/${projectId}/procedures/runs/${encodeURIComponent(run_id)}`,\n { method: \"GET\", signal: ctrl.signal }\n );\n if (!pollResp.ok) {\n throw await apiErrorFromResponse(pollResp);\n }\n const poll = (await pollResp.json()) as\n | { status: \"running\"; progress?: ProcedureProgress }\n | { status: \"done\"; result: ProcedureResult }\n | { status: \"cancelled\" }\n | {\n status: \"failed\";\n error: { message: string; code?: string };\n };\n if (poll.status === \"running\") {\n if (poll.progress) {\n setState((s) => ({ ...s, progress: poll.progress ?? null }));\n }\n continue;\n }\n if (poll.status === \"done\") {\n inflight.current.runId = undefined;\n setState({\n state: \"done\",\n progress: null,\n result: poll.result,\n error: null\n });\n return;\n }\n if (poll.status === \"cancelled\") {\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(\"procedure cancelled\")\n });\n return;\n }\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(poll.error.message)\n });\n return;\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: e instanceof Error ? e : new Error(String(e))\n });\n }\n })();\n },\n [projectId, fetcher, input.procedureId, pollMs, backoffMs, maxWaitMs]\n );\n\n React.useEffect(() => {\n return () => {\n inflight.current.abort?.abort();\n };\n }, []);\n\n return {\n state: state.state,\n run,\n cancel,\n progress: state.progress,\n result: state.result,\n error: state.error\n };\n}\n\n// ── useAgentRun (SSE streaming) ─────────────────────────────────────────────\n//\n// Real-time chat surface. Agentic pipeline emits events as they\n// happen — token-by-token answer text, mid-run SQL artifacts, ask-\n// user clarifications. Bundles use this for any chat / Q&A UI; the\n// drop-in `<OxyChat>` and `<OxyAnswer>` components wrap it for the\n// common cases.\n\nexport type AgentRunState = \"idle\" | \"running\" | \"needs_clarification\" | \"done\" | \"failed\";\n\nexport interface AgentRunEvent {\n type: string;\n data: unknown;\n}\n\n/** SQL produced and (optionally) executed by the agent. Extracted\n * from `query_generated` / `query_executed` / `verified_sql` /\n * `semantic_query` / `omni_query` SSE events so callers don't have\n * to scan the raw event stream themselves. */\nexport interface AgentSqlArtifact {\n type: \"sql\";\n /** Stable id derived from the SSE event id so React keys stay\n * stable across re-renders / reconnects. */\n id: string;\n /** Originating UI event type — preserves the verified/semantic/etc.\n * flavor in case the renderer wants a badge. */\n source: string;\n sql: string;\n /** Present when the SQL was executed and rows came back. */\n results?: {\n columns: string[];\n rows: unknown[][];\n rowCount: number;\n };\n /** Present when execution failed — surface it so the bundle UI can\n * show the failure inline next to the SQL instead of swallowing\n * it inside the agent's final answer. */\n error?: string;\n}\n\nexport type AgentArtifact = AgentSqlArtifact;\n\nexport interface UseAgentRunInput {\n agentId: string;\n}\n\nexport interface UseAgentRunResult {\n state: AgentRunState;\n /** Submit a question and open the SSE stream. */\n ask: (question: string, opts?: { threadId?: string }) => void;\n /** Cancel the in-flight stream + the server-side run. Idempotent. */\n cancel: () => void;\n /** Accumulated raw events for advanced consumers. */\n events: AgentRunEvent[];\n /** SQL artifacts extracted from the event stream — convenience\n * view over `events` so renderers don't have to know which event\n * types carry SQL. */\n artifacts: AgentArtifact[];\n /** Final answer once a `done` event arrives. Markdown. */\n answer: string | null;\n /** Clarification text once a suspension event arrives. */\n clarification: string | null;\n /** Thread id used by the active run (stable across follow-ups). */\n threadId: string | null;\n /**\n * @beta Relative path to the full thread view in oxy (e.g.\n * `/threads/<id>` for local mode, or\n * `/<org_slug>/workspaces/<ws_id>/threads/<id>` in cloud). Set\n * once the run starts so a bundle can render a \"Continue in Oxy\"\n * link without constructing the URL itself.\n *\n * Caveats while in beta:\n * - The bundle's origin and the oxy app shell's origin can\n * differ in cloud deployments. If they do, this relative URL\n * resolves against the bundle's origin and 404s. A future\n * release will expose the oxy app origin via the manifest;\n * for now, prefix at the call site if you know your\n * deployment topology, or hide the link entirely.\n * - The thread row may not be queryable until the run produces\n * its first event — clicking the link immediately after\n * `ask()` can land on a \"thread not found\" page.\n */\n threadUrl: string | null;\n error: Error | null;\n}\n\nexport function useAgentRun(input: UseAgentRunInput): UseAgentRunResult {\n const { projectId, fetcher } = useOxyApp();\n const [state, setState] = React.useState<{\n state: AgentRunState;\n events: AgentRunEvent[];\n artifacts: AgentArtifact[];\n answer: string | null;\n clarification: string | null;\n threadId: string | null;\n threadUrl: string | null;\n error: Error | null;\n }>({\n state: \"idle\",\n events: [],\n artifacts: [],\n answer: null,\n clarification: null,\n threadId: null,\n threadUrl: null,\n error: null\n });\n // Track the latest abort controller + run_id so cancel() can fire\n // the right server endpoint AND tear down the in-flight stream.\n // `terminated` is a local-only flag the consume loop reads to\n // decide whether to reconnect after a clean stream close. We\n // can't read React state directly (closure capture is stale) and\n // the prior stateRef approach was racy because setState is async\n // — by the time we read the ref it might not have flushed yet.\n // The flag is set synchronously inside the onEvent handler right\n // before setState fires, so the consume loop sees it on the next\n // iteration.\n const inflight = React.useRef<{\n abort?: AbortController;\n runId?: string;\n }>({});\n\n // cancel is idempotent + no-op once the run is terminal. We\n // clear inflight.current.runId in the same setState calls that\n // flip state out of \"running\"; a late click then can't overwrite\n // a done answer with a phantom \"failed\".\n const cancel = React.useCallback(() => {\n const runId = inflight.current.runId;\n if (!projectId || !runId) return;\n inflight.current.abort?.abort();\n inflight.current.runId = undefined;\n void fetcher(`/api/projects/${projectId}/agents/asks/${encodeURIComponent(runId)}/cancel`, {\n method: \"POST\"\n }).catch(() => {});\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"agent run cancelled by user\")\n }));\n }, [projectId, fetcher]);\n\n const ask = React.useCallback(\n (question: string, opts: { threadId?: string } = {}) => {\n if (!projectId) {\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"project not configured\")\n }));\n return;\n }\n inflight.current.abort?.abort();\n const ctrl = new AbortController();\n inflight.current = { abort: ctrl };\n setState({\n state: \"running\",\n events: [],\n artifacts: [],\n answer: null,\n clarification: null,\n threadId: opts.threadId ?? null,\n threadUrl: opts.threadId ? `/threads/${opts.threadId}` : null,\n error: null\n });\n\n void (async () => {\n try {\n // 1. POST to start the run.\n const body = JSON.stringify({\n v: 1,\n question,\n ...(opts.threadId ? { thread_id: opts.threadId } : {})\n });\n const startResp = await fetcher(\n `/api/projects/${projectId}/agents/${encodeURIComponent(input.agentId)}/asks`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n }\n );\n if (!startResp.ok) {\n throw await apiErrorFromResponse(startResp);\n }\n const { run_id, thread_id, thread_url } = (await startResp.json()) as {\n run_id: string;\n thread_id: string;\n thread_url?: string;\n };\n inflight.current.runId = run_id;\n setState((s) => ({\n ...s,\n threadId: thread_id,\n threadUrl: thread_url ?? `/threads/${thread_id}`\n }));\n\n // 2. Open the SSE stream via fetch (not EventSource) so we\n // can pass `Last-Event-ID` on reconnect. EventSource has\n // no API to set that header — the browser exposes the\n // server-set id internally but doesn't surface it for\n // custom auth flows or `withCredentials` + custom\n // headers together. Hand-rolled SSE + ReadableStream\n // gives us both.\n //\n // Reconnect strategy: on disconnect (network error,\n // server close before terminal event), wait 1s and\n // re-open with the last-seen sequence id. Max 5\n // attempts so a permanently-broken stream eventually\n // surfaces as `failed`. The `terminated` flag is set\n // synchronously when a terminal event arrives — once\n // set, the loop exits on the next iteration without\n // reconnecting.\n let lastEventId = \"\";\n let attempts = 0;\n let terminated = false;\n // biome-ignore lint/correctness/noConstantCondition: terminated by return/break\n while (true) {\n if (ctrl.signal.aborted) return;\n if (terminated) return;\n attempts += 1;\n\n try {\n await consumeSseStream({\n url: `/api/projects/${projectId}/agents/runs/${encodeURIComponent(run_id)}/events`,\n fetcher,\n signal: ctrl.signal,\n lastEventId,\n onEvent: (ev) => {\n // Track last event id for reconnect resumption.\n if (ev.id) lastEventId = ev.id;\n const data = parseSseData(ev.data);\n const eventType = ev.event || \"message\";\n const artifact = extractSqlArtifact(eventType, ev.id, data);\n\n // `text_delta` carries the streaming answer token-by-\n // token (CoreEvent::LlmToken → UiBlock::TextDelta).\n // The terminal `done` event has an empty payload — it\n // signals the run is over but doesn't restate the\n // answer text. So we accumulate tokens here.\n const token =\n eventType === \"text_delta\" &&\n typeof data === \"object\" &&\n data !== null &&\n \"token\" in data\n ? String((data as { token: unknown }).token)\n : null;\n\n setState((s) => ({\n ...s,\n events: [...s.events, { type: eventType, data }],\n artifacts: artifact ? [...s.artifacts, artifact] : s.artifacts,\n answer: token !== null ? (s.answer ?? \"\") + token : s.answer\n }));\n\n if (ev.event === \"done\") {\n terminated = true;\n inflight.current.runId = undefined;\n setState((s) => ({ ...s, state: \"done\" }));\n } else if (\n ev.event === \"failed\" ||\n ev.event === \"error\" ||\n ev.event === \"cancelled\"\n ) {\n // Mirrors `is_terminal_event` in\n // crates/app/.../agent_run_stream.rs — keep in sync.\n terminated = true;\n inflight.current.runId = undefined;\n const message =\n typeof data === \"object\" && data !== null && \"message\" in data\n ? String((data as { message: unknown }).message)\n : `agent run ${ev.event}`;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(message)\n }));\n } else if (ev.event === \"ask_user\") {\n // Not terminal in the cancel-sense — the user\n // can resume by calling ask() again with the\n // same threadId. Keep runId set so cancel()\n // still works if the user prefers to abort.\n terminated = true;\n const clarification =\n typeof data === \"object\" && data !== null && \"question\" in data\n ? String((data as { question: unknown }).question)\n : \"Agent needs clarification.\";\n setState((s) => ({\n ...s,\n state: \"needs_clarification\",\n clarification\n }));\n }\n }\n });\n // Stream closed cleanly. `terminated` was flipped by\n // the event handler iff a terminal event arrived —\n // check it at loop head; otherwise fall through to\n // reconnect.\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n // Network / parse error. Reconnect up to 5x with\n // 1s sleep before giving up.\n if (attempts >= 5) {\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: err instanceof Error ? err : new Error(String(err))\n }));\n return;\n }\n }\n if (terminated) return;\n await sleep(1000, ctrl.signal);\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: e instanceof Error ? e : new Error(String(e))\n }));\n }\n })();\n },\n [projectId, fetcher, input.agentId]\n );\n\n React.useEffect(() => {\n return () => {\n inflight.current.abort?.abort();\n };\n }, []);\n\n return {\n state: state.state,\n ask,\n cancel,\n events: state.events,\n artifacts: state.artifacts,\n answer: state.answer,\n clarification: state.clarification,\n threadId: state.threadId,\n threadUrl: state.threadUrl,\n error: state.error\n };\n}\n\n/** Parsed JSON payload of an SSE `data:` line, falling back to raw\n * string on parse failure. */\nfunction parseSseData(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** UI event types in the analytics taxonomy that carry SQL the bundle\n * may want to render alongside the answer. Each carries the same\n * shape (`query` / `columns` / `rows` / `success`) so we can parse\n * uniformly. New types added upstream don't surface as artifacts\n * until added here — that's intentional, the renderer needs to\n * know how to display each. */\nconst SQL_EVENT_TYPES = new Set([\n \"query_executed\",\n \"query_generated\",\n \"verified_sql\",\n \"semantic_query\",\n \"omni_query\"\n]);\n\n/** Extract a SQL artifact from a single SSE event when the type +\n * payload shape match. Returns `null` for events that aren't SQL\n * carriers or whose payload doesn't include the expected fields. */\nfunction extractSqlArtifact(\n eventType: string,\n eventId: string,\n data: unknown\n): AgentSqlArtifact | null {\n if (!SQL_EVENT_TYPES.has(eventType)) return null;\n if (typeof data !== \"object\" || data === null) return null;\n const obj = data as Record<string, unknown>;\n const sql =\n typeof obj.query === \"string\" ? obj.query : typeof obj.sql === \"string\" ? obj.sql : null;\n if (!sql) return null;\n\n const columns = Array.isArray(obj.columns) ? (obj.columns as unknown[]).map(String) : undefined;\n const rows = Array.isArray(obj.rows) ? (obj.rows as unknown[][]) : undefined;\n const rowCount =\n typeof obj.row_count === \"number\"\n ? obj.row_count\n : typeof obj.rowCount === \"number\"\n ? obj.rowCount\n : rows?.length;\n\n const artifact: AgentSqlArtifact = {\n type: \"sql\",\n id: eventId || `${eventType}-${sql.slice(0, 32)}`,\n source: eventType,\n sql\n };\n if (columns && rows) {\n artifact.results = { columns, rows, rowCount: rowCount ?? rows.length };\n }\n const errMsg =\n typeof obj.error === \"string\"\n ? obj.error\n : obj.success === false && typeof obj.message === \"string\"\n ? (obj.message as string)\n : undefined;\n if (errMsg) artifact.error = errMsg;\n return artifact;\n}\n\n/** One delivered SSE event. Matches what the spec calls a \"message\"\n * block — id + event-type + accumulated data. */\ninterface ParsedSseEvent {\n id: string;\n event: string;\n data: string;\n}\n\n/**\n * Hand-rolled SSE consumer using `fetch` + `ReadableStream`. We use\n * this in place of `EventSource` because:\n * 1. EventSource doesn't expose the connection's `Last-Event-ID`\n * header in a way you can control. The browser tracks it\n * internally but you can't pass a starting value, so a hook\n * that wants to resume after a tab switch / network blip has\n * no way to ask the server to replay from a known point.\n * 2. EventSource can't pass `Authorization` / other custom\n * headers — only `withCredentials` for cookies. Fine today,\n * but couples us to cookie auth forever.\n *\n * The parser handles the message-block model from the SSE spec\n * verbatim: lines split by `\\n` (or `\\r\\n` / `\\r`), event blocks\n * separated by blank lines, `id:` / `event:` / `data:` fields\n * accumulated per block. Multiple `data:` lines concatenate with\n * `\\n` (per spec) — we honor that even though the server emits\n * single-line data today.\n *\n * Throws on network error or non-2xx. Returns when the stream ends\n * normally (server closed connection cleanly).\n */\nasync function consumeSseStream(opts: {\n url: string;\n fetcher: AppFetcher;\n signal: AbortSignal;\n lastEventId: string;\n onEvent: (ev: ParsedSseEvent) => void;\n}): Promise<void> {\n const headers: Record<string, string> = {\n accept: \"text/event-stream\",\n \"cache-control\": \"no-cache\"\n };\n if (opts.lastEventId) {\n headers[\"Last-Event-ID\"] = opts.lastEventId;\n }\n const resp = await opts.fetcher(opts.url, {\n method: \"GET\",\n headers,\n signal: opts.signal\n });\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n if (!resp.body) {\n throw new Error(\"SSE response has no body\");\n }\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentId = \"\";\n let currentEvent = \"message\";\n let currentData: string[] = [];\n\n const dispatch = () => {\n if (currentData.length === 0 && currentEvent === \"message\" && !currentId) {\n return; // empty block\n }\n opts.onEvent({\n id: currentId,\n event: currentEvent,\n data: currentData.join(\"\\n\")\n });\n // `id` persists across events per spec; `event` and `data` reset.\n currentEvent = \"message\";\n currentData = [];\n };\n\n // biome-ignore lint/correctness/noConstantCondition: terminated by reader.read()\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let nlIndex: number;\n // Process complete lines. Handle \\n, \\r\\n, and bare \\r.\n // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line-buffer drain\n while ((nlIndex = buffer.search(/\\r\\n|\\r|\\n/)) !== -1) {\n // \\r at the very end of the buffer is ambiguous — it could be\n // a lone CR (Mac line ending) or the first half of a CRLF\n // whose LF hasn't arrived yet. Wait for the next read before\n // committing. Without this, a CRLF straddling a chunk boundary\n // gets parsed as two empty lines, which fires `dispatch()`\n // twice and breaks event framing for any future writer that\n // emits CRLF (Axum uses LF today, so latent — but bug-class\n // fix is cheap.)\n if (nlIndex === buffer.length - 1 && buffer[nlIndex] === \"\\r\") {\n break;\n }\n const line = buffer.slice(0, nlIndex);\n // Skip the matched line break (one or two chars).\n const sep = buffer.slice(nlIndex, nlIndex + 2);\n buffer = buffer.slice(nlIndex + (sep === \"\\r\\n\" ? 2 : 1));\n\n if (line === \"\") {\n // Empty line ⇒ end of event block.\n dispatch();\n continue;\n }\n if (line.startsWith(\":\")) {\n // Comment — keep-alive heartbeats. Skip.\n continue;\n }\n const colonAt = line.indexOf(\":\");\n const field = colonAt === -1 ? line : line.slice(0, colonAt);\n let value = colonAt === -1 ? \"\" : line.slice(colonAt + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"id\":\n currentId = value;\n break;\n case \"event\":\n currentEvent = value;\n break;\n case \"data\":\n currentData.push(value);\n break;\n // `retry:` and unknown fields ignored.\n }\n }\n }\n // Stream ended — dispatch any final buffered event without a\n // trailing blank line (server didn't write one before close).\n if (currentData.length > 0) {\n dispatch();\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(t);\n reject(new DOMException(\"aborted\", \"AbortError\"));\n },\n { once: true }\n );\n });\n}\n\n// ── useTrackEvent (custom-app usage tracking) ────────────────────────────────\n\n/**\n * Engineer-tagged usage event. Free-form `event_name` (≤ 64 chars,\n * `[a-z][a-z0-9-]*` validated server-side) + optional JSON `payload`\n * (object, ≤ 4 KiB serialized). Surfaces in the admin Activity tab\n * grouped by name, with drill-down into recent occurrences.\n *\n * The handler returned by [`useTrackEvent`] is **fire-and-forget**:\n * it enqueues the event into an in-memory batch flushed every second\n * (and on `pagehide` so a navigation away doesn't drop the tail).\n * No await semantics — call it inline from a click handler without\n * awaiting it. Server-side validation errors are logged to the\n * console; the call site doesn't need to handle them.\n *\n * Example:\n * ```tsx\n * const track = useTrackEvent();\n * <button\n * onClick={() => {\n * track(\"export-clicked\", { format: \"csv\", rowCount });\n * doExport();\n * }}\n * >Export</button>\n * ```\n *\n * Rate-limited at 60/min per (user, app) on the server. A burst that\n * trips the limit drops the excess events with a console warning;\n * within-limit events are unaffected.\n */\nexport function useTrackEvent(): (name: string, payload?: Record<string, unknown>) => void {\n const { projectId, fetcher } = useOxyApp();\n // Per-mount queue. Held in a ref so callers can fire from event\n // handlers without re-rendering. Flush schedules itself once a\n // queued event exists; the cleanup on unmount drains synchronously\n // via the `pagehide` listener.\n const queueRef = React.useRef<Array<{ event_name: string; payload: Record<string, unknown> }>>(\n []\n );\n const flushTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const flush = React.useCallback(() => {\n flushTimerRef.current = null;\n if (!projectId) return;\n const batch = queueRef.current;\n if (batch.length === 0) return;\n queueRef.current = [];\n // The server endpoint takes one event per request — keep the\n // wire format simple, fire each in parallel. With the 60/min\n // server-side rate limit + the engineer-tagged surface area\n // (one-call-per-meaningful-interaction), batch sizes are tiny.\n for (const evt of batch) {\n const url = `/api/customer-apps/${projectId}/events`;\n // Use sendBeacon when the page is unloading — keeps the request\n // alive past navigation. Otherwise normal fetch via the\n // context-provided wrapper (which includes credentials + the\n // engineer's bearer when running cross-origin in `pnpm dev`).\n //\n // Dev-mode gap: `navigator.sendBeacon` is a fixed browser API\n // that carries the user's cookies but doesn't go through the\n // OxyAppProvider `fetcher` wrapper, so the `OXY_TOKEN` bearer\n // the vite-plugin proxy adds in cross-origin `pnpm dev` is\n // missing on these requests. Result: a click that fires + the\n // tab closes immediately in local dev gets dropped at the\n // gate as 401. Same-origin prod (cookie auth) is unaffected\n // because the cookie travels with sendBeacon.\n const body = JSON.stringify(evt);\n try {\n if (\n typeof navigator !== \"undefined\" &&\n typeof navigator.sendBeacon === \"function\" &&\n document.visibilityState === \"hidden\"\n ) {\n navigator.sendBeacon(url, new Blob([body], { type: \"application/json\" }));\n } else {\n fetcher(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body\n }).catch((e: unknown) => {\n // Server-side validation errors / rate-limit hits land\n // here. Surface in console so engineers can see them\n // during dev without disrupting the app.\n // biome-ignore lint/suspicious/noConsole: tracking is dev-visible diagnostic\n console.warn(\"[oxy] useTrackEvent flush failed:\", e);\n });\n }\n } catch (e) {\n // biome-ignore lint/suspicious/noConsole: see above\n console.warn(\"[oxy] useTrackEvent enqueue failed:\", e);\n }\n }\n }, [projectId, fetcher]);\n\n // Drain on page hide / unload so a \"click then immediately navigate\n // away\" doesn't lose the click. Cleanup also clears any pending\n // 1s flush timer — without this, the timer keeps a callback on an\n // empty queue alive after unmount (harmless but untidy).\n React.useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onHide = () => flush();\n window.addEventListener(\"pagehide\", onHide);\n return () => {\n window.removeEventListener(\"pagehide\", onHide);\n flush();\n if (flushTimerRef.current !== null) {\n clearTimeout(flushTimerRef.current);\n flushTimerRef.current = null;\n }\n };\n }, [flush]);\n\n return React.useCallback(\n (name: string, payload?: Record<string, unknown>) => {\n queueRef.current.push({ event_name: name, payload: payload ?? {} });\n if (flushTimerRef.current === null) {\n flushTimerRef.current = setTimeout(flush, 1000);\n }\n },\n [flush]\n );\n}\n\n// ── <OxyAnswer> + <OxyChat> drop-in components ──────────────────────────────\n//\n// Bundles that want a chat surface without rolling their own UI use\n// `<OxyChat agentId=\"...\">`. Bundles that already have a question\n// input but want oxy to render the answer use `<OxyAnswer>` with the\n// values from `useAgentRun()`.\n//\n// Both components ship with default markdown rendering, SQL artifact\n// display, and a \"Continue in Oxy\" link — the three things every\n// bundle author needs and nobody wants to rebuild.\n//\n// Styling: inline `style={}` only. Bundles inevitably have their\n// own design system (Tailwind, Mantine, CSS-in-JS, etc.) and we\n// don't want the SDK to fight it. Caller can pass `className` to\n// override layout entirely.\n\nexport interface OxyAnswerProps {\n /** Markdown answer text from `useAgentRun().answer`. */\n answer: string | null;\n /** SQL artifacts from `useAgentRun().artifacts`. */\n artifacts?: AgentArtifact[];\n /** Lifecycle state — drives the placeholder, spinner, error UI. */\n state: AgentRunState;\n /** Clarification text when `state === \"needs_clarification\"`. */\n clarification?: string | null;\n /** Failure reason when `state === \"failed\"`. */\n error?: Error | null;\n /**\n * @beta Relative URL to the thread view in oxy — renders a\n * \"Continue in Oxy (beta)\" link when set. Pass `null` to suppress\n * the link entirely; the link is marked beta because the resolved\n * URL may not reach a live thread in every deployment topology\n * (see `UseAgentRunResult.threadUrl`).\n */\n threadUrl?: string | null;\n /** Override the link label. Default: \"Continue this thread in Oxy\". */\n threadLinkLabel?: string;\n /** Maximum number of SQL result rows to render per artifact. Older\n * rows truncated with a \"+N more\" note. Default: 10. */\n maxArtifactRows?: number;\n /** Class on the outer container — for callers using utility CSS. */\n className?: string;\n}\n\n/**\n * Renders an agent run's answer + artifacts + thread link as a\n * single block. The default styling is intentionally neutral\n * (system fonts, gray surfaces) so it blends into any bundle.\n *\n * Designed to be paired with `useAgentRun`:\n *\n * ```tsx\n * const run = useAgentRun({ agentId: \"analyst\" });\n * return (\n * <>\n * <button onClick={() => run.ask(\"how many users last week?\")}>Ask</button>\n * <OxyAnswer {...run} />\n * </>\n * );\n * ```\n */\nexport function OxyAnswer(props: OxyAnswerProps): React.JSX.Element {\n const {\n answer,\n artifacts = [],\n state,\n clarification,\n error,\n threadUrl,\n threadLinkLabel = \"Continue this thread in Oxy\",\n maxArtifactRows = 10,\n className\n } = props;\n\n const isRunning = state === \"running\";\n const isFailed = state === \"failed\";\n const needsClarification = state === \"needs_clarification\";\n\n return (\n <div className={className} style={styles.answerWrap}>\n {isRunning && answer === null ? (\n <div style={styles.statusRow}>\n <span style={styles.spinner} aria-hidden='true' />\n <span style={styles.statusText}>Thinking…</span>\n </div>\n ) : null}\n\n {artifacts.length > 0 ? (\n <div style={styles.artifactList}>\n {artifacts.map((a) => (\n <SqlArtifactBlock key={a.id} artifact={a} maxRows={maxArtifactRows} />\n ))}\n </div>\n ) : null}\n\n {answer ? (\n <div style={styles.markdown}>\n <MarkdownText text={answer} />\n </div>\n ) : null}\n\n {needsClarification && clarification ? (\n <div style={styles.clarification}>\n <strong>Agent needs clarification:</strong>\n <div style={{ marginTop: 4 }}>{clarification}</div>\n </div>\n ) : null}\n\n {isFailed && error ? <ErrorBlock error={error} /> : null}\n\n {threadUrl && (answer || artifacts.length > 0) ? (\n <div style={styles.threadLinkRow}>\n <a href={threadUrl} target='_blank' rel='noreferrer noopener' style={styles.threadLink}>\n {threadLinkLabel} →\n </a>\n <span style={styles.betaBadge} title='Thread linking is in beta — see docs'>\n beta\n </span>\n </div>\n ) : null}\n </div>\n );\n}\n\nexport interface OxyChatProps {\n /** Agent id (matches `<id>.agentic.yml` in the project). */\n agentId: string;\n /** Placeholder for the question input. */\n placeholder?: string;\n /** Button label. Default: \"Ask\". */\n submitLabel?: string;\n /** Rendered when the user hasn't asked anything yet. */\n emptyState?: React.ReactNode;\n /** Forwarded to the inner `<OxyAnswer>`. */\n maxArtifactRows?: number;\n /** Class on the outer container. */\n className?: string;\n}\n\n/**\n * Complete drop-in chat surface. One agent, one input, one answer\n * view. The chat is single-turn by default — each new question\n * cancels the previous run and clears the answer. Bundles that\n * want a multi-turn conversation history compose their own UI\n * using `useAgentRun` directly.\n *\n * Single-turn keeps the surface dead simple: bundles use this for\n * the \"ask anything about your data\" widget that sits next to\n * structured panels. Multi-turn is rare in those contexts and\n * better expressed by the bundle.\n */\nexport function OxyChat(props: OxyChatProps): React.JSX.Element {\n const {\n agentId,\n placeholder = \"Ask a question about your data…\",\n submitLabel = \"Ask\",\n emptyState,\n maxArtifactRows,\n className\n } = props;\n\n const run = useAgentRun({ agentId });\n const [question, setQuestion] = React.useState(\"\");\n\n const submit = React.useCallback(\n (e?: React.FormEvent) => {\n e?.preventDefault();\n const q = question.trim();\n if (!q || run.state === \"running\") return;\n run.ask(q);\n },\n [question, run]\n );\n\n return (\n <div className={className} style={styles.chatWrap}>\n <form onSubmit={submit} style={styles.chatForm}>\n <input\n type='text'\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder={placeholder}\n disabled={run.state === \"running\"}\n style={styles.chatInput}\n aria-label='Question'\n />\n <button\n type='submit'\n disabled={run.state === \"running\" || question.trim() === \"\"}\n style={styles.chatSubmit}\n >\n {run.state === \"running\" ? \"…\" : submitLabel}\n </button>\n {run.state === \"running\" ? (\n <button type='button' onClick={run.cancel} style={styles.chatCancel}>\n Stop\n </button>\n ) : null}\n </form>\n\n {run.state === \"idle\" ? (\n (emptyState ?? <div style={styles.emptyState}>Ask a question to get started.</div>)\n ) : (\n <OxyAnswer\n answer={run.answer}\n artifacts={run.artifacts}\n state={run.state}\n clarification={run.clarification}\n error={run.error}\n threadUrl={run.threadUrl}\n maxArtifactRows={maxArtifactRows}\n />\n )}\n </div>\n );\n}\n\nfunction SqlArtifactBlock(props: {\n artifact: AgentSqlArtifact;\n maxRows: number;\n}): React.JSX.Element {\n const { artifact, maxRows } = props;\n const [open, setOpen] = React.useState(false);\n const results = artifact.results;\n const truncated = results ? results.rows.length > maxRows : false;\n const visibleRows = results ? results.rows.slice(0, maxRows) : [];\n const sourceLabel =\n artifact.source === \"verified_sql\"\n ? \"Verified query\"\n : artifact.source === \"semantic_query\"\n ? \"Semantic query\"\n : artifact.source === \"omni_query\"\n ? \"Omni query\"\n : \"Query\";\n\n return (\n <div style={styles.artifact}>\n <button type='button' onClick={() => setOpen((o) => !o)} style={styles.artifactHeader}>\n <span style={styles.artifactBadge}>{sourceLabel}</span>\n <span style={styles.artifactSummary}>\n {results\n ? `${results.rowCount} row${results.rowCount === 1 ? \"\" : \"s\"}`\n : artifact.error\n ? \"execution failed\"\n : \"SQL only\"}\n </span>\n <span style={styles.artifactToggle}>{open ? \"Hide\" : \"Show\"}</span>\n </button>\n {open ? (\n <div>\n <pre style={styles.sqlBlock}>{artifact.sql}</pre>\n {artifact.error ? (\n <div style={styles.error}>{artifact.error}</div>\n ) : results ? (\n <div style={styles.resultsWrap}>\n <table style={styles.resultsTable}>\n <thead>\n <tr>\n {results.columns.map((c) => (\n <th key={c} style={styles.resultsTh}>\n {c}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {visibleRows.map((row, i) => (\n <tr key={i}>\n {row.map((cell, j) => (\n <td key={j} style={styles.resultsTd}>\n {formatCell(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {truncated ? (\n <div style={styles.truncatedNote}>\n +{results.rows.length - maxRows} more rows. Open the thread in Oxy to see all.\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction formatCell(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Renders a thrown Error with the server's `hint` line broken out\n * if the error is an `OxyApiError`. Falls back to `error.message`\n * for plain Errors. Whitespace-preserving so multi-line hints from\n * the server land readably.\n */\nfunction ErrorBlock(props: { error: Error }): React.JSX.Element {\n const { error } = props;\n if (error instanceof OxyApiError) {\n return (\n <div style={styles.error}>\n <div>\n <strong>Run failed:</strong> {error.message.split(\"\\n\\n\")[0]}\n </div>\n {error.hint ? (\n <div style={{ marginTop: 6, fontWeight: 400, whiteSpace: \"pre-wrap\" }}>\n <strong>Hint:</strong> {error.hint}\n </div>\n ) : null}\n </div>\n );\n }\n return (\n <div style={styles.error}>\n <strong>Run failed:</strong> {error.message}\n </div>\n );\n}\n\n// ── Minimal markdown renderer ───────────────────────────────────────────────\n//\n// Agent answers are simple markdown — paragraphs, headings (h1-h3),\n// fenced code blocks, inline code, bold, italic, links, unordered\n// lists. A 100-line renderer covers it without adding a runtime dep\n// (~30 KB) that bundles may already have in a different version.\n//\n// Out of scope intentionally: tables (use a SQL artifact), images\n// (agents don't emit them), nested lists (rare in answers), HTML\n// passthrough (no XSS surface).\n\nfunction MarkdownText(props: { text: string }): React.JSX.Element {\n const blocks = React.useMemo(() => parseMarkdown(props.text), [props.text]);\n return <>{blocks}</>;\n}\n\ntype MdBlock =\n | { kind: \"h\"; level: 1 | 2 | 3; text: string }\n | { kind: \"p\"; text: string }\n | { kind: \"code\"; lang: string; code: string }\n | { kind: \"list\"; items: string[] };\n\nfunction parseMarkdown(text: string): React.JSX.Element[] {\n const lines = text.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n const blocks: MdBlock[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n if (line === undefined) {\n i++;\n continue;\n }\n // Fenced code block\n const fence = line.match(/^```(\\w*)\\s*$/);\n if (fence) {\n const lang = fence[1] ?? \"\";\n const buf: string[] = [];\n i++;\n while (i < lines.length && !/^```\\s*$/.test(lines[i] ?? \"\")) {\n buf.push(lines[i] ?? \"\");\n i++;\n }\n i++; // skip closing fence\n blocks.push({ kind: \"code\", lang, code: buf.join(\"\\n\") });\n continue;\n }\n // Heading\n const h = line.match(/^(#{1,3})\\s+(.+)$/);\n if (h) {\n blocks.push({\n kind: \"h\",\n level: h[1]?.length as 1 | 2 | 3,\n text: h[2]!\n });\n i++;\n continue;\n }\n // List\n if (/^\\s*[-*]\\s+/.test(line)) {\n const items: string[] = [];\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").replace(/^\\s*[-*]\\s+/, \"\"));\n i++;\n }\n blocks.push({ kind: \"list\", items });\n continue;\n }\n // Blank line\n if (line.trim() === \"\") {\n i++;\n continue;\n }\n // Paragraph — collect consecutive non-empty, non-special lines.\n const buf: string[] = [line];\n i++;\n while (i < lines.length) {\n const next = lines[i] ?? \"\";\n if (\n next.trim() === \"\" ||\n /^#{1,3}\\s+/.test(next) ||\n /^```/.test(next) ||\n /^\\s*[-*]\\s+/.test(next)\n ) {\n break;\n }\n buf.push(next);\n i++;\n }\n blocks.push({ kind: \"p\", text: buf.join(\" \") });\n }\n\n return blocks.map((b, idx) => {\n switch (b.kind) {\n case \"h\": {\n const Tag = `h${b.level}` as unknown as keyof React.JSX.IntrinsicElements;\n const headingStyle = b.level === 1 ? styles.h1 : b.level === 2 ? styles.h2 : styles.h3;\n return (\n <Tag key={idx} style={headingStyle}>\n {renderInline(b.text)}\n </Tag>\n );\n }\n case \"code\":\n return (\n <pre key={idx} style={styles.codeBlock} data-lang={b.lang || undefined}>\n <code>{b.code}</code>\n </pre>\n );\n case \"list\":\n return (\n <ul key={idx} style={styles.list}>\n {b.items.map((item, i) => (\n <li key={i}>{renderInline(item)}</li>\n ))}\n </ul>\n );\n case \"p\":\n return (\n <p key={idx} style={styles.paragraph}>\n {renderInline(b.text)}\n </p>\n );\n }\n });\n}\n\n/**\n * Inline tokenizer for **bold**, *italic*, `code`, and [text](url).\n * Lazy: scan once, split into segments. The patterns are matched in\n * priority order (code first so backticks don't get eaten by bold).\n */\nfunction renderInline(text: string): React.ReactNode[] {\n const segments: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n // Patterns in priority order — code first since `**` inside backticks\n // shouldn't be parsed.\n const patterns: Array<{\n re: RegExp;\n render: (m: RegExpExecArray) => React.ReactNode;\n }> = [\n { re: /`([^`]+)`/, render: (m) => <code style={styles.inlineCode}>{m[1]}</code> },\n {\n re: /\\[([^\\]]+)\\]\\(([^)]+)\\)/,\n render: (m) => {\n // Allowlist URL schemes. Agent answers cross the LLM trust\n // boundary — a malicious prompt fragment reflected into the\n // answer could otherwise emit\n // `[click](javascript:alert(document.cookie))` and execute\n // in the bundle's origin. Accept only http(s), mailto,\n // root-relative paths, and same-page fragments; everything\n // else renders as plain text.\n if (isSafeLinkHref(m[2])) {\n return (\n <a href={m[2]} target='_blank' rel='noreferrer noopener' style={styles.link}>\n {m[1]}\n </a>\n );\n }\n return <>{m[1]}</>;\n }\n },\n { re: /\\*\\*([^*]+)\\*\\*/, render: (m) => <strong>{m[1]}</strong> },\n { re: /\\*([^*]+)\\*/, render: (m) => <em>{m[1]}</em> }\n ];\n\n while (remaining.length > 0) {\n let earliest: { idx: number; len: number; node: React.ReactNode } | null = null;\n for (const { re, render } of patterns) {\n const m = re.exec(remaining);\n if (m && (earliest === null || m.index < earliest.idx)) {\n earliest = { idx: m.index, len: m[0].length, node: render(m) };\n }\n }\n if (earliest === null) {\n segments.push(remaining);\n break;\n }\n if (earliest.idx > 0) segments.push(remaining.slice(0, earliest.idx));\n segments.push(<React.Fragment key={key++}>{earliest.node}</React.Fragment>);\n remaining = remaining.slice(earliest.idx + earliest.len);\n }\n return segments;\n}\n\n// ── Styles ──────────────────────────────────────────────────────────────────\n//\n// All inline. No CSS file, no class names, no global side effects.\n// Bundles that want custom styling pass `className` and override\n// with their own selectors, or skip the drop-in entirely and build\n// on `useAgentRun`.\n\nconst SANS =\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif';\nconst MONO = 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace';\n\nconst styles: Record<string, React.CSSProperties> = {\n answerWrap: {\n fontFamily: SANS,\n fontSize: 14,\n lineHeight: 1.5,\n color: \"#1f2937\"\n },\n statusRow: { display: \"flex\", alignItems: \"center\", gap: 8, padding: \"8px 0\" },\n spinner: {\n display: \"inline-block\",\n width: 12,\n height: 12,\n borderRadius: \"50%\",\n border: \"2px solid #d1d5db\",\n borderTopColor: \"#6b7280\",\n animation: \"oxy-spin 0.8s linear infinite\"\n },\n statusText: { color: \"#6b7280\" },\n markdown: { marginTop: 8 },\n h1: { fontSize: 20, fontWeight: 600, margin: \"16px 0 8px\" },\n h2: { fontSize: 17, fontWeight: 600, margin: \"14px 0 6px\" },\n h3: { fontSize: 15, fontWeight: 600, margin: \"12px 0 4px\" },\n paragraph: { margin: \"0 0 8px\" },\n list: { margin: \"0 0 8px\", paddingLeft: 20 },\n codeBlock: {\n fontFamily: MONO,\n fontSize: 12,\n background: \"#f3f4f6\",\n border: \"1px solid #e5e7eb\",\n borderRadius: 6,\n padding: \"8px 10px\",\n overflowX: \"auto\",\n margin: \"8px 0\"\n },\n inlineCode: {\n fontFamily: MONO,\n fontSize: \"0.92em\",\n background: \"#f3f4f6\",\n padding: \"1px 4px\",\n borderRadius: 3\n },\n link: { color: \"#2563eb\", textDecoration: \"underline\" },\n clarification: {\n marginTop: 12,\n padding: \"10px 12px\",\n background: \"#fef3c7\",\n border: \"1px solid #fcd34d\",\n borderRadius: 6,\n color: \"#78350f\"\n },\n error: {\n marginTop: 12,\n padding: \"10px 12px\",\n background: \"#fee2e2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 6,\n color: \"#991b1b\"\n },\n threadLinkRow: {\n marginTop: 12,\n textAlign: \"right\",\n display: \"flex\",\n justifyContent: \"flex-end\",\n alignItems: \"center\",\n gap: 6\n },\n threadLink: { fontSize: 12, color: \"#6b7280\", textDecoration: \"none\" },\n betaBadge: {\n fontSize: 9,\n fontWeight: 600,\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n padding: \"1px 5px\",\n borderRadius: 3,\n background: \"#fef3c7\",\n color: \"#92400e\",\n border: \"1px solid #fcd34d\"\n },\n artifactList: { display: \"flex\", flexDirection: \"column\", gap: 8, marginBottom: 8 },\n artifact: {\n border: \"1px solid #e5e7eb\",\n borderRadius: 6,\n background: \"#fafafa\",\n overflow: \"hidden\"\n },\n artifactHeader: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n width: \"100%\",\n padding: \"6px 10px\",\n background: \"transparent\",\n border: \"none\",\n borderBottom: \"1px solid transparent\",\n cursor: \"pointer\",\n fontFamily: SANS,\n fontSize: 12,\n color: \"#374151\"\n },\n artifactBadge: {\n fontWeight: 600,\n fontSize: 11,\n textTransform: \"uppercase\",\n letterSpacing: 0.4,\n color: \"#4b5563\"\n },\n artifactSummary: { color: \"#6b7280\", flex: 1 },\n artifactToggle: { color: \"#2563eb\" },\n sqlBlock: {\n fontFamily: MONO,\n fontSize: 12,\n margin: 0,\n padding: \"8px 10px\",\n background: \"#0f172a\",\n color: \"#e2e8f0\",\n overflowX: \"auto\"\n },\n resultsWrap: { padding: 8, overflowX: \"auto\" },\n resultsTable: { width: \"100%\", borderCollapse: \"collapse\", fontSize: 12 },\n resultsTh: {\n textAlign: \"left\",\n padding: \"4px 8px\",\n borderBottom: \"1px solid #e5e7eb\",\n fontWeight: 600,\n color: \"#374151\"\n },\n resultsTd: { padding: \"4px 8px\", borderBottom: \"1px solid #f3f4f6\", color: \"#1f2937\" },\n truncatedNote: { fontSize: 11, color: \"#6b7280\", padding: \"6px 8px\" },\n chatWrap: { fontFamily: SANS, fontSize: 14, color: \"#1f2937\" },\n chatForm: { display: \"flex\", gap: 8, marginBottom: 12 },\n chatInput: {\n flex: 1,\n padding: \"8px 12px\",\n border: \"1px solid #d1d5db\",\n borderRadius: 6,\n fontSize: 14,\n fontFamily: SANS\n },\n chatSubmit: {\n padding: \"8px 16px\",\n border: \"none\",\n borderRadius: 6,\n background: \"#2563eb\",\n color: \"#ffffff\",\n fontSize: 14,\n fontWeight: 500,\n cursor: \"pointer\"\n },\n chatCancel: {\n padding: \"8px 12px\",\n border: \"1px solid #d1d5db\",\n borderRadius: 6,\n background: \"#ffffff\",\n color: \"#374151\",\n fontSize: 14,\n cursor: \"pointer\"\n },\n emptyState: { padding: \"12px 0\", color: \"#9ca3af\", fontStyle: \"italic\" }\n};\n","// Metric-tree types + client. Mirrors `airlayer::engine::metric_tree*`\n// over the `/<project_id>/semantic/metric-tree*` HTTP endpoints. Serde\n// emits snake_case so these field names match the wire format verbatim.\n\nimport type { OxyConfig } from \"./config\";\n\n// ── Tree ──────────────────────────────────────────────────────────────────────\n\nexport type EdgeKind = \"component\" | \"driver\";\nexport type DriverDirection = \"positive\" | \"negative\" | \"unknown\";\nexport type DriverStrength = \"strong\" | \"moderate\" | \"weak\";\nexport type DriverConfidence = \"high\" | \"medium\" | \"low\";\nexport type DriverForm = \"linear\" | \"log-log\" | \"log-linear\" | \"linear-log\";\n\nexport interface MetricNode {\n id: string;\n view: string;\n measure: string;\n label: string;\n description?: string | null;\n measure_type: string;\n is_composite: boolean;\n expr?: string | null;\n}\n\nexport interface MetricEdge {\n from: string;\n to: string;\n kind: EdgeKind;\n /** Sign of a component edge; omitted (defaults to +1) for most edges. */\n sign?: number;\n direction: DriverDirection;\n strength: DriverStrength;\n confidence: DriverConfidence;\n coefficient?: number | null;\n form: DriverForm;\n intercept?: number | null;\n lag?: number | null;\n description?: string | null;\n refs?: string[] | null;\n}\n\nexport interface MetricTree {\n nodes: MetricNode[];\n edges: MetricEdge[];\n root?: string | null;\n}\n\n// ── Sensitivity ──────────────────────────────────────────────────────────────\n\nexport interface SensitivityDriver {\n measure: string;\n path: string[];\n edge_kind: string;\n effective_coefficient?: number | null;\n form?: DriverForm | null;\n direction: DriverDirection;\n strength: DriverStrength;\n lag?: number | null;\n description?: string | null;\n}\n\nexport interface SensitivityResult {\n target: string;\n drivers: SensitivityDriver[];\n}\n\n// ── Predict ──────────────────────────────────────────────────────────────────\n\nexport interface PredictChange {\n measure: string;\n delta: number;\n}\n\nexport interface PredictImpact {\n measure: string;\n estimated_delta: number;\n confidence: string;\n path: string[];\n form: DriverForm;\n lag?: number | null;\n}\n\nexport interface PredictResult {\n inputs: PredictChange[];\n impacts: PredictImpact[];\n}\n\n// ── Explain (RCA) ────────────────────────────────────────────────────────────\n\nexport type SplitKind =\n | { type: \"component\"; child_measure: string }\n | { type: \"dimension\"; dimension: string; value: string }\n | { type: \"uniform_degradation\"; dimension: string; num_elements: number }\n | { type: \"cross_cutting\"; dimension: string; value: string; measures: string[] };\n\nexport interface ExplainSibling {\n split: SplitKind;\n measure: string;\n delta: number;\n root_fraction: number;\n}\n\nexport interface ExplainNode {\n split: SplitKind;\n measure: string;\n filters: unknown[];\n delta: number;\n concentration: number;\n root_fraction: number;\n siblings?: ExplainSibling[];\n dimension_count?: number;\n children?: ExplainNode[];\n}\n\nexport interface DriverAttribution {\n driver_measure: string;\n driver_previous: number;\n driver_current: number;\n driver_delta: number;\n coefficient?: number;\n form: DriverForm;\n estimated_target_impact?: number;\n description?: string;\n}\n\nexport type ExplainWarning =\n | {\n type: \"simpsons_paradox\";\n dimension: string;\n aggregate_delta: number;\n segment_directions: [string, number][];\n }\n | {\n type: \"opposing_offset\";\n component_a: string;\n component_b: string;\n delta_a: number;\n delta_b: number;\n }\n | {\n type: \"non_additive_dimension_split\";\n measure: string;\n measure_type: string;\n dimension: string;\n };\n\nexport interface ExplainConfigOverride {\n deep?: boolean;\n max_depth?: number;\n coverage_threshold?: number;\n}\n\nexport interface ExplainRequest {\n target: string;\n time_dimension: string;\n current_period: [string, string];\n previous_period: [string, string];\n config?: ExplainConfigOverride;\n}\n\nexport interface ExplainResult {\n target: string;\n target_delta: number;\n target_previous: number;\n target_current: number;\n time_dimension: string;\n current_period: [string, string];\n previous_period: [string, string];\n nodes: ExplainNode[];\n coverage: number;\n driver_attribution?: DriverAttribution[];\n alternatives?: unknown[];\n warnings?: ExplainWarning[];\n}\n\n// ── Opportunity ──────────────────────────────────────────────────────────────\n\nexport interface SegmentOpportunity {\n segment: string;\n current_value: number;\n volume: number;\n benchmark: number;\n gap: number;\n /** Match-the-best upside in measure units. */\n upside: number;\n}\n\nexport interface DimensionOpportunity {\n dimension: string;\n cardinality: number;\n /** \"best_peer\" or \"p75\". */\n benchmark_basis: string;\n total_upside: number;\n segments: SegmentOpportunity[];\n other_segments_skipped: number;\n}\n\nexport interface SkippedDimension {\n dimension: string;\n reason: string;\n}\n\nexport interface OpportunityRequest {\n target: string;\n time_dimension: string;\n period: [string, string];\n}\n\nexport interface OpportunityResult {\n target: string;\n period: [string, string];\n overall_value: number;\n /** \"value_share\" (additive) or \"equal\" (ratios). */\n weight_basis: string;\n dimensions: DimensionOpportunity[];\n skipped_dimensions: SkippedDimension[];\n downstream: PredictImpact[];\n}\n\n// ── Client ───────────────────────────────────────────────────────────────────\n\n/**\n * Shape of the inner request helper exposed by `OxyClient`. The metric-tree\n * client reuses it to inherit auth headers, timeout, baseUrl, and project\n * scoping rather than reimplementing fetch end-to-end.\n */\nexport type RequestFn = <T>(endpoint: string, options?: RequestInit) => Promise<T>;\n\n/**\n * Client for the `/semantic/metric-tree*` endpoints. Surfaces the four\n * airlayer metric-tree analyses (tree introspection, sensitivity, predict,\n * explain, opportunity) over typed methods.\n *\n * Construction is internal to {@link OxyClient} — call `client.metricTree`\n * to access an instance rather than building one yourself.\n *\n * @example\n * ```typescript\n * const client = await OxyClient.create({ projectId: \"...\", apiKey: \"...\" });\n * const tree = await client.metricTree.getTree();\n * const drivers = await client.metricTree.getSensitivity(\"orders.net_revenue\");\n * ```\n */\nexport class MetricTreeClient {\n private readonly request: RequestFn;\n private readonly config: OxyConfig;\n\n constructor(config: OxyConfig, request: RequestFn) {\n this.config = config;\n this.request = request;\n }\n\n private path(suffix: string): string {\n return `/${this.config.projectId}/semantic/metric-tree${suffix}`;\n }\n\n private buildQuery(extra: Record<string, string> = {}): string {\n const params: Record<string, string> = { ...extra };\n if (this.config.branch) params.branch = this.config.branch;\n const qs = new URLSearchParams(params).toString();\n return qs ? `?${qs}` : \"\";\n }\n\n /**\n * Fetch the full metric tree, or the subtree rooted at `root`.\n *\n * @param root - Optional fully-qualified measure id to root the tree at.\n * @returns Nodes (measures) and edges (component / driver relationships).\n *\n * @example\n * ```typescript\n * const tree = await client.metricTree.getTree();\n * const subtree = await client.metricTree.getTree(\"orders.net_revenue\");\n * ```\n */\n async getTree(root?: string): Promise<MetricTree> {\n const query = this.buildQuery(root ? { root } : {});\n return this.request<MetricTree>(this.path(query));\n }\n\n /**\n * Rank the declared drivers of a measure by influence.\n *\n * @param measureId - Fully-qualified measure id (`view.measure`).\n *\n * @example\n * ```typescript\n * const sensitivity = await client.metricTree.getSensitivity(\"orders.net_revenue\");\n * for (const driver of sensitivity.drivers) {\n * console.log(driver.measure, driver.direction, driver.strength);\n * }\n * ```\n */\n async getSensitivity(measureId: string): Promise<SensitivityResult> {\n const query = this.buildQuery();\n return this.request<SensitivityResult>(\n this.path(`/${encodeURIComponent(measureId)}/sensitivity${query}`)\n );\n }\n\n /**\n * Propagate hypothetical `(measure, delta)` changes upward through the\n * tree. Returns the estimated impact on every downstream measure.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.predict([\n * { measure: \"marketing_spend.total_spend\", delta: 10000 },\n * ]);\n * ```\n */\n async predict(changes: PredictChange[]): Promise<PredictResult> {\n const query = this.buildQuery();\n return this.request<PredictResult>(this.path(`/predict${query}`), {\n method: \"POST\",\n body: JSON.stringify({ changes })\n });\n }\n\n /**\n * Period-over-period root-cause decomposition. Recursively splits the\n * target measure by components and dimensions until the move concentrates.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.explain({\n * target: \"financials.operating_profit\",\n * time_dimension: \"financials.month\",\n * current_period: [\"2025-09-01\", \"2025-09-30\"],\n * previous_period: [\"2025-08-01\", \"2025-08-31\"],\n * });\n * ```\n */\n async explain(request: ExplainRequest): Promise<ExplainResult> {\n const query = this.buildQuery();\n return this.request<ExplainResult>(this.path(`/explain${query}`), {\n method: \"POST\",\n body: JSON.stringify(request)\n });\n }\n\n /**\n * Size the upside opportunity for a measure by finding underperforming\n * segments. Skips high-cardinality dimensions and trims the long tail.\n *\n * @example\n * ```typescript\n * const result = await client.metricTree.findOpportunities({\n * target: \"orders.net_revenue\",\n * time_dimension: \"orders.order_date\",\n * period: [\"2025-09-01\", \"2025-09-30\"],\n * });\n * for (const dim of result.dimensions) {\n * console.log(dim.dimension, \"+\", dim.total_upside);\n * }\n * ```\n */\n async findOpportunities(request: OpportunityRequest): Promise<OpportunityResult> {\n const query = this.buildQuery();\n return this.request<OpportunityResult>(this.path(`/opportunity${query}`), {\n method: \"POST\",\n body: JSON.stringify(request)\n });\n }\n}\n"],"mappings":"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"}