@mindstudio-ai/interface 0.1.11 → 0.1.13

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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/telemetry-breadcrumbs.ts","../src/telemetry-analytics.ts","../src/telemetry-errors.ts","../src/config.ts","../src/client.ts","../src/agent-chat.ts","../src/platform.ts","../src/auth-phone.ts","../src/auth-email.ts","../src/auth.ts","../src/index.ts"],"sourcesContent":["/**\n * Error class for the @mindstudio-ai/interface SDK.\n *\n * Thrown by `createClient()` method calls on HTTP errors, by `platform`\n * actions on failures, and by the config reader when bootstrap globals\n * are missing.\n */\nexport class MindStudioInterfaceError extends Error {\n /** Machine-readable error code (e.g. 'method_not_found', 'not_initialized'). */\n readonly code: string;\n\n /** HTTP status code, when the error originated from an API response. */\n readonly status?: number;\n\n constructor(message: string, code: string, status?: number) {\n super(message);\n this.name = 'MindStudioInterfaceError';\n this.code = code;\n this.status = status;\n }\n}\n","/**\n * Breadcrumbs — a ring buffer of recent navigations + network calls\n * attached to outgoing error reports for debugging context.\n *\n * Installed by `installErrorMonitoring()` in `telemetry-errors.ts`.\n * Three monkeypatches feed the buffer:\n *\n * - `history.pushState`/`replaceState`/`popstate` → `navigation`\n * - `window.fetch` → `fetch`\n * - `XMLHttpRequest.prototype.open`/`send` → `xhr`\n *\n * Each patch is idempotent — guarded by a `__ms_*_patched` flag so\n * re-imports (HMR, double bundle inclusion, etc.) are no-ops.\n *\n * The SDK's own telemetry endpoint is excluded from capture so error\n * reports don't feed themselves through the breadcrumb pipeline.\n */\n\nconst TELEMETRY_ENDPOINT_FRAGMENT = '/_/telemetry/';\nconst MAX_BREADCRUMBS = 20;\nconst MAX_BODY_BYTES = 1024;\n\n/**\n * A single breadcrumb entry. Discriminated union — server validates the\n * `type` field; unknown types are dropped at ingest.\n */\nexport type Breadcrumb =\n | {\n type: 'navigation';\n from: string;\n to: string;\n timestamp: number;\n }\n | {\n type: 'fetch';\n method: string;\n url: string;\n status?: number;\n ok: boolean;\n duration: number;\n timestamp: number;\n error?: string;\n /** Truncated response body for failed requests, when capture is enabled. */\n body?: string;\n }\n | {\n type: 'xhr';\n method: string;\n url: string;\n status?: number;\n ok: boolean;\n duration: number;\n timestamp: number;\n };\n\nconst buffer: Breadcrumb[] = [];\n\nfunction push(entry: Breadcrumb): void {\n buffer.push(entry);\n if (buffer.length > MAX_BREADCRUMBS) {\n buffer.shift();\n }\n}\n\n/**\n * Return a shallow copy of the current breadcrumb buffer.\n * Called when an error is captured to snapshot context at that moment.\n */\nexport function getBreadcrumbs(): Breadcrumb[] {\n return buffer.slice();\n}\n\nfunction shouldSkipUrl(url: string): boolean {\n return url.includes(TELEMETRY_ENDPOINT_FRAGMENT);\n}\n\nfunction resolveUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') {\n return input;\n }\n if (input instanceof URL) {\n return input.href;\n }\n return input.url || String(input);\n}\n\nlet lastHref = '';\n\nconst navListeners = new Set<(url: string) => void>();\n\n/**\n * Subscribe to navigation events. Fires on every history change\n * (`pushState`, `replaceState`, `popstate`, `hashchange`) with the new URL.\n *\n * Used by the analytics module to emit pageview events without\n * installing a second set of history patches.\n *\n * @returns Unsubscribe function.\n */\nexport function onNavigation(cb: (url: string) => void): () => void {\n navListeners.add(cb);\n return () => {\n navListeners.delete(cb);\n };\n}\n\nfunction emitNavigation(to: string): void {\n if (to === lastHref) {\n return;\n }\n push({\n type: 'navigation',\n from: lastHref,\n to,\n timestamp: Date.now(),\n });\n lastHref = to;\n navListeners.forEach((cb) => {\n try {\n cb(to);\n } catch {\n // listener errors must not break navigation\n }\n });\n}\n\nfunction installHistory(): void {\n const w = window as unknown as Record<string, unknown>;\n if (w.__ms_history_patched) {\n return;\n }\n w.__ms_history_patched = true;\n\n lastHref = location.href;\n\n const originalPushState = history.pushState.bind(history);\n history.pushState = function (\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalPushState(data, unused, url);\n try {\n emitNavigation(location.href);\n } catch {\n // never crash navigation\n }\n };\n\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = function (\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalReplaceState(data, unused, url);\n try {\n emitNavigation(location.href);\n } catch {\n // never crash navigation\n }\n };\n\n window.addEventListener('popstate', () => {\n try {\n emitNavigation(location.href);\n } catch {\n // swallow\n }\n });\n\n window.addEventListener('hashchange', () => {\n try {\n emitNavigation(location.href);\n } catch {\n // swallow\n }\n });\n}\n\nfunction installFetch(captureResponseBodies: boolean): void {\n if (!window.fetch) {\n return;\n }\n const fetchFn = window.fetch as typeof fetch & { __ms_patched?: boolean };\n if (fetchFn.__ms_patched) {\n return;\n }\n\n const originalFetch = window.fetch.bind(window);\n\n const patched: typeof fetch = function (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> {\n const url = resolveUrl(input);\n\n if (shouldSkipUrl(url)) {\n return originalFetch(input, init);\n }\n\n const method = (\n init?.method ||\n (typeof input !== 'string' && !(input instanceof URL)\n ? input.method\n : '') ||\n 'GET'\n ).toUpperCase();\n const startTime = Date.now();\n\n return originalFetch(input, init).then(\n (response) => {\n const entry: Breadcrumb = {\n type: 'fetch',\n method,\n url,\n status: response.status,\n ok: response.ok,\n duration: Date.now() - startTime,\n timestamp: startTime,\n };\n\n if (!response.ok && captureResponseBodies) {\n try {\n response\n .clone()\n .text()\n .then((body) => {\n entry.body = body.slice(0, MAX_BODY_BYTES);\n push(entry);\n })\n .catch(() => push(entry));\n } catch {\n push(entry);\n }\n } else {\n push(entry);\n }\n\n return response;\n },\n (err: unknown) => {\n push({\n type: 'fetch',\n method,\n url,\n ok: false,\n duration: Date.now() - startTime,\n timestamp: startTime,\n error:\n err instanceof Error ? err.message : String(err ?? 'fetch failed'),\n });\n throw err;\n },\n );\n };\n\n (patched as typeof fetch & { __ms_patched: boolean }).__ms_patched = true;\n window.fetch = patched;\n}\n\nfunction installXhr(): void {\n const openProto = XMLHttpRequest.prototype.open as ((\n ...args: unknown[]\n ) => void) & { __ms_patched?: boolean };\n if (openProto.__ms_patched) {\n return;\n }\n\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (\n this: XMLHttpRequest & {\n __ms_method?: string;\n __ms_url?: string;\n },\n method: string,\n url: string | URL,\n ...rest: unknown[]\n ): void {\n this.__ms_method = (method || 'GET').toUpperCase();\n this.__ms_url = String(url);\n return originalOpen.apply(this, [method, url, ...rest] as Parameters<\n typeof originalOpen\n >);\n } as typeof XMLHttpRequest.prototype.open;\n\n XMLHttpRequest.prototype.send = function (\n this: XMLHttpRequest & {\n __ms_method?: string;\n __ms_url?: string;\n },\n body?: Document | XMLHttpRequestBodyInit | null,\n ): void {\n const method = this.__ms_method || 'GET';\n const url = this.__ms_url || '';\n\n if (shouldSkipUrl(url)) {\n return originalSend.apply(this, [body] as Parameters<\n typeof originalSend\n >);\n }\n\n const startTime = Date.now();\n\n this.addEventListener(\n 'loadend',\n function (this: XMLHttpRequest) {\n push({\n type: 'xhr',\n method,\n url,\n status: this.status,\n ok: this.status >= 200 && this.status < 300,\n duration: Date.now() - startTime,\n timestamp: startTime,\n });\n },\n { once: true },\n );\n\n return originalSend.apply(this, [body] as Parameters<typeof originalSend>);\n } as typeof XMLHttpRequest.prototype.send;\n\n (\n XMLHttpRequest.prototype.open as typeof XMLHttpRequest.prototype.open & {\n __ms_patched: boolean;\n }\n ).__ms_patched = true;\n}\n\nlet _breadcrumbsInstalled = false;\n\n/**\n * Install all three breadcrumb sources. Idempotent.\n *\n * @param opts.captureResponseBodies — when `true`, failed `fetch`\n * responses have their bodies read and attached (truncated to ~1KB).\n * Backend strips this field unless the per-app setting is enabled.\n */\nexport function installBreadcrumbs(opts: {\n captureResponseBodies: boolean;\n}): void {\n if (_breadcrumbsInstalled) {\n return;\n }\n _breadcrumbsInstalled = true;\n\n try {\n installHistory();\n } catch {\n // history patches are best-effort\n }\n try {\n installFetch(opts.captureResponseBodies);\n } catch {\n // fetch patches are best-effort\n }\n try {\n installXhr();\n } catch {\n // xhr patches are best-effort\n }\n}\n","/**\n * Analytics — Plausible/Fathom-style visitor analytics.\n *\n * Auto-tracks pageviews via the existing history patches in\n * `telemetry-breadcrumbs.ts` and exposes a small public API:\n *\n * ```ts\n * import { analytics } from '@mindstudio-ai/interface';\n *\n * // Custom events (optional — pageviews track automatically)\n * analytics.track('vendor_submitted', { vendorType: 'restaurant' });\n * ```\n *\n * Server-side derivation handles geo (IP → country), device class\n * (UA → mobile/tablet/desktop), referrers, UTM parsing, and\n * sessionization. The SDK ships nothing for those — just raw\n * pageview events with enough context for backend enrichment.\n *\n * ## How it works\n *\n * Installed by `maybeInstallAnalytics()`, which is called by `getConfig()`\n * in `config.ts`. `getConfig()` runs automatically on the next tick after\n * SDK import when the platform bootstrap is present (see eager-init block\n * in `src/index.ts`), so pageview tracking + presence are live at page\n * load without app code needing to invoke anything. Falls back to lazy\n * install on first SDK use if eager init was skipped.\n *\n * Once installed:\n *\n * 1. Subscribes to navigation events from `telemetry-breadcrumbs.ts`\n * and emits a pageview on each change.\n * 2. Fires an initial pageview for the first-load URL.\n * 3. Batches events (~1s debounce, 100/batch cap) and POSTs to\n * `/_/telemetry/events`.\n * 4. On `pagehide` / `visibilitychange='hidden'`, drains via\n * `fetch` with `keepalive: true`.\n * 5. On `429`, pauses sends for the `Retry-After` window.\n *\n * ## Privacy posture\n *\n * Live visitor counts are intentionally **not** exposed to app code.\n * Aggregate visitor metrics (live count included) are surfaced only\n * through the platform dashboard to the app owner — visitors should\n * not be able to learn about other visitors' presence through the\n * SDK. If an app explicitly wants user-facing presence as a designed\n * feature (e.g. a multiplayer experience), it must build that on its\n * own application-level abstraction with its own consent semantics,\n * not on platform telemetry.\n *\n * Opt-out via `window.__MINDSTUDIO__.telemetry = { analytics: false }`.\n */\n\nimport { getConfig } from './config.js';\nimport { onNavigation } from './telemetry-breadcrumbs.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Flat-primitive prop values for custom events.\n *\n * Backend caps: max 10 keys, key length ~50 chars, value length ~500 chars.\n * Non-primitive values (objects, arrays, null) are stripped client-side\n * before send to save bandwidth — backend would silently drop them anyway.\n */\nexport type CustomEventProps = Record<string, string | number | boolean>;\n\n/**\n * The analytics namespace — pageview auto-tracking + custom events.\n *\n * Pageviews are auto-tracked from history changes; no public API for\n * them. Opt out entirely via `window.__MINDSTUDIO__.telemetry.analytics = false`.\n */\nexport interface AnalyticsClient {\n /**\n * Record a custom event with optional flat-primitive props.\n *\n * Non-primitive prop values (objects, arrays, null, undefined) are\n * stripped client-side before send. Server further caps name length\n * to 200 chars, props to 10 keys, key length to 50, value length to 500.\n *\n * Best-effort — never throws, never blocks. If the SDK isn't\n * initialized yet, the call is silently dropped.\n */\n track(event: string, props?: CustomEventProps): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal event types\n// ---------------------------------------------------------------------------\n\ntype PageviewEvent = {\n type: 'pageview';\n releaseId: string;\n url: string;\n referrer: string;\n userAgent: string;\n language: string;\n screen: { w: number; h: number };\n timestamp: number;\n};\n\ntype CustomEvent = {\n type: 'event';\n releaseId: string;\n name: string;\n url: string;\n timestamp: number;\n props?: CustomEventProps;\n};\n\ntype AnalyticsEvent = PageviewEvent | CustomEvent;\n\n// ---------------------------------------------------------------------------\n// Batched event transport\n// ---------------------------------------------------------------------------\n\nconst EVENTS_ENDPOINT = '/_/telemetry/events';\nconst FLUSH_INTERVAL_MS = 1000;\nconst MAX_BATCH_SIZE = 100;\nconst DEFAULT_RETRY_AFTER_MS = 60_000;\n\nlet pending: AnalyticsEvent[] = [];\nlet flushTimer: ReturnType<typeof setTimeout> | null = null;\nlet suppressUntil = 0;\nlet unloaded = false;\n\nfunction enqueue(event: AnalyticsEvent): void {\n if (unloaded) {\n return;\n }\n if (pending.length >= MAX_BATCH_SIZE) {\n return;\n }\n pending.push(event);\n scheduleFlush();\n}\n\nfunction scheduleFlush(): void {\n if (flushTimer || pending.length === 0) {\n return;\n }\n flushTimer = setTimeout(() => {\n flushTimer = null;\n void flush();\n }, FLUSH_INTERVAL_MS);\n}\n\nfunction takeBatch(): AnalyticsEvent[] {\n const batch = pending;\n pending = [];\n return batch;\n}\n\nasync function flush(): Promise<void> {\n if (pending.length === 0) {\n return;\n }\n\n if (Date.now() < suppressUntil) {\n pending = [];\n return;\n }\n\n let config;\n try {\n config = getConfig();\n } catch {\n pending = [];\n return;\n }\n\n const events = takeBatch();\n\n try {\n const res = await fetch(EVENTS_ENDPOINT, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({ events }),\n });\n\n if (res.status === 429) {\n const retryAfterHeader = res.headers.get('Retry-After');\n const retryAfterSec = retryAfterHeader\n ? parseInt(retryAfterHeader, 10)\n : NaN;\n const waitMs =\n Number.isFinite(retryAfterSec) && retryAfterSec > 0\n ? retryAfterSec * 1000\n : DEFAULT_RETRY_AFTER_MS;\n suppressUntil = Date.now() + waitMs;\n }\n } catch {\n // network / serialization failure — drop silently\n }\n}\n\nfunction drainOnUnload(): void {\n if (pending.length === 0) {\n return;\n }\n if (Date.now() < suppressUntil) {\n pending = [];\n return;\n }\n\n let config;\n try {\n config = getConfig();\n } catch {\n pending = [];\n return;\n }\n\n const events = takeBatch();\n\n try {\n void fetch(EVENTS_ENDPOINT, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({ events }),\n keepalive: true,\n });\n } catch {\n // best effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Custom event prop sanitization\n// ---------------------------------------------------------------------------\n\nfunction sanitizeProps(\n input?: Record<string, unknown>,\n): CustomEventProps | undefined {\n if (!input) {\n return undefined;\n }\n const out: CustomEventProps = {};\n for (const [k, v] of Object.entries(input)) {\n if (\n typeof v === 'string' ||\n typeof v === 'number' ||\n typeof v === 'boolean'\n ) {\n out[k] = v;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Pageview emit\n// ---------------------------------------------------------------------------\n\nfunction emitPageview(url: string): void {\n try {\n const config = getConfig();\n enqueue({\n type: 'pageview',\n releaseId: config.releaseId,\n url,\n referrer: document.referrer,\n userAgent: navigator.userAgent,\n language: navigator.language,\n screen: { w: window.innerWidth, h: window.innerHeight },\n timestamp: Date.now(),\n });\n } catch {\n // capture must never crash\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public `track`\n// ---------------------------------------------------------------------------\n\nfunction track(event: string, props?: CustomEventProps): void {\n try {\n const config = getConfig();\n enqueue({\n type: 'event',\n releaseId: config.releaseId,\n name: event,\n url: location.href,\n timestamp: Date.now(),\n props: sanitizeProps(props as Record<string, unknown> | undefined),\n });\n } catch {\n // never crash\n }\n}\n\n// ---------------------------------------------------------------------------\n// Silent presence connection\n//\n// The SDK opens a long-lived SSE connection to `/_/telemetry/presence`\n// while the tab is alive. The connection itself is the presence signal:\n// while it's open, the visitor is tracked as \"online\" in the server-side\n// Redis set; when the tab closes (or network drops), the connection\n// closes and the server immediately knows the visitor is gone.\n//\n// Any data the server pushes over this connection is silently discarded.\n// The visitor-facing live count is intentionally NOT exposed by the SDK.\n// The owner-facing dashboard reads counts via a separate authenticated\n// internal endpoint, never through the SDK.\n//\n// Reading-and-discarding the stream is required to keep the underlying\n// TCP buffer flowing — the open connection itself is what matters, not\n// the data flowing over it.\n// ---------------------------------------------------------------------------\n\nconst PRESENCE_ENDPOINT = '/_/telemetry/presence';\nconst PRESENCE_MAX_BACKOFF_MS = 10_000;\nconst PRESENCE_503_DEFAULT_MS = 30_000;\n// A stream must stay open at least this long to count as a *stable* connect\n// and earn a backoff reset. Shorter-lived streams are treated as flaps so\n// the backoff keeps escalating instead of hammering a ~1s reconnect loop.\nconst PRESENCE_STABLE_MS = 30_000;\n\nlet presenceAbort: AbortController | null = null;\nlet presenceReconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet presenceBackoffMs = 1000;\nlet presenceActive = false;\n\nfunction presenceNextBackoff(): number {\n const base = presenceBackoffMs;\n presenceBackoffMs = Math.min(presenceBackoffMs * 2, PRESENCE_MAX_BACKOFF_MS);\n const jitter = Math.random() * 250;\n return base + jitter;\n}\n\nfunction schedulePresenceReconnect(ms: number): void {\n if (presenceReconnectTimer) {\n clearTimeout(presenceReconnectTimer);\n }\n presenceReconnectTimer = setTimeout(() => {\n presenceReconnectTimer = null;\n if (presenceActive) {\n void openPresenceConnection();\n }\n }, ms);\n}\n\nfunction closePresenceConnection(): void {\n presenceActive = false;\n if (presenceReconnectTimer) {\n clearTimeout(presenceReconnectTimer);\n presenceReconnectTimer = null;\n }\n if (presenceAbort) {\n try {\n presenceAbort.abort();\n } catch {\n // swallow\n }\n presenceAbort = null;\n }\n}\n\nasync function openPresenceConnection(): Promise<void> {\n if (!presenceActive) {\n return;\n }\n\n let config;\n try {\n config = getConfig();\n } catch {\n presenceActive = false;\n return;\n }\n\n presenceAbort = new AbortController();\n const signal = presenceAbort.signal;\n\n try {\n const res = await fetch(PRESENCE_ENDPOINT, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${config.token}`,\n Accept: 'text/event-stream',\n },\n signal,\n });\n\n if (res.status === 503) {\n const retryAfterHeader = res.headers.get('Retry-After');\n const retryAfterSec = retryAfterHeader\n ? parseInt(retryAfterHeader, 10)\n : NaN;\n const waitMs =\n Number.isFinite(retryAfterSec) && retryAfterSec > 0\n ? retryAfterSec * 1000\n : PRESENCE_503_DEFAULT_MS;\n schedulePresenceReconnect(waitMs);\n return;\n }\n\n if (res.status === 401) {\n // Session bad — stop trying until something refreshes config\n presenceActive = false;\n return;\n }\n\n if (res.status === 204) {\n // Presence is stubbed/disabled for this session — e.g. the dev tunnel\n // mock, or a backend with presence turned off. Per EventSource\n // semantics (which the dev mock explicitly relies on) a 204 means\n // \"abandon the connection and do NOT reconnect.\" Stop cleanly instead\n // of hammering a reconnect loop against an endpoint that will keep\n // returning 204. A 204 has res.ok === true but a null body, so this\n // must be handled before the generic !res.body branch below.\n presenceActive = false;\n return;\n }\n\n if (!res.ok || !res.body) {\n schedulePresenceReconnect(presenceNextBackoff());\n return;\n }\n\n // Note the connect time — but don't reset the backoff yet. Resetting on\n // the bare 200 lets a stream that closes immediately reconnect every ~1s\n // forever (the backoff never engages). We only reset once the stream has\n // stayed open long enough to count as stable (see below).\n const connectedAt = Date.now();\n\n const reader = res.body.getReader();\n\n // Drain and discard. The open connection is the presence signal;\n // any count data the server pushes is intentionally ignored.\n while (presenceActive) {\n const { done } = await reader.read();\n if (done) {\n break;\n }\n }\n\n // Stream ended (server closed, deploy, etc.) — reconnect. If it stayed\n // open long enough to be healthy, reset the backoff so the reconnect is\n // prompt; otherwise let the backoff keep escalating (up to\n // PRESENCE_MAX_BACKOFF_MS) so a flapping stream can't become a per-second\n // reconnect storm.\n if (presenceActive) {\n if (Date.now() - connectedAt >= PRESENCE_STABLE_MS) {\n presenceBackoffMs = 1000;\n }\n schedulePresenceReconnect(presenceNextBackoff());\n }\n } catch (err) {\n const name = (err as { name?: string } | null)?.name;\n if (presenceActive && name !== 'AbortError') {\n schedulePresenceReconnect(presenceNextBackoff());\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public client\n// ---------------------------------------------------------------------------\n\nexport const analytics: AnalyticsClient = { track };\n\n// ---------------------------------------------------------------------------\n// Install + opt-out\n// ---------------------------------------------------------------------------\n\nlet _installed = false;\n\n/**\n * Install pageview auto-tracking + transport. Idempotent.\n *\n * Reads `window.__MINDSTUDIO__.telemetry?.analytics` — if explicitly\n * `false`, the SDK installs nothing and the function is a no-op.\n * `track()` calls also no-op in that case since enqueued events\n * have no flush path established.\n */\nexport function installAnalytics(): void {\n if (_installed) {\n return;\n }\n if (typeof window === 'undefined') {\n return;\n }\n\n let config;\n try {\n config = getConfig();\n } catch {\n return;\n }\n\n if (config.telemetry?.analytics === false) {\n return;\n }\n\n _installed = true;\n\n // Unload listeners (attached once)\n const handleUnload = (): void => {\n unloaded = true;\n drainOnUnload();\n closePresenceConnection();\n };\n window.addEventListener('pagehide', handleUnload);\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n drainOnUnload();\n }\n });\n\n // Auto-pageviews on every history change\n onNavigation((url) => {\n emitPageview(url);\n });\n\n // Initial pageview\n emitPageview(location.href);\n\n // Silent presence heartbeat — the open connection itself signals\n // \"this visitor is online\" to the server. No data is consumed.\n presenceActive = true;\n void openPresenceConnection();\n}\n\n/**\n * Wrapper that swallows any error from {@link installAnalytics} so a\n * misbehaving telemetry layer can never break `getConfig()`.\n *\n * @internal Called from config.ts on every `getConfig()` — idempotency\n * is enforced inside `installAnalytics`.\n */\nexport function maybeInstallAnalytics(): void {\n try {\n installAnalytics();\n } catch {\n // telemetry must never crash the host app\n }\n}\n","/**\n * Error reporting — auto-capture of uncaught errors and unhandled\n * promise rejections, batched and shipped to `/_/telemetry/errors`.\n *\n * ## How it works\n *\n * Installed by `maybeInstallTelemetry()`, which is called by `getConfig()`\n * in `config.ts`. `getConfig()` runs automatically on the next tick after\n * SDK import when the platform bootstrap is present (see eager-init block\n * in `src/index.ts`), so installation happens at page load without app\n * code needing to invoke anything. Falls back to lazy install on first\n * SDK use if eager init was skipped (SSR / tests / no bootstrap).\n *\n * Once installed:\n *\n * 1. `window.error` and `unhandledrejection` listeners enqueue events\n * into a pending batch (max 50 per batch — the server cap).\n * 2. Events with the same fingerprint within a batch collapse into a\n * single entry with `count: N` (bandwidth optimization).\n * 3. The batch flushes ~1s after the first push via `POST` to\n * `/_/telemetry/errors`.\n * 4. On `pagehide` / `visibilitychange='hidden'`, the buffer drains\n * via `fetch` with `keepalive: true` so the Bearer header survives.\n * 5. On `429`, the SDK pauses sends for the `Retry-After` window.\n * 6. Any other transport failure is swallowed — telemetry must never\n * crash the host app.\n *\n * No public API — the only knob is opt-out via\n * `window.__MINDSTUDIO__.telemetry = { errors: false }`.\n */\n\nimport { getConfig } from './config.js';\nimport {\n installBreadcrumbs,\n getBreadcrumbs,\n type Breadcrumb,\n} from './telemetry-breadcrumbs.js';\n\nconst ENDPOINT = '/_/telemetry/errors';\nconst FLUSH_INTERVAL_MS = 1000;\nconst MAX_BATCH_SIZE = 50;\nconst DEFAULT_RETRY_AFTER_MS = 60_000;\n\ninterface ErrorEvent {\n releaseId: string;\n url: string;\n userAgent: string;\n timestamp: number;\n type: 'error' | 'unhandledrejection';\n message: string;\n stack: string;\n source?: string;\n line?: number;\n column?: number;\n breadcrumbs: Breadcrumb[];\n count?: number;\n /** Internal: fingerprint for within-batch dedupe. Stripped before send. */\n _fp?: string;\n}\n\nlet _installed = false;\nlet pending: ErrorEvent[] = [];\nlet flushTimer: ReturnType<typeof setTimeout> | null = null;\nlet suppressUntil = 0;\nlet unloaded = false;\n\nfunction fingerprint(message: string, stack: string): string {\n const firstStackLine = stack.split('\\n')[1] ?? '';\n return `${message}|${firstStackLine.trim()}`;\n}\n\nfunction enqueue(event: ErrorEvent): void {\n if (unloaded) {\n return;\n }\n\n const fp = fingerprint(event.message, event.stack);\n\n for (const existing of pending) {\n if (existing._fp === fp) {\n existing.count = (existing.count ?? 1) + 1;\n return;\n }\n }\n\n if (pending.length >= MAX_BATCH_SIZE) {\n return;\n }\n\n event._fp = fp;\n pending.push(event);\n scheduleFlush();\n}\n\nfunction scheduleFlush(): void {\n if (flushTimer || pending.length === 0) {\n return;\n }\n flushTimer = setTimeout(() => {\n flushTimer = null;\n void flush();\n }, FLUSH_INTERVAL_MS);\n}\n\nfunction takeBatch(): ErrorEvent[] {\n const batch = pending;\n pending = [];\n return batch.map(({ _fp, ...rest }) => {\n void _fp;\n return rest;\n });\n}\n\nasync function flush(): Promise<void> {\n if (pending.length === 0) {\n return;\n }\n\n if (Date.now() < suppressUntil) {\n pending = [];\n return;\n }\n\n const config = (() => {\n try {\n return getConfig();\n } catch {\n return null;\n }\n })();\n if (!config) {\n pending = [];\n return;\n }\n\n const events = takeBatch();\n\n try {\n const res = await fetch(ENDPOINT, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({ events }),\n });\n\n if (res.status === 429) {\n const retryAfterHeader = res.headers.get('Retry-After');\n const retryAfterSec = retryAfterHeader\n ? parseInt(retryAfterHeader, 10)\n : NaN;\n const waitMs =\n Number.isFinite(retryAfterSec) && retryAfterSec > 0\n ? retryAfterSec * 1000\n : DEFAULT_RETRY_AFTER_MS;\n suppressUntil = Date.now() + waitMs;\n }\n } catch {\n // network / serialization failure — drop silently\n }\n}\n\nfunction drainOnUnload(): void {\n if (pending.length === 0) {\n return;\n }\n if (Date.now() < suppressUntil) {\n pending = [];\n return;\n }\n\n let config: ReturnType<typeof getConfig> | null = null;\n try {\n config = getConfig();\n } catch {\n pending = [];\n return;\n }\n\n const events = takeBatch();\n\n try {\n void fetch(ENDPOINT, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({ events }),\n keepalive: true,\n });\n } catch {\n // best effort\n }\n}\n\nfunction captureError(e: globalThis.ErrorEvent): void {\n try {\n const config = getConfig();\n enqueue({\n releaseId: config.releaseId,\n url: location.href,\n userAgent: navigator.userAgent,\n timestamp: Date.now(),\n type: 'error',\n message: e.message || 'Unknown error',\n stack: e.error?.stack ?? '',\n source: e.filename || undefined,\n line: typeof e.lineno === 'number' ? e.lineno : undefined,\n column: typeof e.colno === 'number' ? e.colno : undefined,\n breadcrumbs: getBreadcrumbs(),\n });\n } catch {\n // never crash on capture\n }\n}\n\nfunction captureRejection(e: PromiseRejectionEvent): void {\n try {\n const config = getConfig();\n const reason = e.reason as\n | { message?: string; stack?: string }\n | string\n | undefined;\n\n let message: string;\n let stack = '';\n\n if (reason && typeof reason === 'object') {\n message = reason.message || String(reason);\n stack = reason.stack ?? '';\n } else {\n message = String(reason ?? 'Unhandled rejection');\n }\n\n enqueue({\n releaseId: config.releaseId,\n url: location.href,\n userAgent: navigator.userAgent,\n timestamp: Date.now(),\n type: 'unhandledrejection',\n message,\n stack,\n breadcrumbs: getBreadcrumbs(),\n });\n } catch {\n // never crash on capture\n }\n}\n\n/**\n * Install error capture + breadcrumb collection. Idempotent.\n *\n * Reads `window.__MINDSTUDIO__.telemetry?.errors` — if explicitly\n * `false`, no listeners are attached and the function is a no-op.\n *\n * Otherwise attaches:\n * - `window.error` + `unhandledrejection`\n * - `pagehide` + `visibilitychange === 'hidden'` (for buffer drain)\n * - Breadcrumb sources via {@link installBreadcrumbs}\n */\nexport function installErrorMonitoring(): void {\n if (_installed) {\n return;\n }\n if (typeof window === 'undefined') {\n return;\n }\n\n let config: ReturnType<typeof getConfig>;\n try {\n config = getConfig();\n } catch {\n return;\n }\n\n if (config.telemetry?.errors === false) {\n return;\n }\n\n _installed = true;\n\n installBreadcrumbs({\n captureResponseBodies: !!config.telemetryCaptureResponseBodies,\n });\n\n window.addEventListener('error', captureError);\n window.addEventListener('unhandledrejection', captureRejection);\n\n const handleUnload = (): void => {\n unloaded = true;\n drainOnUnload();\n };\n window.addEventListener('pagehide', handleUnload);\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n drainOnUnload();\n }\n });\n}\n\n/**\n * Wrapper that swallows any error from {@link installErrorMonitoring}\n * so a misbehaving telemetry layer can never break `getConfig()`.\n *\n * @internal Called from config.ts on every `getConfig()` — idempotency\n * is enforced inside `installErrorMonitoring`.\n */\nexport function maybeInstallTelemetry(): void {\n try {\n installErrorMonitoring();\n } catch {\n // telemetry must never crash the host app\n }\n}\n","/**\n * Bootstrap configuration reader.\n *\n * Reads and validates `window.__MINDSTUDIO__` — the globals injected by\n * the platform before the app's JavaScript runs. The result is cached\n * after first read.\n *\n * All other modules in the SDK call `getConfig()` internally. App code\n * should never need to call this directly — use `createClient()`, `auth`,\n * and `platform` instead.\n */\n\nimport { MindStudioInterfaceError } from './errors.js';\nimport { maybeInstallAnalytics } from './telemetry-analytics.js';\nimport { maybeInstallTelemetry } from './telemetry-errors.js';\nimport type { AppUser, BootstrapConfig } from './types.js';\n\nlet _config: BootstrapConfig | undefined;\n\n/**\n * Read and validate the bootstrap configuration from `window.__MINDSTUDIO__`.\n *\n * Caches the result after first successful read. Throws if the globals\n * are missing or incomplete — this means the page isn't running inside\n * the MindStudio platform.\n *\n * @returns The validated bootstrap config\n * @throws {MindStudioInterfaceError} if `window.__MINDSTUDIO__` is missing or invalid\n */\nexport function getConfig(): BootstrapConfig {\n if (_config) {\n return _config;\n }\n\n const raw = (globalThis as Record<string, unknown>).__MINDSTUDIO__ as\n | Partial<BootstrapConfig>\n | undefined;\n\n if (!raw) {\n throw new MindStudioInterfaceError(\n '@mindstudio-ai/interface requires the MindStudio platform context. ' +\n 'This page must be loaded inside MindStudio (window.__MINDSTUDIO__ is missing).',\n 'not_initialized',\n );\n }\n\n // Validate required fields (user can be null for unauthenticated sessions)\n if (!raw.token || !raw.releaseId || !raw.methods) {\n const missing = ['token', 'releaseId', 'methods']\n .filter((k) => !raw[k as keyof BootstrapConfig])\n .join(', ');\n\n throw new MindStudioInterfaceError(\n `MindStudio platform context is incomplete. Missing: ${missing}`,\n 'invalid_config',\n );\n }\n\n _config = raw as BootstrapConfig;\n\n // Auto-install telemetry surfaces on first successful config read.\n // Both are idempotent + try/caught — telemetry must never break getConfig().\n maybeInstallTelemetry();\n maybeInstallAnalytics();\n\n return _config;\n}\n\n/**\n * Update the cached config in-place after an auth state transition.\n *\n * The cached config is a mutable reference — all SDK modules read it\n * on-demand via `getConfig()`, so mutations propagate immediately to\n * method invocation, agent chat, uploads, etc.\n *\n * @internal Not exported from the package — used by the auth module only.\n */\nexport function updateConfig(updates: {\n token?: string;\n user?: AppUser | null;\n methods?: Record<string, string>;\n visitorId?: string;\n}): void {\n const config = getConfig();\n if (updates.token !== undefined) {\n config.token = updates.token;\n }\n if (updates.user !== undefined) {\n config.user = updates.user;\n }\n if (updates.methods !== undefined) {\n config.methods = updates.methods;\n }\n if (updates.visitorId !== undefined) {\n config.visitorId = updates.visitorId;\n }\n}\n","/**\n * Method RPC client — typed function calls to backend methods.\n *\n * `createClient()` returns a Proxy where every property access creates\n * an async function that invokes the corresponding backend method via HTTP.\n * The method name matches the method's `export` field in `app.json`.\n *\n * ## How it works\n *\n * ```\n * api.submitVendorRequest({ name: 'Acme' })\n * │\n * ├─ Look up method ID: config.methods[\"submitVendorRequest\"]\n * │ → \"submit-vendor-request\"\n * │\n * ├─ POST /_/methods/submit-vendor-request/invoke\n * │ Authorization: Bearer {session token}\n * │ Body: { input: { name: \"Acme\" } }\n * │\n * └─ Returns response.output (or throws on error)\n * ```\n *\n * The method registry (export name → method ID mapping) is embedded in\n * `window.__MINDSTUDIO__.methods` at page load time — no extra HTTP call\n * needed to discover methods.\n *\n * ## Streaming\n *\n * Methods can optionally stream output via SSE. Pass `{ stream: true }` as\n * the second argument along with callbacks for the events you care about:\n *\n * ```ts\n * // LLM token text\n * const result = await api.submitVendorRequest({ name: 'Acme' }, {\n * stream: true,\n * onToken: (text) => setResponseText(text),\n * });\n *\n * // Structured progress events from `stream({...})` on the backend\n * const order = await api.processOrder({ id: 'abc' }, {\n * stream: true,\n * onStreamData: (event) => setStatus(event),\n * });\n * ```\n *\n * **Important:** The `text` value passed to `onToken` is the accumulated\n * response so far (not a delta). Replace your display content each time —\n * do not append. See {@link InvokeOptions.onToken} for details.\n *\n * Each `onStreamData` payload is independent (no accumulation) — it's the\n * raw object passed to `stream({...})` on the backend.\n *\n * ## Type safety\n *\n * Since the frontend and backend live in the same project, you can import\n * types directly:\n *\n * ```ts\n * import type { SubmitVendorInput, SubmitVendorOutput }\n * from '../../backend/src/submitVendorRequest';\n *\n * interface AppMethods {\n * submitVendorRequest(input: SubmitVendorInput): Promise<SubmitVendorOutput>;\n * getDashboard(): Promise<GetDashboardOutput>;\n * }\n *\n * const api = createClient<AppMethods>();\n * const result = await api.submitVendorRequest({ name: 'Acme' }); // fully typed\n * ```\n *\n * If no type parameter is provided, methods accept `any` input and return `any`.\n */\n\nimport { getConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\n\n/**\n * Options for method invocation. Pass as the optional second argument to\n * any method on the client returned by {@link createClient}.\n *\n * When `stream` is `true`, the SDK sends `{ stream: true }` in the request\n * body and parses the response as an SSE (Server-Sent Events) stream.\n * Four event types are handled:\n *\n * | SSE `type` | Payload field | SDK action |\n * |------------|---------------|------------|\n * | `token` | `text` | Calls {@link onToken} with accumulated text |\n * | `data` | `data` | Calls {@link onStreamData} with the raw payload |\n * | `error` | `error` | Calls {@link onStreamError} with the message |\n * | `done` | `output` | Captures as the method's return value |\n *\n * The method's `Promise` resolves with the final `output` from the `done`\n * event — the same shape you'd get without streaming.\n */\nexport interface InvokeOptions {\n /**\n * Enable SSE streaming for LLM token output.\n *\n * When `true`, the request body includes `{ stream: true }` and the\n * response is parsed as an SSE stream instead of a single JSON payload.\n * The method still returns a `Promise` that resolves with the final\n * output once the stream completes.\n *\n * @default false\n */\n stream?: boolean;\n\n /**\n * Called with the accumulated response text each time a new token arrives.\n *\n * **Important:** `text` is the full response so far, not a delta/chunk.\n * Your frontend should **replace** (not append to) its display content\n * with this value each time the callback fires.\n *\n * This design avoids edge cases where the platform sanitizes or\n * reformats JSON mid-stream, causing earlier portions of the text to\n * change between tokens.\n *\n * @example\n * ```ts\n * const result = await api.generate({ prompt: 'Hello' }, {\n * stream: true,\n * onToken: (text) => {\n * // Replace — do NOT append\n * responseElement.textContent = text;\n * },\n * });\n * ```\n */\n onToken?: (text: string) => void;\n\n /**\n * Called when the backend method emits a structured event via\n * `stream({...})` (object payload).\n *\n * Receives the raw object passed to `stream()` on the backend —\n * not accumulated, not parsed, not wrapped. Use this for status\n * updates, progress, intermediate results, or any structured\n * streaming UI that isn't LLM token text.\n *\n * Different from {@link onToken}, which receives accumulated text from\n * `stream('text')` calls.\n *\n * @example\n * ```ts\n * await api.processOrder({ id: 'abc' }, {\n * stream: true,\n * onStreamData: (event) => {\n * // event is whatever the backend passed to stream({...})\n * setStatus(event as { status: string; progress: number });\n * },\n * });\n * ```\n */\n onStreamData?: (payload: unknown) => void;\n\n /**\n * Called if a stream-level error event arrives.\n *\n * If the stream then closes without a `done` event, the method rejects\n * with a `MindStudioInterfaceError` containing the error message (code\n * `method_error`). If a `done` event does arrive after the error, the\n * method resolves with the final output — in that case this callback\n * is just for logging or showing a transient warning.\n *\n * If the stream ends without either a `done` or `error` event, the\n * method rejects with `stream_incomplete`.\n */\n onStreamError?: (error: string) => void;\n}\n\n/**\n * Default client type when no generic parameter is provided.\n * Every property returns an async function accepting optional input\n * and optional {@link InvokeOptions} for streaming.\n */\ntype DefaultMethodClient = Record<\n string,\n (input?: Record<string, unknown>, options?: InvokeOptions) => Promise<unknown>\n>;\n\n/**\n * Create a typed RPC client for calling backend methods.\n *\n * @typeParam T - Optional interface mapping method names to their signatures.\n * If omitted, all methods accept `any` and return `Promise<any>`.\n *\n * @returns A Proxy object where each property is an async function that\n * invokes the corresponding backend method.\n *\n * @example\n * ```ts\n * import { createClient } from '@mindstudio-ai/interface';\n *\n * const api = createClient();\n *\n * // Each method maps to a backend method export\n * const result = await api.submitVendorRequest({ name: 'Acme' });\n * const dashboard = await api.getDashboard();\n *\n * // Stream LLM output — onToken receives accumulated text (replace, don't append)\n * const streamed = await api.submitVendorRequest({ name: 'Acme' }, {\n * stream: true,\n * onToken: (text) => setResponseText(text),\n * });\n * // `streamed` is the same final output you'd get without streaming\n * ```\n */\nexport function createClient<T = DefaultMethodClient>(): T {\n return new Proxy({} as Record<string, unknown> as T & object, {\n get(_, methodName: string) {\n return async (input?: unknown, options?: InvokeOptions) => {\n const config = getConfig();\n\n const methodId = config.methods[methodName];\n if (!methodId) {\n throw new MindStudioInterfaceError(\n `Method \"${methodName}\" not found. Available methods: ${Object.keys(config.methods).join(', ') || '(none)'}`,\n 'method_not_found',\n );\n }\n\n const url = `/_/methods/${methodId}/invoke`;\n const wantsStream = options?.stream === true;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({\n input: input ?? {},\n ...(wantsStream && { stream: true }),\n }),\n });\n\n if (!res.ok) {\n let errorMessage = `Method \"${methodName}\" failed: ${res.status} ${res.statusText}`;\n let errorCode = 'method_error';\n try {\n const body = (await res.json()) as {\n error?: string;\n code?: string;\n };\n if (body.error) {\n errorMessage = body.error;\n }\n if (body.code) {\n errorCode = body.code;\n }\n } catch {\n // Response wasn't JSON — use the default message\n }\n throw new MindStudioInterfaceError(\n errorMessage,\n errorCode,\n res.status,\n );\n }\n\n // Non-streaming: unchanged behavior\n if (!wantsStream) {\n const body = (await res.json()) as { output?: unknown };\n return body.output;\n }\n\n // Streaming: parse SSE events from response body\n const reader = res.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let finalOutput: unknown;\n let streamError: string | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) {\n continue;\n }\n const json = line.slice(6);\n try {\n const event = JSON.parse(json) as {\n type: string;\n text?: string;\n error?: string;\n output?: unknown;\n data?: unknown;\n };\n\n if (event.type === 'token' && options?.onToken && event.text) {\n options.onToken(event.text);\n } else if (event.type === 'data' && options?.onStreamData) {\n options.onStreamData(event.data);\n } else if (event.type === 'error' && event.error) {\n streamError = event.error;\n if (options?.onStreamError) {\n options.onStreamError(event.error);\n }\n } else if (event.type === 'done') {\n finalOutput = event.output;\n }\n } catch {\n // Skip malformed SSE lines\n }\n }\n }\n\n // If the stream errored and didn't produce a final output,\n // surface the error message from the error event.\n if (finalOutput === undefined && streamError !== undefined) {\n throw new MindStudioInterfaceError(streamError, 'method_error');\n }\n\n if (finalOutput === undefined) {\n throw new MindStudioInterfaceError(\n 'Stream ended without a done event',\n 'stream_incomplete',\n );\n }\n\n return finalOutput;\n };\n },\n });\n}\n","/**\n * Agent chat client — thread-based conversations with AI agents.\n *\n * `createAgentChatClient()` returns an object with methods for thread\n * lifecycle (create, list, get, rename, delete) and message streaming.\n * The agent runs server-side with access to the app's methods as tools.\n *\n * ## How it works\n *\n * ```\n * chat.sendMessage(threadId, 'Help me prep for my review', {\n * onText: (delta) => setAssistantMessage((prev) => prev + delta),\n * })\n * │\n * ├─ POST /_/agent/threads/{threadId}/messages\n * │ Authorization: Bearer {session token}\n * │ Body: { content: \"Help me prep for my review\" }\n * │\n * ├─ Response: SSE stream\n * │ data: {\"type\":\"text\",\"text\":\"I'll create...\",\"ts\":1711...}\n * │ data: {\"type\":\"tool_call_start\",\"id\":\"call_abc\",\"name\":\"create-todo\",\"ts\":1711...}\n * │ data: {\"type\":\"tool_call_result\",\"id\":\"call_abc\",\"output\":{...},\"ts\":1711...}\n * │ data: {\"type\":\"text\",\"text\":\"Done — added it.\",\"ts\":1711...}\n * │ data: {\"type\":\"done\",\"stopReason\":\"end_turn\",\"usage\":{...},\"ts\":1711...}\n * │\n * └─ Promise resolves with { stopReason, usage }\n * ```\n *\n * ## Stateless design\n *\n * This client is a thin wrapper over HTTP endpoints. It does not manage\n * local state — your React/framework layer owns the thread list, message\n * array, and UI state. Each method is an independent request.\n *\n * ## Streaming callbacks\n *\n * `sendMessage` accepts named callbacks for common events (onText,\n * onThinking, onToolCallStart, etc.) plus a catch-all `onEvent` that\n * receives every SSE event as a discriminated union. Both fire for the\n * same event — use named callbacks for convenience, `onEvent` for\n * logging or handling low-level events like `tool_use` and\n * `tool_input_delta`.\n *\n * @example\n * ```ts\n * import { createAgentChatClient } from '@mindstudio-ai/interface';\n *\n * const chat = createAgentChatClient();\n *\n * // Thread lifecycle\n * const thread = await chat.createThread();\n * const { threads } = await chat.listThreads();\n * const full = await chat.getThread(thread.id);\n *\n * // Send a message with streaming\n * const response = chat.sendMessage(thread.id, 'Hello!', {\n * onText: (delta) => setMessage((prev) => prev + delta),\n * onToolCallStart: (id, name) => showToolSpinner(name),\n * onToolCallResult: (id, output) => showToolResult(output),\n * });\n *\n * const { stopReason, usage } = await response;\n *\n * // Abort mid-stream\n * response.abort();\n * ```\n */\n\nimport { getConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Thread types\n// ---------------------------------------------------------------------------\n\n/** Thread summary returned from create and list endpoints. */\nexport interface ThreadSummary {\n /** Thread ID (UUID). */\n id: string;\n /** User-editable title, auto-generated on first message. */\n title: string | null;\n /** ISO 8601 creation timestamp. */\n dateCreated: string;\n /** ISO 8601 last-updated timestamp. */\n dateUpdated: string;\n}\n\n/** Full thread with message history, returned from get endpoint. */\nexport interface Thread {\n /** Thread ID (UUID). */\n id: string;\n /** User-editable title. */\n title: string | null;\n /** Full conversation history. */\n messages: Message[];\n /** ISO 8601 creation timestamp. */\n dateCreated: string;\n /** ISO 8601 last-updated timestamp. */\n dateUpdated: string;\n}\n\n/** A tool call requested by the assistant. */\nexport interface ToolCall {\n /** Tool call ID, used to match results. */\n id: string;\n /** Method name. */\n name: string;\n /** Input arguments. */\n input: Record<string, unknown>;\n}\n\n/** A single message in a thread's conversation history. */\nexport interface Message {\n /** `\"user\"` for human messages, `\"assistant\"` for agent responses. */\n role: 'user' | 'assistant';\n /** Message text content. For tool results, this is JSON-stringified output. */\n content: string;\n /**\n * MindStudio CDN URLs attached to this message (user messages only).\n *\n * Images (`i.mscdn.ai`) are sent to the model as vision input.\n * Documents (`f.mscdn.ai`, `files.mindstudio-cdn.com`) have their text\n * extracted server-side and included in the model context. The original\n * URLs are preserved in thread history for the frontend to render.\n */\n attachments?: string[];\n /** Tool calls the assistant requested (assistant messages only). */\n toolCalls?: ToolCall[];\n /** ID of the tool call this message is a result for (tool result messages only). */\n toolCallId?: string;\n /** Whether this tool result represents an error. */\n isToolError?: boolean;\n}\n\n/** Paginated thread list response. */\nexport interface ThreadListPage {\n /** Threads for this page. */\n threads: ThreadSummary[];\n /** Cursor for the next page, or `null` if this is the last page. */\n nextCursor: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// SSE event types\n// ---------------------------------------------------------------------------\n\n/** Assistant text delta — append to your display string, do not replace. */\nexport interface AgentTextEvent {\n type: 'text';\n text: string;\n ts: number;\n}\n\n/** Model thinking text (extended thinking). */\nexport interface AgentThinkingEvent {\n type: 'thinking';\n text: string;\n ts: number;\n}\n\n/** Thinking complete with full text and signature. */\nexport interface AgentThinkingCompleteEvent {\n type: 'thinking_complete';\n thinking: string;\n signature: string;\n ts: number;\n}\n\n/** Model requested a tool call (includes parsed input). */\nexport interface AgentToolUseEvent {\n type: 'tool_use';\n id: string;\n name: string;\n input: Record<string, unknown>;\n ts: number;\n}\n\n/** Streaming delta for tool call input JSON. */\nexport interface AgentToolInputDeltaEvent {\n type: 'tool_input_delta';\n id: string;\n name: string;\n delta: string;\n ts: number;\n}\n\n/** Agent began executing a tool call. */\nexport interface AgentToolCallStartEvent {\n type: 'tool_call_start';\n id: string;\n name: string;\n ts: number;\n}\n\n/** Tool call returned a result. */\nexport interface AgentToolCallResultEvent {\n type: 'tool_call_result';\n id: string;\n output: unknown;\n ts: number;\n}\n\n/** Stream complete. */\nexport interface AgentDoneEvent {\n type: 'done';\n stopReason: string;\n usage: { inputTokens: number; outputTokens: number };\n ts: number;\n}\n\n/** Stream-level error. */\nexport interface AgentErrorEvent {\n type: 'error';\n error: string;\n ts: number;\n}\n\n/** Discriminated union of all SSE events from the agent chat stream. */\nexport type AgentChatEvent =\n | AgentTextEvent\n | AgentThinkingEvent\n | AgentThinkingCompleteEvent\n | AgentToolUseEvent\n | AgentToolInputDeltaEvent\n | AgentToolCallStartEvent\n | AgentToolCallResultEvent\n | AgentDoneEvent\n | AgentErrorEvent;\n\n// ---------------------------------------------------------------------------\n// Callbacks and result types\n// ---------------------------------------------------------------------------\n\n/**\n * Callbacks for `sendMessage` streaming.\n *\n * Named callbacks fire for common events. The catch-all `onEvent` fires\n * for every event including those with named callbacks — use it for\n * logging or handling low-level events like `tool_use` and\n * `tool_input_delta`.\n */\nexport interface SendMessageCallbacks {\n /**\n * Called with each text delta as the assistant responds.\n *\n * **Important:** `text` is a delta (new chunk), not the accumulated\n * response. Append it to your display string — do not replace.\n *\n * @example\n * ```ts\n * onText: (delta) => setMessage((prev) => prev + delta)\n * ```\n */\n onText?: (text: string) => void;\n\n /** Called with model thinking text (extended thinking). */\n onThinking?: (text: string) => void;\n\n /** Called when thinking is complete. */\n onThinkingComplete?: (thinking: string, signature: string) => void;\n\n /** Called when a tool call begins executing. */\n onToolCallStart?: (id: string, name: string) => void;\n\n /** Called when a tool call produces a result. */\n onToolCallResult?: (id: string, output: unknown) => void;\n\n /** Called on a stream-level error event. */\n onError?: (error: string) => void;\n\n /** Called for every SSE event (including ones with named callbacks). */\n onEvent?: (event: AgentChatEvent) => void;\n\n /** AbortSignal to cancel the stream. */\n signal?: AbortSignal;\n}\n\n/** Options for `sendMessage`. */\nexport interface SendMessageOptions {\n /**\n * MindStudio CDN URLs to attach to the message.\n *\n * Images (`i.mscdn.ai`) are sent to the model as vision input (one per message).\n * Documents (`f.mscdn.ai`, `files.mindstudio-cdn.com`) have their text\n * extracted server-side and included in context.\n *\n * Upload files first via `platform.uploadFile()`.\n *\n * @example\n * ```ts\n * const url = await platform.uploadFile(file);\n * chat.sendMessage(threadId, 'What is this?', callbacks, {\n * attachments: [url],\n * });\n * ```\n */\n attachments?: string[];\n}\n\n/** Resolved value of `sendMessage` — extracted from the `done` event. */\nexport interface SendMessageResult {\n /** Why the agent stopped (`\"end_turn\"`, `\"max_tokens\"`, etc.). */\n stopReason: string;\n /** Token usage for the full agent loop. */\n usage: { inputTokens: number; outputTokens: number };\n}\n\n/** A Promise with an `abort()` method for cancelling the stream. */\nexport type AbortablePromise<T> = Promise<T> & {\n /** Cancel the in-flight stream. The promise rejects with `AbortError`. */\n abort: () => void;\n};\n\n// ---------------------------------------------------------------------------\n// Client interface\n// ---------------------------------------------------------------------------\n\n/** Agent chat client returned by {@link createAgentChatClient}. */\nexport interface AgentChatClient {\n /** Create a new conversation thread. */\n createThread(): Promise<ThreadSummary>;\n\n /** List threads with cursor-based pagination (50 per page). */\n listThreads(cursor?: string): Promise<ThreadListPage>;\n\n /** Get a thread with its full message history. */\n getThread(threadId: string): Promise<Thread>;\n\n /** Update a thread's title. */\n updateThread(threadId: string, title: string): Promise<void>;\n\n /** Delete a thread (soft delete). */\n deleteThread(threadId: string): Promise<void>;\n\n /**\n * Send a message and stream the agent's response.\n *\n * Returns an {@link AbortablePromise} that resolves with\n * `{ stopReason, usage }` when the stream completes. Call `.abort()`\n * on the returned promise to cancel mid-stream.\n *\n * @param options.attachments - MindStudio CDN URLs to attach.\n * Images (`i.mscdn.ai`) are sent as vision input.\n * Documents (`f.mscdn.ai`) have text extracted into context.\n * Upload files first via `platform.uploadFile()`.\n */\n sendMessage(\n threadId: string,\n content: string,\n callbacks?: SendMessageCallbacks,\n options?: SendMessageOptions,\n ): AbortablePromise<SendMessageResult>;\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nconst AGENT_BASE = '/_/agent';\n\nasync function request<T>(\n path: string,\n method: string,\n body?: unknown,\n): Promise<T> {\n const config = getConfig();\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n };\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const res = await fetch(`${AGENT_BASE}${path}`, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n });\n\n if (!res.ok) {\n let errorMessage = `Agent chat request failed: ${res.status} ${res.statusText}`;\n let errorCode = 'agent_chat_error';\n try {\n const err = (await res.json()) as { error?: string; code?: string };\n if (err.error) {\n errorMessage = err.error;\n }\n if (err.code) {\n errorCode = err.code;\n }\n } catch {\n // Response wasn't JSON — use the default message\n }\n throw new MindStudioInterfaceError(errorMessage, errorCode, res.status);\n }\n\n return (await res.json()) as T;\n}\n\nfunction dispatchEvent(\n event: AgentChatEvent,\n callbacks: SendMessageCallbacks,\n): void {\n // Catch-all fires for every event\n callbacks.onEvent?.(event);\n\n // Named callbacks\n switch (event.type) {\n case 'text':\n callbacks.onText?.(event.text);\n break;\n case 'thinking':\n callbacks.onThinking?.(event.text);\n break;\n case 'thinking_complete':\n callbacks.onThinkingComplete?.(event.thinking, event.signature);\n break;\n case 'tool_call_start':\n callbacks.onToolCallStart?.(event.id, event.name);\n break;\n case 'tool_call_result':\n callbacks.onToolCallResult?.(event.id, event.output);\n break;\n case 'error':\n callbacks.onError?.(event.error);\n break;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an agent chat client for thread-based conversations.\n *\n * The client is stateless — each method is an independent HTTP request.\n * Your application layer owns thread state, message arrays, and UI updates.\n *\n * @returns An {@link AgentChatClient} with methods for thread CRUD and\n * message streaming.\n *\n * @example\n * ```ts\n * import { createAgentChatClient } from '@mindstudio-ai/interface';\n *\n * const chat = createAgentChatClient();\n *\n * const thread = await chat.createThread();\n *\n * const response = chat.sendMessage(thread.id, 'Hello!', {\n * onText: (delta) => setAssistantMessage((prev) => prev + delta),\n * onToolCallStart: (id, name) => showToolSpinner(name),\n * onToolCallResult: (id, output) => showToolResult(output),\n * });\n *\n * const { stopReason, usage } = await response;\n * ```\n */\nexport function createAgentChatClient(): AgentChatClient {\n return {\n createThread() {\n return request<ThreadSummary>('/threads', 'POST');\n },\n\n listThreads(cursor?: string) {\n const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';\n return request<ThreadListPage>(`/threads${query}`, 'GET');\n },\n\n getThread(threadId: string) {\n return request<Thread>(`/threads/${threadId}`, 'GET');\n },\n\n async updateThread(threadId: string, title: string) {\n await request(`/threads/${threadId}`, 'PATCH', { title });\n },\n\n async deleteThread(threadId: string) {\n await request(`/threads/${threadId}`, 'DELETE');\n },\n\n sendMessage(\n threadId: string,\n content: string,\n callbacks?: SendMessageCallbacks,\n options?: SendMessageOptions,\n ): AbortablePromise<SendMessageResult> {\n const controller = new AbortController();\n const cb = callbacks ?? {};\n\n // Wire caller's signal to our internal controller\n if (cb.signal) {\n if (cb.signal.aborted) {\n controller.abort();\n } else {\n cb.signal.addEventListener('abort', () => controller.abort(), {\n once: true,\n });\n }\n }\n\n const promise = (async (): Promise<SendMessageResult> => {\n const config = getConfig();\n const url = `${AGENT_BASE}/threads/${threadId}/messages`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({\n content,\n ...(options?.attachments?.length && {\n attachments: options.attachments,\n }),\n }),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n let errorMessage = `Send message failed: ${res.status} ${res.statusText}`;\n let errorCode = 'agent_chat_error';\n try {\n const err = (await res.json()) as {\n error?: string;\n code?: string;\n };\n if (err.error) {\n errorMessage = err.error;\n }\n if (err.code) {\n errorCode = err.code;\n }\n } catch {\n // Response wasn't JSON — use the default message\n }\n throw new MindStudioInterfaceError(\n errorMessage,\n errorCode,\n res.status,\n );\n }\n\n const reader = res.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let result: SendMessageResult | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) {\n continue;\n }\n const json = line.slice(6);\n try {\n const event = JSON.parse(json) as AgentChatEvent;\n\n if (event.type === 'done') {\n result = {\n stopReason: event.stopReason,\n usage: event.usage,\n };\n }\n\n dispatchEvent(event, cb);\n } catch {\n // Skip malformed SSE lines\n }\n }\n }\n\n if (!result) {\n throw new MindStudioInterfaceError(\n 'Stream ended without a done event',\n 'stream_incomplete',\n );\n }\n\n return result;\n })();\n\n const abortable = promise as AbortablePromise<SendMessageResult>;\n abortable.abort = () => controller.abort();\n return abortable;\n },\n };\n}\n","/**\n * Platform actions — file uploads via presigned S3 POST.\n *\n * Uses the session token from `window.__MINDSTUDIO__` to request a\n * presigned upload URL from the platform, then uploads directly to S3.\n * Returns the public CDN URL.\n *\n * @example\n * ```ts\n * import { platform } from '@mindstudio-ai/interface';\n *\n * const file = document.querySelector('input[type=file]').files[0];\n * const url = await platform.uploadFile(file);\n *\n * // With progress and abort\n * const controller = new AbortController();\n * const url = await platform.uploadFile(file, {\n * onProgress: (fraction) => console.log(`${Math.round(fraction * 100)}%`),\n * signal: controller.signal,\n * });\n * ```\n */\n\nimport { getConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\n\n/**\n * Options for `platform.uploadFile()`.\n */\nexport interface UploadFileOptions {\n /** Called with upload progress as a fraction from 0 to 1. */\n onProgress?: (fraction: number) => void;\n\n /** AbortSignal to cancel the upload. */\n signal?: AbortSignal;\n}\n\n/**\n * An upload token minted by an app's backend (`Store.createUploadToken` in\n * `@mindstudio-ai/agent`) and passed to {@link platform.upload}. Opaque — carry\n * it from the backend response straight into `platform.upload(token, file)`.\n */\nexport interface UploadToken {\n /** The object key the upload lands at. */\n key: string;\n /** The URL the file will be readable at once uploaded. */\n url: string;\n /** The scoped presigned POST the browser submits to. */\n upload: { url: string; fields: Record<string, string> };\n}\n\n/**\n * The platform namespace — file upload actions.\n */\nexport const platform = {\n /**\n * Upload a file to the MindStudio CDN.\n *\n * Requests a presigned upload URL from the platform, then uploads\n * the file directly to S3. Returns the public CDN URL.\n *\n * @param file - The File to upload\n * @param options - Optional progress callback and abort signal\n * @returns CDN URL of the uploaded file\n *\n * @example\n * ```ts\n * const url = await platform.uploadFile(file);\n *\n * // With progress\n * const url = await platform.uploadFile(file, {\n * onProgress: (f) => setProgress(f),\n * });\n *\n * // With abort\n * const controller = new AbortController();\n * const url = await platform.uploadFile(file, {\n * signal: controller.signal,\n * });\n * // controller.abort() to cancel\n * ```\n */\n async uploadFile(file: File, options?: UploadFileOptions): Promise<string> {\n const config = getConfig();\n const { onProgress, signal } = options ?? {};\n\n // Check if already aborted\n signal?.throwIfAborted();\n\n // Step 1: Get presigned upload URL\n const presignUrl = '/_/generate-upload-request';\n const presignRes = await fetch(presignUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${config.token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n filename: file.name,\n contentType: file.type || 'application/octet-stream',\n }),\n signal,\n });\n\n if (!presignRes.ok) {\n throw new MindStudioInterfaceError(\n `Upload presign failed: ${presignRes.status} ${presignRes.statusText}`,\n 'upload_error',\n presignRes.status,\n );\n }\n\n const { uploadUrl, uploadFields, publicUrl } =\n (await presignRes.json()) as {\n uploadUrl: string;\n uploadFields: Record<string, string>;\n publicUrl: string;\n };\n\n // Step 2: Upload directly to S3\n const formData = new FormData();\n for (const [key, value] of Object.entries(uploadFields)) {\n formData.append(key, value);\n }\n formData.append('file', file); // must be last\n\n // Use XHR for upload progress (fetch doesn't support it)\n if (onProgress) {\n await xhrUpload(uploadUrl, formData, onProgress, signal);\n return publicUrl;\n }\n\n const uploadRes = await fetch(uploadUrl, {\n method: 'POST',\n body: formData,\n signal,\n });\n\n if (!uploadRes.ok) {\n throw new MindStudioInterfaceError(\n `File upload failed: ${uploadRes.status} ${uploadRes.statusText}`,\n 'upload_error',\n uploadRes.status,\n );\n }\n\n return publicUrl;\n },\n\n /**\n * Upload a file with an {@link UploadToken} minted by the app's backend\n * (`Store.createUploadToken`). The file is POSTed **directly to storage** —\n * the bytes never pass through the platform — and the returned `{ key, url }`\n * is ready to render (`url` is a stable on-domain link the app can use in\n * `<img>` / `fetch` / `<a download>`).\n *\n * @example\n * ```ts\n * const token = await api.getUploadSlot({ contentType: file.type });\n * const { url } = await platform.upload(token, file, {\n * onProgress: (f) => setProgress(f),\n * });\n * ```\n */\n async upload(\n token: UploadToken,\n file: File,\n options?: UploadFileOptions,\n ): Promise<{ key: string; url: string }> {\n const { onProgress, signal } = options ?? {};\n signal?.throwIfAborted();\n\n const formData = new FormData();\n for (const [key, value] of Object.entries(token.upload.fields)) {\n formData.append(key, value);\n }\n formData.append('file', file); // must be last\n\n if (onProgress) {\n await xhrUpload(token.upload.url, formData, onProgress, signal);\n } else {\n const res = await fetch(token.upload.url, {\n method: 'POST',\n body: formData,\n signal,\n });\n if (!res.ok) {\n throw new MindStudioInterfaceError(\n `File upload failed: ${res.status} ${res.statusText}`,\n 'upload_error',\n res.status,\n );\n }\n }\n\n return { key: token.key, url: token.url };\n },\n};\n\n/**\n * Upload via XMLHttpRequest to get progress events.\n * Falls back from fetch because fetch upload streams don't support progress.\n */\nfunction xhrUpload(\n url: string,\n formData: FormData,\n onProgress: (fraction: number) => void,\n signal?: AbortSignal,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.addEventListener('progress', (e) => {\n if (e.lengthComputable) {\n onProgress(e.loaded / e.total);\n }\n });\n\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n onProgress(1);\n resolve();\n } else {\n reject(\n new MindStudioInterfaceError(\n `File upload failed: ${xhr.status} ${xhr.statusText}`,\n 'upload_error',\n xhr.status,\n ),\n );\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(\n new MindStudioInterfaceError(\n 'File upload failed: network error',\n 'upload_error',\n ),\n );\n });\n\n xhr.addEventListener('abort', () => {\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n\n // Wire up abort signal\n if (signal) {\n if (signal.aborted) {\n reject(new DOMException('Upload aborted', 'AbortError'));\n return;\n }\n signal.addEventListener('abort', () => xhr.abort(), { once: true });\n }\n\n xhr.open('POST', url);\n xhr.send(formData);\n });\n}\n","/**\n * Phone number utilities for auth flows.\n *\n * Provides country data, E.164 conversion, formatting, and validation.\n * No external dependencies — designed for login/signup UIs that need\n * a country code picker and phone number formatting.\n */\n\n// ---------------------------------------------------------------------------\n// Country data\n// ---------------------------------------------------------------------------\n\n/** A country with its dial code and flag emoji. */\nexport interface Country {\n /** ISO 3166-1 alpha-2 code (e.g. \"US\"). */\n code: string;\n /** International dial code with + prefix (e.g. \"+1\"). */\n dialCode: string;\n /** Country name in English. */\n name: string;\n /** Flag emoji. */\n flag: string;\n}\n\n/** All countries with dial codes, sorted alphabetically by name. */\nexport const countries: readonly Country[] = [\n { code: 'AF', dialCode: '+93', name: 'Afghanistan', flag: '🇦🇫' },\n { code: 'AL', dialCode: '+355', name: 'Albania', flag: '🇦🇱' },\n { code: 'DZ', dialCode: '+213', name: 'Algeria', flag: '🇩🇿' },\n { code: 'AD', dialCode: '+376', name: 'Andorra', flag: '🇦🇩' },\n { code: 'AO', dialCode: '+244', name: 'Angola', flag: '🇦🇴' },\n { code: 'AG', dialCode: '+1', name: 'Antigua and Barbuda', flag: '🇦🇬' },\n { code: 'AR', dialCode: '+54', name: 'Argentina', flag: '🇦🇷' },\n { code: 'AM', dialCode: '+374', name: 'Armenia', flag: '🇦🇲' },\n { code: 'AU', dialCode: '+61', name: 'Australia', flag: '🇦🇺' },\n { code: 'AT', dialCode: '+43', name: 'Austria', flag: '🇦🇹' },\n { code: 'AZ', dialCode: '+994', name: 'Azerbaijan', flag: '🇦🇿' },\n { code: 'BS', dialCode: '+1', name: 'Bahamas', flag: '🇧🇸' },\n { code: 'BH', dialCode: '+973', name: 'Bahrain', flag: '🇧🇭' },\n { code: 'BD', dialCode: '+880', name: 'Bangladesh', flag: '🇧🇩' },\n { code: 'BB', dialCode: '+1', name: 'Barbados', flag: '🇧🇧' },\n { code: 'BY', dialCode: '+375', name: 'Belarus', flag: '🇧🇾' },\n { code: 'BE', dialCode: '+32', name: 'Belgium', flag: '🇧🇪' },\n { code: 'BZ', dialCode: '+501', name: 'Belize', flag: '🇧🇿' },\n { code: 'BJ', dialCode: '+229', name: 'Benin', flag: '🇧🇯' },\n { code: 'BT', dialCode: '+975', name: 'Bhutan', flag: '🇧🇹' },\n { code: 'BO', dialCode: '+591', name: 'Bolivia', flag: '🇧🇴' },\n { code: 'BA', dialCode: '+387', name: 'Bosnia and Herzegovina', flag: '🇧🇦' },\n { code: 'BW', dialCode: '+267', name: 'Botswana', flag: '🇧🇼' },\n { code: 'BR', dialCode: '+55', name: 'Brazil', flag: '🇧🇷' },\n { code: 'BN', dialCode: '+673', name: 'Brunei', flag: '🇧🇳' },\n { code: 'BG', dialCode: '+359', name: 'Bulgaria', flag: '🇧🇬' },\n { code: 'BF', dialCode: '+226', name: 'Burkina Faso', flag: '🇧🇫' },\n { code: 'BI', dialCode: '+257', name: 'Burundi', flag: '🇧🇮' },\n { code: 'CV', dialCode: '+238', name: 'Cabo Verde', flag: '🇨🇻' },\n { code: 'KH', dialCode: '+855', name: 'Cambodia', flag: '🇰🇭' },\n { code: 'CM', dialCode: '+237', name: 'Cameroon', flag: '🇨🇲' },\n { code: 'CA', dialCode: '+1', name: 'Canada', flag: '🇨🇦' },\n {\n code: 'CF',\n dialCode: '+236',\n name: 'Central African Republic',\n flag: '🇨🇫',\n },\n { code: 'TD', dialCode: '+235', name: 'Chad', flag: '🇹🇩' },\n { code: 'CL', dialCode: '+56', name: 'Chile', flag: '🇨🇱' },\n { code: 'CN', dialCode: '+86', name: 'China', flag: '🇨🇳' },\n { code: 'CO', dialCode: '+57', name: 'Colombia', flag: '🇨🇴' },\n { code: 'KM', dialCode: '+269', name: 'Comoros', flag: '🇰🇲' },\n { code: 'CG', dialCode: '+242', name: 'Congo', flag: '🇨🇬' },\n { code: 'CD', dialCode: '+243', name: 'Congo (DRC)', flag: '🇨🇩' },\n { code: 'CR', dialCode: '+506', name: 'Costa Rica', flag: '🇨🇷' },\n { code: 'HR', dialCode: '+385', name: 'Croatia', flag: '🇭🇷' },\n { code: 'CU', dialCode: '+53', name: 'Cuba', flag: '🇨🇺' },\n { code: 'CY', dialCode: '+357', name: 'Cyprus', flag: '🇨🇾' },\n { code: 'CZ', dialCode: '+420', name: 'Czechia', flag: '🇨🇿' },\n { code: 'CI', dialCode: '+225', name: \"Cote d'Ivoire\", flag: '🇨🇮' },\n { code: 'DK', dialCode: '+45', name: 'Denmark', flag: '🇩🇰' },\n { code: 'DJ', dialCode: '+253', name: 'Djibouti', flag: '🇩🇯' },\n { code: 'DM', dialCode: '+1', name: 'Dominica', flag: '🇩🇲' },\n { code: 'DO', dialCode: '+1', name: 'Dominican Republic', flag: '🇩🇴' },\n { code: 'EC', dialCode: '+593', name: 'Ecuador', flag: '🇪🇨' },\n { code: 'EG', dialCode: '+20', name: 'Egypt', flag: '🇪🇬' },\n { code: 'SV', dialCode: '+503', name: 'El Salvador', flag: '🇸🇻' },\n { code: 'GQ', dialCode: '+240', name: 'Equatorial Guinea', flag: '🇬🇶' },\n { code: 'ER', dialCode: '+291', name: 'Eritrea', flag: '🇪🇷' },\n { code: 'EE', dialCode: '+372', name: 'Estonia', flag: '🇪🇪' },\n { code: 'SZ', dialCode: '+268', name: 'Eswatini', flag: '🇸🇿' },\n { code: 'ET', dialCode: '+251', name: 'Ethiopia', flag: '🇪🇹' },\n { code: 'FJ', dialCode: '+679', name: 'Fiji', flag: '🇫🇯' },\n { code: 'FI', dialCode: '+358', name: 'Finland', flag: '🇫🇮' },\n { code: 'FR', dialCode: '+33', name: 'France', flag: '🇫🇷' },\n { code: 'GA', dialCode: '+241', name: 'Gabon', flag: '🇬🇦' },\n { code: 'GM', dialCode: '+220', name: 'Gambia', flag: '🇬🇲' },\n { code: 'GE', dialCode: '+995', name: 'Georgia', flag: '🇬🇪' },\n { code: 'DE', dialCode: '+49', name: 'Germany', flag: '🇩🇪' },\n { code: 'GH', dialCode: '+233', name: 'Ghana', flag: '🇬🇭' },\n { code: 'GR', dialCode: '+30', name: 'Greece', flag: '🇬🇷' },\n { code: 'GD', dialCode: '+1', name: 'Grenada', flag: '🇬🇩' },\n { code: 'GT', dialCode: '+502', name: 'Guatemala', flag: '🇬🇹' },\n { code: 'GN', dialCode: '+224', name: 'Guinea', flag: '🇬🇳' },\n { code: 'GW', dialCode: '+245', name: 'Guinea-Bissau', flag: '🇬🇼' },\n { code: 'GY', dialCode: '+592', name: 'Guyana', flag: '🇬🇾' },\n { code: 'HT', dialCode: '+509', name: 'Haiti', flag: '🇭🇹' },\n { code: 'HN', dialCode: '+504', name: 'Honduras', flag: '🇭🇳' },\n { code: 'HK', dialCode: '+852', name: 'Hong Kong', flag: '🇭🇰' },\n { code: 'HU', dialCode: '+36', name: 'Hungary', flag: '🇭🇺' },\n { code: 'IS', dialCode: '+354', name: 'Iceland', flag: '🇮🇸' },\n { code: 'IN', dialCode: '+91', name: 'India', flag: '🇮🇳' },\n { code: 'ID', dialCode: '+62', name: 'Indonesia', flag: '🇮🇩' },\n { code: 'IR', dialCode: '+98', name: 'Iran', flag: '🇮🇷' },\n { code: 'IQ', dialCode: '+964', name: 'Iraq', flag: '🇮🇶' },\n { code: 'IE', dialCode: '+353', name: 'Ireland', flag: '🇮🇪' },\n { code: 'IL', dialCode: '+972', name: 'Israel', flag: '🇮🇱' },\n { code: 'IT', dialCode: '+39', name: 'Italy', flag: '🇮🇹' },\n { code: 'JM', dialCode: '+1', name: 'Jamaica', flag: '🇯🇲' },\n { code: 'JP', dialCode: '+81', name: 'Japan', flag: '🇯🇵' },\n { code: 'JO', dialCode: '+962', name: 'Jordan', flag: '🇯🇴' },\n { code: 'KZ', dialCode: '+7', name: 'Kazakhstan', flag: '🇰🇿' },\n { code: 'KE', dialCode: '+254', name: 'Kenya', flag: '🇰🇪' },\n { code: 'KI', dialCode: '+686', name: 'Kiribati', flag: '🇰🇮' },\n { code: 'KW', dialCode: '+965', name: 'Kuwait', flag: '🇰🇼' },\n { code: 'KG', dialCode: '+996', name: 'Kyrgyzstan', flag: '🇰🇬' },\n { code: 'LA', dialCode: '+856', name: 'Laos', flag: '🇱🇦' },\n { code: 'LV', dialCode: '+371', name: 'Latvia', flag: '🇱🇻' },\n { code: 'LB', dialCode: '+961', name: 'Lebanon', flag: '🇱🇧' },\n { code: 'LS', dialCode: '+266', name: 'Lesotho', flag: '🇱🇸' },\n { code: 'LR', dialCode: '+231', name: 'Liberia', flag: '🇱🇷' },\n { code: 'LY', dialCode: '+218', name: 'Libya', flag: '🇱🇾' },\n { code: 'LI', dialCode: '+423', name: 'Liechtenstein', flag: '🇱🇮' },\n { code: 'LT', dialCode: '+370', name: 'Lithuania', flag: '🇱🇹' },\n { code: 'LU', dialCode: '+352', name: 'Luxembourg', flag: '🇱🇺' },\n { code: 'MO', dialCode: '+853', name: 'Macao', flag: '🇲🇴' },\n { code: 'MG', dialCode: '+261', name: 'Madagascar', flag: '🇲🇬' },\n { code: 'MW', dialCode: '+265', name: 'Malawi', flag: '🇲🇼' },\n { code: 'MY', dialCode: '+60', name: 'Malaysia', flag: '🇲🇾' },\n { code: 'MV', dialCode: '+960', name: 'Maldives', flag: '🇲🇻' },\n { code: 'ML', dialCode: '+223', name: 'Mali', flag: '🇲🇱' },\n { code: 'MT', dialCode: '+356', name: 'Malta', flag: '🇲🇹' },\n { code: 'MH', dialCode: '+692', name: 'Marshall Islands', flag: '🇲🇭' },\n { code: 'MR', dialCode: '+222', name: 'Mauritania', flag: '🇲🇷' },\n { code: 'MU', dialCode: '+230', name: 'Mauritius', flag: '🇲🇺' },\n { code: 'MX', dialCode: '+52', name: 'Mexico', flag: '🇲🇽' },\n { code: 'FM', dialCode: '+691', name: 'Micronesia', flag: '🇫🇲' },\n { code: 'MD', dialCode: '+373', name: 'Moldova', flag: '🇲🇩' },\n { code: 'MC', dialCode: '+377', name: 'Monaco', flag: '🇲🇨' },\n { code: 'MN', dialCode: '+976', name: 'Mongolia', flag: '🇲🇳' },\n { code: 'ME', dialCode: '+382', name: 'Montenegro', flag: '🇲🇪' },\n { code: 'MA', dialCode: '+212', name: 'Morocco', flag: '🇲🇦' },\n { code: 'MZ', dialCode: '+258', name: 'Mozambique', flag: '🇲🇿' },\n { code: 'MM', dialCode: '+95', name: 'Myanmar', flag: '🇲🇲' },\n { code: 'NA', dialCode: '+264', name: 'Namibia', flag: '🇳🇦' },\n { code: 'NR', dialCode: '+674', name: 'Nauru', flag: '🇳🇷' },\n { code: 'NP', dialCode: '+977', name: 'Nepal', flag: '🇳🇵' },\n { code: 'NL', dialCode: '+31', name: 'Netherlands', flag: '🇳🇱' },\n { code: 'NZ', dialCode: '+64', name: 'New Zealand', flag: '🇳🇿' },\n { code: 'NI', dialCode: '+505', name: 'Nicaragua', flag: '🇳🇮' },\n { code: 'NE', dialCode: '+227', name: 'Niger', flag: '🇳🇪' },\n { code: 'NG', dialCode: '+234', name: 'Nigeria', flag: '🇳🇬' },\n { code: 'KP', dialCode: '+850', name: 'North Korea', flag: '🇰🇵' },\n { code: 'MK', dialCode: '+389', name: 'North Macedonia', flag: '🇲🇰' },\n { code: 'NO', dialCode: '+47', name: 'Norway', flag: '🇳🇴' },\n { code: 'OM', dialCode: '+968', name: 'Oman', flag: '🇴🇲' },\n { code: 'PK', dialCode: '+92', name: 'Pakistan', flag: '🇵🇰' },\n { code: 'PW', dialCode: '+680', name: 'Palau', flag: '🇵🇼' },\n { code: 'PS', dialCode: '+970', name: 'Palestine', flag: '🇵🇸' },\n { code: 'PA', dialCode: '+507', name: 'Panama', flag: '🇵🇦' },\n { code: 'PG', dialCode: '+675', name: 'Papua New Guinea', flag: '🇵🇬' },\n { code: 'PY', dialCode: '+595', name: 'Paraguay', flag: '🇵🇾' },\n { code: 'PE', dialCode: '+51', name: 'Peru', flag: '🇵🇪' },\n { code: 'PH', dialCode: '+63', name: 'Philippines', flag: '🇵🇭' },\n { code: 'PL', dialCode: '+48', name: 'Poland', flag: '🇵🇱' },\n { code: 'PT', dialCode: '+351', name: 'Portugal', flag: '🇵🇹' },\n { code: 'PR', dialCode: '+1', name: 'Puerto Rico', flag: '🇵🇷' },\n { code: 'QA', dialCode: '+974', name: 'Qatar', flag: '🇶🇦' },\n { code: 'RO', dialCode: '+40', name: 'Romania', flag: '🇷🇴' },\n { code: 'RU', dialCode: '+7', name: 'Russia', flag: '🇷🇺' },\n { code: 'RW', dialCode: '+250', name: 'Rwanda', flag: '🇷🇼' },\n { code: 'KN', dialCode: '+1', name: 'Saint Kitts and Nevis', flag: '🇰🇳' },\n { code: 'LC', dialCode: '+1', name: 'Saint Lucia', flag: '🇱🇨' },\n {\n code: 'VC',\n dialCode: '+1',\n name: 'Saint Vincent and the Grenadines',\n flag: '🇻🇨',\n },\n { code: 'WS', dialCode: '+685', name: 'Samoa', flag: '🇼🇸' },\n { code: 'SM', dialCode: '+378', name: 'San Marino', flag: '🇸🇲' },\n { code: 'ST', dialCode: '+239', name: 'Sao Tome and Principe', flag: '🇸🇹' },\n { code: 'SA', dialCode: '+966', name: 'Saudi Arabia', flag: '🇸🇦' },\n { code: 'SN', dialCode: '+221', name: 'Senegal', flag: '🇸🇳' },\n { code: 'RS', dialCode: '+381', name: 'Serbia', flag: '🇷🇸' },\n { code: 'SC', dialCode: '+248', name: 'Seychelles', flag: '🇸🇨' },\n { code: 'SL', dialCode: '+232', name: 'Sierra Leone', flag: '🇸🇱' },\n { code: 'SG', dialCode: '+65', name: 'Singapore', flag: '🇸🇬' },\n { code: 'SK', dialCode: '+421', name: 'Slovakia', flag: '🇸🇰' },\n { code: 'SI', dialCode: '+386', name: 'Slovenia', flag: '🇸🇮' },\n { code: 'SB', dialCode: '+677', name: 'Solomon Islands', flag: '🇸🇧' },\n { code: 'SO', dialCode: '+252', name: 'Somalia', flag: '🇸🇴' },\n { code: 'ZA', dialCode: '+27', name: 'South Africa', flag: '🇿🇦' },\n { code: 'KR', dialCode: '+82', name: 'South Korea', flag: '🇰🇷' },\n { code: 'SS', dialCode: '+211', name: 'South Sudan', flag: '🇸🇸' },\n { code: 'ES', dialCode: '+34', name: 'Spain', flag: '🇪🇸' },\n { code: 'LK', dialCode: '+94', name: 'Sri Lanka', flag: '🇱🇰' },\n { code: 'SD', dialCode: '+249', name: 'Sudan', flag: '🇸🇩' },\n { code: 'SR', dialCode: '+597', name: 'Suriname', flag: '🇸🇷' },\n { code: 'SE', dialCode: '+46', name: 'Sweden', flag: '🇸🇪' },\n { code: 'CH', dialCode: '+41', name: 'Switzerland', flag: '🇨🇭' },\n { code: 'SY', dialCode: '+963', name: 'Syria', flag: '🇸🇾' },\n { code: 'TW', dialCode: '+886', name: 'Taiwan', flag: '🇹🇼' },\n { code: 'TJ', dialCode: '+992', name: 'Tajikistan', flag: '🇹🇯' },\n { code: 'TZ', dialCode: '+255', name: 'Tanzania', flag: '🇹🇿' },\n { code: 'TH', dialCode: '+66', name: 'Thailand', flag: '🇹🇭' },\n { code: 'TL', dialCode: '+670', name: 'Timor-Leste', flag: '🇹🇱' },\n { code: 'TG', dialCode: '+228', name: 'Togo', flag: '🇹🇬' },\n { code: 'TO', dialCode: '+676', name: 'Tonga', flag: '🇹🇴' },\n { code: 'TT', dialCode: '+1', name: 'Trinidad and Tobago', flag: '🇹🇹' },\n { code: 'TN', dialCode: '+216', name: 'Tunisia', flag: '🇹🇳' },\n { code: 'TR', dialCode: '+90', name: 'Turkey', flag: '🇹🇷' },\n { code: 'TM', dialCode: '+993', name: 'Turkmenistan', flag: '🇹🇲' },\n { code: 'TV', dialCode: '+688', name: 'Tuvalu', flag: '🇹🇻' },\n { code: 'UG', dialCode: '+256', name: 'Uganda', flag: '🇺🇬' },\n { code: 'UA', dialCode: '+380', name: 'Ukraine', flag: '🇺🇦' },\n { code: 'AE', dialCode: '+971', name: 'United Arab Emirates', flag: '🇦🇪' },\n { code: 'GB', dialCode: '+44', name: 'United Kingdom', flag: '🇬🇧' },\n { code: 'US', dialCode: '+1', name: 'United States', flag: '🇺🇸' },\n { code: 'UY', dialCode: '+598', name: 'Uruguay', flag: '🇺🇾' },\n { code: 'UZ', dialCode: '+998', name: 'Uzbekistan', flag: '🇺🇿' },\n { code: 'VU', dialCode: '+678', name: 'Vanuatu', flag: '🇻🇺' },\n { code: 'VA', dialCode: '+39', name: 'Vatican City', flag: '🇻🇦' },\n { code: 'VE', dialCode: '+58', name: 'Venezuela', flag: '🇻🇪' },\n { code: 'VN', dialCode: '+84', name: 'Vietnam', flag: '🇻🇳' },\n { code: 'YE', dialCode: '+967', name: 'Yemen', flag: '🇾🇪' },\n { code: 'ZM', dialCode: '+260', name: 'Zambia', flag: '🇿🇲' },\n { code: 'ZW', dialCode: '+263', name: 'Zimbabwe', flag: '🇿🇼' },\n];\n\n// ---------------------------------------------------------------------------\n// Country lookup helpers\n// ---------------------------------------------------------------------------\n\nconst byCode = new Map(countries.map((c) => [c.code, c]));\n\n/** Common IANA timezone → country code mapping. */\nconst timezoneToCountry: Record<string, string> = {\n 'America/New_York': 'US',\n 'America/Chicago': 'US',\n 'America/Denver': 'US',\n 'America/Los_Angeles': 'US',\n 'America/Anchorage': 'US',\n 'Pacific/Honolulu': 'US',\n 'America/Phoenix': 'US',\n 'America/Indiana/Indianapolis': 'US',\n 'America/Toronto': 'CA',\n 'America/Vancouver': 'CA',\n 'America/Edmonton': 'CA',\n 'America/Winnipeg': 'CA',\n 'America/Halifax': 'CA',\n 'America/St_Johns': 'CA',\n 'Europe/London': 'GB',\n 'Europe/Dublin': 'IE',\n 'Europe/Paris': 'FR',\n 'Europe/Berlin': 'DE',\n 'Europe/Madrid': 'ES',\n 'Europe/Rome': 'IT',\n 'Europe/Amsterdam': 'NL',\n 'Europe/Brussels': 'BE',\n 'Europe/Zurich': 'CH',\n 'Europe/Vienna': 'AT',\n 'Europe/Stockholm': 'SE',\n 'Europe/Oslo': 'NO',\n 'Europe/Copenhagen': 'DK',\n 'Europe/Helsinki': 'FI',\n 'Europe/Warsaw': 'PL',\n 'Europe/Prague': 'CZ',\n 'Europe/Bucharest': 'RO',\n 'Europe/Budapest': 'HU',\n 'Europe/Athens': 'GR',\n 'Europe/Istanbul': 'TR',\n 'Europe/Moscow': 'RU',\n 'Europe/Kiev': 'UA',\n 'Europe/Lisbon': 'PT',\n 'Europe/Belgrade': 'RS',\n 'Europe/Zagreb': 'HR',\n 'Europe/Bratislava': 'SK',\n 'Europe/Ljubljana': 'SI',\n 'Europe/Tallinn': 'EE',\n 'Europe/Riga': 'LV',\n 'Europe/Vilnius': 'LT',\n 'Asia/Tokyo': 'JP',\n 'Asia/Seoul': 'KR',\n 'Asia/Shanghai': 'CN',\n 'Asia/Hong_Kong': 'HK',\n 'Asia/Taipei': 'TW',\n 'Asia/Singapore': 'SG',\n 'Asia/Kolkata': 'IN',\n 'Asia/Karachi': 'PK',\n 'Asia/Dhaka': 'BD',\n 'Asia/Bangkok': 'TH',\n 'Asia/Jakarta': 'ID',\n 'Asia/Manila': 'PH',\n 'Asia/Kuala_Lumpur': 'MY',\n 'Asia/Ho_Chi_Minh': 'VN',\n 'Asia/Riyadh': 'SA',\n 'Asia/Dubai': 'AE',\n 'Asia/Tehran': 'IR',\n 'Asia/Baghdad': 'IQ',\n 'Asia/Beirut': 'LB',\n 'Asia/Jerusalem': 'IL',\n 'Asia/Amman': 'JO',\n 'Asia/Almaty': 'KZ',\n 'Asia/Tashkent': 'UZ',\n 'Asia/Colombo': 'LK',\n 'Asia/Kathmandu': 'NP',\n 'Asia/Yangon': 'MM',\n 'Australia/Sydney': 'AU',\n 'Australia/Melbourne': 'AU',\n 'Australia/Brisbane': 'AU',\n 'Australia/Perth': 'AU',\n 'Australia/Adelaide': 'AU',\n 'Pacific/Auckland': 'NZ',\n 'Pacific/Fiji': 'FJ',\n 'Africa/Cairo': 'EG',\n 'Africa/Lagos': 'NG',\n 'Africa/Nairobi': 'KE',\n 'Africa/Johannesburg': 'ZA',\n 'Africa/Casablanca': 'MA',\n 'Africa/Accra': 'GH',\n 'Africa/Addis_Ababa': 'ET',\n 'Africa/Dar_es_Salaam': 'TZ',\n 'Africa/Kampala': 'UG',\n 'America/Mexico_City': 'MX',\n 'America/Bogota': 'CO',\n 'America/Lima': 'PE',\n 'America/Santiago': 'CL',\n 'America/Buenos_Aires': 'AR',\n 'America/Sao_Paulo': 'BR',\n 'America/Caracas': 'VE',\n 'America/Guatemala': 'GT',\n 'America/Havana': 'CU',\n 'America/Panama': 'PA',\n 'America/Jamaica': 'JM',\n};\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Detect the user's country from their timezone.\n * Falls back to `'US'` if the timezone can't be mapped.\n */\nexport function detectCountry(): string {\n try {\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n return timezoneToCountry[tz] ?? 'US';\n } catch {\n return 'US';\n }\n}\n\n/**\n * Format an E.164 phone number for display.\n *\n * US/CA numbers: `+1 (555) 123-4567`\n * Others: `+{dialCode} {rest}`\n */\nexport function format(e164: string): string {\n if (!e164.startsWith('+')) {\n return e164;\n }\n\n const digits = e164.slice(1);\n\n // US/CA: +1 followed by 10 digits\n if (digits.startsWith('1') && digits.length === 11) {\n const area = digits.slice(1, 4);\n const prefix = digits.slice(4, 7);\n const line = digits.slice(7);\n return `+1 (${area}) ${prefix}-${line}`;\n }\n\n // Find matching dial code (try longest match first)\n for (let len = 4; len >= 1; len--) {\n const candidate = '+' + digits.slice(0, len);\n const match = countries.find((c) => c.dialCode === candidate);\n if (match) {\n const rest = digits.slice(len);\n return `${match.dialCode} ${rest}`;\n }\n }\n\n return e164;\n}\n\n/**\n * Convert a national phone number to E.164 format.\n *\n * @param national - Phone number with or without formatting (e.g. \"(555) 123-4567\")\n * @param countryCode - ISO 3166-1 alpha-2 code (e.g. \"US\")\n * @returns E.164 formatted number (e.g. \"+15551234567\")\n */\nexport function toE164(national: string, countryCode: string): string {\n const digits = national.replace(/\\D/g, '');\n const country = byCode.get(countryCode);\n if (!country) {\n return '+' + digits;\n }\n const dialDigits = country.dialCode.slice(1); // remove +\n // If the number already starts with the dial code, don't double-add\n if (digits.startsWith(dialDigits)) {\n return '+' + digits;\n }\n return country.dialCode + digits;\n}\n\n/**\n * Check if a string is a valid E.164 phone number.\n * Must start with + followed by 7-15 digits.\n */\nexport function isValid(phone: string): boolean {\n return /^\\+[1-9]\\d{6,14}$/.test(phone);\n}\n","/**\n * Email validation helper for auth flows.\n */\n\n/** Basic email format check. Not RFC 5322 exhaustive. */\nexport function isValid(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n","/**\n * Auth — app-managed authentication for MindStudio v2 apps.\n *\n * Provides verification code flows (email + SMS), session state\n * management, and phone/email validation helpers. The platform\n * handles code delivery, cookie management, and user storage —\n * developers build their own login UI using these methods.\n *\n * ## How it works\n *\n * ```\n * // 1. Send a verification code\n * const { verificationId } = await auth.sendEmailCode('user@example.com');\n *\n * // 2. User enters the code in your UI\n * const user = await auth.verifyEmailCode(verificationId, '123456');\n *\n * // 3. Session is now active — all SDK calls use the authenticated token\n * const result = await api.getDashboard(); // uses authenticated session\n * auth.getCurrentUser(); // { id, email, phone, roles, createdAt }\n * ```\n *\n * Verify, confirm, and logout methods update `window.__MINDSTUDIO__`\n * in-place so all downstream calls (method invocation, agent chat,\n * uploads) immediately use the new session. No page refresh needed.\n *\n * @example\n * ```tsx\n * import { auth } from '@mindstudio-ai/interface';\n *\n * function LoginPage() {\n * const [email, setEmail] = useState('');\n * const [verificationId, setVerificationId] = useState('');\n * const [code, setCode] = useState('');\n *\n * const handleSend = async () => {\n * const { verificationId } = await auth.sendEmailCode(email);\n * setVerificationId(verificationId);\n * };\n *\n * const handleVerify = async () => {\n * await auth.verifyEmailCode(verificationId, code);\n * // Session updated in-place — navigate to your app\n * window.location.href = '/dashboard';\n * };\n * }\n * ```\n */\n\nimport { getConfig, updateConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\nimport type { AppUser, AuthSessionBundle } from './types.js';\nimport * as phoneHelpers from './auth-phone.js';\nimport * as emailHelpers from './auth-email.js';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nasync function authFetch<T>(\n path: string,\n method: 'GET' | 'POST',\n body?: Record<string, unknown>,\n): Promise<T> {\n const config = getConfig();\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n };\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const res = await fetch(path, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n });\n\n if (!res.ok) {\n let message = `Auth request failed: ${res.status} ${res.statusText}`;\n let code = 'auth_error';\n try {\n const err = (await res.json()) as { error?: string; code?: string };\n if (err.error) {\n message = err.error;\n }\n if (err.code) {\n code = err.code;\n }\n } catch {\n // Response wasn't JSON\n }\n throw new MindStudioInterfaceError(message, code, res.status);\n }\n\n return (await res.json()) as T;\n}\n\n// sessionStorage key holding the CSRF `state` for an in-flight top-level\n// \"Sign in with Remy\" round-trip. Set by signInWithRemy(), validated + cleared\n// by handleRemyRedirect(). (The embedded/popup flow keeps `state` in the\n// opener's memory instead — see signInWithRemyPopup.)\nconst REMY_STATE_KEY = '__ms_remy_state';\n\n// Embedded (iframe) flow: a query marker on the popup callback URL so the SDK\n// instance that loads in the popup relays + closes instead of redeeming, and\n// the postMessage discriminator the opener matches on.\nconst REMY_POPUP_MARKER = 'ms_popup';\nconst REMY_POPUP_MESSAGE = 'ms:remy:callback';\n\n// The popup document forwards its one-time code to the opener exactly once,\n// whichever entry point (the import-time fast path or handleRemyRedirect())\n// runs the relay. Flipped after the first successful post — see\n// maybeRelayRemyPopupCallback.\nlet remyPopupForwarded = false;\n\n/**\n * Whether a delegated (\"Sign in with Remy\") handshake is completing right now.\n *\n * Set **synchronously at module load** when the page is a cold-load redirect\n * return (a `?code` in the URL that isn't a popup callback) — before any app\n * render — and cleared when the exchange settles. This is the stable signal\n * apps read via {@link Auth.authStatus}; the `?code` URL param can't serve that\n * role because handleRemyRedirect() strips it at the *start* of the exchange.\n * Also toggled around the embedded popup exchange (see signInWithRemyPopup).\n */\nlet redirectPending = detectColdLoadRedirect();\n\n/** True when this page load is a cold-load delegated redirect return. */\nfunction detectColdLoadRedirect(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n try {\n const params = new URL(window.location.href).searchParams;\n // A popup-callback document (`?ms_popup=1`) relays + closes; it never\n // redeems here, so it isn't \"authenticating\" from the app's perspective.\n return params.has('code') && params.get(REMY_POPUP_MARKER) !== '1';\n } catch {\n return false;\n }\n}\n\n/** Generate an opaque, unguessable CSRF state value. */\nfunction generateState(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') {\n return c.randomUUID();\n }\n const bytes = new Uint8Array(16);\n c?.getRandomValues?.(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** Strip our own `code`/`state` params from the URL without a reload. */\nfunction stripRemyRedirectParams(url: URL): void {\n url.searchParams.delete('code');\n url.searchParams.delete('state');\n try {\n window.history.replaceState(window.history.state, '', url.toString());\n } catch {\n // history unavailable (e.g. non-browser) — nothing to clean\n }\n}\n\nconst authListeners: Set<(user: AppUser | null) => void> = new Set();\n\nfunction applySession(bundle: AuthSessionBundle): void {\n updateConfig({\n token: bundle.token,\n user: bundle.user,\n methods: bundle.methods,\n visitorId: bundle.visitorId,\n });\n authListeners.forEach((cb) => cb(bundle.user));\n}\n\nfunction requireUser(bundle: AuthSessionBundle): AppUser {\n if (!bundle.user) {\n throw new MindStudioInterfaceError(\n 'Verification succeeded but no user was returned',\n 'auth_error',\n );\n }\n return bundle.user;\n}\n\nfunction updateUserAndNotify(update: Partial<AppUser>): void {\n const config = getConfig();\n if (config.user) {\n Object.assign(config.user, update);\n authListeners.forEach((cb) => cb(config.user));\n }\n}\n\n/** The current user, or `null` — never throws if the bootstrap is absent. */\nfunction safeCurrentUser(): AppUser | null {\n try {\n return getConfig().user;\n } catch {\n return null;\n }\n}\n\n/** Fire every auth listener with the current user (for auth-state changes). */\nfunction notifyAuthListeners(): void {\n const user = safeCurrentUser();\n authListeners.forEach((cb) => cb(user));\n}\n\n/** Enter the 'authenticating' state and notify subscribers (idempotent). */\nfunction beginAuthenticating(): void {\n if (redirectPending) {\n return;\n }\n redirectPending = true;\n notifyAuthListeners();\n}\n\n/** Leave the 'authenticating' state and notify subscribers (idempotent). */\nfunction endAuthenticating(): void {\n if (!redirectPending) {\n return;\n }\n redirectPending = false;\n notifyAuthListeners();\n}\n\n/** True when the SDK is running inside a (cross-origin) iframe. */\nfunction isEmbedded(): boolean {\n try {\n return window.top !== window.self;\n } catch {\n // Cross-origin restrictions on reading window.top → we're definitely framed.\n return true;\n }\n}\n\n// Sign-in popup size — compact and roughly square. (The old 480×720 opened tall\n// and, with no left/top, pinned to the top-left corner.)\nconst REMY_POPUP_WIDTH = 520;\nconst REMY_POPUP_HEIGHT = 600;\n\n/**\n * A `window.open` features string for a popup of the given size, centered over\n * the current browser window (dual-monitor-safe — centers on the window the\n * user is actually looking at, not the primary screen), clamped to fit.\n */\nfunction centeredPopupFeatures(width: number, height: number): string {\n const baseLeft = window.screenX;\n const baseTop = window.screenY;\n const viewW = window.outerWidth || window.innerWidth || window.screen.width;\n const viewH =\n window.outerHeight || window.innerHeight || window.screen.height;\n\n const w = Math.min(width, viewW);\n const h = Math.min(height, viewH);\n const left = Math.max(0, Math.round(baseLeft + (viewW - w) / 2));\n const top = Math.max(0, Math.round(baseTop + (viewH - h) / 2));\n\n return `popup=yes,width=${Math.round(w)},height=${Math.round(h)},left=${left},top=${top}`;\n}\n\n/**\n * Run the \"Sign in with Remy\" handshake in a top-level popup — used when the\n * app is embedded (a cross-origin iframe like the dev IDE preview). The\n * authorize page hard-denies framing, so it can only render as a first-party\n * top-level document. The popup relays its one-time code back to this window\n * (the opener) via postMessage; the exchange runs *here* so this context — the\n * one that must end up authenticated — owns the session. The `ms_iface_` token\n * from the response body is the source of truth (held in memory), so it works\n * regardless of whether the third-party `__ms_auth` cookie survives.\n */\nfunction signInWithRemyPopup(\n redirectUri: string,\n state: string,\n): Promise<AppUser | null> {\n // Tag the callback so the SDK instance that loads in the popup relays +\n // closes rather than redeeming in place.\n const callbackUrl = new URL(redirectUri, window.location.href);\n callbackUrl.searchParams.set(REMY_POPUP_MARKER, '1');\n\n const startUrl = `/_/auth/remy/start?${new URLSearchParams({\n redirect_uri: callbackUrl.toString(),\n state,\n }).toString()}`;\n\n // MUST open synchronously on the user's click — awaiting first spends the\n // gesture and the browser blocks the popup.\n const popup = window.open(\n startUrl,\n 'ms-remy-signin',\n centeredPopupFeatures(REMY_POPUP_WIDTH, REMY_POPUP_HEIGHT),\n );\n if (!popup) {\n return Promise.reject(\n new MindStudioInterfaceError(\n 'The sign-in popup was blocked. Allow popups for this site and try again.',\n 'popup_blocked',\n ),\n );\n }\n\n // The opener is now completing a handshake — reflected by auth.authStatus\n // until the exchange settles (or the popup is cancelled/times out).\n beginAuthenticating();\n\n return new Promise<AppUser | null>((resolve, reject) => {\n const appOrigin = window.location.origin;\n let settled = false;\n // Set synchronously when the first valid message is accepted — guards\n // against a duplicate/replayed post starting a second exchange, and stops\n // a later popup.closed from reading as \"cancel\" mid-exchange.\n let accepted = false;\n\n const finish = (fn: () => void): void => {\n if (settled) {\n return;\n }\n settled = true;\n window.removeEventListener('message', onMessage);\n clearInterval(pollTimer);\n clearTimeout(timeoutId);\n // Clear the 'authenticating' state (no-op on success — the exchange\n // already cleared it before applySession; clears + notifies on\n // cancel/timeout/error so the app leaves the \"Completing sign-in…\" UI).\n endAuthenticating();\n try {\n if (!popup.closed) {\n popup.close();\n }\n } catch {\n // ignore — closing a cross-context window can throw in rare cases\n }\n fn();\n };\n\n const onMessage = (event: MessageEvent): void => {\n // Once we've committed to the first valid message, ignore everything\n // else — a duplicate/replayed post must never start a second redemption.\n if (settled || accepted) {\n return;\n }\n // Hygiene: our exact origin, the window we opened, our shape, our state.\n if (event.origin !== appOrigin || event.source !== popup) {\n return;\n }\n const data = event.data as {\n type?: string;\n code?: string;\n state?: string;\n } | null;\n if (!data || data.type !== REMY_POPUP_MESSAGE || data.state !== state) {\n return; // not ours / stale / cross-talk / possible CSRF\n }\n\n // Commit synchronously, before the exchange await: this is the single\n // redemption, and a later popup.closed must not read as \"cancel\".\n accepted = true;\n\n if (!data.code) {\n finish(() =>\n reject(\n new MindStudioInterfaceError(\n 'Sign-in returned no code.',\n 'auth_error',\n ),\n ),\n );\n return;\n }\n\n // Redeem in the opener: this is the context that must be authenticated.\n authFetch<AuthSessionBundle>('/_/auth/remy/exchange', 'POST', {\n code: data.code,\n })\n .then((bundle) => {\n // Clear the flag before applySession's notify so subscribers see\n // 'authenticated' directly — never a stray 'unauthenticated' frame.\n redirectPending = false;\n applySession(bundle);\n finish(() => resolve(requireUser(bundle)));\n })\n .catch((err: unknown) => finish(() => reject(err)));\n };\n\n window.addEventListener('message', onMessage);\n\n // User closed the popup before finishing → cancelled (unless we've already\n // accepted the callback message and are mid-exchange).\n const pollTimer = setInterval(() => {\n if (popup.closed && !accepted) {\n finish(() => resolve(null));\n }\n }, 400);\n\n // Backstop for a popup that never closes or posts (e.g. stuck on an error).\n const timeoutId = setTimeout(\n () =>\n finish(() =>\n reject(\n new MindStudioInterfaceError(\n 'Sign-in timed out.',\n 'signin_timeout',\n ),\n ),\n ),\n 5 * 60 * 1000,\n );\n });\n}\n\n/**\n * If this window is the throwaway \"Sign in with Remy\" popup, relay the\n * one-time code to the opener and let it close us; returns `true` when it\n * handled the callback. Safe and synchronous — invoked both at SDK import\n * (before telemetry installs, so the popup fires no phantom pageview/presence)\n * and as a branch of {@link Auth.handleRemyRedirect}.\n *\n * @internal Not part of the public API surface.\n */\nexport function maybeRelayRemyPopupCallback(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n let params: URLSearchParams;\n try {\n params = new URL(window.location.href).searchParams;\n } catch {\n return false;\n }\n if (params.get(REMY_POPUP_MARKER) !== '1') {\n return false;\n }\n const code = params.get('code');\n if (!code) {\n return false;\n }\n\n // Idempotent: forward the code once per popup document. Both the import-time\n // fast path and handleRemyRedirect() call this on load; without the guard an\n // app following the documented \"call handleRemyRedirect() on load\" runs a\n // second forward inside the popup and the opener redeems the one-time code\n // twice (the second 400s). Return `true` — it's still the \"handled\" path.\n if (remyPopupForwarded) {\n return true;\n }\n\n const opener = window.opener as Window | null;\n if (!opener || opener === window) {\n // No channel back to the app — e.g. COOP severed the opener. Redeeming\n // here would authenticate the throwaway popup, which the storage-\n // partitioned iframe can't use, so we deliberately do nothing.\n return false;\n }\n\n try {\n opener.postMessage(\n {\n type: REMY_POPUP_MESSAGE,\n code,\n state: params.get('state') ?? undefined,\n },\n window.location.origin,\n );\n } catch {\n return false;\n }\n\n remyPopupForwarded = true;\n\n // The opener closes us after the exchange; self-close on the next tick is a\n // backstop in case it isn't listening.\n try {\n setTimeout(() => window.close(), 0);\n } catch {\n // ignore\n }\n return true;\n}\n\n/**\n * The high-level authentication state, from {@link Auth.authStatus}.\n *\n * - `'authenticating'` — a \"Sign in with Remy\" handshake is completing right\n * now: a cold-load redirect return (top-level or dashboard launch) is being\n * redeemed, or an embedded popup exchange is in flight. Render a\n * \"Completing sign-in…\" state, not the logged-out UI.\n * - `'authenticated'` — a user is signed in ({@link Auth.currentUser} is set).\n * - `'unauthenticated'` — no session and nothing in progress.\n */\nexport type AuthStatus = 'authenticating' | 'authenticated' | 'unauthenticated';\n\n/**\n * Options for {@link Auth.signInWithRemy}.\n */\nexport interface SignInWithRemyOptions {\n /**\n * The same-origin URL the platform should return the browser to after\n * resolving access. Must be an origin the app actually serves on (the\n * backend validates it against the app's allow-list).\n *\n * @default `window.location.href` — the user lands back where they started.\n */\n redirectUri?: string;\n\n /**\n * CSRF token round-tripped through the redirect. Provide your own to\n * correlate the return with app state; otherwise an opaque random value\n * is generated. Validated on return by {@link Auth.handleRemyRedirect}\n * (top-level flow) or by the opener (embedded/popup flow).\n */\n state?: string;\n\n /**\n * How to run the handshake.\n *\n * - `'auto'` (default) — full-page redirect when the app is top-level; a\n * popup when it's embedded in a cross-origin iframe (the authorize page\n * can't be framed, so a redirect there would fail).\n * - `'popup'` — always use a popup.\n * - `'redirect'` — always use a full-page redirect.\n *\n * @default 'auto'\n */\n mode?: 'auto' | 'popup' | 'redirect';\n}\n\n// ---------------------------------------------------------------------------\n// Auth interface\n// ---------------------------------------------------------------------------\n\n/**\n * The auth namespace — authentication flows, state, and helpers.\n *\n * Auth methods throw {@link MindStudioInterfaceError} on failure.\n * Common error codes:\n *\n * | Code | Status | Meaning |\n * |------|--------|---------|\n * | `rate_limited` | 429 | Too many code requests (max 5 per 15 min per identifier) |\n * | `invalid_code` | 400 | Wrong verification code |\n * | `verification_expired` | 400 | Code expired (10 min TTL) — request a new one |\n * | `max_attempts_exceeded` | 400 | Too many incorrect attempts (max 3) — request a new code |\n * | `not_authenticated` | 401 | Auth cookie missing (change/logout endpoints) |\n * | `invalid_session` | 401 | Auth cookie expired or invalid |\n */\nexport interface Auth {\n // -- State --\n\n /** The current authenticated user, or `null` if not authenticated. */\n readonly currentUser: AppUser | null;\n\n /** Get the current authenticated user, or `null` if not authenticated. */\n getCurrentUser(): AppUser | null;\n\n /** Whether the current session is authenticated. */\n isAuthenticated(): boolean;\n\n /**\n * The high-level auth state — `'authenticating'`, `'authenticated'`, or\n * `'unauthenticated'`. Synchronous and safe to read in render.\n *\n * The key case is `'authenticating'`: it's `true` from **page load** through\n * settle whenever a \"Sign in with Remy\" handshake is completing (a cold-load\n * redirect return being redeemed, or an embedded popup exchange in flight).\n * Read this to show a \"Completing sign-in…\" state instead of the logged-out\n * UI — the `?code` URL param can't be used for that (it's stripped at the\n * start of the exchange), and the immediate `null` from\n * {@link onAuthStateChanged} during this window is expected.\n *\n * `onAuthStateChanged` fires on every transition, so re-read this there.\n */\n readonly authStatus: AuthStatus;\n\n /**\n * The stable per-browser, per-app visitor identifier, or `null` if\n * unavailable (e.g. cached bootstrap from before this field existed).\n *\n * Opaque string backed by a server-set HttpOnly cookie. For authed\n * sessions this is the user's platform user ID; for guests it's a\n * per-browser UUID. Persists ~1 year; refreshed on each page load.\n * Updates in-place on login/logout transitions.\n */\n readonly currentVisitorId: string | null;\n\n /** Get the current visitor ID, or `null` if unavailable. */\n getCurrentVisitorId(): string | null;\n\n // -- Email code flow --\n\n /** Send a 6-digit verification code to an email address. */\n sendEmailCode(email: string): Promise<{ verificationId: string }>;\n\n /**\n * Verify an email code. On success, updates the session in-place\n * and returns the authenticated user.\n */\n verifyEmailCode(verificationId: string, code: string): Promise<AppUser>;\n\n // -- SMS code flow --\n\n /** Send a 6-digit verification code via SMS. Phone must be E.164. */\n sendSmsCode(phone: string): Promise<{ verificationId: string }>;\n\n /**\n * Verify an SMS code. On success, updates the session in-place\n * and returns the authenticated user.\n */\n verifySmsCode(verificationId: string, code: string): Promise<AppUser>;\n\n // -- Sign in with Remy (platform-delegated) --\n\n /**\n * Start the \"Sign in with Remy\" flow. The platform — not the app — decides\n * who the person is and whether they're allowed (think \"Sign in with\n * Google\"). Auto-detects the context:\n *\n * - **Top-level** (production, standalone tab): redirects the browser to the\n * platform and bounces back to `redirectUri` with a one-time code that\n * {@link handleRemyRedirect} redeems on return. **The page navigates away**,\n * so the returned promise never settles.\n * - **Embedded** (cross-origin iframe, e.g. the dev IDE preview): opens a\n * top-level popup for the handshake (the authorize page can't be framed).\n * The returned promise resolves with the user on success, `null` if the\n * user closes the popup, or rejects (`popup_blocked` / `invalid_state` /\n * `signin_timeout` / exchange error). `onAuthStateChanged` also fires on\n * success.\n *\n * Must be called from a user gesture (e.g. a click) so the popup isn't\n * blocked. Override the auto-detection with `options.mode`.\n *\n * @example\n * ```ts\n * // \"Continue with {Org}\" button — works top-level or embedded:\n * <button onClick={() => auth.signInWithRemy()}>Continue with Acme</button>\n *\n * // Await the result in an embedded app:\n * const user = await auth.signInWithRemy();\n * if (user) navigate('/dashboard');\n * ```\n */\n signInWithRemy(options?: SignInWithRemyOptions): Promise<AppUser | null>;\n\n /**\n * Complete a \"Sign in with Remy\" redirect. Call once on app load — it's a\n * no-op (resolves `null`) when there's no code to redeem, so it's safe to\n * run on every mount.\n *\n * Handles both entry points:\n * - **SP-initiated** (returned from {@link signInWithRemy}) — validates the\n * round-tripped CSRF `state` against the value stashed at start.\n * - **IdP-initiated** (opened from the Remy dashboard) — a `?code=` lands on\n * first paint with no `state`; the single-use, app-bound code is the\n * control, so it's redeemed directly.\n *\n * Inside the embedded/popup document it's also a safe no-op: the popup boots\n * the same bundle, but the code is relayed to the opener exactly once\n * (idempotent — the import-time fast path already forwarded it), and this\n * call resolves `null` without redeeming. So the documented \"call on load\"\n * guidance holds everywhere — no need to special-case the popup.\n *\n * On success, updates the session in-place (fires `onAuthStateChanged`),\n * strips `code`/`state` from the URL, and returns the authenticated user.\n *\n * @returns The authenticated user, or `null` if there was no code to redeem.\n * @throws `invalid_state` if a returned `state` doesn't match the stashed one.\n *\n * @example\n * ```ts\n * useEffect(() => { auth.handleRemyRedirect(); }, []);\n * useEffect(() => auth.onAuthStateChanged(setUser), []);\n * ```\n */\n handleRemyRedirect(): Promise<AppUser | null>;\n\n // -- Email/phone change (requires authentication) --\n\n /** Request an email change. Sends a code to the new email. */\n requestEmailChange(newEmail: string): Promise<void>;\n\n /** Confirm an email change with the verification code. */\n confirmEmailChange(newEmail: string, code: string): Promise<AppUser>;\n\n /** Request a phone change. Sends a code to the new phone (E.164). */\n requestPhoneChange(newPhone: string): Promise<void>;\n\n /** Confirm a phone change with the verification code. */\n confirmPhoneChange(newPhone: string, code: string): Promise<AppUser>;\n\n // -- Session --\n\n /** Log out. Clears the cookie and updates the session to unauthenticated. */\n logout(): Promise<void>;\n\n // -- API keys --\n\n /**\n * Generate an API key for the current user. Returns the full key\n * (shown once). The user's `apiKey` field updates to the masked\n * value and `onAuthStateChanged` fires.\n *\n * @throws `not_authenticated` (401) if no session\n * @throws `not_supported` (400) if api-key auth is not enabled\n */\n createApiKey(): Promise<{ key: string }>;\n\n /**\n * Revoke the current user's API key. The user's `apiKey` field\n * becomes `null` and `onAuthStateChanged` fires.\n *\n * @throws `not_authenticated` (401) if no session\n */\n revokeApiKey(): Promise<void>;\n\n /**\n * Subscribe to auth state changes. Fires immediately with the current user,\n * then again on every transition — verify / confirm / logout, **and** each\n * `authStatus` change (a \"Sign in with Remy\" handshake starting or settling).\n *\n * During `'authenticating'` the callback may fire with `null` (no session\n * yet) — that's expected; read {@link authStatus} to tell it apart from a\n * logged-out visit rather than rendering the login UI.\n *\n * @returns An unsubscribe function.\n *\n * @example\n * ```ts\n * // React hook — track both the user and the status.\n * function useAuth() {\n * const [user, setUser] = useState(auth.currentUser);\n * const [status, setStatus] = useState(auth.authStatus);\n * useEffect(() => auth.onAuthStateChanged(() => {\n * setUser(auth.currentUser);\n * setStatus(auth.authStatus);\n * }), []);\n * return { user, status };\n * }\n * ```\n */\n onAuthStateChanged(callback: (user: AppUser | null) => void): () => void;\n\n // -- Helpers --\n\n /** Phone number utilities — countries, formatting, validation. */\n phone: {\n /** All countries with dial codes, sorted alphabetically. */\n countries: readonly phoneHelpers.Country[];\n /** Detect the user's country from their timezone. Falls back to `'US'`. */\n detectCountry(): string;\n /** Format an E.164 number for display (e.g. `+1 (555) 123-4567`). */\n format(e164: string): string;\n /** Convert a national number to E.164 (e.g. `('5551234567', 'US') → '+15551234567'`). */\n toE164(national: string, countryCode: string): string;\n /** Check if a string is a valid E.164 phone number. */\n isValid(phone: string): boolean;\n };\n\n /** Email validation. */\n email: {\n /** Basic email format check. */\n isValid(email: string): boolean;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport const auth: Auth = {\n // -- State --\n\n get currentUser() {\n return getConfig().user;\n },\n\n getCurrentUser() {\n return getConfig().user;\n },\n\n isAuthenticated() {\n return getConfig().user !== null;\n },\n\n get authStatus() {\n if (redirectPending) {\n return 'authenticating';\n }\n return safeCurrentUser() ? 'authenticated' : 'unauthenticated';\n },\n\n get currentVisitorId() {\n return getConfig().visitorId ?? null;\n },\n\n getCurrentVisitorId() {\n return getConfig().visitorId ?? null;\n },\n\n // -- Email code flow --\n\n sendEmailCode(email: string) {\n return authFetch<{ verificationId: string }>('/_/auth/email/send', 'POST', {\n email,\n });\n },\n\n async verifyEmailCode(verificationId: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/email/verify',\n 'POST',\n { verificationId, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n // -- SMS code flow --\n\n sendSmsCode(phone: string) {\n return authFetch<{ verificationId: string }>('/_/auth/sms/send', 'POST', {\n phone,\n });\n },\n\n async verifySmsCode(verificationId: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/sms/verify',\n 'POST',\n { verificationId, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n // -- Sign in with Remy --\n\n signInWithRemy(options?: SignInWithRemyOptions): Promise<AppUser | null> {\n const mode = options?.mode ?? 'auto';\n const usePopup = mode === 'popup' || (mode === 'auto' && isEmbedded());\n const redirectUri = options?.redirectUri ?? window.location.href;\n const state = options?.state ?? generateState();\n\n if (usePopup) {\n // window.open must happen synchronously on the gesture — do it first.\n return signInWithRemyPopup(redirectUri, state);\n }\n\n // Top-level full-page redirect. The opener/popup handshake isn't needed,\n // so the CSRF `state` is stashed for handleRemyRedirect() to validate.\n try {\n sessionStorage.setItem(REMY_STATE_KEY, state);\n } catch {\n // sessionStorage unavailable (private mode / disabled) — proceed\n // without the CSRF stash; the one-time, app-bound code still gates.\n }\n\n const params = new URLSearchParams({ redirect_uri: redirectUri, state });\n window.location.assign(`/_/auth/remy/start?${params.toString()}`);\n\n // The page is navigating away — nothing after this resolves.\n return new Promise<AppUser | null>(() => {});\n },\n\n async handleRemyRedirect() {\n if (typeof window === 'undefined') {\n return null;\n }\n\n const url = new URL(window.location.href);\n const code = url.searchParams.get('code');\n if (!code) {\n return null;\n }\n\n // Embedded/popup callback: relay the code to the opener and let it close\n // us — never redeem here (the opener owns the session). The import-time\n // fast path usually handles this already; this is the fallback for apps\n // that call handleRemyRedirect() directly.\n if (url.searchParams.get(REMY_POPUP_MARKER) === '1') {\n maybeRelayRemyPopupCallback();\n return null;\n }\n\n const returnedState = url.searchParams.get('state');\n\n // We're committed to consuming this redirect — strip the params up front\n // so a refresh never retries a spent (or failed) one-time code.\n stripRemyRedirectParams(url);\n\n let storedState: string | null = null;\n try {\n storedState = sessionStorage.getItem(REMY_STATE_KEY);\n sessionStorage.removeItem(REMY_STATE_KEY);\n } catch {\n // sessionStorage unavailable — CSRF check degrades to redeem-only.\n }\n\n // We're redeeming now — reflect it in auth.authStatus. Idempotent: module\n // load already set this for a cold-load return; this also covers callers\n // that reach here without the eager path having run.\n beginAuthenticating();\n\n // SP-initiated returns round-trip a `state` that must match what we\n // stashed. IdP-initiated launches carry no `state` — the single-use,\n // app-bound, short-lived code is the control there.\n if (returnedState !== null && returnedState !== storedState) {\n endAuthenticating();\n throw new MindStudioInterfaceError(\n 'Sign-in state mismatch — possible CSRF or a stale redirect. Please try again.',\n 'invalid_state',\n );\n }\n\n try {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/remy/exchange',\n 'POST',\n { code },\n );\n // Clear the flag before applySession's notify so subscribers transition\n // straight to 'authenticated' — no intermediate 'unauthenticated' frame.\n redirectPending = false;\n applySession(bundle);\n return requireUser(bundle);\n } catch (err) {\n // Clear + notify so the app leaves the \"Completing sign-in…\" state.\n endAuthenticating();\n throw err;\n }\n },\n\n // -- Email/phone change --\n\n async requestEmailChange(newEmail: string) {\n await authFetch('/_/auth/email/change', 'POST', { newEmail });\n },\n\n async confirmEmailChange(newEmail: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/email/change/confirm',\n 'POST',\n { newEmail, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n async requestPhoneChange(newPhone: string) {\n await authFetch('/_/auth/phone/change', 'POST', { newPhone });\n },\n\n async confirmPhoneChange(newPhone: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/phone/change/confirm',\n 'POST',\n { newPhone, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n // -- Session --\n\n async logout() {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/logout',\n 'POST',\n {},\n );\n applySession(bundle);\n },\n\n async createApiKey() {\n const result = await authFetch<{ key: string; apiKey: string }>(\n '/_/auth/api-key/create',\n 'POST',\n {},\n );\n updateUserAndNotify({ apiKey: result.apiKey });\n return { key: result.key };\n },\n\n async revokeApiKey() {\n await authFetch('/_/auth/api-key/revoke', 'POST', {});\n updateUserAndNotify({ apiKey: null });\n },\n\n onAuthStateChanged(callback: (user: AppUser | null) => void) {\n authListeners.add(callback);\n callback(safeCurrentUser());\n return () => {\n authListeners.delete(callback);\n };\n },\n\n // -- Helpers --\n\n phone: {\n countries: phoneHelpers.countries,\n detectCountry: phoneHelpers.detectCountry,\n format: phoneHelpers.format,\n toE164: phoneHelpers.toE164,\n isValid: phoneHelpers.isValid,\n },\n\n email: {\n isValid: emailHelpers.isValid,\n },\n};\n","/**\n * @mindstudio-ai/interface — Frontend SDK for MindStudio v2 app interfaces.\n *\n * Provides typed RPC to backend methods, file uploads, authentication,\n * and agent chat conversations. Runs inside web interfaces with\n * credentials injected by the platform via `window.__MINDSTUDIO__`.\n *\n * ## Four exports\n *\n * - `createClient()` — typed method RPC client\n * - `createAgentChatClient()` — thread-based agent conversations\n * - `platform` — file upload actions\n * - `auth` — authentication flows, user state, and validation helpers\n *\n * @example\n * ```ts\n * import {\n * createClient,\n * createAgentChatClient,\n * platform,\n * auth,\n * } from '@mindstudio-ai/interface';\n *\n * const api = createClient();\n * const chat = createAgentChatClient();\n *\n * // Check auth state\n * if (!auth.isAuthenticated()) {\n * const { verificationId } = await auth.sendEmailCode('user@example.com');\n * await auth.verifyEmailCode(verificationId, code);\n * }\n *\n * // Call backend methods (uses authenticated session)\n * const dashboard = await api.getDashboard();\n *\n * // Upload a file\n * const url = await platform.uploadFile(file);\n *\n * // Current user\n * const user = auth.getCurrentUser();\n * ```\n */\n\nexport { createClient, type InvokeOptions } from './client.js';\nexport {\n createAgentChatClient,\n type AgentChatClient,\n type AgentChatEvent,\n type SendMessageCallbacks,\n type SendMessageOptions,\n type SendMessageResult,\n type AbortablePromise,\n type Thread,\n type ThreadSummary,\n type ThreadListPage,\n type Message,\n} from './agent-chat.js';\nexport {\n platform,\n type UploadFileOptions,\n type UploadToken,\n} from './platform.js';\nexport {\n auth,\n type Auth,\n type AuthStatus,\n type SignInWithRemyOptions,\n} from './auth.js';\nexport {\n analytics,\n type AnalyticsClient,\n type CustomEventProps,\n} from './telemetry-analytics.js';\nexport { MindStudioInterfaceError } from './errors.js';\nexport type { BootstrapConfig, AppUser } from './types.js';\nexport type { Country } from './auth-phone.js';\nexport type { Breadcrumb } from './telemetry-breadcrumbs.js';\n\n// ---------------------------------------------------------------------------\n// Eager telemetry init\n//\n// When `window.__MINDSTUDIO__` is present at import time, trigger `getConfig()`\n// on the next tick so telemetry surfaces (errors, analytics, presence) install\n// at page load rather than on first SDK use. Without this, an app that creates\n// an `api` proxy at the module top but only invokes it from user interactions\n// would have no telemetry until the user clicks something.\n//\n// In environments without the bootstrap (SSR/Node, tests, dev without platform\n// context), the guard skips this and the SDK falls back to lazy init — first\n// SDK method call triggers `getConfig()` exactly as before.\n// ---------------------------------------------------------------------------\n\nimport { getConfig } from './config.js';\nimport { maybeRelayRemyPopupCallback } from './auth.js';\n\nif (typeof window !== 'undefined') {\n // If this window is the throwaway \"Sign in with Remy\" popup callback, relay\n // the code to the opener and close — synchronously, *before* installing\n // telemetry, so the popup never fires a phantom pageview / presence\n // connection. When it's not a popup callback this is a cheap no-op.\n const relayed = maybeRelayRemyPopupCallback();\n\n if (!relayed && (globalThis as Record<string, unknown>).__MINDSTUDIO__) {\n setTimeout(() => {\n try {\n getConfig();\n } catch {\n // bootstrap reads can still happen lazily on first SDK call\n }\n }, 0);\n }\n}\n"],"mappings":"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","names":["MindStudioInterfaceError","message","code","status","TELEMETRY_ENDPOINT_FRAGMENT","buffer","push","entry","getBreadcrumbs","shouldSkipUrl","url","TELEMETRY_ENDPOINT_FRAGMENT","resolveUrl","input","lastHref","navListeners","onNavigation","cb","emitNavigation","to","installHistory","w","originalPushState","data","unused","originalReplaceState","installFetch","captureResponseBodies","originalFetch","patched","init","method","startTime","response","body","err","installXhr","originalOpen","originalSend","rest","_breadcrumbsInstalled","installBreadcrumbs","opts","EVENTS_ENDPOINT","FLUSH_INTERVAL_MS","MAX_BATCH_SIZE","DEFAULT_RETRY_AFTER_MS","pending","flushTimer","suppressUntil","unloaded","enqueue","event","scheduleFlush","flush","takeBatch","batch","config","getConfig","events","res","retryAfterHeader","retryAfterSec","waitMs","drainOnUnload","sanitizeProps","input","out","k","v","emitPageview","url","track","props","PRESENCE_ENDPOINT","PRESENCE_MAX_BACKOFF_MS","PRESENCE_503_DEFAULT_MS","PRESENCE_STABLE_MS","presenceAbort","presenceReconnectTimer","presenceBackoffMs","presenceActive","presenceNextBackoff","base","jitter","schedulePresenceReconnect","ms","openPresenceConnection","closePresenceConnection","signal","connectedAt","reader","done","err","name","analytics","_installed","installAnalytics","handleUnload","onNavigation","maybeInstallAnalytics","ENDPOINT","FLUSH_INTERVAL_MS","MAX_BATCH_SIZE","DEFAULT_RETRY_AFTER_MS","_installed","pending","flushTimer","suppressUntil","unloaded","fingerprint","message","stack","firstStackLine","enqueue","event","fp","existing","scheduleFlush","flush","takeBatch","batch","_fp","rest","config","getConfig","events","res","retryAfterHeader","retryAfterSec","waitMs","drainOnUnload","captureError","getBreadcrumbs","captureRejection","reason","installErrorMonitoring","installBreadcrumbs","handleUnload","maybeInstallTelemetry","_config","getConfig","raw","MindStudioInterfaceError","missing","k","maybeInstallTelemetry","maybeInstallAnalytics","updateConfig","updates","config","createClient","_","methodName","input","options","config","getConfig","methodId","MindStudioInterfaceError","url","wantsStream","res","errorMessage","errorCode","body","reader","decoder","buffer","finalOutput","streamError","done","value","lines","line","json","event","AGENT_BASE","request","path","method","body","headers","getConfig","res","errorMessage","errorCode","err","MindStudioInterfaceError","dispatchEvent","event","callbacks","createAgentChatClient","cursor","query","threadId","title","content","options","controller","cb","abortable","config","url","reader","decoder","buffer","result","done","value","lines","line","json","platform","file","options","config","getConfig","onProgress","signal","presignRes","MindStudioInterfaceError","uploadUrl","uploadFields","publicUrl","formData","key","value","xhrUpload","uploadRes","token","res","url","resolve","reject","xhr","e","countries","byCode","c","timezoneToCountry","detectCountry","tz","format","e164","digits","area","prefix","line","len","candidate","match","rest","toE164","national","countryCode","country","dialDigits","isValid","phone","isValid","email","authFetch","path","method","body","headers","getConfig","res","message","code","err","MindStudioInterfaceError","REMY_STATE_KEY","REMY_POPUP_MARKER","REMY_POPUP_MESSAGE","remyPopupForwarded","redirectPending","detectColdLoadRedirect","params","generateState","c","bytes","b","stripRemyRedirectParams","url","authListeners","applySession","bundle","updateConfig","cb","requireUser","updateUserAndNotify","update","config","safeCurrentUser","notifyAuthListeners","user","beginAuthenticating","endAuthenticating","isEmbedded","REMY_POPUP_WIDTH","REMY_POPUP_HEIGHT","centeredPopupFeatures","width","height","baseLeft","baseTop","viewW","viewH","w","h","left","top","signInWithRemyPopup","redirectUri","state","callbackUrl","startUrl","popup","resolve","reject","appOrigin","settled","accepted","finish","fn","onMessage","pollTimer","timeoutId","event","data","maybeRelayRemyPopupCallback","opener","auth","email","verificationId","phone","options","mode","usePopup","returnedState","storedState","newEmail","newPhone","result","callback","countries","detectCountry","format","toE164","isValid","maybeRelayRemyPopupCallback","getConfig"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/agent-chat.ts","../src/platform.ts","../src/auth-phone.ts","../src/auth-email.ts","../src/auth.ts","../src/index.ts"],"sourcesContent":["/**\n * Method RPC client — typed function calls to backend methods.\n *\n * `createClient()` returns a Proxy where every property access creates\n * an async function that invokes the corresponding backend method via HTTP.\n * The method name matches the method's `export` field in `app.json`.\n *\n * ## How it works\n *\n * ```\n * api.submitVendorRequest({ name: 'Acme' })\n * │\n * ├─ Look up method ID: config.methods[\"submitVendorRequest\"]\n * │ → \"submit-vendor-request\"\n * │\n * ├─ POST /_/methods/submit-vendor-request/invoke\n * │ Authorization: Bearer {session token}\n * │ Body: { input: { name: \"Acme\" } }\n * │\n * └─ Returns response.output (or throws on error)\n * ```\n *\n * The method registry (export name → method ID mapping) is embedded in\n * `window.__MINDSTUDIO__.methods` at page load time — no extra HTTP call\n * needed to discover methods.\n *\n * ## Streaming\n *\n * Methods can optionally stream output via SSE. Pass `{ stream: true }` as\n * the second argument along with callbacks for the events you care about:\n *\n * ```ts\n * // LLM token text\n * const result = await api.submitVendorRequest({ name: 'Acme' }, {\n * stream: true,\n * onToken: (text) => setResponseText(text),\n * });\n *\n * // Structured progress events from `stream({...})` on the backend\n * const order = await api.processOrder({ id: 'abc' }, {\n * stream: true,\n * onStreamData: (event) => setStatus(event),\n * });\n * ```\n *\n * **Important:** The `text` value passed to `onToken` is the accumulated\n * response so far (not a delta). Replace your display content each time —\n * do not append. See {@link InvokeOptions.onToken} for details.\n *\n * Each `onStreamData` payload is independent (no accumulation) — it's the\n * raw object passed to `stream({...})` on the backend.\n *\n * ## Type safety\n *\n * Since the frontend and backend live in the same project, you can import\n * types directly:\n *\n * ```ts\n * import type { SubmitVendorInput, SubmitVendorOutput }\n * from '../../backend/src/submitVendorRequest';\n *\n * interface AppMethods {\n * submitVendorRequest(input: SubmitVendorInput): Promise<SubmitVendorOutput>;\n * getDashboard(): Promise<GetDashboardOutput>;\n * }\n *\n * const api = createClient<AppMethods>();\n * const result = await api.submitVendorRequest({ name: 'Acme' }); // fully typed\n * ```\n *\n * If no type parameter is provided, methods accept `any` input and return `any`.\n */\n\nimport { getConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\n\n/**\n * Options for method invocation. Pass as the optional second argument to\n * any method on the client returned by {@link createClient}.\n *\n * When `stream` is `true`, the SDK sends `{ stream: true }` in the request\n * body and parses the response as an SSE (Server-Sent Events) stream.\n * Four event types are handled:\n *\n * | SSE `type` | Payload field | SDK action |\n * |------------|---------------|------------|\n * | `token` | `text` | Calls {@link onToken} with accumulated text |\n * | `data` | `data` | Calls {@link onStreamData} with the raw payload |\n * | `error` | `error` | Calls {@link onStreamError} with the message |\n * | `done` | `output` | Captures as the method's return value |\n *\n * The method's `Promise` resolves with the final `output` from the `done`\n * event — the same shape you'd get without streaming.\n */\nexport interface InvokeOptions {\n /**\n * Enable SSE streaming for LLM token output.\n *\n * When `true`, the request body includes `{ stream: true }` and the\n * response is parsed as an SSE stream instead of a single JSON payload.\n * The method still returns a `Promise` that resolves with the final\n * output once the stream completes.\n *\n * @default false\n */\n stream?: boolean;\n\n /**\n * Called with the accumulated response text each time a new token arrives.\n *\n * **Important:** `text` is the full response so far, not a delta/chunk.\n * Your frontend should **replace** (not append to) its display content\n * with this value each time the callback fires.\n *\n * This design avoids edge cases where the platform sanitizes or\n * reformats JSON mid-stream, causing earlier portions of the text to\n * change between tokens.\n *\n * @example\n * ```ts\n * const result = await api.generate({ prompt: 'Hello' }, {\n * stream: true,\n * onToken: (text) => {\n * // Replace — do NOT append\n * responseElement.textContent = text;\n * },\n * });\n * ```\n */\n onToken?: (text: string) => void;\n\n /**\n * Called when the backend method emits a structured event via\n * `stream({...})` (object payload).\n *\n * Receives the raw object passed to `stream()` on the backend —\n * not accumulated, not parsed, not wrapped. Use this for status\n * updates, progress, intermediate results, or any structured\n * streaming UI that isn't LLM token text.\n *\n * Different from {@link onToken}, which receives accumulated text from\n * `stream('text')` calls.\n *\n * @example\n * ```ts\n * await api.processOrder({ id: 'abc' }, {\n * stream: true,\n * onStreamData: (event) => {\n * // event is whatever the backend passed to stream({...})\n * setStatus(event as { status: string; progress: number });\n * },\n * });\n * ```\n */\n onStreamData?: (payload: unknown) => void;\n\n /**\n * Called if a stream-level error event arrives.\n *\n * If the stream then closes without a `done` event, the method rejects\n * with a `MindStudioInterfaceError` containing the error message (code\n * `method_error`). If a `done` event does arrive after the error, the\n * method resolves with the final output — in that case this callback\n * is just for logging or showing a transient warning.\n *\n * If the stream ends without either a `done` or `error` event, the\n * method rejects with `stream_incomplete`.\n */\n onStreamError?: (error: string) => void;\n}\n\n/**\n * Default client type when no generic parameter is provided.\n * Every property returns an async function accepting optional input\n * and optional {@link InvokeOptions} for streaming.\n */\ntype DefaultMethodClient = Record<\n string,\n (input?: Record<string, unknown>, options?: InvokeOptions) => Promise<unknown>\n>;\n\n/**\n * Create a typed RPC client for calling backend methods.\n *\n * @typeParam T - Optional interface mapping method names to their signatures.\n * If omitted, all methods accept `any` and return `Promise<any>`.\n *\n * @returns A Proxy object where each property is an async function that\n * invokes the corresponding backend method.\n *\n * @example\n * ```ts\n * import { createClient } from '@mindstudio-ai/interface';\n *\n * const api = createClient();\n *\n * // Each method maps to a backend method export\n * const result = await api.submitVendorRequest({ name: 'Acme' });\n * const dashboard = await api.getDashboard();\n *\n * // Stream LLM output — onToken receives accumulated text (replace, don't append)\n * const streamed = await api.submitVendorRequest({ name: 'Acme' }, {\n * stream: true,\n * onToken: (text) => setResponseText(text),\n * });\n * // `streamed` is the same final output you'd get without streaming\n * ```\n */\nexport function createClient<T = DefaultMethodClient>(): T {\n return new Proxy({} as Record<string, unknown> as T & object, {\n get(_, methodName: string) {\n return async (input?: unknown, options?: InvokeOptions) => {\n const config = getConfig();\n\n const methodId = config.methods[methodName];\n if (!methodId) {\n throw new MindStudioInterfaceError(\n `Method \"${methodName}\" not found. Available methods: ${Object.keys(config.methods).join(', ') || '(none)'}`,\n 'method_not_found',\n );\n }\n\n const url = `/_/methods/${methodId}/invoke`;\n const wantsStream = options?.stream === true;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({\n input: input ?? {},\n ...(wantsStream && { stream: true }),\n }),\n });\n\n if (!res.ok) {\n let errorMessage = `Method \"${methodName}\" failed: ${res.status} ${res.statusText}`;\n let errorCode = 'method_error';\n try {\n const body = (await res.json()) as {\n error?: string;\n code?: string;\n };\n if (body.error) {\n errorMessage = body.error;\n }\n if (body.code) {\n errorCode = body.code;\n }\n } catch {\n // Response wasn't JSON — use the default message\n }\n throw new MindStudioInterfaceError(\n errorMessage,\n errorCode,\n res.status,\n );\n }\n\n // Non-streaming: unchanged behavior\n if (!wantsStream) {\n const body = (await res.json()) as { output?: unknown };\n return body.output;\n }\n\n // Streaming: parse SSE events from response body\n const reader = res.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let finalOutput: unknown;\n let streamError: string | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) {\n continue;\n }\n const json = line.slice(6);\n try {\n const event = JSON.parse(json) as {\n type: string;\n text?: string;\n error?: string;\n output?: unknown;\n data?: unknown;\n };\n\n if (event.type === 'token' && options?.onToken && event.text) {\n options.onToken(event.text);\n } else if (event.type === 'data' && options?.onStreamData) {\n options.onStreamData(event.data);\n } else if (event.type === 'error' && event.error) {\n streamError = event.error;\n if (options?.onStreamError) {\n options.onStreamError(event.error);\n }\n } else if (event.type === 'done') {\n finalOutput = event.output;\n }\n } catch {\n // Skip malformed SSE lines\n }\n }\n }\n\n // If the stream errored and didn't produce a final output,\n // surface the error message from the error event.\n if (finalOutput === undefined && streamError !== undefined) {\n throw new MindStudioInterfaceError(streamError, 'method_error');\n }\n\n if (finalOutput === undefined) {\n throw new MindStudioInterfaceError(\n 'Stream ended without a done event',\n 'stream_incomplete',\n );\n }\n\n return finalOutput;\n };\n },\n });\n}\n","/**\n * Agent chat client — thread-based conversations with AI agents.\n *\n * `createAgentChatClient()` returns an object with methods for thread\n * lifecycle (create, list, get, rename, delete) and message streaming.\n * The agent runs server-side with access to the app's methods as tools.\n *\n * ## How it works\n *\n * ```\n * chat.sendMessage(threadId, 'Help me prep for my review', {\n * onText: (delta) => setAssistantMessage((prev) => prev + delta),\n * })\n * │\n * ├─ POST /_/agent/threads/{threadId}/messages\n * │ Authorization: Bearer {session token}\n * │ Body: { content: \"Help me prep for my review\" }\n * │\n * ├─ Response: SSE stream\n * │ data: {\"type\":\"text\",\"text\":\"I'll create...\",\"ts\":1711...}\n * │ data: {\"type\":\"tool_call_start\",\"id\":\"call_abc\",\"name\":\"create-todo\",\"ts\":1711...}\n * │ data: {\"type\":\"tool_call_result\",\"id\":\"call_abc\",\"output\":{...},\"ts\":1711...}\n * │ data: {\"type\":\"text\",\"text\":\"Done — added it.\",\"ts\":1711...}\n * │ data: {\"type\":\"done\",\"stopReason\":\"end_turn\",\"usage\":{...},\"ts\":1711...}\n * │\n * └─ Promise resolves with { stopReason, usage }\n * ```\n *\n * ## Stateless design\n *\n * This client is a thin wrapper over HTTP endpoints. It does not manage\n * local state — your React/framework layer owns the thread list, message\n * array, and UI state. Each method is an independent request.\n *\n * ## Streaming callbacks\n *\n * `sendMessage` accepts named callbacks for common events (onText,\n * onThinking, onToolCallStart, etc.) plus a catch-all `onEvent` that\n * receives every SSE event as a discriminated union. Both fire for the\n * same event — use named callbacks for convenience, `onEvent` for\n * logging or handling low-level events like `tool_use` and\n * `tool_input_delta`.\n *\n * @example\n * ```ts\n * import { createAgentChatClient } from '@mindstudio-ai/interface';\n *\n * const chat = createAgentChatClient();\n *\n * // Thread lifecycle\n * const thread = await chat.createThread();\n * const { threads } = await chat.listThreads();\n * const full = await chat.getThread(thread.id);\n *\n * // Send a message with streaming\n * const response = chat.sendMessage(thread.id, 'Hello!', {\n * onText: (delta) => setMessage((prev) => prev + delta),\n * onToolCallStart: (id, name) => showToolSpinner(name),\n * onToolCallResult: (id, output) => showToolResult(output),\n * });\n *\n * const { stopReason, usage } = await response;\n *\n * // Abort mid-stream\n * response.abort();\n * ```\n */\n\nimport { getConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Thread types\n// ---------------------------------------------------------------------------\n\n/** Thread summary returned from create and list endpoints. */\nexport interface ThreadSummary {\n /** Thread ID (UUID). */\n id: string;\n /** User-editable title, auto-generated on first message. */\n title: string | null;\n /** ISO 8601 creation timestamp. */\n dateCreated: string;\n /** ISO 8601 last-updated timestamp. */\n dateUpdated: string;\n}\n\n/** Full thread with message history, returned from get endpoint. */\nexport interface Thread {\n /** Thread ID (UUID). */\n id: string;\n /** User-editable title. */\n title: string | null;\n /** Full conversation history. */\n messages: Message[];\n /** ISO 8601 creation timestamp. */\n dateCreated: string;\n /** ISO 8601 last-updated timestamp. */\n dateUpdated: string;\n}\n\n/** A tool call requested by the assistant. */\nexport interface ToolCall {\n /** Tool call ID, used to match results. */\n id: string;\n /** Method name. */\n name: string;\n /** Input arguments. */\n input: Record<string, unknown>;\n}\n\n/** A single message in a thread's conversation history. */\nexport interface Message {\n /** `\"user\"` for human messages, `\"assistant\"` for agent responses. */\n role: 'user' | 'assistant';\n /** Message text content. For tool results, this is JSON-stringified output. */\n content: string;\n /**\n * MindStudio CDN URLs attached to this message (user messages only).\n *\n * Images (`i.mscdn.ai`) are sent to the model as vision input.\n * Documents (`f.mscdn.ai`, `files.mindstudio-cdn.com`) have their text\n * extracted server-side and included in the model context. The original\n * URLs are preserved in thread history for the frontend to render.\n */\n attachments?: string[];\n /** Tool calls the assistant requested (assistant messages only). */\n toolCalls?: ToolCall[];\n /** ID of the tool call this message is a result for (tool result messages only). */\n toolCallId?: string;\n /** Whether this tool result represents an error. */\n isToolError?: boolean;\n}\n\n/** Paginated thread list response. */\nexport interface ThreadListPage {\n /** Threads for this page. */\n threads: ThreadSummary[];\n /** Cursor for the next page, or `null` if this is the last page. */\n nextCursor: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// SSE event types\n// ---------------------------------------------------------------------------\n\n/** Assistant text delta — append to your display string, do not replace. */\nexport interface AgentTextEvent {\n type: 'text';\n text: string;\n ts: number;\n}\n\n/** Model thinking text (extended thinking). */\nexport interface AgentThinkingEvent {\n type: 'thinking';\n text: string;\n ts: number;\n}\n\n/** Thinking complete with full text and signature. */\nexport interface AgentThinkingCompleteEvent {\n type: 'thinking_complete';\n thinking: string;\n signature: string;\n ts: number;\n}\n\n/** Model requested a tool call (includes parsed input). */\nexport interface AgentToolUseEvent {\n type: 'tool_use';\n id: string;\n name: string;\n input: Record<string, unknown>;\n ts: number;\n}\n\n/** Streaming delta for tool call input JSON. */\nexport interface AgentToolInputDeltaEvent {\n type: 'tool_input_delta';\n id: string;\n name: string;\n delta: string;\n ts: number;\n}\n\n/** Agent began executing a tool call. */\nexport interface AgentToolCallStartEvent {\n type: 'tool_call_start';\n id: string;\n name: string;\n ts: number;\n}\n\n/** Tool call returned a result. */\nexport interface AgentToolCallResultEvent {\n type: 'tool_call_result';\n id: string;\n output: unknown;\n ts: number;\n}\n\n/** Stream complete. */\nexport interface AgentDoneEvent {\n type: 'done';\n stopReason: string;\n usage: { inputTokens: number; outputTokens: number };\n ts: number;\n}\n\n/** Stream-level error. */\nexport interface AgentErrorEvent {\n type: 'error';\n error: string;\n ts: number;\n}\n\n/** Discriminated union of all SSE events from the agent chat stream. */\nexport type AgentChatEvent =\n | AgentTextEvent\n | AgentThinkingEvent\n | AgentThinkingCompleteEvent\n | AgentToolUseEvent\n | AgentToolInputDeltaEvent\n | AgentToolCallStartEvent\n | AgentToolCallResultEvent\n | AgentDoneEvent\n | AgentErrorEvent;\n\n// ---------------------------------------------------------------------------\n// Callbacks and result types\n// ---------------------------------------------------------------------------\n\n/**\n * Callbacks for `sendMessage` streaming.\n *\n * Named callbacks fire for common events. The catch-all `onEvent` fires\n * for every event including those with named callbacks — use it for\n * logging or handling low-level events like `tool_use` and\n * `tool_input_delta`.\n */\nexport interface SendMessageCallbacks {\n /**\n * Called with each text delta as the assistant responds.\n *\n * **Important:** `text` is a delta (new chunk), not the accumulated\n * response. Append it to your display string — do not replace.\n *\n * @example\n * ```ts\n * onText: (delta) => setMessage((prev) => prev + delta)\n * ```\n */\n onText?: (text: string) => void;\n\n /** Called with model thinking text (extended thinking). */\n onThinking?: (text: string) => void;\n\n /** Called when thinking is complete. */\n onThinkingComplete?: (thinking: string, signature: string) => void;\n\n /** Called when a tool call begins executing. */\n onToolCallStart?: (id: string, name: string) => void;\n\n /** Called when a tool call produces a result. */\n onToolCallResult?: (id: string, output: unknown) => void;\n\n /** Called on a stream-level error event. */\n onError?: (error: string) => void;\n\n /** Called for every SSE event (including ones with named callbacks). */\n onEvent?: (event: AgentChatEvent) => void;\n\n /** AbortSignal to cancel the stream. */\n signal?: AbortSignal;\n}\n\n/** Options for `sendMessage`. */\nexport interface SendMessageOptions {\n /**\n * MindStudio CDN URLs to attach to the message.\n *\n * Images are sent to the model as vision input (one per message).\n * Documents have their text extracted server-side and included in context.\n *\n * Upload files first via {@link platform.upload} (with a backend-minted\n * upload token) and pass the returned URLs.\n *\n * @example\n * ```ts\n * const token = await api.getUploadSlot({ contentType: file.type });\n * const { url } = await platform.upload(token, file);\n * chat.sendMessage(threadId, 'What is this?', callbacks, {\n * attachments: [url],\n * });\n * ```\n */\n attachments?: string[];\n}\n\n/** Resolved value of `sendMessage` — extracted from the `done` event. */\nexport interface SendMessageResult {\n /** Why the agent stopped (`\"end_turn\"`, `\"max_tokens\"`, etc.). */\n stopReason: string;\n /** Token usage for the full agent loop. */\n usage: { inputTokens: number; outputTokens: number };\n}\n\n/** A Promise with an `abort()` method for cancelling the stream. */\nexport type AbortablePromise<T> = Promise<T> & {\n /** Cancel the in-flight stream. The promise rejects with `AbortError`. */\n abort: () => void;\n};\n\n// ---------------------------------------------------------------------------\n// Client interface\n// ---------------------------------------------------------------------------\n\n/** Agent chat client returned by {@link createAgentChatClient}. */\nexport interface AgentChatClient {\n /** Create a new conversation thread. */\n createThread(): Promise<ThreadSummary>;\n\n /** List threads with cursor-based pagination (50 per page). */\n listThreads(cursor?: string): Promise<ThreadListPage>;\n\n /** Get a thread with its full message history. */\n getThread(threadId: string): Promise<Thread>;\n\n /** Update a thread's title. */\n updateThread(threadId: string, title: string): Promise<void>;\n\n /** Delete a thread (soft delete). */\n deleteThread(threadId: string): Promise<void>;\n\n /**\n * Send a message and stream the agent's response.\n *\n * Returns an {@link AbortablePromise} that resolves with\n * `{ stopReason, usage }` when the stream completes. Call `.abort()`\n * on the returned promise to cancel mid-stream.\n *\n * @param options.attachments - URLs to attach.\n * Images are sent as vision input.\n * Documents have text extracted into context.\n * Upload files first via {@link platform.upload} and pass the returned URLs.\n */\n sendMessage(\n threadId: string,\n content: string,\n callbacks?: SendMessageCallbacks,\n options?: SendMessageOptions,\n ): AbortablePromise<SendMessageResult>;\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nconst AGENT_BASE = '/_/agent';\n\nasync function request<T>(\n path: string,\n method: string,\n body?: unknown,\n): Promise<T> {\n const config = getConfig();\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n };\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const res = await fetch(`${AGENT_BASE}${path}`, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n });\n\n if (!res.ok) {\n let errorMessage = `Agent chat request failed: ${res.status} ${res.statusText}`;\n let errorCode = 'agent_chat_error';\n try {\n const err = (await res.json()) as { error?: string; code?: string };\n if (err.error) {\n errorMessage = err.error;\n }\n if (err.code) {\n errorCode = err.code;\n }\n } catch {\n // Response wasn't JSON — use the default message\n }\n throw new MindStudioInterfaceError(errorMessage, errorCode, res.status);\n }\n\n return (await res.json()) as T;\n}\n\nfunction dispatchEvent(\n event: AgentChatEvent,\n callbacks: SendMessageCallbacks,\n): void {\n // Catch-all fires for every event\n callbacks.onEvent?.(event);\n\n // Named callbacks\n switch (event.type) {\n case 'text':\n callbacks.onText?.(event.text);\n break;\n case 'thinking':\n callbacks.onThinking?.(event.text);\n break;\n case 'thinking_complete':\n callbacks.onThinkingComplete?.(event.thinking, event.signature);\n break;\n case 'tool_call_start':\n callbacks.onToolCallStart?.(event.id, event.name);\n break;\n case 'tool_call_result':\n callbacks.onToolCallResult?.(event.id, event.output);\n break;\n case 'error':\n callbacks.onError?.(event.error);\n break;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an agent chat client for thread-based conversations.\n *\n * The client is stateless — each method is an independent HTTP request.\n * Your application layer owns thread state, message arrays, and UI updates.\n *\n * @returns An {@link AgentChatClient} with methods for thread CRUD and\n * message streaming.\n *\n * @example\n * ```ts\n * import { createAgentChatClient } from '@mindstudio-ai/interface';\n *\n * const chat = createAgentChatClient();\n *\n * const thread = await chat.createThread();\n *\n * const response = chat.sendMessage(thread.id, 'Hello!', {\n * onText: (delta) => setAssistantMessage((prev) => prev + delta),\n * onToolCallStart: (id, name) => showToolSpinner(name),\n * onToolCallResult: (id, output) => showToolResult(output),\n * });\n *\n * const { stopReason, usage } = await response;\n * ```\n */\nexport function createAgentChatClient(): AgentChatClient {\n return {\n createThread() {\n return request<ThreadSummary>('/threads', 'POST');\n },\n\n listThreads(cursor?: string) {\n const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';\n return request<ThreadListPage>(`/threads${query}`, 'GET');\n },\n\n getThread(threadId: string) {\n return request<Thread>(`/threads/${threadId}`, 'GET');\n },\n\n async updateThread(threadId: string, title: string) {\n await request(`/threads/${threadId}`, 'PATCH', { title });\n },\n\n async deleteThread(threadId: string) {\n await request(`/threads/${threadId}`, 'DELETE');\n },\n\n sendMessage(\n threadId: string,\n content: string,\n callbacks?: SendMessageCallbacks,\n options?: SendMessageOptions,\n ): AbortablePromise<SendMessageResult> {\n const controller = new AbortController();\n const cb = callbacks ?? {};\n\n // Wire caller's signal to our internal controller\n if (cb.signal) {\n if (cb.signal.aborted) {\n controller.abort();\n } else {\n cb.signal.addEventListener('abort', () => controller.abort(), {\n once: true,\n });\n }\n }\n\n const promise = (async (): Promise<SendMessageResult> => {\n const config = getConfig();\n const url = `${AGENT_BASE}/threads/${threadId}/messages`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.token}`,\n },\n body: JSON.stringify({\n content,\n ...(options?.attachments?.length && {\n attachments: options.attachments,\n }),\n }),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n let errorMessage = `Send message failed: ${res.status} ${res.statusText}`;\n let errorCode = 'agent_chat_error';\n try {\n const err = (await res.json()) as {\n error?: string;\n code?: string;\n };\n if (err.error) {\n errorMessage = err.error;\n }\n if (err.code) {\n errorCode = err.code;\n }\n } catch {\n // Response wasn't JSON — use the default message\n }\n throw new MindStudioInterfaceError(\n errorMessage,\n errorCode,\n res.status,\n );\n }\n\n const reader = res.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let result: SendMessageResult | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) {\n continue;\n }\n const json = line.slice(6);\n try {\n const event = JSON.parse(json) as AgentChatEvent;\n\n if (event.type === 'done') {\n result = {\n stopReason: event.stopReason,\n usage: event.usage,\n };\n }\n\n dispatchEvent(event, cb);\n } catch {\n // Skip malformed SSE lines\n }\n }\n }\n\n if (!result) {\n throw new MindStudioInterfaceError(\n 'Stream ended without a done event',\n 'stream_incomplete',\n );\n }\n\n return result;\n })();\n\n const abortable = promise as AbortablePromise<SendMessageResult>;\n abortable.abort = () => controller.abort();\n return abortable;\n },\n };\n}\n","/**\n * Platform actions — client-direct file uploads.\n *\n * The app's backend mints an upload token (`Store.createUploadToken` in\n * `@mindstudio-ai/agent`); the browser then uploads the bytes straight to\n * storage with {@link platform.upload}. The file lands in the app's own file\n * store (private by default, served on the app's domain).\n *\n * @example\n * ```ts\n * import { createClient, platform } from '@mindstudio-ai/interface';\n *\n * const api = createClient();\n * const file = document.querySelector('input[type=file]').files[0];\n * const token = await api.getUploadSlot({ contentType: file.type });\n * const { key, url } = await platform.upload(token, file, {\n * onProgress: (fraction) => console.log(`${Math.round(fraction * 100)}%`),\n * });\n * ```\n */\n\nimport { getConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\n\n/**\n * Options for `platform.uploadFile()`.\n */\nexport interface UploadFileOptions {\n /** Called with upload progress as a fraction from 0 to 1. */\n onProgress?: (fraction: number) => void;\n\n /** AbortSignal to cancel the upload. */\n signal?: AbortSignal;\n}\n\n/**\n * An upload token minted by an app's backend (`Store.createUploadToken` in\n * `@mindstudio-ai/agent`) and passed to {@link platform.upload}. Opaque — carry\n * it from the backend response straight into `platform.upload(token, file)`.\n */\nexport interface UploadToken {\n /** The object key the upload lands at. */\n key: string;\n /** The URL the file will be readable at once uploaded. */\n url: string;\n /** The scoped presigned POST the browser submits to. */\n upload: { url: string; fields: Record<string, string> };\n}\n\n/**\n * The platform namespace — file upload actions.\n */\nexport const platform = {\n /**\n * Upload a file to the MindStudio CDN.\n *\n * Requests a presigned upload URL from the platform, then uploads\n * the file directly to S3. Returns the public CDN URL.\n *\n * @deprecated Use {@link platform.upload} with a backend-minted upload token\n * (`Store.createUploadToken`) instead. That uploads into the app's own file\n * store (private by default, on the app's domain); this legacy path uploads\n * to the shared, world-readable account media CDN.\n *\n * @param file - The File to upload\n * @param options - Optional progress callback and abort signal\n * @returns CDN URL of the uploaded file\n *\n * @example\n * ```ts\n * const url = await platform.uploadFile(file);\n * ```\n */\n async uploadFile(file: File, options?: UploadFileOptions): Promise<string> {\n const config = getConfig();\n const { onProgress, signal } = options ?? {};\n\n // Check if already aborted\n signal?.throwIfAborted();\n\n // Step 1: Get presigned upload URL\n const presignUrl = '/_/generate-upload-request';\n const presignRes = await fetch(presignUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${config.token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n filename: file.name,\n contentType: file.type || 'application/octet-stream',\n }),\n signal,\n });\n\n if (!presignRes.ok) {\n throw new MindStudioInterfaceError(\n `Upload presign failed: ${presignRes.status} ${presignRes.statusText}`,\n 'upload_error',\n presignRes.status,\n );\n }\n\n const { uploadUrl, uploadFields, publicUrl } =\n (await presignRes.json()) as {\n uploadUrl: string;\n uploadFields: Record<string, string>;\n publicUrl: string;\n };\n\n // Step 2: Upload directly to S3\n const formData = new FormData();\n for (const [key, value] of Object.entries(uploadFields)) {\n formData.append(key, value);\n }\n formData.append('file', file); // must be last\n\n // Use XHR for upload progress (fetch doesn't support it)\n if (onProgress) {\n await xhrUpload(uploadUrl, formData, onProgress, signal);\n return publicUrl;\n }\n\n const uploadRes = await fetch(uploadUrl, {\n method: 'POST',\n body: formData,\n signal,\n });\n\n if (!uploadRes.ok) {\n throw new MindStudioInterfaceError(\n `File upload failed: ${uploadRes.status} ${uploadRes.statusText}`,\n 'upload_error',\n uploadRes.status,\n );\n }\n\n return publicUrl;\n },\n\n /**\n * Upload a file with an {@link UploadToken} minted by the app's backend\n * (`Store.createUploadToken`). The file is POSTed **directly to storage** —\n * the bytes never pass through the platform — and the returned `{ key, url }`\n * is ready to render (`url` is a stable on-domain link the app can use in\n * `<img>` / `fetch` / `<a download>`).\n *\n * @example\n * ```ts\n * const token = await api.getUploadSlot({ contentType: file.type });\n * const { url } = await platform.upload(token, file, {\n * onProgress: (f) => setProgress(f),\n * });\n * ```\n */\n async upload(\n token: UploadToken,\n file: File,\n options?: UploadFileOptions,\n ): Promise<{ key: string; url: string }> {\n const { onProgress, signal } = options ?? {};\n signal?.throwIfAborted();\n\n const formData = new FormData();\n for (const [key, value] of Object.entries(token.upload.fields)) {\n formData.append(key, value);\n }\n formData.append('file', file); // must be last\n\n if (onProgress) {\n await xhrUpload(token.upload.url, formData, onProgress, signal);\n } else {\n const res = await fetch(token.upload.url, {\n method: 'POST',\n body: formData,\n signal,\n });\n if (!res.ok) {\n throw new MindStudioInterfaceError(\n `File upload failed: ${res.status} ${res.statusText}`,\n 'upload_error',\n res.status,\n );\n }\n }\n\n return { key: token.key, url: token.url };\n },\n};\n\n/**\n * Upload via XMLHttpRequest to get progress events.\n * Falls back from fetch because fetch upload streams don't support progress.\n */\nfunction xhrUpload(\n url: string,\n formData: FormData,\n onProgress: (fraction: number) => void,\n signal?: AbortSignal,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.addEventListener('progress', (e) => {\n if (e.lengthComputable) {\n onProgress(e.loaded / e.total);\n }\n });\n\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n onProgress(1);\n resolve();\n } else {\n reject(\n new MindStudioInterfaceError(\n `File upload failed: ${xhr.status} ${xhr.statusText}`,\n 'upload_error',\n xhr.status,\n ),\n );\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(\n new MindStudioInterfaceError(\n 'File upload failed: network error',\n 'upload_error',\n ),\n );\n });\n\n xhr.addEventListener('abort', () => {\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n\n // Wire up abort signal\n if (signal) {\n if (signal.aborted) {\n reject(new DOMException('Upload aborted', 'AbortError'));\n return;\n }\n signal.addEventListener('abort', () => xhr.abort(), { once: true });\n }\n\n xhr.open('POST', url);\n xhr.send(formData);\n });\n}\n","/**\n * Phone number utilities for auth flows.\n *\n * Provides country data, E.164 conversion, formatting, and validation.\n * No external dependencies — designed for login/signup UIs that need\n * a country code picker and phone number formatting.\n */\n\n// ---------------------------------------------------------------------------\n// Country data\n// ---------------------------------------------------------------------------\n\n/** A country with its dial code and flag emoji. */\nexport interface Country {\n /** ISO 3166-1 alpha-2 code (e.g. \"US\"). */\n code: string;\n /** International dial code with + prefix (e.g. \"+1\"). */\n dialCode: string;\n /** Country name in English. */\n name: string;\n /** Flag emoji. */\n flag: string;\n}\n\n/** All countries with dial codes, sorted alphabetically by name. */\nexport const countries: readonly Country[] = [\n { code: 'AF', dialCode: '+93', name: 'Afghanistan', flag: '🇦🇫' },\n { code: 'AL', dialCode: '+355', name: 'Albania', flag: '🇦🇱' },\n { code: 'DZ', dialCode: '+213', name: 'Algeria', flag: '🇩🇿' },\n { code: 'AD', dialCode: '+376', name: 'Andorra', flag: '🇦🇩' },\n { code: 'AO', dialCode: '+244', name: 'Angola', flag: '🇦🇴' },\n { code: 'AG', dialCode: '+1', name: 'Antigua and Barbuda', flag: '🇦🇬' },\n { code: 'AR', dialCode: '+54', name: 'Argentina', flag: '🇦🇷' },\n { code: 'AM', dialCode: '+374', name: 'Armenia', flag: '🇦🇲' },\n { code: 'AU', dialCode: '+61', name: 'Australia', flag: '🇦🇺' },\n { code: 'AT', dialCode: '+43', name: 'Austria', flag: '🇦🇹' },\n { code: 'AZ', dialCode: '+994', name: 'Azerbaijan', flag: '🇦🇿' },\n { code: 'BS', dialCode: '+1', name: 'Bahamas', flag: '🇧🇸' },\n { code: 'BH', dialCode: '+973', name: 'Bahrain', flag: '🇧🇭' },\n { code: 'BD', dialCode: '+880', name: 'Bangladesh', flag: '🇧🇩' },\n { code: 'BB', dialCode: '+1', name: 'Barbados', flag: '🇧🇧' },\n { code: 'BY', dialCode: '+375', name: 'Belarus', flag: '🇧🇾' },\n { code: 'BE', dialCode: '+32', name: 'Belgium', flag: '🇧🇪' },\n { code: 'BZ', dialCode: '+501', name: 'Belize', flag: '🇧🇿' },\n { code: 'BJ', dialCode: '+229', name: 'Benin', flag: '🇧🇯' },\n { code: 'BT', dialCode: '+975', name: 'Bhutan', flag: '🇧🇹' },\n { code: 'BO', dialCode: '+591', name: 'Bolivia', flag: '🇧🇴' },\n { code: 'BA', dialCode: '+387', name: 'Bosnia and Herzegovina', flag: '🇧🇦' },\n { code: 'BW', dialCode: '+267', name: 'Botswana', flag: '🇧🇼' },\n { code: 'BR', dialCode: '+55', name: 'Brazil', flag: '🇧🇷' },\n { code: 'BN', dialCode: '+673', name: 'Brunei', flag: '🇧🇳' },\n { code: 'BG', dialCode: '+359', name: 'Bulgaria', flag: '🇧🇬' },\n { code: 'BF', dialCode: '+226', name: 'Burkina Faso', flag: '🇧🇫' },\n { code: 'BI', dialCode: '+257', name: 'Burundi', flag: '🇧🇮' },\n { code: 'CV', dialCode: '+238', name: 'Cabo Verde', flag: '🇨🇻' },\n { code: 'KH', dialCode: '+855', name: 'Cambodia', flag: '🇰🇭' },\n { code: 'CM', dialCode: '+237', name: 'Cameroon', flag: '🇨🇲' },\n { code: 'CA', dialCode: '+1', name: 'Canada', flag: '🇨🇦' },\n {\n code: 'CF',\n dialCode: '+236',\n name: 'Central African Republic',\n flag: '🇨🇫',\n },\n { code: 'TD', dialCode: '+235', name: 'Chad', flag: '🇹🇩' },\n { code: 'CL', dialCode: '+56', name: 'Chile', flag: '🇨🇱' },\n { code: 'CN', dialCode: '+86', name: 'China', flag: '🇨🇳' },\n { code: 'CO', dialCode: '+57', name: 'Colombia', flag: '🇨🇴' },\n { code: 'KM', dialCode: '+269', name: 'Comoros', flag: '🇰🇲' },\n { code: 'CG', dialCode: '+242', name: 'Congo', flag: '🇨🇬' },\n { code: 'CD', dialCode: '+243', name: 'Congo (DRC)', flag: '🇨🇩' },\n { code: 'CR', dialCode: '+506', name: 'Costa Rica', flag: '🇨🇷' },\n { code: 'HR', dialCode: '+385', name: 'Croatia', flag: '🇭🇷' },\n { code: 'CU', dialCode: '+53', name: 'Cuba', flag: '🇨🇺' },\n { code: 'CY', dialCode: '+357', name: 'Cyprus', flag: '🇨🇾' },\n { code: 'CZ', dialCode: '+420', name: 'Czechia', flag: '🇨🇿' },\n { code: 'CI', dialCode: '+225', name: \"Cote d'Ivoire\", flag: '🇨🇮' },\n { code: 'DK', dialCode: '+45', name: 'Denmark', flag: '🇩🇰' },\n { code: 'DJ', dialCode: '+253', name: 'Djibouti', flag: '🇩🇯' },\n { code: 'DM', dialCode: '+1', name: 'Dominica', flag: '🇩🇲' },\n { code: 'DO', dialCode: '+1', name: 'Dominican Republic', flag: '🇩🇴' },\n { code: 'EC', dialCode: '+593', name: 'Ecuador', flag: '🇪🇨' },\n { code: 'EG', dialCode: '+20', name: 'Egypt', flag: '🇪🇬' },\n { code: 'SV', dialCode: '+503', name: 'El Salvador', flag: '🇸🇻' },\n { code: 'GQ', dialCode: '+240', name: 'Equatorial Guinea', flag: '🇬🇶' },\n { code: 'ER', dialCode: '+291', name: 'Eritrea', flag: '🇪🇷' },\n { code: 'EE', dialCode: '+372', name: 'Estonia', flag: '🇪🇪' },\n { code: 'SZ', dialCode: '+268', name: 'Eswatini', flag: '🇸🇿' },\n { code: 'ET', dialCode: '+251', name: 'Ethiopia', flag: '🇪🇹' },\n { code: 'FJ', dialCode: '+679', name: 'Fiji', flag: '🇫🇯' },\n { code: 'FI', dialCode: '+358', name: 'Finland', flag: '🇫🇮' },\n { code: 'FR', dialCode: '+33', name: 'France', flag: '🇫🇷' },\n { code: 'GA', dialCode: '+241', name: 'Gabon', flag: '🇬🇦' },\n { code: 'GM', dialCode: '+220', name: 'Gambia', flag: '🇬🇲' },\n { code: 'GE', dialCode: '+995', name: 'Georgia', flag: '🇬🇪' },\n { code: 'DE', dialCode: '+49', name: 'Germany', flag: '🇩🇪' },\n { code: 'GH', dialCode: '+233', name: 'Ghana', flag: '🇬🇭' },\n { code: 'GR', dialCode: '+30', name: 'Greece', flag: '🇬🇷' },\n { code: 'GD', dialCode: '+1', name: 'Grenada', flag: '🇬🇩' },\n { code: 'GT', dialCode: '+502', name: 'Guatemala', flag: '🇬🇹' },\n { code: 'GN', dialCode: '+224', name: 'Guinea', flag: '🇬🇳' },\n { code: 'GW', dialCode: '+245', name: 'Guinea-Bissau', flag: '🇬🇼' },\n { code: 'GY', dialCode: '+592', name: 'Guyana', flag: '🇬🇾' },\n { code: 'HT', dialCode: '+509', name: 'Haiti', flag: '🇭🇹' },\n { code: 'HN', dialCode: '+504', name: 'Honduras', flag: '🇭🇳' },\n { code: 'HK', dialCode: '+852', name: 'Hong Kong', flag: '🇭🇰' },\n { code: 'HU', dialCode: '+36', name: 'Hungary', flag: '🇭🇺' },\n { code: 'IS', dialCode: '+354', name: 'Iceland', flag: '🇮🇸' },\n { code: 'IN', dialCode: '+91', name: 'India', flag: '🇮🇳' },\n { code: 'ID', dialCode: '+62', name: 'Indonesia', flag: '🇮🇩' },\n { code: 'IR', dialCode: '+98', name: 'Iran', flag: '🇮🇷' },\n { code: 'IQ', dialCode: '+964', name: 'Iraq', flag: '🇮🇶' },\n { code: 'IE', dialCode: '+353', name: 'Ireland', flag: '🇮🇪' },\n { code: 'IL', dialCode: '+972', name: 'Israel', flag: '🇮🇱' },\n { code: 'IT', dialCode: '+39', name: 'Italy', flag: '🇮🇹' },\n { code: 'JM', dialCode: '+1', name: 'Jamaica', flag: '🇯🇲' },\n { code: 'JP', dialCode: '+81', name: 'Japan', flag: '🇯🇵' },\n { code: 'JO', dialCode: '+962', name: 'Jordan', flag: '🇯🇴' },\n { code: 'KZ', dialCode: '+7', name: 'Kazakhstan', flag: '🇰🇿' },\n { code: 'KE', dialCode: '+254', name: 'Kenya', flag: '🇰🇪' },\n { code: 'KI', dialCode: '+686', name: 'Kiribati', flag: '🇰🇮' },\n { code: 'KW', dialCode: '+965', name: 'Kuwait', flag: '🇰🇼' },\n { code: 'KG', dialCode: '+996', name: 'Kyrgyzstan', flag: '🇰🇬' },\n { code: 'LA', dialCode: '+856', name: 'Laos', flag: '🇱🇦' },\n { code: 'LV', dialCode: '+371', name: 'Latvia', flag: '🇱🇻' },\n { code: 'LB', dialCode: '+961', name: 'Lebanon', flag: '🇱🇧' },\n { code: 'LS', dialCode: '+266', name: 'Lesotho', flag: '🇱🇸' },\n { code: 'LR', dialCode: '+231', name: 'Liberia', flag: '🇱🇷' },\n { code: 'LY', dialCode: '+218', name: 'Libya', flag: '🇱🇾' },\n { code: 'LI', dialCode: '+423', name: 'Liechtenstein', flag: '🇱🇮' },\n { code: 'LT', dialCode: '+370', name: 'Lithuania', flag: '🇱🇹' },\n { code: 'LU', dialCode: '+352', name: 'Luxembourg', flag: '🇱🇺' },\n { code: 'MO', dialCode: '+853', name: 'Macao', flag: '🇲🇴' },\n { code: 'MG', dialCode: '+261', name: 'Madagascar', flag: '🇲🇬' },\n { code: 'MW', dialCode: '+265', name: 'Malawi', flag: '🇲🇼' },\n { code: 'MY', dialCode: '+60', name: 'Malaysia', flag: '🇲🇾' },\n { code: 'MV', dialCode: '+960', name: 'Maldives', flag: '🇲🇻' },\n { code: 'ML', dialCode: '+223', name: 'Mali', flag: '🇲🇱' },\n { code: 'MT', dialCode: '+356', name: 'Malta', flag: '🇲🇹' },\n { code: 'MH', dialCode: '+692', name: 'Marshall Islands', flag: '🇲🇭' },\n { code: 'MR', dialCode: '+222', name: 'Mauritania', flag: '🇲🇷' },\n { code: 'MU', dialCode: '+230', name: 'Mauritius', flag: '🇲🇺' },\n { code: 'MX', dialCode: '+52', name: 'Mexico', flag: '🇲🇽' },\n { code: 'FM', dialCode: '+691', name: 'Micronesia', flag: '🇫🇲' },\n { code: 'MD', dialCode: '+373', name: 'Moldova', flag: '🇲🇩' },\n { code: 'MC', dialCode: '+377', name: 'Monaco', flag: '🇲🇨' },\n { code: 'MN', dialCode: '+976', name: 'Mongolia', flag: '🇲🇳' },\n { code: 'ME', dialCode: '+382', name: 'Montenegro', flag: '🇲🇪' },\n { code: 'MA', dialCode: '+212', name: 'Morocco', flag: '🇲🇦' },\n { code: 'MZ', dialCode: '+258', name: 'Mozambique', flag: '🇲🇿' },\n { code: 'MM', dialCode: '+95', name: 'Myanmar', flag: '🇲🇲' },\n { code: 'NA', dialCode: '+264', name: 'Namibia', flag: '🇳🇦' },\n { code: 'NR', dialCode: '+674', name: 'Nauru', flag: '🇳🇷' },\n { code: 'NP', dialCode: '+977', name: 'Nepal', flag: '🇳🇵' },\n { code: 'NL', dialCode: '+31', name: 'Netherlands', flag: '🇳🇱' },\n { code: 'NZ', dialCode: '+64', name: 'New Zealand', flag: '🇳🇿' },\n { code: 'NI', dialCode: '+505', name: 'Nicaragua', flag: '🇳🇮' },\n { code: 'NE', dialCode: '+227', name: 'Niger', flag: '🇳🇪' },\n { code: 'NG', dialCode: '+234', name: 'Nigeria', flag: '🇳🇬' },\n { code: 'KP', dialCode: '+850', name: 'North Korea', flag: '🇰🇵' },\n { code: 'MK', dialCode: '+389', name: 'North Macedonia', flag: '🇲🇰' },\n { code: 'NO', dialCode: '+47', name: 'Norway', flag: '🇳🇴' },\n { code: 'OM', dialCode: '+968', name: 'Oman', flag: '🇴🇲' },\n { code: 'PK', dialCode: '+92', name: 'Pakistan', flag: '🇵🇰' },\n { code: 'PW', dialCode: '+680', name: 'Palau', flag: '🇵🇼' },\n { code: 'PS', dialCode: '+970', name: 'Palestine', flag: '🇵🇸' },\n { code: 'PA', dialCode: '+507', name: 'Panama', flag: '🇵🇦' },\n { code: 'PG', dialCode: '+675', name: 'Papua New Guinea', flag: '🇵🇬' },\n { code: 'PY', dialCode: '+595', name: 'Paraguay', flag: '🇵🇾' },\n { code: 'PE', dialCode: '+51', name: 'Peru', flag: '🇵🇪' },\n { code: 'PH', dialCode: '+63', name: 'Philippines', flag: '🇵🇭' },\n { code: 'PL', dialCode: '+48', name: 'Poland', flag: '🇵🇱' },\n { code: 'PT', dialCode: '+351', name: 'Portugal', flag: '🇵🇹' },\n { code: 'PR', dialCode: '+1', name: 'Puerto Rico', flag: '🇵🇷' },\n { code: 'QA', dialCode: '+974', name: 'Qatar', flag: '🇶🇦' },\n { code: 'RO', dialCode: '+40', name: 'Romania', flag: '🇷🇴' },\n { code: 'RU', dialCode: '+7', name: 'Russia', flag: '🇷🇺' },\n { code: 'RW', dialCode: '+250', name: 'Rwanda', flag: '🇷🇼' },\n { code: 'KN', dialCode: '+1', name: 'Saint Kitts and Nevis', flag: '🇰🇳' },\n { code: 'LC', dialCode: '+1', name: 'Saint Lucia', flag: '🇱🇨' },\n {\n code: 'VC',\n dialCode: '+1',\n name: 'Saint Vincent and the Grenadines',\n flag: '🇻🇨',\n },\n { code: 'WS', dialCode: '+685', name: 'Samoa', flag: '🇼🇸' },\n { code: 'SM', dialCode: '+378', name: 'San Marino', flag: '🇸🇲' },\n { code: 'ST', dialCode: '+239', name: 'Sao Tome and Principe', flag: '🇸🇹' },\n { code: 'SA', dialCode: '+966', name: 'Saudi Arabia', flag: '🇸🇦' },\n { code: 'SN', dialCode: '+221', name: 'Senegal', flag: '🇸🇳' },\n { code: 'RS', dialCode: '+381', name: 'Serbia', flag: '🇷🇸' },\n { code: 'SC', dialCode: '+248', name: 'Seychelles', flag: '🇸🇨' },\n { code: 'SL', dialCode: '+232', name: 'Sierra Leone', flag: '🇸🇱' },\n { code: 'SG', dialCode: '+65', name: 'Singapore', flag: '🇸🇬' },\n { code: 'SK', dialCode: '+421', name: 'Slovakia', flag: '🇸🇰' },\n { code: 'SI', dialCode: '+386', name: 'Slovenia', flag: '🇸🇮' },\n { code: 'SB', dialCode: '+677', name: 'Solomon Islands', flag: '🇸🇧' },\n { code: 'SO', dialCode: '+252', name: 'Somalia', flag: '🇸🇴' },\n { code: 'ZA', dialCode: '+27', name: 'South Africa', flag: '🇿🇦' },\n { code: 'KR', dialCode: '+82', name: 'South Korea', flag: '🇰🇷' },\n { code: 'SS', dialCode: '+211', name: 'South Sudan', flag: '🇸🇸' },\n { code: 'ES', dialCode: '+34', name: 'Spain', flag: '🇪🇸' },\n { code: 'LK', dialCode: '+94', name: 'Sri Lanka', flag: '🇱🇰' },\n { code: 'SD', dialCode: '+249', name: 'Sudan', flag: '🇸🇩' },\n { code: 'SR', dialCode: '+597', name: 'Suriname', flag: '🇸🇷' },\n { code: 'SE', dialCode: '+46', name: 'Sweden', flag: '🇸🇪' },\n { code: 'CH', dialCode: '+41', name: 'Switzerland', flag: '🇨🇭' },\n { code: 'SY', dialCode: '+963', name: 'Syria', flag: '🇸🇾' },\n { code: 'TW', dialCode: '+886', name: 'Taiwan', flag: '🇹🇼' },\n { code: 'TJ', dialCode: '+992', name: 'Tajikistan', flag: '🇹🇯' },\n { code: 'TZ', dialCode: '+255', name: 'Tanzania', flag: '🇹🇿' },\n { code: 'TH', dialCode: '+66', name: 'Thailand', flag: '🇹🇭' },\n { code: 'TL', dialCode: '+670', name: 'Timor-Leste', flag: '🇹🇱' },\n { code: 'TG', dialCode: '+228', name: 'Togo', flag: '🇹🇬' },\n { code: 'TO', dialCode: '+676', name: 'Tonga', flag: '🇹🇴' },\n { code: 'TT', dialCode: '+1', name: 'Trinidad and Tobago', flag: '🇹🇹' },\n { code: 'TN', dialCode: '+216', name: 'Tunisia', flag: '🇹🇳' },\n { code: 'TR', dialCode: '+90', name: 'Turkey', flag: '🇹🇷' },\n { code: 'TM', dialCode: '+993', name: 'Turkmenistan', flag: '🇹🇲' },\n { code: 'TV', dialCode: '+688', name: 'Tuvalu', flag: '🇹🇻' },\n { code: 'UG', dialCode: '+256', name: 'Uganda', flag: '🇺🇬' },\n { code: 'UA', dialCode: '+380', name: 'Ukraine', flag: '🇺🇦' },\n { code: 'AE', dialCode: '+971', name: 'United Arab Emirates', flag: '🇦🇪' },\n { code: 'GB', dialCode: '+44', name: 'United Kingdom', flag: '🇬🇧' },\n { code: 'US', dialCode: '+1', name: 'United States', flag: '🇺🇸' },\n { code: 'UY', dialCode: '+598', name: 'Uruguay', flag: '🇺🇾' },\n { code: 'UZ', dialCode: '+998', name: 'Uzbekistan', flag: '🇺🇿' },\n { code: 'VU', dialCode: '+678', name: 'Vanuatu', flag: '🇻🇺' },\n { code: 'VA', dialCode: '+39', name: 'Vatican City', flag: '🇻🇦' },\n { code: 'VE', dialCode: '+58', name: 'Venezuela', flag: '🇻🇪' },\n { code: 'VN', dialCode: '+84', name: 'Vietnam', flag: '🇻🇳' },\n { code: 'YE', dialCode: '+967', name: 'Yemen', flag: '🇾🇪' },\n { code: 'ZM', dialCode: '+260', name: 'Zambia', flag: '🇿🇲' },\n { code: 'ZW', dialCode: '+263', name: 'Zimbabwe', flag: '🇿🇼' },\n];\n\n// ---------------------------------------------------------------------------\n// Country lookup helpers\n// ---------------------------------------------------------------------------\n\nconst byCode = new Map(countries.map((c) => [c.code, c]));\n\n/** Common IANA timezone → country code mapping. */\nconst timezoneToCountry: Record<string, string> = {\n 'America/New_York': 'US',\n 'America/Chicago': 'US',\n 'America/Denver': 'US',\n 'America/Los_Angeles': 'US',\n 'America/Anchorage': 'US',\n 'Pacific/Honolulu': 'US',\n 'America/Phoenix': 'US',\n 'America/Indiana/Indianapolis': 'US',\n 'America/Toronto': 'CA',\n 'America/Vancouver': 'CA',\n 'America/Edmonton': 'CA',\n 'America/Winnipeg': 'CA',\n 'America/Halifax': 'CA',\n 'America/St_Johns': 'CA',\n 'Europe/London': 'GB',\n 'Europe/Dublin': 'IE',\n 'Europe/Paris': 'FR',\n 'Europe/Berlin': 'DE',\n 'Europe/Madrid': 'ES',\n 'Europe/Rome': 'IT',\n 'Europe/Amsterdam': 'NL',\n 'Europe/Brussels': 'BE',\n 'Europe/Zurich': 'CH',\n 'Europe/Vienna': 'AT',\n 'Europe/Stockholm': 'SE',\n 'Europe/Oslo': 'NO',\n 'Europe/Copenhagen': 'DK',\n 'Europe/Helsinki': 'FI',\n 'Europe/Warsaw': 'PL',\n 'Europe/Prague': 'CZ',\n 'Europe/Bucharest': 'RO',\n 'Europe/Budapest': 'HU',\n 'Europe/Athens': 'GR',\n 'Europe/Istanbul': 'TR',\n 'Europe/Moscow': 'RU',\n 'Europe/Kiev': 'UA',\n 'Europe/Lisbon': 'PT',\n 'Europe/Belgrade': 'RS',\n 'Europe/Zagreb': 'HR',\n 'Europe/Bratislava': 'SK',\n 'Europe/Ljubljana': 'SI',\n 'Europe/Tallinn': 'EE',\n 'Europe/Riga': 'LV',\n 'Europe/Vilnius': 'LT',\n 'Asia/Tokyo': 'JP',\n 'Asia/Seoul': 'KR',\n 'Asia/Shanghai': 'CN',\n 'Asia/Hong_Kong': 'HK',\n 'Asia/Taipei': 'TW',\n 'Asia/Singapore': 'SG',\n 'Asia/Kolkata': 'IN',\n 'Asia/Karachi': 'PK',\n 'Asia/Dhaka': 'BD',\n 'Asia/Bangkok': 'TH',\n 'Asia/Jakarta': 'ID',\n 'Asia/Manila': 'PH',\n 'Asia/Kuala_Lumpur': 'MY',\n 'Asia/Ho_Chi_Minh': 'VN',\n 'Asia/Riyadh': 'SA',\n 'Asia/Dubai': 'AE',\n 'Asia/Tehran': 'IR',\n 'Asia/Baghdad': 'IQ',\n 'Asia/Beirut': 'LB',\n 'Asia/Jerusalem': 'IL',\n 'Asia/Amman': 'JO',\n 'Asia/Almaty': 'KZ',\n 'Asia/Tashkent': 'UZ',\n 'Asia/Colombo': 'LK',\n 'Asia/Kathmandu': 'NP',\n 'Asia/Yangon': 'MM',\n 'Australia/Sydney': 'AU',\n 'Australia/Melbourne': 'AU',\n 'Australia/Brisbane': 'AU',\n 'Australia/Perth': 'AU',\n 'Australia/Adelaide': 'AU',\n 'Pacific/Auckland': 'NZ',\n 'Pacific/Fiji': 'FJ',\n 'Africa/Cairo': 'EG',\n 'Africa/Lagos': 'NG',\n 'Africa/Nairobi': 'KE',\n 'Africa/Johannesburg': 'ZA',\n 'Africa/Casablanca': 'MA',\n 'Africa/Accra': 'GH',\n 'Africa/Addis_Ababa': 'ET',\n 'Africa/Dar_es_Salaam': 'TZ',\n 'Africa/Kampala': 'UG',\n 'America/Mexico_City': 'MX',\n 'America/Bogota': 'CO',\n 'America/Lima': 'PE',\n 'America/Santiago': 'CL',\n 'America/Buenos_Aires': 'AR',\n 'America/Sao_Paulo': 'BR',\n 'America/Caracas': 'VE',\n 'America/Guatemala': 'GT',\n 'America/Havana': 'CU',\n 'America/Panama': 'PA',\n 'America/Jamaica': 'JM',\n};\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Detect the user's country from their timezone.\n * Falls back to `'US'` if the timezone can't be mapped.\n */\nexport function detectCountry(): string {\n try {\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n return timezoneToCountry[tz] ?? 'US';\n } catch {\n return 'US';\n }\n}\n\n/**\n * Format an E.164 phone number for display.\n *\n * US/CA numbers: `+1 (555) 123-4567`\n * Others: `+{dialCode} {rest}`\n */\nexport function format(e164: string): string {\n if (!e164.startsWith('+')) {\n return e164;\n }\n\n const digits = e164.slice(1);\n\n // US/CA: +1 followed by 10 digits\n if (digits.startsWith('1') && digits.length === 11) {\n const area = digits.slice(1, 4);\n const prefix = digits.slice(4, 7);\n const line = digits.slice(7);\n return `+1 (${area}) ${prefix}-${line}`;\n }\n\n // Find matching dial code (try longest match first)\n for (let len = 4; len >= 1; len--) {\n const candidate = '+' + digits.slice(0, len);\n const match = countries.find((c) => c.dialCode === candidate);\n if (match) {\n const rest = digits.slice(len);\n return `${match.dialCode} ${rest}`;\n }\n }\n\n return e164;\n}\n\n/**\n * Convert a national phone number to E.164 format.\n *\n * @param national - Phone number with or without formatting (e.g. \"(555) 123-4567\")\n * @param countryCode - ISO 3166-1 alpha-2 code (e.g. \"US\")\n * @returns E.164 formatted number (e.g. \"+15551234567\")\n */\nexport function toE164(national: string, countryCode: string): string {\n const digits = national.replace(/\\D/g, '');\n const country = byCode.get(countryCode);\n if (!country) {\n return '+' + digits;\n }\n const dialDigits = country.dialCode.slice(1); // remove +\n // If the number already starts with the dial code, don't double-add\n if (digits.startsWith(dialDigits)) {\n return '+' + digits;\n }\n return country.dialCode + digits;\n}\n\n/**\n * Check if a string is a valid E.164 phone number.\n * Must start with + followed by 7-15 digits.\n */\nexport function isValid(phone: string): boolean {\n return /^\\+[1-9]\\d{6,14}$/.test(phone);\n}\n","/**\n * Email validation helper for auth flows.\n */\n\n/** Basic email format check. Not RFC 5322 exhaustive. */\nexport function isValid(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n","/**\n * Auth — app-managed authentication for MindStudio v2 apps.\n *\n * Provides verification code flows (email + SMS), session state\n * management, and phone/email validation helpers. The platform\n * handles code delivery, cookie management, and user storage —\n * developers build their own login UI using these methods.\n *\n * ## How it works\n *\n * ```\n * // 1. Send a verification code\n * const { verificationId } = await auth.sendEmailCode('user@example.com');\n *\n * // 2. User enters the code in your UI\n * const user = await auth.verifyEmailCode(verificationId, '123456');\n *\n * // 3. Session is now active — all SDK calls use the authenticated token\n * const result = await api.getDashboard(); // uses authenticated session\n * auth.getCurrentUser(); // { id, email, phone, roles, createdAt }\n * ```\n *\n * Verify, confirm, and logout methods update `window.__MINDSTUDIO__`\n * in-place so all downstream calls (method invocation, agent chat,\n * uploads) immediately use the new session. No page refresh needed.\n *\n * @example\n * ```tsx\n * import { auth } from '@mindstudio-ai/interface';\n *\n * function LoginPage() {\n * const [email, setEmail] = useState('');\n * const [verificationId, setVerificationId] = useState('');\n * const [code, setCode] = useState('');\n *\n * const handleSend = async () => {\n * const { verificationId } = await auth.sendEmailCode(email);\n * setVerificationId(verificationId);\n * };\n *\n * const handleVerify = async () => {\n * await auth.verifyEmailCode(verificationId, code);\n * // Session updated in-place — navigate to your app\n * window.location.href = '/dashboard';\n * };\n * }\n * ```\n */\n\nimport { getConfig, updateConfig } from './config.js';\nimport { MindStudioInterfaceError } from './errors.js';\nimport type { AppUser, AuthSessionBundle } from './types.js';\nimport * as phoneHelpers from './auth-phone.js';\nimport * as emailHelpers from './auth-email.js';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nasync function authFetch<T>(\n path: string,\n method: 'GET' | 'POST',\n body?: Record<string, unknown>,\n): Promise<T> {\n const config = getConfig();\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n };\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n const res = await fetch(path, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n });\n\n if (!res.ok) {\n let message = `Auth request failed: ${res.status} ${res.statusText}`;\n let code = 'auth_error';\n try {\n const err = (await res.json()) as { error?: string; code?: string };\n if (err.error) {\n message = err.error;\n }\n if (err.code) {\n code = err.code;\n }\n } catch {\n // Response wasn't JSON\n }\n throw new MindStudioInterfaceError(message, code, res.status);\n }\n\n return (await res.json()) as T;\n}\n\n// sessionStorage key holding the CSRF `state` for an in-flight top-level\n// \"Sign in with Remy\" round-trip. Set by signInWithRemy(), validated + cleared\n// by handleRemyRedirect(). (The embedded/popup flow keeps `state` in the\n// opener's memory instead — see signInWithRemyPopup.)\nconst REMY_STATE_KEY = '__ms_remy_state';\n\n// Embedded (iframe) flow: a query marker on the popup callback URL so the SDK\n// instance that loads in the popup relays + closes instead of redeeming, and\n// the postMessage discriminator the opener matches on.\nconst REMY_POPUP_MARKER = 'ms_popup';\nconst REMY_POPUP_MESSAGE = 'ms:remy:callback';\n\n// The popup document forwards its one-time code to the opener exactly once,\n// whichever entry point (the import-time fast path or handleRemyRedirect())\n// runs the relay. Flipped after the first successful post — see\n// maybeRelayRemyPopupCallback.\nlet remyPopupForwarded = false;\n\n/**\n * Whether a delegated (\"Sign in with Remy\") handshake is completing right now.\n *\n * Set **synchronously at module load** when the page is a cold-load redirect\n * return (a `?code` in the URL that isn't a popup callback) — before any app\n * render — and cleared when the exchange settles. This is the stable signal\n * apps read via {@link Auth.authStatus}; the `?code` URL param can't serve that\n * role because handleRemyRedirect() strips it at the *start* of the exchange.\n * Also toggled around the embedded popup exchange (see signInWithRemyPopup).\n */\nlet redirectPending = detectColdLoadRedirect();\n\n/** True when this page load is a cold-load delegated redirect return. */\nfunction detectColdLoadRedirect(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n try {\n const params = new URL(window.location.href).searchParams;\n // A popup-callback document (`?ms_popup=1`) relays + closes; it never\n // redeems here, so it isn't \"authenticating\" from the app's perspective.\n return params.has('code') && params.get(REMY_POPUP_MARKER) !== '1';\n } catch {\n return false;\n }\n}\n\n/** Generate an opaque, unguessable CSRF state value. */\nfunction generateState(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') {\n return c.randomUUID();\n }\n const bytes = new Uint8Array(16);\n c?.getRandomValues?.(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** Strip our own `code`/`state` params from the URL without a reload. */\nfunction stripRemyRedirectParams(url: URL): void {\n url.searchParams.delete('code');\n url.searchParams.delete('state');\n try {\n window.history.replaceState(window.history.state, '', url.toString());\n } catch {\n // history unavailable (e.g. non-browser) — nothing to clean\n }\n}\n\nconst authListeners: Set<(user: AppUser | null) => void> = new Set();\n\nfunction applySession(bundle: AuthSessionBundle): void {\n updateConfig({\n token: bundle.token,\n user: bundle.user,\n methods: bundle.methods,\n visitorId: bundle.visitorId,\n });\n authListeners.forEach((cb) => cb(bundle.user));\n}\n\nfunction requireUser(bundle: AuthSessionBundle): AppUser {\n if (!bundle.user) {\n throw new MindStudioInterfaceError(\n 'Verification succeeded but no user was returned',\n 'auth_error',\n );\n }\n return bundle.user;\n}\n\nfunction updateUserAndNotify(update: Partial<AppUser>): void {\n const config = getConfig();\n if (config.user) {\n Object.assign(config.user, update);\n authListeners.forEach((cb) => cb(config.user));\n }\n}\n\n/** The current user, or `null` — never throws if the bootstrap is absent. */\nfunction safeCurrentUser(): AppUser | null {\n try {\n return getConfig().user;\n } catch {\n return null;\n }\n}\n\n/** Fire every auth listener with the current user (for auth-state changes). */\nfunction notifyAuthListeners(): void {\n const user = safeCurrentUser();\n authListeners.forEach((cb) => cb(user));\n}\n\n/** Enter the 'authenticating' state and notify subscribers (idempotent). */\nfunction beginAuthenticating(): void {\n if (redirectPending) {\n return;\n }\n redirectPending = true;\n notifyAuthListeners();\n}\n\n/** Leave the 'authenticating' state and notify subscribers (idempotent). */\nfunction endAuthenticating(): void {\n if (!redirectPending) {\n return;\n }\n redirectPending = false;\n notifyAuthListeners();\n}\n\n/** True when the SDK is running inside a (cross-origin) iframe. */\nfunction isEmbedded(): boolean {\n try {\n return window.top !== window.self;\n } catch {\n // Cross-origin restrictions on reading window.top → we're definitely framed.\n return true;\n }\n}\n\n// Sign-in popup size — compact and roughly square. (The old 480×720 opened tall\n// and, with no left/top, pinned to the top-left corner.)\nconst REMY_POPUP_WIDTH = 520;\nconst REMY_POPUP_HEIGHT = 600;\n\n/**\n * A `window.open` features string for a popup of the given size, centered over\n * the current browser window (dual-monitor-safe — centers on the window the\n * user is actually looking at, not the primary screen), clamped to fit.\n */\nfunction centeredPopupFeatures(width: number, height: number): string {\n const baseLeft = window.screenX;\n const baseTop = window.screenY;\n const viewW = window.outerWidth || window.innerWidth || window.screen.width;\n const viewH =\n window.outerHeight || window.innerHeight || window.screen.height;\n\n const w = Math.min(width, viewW);\n const h = Math.min(height, viewH);\n const left = Math.max(0, Math.round(baseLeft + (viewW - w) / 2));\n const top = Math.max(0, Math.round(baseTop + (viewH - h) / 2));\n\n return `popup=yes,width=${Math.round(w)},height=${Math.round(h)},left=${left},top=${top}`;\n}\n\n/**\n * Run the \"Sign in with Remy\" handshake in a top-level popup — used when the\n * app is embedded (a cross-origin iframe like the dev IDE preview). The\n * authorize page hard-denies framing, so it can only render as a first-party\n * top-level document. The popup relays its one-time code back to this window\n * (the opener) via postMessage; the exchange runs *here* so this context — the\n * one that must end up authenticated — owns the session. The `ms_iface_` token\n * from the response body is the source of truth (held in memory), so it works\n * regardless of whether the third-party `__ms_auth` cookie survives.\n */\nfunction signInWithRemyPopup(\n redirectUri: string,\n state: string,\n): Promise<AppUser | null> {\n // Tag the callback so the SDK instance that loads in the popup relays +\n // closes rather than redeeming in place.\n const callbackUrl = new URL(redirectUri, window.location.href);\n callbackUrl.searchParams.set(REMY_POPUP_MARKER, '1');\n\n const startUrl = `/_/auth/remy/start?${new URLSearchParams({\n redirect_uri: callbackUrl.toString(),\n state,\n }).toString()}`;\n\n // MUST open synchronously on the user's click — awaiting first spends the\n // gesture and the browser blocks the popup.\n const popup = window.open(\n startUrl,\n 'ms-remy-signin',\n centeredPopupFeatures(REMY_POPUP_WIDTH, REMY_POPUP_HEIGHT),\n );\n if (!popup) {\n return Promise.reject(\n new MindStudioInterfaceError(\n 'The sign-in popup was blocked. Allow popups for this site and try again.',\n 'popup_blocked',\n ),\n );\n }\n\n // The opener is now completing a handshake — reflected by auth.authStatus\n // until the exchange settles (or the popup is cancelled/times out).\n beginAuthenticating();\n\n return new Promise<AppUser | null>((resolve, reject) => {\n const appOrigin = window.location.origin;\n let settled = false;\n // Set synchronously when the first valid message is accepted — guards\n // against a duplicate/replayed post starting a second exchange, and stops\n // a later popup.closed from reading as \"cancel\" mid-exchange.\n let accepted = false;\n\n const finish = (fn: () => void): void => {\n if (settled) {\n return;\n }\n settled = true;\n window.removeEventListener('message', onMessage);\n clearInterval(pollTimer);\n clearTimeout(timeoutId);\n // Clear the 'authenticating' state (no-op on success — the exchange\n // already cleared it before applySession; clears + notifies on\n // cancel/timeout/error so the app leaves the \"Completing sign-in…\" UI).\n endAuthenticating();\n try {\n if (!popup.closed) {\n popup.close();\n }\n } catch {\n // ignore — closing a cross-context window can throw in rare cases\n }\n fn();\n };\n\n const onMessage = (event: MessageEvent): void => {\n // Once we've committed to the first valid message, ignore everything\n // else — a duplicate/replayed post must never start a second redemption.\n if (settled || accepted) {\n return;\n }\n // Hygiene: our exact origin, the window we opened, our shape, our state.\n if (event.origin !== appOrigin || event.source !== popup) {\n return;\n }\n const data = event.data as {\n type?: string;\n code?: string;\n state?: string;\n } | null;\n if (!data || data.type !== REMY_POPUP_MESSAGE || data.state !== state) {\n return; // not ours / stale / cross-talk / possible CSRF\n }\n\n // Commit synchronously, before the exchange await: this is the single\n // redemption, and a later popup.closed must not read as \"cancel\".\n accepted = true;\n\n if (!data.code) {\n finish(() =>\n reject(\n new MindStudioInterfaceError(\n 'Sign-in returned no code.',\n 'auth_error',\n ),\n ),\n );\n return;\n }\n\n // Redeem in the opener: this is the context that must be authenticated.\n authFetch<AuthSessionBundle>('/_/auth/remy/exchange', 'POST', {\n code: data.code,\n })\n .then((bundle) => {\n // Clear the flag before applySession's notify so subscribers see\n // 'authenticated' directly — never a stray 'unauthenticated' frame.\n redirectPending = false;\n applySession(bundle);\n finish(() => resolve(requireUser(bundle)));\n })\n .catch((err: unknown) => finish(() => reject(err)));\n };\n\n window.addEventListener('message', onMessage);\n\n // User closed the popup before finishing → cancelled (unless we've already\n // accepted the callback message and are mid-exchange).\n const pollTimer = setInterval(() => {\n if (popup.closed && !accepted) {\n finish(() => resolve(null));\n }\n }, 400);\n\n // Backstop for a popup that never closes or posts (e.g. stuck on an error).\n const timeoutId = setTimeout(\n () =>\n finish(() =>\n reject(\n new MindStudioInterfaceError(\n 'Sign-in timed out.',\n 'signin_timeout',\n ),\n ),\n ),\n 5 * 60 * 1000,\n );\n });\n}\n\n/**\n * If this window is the throwaway \"Sign in with Remy\" popup, relay the\n * one-time code to the opener and let it close us; returns `true` when it\n * handled the callback. Safe and synchronous — invoked both at SDK import\n * (before telemetry installs, so the popup fires no phantom pageview/presence)\n * and as a branch of {@link Auth.handleRemyRedirect}.\n *\n * @internal Not part of the public API surface.\n */\nexport function maybeRelayRemyPopupCallback(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n let params: URLSearchParams;\n try {\n params = new URL(window.location.href).searchParams;\n } catch {\n return false;\n }\n if (params.get(REMY_POPUP_MARKER) !== '1') {\n return false;\n }\n const code = params.get('code');\n if (!code) {\n return false;\n }\n\n // Idempotent: forward the code once per popup document. Both the import-time\n // fast path and handleRemyRedirect() call this on load; without the guard an\n // app following the documented \"call handleRemyRedirect() on load\" runs a\n // second forward inside the popup and the opener redeems the one-time code\n // twice (the second 400s). Return `true` — it's still the \"handled\" path.\n if (remyPopupForwarded) {\n return true;\n }\n\n const opener = window.opener as Window | null;\n if (!opener || opener === window) {\n // No channel back to the app — e.g. COOP severed the opener. Redeeming\n // here would authenticate the throwaway popup, which the storage-\n // partitioned iframe can't use, so we deliberately do nothing.\n return false;\n }\n\n try {\n opener.postMessage(\n {\n type: REMY_POPUP_MESSAGE,\n code,\n state: params.get('state') ?? undefined,\n },\n window.location.origin,\n );\n } catch {\n return false;\n }\n\n remyPopupForwarded = true;\n\n // The opener closes us after the exchange; self-close on the next tick is a\n // backstop in case it isn't listening.\n try {\n setTimeout(() => window.close(), 0);\n } catch {\n // ignore\n }\n return true;\n}\n\n/**\n * The high-level authentication state, from {@link Auth.authStatus}.\n *\n * - `'authenticating'` — a \"Sign in with Remy\" handshake is completing right\n * now: a cold-load redirect return (top-level or dashboard launch) is being\n * redeemed, or an embedded popup exchange is in flight. Render a\n * \"Completing sign-in…\" state, not the logged-out UI.\n * - `'authenticated'` — a user is signed in ({@link Auth.currentUser} is set).\n * - `'unauthenticated'` — no session and nothing in progress.\n */\nexport type AuthStatus = 'authenticating' | 'authenticated' | 'unauthenticated';\n\n/**\n * Options for {@link Auth.signInWithRemy}.\n */\nexport interface SignInWithRemyOptions {\n /**\n * The same-origin URL the platform should return the browser to after\n * resolving access. Must be an origin the app actually serves on (the\n * backend validates it against the app's allow-list).\n *\n * @default `window.location.href` — the user lands back where they started.\n */\n redirectUri?: string;\n\n /**\n * CSRF token round-tripped through the redirect. Provide your own to\n * correlate the return with app state; otherwise an opaque random value\n * is generated. Validated on return by {@link Auth.handleRemyRedirect}\n * (top-level flow) or by the opener (embedded/popup flow).\n */\n state?: string;\n\n /**\n * How to run the handshake.\n *\n * - `'auto'` (default) — full-page redirect when the app is top-level; a\n * popup when it's embedded in a cross-origin iframe (the authorize page\n * can't be framed, so a redirect there would fail).\n * - `'popup'` — always use a popup.\n * - `'redirect'` — always use a full-page redirect.\n *\n * @default 'auto'\n */\n mode?: 'auto' | 'popup' | 'redirect';\n}\n\n// ---------------------------------------------------------------------------\n// Auth interface\n// ---------------------------------------------------------------------------\n\n/**\n * The auth namespace — authentication flows, state, and helpers.\n *\n * Auth methods throw {@link MindStudioInterfaceError} on failure.\n * Common error codes:\n *\n * | Code | Status | Meaning |\n * |------|--------|---------|\n * | `rate_limited` | 429 | Too many code requests (max 5 per 15 min per identifier) |\n * | `invalid_code` | 400 | Wrong verification code |\n * | `verification_expired` | 400 | Code expired (10 min TTL) — request a new one |\n * | `max_attempts_exceeded` | 400 | Too many incorrect attempts (max 3) — request a new code |\n * | `not_authenticated` | 401 | Auth cookie missing (change/logout endpoints) |\n * | `invalid_session` | 401 | Auth cookie expired or invalid |\n */\nexport interface Auth {\n // -- State --\n\n /** The current authenticated user, or `null` if not authenticated. */\n readonly currentUser: AppUser | null;\n\n /** Get the current authenticated user, or `null` if not authenticated. */\n getCurrentUser(): AppUser | null;\n\n /** Whether the current session is authenticated. */\n isAuthenticated(): boolean;\n\n /**\n * The high-level auth state — `'authenticating'`, `'authenticated'`, or\n * `'unauthenticated'`. Synchronous and safe to read in render.\n *\n * The key case is `'authenticating'`: it's `true` from **page load** through\n * settle whenever a \"Sign in with Remy\" handshake is completing (a cold-load\n * redirect return being redeemed, or an embedded popup exchange in flight).\n * Read this to show a \"Completing sign-in…\" state instead of the logged-out\n * UI — the `?code` URL param can't be used for that (it's stripped at the\n * start of the exchange), and the immediate `null` from\n * {@link onAuthStateChanged} during this window is expected.\n *\n * `onAuthStateChanged` fires on every transition, so re-read this there.\n */\n readonly authStatus: AuthStatus;\n\n /**\n * The stable per-browser, per-app visitor identifier, or `null` if\n * unavailable (e.g. cached bootstrap from before this field existed).\n *\n * Opaque string backed by a server-set HttpOnly cookie. For authed\n * sessions this is the user's platform user ID; for guests it's a\n * per-browser UUID. Persists ~1 year; refreshed on each page load.\n * Updates in-place on login/logout transitions.\n */\n readonly currentVisitorId: string | null;\n\n /** Get the current visitor ID, or `null` if unavailable. */\n getCurrentVisitorId(): string | null;\n\n // -- Email code flow --\n\n /** Send a 6-digit verification code to an email address. */\n sendEmailCode(email: string): Promise<{ verificationId: string }>;\n\n /**\n * Verify an email code. On success, updates the session in-place\n * and returns the authenticated user.\n */\n verifyEmailCode(verificationId: string, code: string): Promise<AppUser>;\n\n // -- SMS code flow --\n\n /** Send a 6-digit verification code via SMS. Phone must be E.164. */\n sendSmsCode(phone: string): Promise<{ verificationId: string }>;\n\n /**\n * Verify an SMS code. On success, updates the session in-place\n * and returns the authenticated user.\n */\n verifySmsCode(verificationId: string, code: string): Promise<AppUser>;\n\n // -- Sign in with Remy (platform-delegated) --\n\n /**\n * Start the \"Sign in with Remy\" flow. The platform — not the app — decides\n * who the person is and whether they're allowed (think \"Sign in with\n * Google\"). Auto-detects the context:\n *\n * - **Top-level** (production, standalone tab): redirects the browser to the\n * platform and bounces back to `redirectUri` with a one-time code that\n * {@link handleRemyRedirect} redeems on return. **The page navigates away**,\n * so the returned promise never settles.\n * - **Embedded** (cross-origin iframe, e.g. the dev IDE preview): opens a\n * top-level popup for the handshake (the authorize page can't be framed).\n * The returned promise resolves with the user on success, `null` if the\n * user closes the popup, or rejects (`popup_blocked` / `invalid_state` /\n * `signin_timeout` / exchange error). `onAuthStateChanged` also fires on\n * success.\n *\n * Must be called from a user gesture (e.g. a click) so the popup isn't\n * blocked. Override the auto-detection with `options.mode`.\n *\n * @example\n * ```ts\n * // \"Continue with {Org}\" button — works top-level or embedded:\n * <button onClick={() => auth.signInWithRemy()}>Continue with Acme</button>\n *\n * // Await the result in an embedded app:\n * const user = await auth.signInWithRemy();\n * if (user) navigate('/dashboard');\n * ```\n */\n signInWithRemy(options?: SignInWithRemyOptions): Promise<AppUser | null>;\n\n /**\n * Complete a \"Sign in with Remy\" redirect. Call once on app load — it's a\n * no-op (resolves `null`) when there's no code to redeem, so it's safe to\n * run on every mount.\n *\n * Handles both entry points:\n * - **SP-initiated** (returned from {@link signInWithRemy}) — validates the\n * round-tripped CSRF `state` against the value stashed at start.\n * - **IdP-initiated** (opened from the Remy dashboard) — a `?code=` lands on\n * first paint with no `state`; the single-use, app-bound code is the\n * control, so it's redeemed directly.\n *\n * Inside the embedded/popup document it's also a safe no-op: the popup boots\n * the same bundle, but the code is relayed to the opener exactly once\n * (idempotent — the import-time fast path already forwarded it), and this\n * call resolves `null` without redeeming. So the documented \"call on load\"\n * guidance holds everywhere — no need to special-case the popup.\n *\n * On success, updates the session in-place (fires `onAuthStateChanged`),\n * strips `code`/`state` from the URL, and returns the authenticated user.\n *\n * @returns The authenticated user, or `null` if there was no code to redeem.\n * @throws `invalid_state` if a returned `state` doesn't match the stashed one.\n *\n * @example\n * ```ts\n * useEffect(() => { auth.handleRemyRedirect(); }, []);\n * useEffect(() => auth.onAuthStateChanged(setUser), []);\n * ```\n */\n handleRemyRedirect(): Promise<AppUser | null>;\n\n // -- Email/phone change (requires authentication) --\n\n /** Request an email change. Sends a code to the new email. */\n requestEmailChange(newEmail: string): Promise<void>;\n\n /** Confirm an email change with the verification code. */\n confirmEmailChange(newEmail: string, code: string): Promise<AppUser>;\n\n /** Request a phone change. Sends a code to the new phone (E.164). */\n requestPhoneChange(newPhone: string): Promise<void>;\n\n /** Confirm a phone change with the verification code. */\n confirmPhoneChange(newPhone: string, code: string): Promise<AppUser>;\n\n // -- Session --\n\n /** Log out. Clears the cookie and updates the session to unauthenticated. */\n logout(): Promise<void>;\n\n // -- API keys --\n\n /**\n * Generate an API key for the current user. Returns the full key\n * (shown once). The user's `apiKey` field updates to the masked\n * value and `onAuthStateChanged` fires.\n *\n * @throws `not_authenticated` (401) if no session\n * @throws `not_supported` (400) if api-key auth is not enabled\n */\n createApiKey(): Promise<{ key: string }>;\n\n /**\n * Revoke the current user's API key. The user's `apiKey` field\n * becomes `null` and `onAuthStateChanged` fires.\n *\n * @throws `not_authenticated` (401) if no session\n */\n revokeApiKey(): Promise<void>;\n\n /**\n * Subscribe to auth state changes. Fires immediately with the current user,\n * then again on every transition — verify / confirm / logout, **and** each\n * `authStatus` change (a \"Sign in with Remy\" handshake starting or settling).\n *\n * During `'authenticating'` the callback may fire with `null` (no session\n * yet) — that's expected; read {@link authStatus} to tell it apart from a\n * logged-out visit rather than rendering the login UI.\n *\n * @returns An unsubscribe function.\n *\n * @example\n * ```ts\n * // React hook — track both the user and the status.\n * function useAuth() {\n * const [user, setUser] = useState(auth.currentUser);\n * const [status, setStatus] = useState(auth.authStatus);\n * useEffect(() => auth.onAuthStateChanged(() => {\n * setUser(auth.currentUser);\n * setStatus(auth.authStatus);\n * }), []);\n * return { user, status };\n * }\n * ```\n */\n onAuthStateChanged(callback: (user: AppUser | null) => void): () => void;\n\n // -- Helpers --\n\n /** Phone number utilities — countries, formatting, validation. */\n phone: {\n /** All countries with dial codes, sorted alphabetically. */\n countries: readonly phoneHelpers.Country[];\n /** Detect the user's country from their timezone. Falls back to `'US'`. */\n detectCountry(): string;\n /** Format an E.164 number for display (e.g. `+1 (555) 123-4567`). */\n format(e164: string): string;\n /** Convert a national number to E.164 (e.g. `('5551234567', 'US') → '+15551234567'`). */\n toE164(national: string, countryCode: string): string;\n /** Check if a string is a valid E.164 phone number. */\n isValid(phone: string): boolean;\n };\n\n /** Email validation. */\n email: {\n /** Basic email format check. */\n isValid(email: string): boolean;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport const auth: Auth = {\n // -- State --\n\n get currentUser() {\n return getConfig().user;\n },\n\n getCurrentUser() {\n return getConfig().user;\n },\n\n isAuthenticated() {\n return getConfig().user !== null;\n },\n\n get authStatus() {\n if (redirectPending) {\n return 'authenticating';\n }\n return safeCurrentUser() ? 'authenticated' : 'unauthenticated';\n },\n\n get currentVisitorId() {\n return getConfig().visitorId ?? null;\n },\n\n getCurrentVisitorId() {\n return getConfig().visitorId ?? null;\n },\n\n // -- Email code flow --\n\n sendEmailCode(email: string) {\n return authFetch<{ verificationId: string }>('/_/auth/email/send', 'POST', {\n email,\n });\n },\n\n async verifyEmailCode(verificationId: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/email/verify',\n 'POST',\n { verificationId, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n // -- SMS code flow --\n\n sendSmsCode(phone: string) {\n return authFetch<{ verificationId: string }>('/_/auth/sms/send', 'POST', {\n phone,\n });\n },\n\n async verifySmsCode(verificationId: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/sms/verify',\n 'POST',\n { verificationId, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n // -- Sign in with Remy --\n\n signInWithRemy(options?: SignInWithRemyOptions): Promise<AppUser | null> {\n const mode = options?.mode ?? 'auto';\n const usePopup = mode === 'popup' || (mode === 'auto' && isEmbedded());\n const redirectUri = options?.redirectUri ?? window.location.href;\n const state = options?.state ?? generateState();\n\n if (usePopup) {\n // window.open must happen synchronously on the gesture — do it first.\n return signInWithRemyPopup(redirectUri, state);\n }\n\n // Top-level full-page redirect. The opener/popup handshake isn't needed,\n // so the CSRF `state` is stashed for handleRemyRedirect() to validate.\n try {\n sessionStorage.setItem(REMY_STATE_KEY, state);\n } catch {\n // sessionStorage unavailable (private mode / disabled) — proceed\n // without the CSRF stash; the one-time, app-bound code still gates.\n }\n\n const params = new URLSearchParams({ redirect_uri: redirectUri, state });\n window.location.assign(`/_/auth/remy/start?${params.toString()}`);\n\n // The page is navigating away — nothing after this resolves.\n return new Promise<AppUser | null>(() => {});\n },\n\n async handleRemyRedirect() {\n if (typeof window === 'undefined') {\n return null;\n }\n\n const url = new URL(window.location.href);\n const code = url.searchParams.get('code');\n if (!code) {\n return null;\n }\n\n // Embedded/popup callback: relay the code to the opener and let it close\n // us — never redeem here (the opener owns the session). The import-time\n // fast path usually handles this already; this is the fallback for apps\n // that call handleRemyRedirect() directly.\n if (url.searchParams.get(REMY_POPUP_MARKER) === '1') {\n maybeRelayRemyPopupCallback();\n return null;\n }\n\n const returnedState = url.searchParams.get('state');\n\n // We're committed to consuming this redirect — strip the params up front\n // so a refresh never retries a spent (or failed) one-time code.\n stripRemyRedirectParams(url);\n\n let storedState: string | null = null;\n try {\n storedState = sessionStorage.getItem(REMY_STATE_KEY);\n sessionStorage.removeItem(REMY_STATE_KEY);\n } catch {\n // sessionStorage unavailable — CSRF check degrades to redeem-only.\n }\n\n // We're redeeming now — reflect it in auth.authStatus. Idempotent: module\n // load already set this for a cold-load return; this also covers callers\n // that reach here without the eager path having run.\n beginAuthenticating();\n\n // SP-initiated returns round-trip a `state` that must match what we\n // stashed. IdP-initiated launches carry no `state` — the single-use,\n // app-bound, short-lived code is the control there.\n if (returnedState !== null && returnedState !== storedState) {\n endAuthenticating();\n throw new MindStudioInterfaceError(\n 'Sign-in state mismatch — possible CSRF or a stale redirect. Please try again.',\n 'invalid_state',\n );\n }\n\n try {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/remy/exchange',\n 'POST',\n { code },\n );\n // Clear the flag before applySession's notify so subscribers transition\n // straight to 'authenticated' — no intermediate 'unauthenticated' frame.\n redirectPending = false;\n applySession(bundle);\n return requireUser(bundle);\n } catch (err) {\n // Clear + notify so the app leaves the \"Completing sign-in…\" state.\n endAuthenticating();\n throw err;\n }\n },\n\n // -- Email/phone change --\n\n async requestEmailChange(newEmail: string) {\n await authFetch('/_/auth/email/change', 'POST', { newEmail });\n },\n\n async confirmEmailChange(newEmail: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/email/change/confirm',\n 'POST',\n { newEmail, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n async requestPhoneChange(newPhone: string) {\n await authFetch('/_/auth/phone/change', 'POST', { newPhone });\n },\n\n async confirmPhoneChange(newPhone: string, code: string) {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/phone/change/confirm',\n 'POST',\n { newPhone, code },\n );\n applySession(bundle);\n return requireUser(bundle);\n },\n\n // -- Session --\n\n async logout() {\n const bundle = await authFetch<AuthSessionBundle>(\n '/_/auth/logout',\n 'POST',\n {},\n );\n applySession(bundle);\n },\n\n async createApiKey() {\n const result = await authFetch<{ key: string; apiKey: string }>(\n '/_/auth/api-key/create',\n 'POST',\n {},\n );\n updateUserAndNotify({ apiKey: result.apiKey });\n return { key: result.key };\n },\n\n async revokeApiKey() {\n await authFetch('/_/auth/api-key/revoke', 'POST', {});\n updateUserAndNotify({ apiKey: null });\n },\n\n onAuthStateChanged(callback: (user: AppUser | null) => void) {\n authListeners.add(callback);\n callback(safeCurrentUser());\n return () => {\n authListeners.delete(callback);\n };\n },\n\n // -- Helpers --\n\n phone: {\n countries: phoneHelpers.countries,\n detectCountry: phoneHelpers.detectCountry,\n format: phoneHelpers.format,\n toE164: phoneHelpers.toE164,\n isValid: phoneHelpers.isValid,\n },\n\n email: {\n isValid: emailHelpers.isValid,\n },\n};\n","/**\n * @mindstudio-ai/interface — Frontend SDK for MindStudio v2 app interfaces.\n *\n * Provides typed RPC to backend methods, file uploads, authentication,\n * agent chat conversations, and analytics. Runs inside web interfaces with\n * credentials injected by the platform via `window.__MINDSTUDIO__`.\n *\n * ## Exports\n *\n * - `createClient()` — typed method RPC client\n * - `createAgentChatClient()` — thread-based agent conversations\n * - `platform` — file upload actions\n * - `auth` — authentication flows, user state, and validation helpers\n * - `analytics` — custom event tracking\n *\n * Voice sessions live on the `./voice` subpath (`createVoiceClient()` from\n * `@mindstudio-ai/interface/voice`) — deliberately not re-exported here, so\n * the core entry stays free of the realtime media dependency.\n *\n * @example\n * ```ts\n * import {\n * createClient,\n * createAgentChatClient,\n * platform,\n * auth,\n * } from '@mindstudio-ai/interface';\n *\n * const api = createClient();\n * const chat = createAgentChatClient();\n *\n * // Check auth state\n * if (!auth.isAuthenticated()) {\n * const { verificationId } = await auth.sendEmailCode('user@example.com');\n * await auth.verifyEmailCode(verificationId, code);\n * }\n *\n * // Call backend methods (uses authenticated session)\n * const dashboard = await api.getDashboard();\n *\n * // Upload a file (client-direct to the app's file store)\n * const token = await api.getUploadSlot({ contentType: file.type });\n * const { url } = await platform.upload(token, file);\n *\n * // Current user\n * const user = auth.getCurrentUser();\n * ```\n */\n\nexport { createClient, type InvokeOptions } from './client.js';\nexport {\n createAgentChatClient,\n type AgentChatClient,\n type AgentChatEvent,\n type SendMessageCallbacks,\n type SendMessageOptions,\n type SendMessageResult,\n type AbortablePromise,\n type Thread,\n type ThreadSummary,\n type ThreadListPage,\n type Message,\n} from './agent-chat.js';\nexport {\n platform,\n type UploadFileOptions,\n type UploadToken,\n} from './platform.js';\nexport {\n auth,\n type Auth,\n type AuthStatus,\n type SignInWithRemyOptions,\n} from './auth.js';\nexport {\n analytics,\n type AnalyticsClient,\n type CustomEventProps,\n} from './telemetry-analytics.js';\nexport { MindStudioInterfaceError } from './errors.js';\nexport type { BootstrapConfig, AppUser } from './types.js';\nexport type { Country } from './auth-phone.js';\nexport type { Breadcrumb } from './telemetry-breadcrumbs.js';\n\n// ---------------------------------------------------------------------------\n// Eager telemetry init\n//\n// When `window.__MINDSTUDIO__` is present at import time, trigger `getConfig()`\n// on the next tick so telemetry surfaces (errors, analytics, presence) install\n// at page load rather than on first SDK use. Without this, an app that creates\n// an `api` proxy at the module top but only invokes it from user interactions\n// would have no telemetry until the user clicks something.\n//\n// In environments without the bootstrap (SSR/Node, tests, dev without platform\n// context), the guard skips this and the SDK falls back to lazy init — first\n// SDK method call triggers `getConfig()` exactly as before.\n// ---------------------------------------------------------------------------\n\nimport { getConfig } from './config.js';\nimport { maybeRelayRemyPopupCallback } from './auth.js';\n\nif (typeof window !== 'undefined') {\n // If this window is the throwaway \"Sign in with Remy\" popup callback, relay\n // the code to the opener and close — synchronously, *before* installing\n // telemetry, so the popup never fires a phantom pageview / presence\n // connection. When it's not a popup callback this is a cheap no-op.\n const relayed = maybeRelayRemyPopupCallback();\n\n if (!relayed && (globalThis as Record<string, unknown>).__MINDSTUDIO__) {\n setTimeout(() => {\n try {\n getConfig();\n } catch {\n // bootstrap reads can still happen lazily on first SDK call\n }\n }, 0);\n }\n}\n"],"mappings":"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","names":["createClient","_","methodName","input","options","config","getConfig","methodId","MindStudioInterfaceError","url","wantsStream","res","errorMessage","errorCode","body","reader","decoder","buffer","finalOutput","streamError","done","value","lines","line","json","event","AGENT_BASE","request","path","method","body","headers","getConfig","res","errorMessage","errorCode","err","MindStudioInterfaceError","dispatchEvent","event","callbacks","createAgentChatClient","cursor","query","threadId","title","content","options","controller","cb","abortable","config","url","reader","decoder","buffer","result","done","value","lines","line","json","platform","file","options","config","getConfig","onProgress","signal","presignRes","MindStudioInterfaceError","uploadUrl","uploadFields","publicUrl","formData","key","value","xhrUpload","uploadRes","token","res","url","resolve","reject","xhr","e","countries","byCode","c","timezoneToCountry","detectCountry","tz","format","e164","digits","area","prefix","line","len","candidate","match","rest","toE164","national","countryCode","country","dialDigits","isValid","phone","isValid","email","authFetch","path","method","body","headers","getConfig","res","message","code","err","MindStudioInterfaceError","REMY_STATE_KEY","REMY_POPUP_MARKER","REMY_POPUP_MESSAGE","remyPopupForwarded","redirectPending","detectColdLoadRedirect","params","generateState","c","bytes","b","stripRemyRedirectParams","url","authListeners","applySession","bundle","updateConfig","cb","requireUser","updateUserAndNotify","update","config","safeCurrentUser","notifyAuthListeners","user","beginAuthenticating","endAuthenticating","isEmbedded","REMY_POPUP_WIDTH","REMY_POPUP_HEIGHT","centeredPopupFeatures","width","height","baseLeft","baseTop","viewW","viewH","w","h","left","top","signInWithRemyPopup","redirectUri","state","callbackUrl","startUrl","popup","resolve","reject","appOrigin","settled","accepted","finish","fn","onMessage","pollTimer","timeoutId","event","data","maybeRelayRemyPopupCallback","opener","auth","email","verificationId","phone","options","mode","usePopup","returnedState","storedState","newEmail","newPhone","result","callback","countries","detectCountry","format","toE164","isValid","maybeRelayRemyPopupCallback","getConfig"]}