@oxy-hq/sdk 2.12.0 → 2.13.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.
- package/dist/index.cjs +62 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +163 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +62 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{react-DcT-mUPj.cjs → react-CkAQg9wB.cjs} +65 -5
- package/dist/react-CkAQg9wB.cjs.map +1 -0
- package/dist/{react-BXGyzgz0.mjs → react-Cq3xULOr.mjs} +65 -5
- package/dist/react-Cq3xULOr.mjs.map +1 -0
- package/dist/{react-DW7Z96sD.d.mts → react-D-Sf973d.d.cts} +87 -2
- package/dist/react-D-Sf973d.d.cts.map +1 -0
- package/dist/{react-DW7Z96sD.d.cts → react-D-Sf973d.d.mts} +87 -2
- package/dist/react-D-Sf973d.d.mts.map +1 -0
- package/dist/shell.cjs +35 -1
- package/dist/shell.cjs.map +1 -1
- package/dist/shell.d.cts +25 -2
- package/dist/shell.d.cts.map +1 -1
- package/dist/shell.d.mts +25 -2
- package/dist/shell.d.mts.map +1 -1
- package/dist/shell.mjs +34 -2
- package/dist/shell.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/react-BXGyzgz0.mjs.map +0 -1
- package/dist/react-DW7Z96sD.d.cts.map +0 -1
- package/dist/react-DW7Z96sD.d.mts.map +0 -1
- package/dist/react-DcT-mUPj.cjs.map +0 -1
|
@@ -392,10 +392,25 @@ function validateFunctions(raw) {
|
|
|
392
392
|
}
|
|
393
393
|
fn.retries = retries;
|
|
394
394
|
}
|
|
395
|
+
if (value.webhook !== void 0) {
|
|
396
|
+
const w = value.webhook;
|
|
397
|
+
if (!isRecord(w)) throw new Error(`oxy-app.json: function "${fnName}" \`webhook\` must be an object`);
|
|
398
|
+
for (const key of ["secretVar", "signatureHeader"]) if (typeof w[key] !== "string" || !w[key].trim()) throw new Error(`oxy-app.json: function "${fnName}" \`webhook.${key}\` must be a non-empty string`);
|
|
399
|
+
const webhook = {
|
|
400
|
+
secretVar: w.secretVar,
|
|
401
|
+
signatureHeader: w.signatureHeader
|
|
402
|
+
};
|
|
403
|
+
if (w.encoding !== void 0) {
|
|
404
|
+
if (w.encoding !== "hex" && w.encoding !== "base64") throw new Error(`oxy-app.json: function "${fnName}" \`webhook.encoding\` must be "hex" or "base64"`);
|
|
405
|
+
webhook.encoding = w.encoding;
|
|
406
|
+
}
|
|
407
|
+
fn.webhook = webhook;
|
|
408
|
+
}
|
|
395
409
|
if (value.inputExample !== void 0) fn.inputExample = value.inputExample;
|
|
396
410
|
const hasSchedule = fn.schedule !== void 0;
|
|
397
411
|
const hasAirway = fn.airwayStep !== void 0;
|
|
398
|
-
|
|
412
|
+
const hasWebhook = fn.webhook !== void 0;
|
|
413
|
+
if (!(fn.route ?? !(hasSchedule || hasAirway || hasWebhook)) && !hasSchedule && !hasAirway && !hasWebhook) throw new Error(`oxy-app.json: function "${fnName}" must enable at least one of route/schedule/airwayStep/webhook`);
|
|
399
414
|
out[fnName] = fn;
|
|
400
415
|
}
|
|
401
416
|
return out;
|
|
@@ -649,6 +664,44 @@ async function sharedQuery(fetcher, projectId, sql, db, opts = {}) {
|
|
|
649
664
|
return p;
|
|
650
665
|
}
|
|
651
666
|
|
|
667
|
+
//#endregion
|
|
668
|
+
//#region src/custom-app/traceparent.ts
|
|
669
|
+
const HEX = "0123456789abcdef";
|
|
670
|
+
function randomHex(bytes) {
|
|
671
|
+
const buf = new Uint8Array(bytes);
|
|
672
|
+
const c = globalThis.crypto;
|
|
673
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(buf);
|
|
674
|
+
else for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);
|
|
675
|
+
let out = "";
|
|
676
|
+
for (let i = 0; i < bytes; i++) out += HEX[buf[i] >> 4] + HEX[buf[i] & 15];
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
/** Mint a fresh, sampled `traceparent`. All-zero ids are invalid per the spec;
|
|
680
|
+
* the loop guards the astronomically unlikely draw. */
|
|
681
|
+
function newTraceparent() {
|
|
682
|
+
let traceId = randomHex(16);
|
|
683
|
+
while (/^0+$/.test(traceId)) traceId = randomHex(16);
|
|
684
|
+
let spanId = randomHex(8);
|
|
685
|
+
while (/^0+$/.test(spanId)) spanId = randomHex(8);
|
|
686
|
+
return {
|
|
687
|
+
header: `00-${traceId}-${spanId}-01`,
|
|
688
|
+
traceId
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Stamp the ids of a failed invoke onto whatever was thrown, so the app (and
|
|
693
|
+
* the platform's error beacon, which reads `traceId`) can name the trace and
|
|
694
|
+
* the server-minted request id. Non-objects are returned untouched.
|
|
695
|
+
*/
|
|
696
|
+
function withInvocationIds(err, traceId, requestId) {
|
|
697
|
+
if (err && typeof err === "object") {
|
|
698
|
+
const target = err;
|
|
699
|
+
if (!target.traceId) target.traceId = traceId;
|
|
700
|
+
if (requestId && !target.requestId) target.requestId = requestId;
|
|
701
|
+
}
|
|
702
|
+
return err;
|
|
703
|
+
}
|
|
704
|
+
|
|
652
705
|
//#endregion
|
|
653
706
|
//#region src/custom-app/react.tsx
|
|
654
707
|
function defaultFetcher(input, init) {
|
|
@@ -921,9 +974,11 @@ function useFunction(name) {
|
|
|
921
974
|
error: null
|
|
922
975
|
}));
|
|
923
976
|
try {
|
|
977
|
+
const trace = newTraceparent();
|
|
924
978
|
const headers = {
|
|
925
979
|
"content-type": "application/json",
|
|
926
|
-
accept: "text/event-stream"
|
|
980
|
+
accept: "text/event-stream",
|
|
981
|
+
traceparent: trace.header
|
|
927
982
|
};
|
|
928
983
|
if (opts?.idempotencyKey) headers["idempotency-key"] = opts.idempotencyKey;
|
|
929
984
|
const result = await sharedFunctionInvoke(functionInvokeKey(name, body), async () => {
|
|
@@ -932,8 +987,13 @@ function useFunction(name) {
|
|
|
932
987
|
headers,
|
|
933
988
|
body: JSON.stringify(body ?? {})
|
|
934
989
|
});
|
|
935
|
-
|
|
936
|
-
|
|
990
|
+
const requestId = resp.headers?.get?.("x-oxy-request-id") ?? null;
|
|
991
|
+
if (!resp.ok && resp.status !== 200) throw withInvocationIds(await apiErrorFromResponse(resp), trace.traceId, requestId);
|
|
992
|
+
try {
|
|
993
|
+
return await readFunctionSseStream(resp);
|
|
994
|
+
} catch (err) {
|
|
995
|
+
throw withInvocationIds(err, trace.traceId, requestId);
|
|
996
|
+
}
|
|
937
997
|
});
|
|
938
998
|
setState({
|
|
939
999
|
data: result.value,
|
|
@@ -2444,4 +2504,4 @@ Object.defineProperty(exports, 'useTrackEvent', {
|
|
|
2444
2504
|
return useTrackEvent;
|
|
2445
2505
|
}
|
|
2446
2506
|
});
|
|
2447
|
-
//# sourceMappingURL=react-
|
|
2507
|
+
//# sourceMappingURL=react-CkAQg9wB.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-CkAQg9wB.cjs","names":["inflight","React"],"sources":["../src/custom-app/logger.ts","../src/custom-app/errors.ts","../src/custom-app/inject.ts","../src/custom-app/manifest.ts","../src/custom-app/function-invoke.ts","../src/custom-app/function-sse.ts","../src/custom-app/interpolate.ts","../src/custom-app/markdown.ts","../src/custom-app/query-cache.ts","../src/custom-app/traceparent.ts","../src/custom-app/react.tsx"],"sourcesContent":["// Diagnostic logger for custom-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","// Friendly interpretation of the errors a custom-app bundle can hit\n// at startup. The bundle catches an `Error` thrown by\n// `loadCustomAppManifest` or `useQuery`, hands it to\n// `interpretCustomAppError`, 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\n// ── API error types ─────────────────────────────────────────────────────────\n\n/**\n * Error thrown by all custom-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 */\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 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 */\nexport async 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// ── Bundle-startup error interpretation ─────────────────────────────────────\n\nexport interface CustomAppErrorReport {\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 interpretCustomAppError(err: unknown): CustomAppErrorReport {\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 custom-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 custom-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 custom-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 custom-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/custom_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 `loadCustomAppManifest` 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 custom-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/customer-apps-functions.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 /**\n * Receive an unauthenticated POST from a third party at\n * `POST /api/webhooks/apps/<org>/<app>/<name>`.\n *\n * The PLATFORM verifies every request — HMAC-SHA256 over the raw body, in\n * constant time — before the function is enqueued, so app code never sees an\n * unverified request. Omit the block and that endpoint answers 404, as if it\n * did not exist.\n */\n webhook?: {\n /**\n * App-secret key holding the signing key(s) — the same `apps/<app-id>/`\n * namespace `ctx.env` reads. The manifest names the secret, never holds it.\n *\n * **Comma-separated for rotation.** Providers that keep two live signing\n * keys (Uber's `BASIC_HMAC` does) sign with either during a rotation; any\n * match passes, so adopting a new key does not drop the events still\n * signed with the old one.\n */\n secretVar: string;\n /** Header carrying the signature, e.g. `x-uber-signature`. */\n signatureHeader: string;\n /** How the digest is encoded. Default `hex`. */\n encoding?: \"hex\" | \"base64\";\n };\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 * Capability to write app-scoped secrets via `ctx.secrets.set` (fail-closed:\n * omit → writes rejected). Only the app's own `apps/<app-id>/` namespace is\n * writable. Declare for a function that persists state — e.g. a scheduled\n * token-refresher that writes the rotated token back to Oxy Secrets.\n */\n secrets?: { write?: boolean };\n /**\n * Capability to send email via `ctx.email.send` (fail-closed: omit → the\n * host rejects `ctx.email.send` before any provider call). Declare for a\n * function that emails the app's users — e.g. a `notify` route that sends a\n * welcome message, or a scheduled digest. The sender mailbox is\n * platform-controlled; a function may set `replyTo` but never `from`.\n */\n email?: { send?: boolean };\n /**\n * Capability for `ctx.org.people()` — the org's people directory, READ-ONLY\n * (fail-closed: omit → the call is rejected before any query).\n *\n * Declare it for a function that has to name a person: an assignee, a roster\n * entry, who submitted something. It answers with a display name and a role.\n *\n * Three things it deliberately is not, so nobody plans around them:\n * it returns **no email and no phone** — naming a colleague is a different\n * need from contacting them off-platform; it returns **no location**, which\n * the platform does not hold for a member; and it does **not include\n * frontline workers**, who hold no org-membership row by design.\n *\n * One flag, not `read`/`write`: there is no write. Editing the directory\n * would put tenant membership behind an app's manifest.\n */\n org?: { read?: boolean };\n /**\n * Capability for `ctx.oltp` — read/write the app's OWN per-org OLTP schema on\n * the managed Postgres tenant (fail-closed: omit → every `ctx.oltp` call\n * rejected). A pure GATE: the target schema is derived from the app's own slug\n * host-side (`oltp-bookings` → `app_oltp_bookings`), never named here, so a\n * manifest cannot point `ctx.oltp` at another app's schema. The resolved role\n * has DML rights on that one schema and nothing else — reaching neither another\n * app's data nor the analyst-visible `raw_*` schemas. The store must be\n * provisioned first (ask whoever operates the org).\n *\n * NOTE: this shape is `{ enabled }`, not the earlier `{ writer }` — an app\n * had no business naming its own writer (that was the cross-app hole). A\n * manifest still carrying `\"oltp\": { \"writer\": \"…\" }` deserializes to\n * `enabled: undefined` → **disabled**, and `ctx.oltp` then reports the\n * capability as missing. Switch it to `{ \"enabled\": true }`.\n */\n oltp?: { enabled?: boolean };\n /**\n * Retry policy for **background** runs (a `schedule` fire or a manual job\n * trigger). Omit → a job run is attempted once. Route (HTTP) invocations are\n * request-scoped and never retried. `maxAttempts` counts the first try\n * (`maxAttempts: 3` = up to 2 retries); backoff is exponential (doubling)\n * between `minTimeoutMs` and `maxTimeoutMs`. Maps to the durable queue's\n * retry policy — a transient failure re-runs the whole isolate.\n */\n retries?: { maxAttempts?: number; minTimeoutMs?: number; maxTimeoutMs?: number };\n /**\n * Example input params for the function — a sample JSON body the admin \"Run\n * now\" surface prefills so an operator knows what to pass (the function reads\n * it as its `req` body, same as a route invocation). Advisory only; not\n * enforced at runtime.\n */\n inputExample?: unknown;\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 * Schema migrations that ship WITH this bundle and run on promote.\n *\n * `dir` is a directory inside the built bundle holding numbered `.sql` files.\n * The platform runs them in lexical order, **once each, ever**, inside a\n * transaction, as the app's own writer role, and records each one.\n *\n * What changes for the author: you no longer write defensive\n * `IF NOT EXISTS` / idempotent upserts, because re-running is a no-op by\n * construction rather than by your care. And you **may not edit, rename or\n * copy a migration that has already run** — all three fail the promote by\n * name, and the fix is always a new file.\n *\n * The `.sql` files are ordinary bundle files, fetchable over the app's own\n * host: put no secrets in them.\n */\n migrations?: { dir: string };\n /**\n * Optional Ask Oxygen binding (agent ref + composer chips). The\n * platform's registered copy is authoritative (surfaced by\n * shell-context); this local copy is the dev-time fallback so the\n * shell's Ask dock works before the app is registered.\n */\n ask?: { agent?: string; suggestedQuestions?: string[] };\n /**\n * The secrets this app expects, keyed by env-var name — the app's\n * `.env.example`, declared rather than written in a README.\n *\n * These are the keys your functions read as `ctx.env.KEY`. Declaring one puts\n * it in the app's Secrets surface (staff console → app → Secrets, and the\n * workspace's own settings) as a row to fill in, so a fresh deploy says what\n * is still missing instead of failing at the first invocation. Every function's\n * `webhook.secretVar` is folded in automatically — no need to repeat it here.\n *\n * **Names only, never values.** A manifest ships inside the bundle and is\n * fetchable over the app's own host; putting a secret in one publishes it.\n * Values are set out-of-band on the Secrets surface, or by\n * `ctx.secrets.set` from a function holding the `secrets.write` capability.\n *\n * App-level rather than per-function, because a secret is app-scoped by\n * construction: two functions sharing `STRIPE_API_KEY` read the same value,\n * so it can only be described once.\n *\n * ```jsonc\n * \"env\": {\n * \"STRIPE_API_KEY\": { \"required\": true, \"description\": \"Restricted key, Dashboard → Developers\" },\n * \"SLACK_WEBHOOK_URL\": { \"description\": \"Optional ops channel\" }\n * }\n * ```\n *\n * Read by the platform at **publish time** (like {@link OxyAppStorageManifest}),\n * so the block is documented here but not round-tripped through the dev-time\n * manifest fetch.\n */\n env?: Record<string, OxyAppEnvDeclaration>;\n /**\n * Optional app-level storage policy. Distinct from the per-function\n * `storage: { read, write }` capability: those gate what one function may\n * call, while this governs the app's whole asset silo, which every function\n * shares.\n */\n storage?: OxyAppStorageManifest;\n /**\n * Browser-runtime performance opt-outs. Both features below are **on by\n * default** — an app that says nothing gets them — so this block exists only\n * to turn one off.\n *\n * Read by the platform at **publish time** (like {@link OxyAppStorageManifest})\n * rather than by this loader, so the field is documented here but not\n * round-tripped through the dev-time manifest fetch.\n */\n performance?: OxyAppPerformanceManifest;\n /**\n * Opt out of the platform's automatic, zero-config usage instrumentation:\n * SPA pageviews, Core Web Vitals, engagement time, and uncaught-error counts,\n * posted to `<base>/__oxy/beacon` by the runtime Oxy injects into every served\n * page. `false` silences the **client** runtime only — the server still\n * records one view row per HTML navigation (that floor is not opt-out-able),\n * so the Activity tab never goes dark, it just loses the in-page detail.\n *\n * Distinct from `useTrackEvent` (your own named events): those are additive and\n * always on. This governs only the events the platform sends on your behalf.\n *\n * Honored at publish time (see {@link performance} for why it is not\n * round-tripped here). Default: `true`.\n */\n analytics?: boolean;\n}\n\n/** One declared secret — an entry in the `env` block of `oxy-app.json`. */\nexport interface OxyAppEnvDeclaration {\n /**\n * Flag the key as **Missing** (rather than merely absent) while nothing is\n * stored for it, and count it in the app's missing-secrets badge.\n *\n * Advisory, not a gate: a publish is never blocked on an unset key, because\n * the first publish is exactly when nobody could have set one yet.\n *\n * Default: `false` — a declaration is documentation first.\n */\n required?: boolean;\n /**\n * Shown beside the key on the Secrets surface. Say what it is and where to\n * get one — this is the text that saves someone a Slack message.\n */\n description?: string;\n}\n\n/** Browser-runtime performance opt-outs — the `performance` block in `oxy-app.json`. */\nexport interface OxyAppPerformanceManifest {\n /**\n * Opt out of the platform service worker Oxy registers at `<base>/__oxy/sw.js`.\n * It precaches your build's entry assets and serves content-hashed files\n * cache-first, so a repeat load of a published app is near-instant.\n *\n * Set `false` only if your app ships its own service worker (two workers\n * cannot both control the same scope) or genuinely must never be cached.\n * There is nothing to configure to opt *in* — a normal build is precached\n * automatically, and a bundle that inlines everything into one HTML file\n * simply has nothing to precache, which is fine. Default: `true`.\n */\n serviceWorker?: boolean;\n}\n\n/** How long assets under a given prefix are kept. */\nexport interface OxyAppRetentionRule {\n /**\n * Key prefix inside your silo, as you write it — `\"tmp/\"`, `\"generated/\"`.\n * The `customer-app-storage/<app_id>/` part is implicit.\n */\n prefix: string;\n /**\n * One of the five supported classes. `null` (or omitted) pins the prefix to\n * \"keep forever\", which is how you protect it from a broader sibling rule.\n *\n * The set is closed on purpose — each class is one bucket-wide S3 lifecycle\n * rule, so an arbitrary duration can't be honoured. An unrecognized value is\n * ignored with a warning and the prefix simply doesn't expire.\n */\n expireAfter?: \"1d\" | \"7d\" | \"30d\" | \"90d\" | \"365d\" | null;\n}\n\n/** App-level `storage` block in `oxy-app.json`. */\nexport interface OxyAppStorageManifest {\n /**\n * Retention rules for the asset silo. **Longest matching prefix wins**; a key\n * matching no rule is kept forever.\n *\n * Expiry is enforced by S3 lifecycle rules on an object tag, so it is\n * approximate (evaluated daily, not on the hour) and applies from the time an\n * object was written. Editing a rule does not retag assets already stored —\n * new writes pick up the new class.\n *\n * ```jsonc\n * \"storage\": {\n * \"retention\": [\n * { \"prefix\": \"tmp/\", \"expireAfter\": \"1d\" },\n * { \"prefix\": \"generated/\", \"expireAfter\": \"90d\" },\n * { \"prefix\": \"uploads/\", \"expireAfter\": null } // keep forever\n * ]\n * }\n * ```\n */\n retention?: OxyAppRetentionRule[];\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 ResolvedCustomAppManifest {\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<ResolvedCustomAppManifest> | 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 loadCustomAppManifest(\n options: LoadManifestOptions = {}\n): Promise<ResolvedCustomAppManifest> {\n if (!cached) {\n cached = fetchAndValidate(options);\n }\n return cached;\n}\n\n/** For tests: reset the cache between runs. */\nexport function _resetCustomAppManifestCacheForTest(): void {\n cached = null;\n}\n\nasync function fetchAndValidate(options: LoadManifestOptions): Promise<ResolvedCustomAppManifest> {\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 custom-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: ResolvedCustomAppManifest = {\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 if (!isValidSlug(raw.slug)) {\n // The slug becomes the app's OLTP schema/role name, a repo_path segment and\n // the served `/customer-apps/<org>/<slug>/` base path — `oxy publish` (and\n // `app_writer_name`) reject a bad one, so fail here at build, not in CI.\n throw new Error(\n `oxy-app.json: \\`slug\\` ${JSON.stringify(raw.slug)} is invalid — use 1–63 lowercase ` +\n `letters, digits and single hyphens (no leading/trailing/double hyphen, no underscore)`\n );\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 const ask = isRecord(raw.ask)\n ? {\n agent: typeof raw.ask.agent === \"string\" ? raw.ask.agent : undefined,\n suggestedQuestions: Array.isArray(raw.ask.suggestedQuestions)\n ? raw.ask.suggestedQuestions.filter((q): q is string => typeof q === \"string\")\n : undefined\n }\n : undefined;\n\n return { schemaVersion: 2, name, slug, orgSlug, projectId, functions, ask };\n}\n\n// Mirrors the server's `is_valid_slug` (admin/apps/ops.rs): 1–63 chars of\n// lowercase alphanumerics and single hyphens, no leading/trailing/double hyphen,\n// no underscore. The regex forbids a leading/trailing/double hyphen structurally;\n// the length is checked separately. (The vite plugin's build-time gate is the\n// primary one; this runtime check only fires on standalone `pnpm dev`.)\nconst SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nfunction isValidSlug(s: string): boolean {\n return s.length <= 63 && SLUG_RE.test(s);\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; each value\n * declares how the function is invoked. Mirrors the server-side function-name\n * rule enforced at publish in `custom_apps_publish.rs` — `is_valid_function_name`,\n * checked in `record_functions` before any row is written.\n *\n * This runs at manifest LOAD (app boot / `pnpm dev`), not at `oxy build` — the\n * build-time gate is the vite plugin's `validateManifest`, which now checks\n * function names too. `oxy publish` also validates them locally before esbuild,\n * so a bad name fails before the upload regardless.\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 if (value.retries !== undefined) {\n const r = value.retries;\n if (!isRecord(r)) {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`retries\\` must be an object`);\n }\n const retries: { maxAttempts?: number; minTimeoutMs?: number; maxTimeoutMs?: number } = {};\n for (const key of [\"maxAttempts\", \"minTimeoutMs\", \"maxTimeoutMs\"] as const) {\n const n = r[key];\n if (n !== undefined) {\n if (typeof n !== \"number\" || !Number.isInteger(n) || n < 1) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" \\`retries.${key}\\` must be a positive integer`\n );\n }\n retries[key] = n;\n }\n }\n fn.retries = retries;\n }\n if (value.webhook !== undefined) {\n const w = value.webhook;\n if (!isRecord(w)) {\n throw new Error(`oxy-app.json: function \"${fnName}\" \\`webhook\\` must be an object`);\n }\n // Both are required, and the error is loud on purpose: a webhook block\n // the server cannot read is not a degraded endpoint, it is a permanent\n // 404 on a URL the author has already handed to a provider.\n for (const key of [\"secretVar\", \"signatureHeader\"] as const) {\n if (typeof w[key] !== \"string\" || !(w[key] as string).trim()) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" \\`webhook.${key}\\` must be a non-empty string`\n );\n }\n }\n const webhook: NonNullable<OxyAppFunctionManifest[\"webhook\"]> = {\n secretVar: w.secretVar as string,\n signatureHeader: w.signatureHeader as string\n };\n if (w.encoding !== undefined) {\n if (w.encoding !== \"hex\" && w.encoding !== \"base64\") {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" \\`webhook.encoding\\` must be \"hex\" or \"base64\"`\n );\n }\n webhook.encoding = w.encoding;\n }\n fn.webhook = webhook;\n }\n if (value.inputExample !== undefined) {\n // Arbitrary JSON sample — passed through verbatim for the \"Run now\" prefill.\n fn.inputExample = value.inputExample;\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 hasWebhook = fn.webhook !== undefined;\n const routeActive = fn.route ?? !(hasSchedule || hasAirway || hasWebhook);\n if (!routeActive && !hasSchedule && !hasAirway && !hasWebhook) {\n throw new Error(\n `oxy-app.json: function \"${fnName}\" must enable at least one of ` +\n `route/schedule/airwayStep/webhook`\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/**\n * An error carries the logs captured before the throw, so the app can show them.\n *\n * `status` is the HTTP status the FUNCTION returned, present when the function\n * ran and answered a non-2xx. It is absent when the run itself failed (an\n * `event: error` frame — a crash, a timeout, a cancellation), because there was\n * no response to have a status.\n *\n * `body` is the parsed payload the function returned with that status, so a\n * caller can read `{ error: \"…\" }` without re-parsing the message.\n */\nexport type FunctionError = Error & {\n logs?: FunctionLog[];\n status?: number;\n body?: unknown;\n /**\n * The platform trace this invoke ran in (32 hex chars) and the server-minted\n * `x-oxy-request-id`, when the request got as far as the server. Quote\n * either in a bug report: an operator can open the trace in HyperDX, and\n * an app admin can filter the app's Logs by the request.\n */\n traceId?: string;\n requestId?: string;\n};\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 // The status the function returned. The route used to hardcode 200 here,\n // so a handler answering 403 or 409 resolved as an ordinary success and\n // every caller had to infer rejection from the body's shape — which meant\n // a `catch` written for it was dead code that never ran.\n //\n // Absent or non-numeric is treated as success: an older server does not\n // send one, and an app talking to it must keep working rather than start\n // throwing on every call.\n const meta = data ? (JSON.parse(data) as { status?: unknown }) : {};\n const status = typeof meta.status === \"number\" ? meta.status : 200;\n if (status < 200 || status >= 300) {\n const payload = parsed as { error?: unknown; message?: unknown } | null;\n const err = new Error(\n String(payload?.message ?? payload?.error ?? `function returned ${status}`)\n ) as FunctionError;\n err.name = \"FunctionStatusError\";\n err.status = status;\n err.body = parsed;\n err.logs = logs;\n throw err;\n }\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\n// ── GFM table parsing (used by the OxyAnswer markdown renderer) ──────────────\n\n/** Unescape GFM cell escapes (`\\|` → `|`, `\\\\` → `\\`). */\nfunction unescapeCell(cell: string): string {\n return cell.replace(/\\\\([|\\\\])/g, \"$1\");\n}\n\n/** Split a GFM table row into trimmed cells, dropping the outer pipes.\n * Splits on UNESCAPED `|` only — a `\\|` inside a cell is a literal pipe,\n * not a column separator — then unescapes each cell. */\nexport function splitTableRow(line: string): string[] {\n const s = line.trim();\n const cells: string[] = [];\n let cur = \"\";\n for (let i = 0; i < s.length; i++) {\n const ch = s[i];\n // Keep the escape sequence intact here; `unescapeCell` collapses it below.\n if (ch === \"\\\\\" && i + 1 < s.length) {\n cur += ch + s[i + 1];\n i++;\n continue;\n }\n if (ch === \"|\") {\n cells.push(cur);\n cur = \"\";\n continue;\n }\n cur += ch;\n }\n cells.push(cur);\n // Drop the empty cells produced by the optional leading/trailing pipes,\n // without eating a legitimately-empty first/last column mid-row.\n if (cells.length > 1 && cells[0].trim() === \"\") cells.shift();\n if (cells.length > 1 && cells[cells.length - 1].trim() === \"\") cells.pop();\n return cells.map((c) => unescapeCell(c.trim()));\n}\n\n/** A GFM delimiter row: every cell is `-`s with optional leading/trailing `:`. */\nexport function isTableDelimiter(line: string): boolean {\n if (!line?.includes(\"-\")) return false;\n const cells = splitTableRow(line);\n return cells.length > 0 && cells.every((c) => /^:?-{1,}:?$/.test(c));\n}\n\n/** A table starts at `idx` when that line has a pipe and the next line is a\n * delimiter row. */\nexport function isTableStart(lines: string[], idx: number): boolean {\n return (lines[idx] ?? \"\").includes(\"|\") && isTableDelimiter(lines[idx + 1] ?? \"\");\n}\n","// Shared in-flight dedup + short result cache for useQuery. Module-level so\n// every useQuery across the tree shares one cache. A shared in-flight request\n// is intentionally NOT aborted on a single consumer's unmount — others may\n// still need it; consumers guard their own setState with a `cancelled` flag.\n\nimport { apiErrorFromResponse } from \"./errors\";\n\nexport type QueryResult = { columns: string[]; rows: unknown[][] };\nexport type Fetcher = (path: string, init: RequestInit) => Promise<Response>;\n\nconst SWR_TTL_MS = 30_000;\nconst inflight = new Map<string, Promise<QueryResult>>();\nconst cache = new Map<string, { at: number; data: QueryResult }>();\n\nexport function queryKey(projectId: string, db: string | undefined, sql: string): string {\n return `${projectId} ${db ?? \"\"} ${sql}`;\n}\n\nexport function getCached(\n projectId: string,\n sql: string,\n db: string | undefined\n): QueryResult | undefined {\n const e = cache.get(queryKey(projectId, db, sql));\n return e && Date.now() - e.at < SWR_TTL_MS ? e.data : undefined;\n}\n\n/** Fetch with in-flight dedup + cache. `force` bypasses the fresh-cache\n * short-circuit (used by refetch) but still dedupes a concurrent in-flight. */\nexport async function sharedQuery(\n fetcher: Fetcher,\n projectId: string,\n sql: string,\n db: string | undefined,\n opts: { force?: boolean } = {}\n): Promise<QueryResult> {\n const key = queryKey(projectId, db, sql);\n if (!opts.force) {\n const fresh = getCached(projectId, sql, db);\n if (fresh) return fresh;\n }\n const existing = inflight.get(key);\n if (existing) return existing;\n\n const body = JSON.stringify({ sql, ...(db ? { database: db } : {}) });\n const p = (async () => {\n const resp = await fetcher(`/api/projects/${projectId}/query`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body\n });\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n const data = (await resp.json()) as QueryResult;\n cache.set(key, { at: Date.now(), data });\n return data;\n })().finally(() => inflight.delete(key));\n\n inflight.set(key, p);\n return p;\n}\n\n/** Test-only: reset module state between tests. */\nexport function __clearQueryCache(): void {\n inflight.clear();\n cache.clear();\n}\n","// A W3C `traceparent` minted in the browser, one per `useFunction().invoke()`.\n//\n// The server adopts an inbound `traceparent` as the parent of its request\n// span, so a call that carries one lands in a trace whose id the page knows\n// *before* the response arrives. That is what lets a failed invoke, or an\n// uncaught rejection that followed it, name the exact server-side trace — the\n// isolate, every `ctx.*` op, the function's own warnings — in the operator's\n// HyperDX, with nothing but the id.\n//\n// This is not a tracing SDK: nothing is exported from the browser, no\n// ingestion key ships in the bundle. Ids only.\n\n/** `{ header, traceId }` for one outbound call. */\nexport interface Traceparent {\n /** The `traceparent` header value: `00-<trace>-<span>-01`. */\n header: string;\n /** 32 lowercase hex chars — what to paste into HyperDX. */\n traceId: string;\n}\n\nconst HEX = \"0123456789abcdef\";\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes);\n const c = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array } }).crypto;\n if (c && typeof c.getRandomValues === \"function\") {\n c.getRandomValues(buf);\n } else {\n // A non-secure fallback is fine here: these ids group spans, they gate\n // nothing.\n for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);\n }\n let out = \"\";\n for (let i = 0; i < bytes; i++) out += HEX[buf[i] >> 4] + HEX[buf[i] & 15];\n return out;\n}\n\n/** Mint a fresh, sampled `traceparent`. All-zero ids are invalid per the spec;\n * the loop guards the astronomically unlikely draw. */\nexport function newTraceparent(): Traceparent {\n let traceId = randomHex(16);\n while (/^0+$/.test(traceId)) traceId = randomHex(16);\n let spanId = randomHex(8);\n while (/^0+$/.test(spanId)) spanId = randomHex(8);\n return { header: `00-${traceId}-${spanId}-01`, traceId };\n}\n\n/**\n * Stamp the ids of a failed invoke onto whatever was thrown, so the app (and\n * the platform's error beacon, which reads `traceId`) can name the trace and\n * the server-minted request id. Non-objects are returned untouched.\n */\nexport function withInvocationIds<E>(err: E, traceId: string, requestId?: string | null): E {\n if (err && typeof err === \"object\") {\n const target = err as { traceId?: string; requestId?: string };\n if (!target.traceId) target.traceId = traceId;\n if (requestId && !target.requestId) target.requestId = requestId;\n }\n return err;\n}\n","// React provider + hooks for custom-app bundles.\n//\n// Every custom 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 {\n apiErrorFromResponse,\n type CustomAppErrorReport,\n interpretCustomAppError,\n OxyApiError\n} 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 loadCustomAppManifest,\n type ResolvedCustomAppManifest\n} from \"./manifest\";\nimport { isSafeLinkHref, isTableStart, splitTableRow } from \"./markdown\";\nimport { getCached, sharedQuery } from \"./query-cache\";\nimport { newTraceparent, withInvocationIds } from \"./traceparent\";\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\n/**\n * Resolve relative (\"/…\") request paths against `backendUrl` so the SDK's API\n * calls reach a cross-origin oxy backend instead of the app's own origin.\n *\n * This is what lets a standalone dev app (e.g. served on `localhost:3005`)\n * drive the wired shell — `shell-context`, Ask Oxygen agent asks, events —\n * against oxy on another origin (`localhost:3000`) WITHOUT a same-origin dev\n * proxy. The target origin must permit the app's origin (oxy's main `/api`\n * allows configured dev origins + `credentials`); the external data API is a\n * separate, wildcard-CORS surface.\n *\n * Opt-in: when `backendUrl` is unset the base fetcher is returned unchanged, so\n * apps served same-origin by oxy keep their existing relative-URL behaviour.\n * Absolute URLs and non-string inputs pass through untouched (no double-prefix).\n */\nfunction withBackendBase(base: AppFetcher, backendUrl?: string): AppFetcher {\n if (!backendUrl) return base;\n const origin = backendUrl.replace(/\\/+$/, \"\");\n return (input, init) =>\n base(typeof input === \"string\" && input.startsWith(\"/\") ? origin + input : input, init);\n}\n\ninterface OxyAppContextValue {\n status: \"loading\" | \"ready\" | \"error\";\n resolved?: ResolvedCustomAppManifest;\n error?: CustomAppErrorReport;\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 `loadCustomAppManifest`. */\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: CustomAppErrorReport) => 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 /**\n * Origin of the oxy backend to call (e.g. `https://oxy.example.com` or\n * `http://localhost:3000`). When set, the SDK resolves its relative `/api/…`\n * requests — `shell-context`, Ask Oxygen, events — against this origin\n * instead of the app's own, so a standalone / cross-origin dev app can drive\n * the wired shell without a same-origin proxy. The backend must allow the\n * app's origin (see oxy's dev-origin CORS list). Leave unset when the app is\n * served same-origin by oxy.\n */\n backendUrl?: string;\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 {\n manifestOptions,\n fallback,\n errorFallback,\n fetcher: fetcherProp,\n backendUrl,\n children\n } = props;\n // Stable fetcher reference: caller-supplied or the module-level default,\n // wrapped so relative `/api/…` calls hit `backendUrl` when provided. We don't\n // put this in state because it should never change after mount (same\n // reasoning as manifestOptions).\n const fetcher = React.useMemo(\n () => withBackendBase(fetcherProp ?? defaultFetcher, backendUrl),\n [fetcherProp, backendUrl]\n );\n const [state, setState] = React.useState<OxyAppContextValue>({ status: \"loading\", fetcher });\n\n React.useEffect(() => {\n let cancelled = false;\n loadCustomAppManifest(manifestOptions)\n .then((resolved) => {\n if (!cancelled) setState({ status: \"ready\", resolved, fetcher });\n })\n .catch((e: unknown) => {\n if (!cancelled) setState({ status: \"error\", error: interpretCustomAppError(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: CustomAppErrorReport): 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// ── 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(): ResolvedCustomAppManifest {\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 identity without requiring\n * the manifest to be ready (e.g. inside `useQuery`, or the shell\n * chrome, which must never block the app on the manifest load).\n *\n * Exported for sibling hook modules (`metric-tree-hooks`,\n * `world-model-hooks`) that need the same credentialed fetcher +\n * project scope without re-deriving the context wiring. Not part of\n * the public bundle API — bundle authors use the concrete hooks.\n */\nexport function useOxyApp(): {\n projectId: string | undefined;\n /**\n * The `apps.id` this bundle was served as — from `window.__OXY_APP__`, so\n * it is the platform's word and not the manifest's. Undefined under `pnpm\n * dev` against a manifest with no injected identity.\n */\n appId: string | undefined;\n appSlug: string | undefined;\n orgSlug: string | undefined;\n fetcher: AppFetcher;\n} {\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 appId: ctx.resolved?.appId,\n appSlug: ctx.resolved?.appSlug,\n orgSlug: ctx.resolved?.orgSlug,\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 * custom 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 React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n let cancelled = false;\n\n // Serve from cache on initial mount; force-revalidate on refetch (nonce > 0).\n const cached = getCached(projectId, sqlWithParams, input.database);\n if (cached && nonce === 0) {\n const { columns, rows } = cached;\n const objects = rows.map((r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row);\n setState({ rows: objects, columns, loading: false, error: null });\n return;\n }\n\n setState((s) => ({ ...s, loading: true, error: null }));\n sharedQuery(fetcher, projectId, sqlWithParams, input.database, { force: nonce > 0 })\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: unknown) => {\n if (cancelled) 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 };\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 /**\n * Last invocation error, or null. On error this carries `.logs`, and\n * `.traceId` / `.requestId` — the ids that name the run to an operator.\n */\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 // One trace per invoke, minted here so the page knows its id even\n // when the call never returns; the server adopts it as the parent.\n const trace = newTraceparent();\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n accept: \"text/event-stream\",\n traceparent: trace.header\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 const requestId = resp.headers?.get?.(\"x-oxy-request-id\") ?? null;\n if (!resp.ok && resp.status !== 200) {\n throw withInvocationIds(await apiErrorFromResponse(resp), trace.traceId, requestId);\n }\n try {\n return await readFunctionSseStream<Data>(resp);\n } catch (err) {\n throw withInvocationIds(err, trace.traceId, requestId);\n }\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 model 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 * `\"reach\"` — pin the query server-side to the viewer's places: one `in`\n * filter per view it names whose primary entity is bound to the org's\n * locations registry. The bundle's own app is sent along so app-admin\n * standing counts. A query naming no bound view is refused.\n */\n scope?: \"reach\";\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-model 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, appId, 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 custom_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 ...(input.scope ? { scope: input.scope, ...(appId ? { app: appId } : {}) } : {})\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, appId, 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 === \"awaiting_input\") {\n // Suspension for a clarifying question. The server emits\n // `awaiting_input` (UiBlock::AwaitingInput) with a\n // `questions: [{ prompt, suggestions }]` array — NOT an\n // `ask_user` event, which never fires. Not terminal in the\n // cancel-sense: the user resumes by calling ask() again with\n // the same threadId, so keep runId set so cancel() still\n // works if they prefer to abort.\n terminated = true;\n const clarification =\n clarificationFromData(data) ?? \"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 if (attempts >= 5) {\n // Clean close without a terminal event hits the same\n // ceiling as the error path — otherwise a server that\n // keeps closing early spins the reconnect loop forever.\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"run event stream closed without a terminal event\")\n }));\n return;\n }\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/** Extract the clarifying-question text from an `awaiting_input` payload.\n * The server sends `{ questions: [{ prompt, suggestions }] }`; older shapes\n * used a single `{ question }`. Returns null when neither is present. */\nfunction clarificationFromData(data: unknown): string | null {\n if (typeof data !== \"object\" || data === null) return null;\n const d = data as Record<string, unknown>;\n const questions = Array.isArray(d.questions) ? d.questions : [];\n const first = questions[0] as Record<string, unknown> | undefined;\n if (first && typeof first.prompt === \"string\") return first.prompt;\n if (typeof d.question === \"string\") return d.question;\n return null;\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, appId, 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 // The event names its app. The endpoint is keyed by WORKSPACE (like\n // the rest of the bundle surface), and a workspace can publish several\n // apps; without this the server picked one of them to attribute the\n // event to, and a click in the Locations app could land in Store Ops'\n // activity. Omitted only when the bundle has no injected identity\n // (`pnpm dev`), where the server falls back to the old lookup.\n const body = JSON.stringify(appId ? { ...evt, app_id: appId } : 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, appId]);\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 ensureSpinKeyframes();\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 ensureSpinKeyframes();\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 | { kind: \"table\"; headers: string[]; rows: 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 // GFM table: header row followed by a delimiter row.\n if (isTableStart(lines, i)) {\n const headers = splitTableRow(line);\n i += 2; // skip header + delimiter\n const rows: string[][] = [];\n while (i < lines.length && (lines[i] ?? \"\").includes(\"|\") && (lines[i] ?? \"\").trim() !== \"\") {\n rows.push(splitTableRow(lines[i] ?? \"\"));\n i++;\n }\n blocks.push({ kind: \"table\", headers, rows });\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 isTableStart(lines, i)\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 \"table\":\n return (\n <div key={idx} style={styles.mdTableWrap}>\n <table style={styles.mdTable}>\n <thead>\n <tr>\n {b.headers.map((h, hi) => (\n <th key={`${hi}-${h}`} style={styles.mdTh}>\n {renderInline(h)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {b.rows.map((row, ri) => (\n <tr key={`${ri}-${row[0] ?? \"\"}`}>\n {b.headers.map((_h, ci) => (\n <td key={ci} style={styles.mdTd}>\n {renderInline(row[ci] ?? \"\")}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\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\n// The spinner animation referenced by `styles.spinner`. Injected once into\n// <head> at first render — the SDK ships no stylesheet on the main entry, so\n// without this the keyframes never exist and the spinner can't rotate.\nlet spinKeyframesInjected = false;\nfunction ensureSpinKeyframes(): void {\n if (spinKeyframesInjected || typeof document === \"undefined\") return;\n spinKeyframesInjected = true;\n if (document.getElementById(\"oxy-spin-keyframes\")) return;\n const el = document.createElement(\"style\");\n el.id = \"oxy-spin-keyframes\";\n el.textContent = \"@keyframes oxy-spin { to { transform: rotate(360deg); } }\";\n document.head.appendChild(el);\n}\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: \"var(--oxy-shell-foreground, #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 var(--oxy-shell-border, #d1d5db)\",\n borderTopColor: \"var(--oxy-shell-muted-fg, #6b7280)\",\n animation: \"oxy-spin 0.8s linear infinite\"\n },\n statusText: { color: \"var(--oxy-shell-muted-fg, #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: \"var(--oxy-shell-accent, #f3f4f6)\",\n border: \"1px solid var(--oxy-shell-border, #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: \"var(--oxy-shell-accent, #f3f4f6)\",\n padding: \"1px 4px\",\n borderRadius: 3\n },\n link: { color: \"var(--oxy-shell-link, #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: \"var(--oxy-shell-muted-fg, #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 var(--oxy-shell-border, #e5e7eb)\",\n borderRadius: 6,\n background: \"var(--oxy-shell-accent, #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: \"var(--oxy-shell-foreground, #374151)\"\n },\n artifactBadge: {\n fontWeight: 600,\n fontSize: 11,\n textTransform: \"uppercase\",\n letterSpacing: 0.4,\n color: \"var(--oxy-shell-muted-fg, #4b5563)\"\n },\n artifactSummary: { color: \"var(--oxy-shell-muted-fg, #6b7280)\", flex: 1 },\n artifactToggle: { color: \"var(--oxy-shell-link, #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 var(--oxy-shell-border, #e5e7eb)\",\n fontWeight: 600,\n color: \"var(--oxy-shell-foreground, #374151)\"\n },\n resultsTd: {\n padding: \"4px 8px\",\n borderBottom: \"1px solid var(--oxy-shell-border, #f3f4f6)\",\n color: \"var(--oxy-shell-foreground, #1f2937)\"\n },\n truncatedNote: { fontSize: 11, color: \"var(--oxy-shell-muted-fg, #6b7280)\", padding: \"6px 8px\" },\n // GFM markdown tables (answer body).\n mdTableWrap: { overflowX: \"auto\", margin: \"8px 0\" },\n mdTable: {\n width: \"100%\",\n borderCollapse: \"collapse\",\n fontSize: 12.5,\n border: \"1px solid var(--oxy-shell-border, #e5e7eb)\"\n },\n mdTh: {\n textAlign: \"left\",\n padding: \"5px 9px\",\n borderBottom: \"1px solid var(--oxy-shell-border, #e5e7eb)\",\n background: \"var(--oxy-shell-accent, #f3f4f6)\",\n fontWeight: 600,\n whiteSpace: \"nowrap\",\n color: \"var(--oxy-shell-foreground, #374151)\"\n },\n mdTd: {\n padding: \"5px 9px\",\n borderTop: \"1px solid var(--oxy-shell-border, #f3f4f6)\",\n verticalAlign: \"top\",\n color: \"var(--oxy-shell-foreground, #1f2937)\"\n },\n chatWrap: { fontFamily: SANS, fontSize: 14, color: \"var(--oxy-shell-foreground, #1f2937)\" },\n chatForm: { display: \"flex\", gap: 8, marginBottom: 12 },\n chatInput: {\n flex: 1,\n padding: \"8px 12px\",\n border: \"1px solid var(--oxy-shell-border, #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: \"var(--oxy-shell-link, #2563eb)\",\n color: \"#ffffff\",\n fontSize: 14,\n fontWeight: 500,\n cursor: \"pointer\"\n },\n chatCancel: {\n padding: \"8px 12px\",\n border: \"1px solid var(--oxy-shell-border, #d1d5db)\",\n borderRadius: 6,\n background: \"var(--oxy-shell-background, #ffffff)\",\n color: \"var(--oxy-shell-foreground, #374151)\",\n fontSize: 14,\n cursor: \"pointer\"\n },\n emptyState: {\n padding: \"12px 0\",\n color: \"var(--oxy-shell-muted-fg, #9ca3af)\",\n fontStyle: \"italic\"\n }\n};\n"],"mappings":"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"}
|