@mindstudio-ai/interface 0.1.15 → 0.1.16

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/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/**\n * The agent invoked a client tool (agent.md `target: \"client\"`) — an action\n * that happens in this browser (open a sheet, navigate, highlight). Handle it\n * via `onClientToolCall`. Fire-and-forget: the agent continues immediately\n * with an acknowledgment; your next message closes the loop.\n */\nexport interface AgentClientToolCallEvent {\n type: 'client_tool_call';\n id: string;\n name: string;\n input: 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 | AgentClientToolCallEvent\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 /**\n * Called when the agent invokes a client tool (agent.md `target:\n * \"client\"`) — run the on-screen action here. Fire-and-forget: the agent\n * has already been told the action was displayed.\n */\n onClientToolCall?: (name: string, input: unknown, id: string) => 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 * Claim an anonymous thread after in-app verification (progressive auth).\n * Login replaces the app's session token, which makes threads started\n * anonymously unreachable — claiming re-binds the thread to the signed-in\n * user so the conversation survives the login. Call it right after your\n * verification succeeds, for each anonymous thread you want to keep.\n *\n * Ownership is proven with the token the thread was created/used under:\n * the client remembers it automatically for threads touched in this page\n * session; pass `previousToken` explicitly for threads from an earlier\n * page load (e.g. a token you persisted before login).\n * Rejects with `already_identified` if the thread already has an owner.\n */\n claimThread(\n threadId: string,\n options?: { previousToken?: string },\n ): 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 'client_tool_call':\n callbacks.onClientToolCall?.(event.name, event.input, event.id);\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 // Mint-time bearer per thread, for claimThread's ownership proof — after an\n // in-app login the app's config token is REPLACED (a fresh interface\n // session), so the pre-login token must be remembered to claim with.\n const threadTokens = new Map<string, string>();\n const rememberToken = (threadId: string) => {\n const token = getConfig().token;\n if (token && !threadTokens.has(threadId)) {\n threadTokens.set(threadId, token);\n }\n };\n\n return {\n async createThread() {\n const token = getConfig().token;\n const thread = await request<ThreadSummary>('/threads', 'POST');\n if (token) {\n threadTokens.set(thread.id, token);\n }\n return thread;\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 async claimThread(threadId: string, options?: { previousToken?: string }) {\n const previousToken =\n options?.previousToken ?? threadTokens.get(threadId);\n if (!previousToken) {\n throw new MindStudioInterfaceError(\n 'No pre-login token known for this thread — pass options.previousToken.',\n 'missing_previous_token',\n 400,\n );\n }\n await request(`/threads/${threadId}/claim`, 'POST', { previousToken });\n threadTokens.delete(threadId);\n },\n\n sendMessage(\n threadId: string,\n content: string,\n callbacks?: SendMessageCallbacks,\n options?: SendMessageOptions,\n ): AbortablePromise<SendMessageResult> {\n rememberToken(threadId);\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","threadTokens","rememberToken","threadId","token","thread","cursor","query","title","options","previousToken","content","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, withBase } 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 = withBase(`/_/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, withBase } 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/**\n * The agent invoked a client tool (agent.md `target: \"client\"`) — an action\n * that happens in this browser (open a sheet, navigate, highlight). Handle it\n * via `onClientToolCall`. Fire-and-forget: the agent continues immediately\n * with an acknowledgment; your next message closes the loop.\n */\nexport interface AgentClientToolCallEvent {\n type: 'client_tool_call';\n id: string;\n name: string;\n input: 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 | AgentClientToolCallEvent\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 /**\n * Called when the agent invokes a client tool (agent.md `target:\n * \"client\"`) — run the on-screen action here. Fire-and-forget: the agent\n * has already been told the action was displayed.\n */\n onClientToolCall?: (name: string, input: unknown, id: string) => 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 * Claim an anonymous thread after in-app verification (progressive auth).\n * Login replaces the app's session token, which makes threads started\n * anonymously unreachable — claiming re-binds the thread to the signed-in\n * user so the conversation survives the login. Call it right after your\n * verification succeeds, for each anonymous thread you want to keep.\n *\n * Ownership is proven with the token the thread was created/used under:\n * the client remembers it automatically for threads touched in this page\n * session; pass `previousToken` explicitly for threads from an earlier\n * page load (e.g. a token you persisted before login).\n * Rejects with `already_identified` if the thread already has an owner.\n */\n claimThread(\n threadId: string,\n options?: { previousToken?: string },\n ): 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(withBase(`${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 'client_tool_call':\n callbacks.onClientToolCall?.(event.name, event.input, event.id);\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 // Mint-time bearer per thread, for claimThread's ownership proof — after an\n // in-app login the app's config token is REPLACED (a fresh interface\n // session), so the pre-login token must be remembered to claim with.\n const threadTokens = new Map<string, string>();\n const rememberToken = (threadId: string) => {\n const token = getConfig().token;\n if (token && !threadTokens.has(threadId)) {\n threadTokens.set(threadId, token);\n }\n };\n\n return {\n async createThread() {\n const token = getConfig().token;\n const thread = await request<ThreadSummary>('/threads', 'POST');\n if (token) {\n threadTokens.set(thread.id, token);\n }\n return thread;\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 async claimThread(threadId: string, options?: { previousToken?: string }) {\n const previousToken =\n options?.previousToken ?? threadTokens.get(threadId);\n if (!previousToken) {\n throw new MindStudioInterfaceError(\n 'No pre-login token known for this thread — pass options.previousToken.',\n 'missing_previous_token',\n 400,\n );\n }\n await request(`/threads/${threadId}/claim`, 'POST', { previousToken });\n threadTokens.delete(threadId);\n },\n\n sendMessage(\n threadId: string,\n content: string,\n callbacks?: SendMessageCallbacks,\n options?: SendMessageOptions,\n ): AbortablePromise<SendMessageResult> {\n rememberToken(threadId);\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 = withBase(`${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 { getBasePath, getConfig, withBase } 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 + serving context.\n */\nexport const platform = {\n /**\n * The mount prefix this app is served under on the current host, or ''\n * when serving at the root of its own subdomain/custom host. Use it as the\n * router basename so routes resolve wherever the app is mounted:\n *\n * ```ts\n * createBrowserRouter(routes, { basename: platform.basePath || '/' });\n * ```\n *\n * The SDK already prefixes all its own platform calls with it.\n */\n get basePath(): string {\n return getBasePath();\n },\n\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 = withBase('/_/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, withBase } 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(withBase(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 = `${withBase('/_/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(\n `${withBase('/_/auth/remy/start')}?${params.toString()}`,\n );\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","withBase","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","withBase","errorMessage","errorCode","err","MindStudioInterfaceError","dispatchEvent","event","callbacks","createAgentChatClient","threadTokens","rememberToken","threadId","token","thread","cursor","query","title","options","previousToken","content","controller","cb","abortable","config","url","reader","decoder","buffer","result","done","value","lines","line","json","platform","getBasePath","file","options","config","getConfig","onProgress","signal","presignUrl","withBase","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","withBase","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"]}