@oxy-hq/sdk 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/config.ts","../src/parquet.ts","../src/client.ts","../src/sdk.ts","../src/react.tsx"],"sourcesContent":["/**\n * Configuration for the Oxy SDK\n */\nexport interface OxyConfig {\n /**\n * Base URL of the Oxy API (e.g., 'https://api.oxy.tech' or 'http://localhost:3000')\n */\n baseUrl: string;\n\n /**\n * API key for authentication (optional for local development)\n */\n apiKey?: string;\n\n /**\n * Project ID (UUID)\n */\n projectId: string;\n\n /**\n * Optional branch name (defaults to current branch if not specified)\n */\n branch?: string;\n\n /**\n * Request timeout in milliseconds (default: 30000)\n */\n timeout?: number;\n\n /**\n * Parent window origin for postMessage authentication (iframe scenarios)\n * Required when using postMessage auth for security.\n * Example: 'https://app.example.com'\n * Use '*' only in development!\n */\n parentOrigin?: string;\n\n /**\n * Disable automatic postMessage authentication even if in iframe\n * Set to true if you want to provide API key manually in iframe context\n */\n disableAutoAuth?: boolean;\n}\n\n/**\n * Safely get environment variable in both Node.js and browser environments\n */\nfunction getEnvVar(name: string): string | undefined {\n // Check if we're in Node.js\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[name];\n }\n\n // Check if we're in a Vite environment (browser)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (typeof import.meta !== \"undefined\" && (import.meta as any).env) {\n // Try with VITE_ prefix first (Vite convention)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const viteValue = (import.meta as any).env[`VITE_${name}`];\n if (viteValue !== undefined) return viteValue;\n\n // Try without prefix\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (import.meta as any).env[name];\n }\n\n return undefined;\n}\n\n/**\n * Creates an Oxy configuration from environment variables\n *\n * Environment variables:\n * - OXY_URL: Base URL of the Oxy API\n * - OXY_API_KEY: API key for authentication\n * - OXY_PROJECT_ID: Project ID (UUID)\n * - OXY_BRANCH: (Optional) Branch name\n *\n * @param overrides - Optional configuration overrides\n * @returns OxyConfig object\n * @throws Error if required environment variables are missing\n */\nexport function createConfig(overrides?: Partial<OxyConfig>): OxyConfig {\n const baseUrl = overrides?.baseUrl || getEnvVar(\"OXY_URL\");\n const apiKey = overrides?.apiKey || getEnvVar(\"OXY_API_KEY\");\n const projectId = overrides?.projectId || getEnvVar(\"OXY_PROJECT_ID\");\n\n if (!baseUrl) {\n throw new Error(\n \"OXY_URL environment variable or baseUrl config is required\",\n );\n }\n\n if (!projectId) {\n throw new Error(\n \"OXY_PROJECT_ID environment variable or projectId config is required\",\n );\n }\n\n return {\n baseUrl: baseUrl.replace(/\\/$/, \"\"), // Remove trailing slash\n apiKey,\n projectId,\n branch: overrides?.branch || getEnvVar(\"OXY_BRANCH\"),\n timeout: overrides?.timeout || 30000,\n parentOrigin: overrides?.parentOrigin,\n disableAutoAuth: overrides?.disableAutoAuth,\n };\n}\n\n/**\n * Creates an Oxy configuration asynchronously with support for postMessage authentication\n *\n * This is the recommended method for iframe scenarios where authentication\n * needs to be obtained from the parent window via postMessage.\n *\n * When running in an iframe without an API key, this function will:\n * 1. Detect the iframe context\n * 2. Send an authentication request to the parent window\n * 3. Wait for the parent to respond with credentials\n * 4. Return the configured client\n *\n * Environment variables (fallback):\n * - OXY_URL: Base URL of the Oxy API\n * - OXY_API_KEY: API key for authentication\n * - OXY_PROJECT_ID: Project ID (UUID)\n * - OXY_BRANCH: (Optional) Branch name\n *\n * @param overrides - Optional configuration overrides\n * @returns Promise resolving to OxyConfig object\n * @throws Error if required configuration is missing\n * @throws PostMessageAuthTimeoutError if parent doesn't respond\n *\n * @example\n * ```typescript\n * // Automatic iframe detection and authentication\n * const config = await createConfigAsync({\n * parentOrigin: 'https://app.example.com',\n * projectId: 'my-project-id',\n * baseUrl: 'https://api.oxy.tech'\n * });\n * ```\n */\nexport async function createConfigAsync(\n overrides?: Partial<OxyConfig>,\n): Promise<OxyConfig> {\n // Import postMessage utilities (dynamic to avoid circular deps)\n const { isInIframe } = await import(\"./auth/postMessage\");\n\n // Start with environment variables and overrides\n let baseUrl = overrides?.baseUrl || getEnvVar(\"OXY_URL\");\n let apiKey = overrides?.apiKey || getEnvVar(\"OXY_API_KEY\");\n let projectId = overrides?.projectId || getEnvVar(\"OXY_PROJECT_ID\");\n\n const disableAutoAuth = overrides?.disableAutoAuth ?? false;\n const parentOrigin =\n overrides?.parentOrigin ||\n (window?.location.ancestorOrigins?.[0]\n ? window.location.ancestorOrigins[0]\n : \"https://app.oxy.tech\");\n // Automatic iframe detection and authentication\n if (!disableAutoAuth && isInIframe() && !apiKey) {\n if (!parentOrigin) {\n logWarningAboutMissingParentOrigin();\n } else {\n apiKey = await attemptPostMessageAuth(\n parentOrigin,\n overrides?.timeout || 5000,\n apiKey,\n projectId,\n baseUrl,\n )\n .then((result) => {\n if (result.projectId) projectId = result.projectId;\n if (result.baseUrl) baseUrl = result.baseUrl;\n return result.apiKey;\n })\n .catch((error) => {\n console.error(\n \"[Oxy SDK] Failed to authenticate via postMessage:\",\n (error as Error).message,\n );\n return apiKey;\n });\n }\n }\n\n return createFinalConfig(baseUrl, apiKey, projectId, overrides);\n}\n\nfunction logWarningAboutMissingParentOrigin(): void {\n console.warn(\n \"[Oxy SDK] Running in iframe without API key and no parentOrigin specified. \" +\n \"PostMessage authentication will be skipped. \" +\n \"Provide parentOrigin config to enable automatic authentication.\",\n );\n}\n\nasync function attemptPostMessageAuth(\n parentOrigin: string,\n timeout: number,\n currentApiKey: string | undefined,\n currentProjectId: string | undefined,\n currentBaseUrl: string | undefined,\n): Promise<{ apiKey?: string; projectId?: string; baseUrl?: string }> {\n const { requestAuthFromParent } = await import(\"./auth/postMessage\");\n const authResult = await requestAuthFromParent({ parentOrigin, timeout });\n\n console.log(\"[Oxy SDK] Successfully authenticated via postMessage\");\n\n return {\n apiKey: authResult.apiKey || currentApiKey,\n projectId: authResult.projectId || currentProjectId,\n baseUrl: authResult.baseUrl || currentBaseUrl,\n };\n}\n\nfunction createFinalConfig(\n baseUrl: string | undefined,\n apiKey: string | undefined,\n projectId: string | undefined,\n overrides?: Partial<OxyConfig>,\n): OxyConfig {\n // Validation\n if (!baseUrl) {\n throw new Error(\n \"OXY_URL environment variable or baseUrl config is required\",\n );\n }\n\n if (!projectId) {\n throw new Error(\n \"OXY_PROJECT_ID environment variable or projectId config is required\",\n );\n }\n\n return {\n baseUrl: baseUrl.replace(/\\/$/, \"\"), // Remove trailing slash\n apiKey,\n projectId,\n branch: overrides?.branch || getEnvVar(\"OXY_BRANCH\"),\n timeout: overrides?.timeout || 30000,\n parentOrigin: overrides?.parentOrigin,\n disableAutoAuth: overrides?.disableAutoAuth,\n };\n}\n","import * as duckdb from \"@duckdb/duckdb-wasm\";\n\nlet dbInstance: duckdb.AsyncDuckDB | null = null;\nlet connection: duckdb.AsyncDuckDBConnection | null = null;\n\n// Queue to serialize operations and prevent race conditions\nlet operationQueue = Promise.resolve();\n\n/**\n * Enqueue an operation to prevent race conditions on shared DuckDB instance\n */\nfunction enqueueOperation<T>(operation: () => Promise<T>): Promise<T> {\n const currentOperation = operationQueue.then(operation, operation);\n operationQueue = currentOperation.then(\n () => {\n return;\n },\n () => {\n return;\n },\n );\n return currentOperation;\n}\n\n/**\n * Initialize DuckDB-WASM instance\n */\nexport async function initializeDuckDB(): Promise<duckdb.AsyncDuckDB> {\n if (dbInstance) {\n return dbInstance;\n }\n\n const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();\n\n // Select a bundle based on browser features\n const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);\n\n const worker_url = URL.createObjectURL(\n new Blob([`importScripts(\"${bundle.mainWorker}\");`], {\n type: \"text/javascript\",\n }),\n );\n\n const worker = new Worker(worker_url);\n const logger = new duckdb.ConsoleLogger();\n\n dbInstance = new duckdb.AsyncDuckDB(logger, worker);\n await dbInstance.instantiate(bundle.mainModule, bundle.pthreadWorker);\n URL.revokeObjectURL(worker_url);\n\n return dbInstance;\n}\n\n/**\n * Get or create a DuckDB connection\n */\nasync function getConnection(): Promise<duckdb.AsyncDuckDBConnection> {\n if (connection) {\n return connection;\n }\n\n const db = await initializeDuckDB();\n connection = await db.connect();\n return connection;\n}\n\n/**\n * Query result interface\n */\nexport interface QueryResult {\n columns: string[];\n rows: unknown[][];\n rowCount: number;\n}\n\n/**\n * ParquetReader provides methods to read and query Parquet files.\n * Supports registering multiple Parquet files with different table names.\n */\nexport class ParquetReader {\n private tableMap: Map<string, string> = new Map(); // Maps user table name -> internal unique table name\n\n constructor() {\n // No default table name - all tables must be explicitly named\n }\n\n /**\n * Generate a unique internal table name to prevent conflicts\n */\n private generateInternalTableName(tableName: string): string {\n // Note: Math.random() is acceptable here as uniqueness is only needed for table naming (not security-critical)\n /* eslint-disable sonarjs/pseudo-random */\n const uniqueId = `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n /* eslint-enable sonarjs/pseudo-random */\n return `${tableName}_${uniqueId}`;\n }\n\n /**\n * Register a Parquet file from a Blob with a specific table name\n *\n * @param blob - Parquet file as Blob\n * @param tableName - Name to use for the table in queries (required)\n *\n * @example\n * ```typescript\n * const blob = await client.getFile('data/sales.parquet');\n * const reader = new ParquetReader();\n * await reader.registerParquet(blob, 'sales');\n * ```\n *\n * @example\n * ```typescript\n * // Register multiple files\n * const reader = new ParquetReader();\n * await reader.registerParquet(salesBlob, 'sales');\n * await reader.registerParquet(customersBlob, 'customers');\n * const result = await reader.query('SELECT * FROM sales JOIN customers ON sales.customer_id = customers.id');\n * ```\n */\n async registerParquet(blob: Blob, tableName: string): Promise<void> {\n const internalTableName = this.generateInternalTableName(tableName);\n\n await enqueueOperation(async () => {\n const conn = await getConnection();\n const db = await initializeDuckDB();\n\n // Convert blob to Uint8Array\n const arrayBuffer = await blob.arrayBuffer();\n const uint8Array = new Uint8Array(arrayBuffer);\n\n // Register the file with DuckDB using unique name\n await db.registerFileBuffer(`${internalTableName}.parquet`, uint8Array);\n\n // Drop table if it exists\n try {\n await conn.query(`DROP TABLE IF EXISTS ${internalTableName}`);\n } catch {\n // Ignore error if table doesn't exist\n }\n\n // Create table from parquet\n await conn.query(\n `CREATE TABLE ${internalTableName} AS SELECT * FROM '${internalTableName}.parquet'`,\n );\n\n // Store mapping\n this.tableMap.set(tableName, internalTableName);\n });\n }\n\n /**\n * Register multiple Parquet files at once\n *\n * @param files - Array of objects containing blob and tableName\n *\n * @example\n * ```typescript\n * const reader = new ParquetReader();\n * await reader.registerMultipleParquet([\n * { blob: salesBlob, tableName: 'sales' },\n * { blob: customersBlob, tableName: 'customers' },\n * { blob: productsBlob, tableName: 'products' }\n * ]);\n * const result = await reader.query('SELECT * FROM sales JOIN customers ON sales.customer_id = customers.id');\n * ```\n */\n async registerMultipleParquet(\n files: Array<{ blob: Blob; tableName: string }>,\n ): Promise<void> {\n for (const file of files) {\n await this.registerParquet(file.blob, file.tableName);\n }\n }\n\n /**\n * Execute a SQL query against the registered Parquet data\n *\n * @param sql - SQL query string\n * @returns Query result with columns and rows\n *\n * @example\n * ```typescript\n * const result = await reader.query('SELECT * FROM sales LIMIT 10');\n * console.log(result.columns);\n * console.log(result.rows);\n * ```\n *\n * @example\n * ```typescript\n * // Query multiple tables\n * await reader.registerParquet(salesBlob, 'sales');\n * await reader.registerParquet(customersBlob, 'customers');\n * const result = await reader.query(`\n * SELECT s.*, c.name\n * FROM sales s\n * JOIN customers c ON s.customer_id = c.id\n * `);\n * ```\n */\n async query(sql: string): Promise<QueryResult> {\n if (this.tableMap.size === 0) {\n throw new Error(\n \"No Parquet files registered. Call registerParquet() first.\",\n );\n }\n\n return enqueueOperation(async () => {\n const conn = await getConnection();\n\n // Replace all registered table names with their internal unique names\n let rewrittenSql = sql;\n for (const [\n userTableName,\n internalTableName,\n ] of this.tableMap.entries()) {\n rewrittenSql = rewrittenSql.replace(\n new RegExp(`\\\\b${userTableName}\\\\b`, \"g\"),\n internalTableName,\n );\n }\n\n const result = await conn.query(rewrittenSql);\n\n const columns = result.schema.fields.map((field) => field.name);\n const rows: unknown[][] = [];\n\n // Convert Arrow table to rows\n for (let i = 0; i < result.numRows; i++) {\n const row: unknown[] = [];\n for (let j = 0; j < result.numCols; j++) {\n const col = result.getChildAt(j);\n row.push(col?.get(i));\n }\n rows.push(row);\n }\n\n return {\n columns,\n rows,\n rowCount: result.numRows,\n };\n });\n }\n\n /**\n * Get all data from a registered table\n *\n * @param tableName - Name of the table to query\n * @param limit - Maximum number of rows to return (default: all)\n * @returns Query result\n *\n * @example\n * ```typescript\n * const allData = await reader.getAll('sales');\n * const first100 = await reader.getAll('sales', 100);\n * ```\n */\n async getAll(tableName: string, limit?: number): Promise<QueryResult> {\n const limitClause = limit ? ` LIMIT ${limit}` : \"\";\n return this.query(`SELECT * FROM ${tableName}${limitClause}`);\n }\n\n /**\n * Get table schema information\n *\n * @param tableName - Name of the table to describe\n * @returns Schema information\n *\n * @example\n * ```typescript\n * const schema = await reader.getSchema('sales');\n * console.log(schema.columns); // ['id', 'name', 'sales']\n * console.log(schema.rows); // [['id', 'INTEGER'], ['name', 'VARCHAR'], ...]\n * ```\n */\n async getSchema(tableName: string): Promise<QueryResult> {\n return this.query(`DESCRIBE ${tableName}`);\n }\n\n /**\n * Get row count for a table\n *\n * @param tableName - Name of the table to count\n * @returns Number of rows in the table\n *\n * @example\n * ```typescript\n * const count = await reader.count('sales');\n * console.log(`Total rows: ${count}`);\n * ```\n */\n async count(tableName: string): Promise<number> {\n const result = await this.query(\n `SELECT COUNT(*) as count FROM ${tableName}`,\n );\n return result.rows[0][0] as number;\n }\n\n /**\n * Close and cleanup all registered resources\n */\n async close(): Promise<void> {\n if (this.tableMap.size > 0) {\n await enqueueOperation(async () => {\n const conn = await getConnection();\n const db = await initializeDuckDB();\n\n // Drop all registered tables and file buffers\n for (const [, internalTableName] of this.tableMap.entries()) {\n // Drop the table using internal unique name\n try {\n await conn.query(`DROP TABLE IF EXISTS ${internalTableName}`);\n } catch {\n // Ignore error\n }\n\n // Drop the registered file buffer using internal unique name\n try {\n await db.dropFile(`${internalTableName}.parquet`);\n } catch {\n // Ignore error if file doesn't exist\n }\n }\n\n // Clear the table map\n this.tableMap.clear();\n });\n }\n }\n}\n\n/**\n * Helper function to quickly read a Parquet blob and execute a query\n *\n * @param blob - Parquet file as Blob\n * @param tableName - Name to use for the table in queries (default: 'data')\n * @param sql - SQL query to execute (optional, defaults to SELECT * FROM tableName)\n * @returns Query result\n *\n * @example\n * ```typescript\n * const blob = await client.getFile('data/sales.parquet');\n * const result = await queryParquet(blob, 'sales', 'SELECT product, SUM(amount) as total FROM sales GROUP BY product');\n * console.log(result);\n * ```\n */\nexport async function queryParquet(\n blob: Blob,\n tableName: string = \"data\",\n sql?: string,\n): Promise<QueryResult> {\n const reader = new ParquetReader();\n\n await reader.registerParquet(blob, tableName);\n\n const query = sql || `SELECT * FROM ${tableName}`;\n const result = await reader.query(query);\n\n await reader.close();\n return result;\n}\n\n/**\n * Helper function to read Parquet file and get all data\n *\n * @param blob - Parquet file as Blob\n * @param tableName - Name to use for the table (default: 'data')\n * @param limit - Maximum number of rows (optional)\n * @returns Query result\n *\n * @example\n * ```typescript\n * const blob = await client.getFile('data/sales.parquet');\n * const data = await readParquet(blob, 'sales', 1000);\n * console.log(`Loaded ${data.rowCount} rows`);\n * ```\n */\nexport async function readParquet(\n blob: Blob,\n tableName: string = \"data\",\n limit?: number,\n): Promise<QueryResult> {\n const reader = new ParquetReader();\n\n await reader.registerParquet(blob, tableName);\n\n const result = await reader.getAll(tableName, limit);\n\n await reader.close();\n return result;\n}\n","import { createConfigAsync, type OxyConfig } from \"./config\";\r\nimport { readParquet } from \"./parquet\";\r\nimport type { ApiError, AppDataResponse, AppItem, GetDisplaysResponse, TableData } from \"./types\";\r\n\r\n/**\r\n * Oxy API Client for interacting with Oxy data\r\n */\r\nexport class OxyClient {\r\n private config: OxyConfig;\r\n\r\n constructor(config: OxyConfig) {\r\n this.config = config;\r\n }\r\n\r\n /**\r\n * Creates an OxyClient instance asynchronously with support for postMessage authentication\r\n *\r\n * This is the recommended method when using the SDK in an iframe that needs to\r\n * obtain authentication from the parent window via postMessage.\r\n *\r\n * @param config - Optional configuration overrides\r\n * @returns Promise resolving to OxyClient instance\r\n * @throws Error if required configuration is missing\r\n * @throws PostMessageAuthTimeoutError if parent doesn't respond\r\n *\r\n * @example\r\n * ```typescript\r\n * // In an iframe - automatic postMessage auth\r\n * const client = await OxyClient.create({\r\n * parentOrigin: 'https://app.example.com',\r\n * projectId: 'my-project-id',\r\n * baseUrl: 'https://api.oxy.tech'\r\n * });\r\n *\r\n * // Use the client normally\r\n * const apps = await client.listApps();\r\n * ```\r\n */\r\n static async create(config?: Partial<OxyConfig>): Promise<OxyClient> {\r\n const resolvedConfig = await createConfigAsync(config);\r\n return new OxyClient(resolvedConfig);\r\n }\r\n\r\n /**\r\n * Encodes a file path to base64 for use in API URLs.\r\n * Handles Unicode characters (e.g., emojis) properly in both Node.js and browser.\r\n */\r\n private encodePathBase64(path: string): string {\r\n if (typeof Buffer !== \"undefined\") {\r\n // Node.js environment\r\n return Buffer.from(path).toString(\"base64\");\r\n } else {\r\n // Browser environment - handle Unicode properly\r\n return btoa(\r\n encodeURIComponent(path).replace(/%([0-9A-F]{2})/g, (_, p1) =>\r\n String.fromCharCode(parseInt(p1, 16))\r\n )\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Makes an authenticated HTTP request to the Oxy API\r\n */\r\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\r\n const url = `${this.config.baseUrl}${endpoint}`;\r\n\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json\",\r\n ...((options.headers as Record<string, string>) || {})\r\n };\r\n\r\n // Only add Authorization header if API key is provided (optional for local dev)\r\n if (this.config.apiKey) {\r\n headers.Authorization = `Bearer ${this.config.apiKey}`;\r\n }\r\n\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout || 30000);\r\n\r\n try {\r\n const response = await fetch(url, {\r\n ...options,\r\n headers,\r\n signal: controller.signal\r\n });\r\n\r\n clearTimeout(timeoutId);\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text().catch(() => \"Unknown error\");\r\n const error: ApiError = {\r\n message: `API request failed: ${response.statusText}`,\r\n status: response.status,\r\n details: errorText\r\n };\r\n throw error;\r\n }\r\n\r\n // Handle binary responses\r\n const acceptHeader =\r\n typeof options.headers === \"object\" && options.headers !== null\r\n ? (options.headers as Record<string, string>).Accept\r\n : undefined;\r\n if (acceptHeader === \"application/octet-stream\") {\r\n return response.blob() as Promise<T>;\r\n }\r\n\r\n return response.json();\r\n } catch (error: unknown) {\r\n clearTimeout(timeoutId);\r\n\r\n if (error instanceof Error && error.name === \"AbortError\") {\r\n throw new Error(`Request timeout after ${this.config.timeout || 30000}ms`);\r\n }\r\n\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Builds query parameters including optional branch\r\n */\r\n private buildQueryParams(additionalParams: Record<string, string> = {}): string {\r\n const params: Record<string, string> = { ...additionalParams };\r\n\r\n if (this.config.branch) {\r\n params.branch = this.config.branch;\r\n }\r\n\r\n const searchParams = new URLSearchParams(params);\r\n const queryString = searchParams.toString();\r\n return queryString ? `?${queryString}` : \"\";\r\n }\r\n\r\n /**\r\n * Lists all apps in the project\r\n *\r\n * @returns Array of app items\r\n *\r\n * @example\r\n * ```typescript\r\n * const apps = await client.listApps();\r\n * console.log('Available apps:', apps);\r\n * ```\r\n */\r\n async listApps(): Promise<AppItem[]> {\r\n const query = this.buildQueryParams();\r\n return this.request<AppItem[]>(`/${this.config.projectId}/apps${query}`);\r\n }\r\n\r\n /**\r\n * Gets data for a specific app\r\n *\r\n * @param appPath - Relative path to the app file (e.g., 'my-app.app.yml')\r\n * @returns App data response\r\n *\r\n * @example\r\n * ```typescript\r\n * const data = await client.getAppData('dashboard.app.yml');\r\n * if (data.error) {\r\n * console.error('Error:', data.error);\r\n * } else {\r\n * console.log('App data:', data.data);\r\n * }\r\n * ```\r\n */\r\n async getAppData(appPath: string): Promise<AppDataResponse> {\r\n const pathb64 = this.encodePathBase64(appPath);\r\n const query = this.buildQueryParams();\r\n return this.request<AppDataResponse>(`/${this.config.projectId}/apps/${pathb64}${query}`);\r\n }\r\n\r\n /**\r\n * Runs an app and returns fresh data (bypasses cache)\r\n *\r\n * @param appPath - Relative path to the app file\r\n * @returns App data response\r\n *\r\n * @example\r\n * ```typescript\r\n * const data = await client.runApp('dashboard.app.yml');\r\n * console.log('Fresh app data:', data.data);\r\n * ```\r\n */\r\n async runApp(appPath: string): Promise<AppDataResponse> {\r\n const pathb64 = this.encodePathBase64(appPath);\r\n const query = this.buildQueryParams();\r\n return this.request<AppDataResponse>(`/${this.config.projectId}/apps/${pathb64}/run${query}`, {\r\n method: \"POST\"\r\n });\r\n }\r\n\r\n /**\r\n * Gets display configurations for an app\r\n *\r\n * @param appPath - Relative path to the app file\r\n * @returns Display configurations with potential errors\r\n *\r\n * @example\r\n * ```typescript\r\n * const displays = await client.getDisplays('dashboard.app.yml');\r\n * displays.displays.forEach(d => {\r\n * if (d.error) {\r\n * console.error('Display error:', d.error);\r\n * } else {\r\n * console.log('Display:', d.display);\r\n * }\r\n * });\r\n * ```\r\n */\r\n async getDisplays(appPath: string): Promise<GetDisplaysResponse> {\r\n const pathb64 = this.encodePathBase64(appPath);\r\n const query = this.buildQueryParams();\r\n return this.request<GetDisplaysResponse>(\r\n `/${this.config.projectId}/apps/${pathb64}/displays${query}`\r\n );\r\n }\r\n\r\n /**\r\n * Gets a file from the app state directory (e.g., generated charts, images)\r\n *\r\n * This is useful for retrieving generated assets like charts, images, or other\r\n * files produced by app workflows and stored in the state directory.\r\n *\r\n * @param filePath - Relative path to the file in state directory\r\n * @returns Blob containing the file data\r\n *\r\n * @example\r\n * ```typescript\r\n * // Get a generated chart image\r\n * const blob = await client.getFile('charts/sales-chart.png');\r\n * const imageUrl = URL.createObjectURL(blob);\r\n *\r\n * // Use in an img tag\r\n * document.querySelector('img').src = imageUrl;\r\n * ```\r\n *\r\n * @example\r\n * ```typescript\r\n * // Download a file\r\n * const blob = await client.getFile('exports/data.csv');\r\n * const a = document.createElement('a');\r\n * a.href = URL.createObjectURL(blob);\r\n * a.download = 'data.csv';\r\n * a.click();\r\n * ```\r\n */\r\n async getFile(filePath: string): Promise<Blob> {\r\n const pathb64 = this.encodePathBase64(filePath);\r\n const query = this.buildQueryParams();\r\n return this.request<Blob>(`/${this.config.projectId}/apps/file/${pathb64}${query}`, {\r\n headers: {\r\n Accept: \"application/octet-stream\"\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Gets a file URL for direct browser access\r\n *\r\n * This returns a URL that can be used directly in img tags, fetch calls, etc.\r\n * The URL includes authentication via query parameters.\r\n *\r\n * @param filePath - Relative path to the file in state directory\r\n * @returns Full URL to the file\r\n *\r\n * @example\r\n * ```typescript\r\n * const imageUrl = client.getFileUrl('charts/sales-chart.png');\r\n *\r\n * // Use directly in img tag (in environments where query-based auth is supported)\r\n * document.querySelector('img').src = imageUrl;\r\n * ```\r\n */\r\n getFileUrl(filePath: string): string {\r\n const pathb64 = this.encodePathBase64(filePath);\r\n const query = this.buildQueryParams();\r\n return `${this.config.baseUrl}/${this.config.projectId}/apps/file/${pathb64}${query}`;\r\n }\r\n\r\n /**\r\n * Fetches a parquet file and parses it into table data\r\n *\r\n * @param filePath - Relative path to the parquet file\r\n * @param limit - Maximum number of rows to return (default: 100)\r\n * @returns TableData with columns and rows\r\n *\r\n * @example\r\n * ```typescript\r\n * const tableData = await client.getTableData('data/sales.parquet', 50);\r\n * console.log(tableData.columns);\r\n * console.log(tableData.rows);\r\n * console.log(`Total rows: ${tableData.total_rows}`);\r\n * ```\r\n */\r\n async getTableData(filePath: string, limit: number = 100): Promise<TableData> {\r\n const blob = await this.getFile(filePath);\r\n const result = await readParquet(blob, \"data\", limit);\r\n\r\n return {\r\n columns: result.columns,\r\n rows: result.rows,\r\n total_rows: result.rowCount\r\n };\r\n }\r\n}\r\n","import { OxyClient } from \"./client\";\nimport { OxyConfig, createConfigAsync } from \"./config\";\nimport { ParquetReader, QueryResult } from \"./parquet\";\nimport { DataContainer } from \"./types\";\n\n/**\n * OxySDK provides a unified interface for fetching data from Oxy and querying it with SQL.\n * It combines OxyClient (for API calls) and ParquetReader (for SQL queries) into a single,\n * easy-to-use interface.\n *\n * @example\n * ```typescript\n * // Create SDK instance\n * const sdk = new OxySDK({ apiKey: 'your-key', projectId: 'your-project' });\n *\n * // Load a parquet file and query it\n * await sdk.loadFile('data/sales.parquet', 'sales');\n * const result = await sdk.query('SELECT * FROM sales WHERE amount > 1000');\n * console.log(result.rows);\n *\n * // Clean up when done\n * await sdk.close();\n * ```\n */\nexport class OxySDK {\n private client: OxyClient;\n private reader: ParquetReader;\n\n constructor(config: OxyConfig) {\n this.client = new OxyClient(config);\n this.reader = new ParquetReader();\n }\n\n /**\n * Creates an OxySDK instance asynchronously with support for postMessage authentication\n *\n * @param config - Optional configuration overrides\n * @returns Promise resolving to OxySDK instance\n *\n * @example\n * ```typescript\n * // In an iframe - automatic postMessage auth\n * const sdk = await OxySDK.create({\n * parentOrigin: 'https://app.example.com',\n * projectId: 'my-project-id'\n * });\n * ```\n */\n static async create(config?: Partial<OxyConfig>): Promise<OxySDK> {\n const resolvedConfig = await createConfigAsync(config);\n return new OxySDK(resolvedConfig);\n }\n\n /**\n * Load a Parquet file from Oxy and register it for SQL queries\n *\n * @param filePath - Path to the parquet file in the app state directory\n * @param tableName - Name to use for the table in SQL queries\n *\n * @example\n * ```typescript\n * await sdk.loadFile('data/sales.parquet', 'sales');\n * await sdk.loadFile('data/customers.parquet', 'customers');\n *\n * const result = await sdk.query(`\n * SELECT s.*, c.name\n * FROM sales s\n * JOIN customers c ON s.customer_id = c.id\n * `);\n * ```\n */\n async loadFile(filePath: string, tableName: string): Promise<void> {\n const blob = await this.client.getFile(filePath);\n await this.reader.registerParquet(blob, tableName);\n }\n\n /**\n * Load multiple Parquet files at once\n *\n * @param files - Array of file paths and table names\n *\n * @example\n * ```typescript\n * await sdk.loadFiles([\n * { filePath: 'data/sales.parquet', tableName: 'sales' },\n * { filePath: 'data/customers.parquet', tableName: 'customers' },\n * { filePath: 'data/products.parquet', tableName: 'products' }\n * ]);\n *\n * const result = await sdk.query('SELECT * FROM sales');\n * ```\n */\n async loadFiles(\n files: Array<{ filePath: string; tableName: string }>,\n ): Promise<void> {\n for (const file of files) {\n await this.loadFile(file.filePath, file.tableName);\n }\n }\n\n /**\n * Load all data from an app's data container\n *\n * This fetches the app's data and registers all parquet files using their container keys as table names.\n *\n * @param appPath - Path to the app file\n * @returns DataContainer with file references\n *\n * @example\n * ```typescript\n * // If app has data: { sales: { file_path: 'data/sales.parquet' } }\n * const data = await sdk.loadAppData('dashboard.app.yml');\n * // Now you can query the 'sales' table\n * const result = await sdk.query('SELECT * FROM sales LIMIT 10');\n * ```\n */\n async loadAppData(appPath: string): Promise<DataContainer | null> {\n const appDataResponse = await this.client.getAppData(appPath);\n\n if (appDataResponse.error) {\n throw new Error(`Failed to load app data: ${appDataResponse.error}`);\n }\n\n if (!appDataResponse.data) {\n return null;\n }\n\n // Load each file in the data container\n const loadPromises = Object.entries(appDataResponse.data).map(\n async ([tableName, fileRef]) => {\n await this.loadFile(fileRef.file_path, tableName);\n },\n );\n\n await Promise.all(loadPromises);\n\n return appDataResponse.data;\n }\n\n /**\n * Execute a SQL query against loaded data\n *\n * @param sql - SQL query to execute\n * @returns Query result with columns and rows\n *\n * @example\n * ```typescript\n * await sdk.loadFile('data/sales.parquet', 'sales');\n *\n * const result = await sdk.query('SELECT product, SUM(amount) as total FROM sales GROUP BY product');\n * console.log(result.columns); // ['product', 'total']\n * console.log(result.rows); // [['Product A', 1000], ['Product B', 2000]]\n * console.log(result.rowCount); // 2\n * ```\n */\n async query(sql: string): Promise<QueryResult> {\n return this.reader.query(sql);\n }\n\n /**\n * Get all data from a loaded table\n *\n * @param tableName - Name of the table\n * @param limit - Maximum number of rows (optional)\n * @returns Query result\n *\n * @example\n * ```typescript\n * await sdk.loadFile('data/sales.parquet', 'sales');\n * const allData = await sdk.getAll('sales');\n * const first100 = await sdk.getAll('sales', 100);\n * ```\n */\n async getAll(tableName: string, limit?: number): Promise<QueryResult> {\n return this.reader.getAll(tableName, limit);\n }\n\n /**\n * Get schema information for a loaded table\n *\n * @param tableName - Name of the table\n * @returns Schema information\n *\n * @example\n * ```typescript\n * await sdk.loadFile('data/sales.parquet', 'sales');\n * const schema = await sdk.getSchema('sales');\n * console.log(schema.columns); // ['column_name', 'column_type', ...]\n * console.log(schema.rows); // [['id', 'INTEGER'], ['name', 'VARCHAR'], ...]\n * ```\n */\n async getSchema(tableName: string): Promise<QueryResult> {\n return this.reader.getSchema(tableName);\n }\n\n /**\n * Get row count for a loaded table\n *\n * @param tableName - Name of the table\n * @returns Number of rows\n *\n * @example\n * ```typescript\n * await sdk.loadFile('data/sales.parquet', 'sales');\n * const count = await sdk.count('sales');\n * console.log(`Total rows: ${count}`);\n * ```\n */\n async count(tableName: string): Promise<number> {\n return this.reader.count(tableName);\n }\n\n /**\n * Get direct access to the underlying OxyClient\n *\n * Useful for advanced operations like listing apps, getting displays, etc.\n *\n * @returns The OxyClient instance\n *\n * @example\n * ```typescript\n * const apps = await sdk.getClient().listApps();\n * const displays = await sdk.getClient().getDisplays('my-app.app.yml');\n * ```\n */\n getClient(): OxyClient {\n return this.client;\n }\n\n /**\n * Get direct access to the underlying ParquetReader\n *\n * Useful for advanced operations like registering blobs directly.\n *\n * @returns The ParquetReader instance\n *\n * @example\n * ```typescript\n * const myBlob = new Blob([parquetData]);\n * await sdk.getReader().registerParquet(myBlob, 'mydata');\n * ```\n */\n getReader(): ParquetReader {\n return this.reader;\n }\n\n /**\n * Close and cleanup all resources\n *\n * This clears all loaded data and releases resources. Call this when you're done with the SDK.\n *\n * @example\n * ```typescript\n * const sdk = new OxySDK({ apiKey: 'key', projectId: 'project' });\n * await sdk.loadFile('data/sales.parquet', 'sales');\n * const result = await sdk.query('SELECT * FROM sales');\n * await sdk.close(); // Clean up\n * ```\n */\n async close(): Promise<void> {\n await this.reader.close();\n }\n}\n","import React, {\n createContext,\n useContext,\n useEffect,\n useState,\n ReactNode,\n} from \"react\";\nimport { OxySDK } from \"./sdk\";\nimport { OxyConfig } from \"./config\";\n\n/**\n * Context value provided to child components\n */\nexport interface OxyContextValue {\n sdk: OxySDK | null;\n isLoading: boolean;\n error: Error | null;\n}\n\n/**\n * React context for OxySDK\n */\nconst OxyContext = createContext<OxyContextValue | undefined>(undefined);\n\n/**\n * Props for OxyProvider component\n */\nexport interface OxyProviderProps {\n children: ReactNode;\n config?: Partial<OxyConfig>;\n /**\n * If true, uses async initialization (supports postMessage auth in iframes)\n * If false, uses synchronous initialization with provided config\n */\n useAsync?: boolean;\n /**\n * Optional app path to load initial app data from upon initialization\n */\n appPath?: string;\n /**\n * Optional initial files to preload into the SDK as a mapping of filename to content\n */\n files?: Record<string, string>;\n /**\n * Called when SDK is successfully initialized\n */\n onReady?: (sdk: OxySDK) => void;\n /**\n * Called when initialization fails\n */\n onError?: (error: Error) => void;\n /**\n * Rendered while the SDK is initializing. Defaults to rendering children (no loading UI).\n */\n loadingFallback?: ReactNode;\n /**\n * Rendered when SDK initialization fails. Receives the error.\n * Defaults to rendering children (no error UI).\n */\n errorFallback?: ReactNode | ((error: Error) => ReactNode);\n}\n\n/**\n * Provider component that initializes and provides OxySDK to child components\n *\n * @example\n * ```tsx\n * // Synchronous initialization with config\n * function App() {\n * return (\n * <OxyProvider config={{\n * apiKey: 'your-key',\n * projectId: 'your-project',\n * baseUrl: 'https://api.oxy.tech'\n * }}>\n * <Dashboard />\n * </OxyProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Async initialization (for iframe/postMessage auth)\n * function App() {\n * return (\n * <OxyProvider\n * useAsync\n * config={{ parentOrigin: 'https://app.example.com' }}\n * >\n * <Dashboard />\n * </OxyProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With environment variables\n * import { createConfig } from '@oxy/sdk';\n *\n * function App() {\n * return (\n * <OxyProvider config={createConfig()}>\n * <Dashboard />\n * </OxyProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With loading and error slots\n * function App() {\n * return (\n * <OxyProvider\n * config={createConfig()}\n * loadingFallback={<div>Loading SDK...</div>}\n * errorFallback={(error) => <div>Error: {error.message}</div>}\n * >\n * <Dashboard />\n * </OxyProvider>\n * );\n * }\n * ```\n */\nexport function OxyProvider({\n children,\n config,\n useAsync = false,\n appPath,\n files,\n onReady,\n onError,\n loadingFallback,\n errorFallback,\n}: OxyProviderProps) {\n const [sdk, setSdk] = useState<OxySDK | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let mounted = true;\n let sdkInstance: OxySDK | null = null;\n\n async function initializeSDK() {\n try {\n setIsLoading(true);\n setError(null);\n\n if (useAsync) {\n // Async initialization (supports postMessage auth)\n sdkInstance = await OxySDK.create(config);\n if (appPath) {\n await sdkInstance.loadAppData(appPath);\n }\n\n if (files) {\n const fileEntries = Object.entries(files).map(\n ([tableName, filePath]) => ({\n tableName,\n filePath,\n }),\n );\n await sdkInstance.loadFiles(fileEntries);\n }\n } else {\n // Sync initialization with provided config\n if (!config) {\n throw new Error(\n \"Config is required when useAsync is false. Either provide config or set useAsync=true.\",\n );\n }\n sdkInstance = new OxySDK(config as OxyConfig);\n }\n\n if (mounted) {\n setSdk(sdkInstance);\n setIsLoading(false);\n onReady?.(sdkInstance);\n }\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to initialize SDK\");\n\n if (mounted) {\n setError(error);\n setIsLoading(false);\n onError?.(error);\n }\n }\n }\n\n initializeSDK();\n\n // Cleanup function\n return () => {\n mounted = false;\n if (sdkInstance) {\n sdkInstance.close().catch(console.error);\n }\n };\n }, [config, useAsync, onReady, onError]);\n\n const content = (() => {\n if (isLoading && loadingFallback !== undefined) return loadingFallback;\n if (error && errorFallback !== undefined) {\n return typeof errorFallback === \"function\" ? errorFallback(error) : errorFallback;\n }\n return children;\n })();\n\n return (\n <OxyContext.Provider value={{ sdk, isLoading, error }}>\n {content}\n </OxyContext.Provider>\n );\n}\n\n/**\n * Hook to access OxySDK from child components\n *\n * @throws {Error} If used outside of OxyProvider\n * @returns {OxyContextValue} The SDK instance, loading state, and error\n *\n * @example\n * ```tsx\n * function Dashboard() {\n * const { sdk, isLoading, error } = useOxy();\n *\n * useEffect(() => {\n * if (sdk) {\n * sdk.loadAppData('dashboard.app.yml')\n * .then(() => sdk.query('SELECT * FROM my_table'))\n * .then(result => console.log(result));\n * }\n * }, [sdk]);\n *\n * if (isLoading) return <div>Loading SDK...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * if (!sdk) return null;\n *\n * return <div>Dashboard</div>;\n * }\n * ```\n */\nexport function useOxy(): OxyContextValue {\n const context = useContext(OxyContext);\n\n if (context === undefined) {\n throw new Error(\"useOxy must be used within an OxyProvider\");\n }\n\n return context;\n}\n\n/**\n * Hook to access OxySDK that throws if not ready\n *\n * This is a convenience hook that returns the SDK directly or throws an error if not initialized.\n * Use this when you know the SDK should be ready.\n *\n * @throws {Error} If used outside of OxyProvider or if SDK is not initialized\n * @returns {OxySDK} The SDK instance\n *\n * @example\n * ```tsx\n * function DataTable() {\n * const sdk = useOxySDK();\n * const [data, setData] = useState(null);\n *\n * useEffect(() => {\n * sdk.loadFile('data.parquet', 'data')\n * .then(() => sdk.query('SELECT * FROM data LIMIT 100'))\n * .then(setData);\n * }, [sdk]);\n *\n * return <table>...</table>;\n * }\n * ```\n */\nexport function useOxySDK(): OxySDK {\n const { sdk, isLoading, error } = useOxy();\n\n if (error) {\n throw error;\n }\n\n if (isLoading || !sdk) {\n throw new Error(\"OxySDK is not yet initialized\");\n }\n\n return sdk;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/customer-app/logger.ts","../src/customer-app/debug.ts","../src/customer-app/errors.ts","../src/customer-app/inject.ts","../src/customer-app/manifest.ts","../src/customer-app/interpolate.ts","../src/customer-app/markdown.ts","../src/customer-app/react.tsx"],"sourcesContent":["// Diagnostic logger for customer-app bundles.\n//\n// Customer apps are built by our internal team; \"open DevTools, read\n// the logs\" is a real debugging workflow. The SDK logs every fetch\n// lifecycle (start, success, error) with structured context so an\n// internal dev can correlate UI behavior with what hit the wire\n// without needing server logs.\n//\n// Defaults to console at info level with a `[oxy-app]` prefix.\n// Override via `setOxyAppLogger(...)` for tests or production silence.\n//\n// Log lines are formatted as:\n// [oxy-app] <event> { …structured ctx… }\n// so DevTools' object inspector unfolds them.\n\nexport type OxyAppLogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface OxyAppLogger {\n log(level: OxyAppLogLevel, msg: string, ctx?: Record<string, unknown>): void;\n}\n\nlet activeLogger: OxyAppLogger = createConsoleLogger();\n\n/** Replace the global logger. Pass `null` to silence everything. */\nexport function setOxyAppLogger(logger: OxyAppLogger | null): void {\n activeLogger = logger ?? silentLogger();\n}\n\n/** Used by the SDK internals; not part of the public surface. */\nexport function getOxyAppLogger(): OxyAppLogger {\n return activeLogger;\n}\n\nfunction createConsoleLogger(): OxyAppLogger {\n return {\n log(level, msg, ctx) {\n if (typeof console === \"undefined\") return;\n const prefix = \"[oxy-app]\";\n const args: unknown[] = ctx ? [prefix, msg, ctx] : [prefix, msg];\n switch (level) {\n case \"debug\":\n console.debug(...args);\n break;\n case \"info\":\n console.info(...args);\n break;\n case \"warn\":\n console.warn(...args);\n break;\n case \"error\":\n console.error(...args);\n break;\n }\n }\n };\n}\n\nfunction silentLogger(): OxyAppLogger {\n return { log() {} };\n}\n","// Bundle-side accessor for the server's diagnostic snapshot.\n//\n// `GET /api/customer-apps/<org>/<app>/debug` returns a structured\n// snapshot of what oxy currently sees about a registered customer\n// app: the app row, bundle dir resolution, and parsed manifest (or\n// parse error). Useful when a bundle isn't loading what you expected\n// and you want to verify what the server actually sees — without\n// needing terminal access.\n//\n// The `products` field on the snapshot is a legacy artifact carried\n// for server-side compatibility; in v2 the bundle owns its queries\n// via `useQuery` and the field is always empty for v2 manifests.\n\nimport { getOxyAppLogger } from \"./logger\";\nimport type { ResolvedCustomerAppManifest } from \"./manifest\";\n\n/** Untyped at the boundary — keep it loose so server-side schema\n * additions don't break older bundles. Stable enough for inspection\n * but not a contract clients should depend on field-by-field. */\nexport interface CustomerAppDebugSnapshot {\n org_slug: string;\n app_slug: string;\n app: {\n id: string;\n slug: string;\n name: string;\n status: string;\n source_type: string;\n project_id: string;\n branch: string;\n };\n bundle_dir: string | null;\n bundle_dir_exists: boolean;\n /** Raw parsed manifest from the server — kept loose so schema additions don't break older bundles. */\n manifest: Record<string, unknown> | null;\n manifest_error: string | null;\n products: Array<{ name: string; producer: string }>;\n}\n\n/**\n * Fetch the server-side diagnostic snapshot for this bundle. Pair with\n * `loadCustomerAppManifest()` — pass its result here. Logs the\n * snapshot through the SDK logger so it appears in the bundle's\n * console at info level.\n */\nexport async function getCustomerAppDebug(\n resolved: ResolvedCustomerAppManifest\n): Promise<CustomerAppDebugSnapshot> {\n const log = getOxyAppLogger();\n const { apiBaseUrl, orgSlug, appSlug } = resolved;\n const url =\n `${apiBaseUrl}/api/customer-apps/` +\n `${encodeURIComponent(orgSlug)}/${encodeURIComponent(appSlug)}/debug`;\n\n log.log(\"debug\", \"fetching debug snapshot\", { url });\n const res = await fetch(url, { credentials: \"same-origin\" });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch debug snapshot (HTTP ${res.status}): ${detail || res.statusText}`\n );\n }\n const snapshot = (await res.json()) as CustomerAppDebugSnapshot;\n log.log(\"info\", \"debug snapshot\", snapshot as unknown as Record<string, unknown>);\n return snapshot;\n}\n","// Friendly interpretation of the errors a customer-app bundle can hit\n// at startup. The bundle catches an `Error` thrown by\n// `loadCustomerAppManifest` or `useQuery`, hands it to\n// `interpretCustomerAppError`, and renders the returned struct as a\n// proper error page — instead of dumping a raw exception that asks\n// the developer to learn the internal contract from a stack trace.\n//\n// Every interpretation includes:\n// - `title`: short headline (\"Manifest not found\")\n// - `message`: the underlying technical message (the raw err.message)\n// - `hint`: an actionable next step (\"commit public/oxy-app.json and rebuild\")\n// - `docs`: a pointer to the relevant section of the architecture doc\n//\n// Add cases as we hit new failure modes in the wild — the catch-all\n// keeps the surface safe in the meantime.\n\nexport interface CustomerAppErrorReport {\n title: string;\n message: string;\n hint: string;\n docs?: string;\n}\n\nconst ARCH_DOC = \"internal-docs/customer-apps.md\";\n\n/** Interpret a thrown error as a structured report for UI display. */\nexport function interpretCustomerAppError(err: unknown): CustomerAppErrorReport {\n const message = err instanceof Error ? err.message : String(err);\n\n // Order matters — earlier matches take priority. Use specific\n // substrings that the loader / fetcher actually emit so this stays\n // grep-discoverable from both ends.\n\n if (/Failed to load oxy-app\\.json.*HTTP 404/.test(message)) {\n return {\n title: \"Manifest not found\",\n message,\n hint:\n \"The bundle is being served, but oxy-app.json was not. Check that \" +\n \"public/oxy-app.json is committed in the customer-app repo and \" +\n \"that the build copied it into the static output. If you're using \" +\n \"Next.js, anything under public/ is auto-copied to out/.\",\n docs: ARCH_DOC\n };\n }\n\n if (/Failed to load oxy-app\\.json/.test(message)) {\n return {\n title: \"Manifest could not be loaded\",\n message,\n hint:\n \"Network error fetching the manifest. Confirm the bundle is being \" +\n \"served from a path that matches OXY_APP_BASE_PATH at \" +\n \"build time — a mismatch causes assets and the manifest to 404.\",\n docs: ARCH_DOC\n };\n }\n\n if (/schemaVersion/i.test(message)) {\n return {\n title: \"Manifest schema mismatch\",\n message,\n hint:\n \"This bundle was built against a different version of the \" +\n \"data-product contract than the SDK it ships. Rebuild the bundle \" +\n \"with a compatible @oxy-hq/sdk version.\",\n docs: ARCH_DOC\n };\n }\n\n // Query proxy responses. The `${status}: ${body}` shape comes from\n // useQuery — body is the server's `{ \"message\": \"...\" }` JSON for\n // structured errors, or raw text for unstructured ones.\n\n if (/^401:/m.test(message)) {\n return {\n title: \"Session expired\",\n message,\n hint: \"Reload the page to re-authenticate via oxy's session cookie.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*origin not allowed/im.test(message)) {\n return {\n title: \"Request origin not allowed\",\n message,\n hint:\n \"The bundle's host isn't in oxy's OXY_ALLOWED_ORIGINS. \" +\n \"Production: add the bundle's serving origin to the env var. \" +\n \"Local dev: oxy auto-allows http://localhost:5173 and :5174.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*not a member/im.test(message)) {\n return {\n title: \"Access denied\",\n message,\n hint:\n \"Your account isn't a member of the org that owns this project. \" +\n \"Ask an org owner to add you.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:.*SELECT.*WITH/im.test(message)) {\n return {\n title: \"Query rejected — read-only endpoint\",\n message,\n hint:\n \"This proxy only runs SELECT or WITH queries. Mutations \" +\n \"(INSERT/UPDATE/DELETE/DROP) are not allowed from customer-app \" +\n \"bundles.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^403:/m.test(message)) {\n return {\n title: \"Access denied\",\n message,\n hint: \"The request was rejected by the server. Check the oxy server \" + \"logs for details.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^404:/m.test(message) && /project/i.test(message)) {\n return {\n title: \"Project not found\",\n message,\n hint:\n \"The projectId in oxy-app.json doesn't match any registered \" +\n \"project. Confirm the manifest's projectId is a real UUID for \" +\n \"this deployment.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^400:.*sql.*must be non-empty/im.test(message)) {\n return {\n title: \"Empty SQL\",\n message,\n hint:\n \"useQuery was called with an empty or whitespace-only `sql`. \" +\n \"Pass a real query, or set `enabled: false` to skip the call.\",\n docs: ARCH_DOC\n };\n }\n\n if (/^400:/m.test(message) && /query failed/i.test(message)) {\n return {\n title: \"Query failed\",\n message,\n hint:\n \"The SQL ran but the warehouse rejected it. Full error in the \" +\n \"oxy server logs (look for the projects::query span).\",\n docs: ARCH_DOC\n };\n }\n\n if (/^502:/m.test(message)) {\n return {\n title: \"Warehouse unreachable\",\n message,\n hint:\n \"Oxy couldn't reach the configured database. Check connector \" +\n \"config + warehouse health.\",\n docs: ARCH_DOC\n };\n }\n\n // \"Unexpected token '<', '<!doctype '...\" — the bundle asked for JSON\n // at a path that oxy resolved to its SPA-fallback HTML.\n //\n // In v2 the single most likely cause is: the built bundle is stale.\n // It was built against an older SDK whose useQuery / fetchers pointed\n // at endpoints that no longer exist (e.g. the deleted /products/...\n // route), so the request resolved to the SPA fallback and returned\n // index.html where the bundle expected a JSON body.\n if (/Unexpected token '<'|<!doctype/i.test(message)) {\n return {\n title: \"Fetched HTML where JSON was expected\",\n message,\n hint:\n \"Most likely the built bundle is stale — built against an old \" +\n \"SDK whose endpoints no longer exist on the server. Rebuild the \" +\n \"bundle (vite build) with @oxy-hq/sdk@^2.0.0 and reload. If \" +\n \"the bundle is current, check that OXY_APP_BASE_PATH matches \" +\n \"the path the customer-app row is served at.\",\n docs: ARCH_DOC\n };\n }\n\n // Catch-all — surfaces the raw message but with a generic next step.\n return {\n title: \"Unexpected error loading the dashboard\",\n message,\n hint:\n \"Check the browser console for the full stack trace, and the oxy \" +\n \"server logs for the corresponding request.\",\n docs: ARCH_DOC\n };\n}\n","// Runtime app-config injected into the browser by oxy when it serves\n// a customer-app bundle's HTML. Lets a single bundle serve any\n// registered app without having `(orgId, projectId)` baked in at\n// build time — see\n// `crates/app/src/server/api/customer_apps_serve.rs::inject_app_config`\n// on the server side.\n\n/**\n * Shape of `window.__OXY_APP__` written by oxy at serve time.\n * Consumed by `loadCustomerAppManifest` as the authoritative identity\n * source (overrides any hints in `oxy-app.json`).\n */\nexport interface OxyInjectedAppConfig {\n appId: string;\n slug: string;\n orgId: string;\n orgSlug: string;\n projectId: string;\n branch: string;\n /** Empty string means same-origin (the default for v2). */\n apiBaseUrl: string;\n}\n\ndeclare global {\n interface Window {\n __OXY_APP__?: OxyInjectedAppConfig;\n }\n}\n\n/**\n * Read the runtime app-config oxy injected at serve time. Returns\n * `undefined` outside the browser or when the global isn't set\n * (`pnpm dev` against a non-oxy server, etc. — manifest hints are\n * the fallback).\n */\nexport function readInjectedAppConfig(): OxyInjectedAppConfig | undefined {\n if (typeof window === \"undefined\") return undefined;\n return window.__OXY_APP__;\n}\n","// Manifest loader for customer-app bundles served by oxy at\n// `app.oxy.tech/customer-apps/<org_slug>/<app_slug>/`.\n//\n// The bundle commits a `public/oxy-app.json` declaring its identity\n// (slug, orgSlug, projectId). This module:\n// 1. Fetches that manifest at startup (cached after the first call).\n// 2. Validates the schema with clear errors (v2 only — v1 is rejected).\n// 3. Joins it with the runtime identity oxy injects via\n// `<script>window.__OXY_APP__=...</script>`.\n//\n// Bundles call `useQuery` directly for data access — there are no\n// `products` or `writers` declarations in v2 manifests.\n\nimport { type OxyInjectedAppConfig, readInjectedAppConfig } from \"./inject\";\nimport { getOxyAppLogger } from \"./logger\";\n\n// ── Manifest types ──────────────────────────────────────────────────────────\n\n/** Wire shape of `oxy-app.json` (v2 only). */\nexport interface OxyAppManifest {\n /** Must be 2. v1 manifests are no longer supported. */\n schemaVersion: 2;\n /**\n * Optional display name. The admin \"Link existing\" dialog prefills\n * its Name field from this. Omit to let oxy fall back to the\n * folder basename.\n */\n name?: string;\n /**\n * URL slug. **Required.** The canonical source of truth — the\n * dialog locks the slug field to this value, and\n * `OXY_APP_BASE_PATH=/customer-apps/<org>/<slug>/` baked into the\n * build must match.\n */\n slug: string;\n /**\n * Optional org slug. Prefills the dialog's org picker; operator\n * can still override. Carries no security weight — the actual\n * access check is on the linked row.\n */\n orgSlug?: string;\n /**\n * Optional project (workspace) uuid the bundle expects to read\n * from. Used by `useQuery` to construct the\n * `/api/projects/:id/query` URL.\n */\n projectId?: string;\n}\n\n// ── Resolved manifest ───────────────────────────────────────────────────────\n\n/**\n * Manifest + runtime-injected identity needed to call oxy. Callers\n * should treat this as the only source of truth for \"which org/app\n * does this bundle belong to.\"\n */\nexport interface ResolvedCustomerAppManifest {\n manifest: OxyAppManifest;\n /**\n * Always an empty array for v2 manifests. Kept for API compatibility;\n * callers that previously iterated product names should switch to\n * explicit `useQuery` calls.\n * @deprecated Will be removed in a future version.\n */\n productNames: string[];\n /** Org slug injected by oxy. */\n orgSlug: string;\n /** App slug injected by oxy. */\n appSlug: string;\n /**\n * The oxy server's API base URL. Empty string when oxy serves the\n * bundle itself (same-origin, the common case); a full URL only\n * when the bundle is running under a dev server proxy.\n */\n apiBaseUrl: string;\n /** App UUID; informational. */\n appId?: string;\n /**\n * Project (workspace) UUID. Injection (`window.__OXY_APP__.projectId`)\n * wins over the manifest's `projectId` field — the admin row is\n * authoritative. Manifest `projectId` is a dev-time hint used only\n * when running without a server. Used by `useQuery` to construct the\n * `/api/projects/:id/query` URL.\n */\n projectId?: string;\n}\n\nexport interface LoadManifestOptions {\n /**\n * Override the URL the manifest is fetched from. Default:\n * `<injected_base>/oxy-app.json` or `/oxy-app.json`.\n * Useful for non-Next bundlers — set explicitly to wherever your\n * bundler emits static assets.\n */\n manifestUrl?: string;\n}\n\nlet cached: Promise<ResolvedCustomerAppManifest> | null = null;\n\n/**\n * Load + validate the manifest. Cached after the first call so callers\n * can invoke this from every component without coordinating.\n */\nexport function loadCustomerAppManifest(\n options: LoadManifestOptions = {}\n): Promise<ResolvedCustomerAppManifest> {\n if (!cached) {\n cached = fetchAndValidate(options);\n }\n return cached;\n}\n\n/** For tests: reset the cache between runs. */\nexport function _resetCustomerAppManifestCacheForTest(): void {\n cached = null;\n}\n\nasync function fetchAndValidate(\n options: LoadManifestOptions\n): Promise<ResolvedCustomerAppManifest> {\n const log = getOxyAppLogger();\n const injected = readInjectedAppConfig();\n const manifestUrl = options.manifestUrl ?? defaultManifestUrl(injected);\n\n log.log(\"info\", \"loading manifest\", {\n manifestUrl,\n injectionPresent: !!injected,\n orgSlug: injected?.orgSlug,\n appSlug: injected?.slug,\n appId: injected?.appId\n });\n\n const startedAt = Date.now();\n const res = await fetch(manifestUrl, { credentials: \"same-origin\" });\n if (!res.ok) {\n log.log(\"error\", \"manifest fetch failed\", {\n manifestUrl,\n status: res.status,\n statusText: res.statusText\n });\n throw new Error(\n `Failed to load oxy-app.json from ${manifestUrl} (HTTP ${res.status}). ` +\n `The customer-app repo must commit this file alongside the bundle.`\n );\n }\n const raw = (await res.json()) as unknown;\n const manifest = validateManifest(raw, manifestUrl);\n\n const resolved: ResolvedCustomerAppManifest = {\n manifest,\n productNames: [],\n orgSlug: injected?.orgSlug ?? \"\",\n appSlug: injected?.slug ?? \"\",\n apiBaseUrl: injected?.apiBaseUrl || \"\",\n appId: injected?.appId,\n projectId: injected?.projectId ?? manifest.projectId\n };\n log.log(\"info\", \"manifest ready\", {\n durationMs: Date.now() - startedAt,\n schemaVersion: manifest.schemaVersion,\n slug: manifest.slug\n });\n return resolved;\n}\n\n/**\n * Default manifest URL.\n *\n * Resolution order (bundler-agnostic):\n * 1. `window.__OXY_APP__.orgSlug`/`slug` injection → the canonical\n * `/customer-apps/<org>/<app>/oxy-app.json`. Works for every\n * bundle oxy serves regardless of how it was built.\n * 2. `NEXT_PUBLIC_APP_BASE_PATH` env var — kept for backward compat\n * with Next.js bundles that bake basePath at build time.\n * 3. Empty basePath → `/oxy-app.json` (only matches when running in\n * a `vite dev` / `next dev` root mount; will 404 under oxy).\n */\nfunction defaultManifestUrl(injected: OxyInjectedAppConfig | undefined): string {\n if (injected?.orgSlug && injected?.slug) {\n const org = encodeURIComponent(injected.orgSlug);\n const app = encodeURIComponent(injected.slug);\n return `/customer-apps/${org}/${app}/oxy-app.json`;\n }\n // No injection → bundle is running outside oxy (`pnpm dev` against\n // a local Vite, an iframe preview, etc.). Look up `/oxy-app.json`\n // at the document root; the vite-plugin's dev shim and the\n // standard `public/` convention both serve it there.\n return \"/oxy-app.json\";\n}\n\n// ── Validation ──────────────────────────────────────────────────────────────\n\n/**\n * Validate a v2 manifest. Required: schemaVersion === 2, slug (non-empty).\n * Optional: name (display), orgSlug (dev-time hint for the admin dialog),\n * projectId (dev-time hint when there's no server-side injection).\n *\n * At serve time, oxy's identity injection (window.__OXY_APP__) overrides\n * the manifest's orgSlug/projectId — the manifest fields are advisory.\n */\nfunction validateManifest(raw: unknown, url: string): OxyAppManifest {\n if (!isRecord(raw)) {\n throw new Error(`Manifest at ${url} is not a JSON object`);\n }\n if (raw.schemaVersion !== 2) {\n throw new Error(\n `oxy-app.json: schemaVersion must be 2 (got ${JSON.stringify(raw.schemaVersion)}). ` +\n `v1 manifests are no longer supported — upgrade to the identity-only shape.`\n );\n }\n if (raw.products !== undefined || raw.writers !== undefined) {\n throw new Error(\n `oxy-app.json is schemaVersion 2 (identity-only); \\`products\\` and \\`writers\\` are no longer supported`\n );\n }\n if (typeof raw.slug !== \"string\" || !raw.slug.trim()) {\n throw new Error(\"oxy-app.json: `slug` is required and must be a non-empty string\");\n }\n\n const name = typeof raw.name === \"string\" ? raw.name : undefined;\n const slug = raw.slug;\n const orgSlug = typeof raw.orgSlug === \"string\" ? raw.orgSlug : undefined;\n const projectId = typeof raw.projectId === \"string\" ? raw.projectId : undefined;\n\n return { schemaVersion: 2, name, slug, orgSlug, projectId };\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n","/**\n * Interpolate `{{ params.X }}` and `{{ params.X | sqlquote }}` placeholders\n * in a SQL template.\n *\n * - `{{ params.X | sqlquote }}` — quote strings ('foo'), pass numbers and\n * booleans raw, nullish becomes NULL. Mirrors the server's Jinja sqlquote\n * filter.\n * - `{{ params.X }}` — raw pass-through. Used for already-trusted values\n * (numbers, identifiers the caller has validated). Caller is responsible\n * for safety.\n *\n * Not a security boundary. The server still gates SQL execution by\n * project membership. Bundles that accept untrusted user input should\n * use `| sqlquote` or validate/coerce before passing.\n */\nexport function interpolateSqlParams(\n sql: string,\n params: Record<string, string | number | boolean | null | undefined>\n): string {\n return sql.replace(\n /\\{\\{\\s*params\\.([a-zA-Z0-9_]+)(\\s*\\|\\s*sqlquote)?\\s*\\}\\}/g,\n (_match, key: string, sqlquote: string | undefined) => {\n const v = params[key];\n if (v === null || v === undefined) return \"NULL\";\n if (sqlquote) {\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n return `'${String(v).replace(/'/g, \"''\")}'`;\n }\n // No filter — raw pass-through. Caller is responsible.\n return String(v);\n }\n );\n}\n","// Markdown helpers used by `<OxyAnswer>`.\n//\n// Extracted from `react.tsx` so they're testable without spinning up\n// a React renderer in unit tests. The renderer itself stays in\n// `react.tsx` because it's JSX-heavy.\n\n/**\n * Allowlist for `[text](url)` href values in agent-emitted markdown.\n * Markdown comes from an LLM, which sits across an external trust\n * boundary — without this filter, a `javascript:` URL produced by\n * the model would render as a clickable XSS in the bundle's origin.\n *\n * Accepts:\n * - http(s):// absolute URLs\n * - mailto: addresses\n * - root-relative paths (`/foo`)\n * - same-page fragments (`#section`)\n *\n * Rejects everything else, including `javascript:`, `data:`,\n * protocol-relative `//evil.com`, and any other scheme. Comparison\n * is case-insensitive after stripping leading whitespace + ASCII\n * control bytes (browsers strip these before scheme resolution, so\n * `java\\tscript:` would otherwise slip past a naive prefix check).\n */\nexport function isSafeLinkHref(raw: string): boolean {\n // Built char-by-char rather than via regex to avoid embedding\n // actual control bytes in source (linters/IDEs mangle them).\n let cleaned = \"\";\n for (let i = 0; i < raw.length; i++) {\n const cc = raw.charCodeAt(i);\n if (cc > 0x20 && cc !== 0x7f) cleaned += raw[i];\n }\n if (cleaned === \"\") return false;\n if (cleaned.startsWith(\"#\") || cleaned.startsWith(\"/\")) {\n // Reject protocol-relative (`//host/...`) — resolves against\n // current scheme + host, a classic open-redirect vector.\n if (cleaned.startsWith(\"//\")) return false;\n return true;\n }\n const lower = cleaned.toLowerCase();\n return lower.startsWith(\"http://\") || lower.startsWith(\"https://\") || lower.startsWith(\"mailto:\");\n}\n","// React provider + hooks for customer-app bundles.\n//\n// Every customer app does the same dance: load the manifest once at\n// boot, then fire queries as components mount. The bundle developer\n// shouldn't have to thread the resolved manifest through every prop\n// or wire a custom context per app — that's what this file is for.\n//\n// Usage:\n//\n// import { OxyAppProvider, useQuery } from \"@oxy-hq/sdk\";\n//\n// function App() {\n// return <OxyAppProvider><Dashboard /></OxyAppProvider>;\n// }\n// function Dashboard() {\n// const { rows, error, loading } = useQuery({ sql: \"SELECT 1\" });\n// ...\n// }\n//\n// Loading + error states are per-query so the bundle can render\n// per-widget skeletons.\n\nimport * as React from \"react\";\nimport { type CustomerAppErrorReport, interpretCustomerAppError } from \"./errors\";\nimport { interpolateSqlParams } from \"./interpolate\";\nimport {\n type LoadManifestOptions,\n loadCustomerAppManifest,\n type ResolvedCustomerAppManifest\n} from \"./manifest\";\nimport { isSafeLinkHref } from \"./markdown\";\n\n// ── Context ─────────────────────────────────────────────────────────────────\n\n/**\n * Credentialed fetch wrapper stored in context so `useQuery` can share\n * the same request mechanism without coupling it to the global `fetch`.\n *\n * Sends `credentials: \"include\"` so the session cookie rides along when\n * the app is served by oxy (in-workspace / admin preview) — that cookie\n * authorizes data calls. For local dev (cross-origin), the\n * `@oxy-hq/vite-plugin` proxy attaches the developer's token. Bundles may\n * override the fetcher for test/proxy environments.\n */\nexport type AppFetcher = typeof fetch;\n\nfunction defaultFetcher(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n return fetch(input, { credentials: \"include\", ...init });\n}\n\ninterface OxyAppContextValue {\n status: \"loading\" | \"ready\" | \"error\";\n resolved?: ResolvedCustomerAppManifest;\n error?: CustomerAppErrorReport;\n /** Credentialed fetch implementation shared by all hooks. */\n fetcher: AppFetcher;\n}\n\nconst OxyAppContext = React.createContext<OxyAppContextValue | undefined>(undefined);\n\nexport interface OxyAppProviderProps {\n /** Optional manifest load options. Same shape as `loadCustomerAppManifest`. */\n manifestOptions?: LoadManifestOptions;\n /**\n * Rendered while the manifest is loading. Defaults to nothing; pass a\n * spinner if you want one.\n */\n fallback?: React.ReactNode;\n /**\n * Rendered on manifest load failure. Receives the structured error\n * report so the bundle can show its own branded error card. Defaults\n * to a minimal text-only fallback (better than a blank page).\n */\n errorFallback?: (err: CustomerAppErrorReport) => React.ReactNode;\n /**\n * Override the fetch implementation used by all hooks (`useQuery`).\n * Useful for test environments or proxy setups. Defaults to a wrapper\n * that sets `credentials: \"include\"` on every request.\n */\n fetcher?: AppFetcher;\n children: React.ReactNode;\n}\n\n/**\n * Top-level provider. Loads the manifest once on mount; children only\n * render after the manifest is ready (or the error fallback fires).\n */\nexport function OxyAppProvider(props: OxyAppProviderProps): React.JSX.Element {\n const { manifestOptions, fallback, errorFallback, fetcher: fetcherProp, children } = props;\n // Stable fetcher reference: caller-supplied or the module-level default.\n // We don't put this in state because it should never change after mount\n // (same reasoning as manifestOptions).\n const fetcher = fetcherProp ?? defaultFetcher;\n const [state, setState] = React.useState<OxyAppContextValue>({ status: \"loading\", fetcher });\n\n React.useEffect(() => {\n let cancelled = false;\n loadCustomerAppManifest(manifestOptions)\n .then((resolved) => {\n if (!cancelled) setState({ status: \"ready\", resolved, fetcher });\n })\n .catch((e: unknown) => {\n if (!cancelled) setState({ status: \"error\", error: interpretCustomerAppError(e), fetcher });\n });\n return () => {\n cancelled = true;\n };\n // `manifestOptions` is treated as stable — changing it mid-flight\n // wouldn't make sense for a manifest load (the URL is baked at\n // build time). `fetcher` is derived from props but also stable;\n // it's included here so biome is satisfied and so the effect does\n // update if a test swaps the fetcher between renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [manifestOptions, fetcher]);\n\n if (state.status === \"error\" && state.error) {\n const err = state.error;\n return (\n <OxyAppContext.Provider value={state}>\n {errorFallback ? errorFallback(err) : defaultErrorFallback(err)}\n </OxyAppContext.Provider>\n );\n }\n if (state.status === \"loading\") {\n return <OxyAppContext.Provider value={state}>{fallback ?? null}</OxyAppContext.Provider>;\n }\n return <OxyAppContext.Provider value={state}>{children}</OxyAppContext.Provider>;\n}\n\nfunction defaultErrorFallback(err: CustomerAppErrorReport): React.ReactNode {\n // Intentionally inline-styled with system fonts so it works in any\n // bundle without depending on Tailwind / CSS-in-JS / etc.\n return (\n <div\n style={{\n margin: \"2rem auto\",\n maxWidth: \"640px\",\n padding: \"1rem\",\n border: \"1px solid #fca5a5\",\n background: \"#fee2e2\",\n color: \"#991b1b\",\n borderRadius: \"8px\",\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n fontSize: \"14px\"\n }}\n >\n <div style={{ fontWeight: 600 }}>{err.title}</div>\n <pre style={{ fontSize: \"12px\", marginTop: \"4px\" }}>{err.message}</pre>\n <div style={{ marginTop: \"12px\" }}>\n <strong>What to try:</strong> {err.hint}\n </div>\n </div>\n );\n}\n\n// ── Beta-warning helper ─────────────────────────────────────────────────────\n\n/**\n * Emit a one-time `console.warn` the first time a beta hook is\n * used in a given page load. Bundles upgrading from a future GA\n * release won't see the warning; the message lets us flag rough\n * edges without breaking the build.\n */\nconst _warnedBeta = new Set<string>();\nfunction warnBetaOnce(name: string): void {\n if (_warnedBeta.has(name)) return;\n _warnedBeta.add(name);\n if (typeof console !== \"undefined\" && typeof console.warn === \"function\") {\n console.warn(\n `[@oxy-hq/sdk] \\`${name}\\` is in beta — interface and behavior may change. ` +\n `See https://github.com/oxy-hq/customer-apps for caveats and the migration guide.`\n );\n }\n}\n\n// ── Error helpers ───────────────────────────────────────────────────────────\n\n/**\n * Error thrown by all customer-app hooks when an API call returns a\n * non-2xx response. Carries the structured `code` + `hint` the server\n * emits so bundle UIs can render an actionable message instead of\n * \"404: { ...json... }\".\n *\n * The server contract is documented in\n * `crates/app/src/server/api/projects/agent_ask.rs` and\n * `procedure_run.rs` — both emit `{ message, code?, hint? }` as JSON.\n * Hooks that previously wrapped the raw text in `new Error()` now\n * throw this type instead.\n */\nexport class OxyApiError extends Error {\n readonly status: number;\n readonly code: string | null;\n readonly hint: string | null;\n constructor(opts: {\n status: number;\n message: string;\n code?: string | null;\n hint?: string | null;\n }) {\n // Build a single multi-line `message` so consumers that just\n // render `.message` still see the hint. Code is included so\n // logs / dev tools can grep for it.\n const base = opts.message || `HTTP ${opts.status}`;\n const code = opts.code ? ` [${opts.code}]` : \"\";\n const hint = opts.hint ? `\\n\\n${opts.hint}` : \"\";\n super(`${base}${code}${hint}`);\n this.name = \"OxyApiError\";\n this.status = opts.status;\n this.code = opts.code ?? null;\n this.hint = opts.hint ?? null;\n }\n}\n\n/**\n * Read a non-2xx response from oxy and return an `OxyApiError`.\n * Parses the JSON envelope when present; falls back to raw text\n * (truncated to 240 chars so a runaway HTML error page doesn't\n * dominate the bundle UI).\n */\nasync function apiErrorFromResponse(resp: Response): Promise<OxyApiError> {\n let body: unknown;\n let raw = \"\";\n try {\n raw = await resp.text();\n body = raw ? JSON.parse(raw) : undefined;\n } catch {\n // Non-JSON body — keep `raw` for the fallback path.\n }\n if (body && typeof body === \"object\") {\n const b = body as { message?: unknown; code?: unknown; hint?: unknown };\n return new OxyApiError({\n status: resp.status,\n message: typeof b.message === \"string\" ? b.message : `HTTP ${resp.status}`,\n code: typeof b.code === \"string\" ? b.code : null,\n hint: typeof b.hint === \"string\" ? b.hint : null\n });\n }\n const snippet = raw.length > 240 ? `${raw.slice(0, 237)}…` : raw;\n return new OxyApiError({\n status: resp.status,\n message: snippet || `HTTP ${resp.status}`\n });\n}\n\n// ── Hooks ───────────────────────────────────────────────────────────────────\n\n/**\n * Read the resolved manifest from context. Throws if called outside\n * `<OxyAppProvider>` — that's a programmer error worth surfacing\n * loudly, not silently swallowing.\n */\nexport function useResolvedManifest(): ResolvedCustomerAppManifest {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useResolvedManifest must be called inside <OxyAppProvider>\");\n }\n if (ctx.status !== \"ready\" || !ctx.resolved) {\n throw new Error(\n \"useResolvedManifest called before manifest finished loading. \" +\n \"Use the provider's `fallback` prop to render while loading.\"\n );\n }\n return ctx.resolved;\n}\n\n/**\n * Low-level hook that returns the raw context value (including the\n * fetcher). Prefer `useResolvedManifest` for manifest access; use\n * this only when you need the fetcher or projectId without requiring\n * the manifest to be ready (e.g. inside `useQuery`).\n */\nfunction useOxyApp(): { projectId: string | undefined; fetcher: AppFetcher } {\n const ctx = React.useContext(OxyAppContext);\n if (!ctx) {\n throw new Error(\"useOxyApp must be called inside <OxyAppProvider>\");\n }\n return {\n projectId: ctx.resolved?.projectId,\n fetcher: ctx.fetcher\n };\n}\n\n// ── useQuery ────────────────────────────────────────────────────────────────\n\nexport interface UseQueryInput {\n sql: string;\n database?: string;\n}\n\nexport interface UseQueryOpts {\n params?: Record<string, string | number | boolean | null | undefined>;\n /** Set false to skip the request (e.g., waiting on user input). */\n enabled?: boolean;\n}\n\nexport interface UseQueryResult<Row = Record<string, unknown>> {\n rows: Row[];\n columns: string[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/**\n * Execute an ad-hoc SQL query against the project linked to this\n * customer app. The query is specified inline by the caller; no\n * manifest declaration is involved.\n *\n * Re-runs whenever `input` or enabled `params` change. Use the\n * `enabled` option to defer the first fetch until required data is\n * available (e.g. a user-supplied filter value).\n */\nexport function useQuery<Row = Record<string, unknown>>(\n input: UseQueryInput,\n opts: UseQueryOpts = {}\n): UseQueryResult<Row> {\n const { projectId, fetcher } = useOxyApp();\n const enabled = opts.enabled !== false;\n // Serialise opts.params so `useMemo` fires only when the values\n // change, not on every render when callers pass a new object literal.\n const paramsKey = JSON.stringify(opts.params);\n // biome-ignore lint/correctness/useExhaustiveDependencies: paramsKey replaces opts.params as the dep\n const sqlWithParams = React.useMemo(\n () => interpolateSqlParams(input.sql, opts.params ?? {}),\n [input.sql, paramsKey]\n );\n\n const [state, setState] = React.useState<{\n rows: Row[];\n columns: string[];\n loading: boolean;\n error: Error | null;\n }>({\n rows: [],\n columns: [],\n loading: enabled && !!projectId,\n error: null\n });\n const [nonce, setNonce] = React.useState(0);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: nonce forces refetch\n React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n const ctrl = new AbortController();\n let cancelled = false;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n const body = JSON.stringify({\n sql: sqlWithParams,\n ...(input.database ? { database: input.database } : {})\n });\n\n fetcher(`/api/projects/${projectId}/query`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n })\n .then(async (resp) => {\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n return resp.json() as Promise<{ columns: string[]; rows: unknown[][] }>;\n })\n .then(({ columns, rows }) => {\n if (cancelled) return;\n const objects = rows.map(\n (r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row\n );\n setState({ rows: objects, columns, loading: false, error: null });\n })\n .catch((err) => {\n if (cancelled) return;\n // AbortError is not a real failure — the cleanup cancelled the request.\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setState((s) => ({\n ...s,\n loading: false,\n error: err instanceof Error ? err : new Error(String(err))\n }));\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n }, [enabled, projectId, sqlWithParams, input.database, nonce, fetcher]);\n\n return {\n rows: state.rows,\n columns: state.columns,\n loading: state.loading,\n error: state.error,\n refetch: () => setNonce((n) => n + 1)\n };\n}\n\n// ── useSemanticQuery ────────────────────────────────────────────────────────\n//\n// Bundles reference the project's semantic layer by topic + dimensions\n// + measures + filters. The server compiles to dialect-specific SQL via\n// airlayer and executes through the same connector path as useQuery —\n// when the data team refactors the SQL behind a measure, the bundle\n// picks up the change without an edit.\n\n/** Scalar filter operators (compared against a single value). */\nexport type SemanticScalarOp = \"eq\" | \"neq\" | \"lt\" | \"lte\" | \"gt\" | \"gte\";\n\n/** Array filter operators (compared against a list). */\nexport type SemanticArrayOp = \"in\" | \"not_in\";\n\n/** Date-range filter operators. `from` / `to` accept ISO date strings. */\nexport type SemanticDateRangeOp = \"in_date_range\" | \"not_in_date_range\";\n\n/**\n * One filter clause. The `field` references a dimension name within\n * the topic; the `op` discriminator picks which other fields are\n * meaningful. Wire shape matches `agentic_semantic::SemanticFilter`\n * verbatim — the bundle's request body is forwarded to airlayer's\n * compiler with no translation.\n */\nexport type SemanticFilter =\n | { field: string; op: SemanticScalarOp; value: string | number | boolean | null }\n | { field: string; op: SemanticArrayOp; values: Array<string | number | boolean | null> }\n | { field: string; op: SemanticDateRangeOp; from: string; to: string };\n\n/** Time dimensions with optional granularity (e.g. \"day\", \"month\"). */\nexport interface SemanticTimeDimension {\n dimension: string;\n granularity?: \"day\" | \"week\" | \"month\" | \"quarter\" | \"year\";\n}\n\nexport interface UseSemanticQueryInput {\n topic: string;\n dimensions?: string[];\n measures?: string[];\n time_dimensions?: SemanticTimeDimension[];\n filters?: SemanticFilter[];\n limit?: number;\n}\n\nexport interface UseSemanticQueryOpts {\n /** Set false to skip the request (e.g., waiting on user input). */\n enabled?: boolean;\n /**\n * When true, the response includes the compiled SQL string at\n * `sql`. Off by default — production callers shouldn't bake the\n * warehouse SQL into their UI. Bundle authors flip this on while\n * debugging.\n */\n debug?: boolean;\n}\n\nexport interface UseSemanticQueryResult<Row = Record<string, unknown>> {\n rows: Row[];\n columns: string[];\n /** True when the result was capped at the server's row limit. */\n truncated: boolean;\n /** Compiled SQL — populated only when `opts.debug` is true. */\n sql: string | null;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/**\n * Run a semantic-layer query against the project's `.view.yml` /\n * `.topic.yml` definitions. The server compiles to SQL and executes\n * through the same connector path as `useQuery`, so result shape\n * matches.\n *\n * Re-runs whenever the input shape changes (deep-compared via JSON).\n * Use `opts.enabled = false` to defer the first fetch until required\n * inputs (e.g. a user-picked filter value) are available.\n */\nexport function useSemanticQuery<Row = Record<string, unknown>>(\n input: UseSemanticQueryInput,\n opts: UseSemanticQueryOpts = {}\n): UseSemanticQueryResult<Row> {\n const { projectId, fetcher } = useOxyApp();\n const enabled = opts.enabled !== false;\n const debug = opts.debug === true;\n\n // Stable key for the effect dep — re-running on every render when\n // callers pass a new object literal would mean re-fetching on every\n // parent render.\n const inputKey = React.useMemo(() => JSON.stringify(input), [input]);\n\n const [state, setState] = React.useState<{\n rows: Row[];\n columns: string[];\n truncated: boolean;\n sql: string | null;\n loading: boolean;\n error: Error | null;\n }>({\n rows: [],\n columns: [],\n truncated: false,\n sql: null,\n loading: enabled && !!projectId,\n error: null\n });\n const [nonce, setNonce] = React.useState(0);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: inputKey serializes input; nonce forces refetch\n React.useEffect(() => {\n if (!enabled || !projectId) {\n setState((s) => (s.loading ? { ...s, loading: false } : s));\n return;\n }\n const ctrl = new AbortController();\n let cancelled = false;\n setState((s) => ({ ...s, loading: true, error: null }));\n\n // `v: 1` is the customer-apps-platform body versioning convention\n // (see customer_apps_gates.rs::parse_versioned_body). Pinning it\n // here means a future v2 server can reject this stale client\n // cleanly instead of silently misinterpreting it.\n const body = JSON.stringify({\n v: 1,\n topic: input.topic,\n dimensions: input.dimensions ?? [],\n measures: input.measures ?? [],\n time_dimensions: input.time_dimensions ?? [],\n filters: input.filters ?? [],\n ...(input.limit != null ? { limit: input.limit } : {})\n });\n\n const url = `/api/projects/${projectId}/semantic-query${debug ? \"?debug=1\" : \"\"}`;\n fetcher(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n })\n .then(async (resp) => {\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n return resp.json() as Promise<{\n columns: string[];\n rows: unknown[][];\n truncated: boolean;\n sql?: string;\n }>;\n })\n .then(({ columns, rows, truncated, sql }) => {\n if (cancelled) return;\n const objects = rows.map(\n (r) => Object.fromEntries(columns.map((c, i) => [c, r[i]])) as Row\n );\n setState({\n rows: objects,\n columns,\n truncated,\n sql: sql ?? null,\n loading: false,\n error: null\n });\n })\n .catch((err) => {\n if (cancelled) return;\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setState((s) => ({\n ...s,\n loading: false,\n error: err instanceof Error ? err : new Error(String(err))\n }));\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n }, [enabled, projectId, inputKey, debug, nonce, fetcher]);\n\n return {\n rows: state.rows,\n columns: state.columns,\n truncated: state.truncated,\n sql: state.sql,\n loading: state.loading,\n error: state.error,\n refetch: () => setNonce((n) => n + 1)\n };\n}\n\n// ── useProcedureRun ─────────────────────────────────────────────────────────\n//\n// Trigger a long-running procedure (`.procedure.yml`) from the\n// bundle. Returns state + progress + structured outputs. Use for\n// \"Generate report\" / \"Recompute\" buttons.\n\nexport type ProcedureRunState = \"idle\" | \"running\" | \"done\" | \"failed\";\n\nexport interface UseProcedureRunInput {\n procedureId: string;\n}\n\nexport interface UseProcedureRunOpts {\n /** Polling cadence in ms while running. Default: 2000 (procedures\n * are typically minutes-long; tighter cadence wastes resources). */\n pollIntervalMs?: number;\n pollIntervalBackoffMs?: number;\n /** Max client-side wait in ms. Default: 1 hour. */\n maxWaitMs?: number;\n}\n\nexport interface ProcedureProgress {\n step: string;\n percent: number;\n}\n\nexport interface ProcedureResult {\n summary: string;\n outputs: Record<string, unknown>;\n}\n\nexport interface UseProcedureRunResult {\n state: ProcedureRunState;\n run: (params?: Record<string, unknown>) => void;\n /** Cancel the in-flight run. Idempotent. */\n cancel: () => void;\n progress: ProcedureProgress | null;\n result: ProcedureResult | null;\n error: Error | null;\n}\n\nconst PROCEDURE_POLL_MS = 2000;\nconst PROCEDURE_BACKOFF_MS = 5000;\nconst PROCEDURE_MAX_WAIT_MS = 60 * 60 * 1000;\n\n/**\n * @beta Long-running procedure runner. The wire shape works end-to-end\n * (start → poll → cancel; runs survive server restarts via the\n * `customer_app_procedure_runs` table) but a few rough edges remain\n * before this is GA-ready:\n *\n * - Hint surfaces for `procedure_not_found` are correct but the\n * procedure-discovery rules (which directories the server scans,\n * case-sensitivity, branch awareness) aren't documented yet.\n * - Cancellation across multi-instance deployments leans on a\n * periodic sweep — fine for now, but expect occasional latency\n * between `cancel()` and the run actually stopping.\n * - Progress reporting requires the procedure to emit named\n * steps; bundles get `progress: null` until that lands.\n *\n * The API surface is stable; expect breaking changes only if the\n * server-side `customer_app_procedure_runs` schema changes.\n */\nexport function useProcedureRun(\n input: UseProcedureRunInput,\n opts: UseProcedureRunOpts = {}\n): UseProcedureRunResult {\n warnBetaOnce(\"useProcedureRun\");\n const { projectId, fetcher } = useOxyApp();\n const pollMs = opts.pollIntervalMs ?? PROCEDURE_POLL_MS;\n const backoffMs = opts.pollIntervalBackoffMs ?? PROCEDURE_BACKOFF_MS;\n const maxWaitMs = opts.maxWaitMs ?? PROCEDURE_MAX_WAIT_MS;\n\n const [state, setState] = React.useState<{\n state: ProcedureRunState;\n progress: ProcedureProgress | null;\n result: ProcedureResult | null;\n error: Error | null;\n }>({\n state: \"idle\",\n progress: null,\n result: null,\n error: null\n });\n const inflight = React.useRef<{\n abort?: AbortController;\n runId?: string;\n }>({});\n\n // cancel is idempotent and a no-op once the run has reached a\n // terminal state. inflight.current.runId is cleared in the same\n // setState calls that flip state out of \"running\"; that's the\n // single source of truth so a slow click after `done` can't\n // overwrite the result with a phantom \"failed\".\n const cancel = React.useCallback(() => {\n const runId = inflight.current.runId;\n if (!projectId || !runId) return;\n inflight.current.abort?.abort();\n inflight.current.runId = undefined;\n void fetcher(`/api/projects/${projectId}/procedures/runs/${encodeURIComponent(runId)}/cancel`, {\n method: \"POST\"\n }).catch(() => {});\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(\"procedure cancelled by user\")\n });\n }, [projectId, fetcher]);\n\n const run = React.useCallback(\n (params?: Record<string, unknown>) => {\n if (!projectId) {\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"project not configured\")\n }));\n return;\n }\n inflight.current.abort?.abort();\n const ctrl = new AbortController();\n inflight.current = { abort: ctrl };\n setState({ state: \"running\", progress: null, result: null, error: null });\n\n void (async () => {\n try {\n const body = JSON.stringify({\n v: 1,\n ...(params ? { params } : {})\n });\n const startResp = await fetcher(\n `/api/projects/${projectId}/procedures/${encodeURIComponent(input.procedureId)}/runs`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n }\n );\n if (!startResp.ok) {\n throw await apiErrorFromResponse(startResp);\n }\n const { run_id } = (await startResp.json()) as { run_id: string };\n inflight.current.runId = run_id;\n\n const startedAt = Date.now();\n let pollCount = 0;\n // biome-ignore lint/correctness/noConstantCondition: terminated by return/throw\n while (true) {\n if (ctrl.signal.aborted) return;\n if (Date.now() - startedAt > maxWaitMs) {\n throw new Error(\"procedure run timed out client-side\");\n }\n const interval = pollCount < 6 ? pollMs : backoffMs;\n await sleep(interval, ctrl.signal);\n if (ctrl.signal.aborted) return;\n pollCount += 1;\n\n const pollResp = await fetcher(\n `/api/projects/${projectId}/procedures/runs/${encodeURIComponent(run_id)}`,\n { method: \"GET\", signal: ctrl.signal }\n );\n if (!pollResp.ok) {\n throw await apiErrorFromResponse(pollResp);\n }\n const poll = (await pollResp.json()) as\n | { status: \"running\"; progress?: ProcedureProgress }\n | { status: \"done\"; result: ProcedureResult }\n | { status: \"cancelled\" }\n | {\n status: \"failed\";\n error: { message: string; code?: string };\n };\n if (poll.status === \"running\") {\n if (poll.progress) {\n setState((s) => ({ ...s, progress: poll.progress ?? null }));\n }\n continue;\n }\n if (poll.status === \"done\") {\n inflight.current.runId = undefined;\n setState({\n state: \"done\",\n progress: null,\n result: poll.result,\n error: null\n });\n return;\n }\n if (poll.status === \"cancelled\") {\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(\"procedure cancelled\")\n });\n return;\n }\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: new Error(poll.error.message)\n });\n return;\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n inflight.current.runId = undefined;\n setState({\n state: \"failed\",\n progress: null,\n result: null,\n error: e instanceof Error ? e : new Error(String(e))\n });\n }\n })();\n },\n [projectId, fetcher, input.procedureId, pollMs, backoffMs, maxWaitMs]\n );\n\n React.useEffect(() => {\n return () => {\n inflight.current.abort?.abort();\n };\n }, []);\n\n return {\n state: state.state,\n run,\n cancel,\n progress: state.progress,\n result: state.result,\n error: state.error\n };\n}\n\n// ── useAgentRun (SSE streaming) ─────────────────────────────────────────────\n//\n// Real-time chat surface. Agentic pipeline emits events as they\n// happen — token-by-token answer text, mid-run SQL artifacts, ask-\n// user clarifications. Bundles use this for any chat / Q&A UI; the\n// drop-in `<OxyChat>` and `<OxyAnswer>` components wrap it for the\n// common cases.\n\nexport type AgentRunState = \"idle\" | \"running\" | \"needs_clarification\" | \"done\" | \"failed\";\n\nexport interface AgentRunEvent {\n type: string;\n data: unknown;\n}\n\n/** SQL produced and (optionally) executed by the agent. Extracted\n * from `query_generated` / `query_executed` / `verified_sql` /\n * `semantic_query` / `omni_query` SSE events so callers don't have\n * to scan the raw event stream themselves. */\nexport interface AgentSqlArtifact {\n type: \"sql\";\n /** Stable id derived from the SSE event id so React keys stay\n * stable across re-renders / reconnects. */\n id: string;\n /** Originating UI event type — preserves the verified/semantic/etc.\n * flavor in case the renderer wants a badge. */\n source: string;\n sql: string;\n /** Present when the SQL was executed and rows came back. */\n results?: {\n columns: string[];\n rows: unknown[][];\n rowCount: number;\n };\n /** Present when execution failed — surface it so the bundle UI can\n * show the failure inline next to the SQL instead of swallowing\n * it inside the agent's final answer. */\n error?: string;\n}\n\nexport type AgentArtifact = AgentSqlArtifact;\n\nexport interface UseAgentRunInput {\n agentId: string;\n}\n\nexport interface UseAgentRunResult {\n state: AgentRunState;\n /** Submit a question and open the SSE stream. */\n ask: (question: string, opts?: { threadId?: string }) => void;\n /** Cancel the in-flight stream + the server-side run. Idempotent. */\n cancel: () => void;\n /** Accumulated raw events for advanced consumers. */\n events: AgentRunEvent[];\n /** SQL artifacts extracted from the event stream — convenience\n * view over `events` so renderers don't have to know which event\n * types carry SQL. */\n artifacts: AgentArtifact[];\n /** Final answer once a `done` event arrives. Markdown. */\n answer: string | null;\n /** Clarification text once a suspension event arrives. */\n clarification: string | null;\n /** Thread id used by the active run (stable across follow-ups). */\n threadId: string | null;\n /**\n * @beta Relative path to the full thread view in oxy (e.g.\n * `/threads/<id>` for local mode, or\n * `/<org_slug>/workspaces/<ws_id>/threads/<id>` in cloud). Set\n * once the run starts so a bundle can render a \"Continue in Oxy\"\n * link without constructing the URL itself.\n *\n * Caveats while in beta:\n * - The bundle's origin and the oxy app shell's origin can\n * differ in cloud deployments. If they do, this relative URL\n * resolves against the bundle's origin and 404s. A future\n * release will expose the oxy app origin via the manifest;\n * for now, prefix at the call site if you know your\n * deployment topology, or hide the link entirely.\n * - The thread row may not be queryable until the run produces\n * its first event — clicking the link immediately after\n * `ask()` can land on a \"thread not found\" page.\n */\n threadUrl: string | null;\n error: Error | null;\n}\n\nexport function useAgentRun(input: UseAgentRunInput): UseAgentRunResult {\n const { projectId, fetcher } = useOxyApp();\n const [state, setState] = React.useState<{\n state: AgentRunState;\n events: AgentRunEvent[];\n artifacts: AgentArtifact[];\n answer: string | null;\n clarification: string | null;\n threadId: string | null;\n threadUrl: string | null;\n error: Error | null;\n }>({\n state: \"idle\",\n events: [],\n artifacts: [],\n answer: null,\n clarification: null,\n threadId: null,\n threadUrl: null,\n error: null\n });\n // Track the latest abort controller + run_id so cancel() can fire\n // the right server endpoint AND tear down the in-flight stream.\n // `terminated` is a local-only flag the consume loop reads to\n // decide whether to reconnect after a clean stream close. We\n // can't read React state directly (closure capture is stale) and\n // the prior stateRef approach was racy because setState is async\n // — by the time we read the ref it might not have flushed yet.\n // The flag is set synchronously inside the onEvent handler right\n // before setState fires, so the consume loop sees it on the next\n // iteration.\n const inflight = React.useRef<{\n abort?: AbortController;\n runId?: string;\n }>({});\n\n // cancel is idempotent + no-op once the run is terminal. We\n // clear inflight.current.runId in the same setState calls that\n // flip state out of \"running\"; a late click then can't overwrite\n // a done answer with a phantom \"failed\".\n const cancel = React.useCallback(() => {\n const runId = inflight.current.runId;\n if (!projectId || !runId) return;\n inflight.current.abort?.abort();\n inflight.current.runId = undefined;\n void fetcher(`/api/projects/${projectId}/agents/asks/${encodeURIComponent(runId)}/cancel`, {\n method: \"POST\"\n }).catch(() => {});\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"agent run cancelled by user\")\n }));\n }, [projectId, fetcher]);\n\n const ask = React.useCallback(\n (question: string, opts: { threadId?: string } = {}) => {\n if (!projectId) {\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(\"project not configured\")\n }));\n return;\n }\n inflight.current.abort?.abort();\n const ctrl = new AbortController();\n inflight.current = { abort: ctrl };\n setState({\n state: \"running\",\n events: [],\n artifacts: [],\n answer: null,\n clarification: null,\n threadId: opts.threadId ?? null,\n threadUrl: opts.threadId ? `/threads/${opts.threadId}` : null,\n error: null\n });\n\n void (async () => {\n try {\n // 1. POST to start the run.\n const body = JSON.stringify({\n v: 1,\n question,\n ...(opts.threadId ? { thread_id: opts.threadId } : {})\n });\n const startResp = await fetcher(\n `/api/projects/${projectId}/agents/${encodeURIComponent(input.agentId)}/asks`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n signal: ctrl.signal\n }\n );\n if (!startResp.ok) {\n throw await apiErrorFromResponse(startResp);\n }\n const { run_id, thread_id, thread_url } = (await startResp.json()) as {\n run_id: string;\n thread_id: string;\n thread_url?: string;\n };\n inflight.current.runId = run_id;\n setState((s) => ({\n ...s,\n threadId: thread_id,\n threadUrl: thread_url ?? `/threads/${thread_id}`\n }));\n\n // 2. Open the SSE stream via fetch (not EventSource) so we\n // can pass `Last-Event-ID` on reconnect. EventSource has\n // no API to set that header — the browser exposes the\n // server-set id internally but doesn't surface it for\n // custom auth flows or `withCredentials` + custom\n // headers together. Hand-rolled SSE + ReadableStream\n // gives us both.\n //\n // Reconnect strategy: on disconnect (network error,\n // server close before terminal event), wait 1s and\n // re-open with the last-seen sequence id. Max 5\n // attempts so a permanently-broken stream eventually\n // surfaces as `failed`. The `terminated` flag is set\n // synchronously when a terminal event arrives — once\n // set, the loop exits on the next iteration without\n // reconnecting.\n let lastEventId = \"\";\n let attempts = 0;\n let terminated = false;\n // biome-ignore lint/correctness/noConstantCondition: terminated by return/break\n while (true) {\n if (ctrl.signal.aborted) return;\n if (terminated) return;\n attempts += 1;\n\n try {\n await consumeSseStream({\n url: `/api/projects/${projectId}/agents/runs/${encodeURIComponent(run_id)}/events`,\n fetcher,\n signal: ctrl.signal,\n lastEventId,\n onEvent: (ev) => {\n // Track last event id for reconnect resumption.\n if (ev.id) lastEventId = ev.id;\n const data = parseSseData(ev.data);\n const eventType = ev.event || \"message\";\n const artifact = extractSqlArtifact(eventType, ev.id, data);\n\n // `text_delta` carries the streaming answer token-by-\n // token (CoreEvent::LlmToken → UiBlock::TextDelta).\n // The terminal `done` event has an empty payload — it\n // signals the run is over but doesn't restate the\n // answer text. So we accumulate tokens here.\n const token =\n eventType === \"text_delta\" &&\n typeof data === \"object\" &&\n data !== null &&\n \"token\" in data\n ? String((data as { token: unknown }).token)\n : null;\n\n setState((s) => ({\n ...s,\n events: [...s.events, { type: eventType, data }],\n artifacts: artifact ? [...s.artifacts, artifact] : s.artifacts,\n answer: token !== null ? (s.answer ?? \"\") + token : s.answer\n }));\n\n if (ev.event === \"done\") {\n terminated = true;\n inflight.current.runId = undefined;\n setState((s) => ({ ...s, state: \"done\" }));\n } else if (\n ev.event === \"failed\" ||\n ev.event === \"error\" ||\n ev.event === \"cancelled\"\n ) {\n // Mirrors `is_terminal_event` in\n // crates/app/.../agent_run_stream.rs — keep in sync.\n terminated = true;\n inflight.current.runId = undefined;\n const message =\n typeof data === \"object\" && data !== null && \"message\" in data\n ? String((data as { message: unknown }).message)\n : `agent run ${ev.event}`;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: new Error(message)\n }));\n } else if (ev.event === \"ask_user\") {\n // Not terminal in the cancel-sense — the user\n // can resume by calling ask() again with the\n // same threadId. Keep runId set so cancel()\n // still works if the user prefers to abort.\n terminated = true;\n const clarification =\n typeof data === \"object\" && data !== null && \"question\" in data\n ? String((data as { question: unknown }).question)\n : \"Agent needs clarification.\";\n setState((s) => ({\n ...s,\n state: \"needs_clarification\",\n clarification\n }));\n }\n }\n });\n // Stream closed cleanly. `terminated` was flipped by\n // the event handler iff a terminal event arrived —\n // check it at loop head; otherwise fall through to\n // reconnect.\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n // Network / parse error. Reconnect up to 5x with\n // 1s sleep before giving up.\n if (attempts >= 5) {\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: err instanceof Error ? err : new Error(String(err))\n }));\n return;\n }\n }\n if (terminated) return;\n await sleep(1000, ctrl.signal);\n }\n } catch (e) {\n if (e instanceof DOMException && e.name === \"AbortError\") return;\n inflight.current.runId = undefined;\n setState((s) => ({\n ...s,\n state: \"failed\",\n error: e instanceof Error ? e : new Error(String(e))\n }));\n }\n })();\n },\n [projectId, fetcher, input.agentId]\n );\n\n React.useEffect(() => {\n return () => {\n inflight.current.abort?.abort();\n };\n }, []);\n\n return {\n state: state.state,\n ask,\n cancel,\n events: state.events,\n artifacts: state.artifacts,\n answer: state.answer,\n clarification: state.clarification,\n threadId: state.threadId,\n threadUrl: state.threadUrl,\n error: state.error\n };\n}\n\n/** Parsed JSON payload of an SSE `data:` line, falling back to raw\n * string on parse failure. */\nfunction parseSseData(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** UI event types in the analytics taxonomy that carry SQL the bundle\n * may want to render alongside the answer. Each carries the same\n * shape (`query` / `columns` / `rows` / `success`) so we can parse\n * uniformly. New types added upstream don't surface as artifacts\n * until added here — that's intentional, the renderer needs to\n * know how to display each. */\nconst SQL_EVENT_TYPES = new Set([\n \"query_executed\",\n \"query_generated\",\n \"verified_sql\",\n \"semantic_query\",\n \"omni_query\"\n]);\n\n/** Extract a SQL artifact from a single SSE event when the type +\n * payload shape match. Returns `null` for events that aren't SQL\n * carriers or whose payload doesn't include the expected fields. */\nfunction extractSqlArtifact(\n eventType: string,\n eventId: string,\n data: unknown\n): AgentSqlArtifact | null {\n if (!SQL_EVENT_TYPES.has(eventType)) return null;\n if (typeof data !== \"object\" || data === null) return null;\n const obj = data as Record<string, unknown>;\n const sql =\n typeof obj.query === \"string\" ? obj.query : typeof obj.sql === \"string\" ? obj.sql : null;\n if (!sql) return null;\n\n const columns = Array.isArray(obj.columns) ? (obj.columns as unknown[]).map(String) : undefined;\n const rows = Array.isArray(obj.rows) ? (obj.rows as unknown[][]) : undefined;\n const rowCount =\n typeof obj.row_count === \"number\"\n ? obj.row_count\n : typeof obj.rowCount === \"number\"\n ? obj.rowCount\n : rows?.length;\n\n const artifact: AgentSqlArtifact = {\n type: \"sql\",\n id: eventId || `${eventType}-${sql.slice(0, 32)}`,\n source: eventType,\n sql\n };\n if (columns && rows) {\n artifact.results = { columns, rows, rowCount: rowCount ?? rows.length };\n }\n const errMsg =\n typeof obj.error === \"string\"\n ? obj.error\n : obj.success === false && typeof obj.message === \"string\"\n ? (obj.message as string)\n : undefined;\n if (errMsg) artifact.error = errMsg;\n return artifact;\n}\n\n/** One delivered SSE event. Matches what the spec calls a \"message\"\n * block — id + event-type + accumulated data. */\ninterface ParsedSseEvent {\n id: string;\n event: string;\n data: string;\n}\n\n/**\n * Hand-rolled SSE consumer using `fetch` + `ReadableStream`. We use\n * this in place of `EventSource` because:\n * 1. EventSource doesn't expose the connection's `Last-Event-ID`\n * header in a way you can control. The browser tracks it\n * internally but you can't pass a starting value, so a hook\n * that wants to resume after a tab switch / network blip has\n * no way to ask the server to replay from a known point.\n * 2. EventSource can't pass `Authorization` / other custom\n * headers — only `withCredentials` for cookies. Fine today,\n * but couples us to cookie auth forever.\n *\n * The parser handles the message-block model from the SSE spec\n * verbatim: lines split by `\\n` (or `\\r\\n` / `\\r`), event blocks\n * separated by blank lines, `id:` / `event:` / `data:` fields\n * accumulated per block. Multiple `data:` lines concatenate with\n * `\\n` (per spec) — we honor that even though the server emits\n * single-line data today.\n *\n * Throws on network error or non-2xx. Returns when the stream ends\n * normally (server closed connection cleanly).\n */\nasync function consumeSseStream(opts: {\n url: string;\n fetcher: AppFetcher;\n signal: AbortSignal;\n lastEventId: string;\n onEvent: (ev: ParsedSseEvent) => void;\n}): Promise<void> {\n const headers: Record<string, string> = {\n accept: \"text/event-stream\",\n \"cache-control\": \"no-cache\"\n };\n if (opts.lastEventId) {\n headers[\"Last-Event-ID\"] = opts.lastEventId;\n }\n const resp = await opts.fetcher(opts.url, {\n method: \"GET\",\n headers,\n signal: opts.signal\n });\n if (!resp.ok) {\n throw await apiErrorFromResponse(resp);\n }\n if (!resp.body) {\n throw new Error(\"SSE response has no body\");\n }\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentId = \"\";\n let currentEvent = \"message\";\n let currentData: string[] = [];\n\n const dispatch = () => {\n if (currentData.length === 0 && currentEvent === \"message\" && !currentId) {\n return; // empty block\n }\n opts.onEvent({\n id: currentId,\n event: currentEvent,\n data: currentData.join(\"\\n\")\n });\n // `id` persists across events per spec; `event` and `data` reset.\n currentEvent = \"message\";\n currentData = [];\n };\n\n // biome-ignore lint/correctness/noConstantCondition: terminated by reader.read()\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let nlIndex: number;\n // Process complete lines. Handle \\n, \\r\\n, and bare \\r.\n // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line-buffer drain\n while ((nlIndex = buffer.search(/\\r\\n|\\r|\\n/)) !== -1) {\n // \\r at the very end of the buffer is ambiguous — it could be\n // a lone CR (Mac line ending) or the first half of a CRLF\n // whose LF hasn't arrived yet. Wait for the next read before\n // committing. Without this, a CRLF straddling a chunk boundary\n // gets parsed as two empty lines, which fires `dispatch()`\n // twice and breaks event framing for any future writer that\n // emits CRLF (Axum uses LF today, so latent — but bug-class\n // fix is cheap.)\n if (nlIndex === buffer.length - 1 && buffer[nlIndex] === \"\\r\") {\n break;\n }\n const line = buffer.slice(0, nlIndex);\n // Skip the matched line break (one or two chars).\n const sep = buffer.slice(nlIndex, nlIndex + 2);\n buffer = buffer.slice(nlIndex + (sep === \"\\r\\n\" ? 2 : 1));\n\n if (line === \"\") {\n // Empty line ⇒ end of event block.\n dispatch();\n continue;\n }\n if (line.startsWith(\":\")) {\n // Comment — keep-alive heartbeats. Skip.\n continue;\n }\n const colonAt = line.indexOf(\":\");\n const field = colonAt === -1 ? line : line.slice(0, colonAt);\n let value = colonAt === -1 ? \"\" : line.slice(colonAt + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"id\":\n currentId = value;\n break;\n case \"event\":\n currentEvent = value;\n break;\n case \"data\":\n currentData.push(value);\n break;\n // `retry:` and unknown fields ignored.\n }\n }\n }\n // Stream ended — dispatch any final buffered event without a\n // trailing blank line (server didn't write one before close).\n if (currentData.length > 0) {\n dispatch();\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(t);\n reject(new DOMException(\"aborted\", \"AbortError\"));\n },\n { once: true }\n );\n });\n}\n\n// ── <OxyAnswer> + <OxyChat> drop-in components ──────────────────────────────\n//\n// Bundles that want a chat surface without rolling their own UI use\n// `<OxyChat agentId=\"...\">`. Bundles that already have a question\n// input but want oxy to render the answer use `<OxyAnswer>` with the\n// values from `useAgentRun()`.\n//\n// Both components ship with default markdown rendering, SQL artifact\n// display, and a \"Continue in Oxy\" link — the three things every\n// bundle author needs and nobody wants to rebuild.\n//\n// Styling: inline `style={}` only. Bundles inevitably have their\n// own design system (Tailwind, Mantine, CSS-in-JS, etc.) and we\n// don't want the SDK to fight it. Caller can pass `className` to\n// override layout entirely.\n\nexport interface OxyAnswerProps {\n /** Markdown answer text from `useAgentRun().answer`. */\n answer: string | null;\n /** SQL artifacts from `useAgentRun().artifacts`. */\n artifacts?: AgentArtifact[];\n /** Lifecycle state — drives the placeholder, spinner, error UI. */\n state: AgentRunState;\n /** Clarification text when `state === \"needs_clarification\"`. */\n clarification?: string | null;\n /** Failure reason when `state === \"failed\"`. */\n error?: Error | null;\n /**\n * @beta Relative URL to the thread view in oxy — renders a\n * \"Continue in Oxy (beta)\" link when set. Pass `null` to suppress\n * the link entirely; the link is marked beta because the resolved\n * URL may not reach a live thread in every deployment topology\n * (see `UseAgentRunResult.threadUrl`).\n */\n threadUrl?: string | null;\n /** Override the link label. Default: \"Continue this thread in Oxy\". */\n threadLinkLabel?: string;\n /** Maximum number of SQL result rows to render per artifact. Older\n * rows truncated with a \"+N more\" note. Default: 10. */\n maxArtifactRows?: number;\n /** Class on the outer container — for callers using utility CSS. */\n className?: string;\n}\n\n/**\n * Renders an agent run's answer + artifacts + thread link as a\n * single block. The default styling is intentionally neutral\n * (system fonts, gray surfaces) so it blends into any bundle.\n *\n * Designed to be paired with `useAgentRun`:\n *\n * ```tsx\n * const run = useAgentRun({ agentId: \"analyst\" });\n * return (\n * <>\n * <button onClick={() => run.ask(\"how many users last week?\")}>Ask</button>\n * <OxyAnswer {...run} />\n * </>\n * );\n * ```\n */\nexport function OxyAnswer(props: OxyAnswerProps): React.JSX.Element {\n const {\n answer,\n artifacts = [],\n state,\n clarification,\n error,\n threadUrl,\n threadLinkLabel = \"Continue this thread in Oxy\",\n maxArtifactRows = 10,\n className\n } = props;\n\n const isRunning = state === \"running\";\n const isFailed = state === \"failed\";\n const needsClarification = state === \"needs_clarification\";\n\n return (\n <div className={className} style={styles.answerWrap}>\n {isRunning && answer === null ? (\n <div style={styles.statusRow}>\n <span style={styles.spinner} aria-hidden='true' />\n <span style={styles.statusText}>Thinking…</span>\n </div>\n ) : null}\n\n {artifacts.length > 0 ? (\n <div style={styles.artifactList}>\n {artifacts.map((a) => (\n <SqlArtifactBlock key={a.id} artifact={a} maxRows={maxArtifactRows} />\n ))}\n </div>\n ) : null}\n\n {answer ? (\n <div style={styles.markdown}>\n <MarkdownText text={answer} />\n </div>\n ) : null}\n\n {needsClarification && clarification ? (\n <div style={styles.clarification}>\n <strong>Agent needs clarification:</strong>\n <div style={{ marginTop: 4 }}>{clarification}</div>\n </div>\n ) : null}\n\n {isFailed && error ? <ErrorBlock error={error} /> : null}\n\n {threadUrl && (answer || artifacts.length > 0) ? (\n <div style={styles.threadLinkRow}>\n <a href={threadUrl} target='_blank' rel='noreferrer noopener' style={styles.threadLink}>\n {threadLinkLabel} →\n </a>\n <span style={styles.betaBadge} title='Thread linking is in beta — see docs'>\n beta\n </span>\n </div>\n ) : null}\n </div>\n );\n}\n\nexport interface OxyChatProps {\n /** Agent id (matches `<id>.agentic.yml` in the project). */\n agentId: string;\n /** Placeholder for the question input. */\n placeholder?: string;\n /** Button label. Default: \"Ask\". */\n submitLabel?: string;\n /** Rendered when the user hasn't asked anything yet. */\n emptyState?: React.ReactNode;\n /** Forwarded to the inner `<OxyAnswer>`. */\n maxArtifactRows?: number;\n /** Class on the outer container. */\n className?: string;\n}\n\n/**\n * Complete drop-in chat surface. One agent, one input, one answer\n * view. The chat is single-turn by default — each new question\n * cancels the previous run and clears the answer. Bundles that\n * want a multi-turn conversation history compose their own UI\n * using `useAgentRun` directly.\n *\n * Single-turn keeps the surface dead simple: bundles use this for\n * the \"ask anything about your data\" widget that sits next to\n * structured panels. Multi-turn is rare in those contexts and\n * better expressed by the bundle.\n */\nexport function OxyChat(props: OxyChatProps): React.JSX.Element {\n const {\n agentId,\n placeholder = \"Ask a question about your data…\",\n submitLabel = \"Ask\",\n emptyState,\n maxArtifactRows,\n className\n } = props;\n\n const run = useAgentRun({ agentId });\n const [question, setQuestion] = React.useState(\"\");\n\n const submit = React.useCallback(\n (e?: React.FormEvent) => {\n e?.preventDefault();\n const q = question.trim();\n if (!q || run.state === \"running\") return;\n run.ask(q);\n },\n [question, run]\n );\n\n return (\n <div className={className} style={styles.chatWrap}>\n <form onSubmit={submit} style={styles.chatForm}>\n <input\n type='text'\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder={placeholder}\n disabled={run.state === \"running\"}\n style={styles.chatInput}\n aria-label='Question'\n />\n <button\n type='submit'\n disabled={run.state === \"running\" || question.trim() === \"\"}\n style={styles.chatSubmit}\n >\n {run.state === \"running\" ? \"…\" : submitLabel}\n </button>\n {run.state === \"running\" ? (\n <button type='button' onClick={run.cancel} style={styles.chatCancel}>\n Stop\n </button>\n ) : null}\n </form>\n\n {run.state === \"idle\" ? (\n (emptyState ?? <div style={styles.emptyState}>Ask a question to get started.</div>)\n ) : (\n <OxyAnswer\n answer={run.answer}\n artifacts={run.artifacts}\n state={run.state}\n clarification={run.clarification}\n error={run.error}\n threadUrl={run.threadUrl}\n maxArtifactRows={maxArtifactRows}\n />\n )}\n </div>\n );\n}\n\nfunction SqlArtifactBlock(props: {\n artifact: AgentSqlArtifact;\n maxRows: number;\n}): React.JSX.Element {\n const { artifact, maxRows } = props;\n const [open, setOpen] = React.useState(false);\n const results = artifact.results;\n const truncated = results ? results.rows.length > maxRows : false;\n const visibleRows = results ? results.rows.slice(0, maxRows) : [];\n const sourceLabel =\n artifact.source === \"verified_sql\"\n ? \"Verified query\"\n : artifact.source === \"semantic_query\"\n ? \"Semantic query\"\n : artifact.source === \"omni_query\"\n ? \"Omni query\"\n : \"Query\";\n\n return (\n <div style={styles.artifact}>\n <button type='button' onClick={() => setOpen((o) => !o)} style={styles.artifactHeader}>\n <span style={styles.artifactBadge}>{sourceLabel}</span>\n <span style={styles.artifactSummary}>\n {results\n ? `${results.rowCount} row${results.rowCount === 1 ? \"\" : \"s\"}`\n : artifact.error\n ? \"execution failed\"\n : \"SQL only\"}\n </span>\n <span style={styles.artifactToggle}>{open ? \"Hide\" : \"Show\"}</span>\n </button>\n {open ? (\n <div>\n <pre style={styles.sqlBlock}>{artifact.sql}</pre>\n {artifact.error ? (\n <div style={styles.error}>{artifact.error}</div>\n ) : results ? (\n <div style={styles.resultsWrap}>\n <table style={styles.resultsTable}>\n <thead>\n <tr>\n {results.columns.map((c) => (\n <th key={c} style={styles.resultsTh}>\n {c}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {visibleRows.map((row, i) => (\n <tr key={i}>\n {row.map((cell, j) => (\n <td key={j} style={styles.resultsTd}>\n {formatCell(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {truncated ? (\n <div style={styles.truncatedNote}>\n +{results.rows.length - maxRows} more rows. Open the thread in Oxy to see all.\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction formatCell(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Renders a thrown Error with the server's `hint` line broken out\n * if the error is an `OxyApiError`. Falls back to `error.message`\n * for plain Errors. Whitespace-preserving so multi-line hints from\n * the server land readably.\n */\nfunction ErrorBlock(props: { error: Error }): React.JSX.Element {\n const { error } = props;\n if (error instanceof OxyApiError) {\n return (\n <div style={styles.error}>\n <div>\n <strong>Run failed:</strong> {error.message.split(\"\\n\\n\")[0]}\n </div>\n {error.hint ? (\n <div style={{ marginTop: 6, fontWeight: 400, whiteSpace: \"pre-wrap\" }}>\n <strong>Hint:</strong> {error.hint}\n </div>\n ) : null}\n </div>\n );\n }\n return (\n <div style={styles.error}>\n <strong>Run failed:</strong> {error.message}\n </div>\n );\n}\n\n// ── Minimal markdown renderer ───────────────────────────────────────────────\n//\n// Agent answers are simple markdown — paragraphs, headings (h1-h3),\n// fenced code blocks, inline code, bold, italic, links, unordered\n// lists. A 100-line renderer covers it without adding a runtime dep\n// (~30 KB) that bundles may already have in a different version.\n//\n// Out of scope intentionally: tables (use a SQL artifact), images\n// (agents don't emit them), nested lists (rare in answers), HTML\n// passthrough (no XSS surface).\n\nfunction MarkdownText(props: { text: string }): React.JSX.Element {\n const blocks = React.useMemo(() => parseMarkdown(props.text), [props.text]);\n return <>{blocks}</>;\n}\n\ntype MdBlock =\n | { kind: \"h\"; level: 1 | 2 | 3; text: string }\n | { kind: \"p\"; text: string }\n | { kind: \"code\"; lang: string; code: string }\n | { kind: \"list\"; items: string[] };\n\nfunction parseMarkdown(text: string): React.JSX.Element[] {\n const lines = text.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n const blocks: MdBlock[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n if (line === undefined) {\n i++;\n continue;\n }\n // Fenced code block\n const fence = line.match(/^```(\\w*)\\s*$/);\n if (fence) {\n const lang = fence[1] ?? \"\";\n const buf: string[] = [];\n i++;\n while (i < lines.length && !/^```\\s*$/.test(lines[i] ?? \"\")) {\n buf.push(lines[i] ?? \"\");\n i++;\n }\n i++; // skip closing fence\n blocks.push({ kind: \"code\", lang, code: buf.join(\"\\n\") });\n continue;\n }\n // Heading\n const h = line.match(/^(#{1,3})\\s+(.+)$/);\n if (h) {\n blocks.push({\n kind: \"h\",\n level: h[1]?.length as 1 | 2 | 3,\n text: h[2]!\n });\n i++;\n continue;\n }\n // List\n if (/^\\s*[-*]\\s+/.test(line)) {\n const items: string[] = [];\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i] ?? \"\")) {\n items.push((lines[i] ?? \"\").replace(/^\\s*[-*]\\s+/, \"\"));\n i++;\n }\n blocks.push({ kind: \"list\", items });\n continue;\n }\n // Blank line\n if (line.trim() === \"\") {\n i++;\n continue;\n }\n // Paragraph — collect consecutive non-empty, non-special lines.\n const buf: string[] = [line];\n i++;\n while (i < lines.length) {\n const next = lines[i] ?? \"\";\n if (\n next.trim() === \"\" ||\n /^#{1,3}\\s+/.test(next) ||\n /^```/.test(next) ||\n /^\\s*[-*]\\s+/.test(next)\n ) {\n break;\n }\n buf.push(next);\n i++;\n }\n blocks.push({ kind: \"p\", text: buf.join(\" \") });\n }\n\n return blocks.map((b, idx) => {\n switch (b.kind) {\n case \"h\": {\n const Tag = `h${b.level}` as unknown as keyof React.JSX.IntrinsicElements;\n const headingStyle = b.level === 1 ? styles.h1 : b.level === 2 ? styles.h2 : styles.h3;\n return (\n <Tag key={idx} style={headingStyle}>\n {renderInline(b.text)}\n </Tag>\n );\n }\n case \"code\":\n return (\n <pre key={idx} style={styles.codeBlock} data-lang={b.lang || undefined}>\n <code>{b.code}</code>\n </pre>\n );\n case \"list\":\n return (\n <ul key={idx} style={styles.list}>\n {b.items.map((item, i) => (\n <li key={i}>{renderInline(item)}</li>\n ))}\n </ul>\n );\n case \"p\":\n return (\n <p key={idx} style={styles.paragraph}>\n {renderInline(b.text)}\n </p>\n );\n }\n });\n}\n\n/**\n * Inline tokenizer for **bold**, *italic*, `code`, and [text](url).\n * Lazy: scan once, split into segments. The patterns are matched in\n * priority order (code first so backticks don't get eaten by bold).\n */\nfunction renderInline(text: string): React.ReactNode[] {\n const segments: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n // Patterns in priority order — code first since `**` inside backticks\n // shouldn't be parsed.\n const patterns: Array<{\n re: RegExp;\n render: (m: RegExpExecArray) => React.ReactNode;\n }> = [\n { re: /`([^`]+)`/, render: (m) => <code style={styles.inlineCode}>{m[1]}</code> },\n {\n re: /\\[([^\\]]+)\\]\\(([^)]+)\\)/,\n render: (m) => {\n // Allowlist URL schemes. Agent answers cross the LLM trust\n // boundary — a malicious prompt fragment reflected into the\n // answer could otherwise emit\n // `[click](javascript:alert(document.cookie))` and execute\n // in the bundle's origin. Accept only http(s), mailto,\n // root-relative paths, and same-page fragments; everything\n // else renders as plain text.\n if (isSafeLinkHref(m[2])) {\n return (\n <a href={m[2]} target='_blank' rel='noreferrer noopener' style={styles.link}>\n {m[1]}\n </a>\n );\n }\n return <>{m[1]}</>;\n }\n },\n { re: /\\*\\*([^*]+)\\*\\*/, render: (m) => <strong>{m[1]}</strong> },\n { re: /\\*([^*]+)\\*/, render: (m) => <em>{m[1]}</em> }\n ];\n\n while (remaining.length > 0) {\n let earliest: { idx: number; len: number; node: React.ReactNode } | null = null;\n for (const { re, render } of patterns) {\n const m = re.exec(remaining);\n if (m && (earliest === null || m.index < earliest.idx)) {\n earliest = { idx: m.index, len: m[0].length, node: render(m) };\n }\n }\n if (earliest === null) {\n segments.push(remaining);\n break;\n }\n if (earliest.idx > 0) segments.push(remaining.slice(0, earliest.idx));\n segments.push(<React.Fragment key={key++}>{earliest.node}</React.Fragment>);\n remaining = remaining.slice(earliest.idx + earliest.len);\n }\n return segments;\n}\n\n// ── Styles ──────────────────────────────────────────────────────────────────\n//\n// All inline. No CSS file, no class names, no global side effects.\n// Bundles that want custom styling pass `className` and override\n// with their own selectors, or skip the drop-in entirely and build\n// on `useAgentRun`.\n\nconst SANS =\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif';\nconst MONO = 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace';\n\nconst styles: Record<string, React.CSSProperties> = {\n answerWrap: {\n fontFamily: SANS,\n fontSize: 14,\n lineHeight: 1.5,\n color: \"#1f2937\"\n },\n statusRow: { display: \"flex\", alignItems: \"center\", gap: 8, padding: \"8px 0\" },\n spinner: {\n display: \"inline-block\",\n width: 12,\n height: 12,\n borderRadius: \"50%\",\n border: \"2px solid #d1d5db\",\n borderTopColor: \"#6b7280\",\n animation: \"oxy-spin 0.8s linear infinite\"\n },\n statusText: { color: \"#6b7280\" },\n markdown: { marginTop: 8 },\n h1: { fontSize: 20, fontWeight: 600, margin: \"16px 0 8px\" },\n h2: { fontSize: 17, fontWeight: 600, margin: \"14px 0 6px\" },\n h3: { fontSize: 15, fontWeight: 600, margin: \"12px 0 4px\" },\n paragraph: { margin: \"0 0 8px\" },\n list: { margin: \"0 0 8px\", paddingLeft: 20 },\n codeBlock: {\n fontFamily: MONO,\n fontSize: 12,\n background: \"#f3f4f6\",\n border: \"1px solid #e5e7eb\",\n borderRadius: 6,\n padding: \"8px 10px\",\n overflowX: \"auto\",\n margin: \"8px 0\"\n },\n inlineCode: {\n fontFamily: MONO,\n fontSize: \"0.92em\",\n background: \"#f3f4f6\",\n padding: \"1px 4px\",\n borderRadius: 3\n },\n link: { color: \"#2563eb\", textDecoration: \"underline\" },\n clarification: {\n marginTop: 12,\n padding: \"10px 12px\",\n background: \"#fef3c7\",\n border: \"1px solid #fcd34d\",\n borderRadius: 6,\n color: \"#78350f\"\n },\n error: {\n marginTop: 12,\n padding: \"10px 12px\",\n background: \"#fee2e2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 6,\n color: \"#991b1b\"\n },\n threadLinkRow: {\n marginTop: 12,\n textAlign: \"right\",\n display: \"flex\",\n justifyContent: \"flex-end\",\n alignItems: \"center\",\n gap: 6\n },\n threadLink: { fontSize: 12, color: \"#6b7280\", textDecoration: \"none\" },\n betaBadge: {\n fontSize: 9,\n fontWeight: 600,\n letterSpacing: 0.5,\n textTransform: \"uppercase\",\n padding: \"1px 5px\",\n borderRadius: 3,\n background: \"#fef3c7\",\n color: \"#92400e\",\n border: \"1px solid #fcd34d\"\n },\n artifactList: { display: \"flex\", flexDirection: \"column\", gap: 8, marginBottom: 8 },\n artifact: {\n border: \"1px solid #e5e7eb\",\n borderRadius: 6,\n background: \"#fafafa\",\n overflow: \"hidden\"\n },\n artifactHeader: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n width: \"100%\",\n padding: \"6px 10px\",\n background: \"transparent\",\n border: \"none\",\n borderBottom: \"1px solid transparent\",\n cursor: \"pointer\",\n fontFamily: SANS,\n fontSize: 12,\n color: \"#374151\"\n },\n artifactBadge: {\n fontWeight: 600,\n fontSize: 11,\n textTransform: \"uppercase\",\n letterSpacing: 0.4,\n color: \"#4b5563\"\n },\n artifactSummary: { color: \"#6b7280\", flex: 1 },\n artifactToggle: { color: \"#2563eb\" },\n sqlBlock: {\n fontFamily: MONO,\n fontSize: 12,\n margin: 0,\n padding: \"8px 10px\",\n background: \"#0f172a\",\n color: \"#e2e8f0\",\n overflowX: \"auto\"\n },\n resultsWrap: { padding: 8, overflowX: \"auto\" },\n resultsTable: { width: \"100%\", borderCollapse: \"collapse\", fontSize: 12 },\n resultsTh: {\n textAlign: \"left\",\n padding: \"4px 8px\",\n borderBottom: \"1px solid #e5e7eb\",\n fontWeight: 600,\n color: \"#374151\"\n },\n resultsTd: { padding: \"4px 8px\", borderBottom: \"1px solid #f3f4f6\", color: \"#1f2937\" },\n truncatedNote: { fontSize: 11, color: \"#6b7280\", padding: \"6px 8px\" },\n chatWrap: { fontFamily: SANS, fontSize: 14, color: \"#1f2937\" },\n chatForm: { display: \"flex\", gap: 8, marginBottom: 12 },\n chatInput: {\n flex: 1,\n padding: \"8px 12px\",\n border: \"1px solid #d1d5db\",\n borderRadius: 6,\n fontSize: 14,\n fontFamily: SANS\n },\n chatSubmit: {\n padding: \"8px 16px\",\n border: \"none\",\n borderRadius: 6,\n background: \"#2563eb\",\n color: \"#ffffff\",\n fontSize: 14,\n fontWeight: 500,\n cursor: \"pointer\"\n },\n chatCancel: {\n padding: \"8px 12px\",\n border: \"1px solid #d1d5db\",\n borderRadius: 6,\n background: \"#ffffff\",\n color: \"#374151\",\n fontSize: 14,\n cursor: \"pointer\"\n },\n emptyState: { padding: \"12px 0\", color: \"#9ca3af\", fontStyle: \"italic\" }\n};\n"],"mappings":";;;;AAqBA,IAAI,eAA6B,qBAAqB;;AAGtD,SAAgB,gBAAgB,QAAmC;CACjE,eAAe,UAAU,cAAc;;;AAIzC,SAAgB,kBAAgC;CAC9C,OAAO;;AAGT,SAAS,sBAAoC;CAC3C,OAAO,EACL,IAAI,OAAO,KAAK,KAAK;EACnB,IAAI,OAAO,YAAY,aAAa;EACpC,MAAM,SAAS;EACf,MAAM,OAAkB,MAAM;GAAC;GAAQ;GAAK;GAAI,GAAG,CAAC,QAAQ,IAAI;EAChE,QAAQ,OAAR;GACE,KAAK;IACH,QAAQ,MAAM,GAAG,KAAK;IACtB;GACF,KAAK;IACH,QAAQ,KAAK,GAAG,KAAK;IACrB;GACF,KAAK;IACH,QAAQ,KAAK,GAAG,KAAK;IACrB;GACF,KAAK;IACH,QAAQ,MAAM,GAAG,KAAK;IACtB;;IAGP;;AAGH,SAAS,eAA6B;CACpC,OAAO,EAAE,MAAM,IAAI;;;;;;;;;;;ACbrB,eAAsB,oBACpB,UACmC;CACnC,MAAM,MAAM,iBAAiB;CAC7B,MAAM,EAAE,YAAY,SAAS,YAAY;CACzC,MAAM,MACJ,GAAG,WAAW,qBACX,mBAAmB,QAAQ,CAAC,GAAG,mBAAmB,QAAQ,CAAC;CAEhE,IAAI,IAAI,SAAS,2BAA2B,EAAE,KAAK,CAAC;CACpD,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,aAAa,eAAe,CAAC;CAC5D,IAAI,CAAC,IAAI,IAAI;EACX,MAAM,SAAS,MAAM,IAAI,MAAM,CAAC,YAAY,GAAG;EAC/C,MAAM,IAAI,MACR,wCAAwC,IAAI,OAAO,KAAK,UAAU,IAAI,aACvE;;CAEH,MAAM,WAAY,MAAM,IAAI,MAAM;CAClC,IAAI,IAAI,QAAQ,kBAAkB,SAA+C;CACjF,OAAO;;;;;ACzCT,MAAM,WAAW;;AAGjB,SAAgB,0BAA0B,KAAsC;CAC9E,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;CAMhE,IAAI,yCAAyC,KAAK,QAAQ,EACxD,OAAO;EACL,OAAO;EACP;EACA,MACE;EAIF,MAAM;EACP;CAGH,IAAI,+BAA+B,KAAK,QAAQ,EAC9C,OAAO;EACL,OAAO;EACP;EACA,MACE;EAGF,MAAM;EACP;CAGH,IAAI,iBAAiB,KAAK,QAAQ,EAChC,OAAO;EACL,OAAO;EACP;EACA,MACE;EAGF,MAAM;EACP;CAOH,IAAI,SAAS,KAAK,QAAQ,EACxB,OAAO;EACL,OAAO;EACP;EACA,MAAM;EACN,MAAM;EACP;CAGH,IAAI,8BAA8B,KAAK,QAAQ,EAC7C,OAAO;EACL,OAAO;EACP;EACA,MACE;EAGF,MAAM;EACP;CAGH,IAAI,wBAAwB,KAAK,QAAQ,EACvC,OAAO;EACL,OAAO;EACP;EACA,MACE;EAEF,MAAM;EACP;CAGH,IAAI,wBAAwB,KAAK,QAAQ,EACvC,OAAO;EACL,OAAO;EACP;EACA,MACE;EAGF,MAAM;EACP;CAGH,IAAI,SAAS,KAAK,QAAQ,EACxB,OAAO;EACL,OAAO;EACP;EACA,MAAM;EACN,MAAM;EACP;CAGH,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,KAAK,QAAQ,EACpD,OAAO;EACL,OAAO;EACP;EACA,MACE;EAGF,MAAM;EACP;CAGH,IAAI,kCAAkC,KAAK,QAAQ,EACjD,OAAO;EACL,OAAO;EACP;EACA,MACE;EAEF,MAAM;EACP;CAGH,IAAI,SAAS,KAAK,QAAQ,IAAI,gBAAgB,KAAK,QAAQ,EACzD,OAAO;EACL,OAAO;EACP;EACA,MACE;EAEF,MAAM;EACP;CAGH,IAAI,SAAS,KAAK,QAAQ,EACxB,OAAO;EACL,OAAO;EACP;EACA,MACE;EAEF,MAAM;EACP;CAWH,IAAI,kCAAkC,KAAK,QAAQ,EACjD,OAAO;EACL,OAAO;EACP;EACA,MACE;EAKF,MAAM;EACP;CAIH,OAAO;EACL,OAAO;EACP;EACA,MACE;EAEF,MAAM;EACP;;;;;;;;;;;ACvKH,SAAgB,wBAA0D;CACxE,IAAI,OAAO,WAAW,aAAa,OAAO;CAC1C,OAAO,OAAO;;;;;AC4DhB,IAAI,SAAsD;;;;;AAM1D,SAAgB,wBACd,UAA+B,EAAE,EACK;CACtC,IAAI,CAAC,QACH,SAAS,iBAAiB,QAAQ;CAEpC,OAAO;;;AAIT,SAAgB,wCAA8C;CAC5D,SAAS;;AAGX,eAAe,iBACb,SACsC;CACtC,MAAM,MAAM,iBAAiB;CAC7B,MAAM,WAAW,uBAAuB;CACxC,MAAM,cAAc,QAAQ,eAAe,mBAAmB,SAAS;CAEvE,IAAI,IAAI,QAAQ,oBAAoB;EAClC;EACA,kBAAkB,CAAC,CAAC;EACpB,SAAS,UAAU;EACnB,SAAS,UAAU;EACnB,OAAO,UAAU;EAClB,CAAC;CAEF,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,MAAM,MAAM,MAAM,aAAa,EAAE,aAAa,eAAe,CAAC;CACpE,IAAI,CAAC,IAAI,IAAI;EACX,IAAI,IAAI,SAAS,yBAAyB;GACxC;GACA,QAAQ,IAAI;GACZ,YAAY,IAAI;GACjB,CAAC;EACF,MAAM,IAAI,MACR,oCAAoC,YAAY,SAAS,IAAI,OAAO,sEAErE;;CAGH,MAAM,WAAW,iBAAiB,MADf,IAAI,MAAM,EACU,YAAY;CAEnD,MAAM,WAAwC;EAC5C;EACA,cAAc,EAAE;EAChB,SAAS,UAAU,WAAW;EAC9B,SAAS,UAAU,QAAQ;EAC3B,YAAY,UAAU,cAAc;EACpC,OAAO,UAAU;EACjB,WAAW,UAAU,aAAa,SAAS;EAC5C;CACD,IAAI,IAAI,QAAQ,kBAAkB;EAChC,YAAY,KAAK,KAAK,GAAG;EACzB,eAAe,SAAS;EACxB,MAAM,SAAS;EAChB,CAAC;CACF,OAAO;;;;;;;;;;;;;;AAeT,SAAS,mBAAmB,UAAoD;CAC9E,IAAI,UAAU,WAAW,UAAU,MAGjC,OAAO,kBAFK,mBAAmB,SAAS,QAEZ,CAAC,GADjB,mBAAmB,SAAS,KACL,CAAC;CAMtC,OAAO;;;;;;;;;;AAaT,SAAS,iBAAiB,KAAc,KAA6B;CACnE,IAAI,CAAC,SAAS,IAAI,EAChB,MAAM,IAAI,MAAM,eAAe,IAAI,uBAAuB;CAE5D,IAAI,IAAI,kBAAkB,GACxB,MAAM,IAAI,MACR,8CAA8C,KAAK,UAAU,IAAI,cAAc,CAAC,+EAEjF;CAEH,IAAI,IAAI,aAAa,UAAa,IAAI,YAAY,QAChD,MAAM,IAAI,MACR,wGACD;CAEH,IAAI,OAAO,IAAI,SAAS,YAAY,CAAC,IAAI,KAAK,MAAM,EAClD,MAAM,IAAI,MAAM,kEAAkE;CAQpF,OAAO;EAAE,eAAe;EAAG,MALd,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;EAKtB,MAJpB,IAAI;EAIsB,SAHvB,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;EAGhB,WAF9B,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY;EAEX;;AAG7D,SAAS,SAAS,GAA0C;CAC1D,OAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;ACrNjE,SAAgB,qBACd,KACA,QACQ;CACR,OAAO,IAAI,QACT,8DACC,QAAQ,KAAa,aAAiC;EACrD,MAAM,IAAI,OAAO;EACjB,IAAI,MAAM,QAAQ,MAAM,QAAW,OAAO;EAC1C,IAAI,UAAU;GACZ,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,OAAO,OAAO,EAAE;GACrE,OAAO,IAAI,OAAO,EAAE,CAAC,QAAQ,MAAM,KAAK,CAAC;;EAG3C,OAAO,OAAO,EAAE;GAEnB;;;;;;;;;;;;;;;;;;;;;;;ACPH,SAAgB,eAAe,KAAsB;CAGnD,IAAI,UAAU;CACd,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,KAAK,IAAI,WAAW,EAAE;EAC5B,IAAI,KAAK,MAAQ,OAAO,KAAM,WAAW,IAAI;;CAE/C,IAAI,YAAY,IAAI,OAAO;CAC3B,IAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,IAAI,EAAE;EAGtD,IAAI,QAAQ,WAAW,KAAK,EAAE,OAAO;EACrC,OAAO;;CAET,MAAM,QAAQ,QAAQ,aAAa;CACnC,OAAO,MAAM,WAAW,UAAU,IAAI,MAAM,WAAW,WAAW,IAAI,MAAM,WAAW,UAAU;;;;;ACMnG,SAAS,eAAe,OAA0B,MAAuC;CACvF,OAAO,MAAM,OAAO;EAAE,aAAa;EAAW,GAAG;EAAM,CAAC;;AAW1D,MAAM,gBAAgB,MAAM,cAA8C,OAAU;;;;;AA6BpF,SAAgB,eAAe,OAA+C;CAC5E,MAAM,EAAE,iBAAiB,UAAU,eAAe,SAAS,aAAa,aAAa;CAIrF,MAAM,UAAU,eAAe;CAC/B,MAAM,CAAC,OAAO,YAAY,MAAM,SAA6B;EAAE,QAAQ;EAAW;EAAS,CAAC;CAE5F,MAAM,gBAAgB;EACpB,IAAI,YAAY;EAChB,wBAAwB,gBAAgB,CACrC,MAAM,aAAa;GAClB,IAAI,CAAC,WAAW,SAAS;IAAE,QAAQ;IAAS;IAAU;IAAS,CAAC;IAChE,CACD,OAAO,MAAe;GACrB,IAAI,CAAC,WAAW,SAAS;IAAE,QAAQ;IAAS,OAAO,0BAA0B,EAAE;IAAE;IAAS,CAAC;IAC3F;EACJ,aAAa;GACX,YAAY;;IAQb,CAAC,iBAAiB,QAAQ,CAAC;CAE9B,IAAI,MAAM,WAAW,WAAW,MAAM,OAAO;EAC3C,MAAM,MAAM,MAAM;EAClB,OACE,oCAAC,cAAc,UAAf,EAAwB,OAAO,OAEN,EADtB,gBAAgB,cAAc,IAAI,GAAG,qBAAqB,IAAI,CACxC;;CAG7B,IAAI,MAAM,WAAW,WACnB,OAAO,oCAAC,cAAc,UAAf,EAAwB,OAAO,OAAkD,EAA1C,YAAY,KAA8B;CAE1F,OAAO,oCAAC,cAAc,UAAf,EAAwB,OAAO,OAA0C,EAAlC,SAAkC;;AAGlF,SAAS,qBAAqB,KAA8C;CAG1E,OACE,oCAAC,OAAD,EACE,OAAO;EACL,QAAQ;EACR,UAAU;EACV,SAAS;EACT,QAAQ;EACR,YAAY;EACZ,OAAO;EACP,cAAc;EACd,YAAY;EACZ,UAAU;EACX,EAOG,EALJ,oCAAC,OAAD,EAAK,OAAO,EAAE,YAAY,KAAK,EAAmB,EAAhB,IAAI,MAAY,EAClD,oCAAC,OAAD,EAAK,OAAO;EAAE,UAAU;EAAQ,WAAW;EAAO,EAAqB,EAAlB,IAAI,QAAc,EACvE,oCAAC,OAAD,EAAK,OAAO,EAAE,WAAW,QAAQ,EAE3B,EADJ,oCAAC,gBAAO,eAAqB,OAAE,IAAI,KAC/B,CACF;;;;;;;;AAYV,MAAM,8BAAc,IAAI,KAAa;AACrC,SAAS,aAAa,MAAoB;CACxC,IAAI,YAAY,IAAI,KAAK,EAAE;CAC3B,YAAY,IAAI,KAAK;CACrB,IAAI,OAAO,YAAY,eAAe,OAAO,QAAQ,SAAS,YAC5D,QAAQ,KACN,mBAAmB,KAAK,qIAEzB;;;;;;;;;;;;;;AAkBL,IAAa,cAAb,cAAiC,MAAM;CAIrC,YAAY,MAKT;EAID,MAAM,OAAO,KAAK,WAAW,QAAQ,KAAK;EAC1C,MAAM,OAAO,KAAK,OAAO,KAAK,KAAK,KAAK,KAAK;EAC7C,MAAM,OAAO,KAAK,OAAO,OAAO,KAAK,SAAS;EAC9C,MAAM,GAAG,OAAO,OAAO,OAAO;EAC9B,KAAK,OAAO;EACZ,KAAK,SAAS,KAAK;EACnB,KAAK,OAAO,KAAK,QAAQ;EACzB,KAAK,OAAO,KAAK,QAAQ;;;;;;;;;AAU7B,eAAe,qBAAqB,MAAsC;CACxE,IAAI;CACJ,IAAI,MAAM;CACV,IAAI;EACF,MAAM,MAAM,KAAK,MAAM;EACvB,OAAO,MAAM,KAAK,MAAM,IAAI,GAAG;SACzB;CAGR,IAAI,QAAQ,OAAO,SAAS,UAAU;EACpC,MAAM,IAAI;EACV,OAAO,IAAI,YAAY;GACrB,QAAQ,KAAK;GACb,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,QAAQ,KAAK;GAClE,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;GAC5C,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;GAC7C,CAAC;;CAEJ,MAAM,UAAU,IAAI,SAAS,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,CAAC,KAAK;CAC7D,OAAO,IAAI,YAAY;EACrB,QAAQ,KAAK;EACb,SAAS,WAAW,QAAQ,KAAK;EAClC,CAAC;;;;;;;AAUJ,SAAgB,sBAAmD;CACjE,MAAM,MAAM,MAAM,WAAW,cAAc;CAC3C,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,6DAA6D;CAE/E,IAAI,IAAI,WAAW,WAAW,CAAC,IAAI,UACjC,MAAM,IAAI,MACR,2HAED;CAEH,OAAO,IAAI;;;;;;;;AASb,SAAS,YAAoE;CAC3E,MAAM,MAAM,MAAM,WAAW,cAAc;CAC3C,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,mDAAmD;CAErE,OAAO;EACL,WAAW,IAAI,UAAU;EACzB,SAAS,IAAI;EACd;;;;;;;;;;;AAiCH,SAAgB,SACd,OACA,OAAqB,EAAE,EACF;CACrB,MAAM,EAAE,WAAW,YAAY,WAAW;CAC1C,MAAM,UAAU,KAAK,YAAY;CAGjC,MAAM,YAAY,KAAK,UAAU,KAAK,OAAO;CAE7C,MAAM,gBAAgB,MAAM,cACpB,qBAAqB,MAAM,KAAK,KAAK,UAAU,EAAE,CAAC,EACxD,CAAC,MAAM,KAAK,UAAU,CACvB;CAED,MAAM,CAAC,OAAO,YAAY,MAAM,SAK7B;EACD,MAAM,EAAE;EACR,SAAS,EAAE;EACX,SAAS,WAAW,CAAC,CAAC;EACtB,OAAO;EACR,CAAC;CACF,MAAM,CAAC,OAAO,YAAY,MAAM,SAAS,EAAE;CAG3C,MAAM,gBAAgB;EACpB,IAAI,CAAC,WAAW,CAAC,WAAW;GAC1B,UAAU,MAAO,EAAE,UAAU;IAAE,GAAG;IAAG,SAAS;IAAO,GAAG,EAAG;GAC3D;;EAEF,MAAM,OAAO,IAAI,iBAAiB;EAClC,IAAI,YAAY;EAChB,UAAU,OAAO;GAAE,GAAG;GAAG,SAAS;GAAM,OAAO;GAAM,EAAE;EAEvD,MAAM,OAAO,KAAK,UAAU;GAC1B,KAAK;GACL,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,UAAU,GAAG,EAAE;GACvD,CAAC;EAEF,QAAQ,iBAAiB,UAAU,SAAS;GAC1C,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C;GACA,QAAQ,KAAK;GACd,CAAC,CACC,KAAK,OAAO,SAAS;GACpB,IAAI,CAAC,KAAK,IACR,MAAM,MAAM,qBAAqB,KAAK;GAExC,OAAO,KAAK,MAAM;IAClB,CACD,MAAM,EAAE,SAAS,WAAW;GAC3B,IAAI,WAAW;GAIf,SAAS;IAAE,MAHK,KAAK,KAClB,MAAM,OAAO,YAAY,QAAQ,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAErC;IAAE;IAAS,SAAS;IAAO,OAAO;IAAM,CAAC;IACjE,CACD,OAAO,QAAQ;GACd,IAAI,WAAW;GAEf,IAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;GAC9D,UAAU,OAAO;IACf,GAAG;IACH,SAAS;IACT,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;IAC3D,EAAE;IACH;EAEJ,aAAa;GACX,YAAY;GACZ,KAAK,OAAO;;IAEb;EAAC;EAAS;EAAW;EAAe,MAAM;EAAU;EAAO;EAAQ,CAAC;CAEvE,OAAO;EACL,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,SAAS,MAAM;EACf,OAAO,MAAM;EACb,eAAe,UAAU,MAAM,IAAI,EAAE;EACtC;;;;;;;;;;;;AAiFH,SAAgB,iBACd,OACA,OAA6B,EAAE,EACF;CAC7B,MAAM,EAAE,WAAW,YAAY,WAAW;CAC1C,MAAM,UAAU,KAAK,YAAY;CACjC,MAAM,QAAQ,KAAK,UAAU;CAK7B,MAAM,WAAW,MAAM,cAAc,KAAK,UAAU,MAAM,EAAE,CAAC,MAAM,CAAC;CAEpE,MAAM,CAAC,OAAO,YAAY,MAAM,SAO7B;EACD,MAAM,EAAE;EACR,SAAS,EAAE;EACX,WAAW;EACX,KAAK;EACL,SAAS,WAAW,CAAC,CAAC;EACtB,OAAO;EACR,CAAC;CACF,MAAM,CAAC,OAAO,YAAY,MAAM,SAAS,EAAE;CAG3C,MAAM,gBAAgB;EACpB,IAAI,CAAC,WAAW,CAAC,WAAW;GAC1B,UAAU,MAAO,EAAE,UAAU;IAAE,GAAG;IAAG,SAAS;IAAO,GAAG,EAAG;GAC3D;;EAEF,MAAM,OAAO,IAAI,iBAAiB;EAClC,IAAI,YAAY;EAChB,UAAU,OAAO;GAAE,GAAG;GAAG,SAAS;GAAM,OAAO;GAAM,EAAE;EAMvD,MAAM,OAAO,KAAK,UAAU;GAC1B,GAAG;GACH,OAAO,MAAM;GACb,YAAY,MAAM,cAAc,EAAE;GAClC,UAAU,MAAM,YAAY,EAAE;GAC9B,iBAAiB,MAAM,mBAAmB,EAAE;GAC5C,SAAS,MAAM,WAAW,EAAE;GAC5B,GAAI,MAAM,SAAS,OAAO,EAAE,OAAO,MAAM,OAAO,GAAG,EAAE;GACtD,CAAC;EAGF,QAAQ,iBADqB,UAAU,iBAAiB,QAAQ,aAAa,MAChE;GACX,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C;GACA,QAAQ,KAAK;GACd,CAAC,CACC,KAAK,OAAO,SAAS;GACpB,IAAI,CAAC,KAAK,IACR,MAAM,MAAM,qBAAqB,KAAK;GAExC,OAAO,KAAK,MAAM;IAMlB,CACD,MAAM,EAAE,SAAS,MAAM,WAAW,UAAU;GAC3C,IAAI,WAAW;GAIf,SAAS;IACP,MAJc,KAAK,KAClB,MAAM,OAAO,YAAY,QAAQ,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAG9C;IACb;IACA;IACA,KAAK,OAAO;IACZ,SAAS;IACT,OAAO;IACR,CAAC;IACF,CACD,OAAO,QAAQ;GACd,IAAI,WAAW;GACf,IAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;GAC9D,UAAU,OAAO;IACf,GAAG;IACH,SAAS;IACT,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;IAC3D,EAAE;IACH;EAEJ,aAAa;GACX,YAAY;GACZ,KAAK,OAAO;;IAEb;EAAC;EAAS;EAAW;EAAU;EAAO;EAAO;EAAQ,CAAC;CAEzD,OAAO;EACL,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,WAAW,MAAM;EACjB,KAAK,MAAM;EACX,SAAS,MAAM;EACf,OAAO,MAAM;EACb,eAAe,UAAU,MAAM,IAAI,EAAE;EACtC;;AA4CH,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,wBAAwB,OAAU;;;;;;;;;;;;;;;;;;;AAoBxC,SAAgB,gBACd,OACA,OAA4B,EAAE,EACP;CACvB,aAAa,kBAAkB;CAC/B,MAAM,EAAE,WAAW,YAAY,WAAW;CAC1C,MAAM,SAAS,KAAK,kBAAkB;CACtC,MAAM,YAAY,KAAK,yBAAyB;CAChD,MAAM,YAAY,KAAK,aAAa;CAEpC,MAAM,CAAC,OAAO,YAAY,MAAM,SAK7B;EACD,OAAO;EACP,UAAU;EACV,QAAQ;EACR,OAAO;EACR,CAAC;CACF,MAAM,WAAW,MAAM,OAGpB,EAAE,CAAC;CAON,MAAM,SAAS,MAAM,kBAAkB;EACrC,MAAM,QAAQ,SAAS,QAAQ;EAC/B,IAAI,CAAC,aAAa,CAAC,OAAO;EAC1B,SAAS,QAAQ,OAAO,OAAO;EAC/B,SAAS,QAAQ,QAAQ;EACzB,AAAK,QAAQ,iBAAiB,UAAU,mBAAmB,mBAAmB,MAAM,CAAC,UAAU,EAC7F,QAAQ,QACT,CAAC,CAAC,YAAY,GAAG;EAClB,SAAS;GACP,OAAO;GACP,UAAU;GACV,QAAQ;GACR,uBAAO,IAAI,MAAM,8BAA8B;GAChD,CAAC;IACD,CAAC,WAAW,QAAQ,CAAC;CAExB,MAAM,MAAM,MAAM,aACf,WAAqC;EACpC,IAAI,CAAC,WAAW;GACd,UAAU,OAAO;IACf,GAAG;IACH,OAAO;IACP,uBAAO,IAAI,MAAM,yBAAyB;IAC3C,EAAE;GACH;;EAEF,SAAS,QAAQ,OAAO,OAAO;EAC/B,MAAM,OAAO,IAAI,iBAAiB;EAClC,SAAS,UAAU,EAAE,OAAO,MAAM;EAClC,SAAS;GAAE,OAAO;GAAW,UAAU;GAAM,QAAQ;GAAM,OAAO;GAAM,CAAC;EAEzE,CAAM,YAAY;GAChB,IAAI;IACF,MAAM,OAAO,KAAK,UAAU;KAC1B,GAAG;KACH,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;KAC7B,CAAC;IACF,MAAM,YAAY,MAAM,QACtB,iBAAiB,UAAU,cAAc,mBAAmB,MAAM,YAAY,CAAC,QAC/E;KACE,QAAQ;KACR,SAAS,EAAE,gBAAgB,oBAAoB;KAC/C;KACA,QAAQ,KAAK;KACd,CACF;IACD,IAAI,CAAC,UAAU,IACb,MAAM,MAAM,qBAAqB,UAAU;IAE7C,MAAM,EAAE,WAAY,MAAM,UAAU,MAAM;IAC1C,SAAS,QAAQ,QAAQ;IAEzB,MAAM,YAAY,KAAK,KAAK;IAC5B,IAAI,YAAY;IAEhB,OAAO,MAAM;KACX,IAAI,KAAK,OAAO,SAAS;KACzB,IAAI,KAAK,KAAK,GAAG,YAAY,WAC3B,MAAM,IAAI,MAAM,sCAAsC;KAGxD,MAAM,MADW,YAAY,IAAI,SAAS,WACpB,KAAK,OAAO;KAClC,IAAI,KAAK,OAAO,SAAS;KACzB,aAAa;KAEb,MAAM,WAAW,MAAM,QACrB,iBAAiB,UAAU,mBAAmB,mBAAmB,OAAO,IACxE;MAAE,QAAQ;MAAO,QAAQ,KAAK;MAAQ,CACvC;KACD,IAAI,CAAC,SAAS,IACZ,MAAM,MAAM,qBAAqB,SAAS;KAE5C,MAAM,OAAQ,MAAM,SAAS,MAAM;KAQnC,IAAI,KAAK,WAAW,WAAW;MAC7B,IAAI,KAAK,UACP,UAAU,OAAO;OAAE,GAAG;OAAG,UAAU,KAAK,YAAY;OAAM,EAAE;MAE9D;;KAEF,IAAI,KAAK,WAAW,QAAQ;MAC1B,SAAS,QAAQ,QAAQ;MACzB,SAAS;OACP,OAAO;OACP,UAAU;OACV,QAAQ,KAAK;OACb,OAAO;OACR,CAAC;MACF;;KAEF,IAAI,KAAK,WAAW,aAAa;MAC/B,SAAS,QAAQ,QAAQ;MACzB,SAAS;OACP,OAAO;OACP,UAAU;OACV,QAAQ;OACR,uBAAO,IAAI,MAAM,sBAAsB;OACxC,CAAC;MACF;;KAEF,SAAS,QAAQ,QAAQ;KACzB,SAAS;MACP,OAAO;MACP,UAAU;MACV,QAAQ;MACR,OAAO,IAAI,MAAM,KAAK,MAAM,QAAQ;MACrC,CAAC;KACF;;YAEK,GAAG;IACV,IAAI,aAAa,gBAAgB,EAAE,SAAS,cAAc;IAC1D,SAAS,QAAQ,QAAQ;IACzB,SAAS;KACP,OAAO;KACP,UAAU;KACV,QAAQ;KACR,OAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC;KACrD,CAAC;;MAEF;IAEN;EAAC;EAAW;EAAS,MAAM;EAAa;EAAQ;EAAW;EAAU,CACtE;CAED,MAAM,gBAAgB;EACpB,aAAa;GACX,SAAS,QAAQ,OAAO,OAAO;;IAEhC,EAAE,CAAC;CAEN,OAAO;EACL,OAAO,MAAM;EACb;EACA;EACA,UAAU,MAAM;EAChB,QAAQ,MAAM;EACd,OAAO,MAAM;EACd;;AAyFH,SAAgB,YAAY,OAA4C;CACtE,MAAM,EAAE,WAAW,YAAY,WAAW;CAC1C,MAAM,CAAC,OAAO,YAAY,MAAM,SAS7B;EACD,OAAO;EACP,QAAQ,EAAE;EACV,WAAW,EAAE;EACb,QAAQ;EACR,eAAe;EACf,UAAU;EACV,WAAW;EACX,OAAO;EACR,CAAC;CAWF,MAAM,WAAW,MAAM,OAGpB,EAAE,CAAC;CAMN,MAAM,SAAS,MAAM,kBAAkB;EACrC,MAAM,QAAQ,SAAS,QAAQ;EAC/B,IAAI,CAAC,aAAa,CAAC,OAAO;EAC1B,SAAS,QAAQ,OAAO,OAAO;EAC/B,SAAS,QAAQ,QAAQ;EACzB,AAAK,QAAQ,iBAAiB,UAAU,eAAe,mBAAmB,MAAM,CAAC,UAAU,EACzF,QAAQ,QACT,CAAC,CAAC,YAAY,GAAG;EAClB,UAAU,OAAO;GACf,GAAG;GACH,OAAO;GACP,uBAAO,IAAI,MAAM,8BAA8B;GAChD,EAAE;IACF,CAAC,WAAW,QAAQ,CAAC;CAExB,MAAM,MAAM,MAAM,aACf,UAAkB,OAA8B,EAAE,KAAK;EACtD,IAAI,CAAC,WAAW;GACd,UAAU,OAAO;IACf,GAAG;IACH,OAAO;IACP,uBAAO,IAAI,MAAM,yBAAyB;IAC3C,EAAE;GACH;;EAEF,SAAS,QAAQ,OAAO,OAAO;EAC/B,MAAM,OAAO,IAAI,iBAAiB;EAClC,SAAS,UAAU,EAAE,OAAO,MAAM;EAClC,SAAS;GACP,OAAO;GACP,QAAQ,EAAE;GACV,WAAW,EAAE;GACb,QAAQ;GACR,eAAe;GACf,UAAU,KAAK,YAAY;GAC3B,WAAW,KAAK,WAAW,YAAY,KAAK,aAAa;GACzD,OAAO;GACR,CAAC;EAEF,CAAM,YAAY;GAChB,IAAI;IAEF,MAAM,OAAO,KAAK,UAAU;KAC1B,GAAG;KACH;KACA,GAAI,KAAK,WAAW,EAAE,WAAW,KAAK,UAAU,GAAG,EAAE;KACtD,CAAC;IACF,MAAM,YAAY,MAAM,QACtB,iBAAiB,UAAU,UAAU,mBAAmB,MAAM,QAAQ,CAAC,QACvE;KACE,QAAQ;KACR,SAAS,EAAE,gBAAgB,oBAAoB;KAC/C;KACA,QAAQ,KAAK;KACd,CACF;IACD,IAAI,CAAC,UAAU,IACb,MAAM,MAAM,qBAAqB,UAAU;IAE7C,MAAM,EAAE,QAAQ,WAAW,eAAgB,MAAM,UAAU,MAAM;IAKjE,SAAS,QAAQ,QAAQ;IACzB,UAAU,OAAO;KACf,GAAG;KACH,UAAU;KACV,WAAW,cAAc,YAAY;KACtC,EAAE;IAkBH,IAAI,cAAc;IAClB,IAAI,WAAW;IACf,IAAI,aAAa;IAEjB,OAAO,MAAM;KACX,IAAI,KAAK,OAAO,SAAS;KACzB,IAAI,YAAY;KAChB,YAAY;KAEZ,IAAI;MACF,MAAM,iBAAiB;OACrB,KAAK,iBAAiB,UAAU,eAAe,mBAAmB,OAAO,CAAC;OAC1E;OACA,QAAQ,KAAK;OACb;OACA,UAAU,OAAO;QAEf,IAAI,GAAG,IAAI,cAAc,GAAG;QAC5B,MAAM,OAAO,aAAa,GAAG,KAAK;QAClC,MAAM,YAAY,GAAG,SAAS;QAC9B,MAAM,WAAW,mBAAmB,WAAW,GAAG,IAAI,KAAK;QAO3D,MAAM,QACJ,cAAc,gBACd,OAAO,SAAS,YAChB,SAAS,QACT,WAAW,OACP,OAAQ,KAA4B,MAAM,GAC1C;QAEN,UAAU,OAAO;SACf,GAAG;SACH,QAAQ,CAAC,GAAG,EAAE,QAAQ;UAAE,MAAM;UAAW;UAAM,CAAC;SAChD,WAAW,WAAW,CAAC,GAAG,EAAE,WAAW,SAAS,GAAG,EAAE;SACrD,QAAQ,UAAU,QAAQ,EAAE,UAAU,MAAM,QAAQ,EAAE;SACvD,EAAE;QAEH,IAAI,GAAG,UAAU,QAAQ;SACvB,aAAa;SACb,SAAS,QAAQ,QAAQ;SACzB,UAAU,OAAO;UAAE,GAAG;UAAG,OAAO;UAAQ,EAAE;eACrC,IACL,GAAG,UAAU,YACb,GAAG,UAAU,WACb,GAAG,UAAU,aACb;SAGA,aAAa;SACb,SAAS,QAAQ,QAAQ;SACzB,MAAM,UACJ,OAAO,SAAS,YAAY,SAAS,QAAQ,aAAa,OACtD,OAAQ,KAA8B,QAAQ,GAC9C,aAAa,GAAG;SACtB,UAAU,OAAO;UACf,GAAG;UACH,OAAO;UACP,OAAO,IAAI,MAAM,QAAQ;UAC1B,EAAE;eACE,IAAI,GAAG,UAAU,YAAY;SAKlC,aAAa;SACb,MAAM,gBACJ,OAAO,SAAS,YAAY,SAAS,QAAQ,cAAc,OACvD,OAAQ,KAA+B,SAAS,GAChD;SACN,UAAU,OAAO;UACf,GAAG;UACH,OAAO;UACP;UACD,EAAE;;;OAGR,CAAC;cAKK,KAAK;MACZ,IAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;MAG9D,IAAI,YAAY,GAAG;OACjB,SAAS,QAAQ,QAAQ;OACzB,UAAU,OAAO;QACf,GAAG;QACH,OAAO;QACP,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;QAC3D,EAAE;OACH;;;KAGJ,IAAI,YAAY;KAChB,MAAM,MAAM,KAAM,KAAK,OAAO;;YAEzB,GAAG;IACV,IAAI,aAAa,gBAAgB,EAAE,SAAS,cAAc;IAC1D,SAAS,QAAQ,QAAQ;IACzB,UAAU,OAAO;KACf,GAAG;KACH,OAAO;KACP,OAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC;KACrD,EAAE;;MAEH;IAEN;EAAC;EAAW;EAAS,MAAM;EAAQ,CACpC;CAED,MAAM,gBAAgB;EACpB,aAAa;GACX,SAAS,QAAQ,OAAO,OAAO;;IAEhC,EAAE,CAAC;CAEN,OAAO;EACL,OAAO,MAAM;EACb;EACA;EACA,QAAQ,MAAM;EACd,WAAW,MAAM;EACjB,QAAQ,MAAM;EACd,eAAe,MAAM;EACrB,UAAU,MAAM;EAChB,WAAW,MAAM;EACjB,OAAO,MAAM;EACd;;;;AAKH,SAAS,aAAa,KAAsB;CAC1C,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;SAChB;EACN,OAAO;;;;;;;;;AAUX,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,SAAS,mBACP,WACA,SACA,MACyB;CACzB,IAAI,CAAC,gBAAgB,IAAI,UAAU,EAAE,OAAO;CAC5C,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,OAAO;CACtD,MAAM,MAAM;CACZ,MAAM,MACJ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM;CACtF,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,UAAU,MAAM,QAAQ,IAAI,QAAQ,GAAI,IAAI,QAAsB,IAAI,OAAO,GAAG;CACtF,MAAM,OAAO,MAAM,QAAQ,IAAI,KAAK,GAAI,IAAI,OAAuB;CACnE,MAAM,WACJ,OAAO,IAAI,cAAc,WACrB,IAAI,YACJ,OAAO,IAAI,aAAa,WACtB,IAAI,WACJ,MAAM;CAEd,MAAM,WAA6B;EACjC,MAAM;EACN,IAAI,WAAW,GAAG,UAAU,GAAG,IAAI,MAAM,GAAG,GAAG;EAC/C,QAAQ;EACR;EACD;CACD,IAAI,WAAW,MACb,SAAS,UAAU;EAAE;EAAS;EAAM,UAAU,YAAY,KAAK;EAAQ;CAEzE,MAAM,SACJ,OAAO,IAAI,UAAU,WACjB,IAAI,QACJ,IAAI,YAAY,SAAS,OAAO,IAAI,YAAY,WAC7C,IAAI,UACL;CACR,IAAI,QAAQ,SAAS,QAAQ;CAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;AAiCT,eAAe,iBAAiB,MAMd;CAChB,MAAM,UAAkC;EACtC,QAAQ;EACR,iBAAiB;EAClB;CACD,IAAI,KAAK,aACP,QAAQ,mBAAmB,KAAK;CAElC,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK,KAAK;EACxC,QAAQ;EACR;EACA,QAAQ,KAAK;EACd,CAAC;CACF,IAAI,CAAC,KAAK,IACR,MAAM,MAAM,qBAAqB,KAAK;CAExC,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,MAAM,2BAA2B;CAG7C,MAAM,SAAS,KAAK,KAAK,WAAW;CACpC,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,SAAS;CACb,IAAI,YAAY;CAChB,IAAI,eAAe;CACnB,IAAI,cAAwB,EAAE;CAE9B,MAAM,iBAAiB;EACrB,IAAI,YAAY,WAAW,KAAK,iBAAiB,aAAa,CAAC,WAC7D;EAEF,KAAK,QAAQ;GACX,IAAI;GACJ,OAAO;GACP,MAAM,YAAY,KAAK,KAAK;GAC7B,CAAC;EAEF,eAAe;EACf,cAAc,EAAE;;CAIlB,OAAO,MAAM;EACX,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,MAAM;EAC3C,IAAI,MAAM;EACV,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;EAEjD,IAAI;EAGJ,QAAQ,UAAU,OAAO,OAAO,aAAa,MAAM,IAAI;GASrD,IAAI,YAAY,OAAO,SAAS,KAAK,OAAO,aAAa,MACvD;GAEF,MAAM,OAAO,OAAO,MAAM,GAAG,QAAQ;GAErC,MAAM,MAAM,OAAO,MAAM,SAAS,UAAU,EAAE;GAC9C,SAAS,OAAO,MAAM,WAAW,QAAQ,SAAS,IAAI,GAAG;GAEzD,IAAI,SAAS,IAAI;IAEf,UAAU;IACV;;GAEF,IAAI,KAAK,WAAW,IAAI,EAEtB;GAEF,MAAM,UAAU,KAAK,QAAQ,IAAI;GACjC,MAAM,QAAQ,YAAY,KAAK,OAAO,KAAK,MAAM,GAAG,QAAQ;GAC5D,IAAI,QAAQ,YAAY,KAAK,KAAK,KAAK,MAAM,UAAU,EAAE;GACzD,IAAI,MAAM,WAAW,IAAI,EAAE,QAAQ,MAAM,MAAM,EAAE;GAEjD,QAAQ,OAAR;IACE,KAAK;KACH,YAAY;KACZ;IACF,KAAK;KACH,eAAe;KACf;IACF,KAAK;KACH,YAAY,KAAK,MAAM;KACvB;;;;CAOR,IAAI,YAAY,SAAS,GACvB,UAAU;;AAId,SAAS,MAAM,IAAY,QAAqC;CAC9D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,IAAI,WAAW,SAAS,GAAG;EACjC,QAAQ,iBACN,eACM;GACJ,aAAa,EAAE;GACf,OAAO,IAAI,aAAa,WAAW,aAAa,CAAC;KAEnD,EAAE,MAAM,MAAM,CACf;GACD;;;;;;;;;;;;;;;;;;;AAgEJ,SAAgB,UAAU,OAA0C;CAClE,MAAM,EACJ,QACA,YAAY,EAAE,EACd,OACA,eACA,OACA,WACA,kBAAkB,+BAClB,kBAAkB,IAClB,cACE;CAEJ,MAAM,YAAY,UAAU;CAC5B,MAAM,WAAW,UAAU;CAC3B,MAAM,qBAAqB,UAAU;CAErC,OACE,oCAAC,OAAD;EAAgB;EAAW,OAAO,OAAO;EAyCnC,EAxCH,aAAa,WAAW,OACvB,oCAAC,OAAD,EAAK,OAAO,OAAO,WAGb,EAFJ,oCAAC,QAAD;EAAM,OAAO,OAAO;EAAS,eAAY;EAAS,GAClD,oCAAC,QAAD,EAAM,OAAO,OAAO,YAA4B,EAAhB,YAAgB,CAC5C,GACJ,MAEH,UAAU,SAAS,IAClB,oCAAC,OAAD,EAAK,OAAO,OAAO,cAIb,EAHH,UAAU,KAAK,MACd,oCAAC,kBAAD;EAAkB,KAAK,EAAE;EAAI,UAAU;EAAG,SAAS;EAAmB,EACtE,CACE,GACJ,MAEH,SACC,oCAAC,OAAD,EAAK,OAAO,OAAO,UAEb,EADJ,oCAAC,cAAD,EAAc,MAAM,QAAU,EAC1B,GACJ,MAEH,sBAAsB,gBACrB,oCAAC,OAAD,EAAK,OAAO,OAAO,eAGb,EAFJ,oCAAC,gBAAO,6BAAmC,EAC3C,oCAAC,OAAD,EAAK,OAAO,EAAE,WAAW,GAAG,EAAuB,EAApB,cAAoB,CAC/C,GACJ,MAEH,YAAY,QAAQ,oCAAC,YAAD,EAAmB,OAAS,IAAG,MAEnD,cAAc,UAAU,UAAU,SAAS,KAC1C,oCAAC,OAAD,EAAK,OAAO,OAAO,eAOb,EANJ,oCAAC,KAAD;EAAG,MAAM;EAAW,QAAO;EAAS,KAAI;EAAsB,OAAO,OAAO;EAExE,EADD,iBAAgB,KACf,EACJ,oCAAC,QAAD;EAAM,OAAO,OAAO;EAAW,OAAM;EAE9B,EAFqE,OAErE,CACH,GACJ,KACA;;;;;;;;;;;;;;AA+BV,SAAgB,QAAQ,OAAwC;CAC9D,MAAM,EACJ,SACA,cAAc,mCACd,cAAc,OACd,YACA,iBACA,cACE;CAEJ,MAAM,MAAM,YAAY,EAAE,SAAS,CAAC;CACpC,MAAM,CAAC,UAAU,eAAe,MAAM,SAAS,GAAG;CAElD,MAAM,SAAS,MAAM,aAClB,MAAwB;EACvB,GAAG,gBAAgB;EACnB,MAAM,IAAI,SAAS,MAAM;EACzB,IAAI,CAAC,KAAK,IAAI,UAAU,WAAW;EACnC,IAAI,IAAI,EAAE;IAEZ,CAAC,UAAU,IAAI,CAChB;CAED,OACE,oCAAC,OAAD;EAAgB;EAAW,OAAO,OAAO;EAsCnC,EArCJ,oCAAC,QAAD;EAAM,UAAU;EAAQ,OAAO,OAAO;EAsB/B,EArBL,oCAAC,SAAD;EACE,MAAK;EACL,OAAO;EACP,WAAW,MAAM,YAAY,EAAE,OAAO,MAAM;EAC/B;EACb,UAAU,IAAI,UAAU;EACxB,OAAO,OAAO;EACd,cAAW;EACX,GACF,oCAAC,UAAD;EACE,MAAK;EACL,UAAU,IAAI,UAAU,aAAa,SAAS,MAAM,KAAK;EACzD,OAAO,OAAO;EAGP,EADN,IAAI,UAAU,YAAY,MAAM,YAC1B,EACR,IAAI,UAAU,YACb,oCAAC,UAAD;EAAQ,MAAK;EAAS,SAAS,IAAI;EAAQ,OAAO,OAAO;EAEhD,EAF4D,OAE5D,GACP,KACC,EAEN,IAAI,UAAU,SACZ,cAAc,oCAAC,OAAD,EAAK,OAAO,OAAO,YAAgD,EAApC,iCAAoC,GAElF,oCAAC,WAAD;EACE,QAAQ,IAAI;EACZ,WAAW,IAAI;EACf,OAAO,IAAI;EACX,eAAe,IAAI;EACnB,OAAO,IAAI;EACX,WAAW,IAAI;EACE;EACjB,EAEA;;AAIV,SAAS,iBAAiB,OAGJ;CACpB,MAAM,EAAE,UAAU,YAAY;CAC9B,MAAM,CAAC,MAAM,WAAW,MAAM,SAAS,MAAM;CAC7C,MAAM,UAAU,SAAS;CACzB,MAAM,YAAY,UAAU,QAAQ,KAAK,SAAS,UAAU;CAC5D,MAAM,cAAc,UAAU,QAAQ,KAAK,MAAM,GAAG,QAAQ,GAAG,EAAE;CACjE,MAAM,cACJ,SAAS,WAAW,iBAChB,mBACA,SAAS,WAAW,mBAClB,mBACA,SAAS,WAAW,eAClB,eACA;CAEV,OACE,oCAAC,OAAD,EAAK,OAAO,OAAO,UAkDb,EAjDJ,oCAAC,UAAD;EAAQ,MAAK;EAAS,eAAe,SAAS,MAAM,CAAC,EAAE;EAAE,OAAO,OAAO;EAU9D,EATP,oCAAC,QAAD,EAAM,OAAO,OAAO,eAAmC,EAAnB,YAAmB,EACvD,oCAAC,QAAD,EAAM,OAAO,OAAO,iBAMb,EALJ,UACG,GAAG,QAAQ,SAAS,MAAM,QAAQ,aAAa,IAAI,KAAK,QACxD,SAAS,QACP,qBACA,WACD,EACP,oCAAC,QAAD,EAAM,OAAO,OAAO,gBAA+C,EAA9B,OAAO,SAAS,OAAc,CAC5D,EACR,OACC,oCAAC,aACC,oCAAC,OAAD,EAAK,OAAO,OAAO,UAA8B,EAAnB,SAAS,IAAU,EAChD,SAAS,QACR,oCAAC,OAAD,EAAK,OAAO,OAAO,OAA6B,EAArB,SAAS,MAAY,GAC9C,UACF,oCAAC,OAAD,EAAK,OAAO,OAAO,aA4Bb,EA3BJ,oCAAC,SAAD,EAAO,OAAO,OAAO,cAqBb,EApBN,oCAAC,eACC,oCAAC,YACE,QAAQ,QAAQ,KAAK,MACpB,oCAAC,MAAD;EAAI,KAAK;EAAG,OAAO,OAAO;EAErB,EADF,EACE,CACL,CACC,CACC,EACR,oCAAC,eACE,YAAY,KAAK,KAAK,MACrB,oCAAC,MAAD,EAAI,KAAK,GAMJ,EALF,IAAI,KAAK,MAAM,MACd,oCAAC,MAAD;EAAI,KAAK;EAAG,OAAO,OAAO;EAErB,EADF,WAAW,KAAK,CACd,CACL,CACC,CACL,CACI,CACF,EACP,YACC,oCAAC,OAAD,EAAK,OAAO,OAAO,eAEb,EAF4B,KAC9B,QAAQ,KAAK,SAAS,SAAQ,iDAC5B,GACJ,KACA,GACJ,KACA,GACJ,KACA;;AAIV,SAAS,WAAW,OAAwB;CAC1C,IAAI,UAAU,QAAQ,UAAU,QAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,MAAM;CAC3D,OAAO,OAAO,MAAM;;;;;;;;AAStB,SAAS,WAAW,OAA4C;CAC9D,MAAM,EAAE,UAAU;CAClB,IAAI,iBAAiB,aACnB,OACE,oCAAC,OAAD,EAAK,OAAO,OAAO,OASb,EARJ,oCAAC,aACC,oCAAC,gBAAO,cAAoB,OAAE,MAAM,QAAQ,MAAM,OAAO,CAAC,GACtD,EACL,MAAM,OACL,oCAAC,OAAD,EAAK,OAAO;EAAE,WAAW;EAAG,YAAY;EAAK,YAAY;EAAY,EAE/D,EADJ,oCAAC,gBAAO,QAAc,OAAE,MAAM,KAC1B,GACJ,KACA;CAGV,OACE,oCAAC,OAAD,EAAK,OAAO,OAAO,OAEb,EADJ,oCAAC,gBAAO,cAAoB,OAAE,MAAM,QAChC;;AAeV,SAAS,aAAa,OAA4C;CAChE,MAAM,SAAS,MAAM,cAAc,cAAc,MAAM,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC;CAC3E,OAAO,0DAAG,OAAU;;AAStB,SAAS,cAAc,MAAmC;CACxD,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,CAAC,MAAM,KAAK;CACrD,MAAM,SAAoB,EAAE;CAC5B,IAAI,IAAI;CACR,OAAO,IAAI,MAAM,QAAQ;EACvB,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,QAAW;GACtB;GACA;;EAGF,MAAM,QAAQ,KAAK,MAAM,gBAAgB;EACzC,IAAI,OAAO;GACT,MAAM,OAAO,MAAM,MAAM;GACzB,MAAM,MAAgB,EAAE;GACxB;GACA,OAAO,IAAI,MAAM,UAAU,CAAC,WAAW,KAAK,MAAM,MAAM,GAAG,EAAE;IAC3D,IAAI,KAAK,MAAM,MAAM,GAAG;IACxB;;GAEF;GACA,OAAO,KAAK;IAAE,MAAM;IAAQ;IAAM,MAAM,IAAI,KAAK,KAAK;IAAE,CAAC;GACzD;;EAGF,MAAM,IAAI,KAAK,MAAM,oBAAoB;EACzC,IAAI,GAAG;GACL,OAAO,KAAK;IACV,MAAM;IACN,OAAO,EAAE,IAAI;IACb,MAAM,EAAE;IACT,CAAC;GACF;GACA;;EAGF,IAAI,cAAc,KAAK,KAAK,EAAE;GAC5B,MAAM,QAAkB,EAAE;GAC1B,OAAO,IAAI,MAAM,UAAU,cAAc,KAAK,MAAM,MAAM,GAAG,EAAE;IAC7D,MAAM,MAAM,MAAM,MAAM,IAAI,QAAQ,eAAe,GAAG,CAAC;IACvD;;GAEF,OAAO,KAAK;IAAE,MAAM;IAAQ;IAAO,CAAC;GACpC;;EAGF,IAAI,KAAK,MAAM,KAAK,IAAI;GACtB;GACA;;EAGF,MAAM,MAAgB,CAAC,KAAK;EAC5B;EACA,OAAO,IAAI,MAAM,QAAQ;GACvB,MAAM,OAAO,MAAM,MAAM;GACzB,IACE,KAAK,MAAM,KAAK,MAChB,aAAa,KAAK,KAAK,IACvB,OAAO,KAAK,KAAK,IACjB,cAAc,KAAK,KAAK,EAExB;GAEF,IAAI,KAAK,KAAK;GACd;;EAEF,OAAO,KAAK;GAAE,MAAM;GAAK,MAAM,IAAI,KAAK,IAAI;GAAE,CAAC;;CAGjD,OAAO,OAAO,KAAK,GAAG,QAAQ;EAC5B,QAAQ,EAAE,MAAV;GACE,KAAK,KAAK;IACR,MAAM,MAAM,IAAI,EAAE;IAClB,MAAM,eAAe,EAAE,UAAU,IAAI,OAAO,KAAK,EAAE,UAAU,IAAI,OAAO,KAAK,OAAO;IACpF,OACE,oCAAC,KAAD;KAAK,KAAK;KAAK,OAAO;KAEhB,EADH,aAAa,EAAE,KAAK,CACjB;;GAGV,KAAK,QACH,OACE,oCAAC,OAAD;IAAK,KAAK;IAAK,OAAO,OAAO;IAAW,aAAW,EAAE,QAAQ;IAEvD,EADJ,oCAAC,cAAM,EAAE,KAAY,CACjB;GAEV,KAAK,QACH,OACE,oCAAC,MAAD;IAAI,KAAK;IAAK,OAAO,OAAO;IAIvB,EAHF,EAAE,MAAM,KAAK,MAAM,MAClB,oCAAC,MAAD,EAAI,KAAK,GAA4B,EAAxB,aAAa,KAAK,CAAM,CACrC,CACC;GAET,KAAK,KACH,OACE,oCAAC,KAAD;IAAG,KAAK;IAAK,OAAO,OAAO;IAEvB,EADD,aAAa,EAAE,KAAK,CACnB;;GAGV;;;;;;;AAQJ,SAAS,aAAa,MAAiC;CACrD,MAAM,WAA8B,EAAE;CACtC,IAAI,YAAY;CAChB,IAAI,MAAM;CAGV,MAAM,WAGD;EACH;GAAE,IAAI;GAAa,SAAS,MAAM,oCAAC,QAAD,EAAM,OAAO,OAAO,YAAyB,EAAZ,EAAE,GAAU;GAAE;EACjF;GACE,IAAI;GACJ,SAAS,MAAM;IAQb,IAAI,eAAe,EAAE,GAAG,EACtB,OACE,oCAAC,KAAD;KAAG,MAAM,EAAE;KAAI,QAAO;KAAS,KAAI;KAAsB,OAAO,OAAO;KAEnE,EADD,EAAE,GACD;IAGR,OAAO,0DAAG,EAAE,GAAM;;GAErB;EACD;GAAE,IAAI;GAAmB,SAAS,MAAM,oCAAC,gBAAQ,EAAE,GAAY;GAAE;EACjE;GAAE,IAAI;GAAe,SAAS,MAAM,oCAAC,YAAI,EAAE,GAAQ;GAAE;EACtD;CAED,OAAO,UAAU,SAAS,GAAG;EAC3B,IAAI,WAAuE;EAC3E,KAAK,MAAM,EAAE,IAAI,YAAY,UAAU;GACrC,MAAM,IAAI,GAAG,KAAK,UAAU;GAC5B,IAAI,MAAM,aAAa,QAAQ,EAAE,QAAQ,SAAS,MAChD,WAAW;IAAE,KAAK,EAAE;IAAO,KAAK,EAAE,GAAG;IAAQ,MAAM,OAAO,EAAE;IAAE;;EAGlE,IAAI,aAAa,MAAM;GACrB,SAAS,KAAK,UAAU;GACxB;;EAEF,IAAI,SAAS,MAAM,GAAG,SAAS,KAAK,UAAU,MAAM,GAAG,SAAS,IAAI,CAAC;EACrE,SAAS,KAAK,oCAAC,MAAM,UAAP,EAAgB,KAAK,OAAuC,EAA/B,SAAS,KAAsB,CAAC;EAC3E,YAAY,UAAU,MAAM,SAAS,MAAM,SAAS,IAAI;;CAE1D,OAAO;;AAUT,MAAM,OACJ;AACF,MAAM,OAAO;AAEb,MAAM,SAA8C;CAClD,YAAY;EACV,YAAY;EACZ,UAAU;EACV,YAAY;EACZ,OAAO;EACR;CACD,WAAW;EAAE,SAAS;EAAQ,YAAY;EAAU,KAAK;EAAG,SAAS;EAAS;CAC9E,SAAS;EACP,SAAS;EACT,OAAO;EACP,QAAQ;EACR,cAAc;EACd,QAAQ;EACR,gBAAgB;EAChB,WAAW;EACZ;CACD,YAAY,EAAE,OAAO,WAAW;CAChC,UAAU,EAAE,WAAW,GAAG;CAC1B,IAAI;EAAE,UAAU;EAAI,YAAY;EAAK,QAAQ;EAAc;CAC3D,IAAI;EAAE,UAAU;EAAI,YAAY;EAAK,QAAQ;EAAc;CAC3D,IAAI;EAAE,UAAU;EAAI,YAAY;EAAK,QAAQ;EAAc;CAC3D,WAAW,EAAE,QAAQ,WAAW;CAChC,MAAM;EAAE,QAAQ;EAAW,aAAa;EAAI;CAC5C,WAAW;EACT,YAAY;EACZ,UAAU;EACV,YAAY;EACZ,QAAQ;EACR,cAAc;EACd,SAAS;EACT,WAAW;EACX,QAAQ;EACT;CACD,YAAY;EACV,YAAY;EACZ,UAAU;EACV,YAAY;EACZ,SAAS;EACT,cAAc;EACf;CACD,MAAM;EAAE,OAAO;EAAW,gBAAgB;EAAa;CACvD,eAAe;EACb,WAAW;EACX,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,cAAc;EACd,OAAO;EACR;CACD,OAAO;EACL,WAAW;EACX,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,cAAc;EACd,OAAO;EACR;CACD,eAAe;EACb,WAAW;EACX,WAAW;EACX,SAAS;EACT,gBAAgB;EAChB,YAAY;EACZ,KAAK;EACN;CACD,YAAY;EAAE,UAAU;EAAI,OAAO;EAAW,gBAAgB;EAAQ;CACtE,WAAW;EACT,UAAU;EACV,YAAY;EACZ,eAAe;EACf,eAAe;EACf,SAAS;EACT,cAAc;EACd,YAAY;EACZ,OAAO;EACP,QAAQ;EACT;CACD,cAAc;EAAE,SAAS;EAAQ,eAAe;EAAU,KAAK;EAAG,cAAc;EAAG;CACnF,UAAU;EACR,QAAQ;EACR,cAAc;EACd,YAAY;EACZ,UAAU;EACX;CACD,gBAAgB;EACd,SAAS;EACT,YAAY;EACZ,KAAK;EACL,OAAO;EACP,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,cAAc;EACd,QAAQ;EACR,YAAY;EACZ,UAAU;EACV,OAAO;EACR;CACD,eAAe;EACb,YAAY;EACZ,UAAU;EACV,eAAe;EACf,eAAe;EACf,OAAO;EACR;CACD,iBAAiB;EAAE,OAAO;EAAW,MAAM;EAAG;CAC9C,gBAAgB,EAAE,OAAO,WAAW;CACpC,UAAU;EACR,YAAY;EACZ,UAAU;EACV,QAAQ;EACR,SAAS;EACT,YAAY;EACZ,OAAO;EACP,WAAW;EACZ;CACD,aAAa;EAAE,SAAS;EAAG,WAAW;EAAQ;CAC9C,cAAc;EAAE,OAAO;EAAQ,gBAAgB;EAAY,UAAU;EAAI;CACzE,WAAW;EACT,WAAW;EACX,SAAS;EACT,cAAc;EACd,YAAY;EACZ,OAAO;EACR;CACD,WAAW;EAAE,SAAS;EAAW,cAAc;EAAqB,OAAO;EAAW;CACtF,eAAe;EAAE,UAAU;EAAI,OAAO;EAAW,SAAS;EAAW;CACrE,UAAU;EAAE,YAAY;EAAM,UAAU;EAAI,OAAO;EAAW;CAC9D,UAAU;EAAE,SAAS;EAAQ,KAAK;EAAG,cAAc;EAAI;CACvD,WAAW;EACT,MAAM;EACN,SAAS;EACT,QAAQ;EACR,cAAc;EACd,UAAU;EACV,YAAY;EACb;CACD,YAAY;EACV,SAAS;EACT,QAAQ;EACR,cAAc;EACd,YAAY;EACZ,OAAO;EACP,UAAU;EACV,YAAY;EACZ,QAAQ;EACT;CACD,YAAY;EACV,SAAS;EACT,QAAQ;EACR,cAAc;EACd,YAAY;EACZ,OAAO;EACP,UAAU;EACV,QAAQ;EACT;CACD,YAAY;EAAE,SAAS;EAAU,OAAO;EAAW,WAAW;EAAU;CACzE"}