{"version":3,"file":"grasshopper.cjs","names":["validateServerUrl","ComputeError","ErrorCodes","ErrorCodes","ComputeError","readField","isDefinitionRef","fetchCompute","getResponseWireSize","base64ByteArray","detectBase64Payload","encodeStringToBase64","isDefinitionRef","ComputeError","ErrorCodes","readField","getResponseWireSize","isDefinitionRef","readField","classifyProbeFailure","ComputeError","ErrorCodes","validateServerUrl","readField","hasField","itemType","downloadFileData","ComputeError","ErrorCodes","readField","hasField","ComputeError","readField","ComputeError","ErrorCodes","fetchCompute","readField","readField","ComputeError","ErrorCodes"],"sources":["../src/grasshopper/server/compute-server-stats.ts","../src/core/utils/warnings.ts","../src/grasshopper/solve.ts","../src/grasshopper/scheduler/stable-hash.ts","../src/grasshopper/scheduler/solve-scheduler.ts","../src/grasshopper/client/grasshopper-client.ts","../src/grasshopper/io/output/rhino-decoder.ts","../src/grasshopper/io/output/response-processors.ts","../src/grasshopper/client/grasshopper-response-processor.ts","../src/grasshopper/data-tree/tree-path.ts","../src/grasshopper/io/input/numeric-rounding.ts","../src/grasshopper/io/input/transformers.ts","../src/grasshopper/io/input/input-type-parsers.ts","../src/grasshopper/io/input/normalize-default.ts","../src/grasshopper/io/input/input-processors.ts","../src/grasshopper/io/normalize-schema.ts","../src/grasshopper/io/definition-io.ts","../src/grasshopper/io/schema-endpoint.ts","../src/grasshopper/io/normalize-ui-schema.ts","../src/grasshopper/data-tree/data-tree.ts"],"sourcesContent":["import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { validateServerUrl } from '@/core/server/validate-server-url';\n\n/**\n * ComputeServerStats provides methods to query Rhino Compute server statistics.\n *\n * @public Use this for server health monitoring and statistics.\n *\n * @example\n * ```typescript\n * const stats = new ComputeServerStats('http://localhost:6500', 'your-api-key');\n *\n * try {\n *   const isOnline = await stats.isServerOnline();\n *   const children = await stats.getActiveChildren();\n *   const version = await stats.getVersion();\n *\n *   // Or get everything at once\n *   const allStats = await stats.getServerStats();\n * } finally {\n *   await stats.dispose(); // Clean up resources\n * }\n * ```\n */\nexport default class ComputeServerStats {\n\tprivate readonly serverUrl: string;\n\tprivate readonly apiKey?: string;\n\tprivate disposed = false;\n\tprivate activeMonitors: Set<() => void> = new Set();\n\tprivate activeTimeouts: Set<ReturnType<typeof setTimeout>> = new Set();\n\n\t/** Timeout (ms) for the fast read/monitoring endpoints. */\n\tprivate static readonly DEFAULT_TIMEOUT_MS = 5000;\n\n\t/** Timeout (ms) for child-lifecycle POSTs — a cold Windows child can take ~30s to spawn. */\n\tprivate static readonly LIFECYCLE_TIMEOUT_MS = 60_000;\n\n\t/** Floor for `monitor()`'s `intervalMs` — anything lower hot-loops the server. */\n\tprivate static readonly MIN_MONITOR_INTERVAL_MS = 100;\n\n\t/**\n\t * @param serverUrl - Base URL of the Rhino Compute server with http:// or https:// scheme (e.g., 'http://localhost:6500')\n\t * @param apiKey - Optional API key for authentication\n\t */\n\tconstructor(serverUrl: string, apiKey?: string) {\n\t\tthis.serverUrl = validateServerUrl(serverUrl);\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Build request headers with optional API key.\n\t */\n\tprivate buildHeaders(): Record<string, string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\t'Content-Type': 'application/json'\n\t\t};\n\n\t\tif (this.apiKey) {\n\t\t\theaders['RhinoComputeKey'] = this.apiKey;\n\t\t}\n\n\t\treturn headers;\n\t}\n\n\t/**\n\t * `fetch` wrapper that aborts after `timeoutMs` so a hung connection can't stall\n\t * a probe (or the `monitor()` loop) forever. Pass `0` to disable the timeout.\n\t */\n\tprivate fetchWithTimeout(\n\t\turl: string,\n\t\tinit: RequestInit = {},\n\t\ttimeoutMs: number = ComputeServerStats.DEFAULT_TIMEOUT_MS\n\t): Promise<Response> {\n\t\t// Merge caller headers OVER the defaults instead of `{ headers: …, ...init }`,\n\t\t// where any `init.headers` would silently replace the whole set (dropping\n\t\t// the API key). BOTH sides go through `new Headers()` so every key is\n\t\t// normalized (lowercased) identically — a caller's `Content-Type` replaces\n\t\t// the default instead of coexisting as a differently-cased duplicate that\n\t\t// the runtime would combine into \"application/json, text/plain\".\n\t\tconst merged = new Headers(this.buildHeaders());\n\t\tif (init.headers) {\n\t\t\tnew Headers(init.headers).forEach((value, key) => {\n\t\t\t\tmerged.set(key, value);\n\t\t\t});\n\t\t}\n\t\tconst headers: Record<string, string> = {};\n\t\tmerged.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tconst requestInit: RequestInit = { ...init, headers };\n\t\tif (timeoutMs > 0 && !requestInit.signal) {\n\t\t\trequestInit.signal = AbortSignal.timeout(timeoutMs);\n\t\t}\n\t\treturn fetch(url, requestInit);\n\t}\n\n\t/**\n\t * Check if the server is online.\n\t *\n\t * This is a single-sample probe: it returns `true` only on a 2xx from the\n\t * proxy liveness root `/`, and `false` for every other outcome (non-2xx,\n\t * network error, or timeout). A cold or briefly-busy-but-up server can therefore\n\t * read as offline — callers that gate on this (e.g. client construction)\n\t * should retry rather than treat a single `false` as authoritative.\n\t *\n\t * @param timeoutMs - Abort the probe after this many ms (default: 5000).\n\t *   Pass `0` to disable the timeout. Prevents a hung connection from\n\t *   stalling the caller indefinitely.\n\t */\n\tpublic async isServerOnline(timeoutMs: number = 5000): Promise<boolean> {\n\t\treturn (await this.probeServer(timeoutMs)).online;\n\t}\n\n\t/**\n\t * Detailed liveness probe backing {@link isServerOnline}.\n\t *\n\t * Reports what the probe actually saw so callers can distinguish \"connection\n\t * failed\" (`error` set) from \"server answered non-2xx\" (`status` set) — e.g.\n\t * a 401 from a proxy that requires an API key is a misconfiguration, not an\n\t * offline server. Same single-sample caveat as {@link isServerOnline}.\n\t *\n\t * @param timeoutMs - Abort the probe after this many ms (default: 5000).\n\t *   Pass `0` to disable the timeout.\n\t * @returns `online` plus either the HTTP `status` the server answered with,\n\t *   or the `error` the connection attempt failed with.\n\t */\n\tpublic async probeServer(\n\t\ttimeoutMs: number = 5000\n\t): Promise<{ online: boolean; status?: number; error?: string }> {\n\t\tthis.ensureNotDisposed();\n\n\t\t// The rhino.compute proxy has no `/healthcheck` route; its real liveness\n\t\t// signal is `GET /`, which returns \"compute.rhino3d running\". Probing an\n\t\t// unknown path would instead be forwarded to a child and only tells us the\n\t\t// proxy can reach one.\n\t\tconst url = `${this.serverUrl}/`;\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(url, { method: 'GET' }, timeoutMs);\n\n\t\t\treturn { online: response.ok, status: response.status };\n\t\t} catch (err) {\n\t\t\tgetLogger().debug('[ComputeServerStats] Fetch error:', err);\n\t\t\treturn {\n\t\t\t\tonline: false,\n\t\t\t\terror: err instanceof Error ? `${err.name}: ${err.message}` : String(err)\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Get the number of active child processes on the server.\n\t *\n\t * By default the proxy's `/activechildren` endpoint will *spawn* children up\n\t * to the configured count if none are running, then report the count — which\n\t * wakes (and bills) an idle server. Pass `{ initialize: false }` for a passive\n\t * read that reports the current count without spawning; use this for\n\t * monitoring or before a purge/probe where you must not wake the server.\n\t *\n\t * @param options.initialize - When `false`, append `?initialize=false` so the\n\t *   server reports without spawning. Defaults to `true` (the server's default).\n\t * @returns Number of active children, or null if unavailable\n\t */\n\tpublic async getActiveChildren(options: { initialize?: boolean } = {}): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\tconst { initialize = true } = options;\n\t\tconst url = initialize\n\t\t\t? `${this.serverUrl}/activechildren`\n\t\t\t: `${this.serverUrl}/activechildren?initialize=false`;\n\n\t\t// `initialize` mode may spawn children before answering — give it the\n\t\t// lifecycle budget; the passive read stays on the short default.\n\t\tconst timeoutMs = initialize\n\t\t\t? ComputeServerStats.LIFECYCLE_TIMEOUT_MS\n\t\t\t: ComputeServerStats.DEFAULT_TIMEOUT_MS;\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(url, {}, timeoutMs);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch active children:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Strict integer parse: `parseInt` would accept garbage-prefixed bodies\n\t\t\t// (e.g. an HTML error page starting \"302 Found\" → 302 children).\n\t\t\tconst text = (await response.text()).trim();\n\t\t\tif (!/^\\d+$/.test(text)) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Invalid active children response:', text);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn parseInt(text, 10);\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching active children:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the server version information.\n\t *\n\t * @returns Version object with rhino, compute, and git_sha, or null if unavailable\n\t */\n\tpublic async getVersion(): Promise<{\n\t\trhino: string;\n\t\tcompute: string;\n\t\tgit_sha: string | null;\n\t} | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/version`);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch version:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Read body as text first, then try JSON.parse — avoids the\n\t\t\t// \"Body has already been read\" error if response.json() fails.\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn {\n\t\t\t\t\trhino: json.rhino ?? '',\n\t\t\t\t\tcompute: json.compute ?? '',\n\t\t\t\t\tgit_sha: json.git_sha ?? null\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn { rhino: text, compute: '', git_sha: null };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching version:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the plugins installed on the server.\n\t *\n\t * Returns a `name → version` map of non-core plugins the server has loaded,\n\t * or `null` if the request failed. Pass `kind` to choose which inventory:\n\t * `'gh'` (default) lists Grasshopper add-on assemblies via\n\t * `/plugins/gh/installed`; `'rhino'` lists Rhino plugins via\n\t * `/plugins/rhino/installed`. Plugins that ship with Rhino / are core\n\t * libraries are excluded by the server.\n\t *\n\t * @param kind - `'gh'` for Grasshopper add-ons (default) or `'rhino'` for Rhino plugins.\n\t * @returns Map of plugin name to version, or `null` on failure.\n\t *\n\t * @example\n\t * ```ts\n\t * const gh = await stats.getInstalledPlugins();        // Grasshopper add-ons\n\t * const selvaVersion = gh?.['Selva'] ?? null;\n\t * ```\n\t */\n\tpublic async getInstalledPlugins(\n\t\tkind: 'gh' | 'rhino' = 'gh'\n\t): Promise<Record<string, string> | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/plugins/${kind}/installed`);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn(`[ComputeServerStats] Failed to fetch ${kind} plugins:`, response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn json && typeof json === 'object' ? (json as Record<string, string>) : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn(`[ComputeServerStats] Error fetching ${kind} plugins:`, err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get comprehensive server statistics.\n\t * Fetches all available server information in parallel.\n\t *\n\t * @returns Object containing server status and available stats\n\t */\n\tpublic async getServerStats(): Promise<{\n\t\tisOnline: boolean;\n\t\tversion?: { rhino: string; compute: string; git_sha: string | null };\n\t\tactiveChildren?: number;\n\t}> {\n\t\tthis.ensureNotDisposed();\n\n\t\tconst isOnline = await this.isServerOnline();\n\n\t\tif (!isOnline) {\n\t\t\treturn { isOnline: false };\n\t\t}\n\n\t\t// Passive child count — never spawn from a stats read, or merely viewing\n\t\t// server health would wake (and bill) an idle server.\n\t\tconst [version, activeChildren] = await Promise.all([\n\t\t\tthis.getVersion(),\n\t\t\tthis.getActiveChildren({ initialize: false })\n\t\t]);\n\n\t\treturn {\n\t\t\tisOnline: true,\n\t\t\t...(version && { version }),\n\t\t\t...(activeChildren !== null && { activeChildren })\n\t\t};\n\t}\n\n\t/**\n\t * Purge the server's solve-results / URL-data cache.\n\t *\n\t * POSTs to `cache/purge` and returns the number of entries removed, or `null`\n\t * if the request failed. This clears cached solve responses and fetched\n\t * definition-URL data; it does NOT evict the definition cache (active\n\t * `pointer` references stay valid).\n\t *\n\t * **Caveat:** `cache/purge` is forwarded by the rhino.compute proxy to a\n\t * single round-robin-selected child, so in a multi-child deployment one call\n\t * purges one child's cache. Call repeatedly (or size the pool to 1) if you\n\t * need a fleet-wide purge.\n\t *\n\t * @returns Number of entries removed, or `null` on failure.\n\t *\n\t * @example\n\t * ```ts\n\t * const removed = await stats.purgeCache();\n\t * if (removed !== null) console.log(`Purged ${removed} cached solves`);\n\t * ```\n\t */\n\tpublic async purgeCache(): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/cache/purge`, {\n\t\t\t\tmethod: 'POST'\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to purge cache:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Read text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(text);\n\t\t\t\treturn typeof json.purged === 'number' ? json.purged : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error purging cache:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort fleet-wide cache purge across a multi-child deployment.\n\t *\n\t * A single {@link purgeCache} POST is forwarded by the rhino.compute proxy to\n\t * just ONE round-robin-selected child, so the other children keep serving\n\t * stale cached solves. There is no proxy endpoint that addresses children\n\t * individually, so this method reads the active child count (passively, never\n\t * spawning) and fires `2 × count` sequential purges, relying on the proxy's\n\t * round-robin to spread the hits across the pool.\n\t *\n\t * **This is best-effort, not a guarantee.** Round-robin can revisit one child\n\t * and skip another; under concurrent traffic the rotation drifts. The result's\n\t * `confident` flag is `true` only when the server reports a single child (where\n\t * one purge is exact) — surface it so callers don't over-promise. For a hard\n\t * fleet-wide guarantee, run the deployment at `--childcount 1` or add a\n\t * server-side fan-out endpoint.\n\t *\n\t * @returns `{ totalPurged, calls, children, confident }`, or `null` if the\n\t *   child count couldn't be read (server unreachable). `totalPurged` sums the\n\t *   per-call counts; `calls` is how many purges were issued; `children` is the\n\t *   reported pool size; `confident` is `true` only at a single-child pool.\n\t *\n\t * @example\n\t * ```ts\n\t * const r = await stats.purgeAllChildren();\n\t * if (r && !r.confident) {\n\t *   console.warn(`Purged ~${r.totalPurged} across ${r.children} children (best-effort)`);\n\t * }\n\t * ```\n\t */\n\tpublic async purgeAllChildren(): Promise<{\n\t\ttotalPurged: number;\n\t\tcalls: number;\n\t\tchildren: number;\n\t\tconfident: boolean;\n\t} | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\t// Passive read — must not spawn children just to purge them.\n\t\tconst children = await this.getActiveChildren({ initialize: false });\n\t\tif (children === null) {\n\t\t\tgetLogger().warn('[ComputeServerStats] purgeAllChildren: could not read child count');\n\t\t\treturn null;\n\t\t}\n\n\t\tif (children === 0) {\n\t\t\t// No live children means nothing is cached on the compute side.\n\t\t\treturn { totalPurged: 0, calls: 0, children: 0, confident: true };\n\t\t}\n\n\t\t// 2× the pool size gives round-robin a strong chance of reaching every\n\t\t// child at least once. Sequential (not parallel) so the proxy advances its\n\t\t// round-robin cursor one child per call rather than racing them onto one.\n\t\tconst calls = children * 2;\n\t\tlet totalPurged = 0;\n\t\tfor (let i = 0; i < calls; i++) {\n\t\t\tconst purged = await this.purgeCache();\n\t\t\tif (purged !== null) totalPurged += purged;\n\t\t}\n\n\t\treturn { totalPurged, calls, children, confident: children === 1 };\n\t}\n\n\t/**\n\t * Get the server's current UTC clock.\n\t *\n\t * GETs `/servertime`, which the server emits as a JSON-encoded ISO-8601\n\t * timestamp (e.g. `\"2026-06-18T08:30:00Z\"`). Useful for detecting clock skew\n\t * between caller and server. Returns `null` if the request failed or the body\n\t * isn't a parseable date.\n\t *\n\t * @returns A `Date` for the server's UTC time, or `null` on failure.\n\t */\n\tpublic async getServerTime(): Promise<Date | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/servertime`);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch server time:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Body is a JSON string (\"2026-…Z\"); strip surrounding quotes if present.\n\t\t\tconst text = (await response.text()).trim().replace(/^\"|\"$/g, '');\n\t\t\tconst date = new Date(text);\n\t\t\treturn isNaN(date.getTime()) ? null : date;\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching server time:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get how long the rhino.compute proxy has been idle.\n\t *\n\t * GETs `/idlespan` on the proxy, which returns the seconds elapsed since the\n\t * last request was forwarded to a compute child. This is a proxy-level metric\n\t * (not proxied to a child) used by autoscalers to decide when a node can be\n\t * drained. Returns `null` if unavailable.\n\t *\n\t * @returns Idle time in seconds, or `null` on failure.\n\t */\n\tpublic async getIdleSpan(): Promise<number | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(`${this.serverUrl}/idlespan`);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn('[ComputeServerStats] Failed to fetch idle span:', response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst seconds = parseFloat((await response.text()).trim());\n\t\t\treturn isNaN(seconds) ? null : seconds;\n\t\t} catch (err) {\n\t\t\tgetLogger().warn('[ComputeServerStats] Error fetching idle span:', err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Fill the compute child pool up to the server's configured baseline.\n\t *\n\t * POSTs `/launch-children`. No-op when the pool is already at or above the\n\t * configured `--childcount`. Returns `{ spawned, active }` — how many children\n\t * were started and the resulting child count — or `null` on failure.\n\t *\n\t * To raise capacity above the baseline use {@link launchChild}; the baseline\n\t * itself can only be changed by restarting rhino.compute.\n\t *\n\t * @returns `{ spawned, active }`, or `null` on failure.\n\t */\n\tpublic async launchChildren(): Promise<{ spawned: number[]; active: number } | null> {\n\t\treturn this.postJson('/launch-children');\n\t}\n\n\t/**\n\t * Add a single compute child to the pool, optionally on a specific port.\n\t *\n\t * POSTs `/launch-child` (with `?port=N` when `port` is given). Unlike\n\t * {@link launchChildren}, this can push the pool above the baseline, up to the\n\t * server's `MaxChildren` cap. Returns `{ spawned: [port] }` on success, or\n\t * `null` on failure (server replies 400 for a bad port, 409 if the port is in\n\t * use, 503 at the max-children cap).\n\t *\n\t * @param port - Optional specific port to launch on; otherwise the next free one.\n\t * @returns `{ spawned }` listing the launched port, or `null` on failure.\n\t */\n\tpublic async launchChild(port?: number): Promise<{ spawned: number[] } | null> {\n\t\tconst path = port === undefined ? '/launch-child' : `/launch-child?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * Gracefully shut down compute children without respawning them.\n\t *\n\t * POSTs `/shutdown-children`. With no `port` it shuts down every child; with\n\t * `port` it targets just that one. Children do not respawn, but the next\n\t * `/grasshopper` request auto-spawns the pool back to the baseline. Returns\n\t * `{ shutdown, active }` — how many were stopped and the remaining count — or\n\t * `null` on failure.\n\t *\n\t * @param port - Optional port to target; omit to shut down all children.\n\t * @returns `{ shutdown, active }`, or `null` on failure.\n\t */\n\tpublic async shutdownChildren(\n\t\tport?: number\n\t): Promise<{ shutdown: number; active: number } | null> {\n\t\tconst path = port === undefined ? '/shutdown-children' : `/shutdown-children?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * Shut down compute children and respawn replacements (rolling restart).\n\t *\n\t * POSTs `/recycle-children`. With no `port` it recycles every child; with\n\t * `port` it recycles just that one. The server recycles sequentially (each\n\t * replacement is serving before the next child is stopped) so the pool never\n\t * drops to zero mid-recycle. Returns `{ shutdown, spawned, active }`, or\n\t * `null` on failure.\n\t *\n\t * @param port - Optional port to target; omit to recycle all children.\n\t * @returns `{ shutdown, spawned, active }`, or `null` on failure.\n\t */\n\tpublic async recycleChildren(\n\t\tport?: number\n\t): Promise<{ shutdown: number; spawned: number[]; active: number } | null> {\n\t\tconst path = port === undefined ? '/recycle-children' : `/recycle-children?port=${port}`;\n\t\treturn this.postJson(path);\n\t}\n\n\t/**\n\t * POST a control endpoint that replies with a JSON object and return it,\n\t * degrading to `null` on any non-2xx, non-JSON, or network failure. Shared by\n\t * the child-lifecycle methods so their failure semantics stay identical. Uses\n\t * the longer lifecycle timeout since a spawn/recycle can take ~30s.\n\t */\n\tprivate async postJson<T>(path: string): Promise<T | null> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\tconst response = await this.fetchWithTimeout(\n\t\t\t\t`${this.serverUrl}${path}`,\n\t\t\t\t{ method: 'POST' },\n\t\t\t\tComputeServerStats.LIFECYCLE_TIMEOUT_MS\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tgetLogger().warn(`[ComputeServerStats] POST ${path} failed:`, response.status);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Text-first so a non-JSON body can't throw \"body already read\".\n\t\t\tconst text = await response.text();\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(text) as T;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tgetLogger().warn(`[ComputeServerStats] Error on POST ${path}:`, err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Continuously monitor server stats at specified interval.\n\t *\n\t * @param callback - Function called with stats on each interval\n\t * @param intervalMs - Milliseconds between checks (default: 5000). Must be a\n\t *   finite number of at least 100 ms — lower values would hot-loop the server.\n\t * @returns Function to stop monitoring\n\t * @throws {ComputeError} `INVALID_CONFIG` if `intervalMs` is not a finite number >= 100.\n\t *\n\t * @example\n\t * ```typescript\n\t * const stopMonitoring = stats.monitor((data) => {\n\t *   console.log('Server stats:', data);\n\t * }, 3000);\n\t *\n\t * // Later...\n\t * stopMonitoring();\n\t * ```\n\t */\n\tpublic monitor(\n\t\tcallback: (stats: Awaited<ReturnType<typeof this.getServerStats>>) => void,\n\t\tintervalMs: number = 5000\n\t): () => void {\n\t\tthis.ensureNotDisposed();\n\n\t\tif (!Number.isFinite(intervalMs) || intervalMs < ComputeServerStats.MIN_MONITOR_INTERVAL_MS) {\n\t\t\t// ComputeError (not RangeError) so `err instanceof ComputeError` holds\n\t\t\t// across the package's whole public surface.\n\t\t\tthrow new ComputeError(\n\t\t\t\t`monitor() intervalMs must be a finite number >= ${ComputeServerStats.MIN_MONITOR_INTERVAL_MS}ms, ` +\n\t\t\t\t\t`got ${intervalMs}. Sub-${ComputeServerStats.MIN_MONITOR_INTERVAL_MS}ms polling would hot-loop the server.`,\n\t\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t\t{ context: { intervalMs } }\n\t\t\t);\n\t\t}\n\n\t\tlet active = true;\n\t\tlet currentTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n\t\tgetLogger().info(`🔄 Starting server stats monitoring every ${intervalMs}ms`);\n\n\t\tconst check = async () => {\n\t\t\t// Clear current timeout from tracking since it has fired\n\t\t\tif (currentTimeoutId !== null) {\n\t\t\t\tthis.activeTimeouts.delete(currentTimeoutId);\n\t\t\t\tcurrentTimeoutId = null;\n\t\t\t}\n\n\t\t\tif (!active || this.disposed) return;\n\n\t\t\ttry {\n\t\t\t\tconst _stats = await this.getServerStats();\n\n\t\t\t\t// Check again after async operation to prevent race condition\n\t\t\t\tif (!active || this.disposed) return;\n\n\t\t\t\ttry {\n\t\t\t\t\tcallback(_stats);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tgetLogger().error('[ComputeServerStats] Monitor callback threw:', err);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (!active || this.disposed) {\n\t\t\t\t\t// dispose()/stop racing an in-flight poll is normal shutdown, not an\n\t\t\t\t\t// error — the nested ensureNotDisposed() throw is expected here.\n\t\t\t\t\tgetLogger().debug('[ComputeServerStats] Monitor poll cancelled during shutdown:', err);\n\t\t\t\t} else {\n\t\t\t\t\tgetLogger().error('[ComputeServerStats] Failed to fetch stats during monitor:', err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (active && !this.disposed) {\n\t\t\t\tcurrentTimeoutId = setTimeout(() => void check(), intervalMs);\n\t\t\t\tthis.activeTimeouts.add(currentTimeoutId);\n\t\t\t}\n\t\t};\n\n\t\tconst stopMonitoring = () => {\n\t\t\tactive = false;\n\n\t\t\t// Clear any pending timeout\n\t\t\tif (currentTimeoutId !== null) {\n\t\t\t\tclearTimeout(currentTimeoutId);\n\t\t\t\tthis.activeTimeouts.delete(currentTimeoutId);\n\t\t\t\tcurrentTimeoutId = null;\n\t\t\t}\n\n\t\t\tthis.activeMonitors.delete(stopMonitoring);\n\t\t};\n\n\t\tthis.activeMonitors.add(stopMonitoring);\n\n\t\t// Explicitly mark as fire-and-forget since we don't need to await the initial call\n\t\tvoid check();\n\n\t\treturn stopMonitoring;\n\t}\n\n\t/**\n\t * Disposes of all resources and stops all active monitors.\n\t * Call this when you're done using the stats instance.\n\t */\n\tpublic async dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\n\t\t// Stop all active monitors (this will also clear their timeouts)\n\t\tfor (const stopMonitor of this.activeMonitors) {\n\t\t\tstopMonitor();\n\t\t}\n\t\tthis.activeMonitors.clear();\n\n\t\t// Clear any remaining timeouts (defensive cleanup)\n\t\tfor (const timeoutId of this.activeTimeouts) {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t\tthis.activeTimeouts.clear();\n\t}\n\n\t/**\n\t * Ensures the instance hasn't been disposed.\n\t */\n\tprivate ensureNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t'ComputeServerStats has been disposed and cannot be used',\n\t\t\t\tErrorCodes.INVALID_STATE,\n\t\t\t\t{ context: { disposed: this.disposed } }\n\t\t\t);\n\t\t}\n\t}\n}\n","import { getLogger } from './logger';\n\n/** Functions that already warned — the exposure risk doesn't change per call, so once is enough. */\nconst warnedFunctions = new Set<string>();\n\n/**\n * True only in a GENUINE browser/worker context — the case where an API key in\n * the config is actually exposed to end users. `typeof window !== 'undefined'`\n * alone is not enough: jsdom test environments define `window` too, and every\n * unsuppressed vitest/jest run would warn noisily (issue 110).\n *\n * Detection: `window` must exist, AND we must not be in Node (jsdom runs in\n * Node, so `process.versions.node` is defined there but never in a real\n * browser), AND the user agent must not carry jsdom's marker (belt-and-braces\n * for exotic setups that hide `process`). Genuine browsers pass all three.\n */\nfunction isRealBrowser(): boolean {\n\tif (typeof window === 'undefined') return false;\n\n\tconst proc = (globalThis as { process?: { versions?: { node?: unknown } } }).process;\n\tif (proc?.versions?.node != null) return false;\n\n\tconst nav = (globalThis as { navigator?: { userAgent?: unknown } }).navigator;\n\tconst userAgent = typeof nav?.userAgent === 'string' ? nav.userAgent : '';\n\tif (/jsdom/i.test(userAgent)) return false;\n\n\treturn true;\n}\n\nexport function warnIfClientSide(functionName: string, suppress?: boolean): void {\n\tif (suppress) {\n\t\treturn;\n\t}\n\n\tif (isRealBrowser() && !warnedFunctions.has(functionName)) {\n\t\twarnedFunctions.add(functionName);\n\t\tgetLogger().warn(\n\t\t\t`Warning: ${functionName} is running on the client side. For better performance and security, consider running this on the server side.`\n\t\t);\n\t}\n}\n\n/** @internal Reset the once-per-function dedupe — for tests only. */\nexport function resetClientSideWarnings(): void {\n\twarnedFunctions.clear();\n}\n","import { fetchCompute, ComputeError, ErrorCodes } from '@/core';\nimport type { ComputeConfig, ServerErrorCodeMap } from '@/core/types';\nimport { getResponseWireSize, setResponseWireSize } from '@/core/compute-fetch/wire-size';\nimport { base64ByteArray, detectBase64Payload, encodeStringToBase64 } from '@/core/utils/encoding';\nimport { getLogger } from '@/core/utils/logger';\nimport { readField } from '@/core/utils/read-field';\nimport { warnIfClientSide } from '@/core/utils/warnings';\n\nimport {\n\tGrasshopperRequestSchema,\n\tGrasshopperComputeConfig,\n\tGrasshopperComputeResponse,\n\tDataTree\n} from './types';\nimport { isDefinitionRef, type SolveDefinition } from '@/core/definition-ref';\n\n/**\n * The exact message the server throws when it can neither resolve a `pointer`\n * nor a base64 `algo` to a definition (ResthopperEndpoints.cs). This is the\n * signal that a cache-key pointer missed the server's definition cache (GC'd, or\n * a different child in the pool), so the caller should retry with the full\n * definition. Matched as a substring because the server wraps it with a category\n * prefix in its exception handler.\n *\n * NOTE: the server only includes this message when running in debug mode; its\n * production exception handler scrubs the message to a generic string. The\n * reliable signal is therefore {@link ErrorCodes.DEFINITION_NOT_CACHED}, derived\n * from the server's machine `code` (which isn't scrubbed). The string match is\n * kept as a fallback for debug-mode servers and forks that don't yet emit a code.\n */\nconst DEFINITION_LOAD_FAILED = 'Unable to load grasshopper definition';\n\n/**\n * Wire codes rhino.compute tags onto its error bodies. Passed to the transport\n * on every Grasshopper request — `core/` deliberately knows no backend's codes.\n */\nexport const GRASSHOPPER_SERVER_ERROR_CODES: ServerErrorCodeMap = {\n\tdefinition_not_cached: ErrorCodes.DEFINITION_NOT_CACHED\n};\n\n/** Attach the Grasshopper wire-code table without clobbering a caller's own entries. */\nexport function withGrasshopperErrorCodes<T extends ComputeConfig>(config: T): T {\n\treturn {\n\t\t...config,\n\t\tserverErrorCodes: { ...GRASSHOPPER_SERVER_ERROR_CODES, ...config.serverErrorCodes }\n\t};\n}\n\n/** Does this error look like a server-side definition-load miss? */\nfunction isDefinitionLoadMiss(error: unknown): boolean {\n\tif (!(error instanceof ComputeError)) return false;\n\treturn (\n\t\terror.code === ErrorCodes.DEFINITION_NOT_CACHED ||\n\t\terror.message.includes(DEFINITION_LOAD_FAILED)\n\t);\n}\n\n/**\n * Debug aid: a solve can return successfully yet hand back outputs whose\n * `InnerTree` is empty (`{}`), meaning that parameter produced nothing — often a\n * sign the definition didn't actually compute (wrong/missing inputs, a guarded\n * branch). The names tell you exactly which output was empty so you can trace it\n * back to the responsible branch.\n *\n * Only logs when `debug` is set: an empty output can be legitimate, so this is a\n * diagnostic, never a hard failure. Reads `ParamName` / `InnerTree`\n * case-insensitively to stay robust across server-branch casing.\n *\n * @internal Exported for testing.\n */\nexport function warnOnEmptyInnerTrees(response: GrasshopperComputeResponse, debug?: boolean): void {\n\tif (!debug) return;\n\n\tconst values = readField<unknown[]>(response, 'values');\n\tif (!Array.isArray(values) || values.length === 0) return;\n\n\tconst empty: string[] = [];\n\tfor (const param of values) {\n\t\tconst innerTree = readField<Record<string, unknown>>(param, 'innerTree');\n\t\t// Treat a missing or empty innerTree as \"produced nothing\".\n\t\tif (!innerTree || Object.keys(innerTree).length === 0) {\n\t\t\tempty.push(readField<string>(param, 'paramName') ?? '<unnamed>');\n\t\t}\n\t}\n\n\tif (empty.length === 0) return;\n\n\tconst scope = empty.length === values.length ? 'all' : `${empty.length}/${values.length}`;\n\tgetLogger().warn(\n\t\t`Solve returned empty output(s) (${scope}): ${empty.join(', ')}. ` +\n\t\t\t`These parameters produced no data — check the definition's inputs and the branch feeding each.`\n\t);\n}\n\n/**\n * Result of a solve that also reports the definition's server-side cache key.\n *\n * `cacheKey` is the `md5_…` identifier the server assigned to the (base64)\n * definition — stable for identical content. A caller that holds it can solve\n * the same definition again by reference (`pointer: cacheKey`) instead of\n * re-uploading the full base64, which matters a lot for large (multi-MB)\n * definitions on a live UI. For a URL-pointer solve the server echoes the\n * request schema back, so `cacheKey` is the definition URL itself (already a\n * reference — nothing gained by re-pointing at it). `null` only when the\n * server's response carried no `pointer` at all — do NOT use `null` to detect\n * URL-pointer solves.\n */\nexport interface SolveWithCacheKey {\n\tresponse: GrasshopperComputeResponse;\n\tcacheKey: string | null;\n}\n\n/**\n * Runs a Rhino Compute job using the provided tree prototypes and Grasshopper definition.\n *\n * @public Use this for direct compute control. For high-level API, use `GrasshopperClient.solve()`.\n *\n * @param dataTree - An array of `DataTree` objects representing the input data for the compute job.\n * @param definition - The Grasshopper definition, which can be:\n *   - A URL string (e.g., 'https://example.com/definition.gh')\n *   - A base64-encoded string of the .gh file\n *   - A plain string (will be base64-encoded)\n *   - A Uint8Array of the .gh file (will be base64-encoded)\n *   - A `DefinitionRef` (bytes are loaded via `ref.load()` for the upload)\n * @param config - Compute configuration (server URL, API key, etc. along with optional timeout, units, etc.)\n * @returns An object containing the compute result and extracted file data.\n *\n * @example\n * // Using a URL\n * await solveGrasshopperDefinition(trees, 'https://example.com/definition.gh', config);\n *\n * // Using a base64 string\n * await solveGrasshopperDefinition(trees, 'UEsDBBQAAAAIAL...', config);\n *\n * // Using binary data\n * const fileData = new Uint8Array([...]);\n * await solveGrasshopperDefinition(trees, fileData, config);\n */\nexport async function solveGrasshopperDefinition(\n\tdataTree: DataTree[],\n\tdefinition: SolveDefinition,\n\tconfig: GrasshopperComputeConfig\n): Promise<GrasshopperComputeResponse> {\n\t// Not gated on `debug`: exposing an API key in the browser is a security\n\t// concern in every configuration. `suppressBrowserWarning` is the opt-out.\n\twarnIfClientSide('solveGrasshopperDefinition', config.suppressBrowserWarning);\n\n\tconst bytes = await materializeDefinition(definition);\n\tconst { response } = await runSolve(prepareGrasshopperArgs(bytes, dataTree), config);\n\treturn response;\n}\n\n/**\n * Solve while reporting the server's definition cache key.\n *\n * Behaves like {@link solveGrasshopperDefinition} but returns the `cacheKey` the\n * server assigned, so a caller (e.g. the scheduler) can later solve the same\n * definition by reference instead of re-uploading it. The cache key is only\n * meaningful for base64/binary definitions; a URL-pointer solve returns the URL.\n *\n * @internal\n */\nexport async function solveGrasshopperDefinitionWithCacheKey(\n\tdataTree: DataTree[],\n\tdefinition: SolveDefinition,\n\tconfig: GrasshopperComputeConfig\n): Promise<SolveWithCacheKey> {\n\twarnIfClientSide('solveGrasshopperDefinitionWithCacheKey', config.suppressBrowserWarning);\n\n\tconst bytes = await materializeDefinition(definition);\n\treturn runSolve(prepareGrasshopperArgs(bytes, dataTree), config);\n}\n\n/**\n * Solve a definition by its server-side cache key (`pointer: cacheKey`),\n * skipping the (potentially multi-MB) base64 upload. If the key has been evicted\n * from the server's definition cache — `DEFINITION_LOAD_FAILED` — transparently\n * retry once with the full `definition` and report the fresh cache key so the\n * caller can update its mapping. A `DefinitionRef` definition is only\n * materialized (`ref.load()`) inside that miss branch — a pointer hit never\n * touches the bytes.\n *\n * @returns The solve result plus the (possibly refreshed) cache key, and whether\n *   the fast path missed (so callers can record the new key / track hit rate).\n * @internal\n */\nexport async function solveByCacheKey(\n\tdataTree: DataTree[],\n\tcacheKey: string,\n\tdefinition: SolveDefinition,\n\tconfig: GrasshopperComputeConfig\n): Promise<SolveWithCacheKey & { missed: boolean }> {\n\twarnIfClientSide('solveByCacheKey', config.suppressBrowserWarning);\n\n\tconst pointerArgs: GrasshopperRequestSchema = { algo: null, pointer: cacheKey, values: dataTree };\n\n\ttry {\n\t\tconst fast = await runSolve(pointerArgs, config);\n\t\treturn { ...fast, missed: false };\n\t} catch (error) {\n\t\tif (!isDefinitionLoadMiss(error)) throw error;\n\t\t// Cache miss — fall back to the full upload and capture the fresh key.\n\t\tconst bytes = await materializeDefinition(definition);\n\t\tconst full = await runSolve(prepareGrasshopperArgs(bytes, dataTree), config);\n\t\treturn { ...full, missed: true };\n\t}\n}\n\n/**\n * Resolve a definition to uploadable form: a `DefinitionRef` is materialized\n * via `load()` (failures are wrapped so callers get a `ComputeError`\n * naming the ref, not an opaque loader exception); other forms pass through.\n */\nasync function materializeDefinition(definition: SolveDefinition): Promise<string | Uint8Array> {\n\tif (!isDefinitionRef(definition)) return definition;\n\ttry {\n\t\treturn await definition.load();\n\t} catch (error) {\n\t\tthrow new ComputeError(\n\t\t\t`Failed to load definition bytes for ref '${definition.key}'`,\n\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t{\n\t\t\t\tcontext: { definitionKey: definition.key },\n\t\t\t\toriginalError: error instanceof Error ? error : new Error(String(error))\n\t\t\t}\n\t\t);\n\t}\n}\n\n/**\n * Shared solve body: apply optional settings, POST, and split the server's\n * `pointer` (its cache key) off the response. `algo` — the request's full\n * base64 definition echoed back on every solve — is stripped too: keeping it\n * pins a multi-MB copy per response, which would consume the scheduler cache's\n * byte budget many times over. Stripping via shallow copy rather than `delete`\n * keeps any already-observed response object unmutated.\n */\nasync function runSolve(\n\targs: GrasshopperRequestSchema,\n\tconfig: GrasshopperComputeConfig\n): Promise<SolveWithCacheKey> {\n\tapplyOptionalComputeSettings(args, config);\n\n\tconst result = await fetchCompute<GrasshopperComputeResponse>(\n\t\t'grasshopper',\n\t\targs,\n\t\twithGrasshopperErrorCodes(config)\n\t);\n\n\tconst {\n\t\tpointer,\n\t\talgo: _algo,\n\t\t...rest\n\t} = result as GrasshopperComputeResponse & { pointer?: unknown; algo?: unknown };\n\tconst response = rest as GrasshopperComputeResponse;\n\t// The wire-size hint follows object identity, so the stripped copy must\n\t// re-register it — minus the echoed `algo`, which is no longer retained (it\n\t// can dwarf the actual outputs for a large definition).\n\tconst wireSize = getResponseWireSize(result);\n\tif (wireSize !== undefined) {\n\t\tconst algoLength = typeof _algo === 'string' ? _algo.length : 0;\n\t\tsetResponseWireSize(response, Math.max(0, wireSize - algoLength));\n\t}\n\twarnOnEmptyInnerTrees(response, config.debug);\n\treturn {\n\t\tresponse,\n\t\tcacheKey: typeof pointer === 'string' ? pointer : null\n\t};\n}\n\n// ============================================================================\n// Grasshopper Arguments\n// ============================================================================\n\n/**\n * Prepares Grasshopper arguments from a definition and data tree.\n * Automatically detects the definition format and converts it appropriately.\n *\n * @param definition - Can be a URL, base64 string, plain string, or Uint8Array\n * @param dataTree - Array of DataTree objects for compute inputs\n * @internal\n */\nexport function prepareGrasshopperArgs(\n\tdefinition: string | Uint8Array,\n\tdataTree: DataTree[]\n): GrasshopperRequestSchema {\n\tconst args: GrasshopperRequestSchema = {\n\t\talgo: null,\n\t\tpointer: null,\n\t\tvalues: dataTree\n\t};\n\n\tif (definition instanceof Uint8Array) {\n\t\t// Binary data → convert to base64\n\t\targs.algo = base64ByteArray(definition);\n\t} else if (/^https?:\\/\\//i.test(definition)) {\n\t\t// URL → use as pointer reference\n\t\targs.pointer = definition;\n\t} else {\n\t\t// Base64 detection is a heuristic (see detectBase64Payload): only long\n\t\t// (≥64 data chars), canonical base64 is passed through — normalized, so\n\t\t// newline-wrapped/unpadded definitions reach the server decodable instead\n\t\t// of double-encoded. Everything else (incl. short base64-shaped strings\n\t\t// like \"test\") is treated as a plain string and encoded. Pass a\n\t\t// Uint8Array to bypass sniffing entirely.\n\t\targs.algo = detectBase64Payload(definition) ?? encodeStringToBase64(definition);\n\t}\n\n\treturn args;\n}\n\n/**\n * @internal\n */\nexport function applyOptionalComputeSettings(\n\targlist: GrasshopperRequestSchema,\n\toptions: GrasshopperComputeConfig\n): void {\n\tif (options.cachesolve != null) arglist.cachesolve = options.cachesolve;\n\tif (options.cacheerroredsolves != null) arglist.cacheerroredsolves = options.cacheerroredsolves;\n\tif (options.modelunits != null) arglist.modelunits = options.modelunits;\n\tif (options.angletolerance != null) arglist.angletolerance = options.angletolerance;\n\tif (options.absolutetolerance != null) arglist.absolutetolerance = options.absolutetolerance;\n\tif (options.dataversion != null) arglist.dataversion = options.dataversion;\n}\n","/**\n * Stable hashing for solve deduplication and caching.\n *\n * The public keying surface here — {@link stableStringify}, {@link hashDefinition},\n * {@link hashSolveInput} — is exported from the package barrel so a durable\n * (app-layer) cache or a pre-solved bundle viewer can reproduce the *exact*\n * canonicalize-and-hash path the scheduler's in-process cache uses. Key parity\n * across the two layers is a correctness requirement: a durable-cache key that\n * canonicalizes even slightly differently would miss every entry the scheduler\n * wrote (harmless) or, worse, collide differently (not). The FNV plumbing\n * (`fnv1a`/`fnv1aBytes`) and the pre-hashed-definition fast path\n * (`hashSolveInputForDefinition`) stay internal.\n */\n\nimport { isDefinitionRef, type SolveDefinition } from '@/core/definition-ref';\n\n/**\n * Deterministic stringify with sorted keys. {a:1,b:2} and {b:2,a:1} produce\n * the same string. Safely handles circular references and non-finite numbers.\n *\n * The output is a cache key, so the invariant that matters is: two payloads\n * that serialize differently on the wire must stringify differently here\n * (false misses are harmless; false hits serve the wrong cached solve).\n */\nexport function stableStringify(value: unknown): string {\n\t// Tracks the current recursion path only (entries are removed on the way\n\t// out), so genuine cycles read \"[Circular]\" while shared non-circular\n\t// references stringify by content each time they appear.\n\tconst path = new WeakSet<object>();\n\n\tconst stringify = (v: unknown): string => {\n\t\tif (v === undefined) return 'undefined';\n\t\tif (v === null) return 'null';\n\t\tif (typeof v === 'number') {\n\t\t\treturn Number.isFinite(v) ? String(v) : 'null';\n\t\t}\n\t\tif (typeof v === 'string' || typeof v === 'boolean') return JSON.stringify(v);\n\t\t// Unquoted `n` suffix keeps 1n distinct from the string \"1\".\n\t\tif (typeof v === 'bigint') return `${v}n`;\n\t\tif (v instanceof Uint8Array) {\n\t\t\t// Full-content hash in one linear pass — sampling head/tail let two\n\t\t\t// buffers differing only in the middle share a cache key.\n\t\t\treturn `{\"__u8\":${v.length},\"hash\":\"${fnv1aBytes(v)}\"}`;\n\t\t}\n\t\tif (Array.isArray(v)) {\n\t\t\tif (path.has(v)) return '\"[Circular]\"';\n\t\t\tpath.add(v);\n\t\t\tconst parts: string[] = [];\n\t\t\t// Indexed access (not .map) so holes stringify like undefined instead\n\t\t\t// of vanishing from the joined output.\n\t\t\tfor (let i = 0; i < v.length; i++) parts.push(stringify(v[i]));\n\t\t\tpath.delete(v);\n\t\t\treturn `[${parts.join(',')}]`;\n\t\t}\n\t\tif (typeof v === 'object') {\n\t\t\tif (path.has(v)) return '\"[Circular]\"';\n\t\t\tpath.add(v);\n\t\t\tlet out: string;\n\t\t\tif (typeof (v as { toJSON?: unknown }).toJSON === 'function') {\n\t\t\t\t// Matches wire behavior: JSON.stringify calls toJSON (Date → ISO string).\n\t\t\t\tout = stringify((v as { toJSON: () => unknown }).toJSON());\n\t\t\t} else if (v instanceof Map) {\n\t\t\t\tconst entries = [...v.entries()].map(([k, val]) => `[${stringify(k)},${stringify(val)}]`);\n\t\t\t\tout = `{\"__map\":[${entries.sort().join(',')}]}`;\n\t\t\t} else if (v instanceof Set) {\n\t\t\t\tconst items = [...v.values()].map(stringify);\n\t\t\t\tout = `{\"__set\":[${items.sort().join(',')}]}`;\n\t\t\t} else {\n\t\t\t\tconst keys = Object.keys(v as object).sort();\n\t\t\t\tconst parts = keys.map(\n\t\t\t\t\t(k) => `${JSON.stringify(k)}:${stringify((v as Record<string, unknown>)[k])}`\n\t\t\t\t);\n\t\t\t\tout = `{${parts.join(',')}}`;\n\t\t\t}\n\t\t\tpath.delete(v);\n\t\t\treturn out;\n\t\t}\n\t\t// Fallback for functions, symbols, etc.\n\t\treturn 'null';\n\t};\n\n\treturn stringify(value);\n}\n\n/**\n * 32-bit FNV-1a core over a sequence of byte/char codes. Returns unsigned hex.\n * Shared by the string and byte hashers so they stay the same algorithm.\n */\nfunction fnv1aCore(length: number, codeAt: (i: number) => number): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < length; i++) {\n\t\thash ^= codeAt(i);\n\t\thash = (hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24))) >>> 0;\n\t}\n\treturn hash.toString(16).padStart(8, '0');\n}\n\n/**\n * 32-bit FNV-1a— fast, no dependencies. Returns unsigned hex string.\n * @internal\n */\nexport function fnv1a(input: string): string {\n\treturn fnv1aCore(input.length, (i) => input.charCodeAt(i));\n}\n\n/**\n * 32-bit FNV-1a over raw bytes. Returns unsigned hex string.\n * @internal\n */\nexport function fnv1aBytes(bytes: Uint8Array): string {\n\treturn fnv1aCore(bytes.length, (i) => bytes[i]);\n}\n\n/**\n * Hash definition and data tree into a stable cache key.\n *\n * The definition is the *identity* of what we solve, so a binary definition is\n * hashed over its full content (`fnv1aBytes`) — a length-only or sampled key\n * would let two different `.gh` files collide and serve one's cached solve for\n * the other. `.gh` files are small enough that a single linear pass is\n * negligible. A {@link DefinitionRef} is keyed by its `key` alone — the\n * caller-declared identity of immutable bytes — so no bytes are materialized\n * or hashed at all.\n *\n * The key keeps the definition and tree hashes as separate parts rather than\n * collapsing them into one 32-bit hash: a single FNV pass over the pair would\n * birthday-collide quadratically in cache size, while requiring both 32-bit\n * parts (plus lengths) to collide at once makes that negligible.\n */\nexport function hashSolveInput(definition: SolveDefinition, dataTree: unknown): string {\n\treturn hashSolveInputForDefinition(hashDefinition(definition), dataTree);\n}\n\n/**\n * Build the solve cache key from an already-computed definition hash (a\n * {@link hashDefinition} result) plus the data tree. Split out so a caller\n * that needs the definition hash anyway (the scheduler keys its\n * server-cache-key map by it) computes it once at `solve()` entry and threads\n * it through, instead of paying a second linear FNV pass over a potentially\n * multi-MB base64 definition per solve.\n *\n * Invariant: `hashSolveInputForDefinition(hashDefinition(d), t)` produces the\n * exact same key as `hashSolveInput(d, t)` — cache-key semantics are unchanged.\n *\n * @internal\n */\nexport function hashSolveInputForDefinition(definitionHash: string, dataTree: unknown): string {\n\tconst tree = stableStringify(dataTree);\n\treturn `${definitionHash}|t:${tree.length}:${fnv1a(tree)}`;\n}\n\n/**\n * Stable identity of a definition alone (no inputs) — used to key the\n * server-cache-key map so the same definition reuses its `pointer` across solves\n * with different inputs. Same full-content hashing as {@link hashSolveInput}: a\n * binary definition is hashed over all its bytes so two distinct `.gh` files of\n * equal length can't share a cache key. A {@link DefinitionRef} is keyed by its\n * `key` verbatim (refs are short identities like UUIDs, safe as Map keys) —\n * its immutability contract makes the key equivalent to a content hash.\n */\nexport function hashDefinition(definition: SolveDefinition): string {\n\tif (isDefinitionRef(definition)) return `r:${definition.key}`;\n\t// Hash strings too (don't return the raw definition): a multi-MB base64 `.gh`\n\t// would otherwise become the literal Map key in serverCacheKeys / the cache.\n\treturn typeof definition === 'string'\n\t\t? `s:${definition.length}:${fnv1a(definition)}`\n\t\t: `u8:${definition.length}:${fnv1aBytes(definition)}`;\n}\n","import { ComputeError, ErrorCodes } from '@/core/errors';\nimport type { RetryPolicy } from '@/core/types';\nimport { getLogger } from '@/core/utils/logger';\nimport { readField } from '@/core/utils/read-field';\nimport { getResponseWireSize } from '@/core/compute-fetch/wire-size';\n\nimport type { DataTree, GrasshopperComputeResponse, GrasshopperComputeConfig } from '../types';\nimport type { SolveDefinition } from '@/core/definition-ref';\nimport { hashSolveInputForDefinition, hashDefinition } from './stable-hash';\n\nimport type {\n\tCacheOptions,\n\tCacheKeyExecutor,\n\tSchedulerMode,\n\tSolveContext,\n\tSolveExecutor,\n\tSolveResult,\n\tSolveSchedulerOptions\n} from './types';\n\nexport type {\n\tCacheOptions,\n\tCacheKeyExecutor,\n\tSchedulerMode,\n\tSolveContext,\n\tSolveExecutor,\n\tSolveResult,\n\tSolveSchedulerOptions\n};\n\ninterface CacheEntry {\n\tresponse: GrasshopperComputeResponse;\n\tinsertedAt: number;\n\t/** Wire size (or stringify-fallback estimate) counted against `maxBytes`. */\n\tsizeBytes: number;\n}\n\n/** Cap on the definition→server-cache-key map so it can't grow without bound. */\nconst SERVER_CACHE_KEYS_MAX = 100;\n\ninterface PendingItem {\n\tdefinition: SolveDefinition;\n\tdataTree: DataTree[];\n\t/**\n\t * Definition hash computed once at `solve()` entry ({@link hashDefinition})\n\t * and threaded through to {@link SolveScheduler.runExecutor}, so the\n\t * (potentially multi-MB) definition is never linearly hashed a second time\n\t * for the server-cache-key map.\n\t */\n\tdefinitionHash: string;\n\tctx: SolveContext;\n\t/** Monotonic solve() ordinal — used to keep older items' late settles from overwriting newer state. */\n\tseq: number;\n\tresolve: (response: GrasshopperComputeResponse) => void;\n\treject: (error: ComputeError) => void;\n\texternalSignal?: AbortSignal;\n\t/**\n\t * Abort listener attached while the item waits in a queue, so a signal\n\t * firing pre-execution settles it instead of being silently ignored.\n\t * Removed when execution starts (the in-flight controller takes over) or\n\t * when the item settles by any other path.\n\t */\n\tqueuedAbortHandler?: () => void;\n\t/**\n\t * Queue-wait deadline timer ({@link SolveSchedulerOptions.queueWaitMs}).\n\t * Cleared when the item starts executing or settles by any other path.\n\t */\n\tqueueWaitTimer?: ReturnType<typeof setTimeout>;\n\t/** Set once the promise has been settled, so a late executor rejection becomes a no-op. */\n\tsettled?: { error: ComputeError } | { ok: true };\n}\n\ninterface InFlightItem extends PendingItem {\n\tcontroller: AbortController;\n}\n\n/**\n * Adapter for the underlying solve function. Lets the scheduler be tested\n * without a real Compute server, and decouples it from the client class.\n */\n\n/**\n * Whether a definition is worth solving by server cache key. Binary and\n * base64/plain-string definitions are uploaded in full, so referencing them by\n * key on later solves saves the (potentially huge) payload — and a\n * `DefinitionRef` additionally saves the `load()` itself on a pointer hit. An\n * `http(s)://` URL is already a reference — the server keys it by URL and\n * there's nothing to re-upload — so the fast path adds no value there.\n */\nfunction isReusableDefinition(definition: SolveDefinition): boolean {\n\tif (typeof definition !== 'string') return true;\n\treturn !/^https?:\\/\\//i.test(definition);\n}\n\n/**\n * Fallback sizing for a response that never crossed the fetch boundary (custom\n * executor, hand-built test responses) and so carries no wire-size hint. A full\n * stringify — linear, once per cache write, never on the hit path. Responses\n * are JSON-derived, but guard anyway: an unserializable one is sized 0 (cached\n * under the count bound only) rather than failing the solve.\n */\nfunction estimateResponseSize(response: GrasshopperComputeResponse): number {\n\ttry {\n\t\treturn JSON.stringify(response)?.length ?? 0;\n\t} catch {\n\t\treturn 0;\n\t}\n}\n\n/**\n * Robust scheduler for Grasshopper solves.\n *\n * Sits between your application code and the underlying compute call,\n * adding:\n * - Configurable scheduling (latest-wins for sliders, queue for jobs)\n * - Backpressure (bounded queue depth + queue-wait deadline) for the miss path\n * - In-flight cancellation (per-call signal + cancelAll)\n * - Optional response caching for repeated inputs\n * - Lifecycle hooks for UI indicators (start / settle / superseded)\n * - State observability via subscribe()\n *\n * Multiple schedulers can share a single GrasshopperClient — typically one\n * per UI surface (e.g. one for slider scrubs, one for long-running submits).\n *\n * @example\n * ```ts\n * const scheduler = client.createScheduler({ mode: 'latest-wins', timeoutMs: 30_000 });\n *\n * // From a slider handler:\n * scheduler.solve(definition, tree).then((result) => {\n *   updateMeshes(result);\n * }).catch((err) => {\n *   if (err.code !== 'SUPERSEDED') showError(err);\n * });\n *\n * // From a UI binding:\n * scheduler.subscribe(() => {\n *   showSpinner = scheduler.isSolving;\n * });\n * ```\n */\nexport class SolveScheduler {\n\tprivate readonly executor: SolveExecutor;\n\tprivate readonly baseConfig: GrasshopperComputeConfig;\n\n\tprivate readonly mode: SchedulerMode;\n\t/**\n\t * Mutable via {@link setMaxConcurrent}: the compute server's worker pool can grow\n\t * or shrink while this scheduler is alive, and the dispatch loops re-read this on\n\t * every pass rather than capturing it.\n\t */\n\tprivate maxConcurrent: number;\n\tprivate readonly maxQueueDepth: number | undefined;\n\tprivate readonly queueWaitMs: number | undefined;\n\tprivate readonly timeoutMs: number | undefined;\n\tprivate readonly retry: RetryPolicy | undefined;\n\n\tprivate readonly cacheEnabled: boolean;\n\tprivate readonly cacheMaxBytes: number;\n\tprivate readonly cacheTtl: number;\n\tprivate readonly cacheErroredSolves: boolean;\n\tprivate readonly cache = new Map<string, CacheEntry>();\n\t/** Sum of `sizeBytes` across retained cache entries. */\n\tprivate cacheBytes = 0;\n\t/**\n\t * Cumulative hit/miss/eviction counters for {@link cacheStats}. Deliberately\n\t * NOT reset by `clearCache()` — they measure this scheduler's whole lifetime,\n\t * so a hit rate stays comparable across a session rather than restarting at\n\t * every clear.\n\t */\n\tprivate cacheHits = 0;\n\tprivate cacheMisses = 0;\n\tprivate cacheEvictions = 0;\n\n\t/** Optional cache-key-aware executor and whether server-def-cache reuse is on. */\n\tprivate readonly cacheKeyExecutor?: CacheKeyExecutor;\n\tprivate readonly reuseServerDefinitionCache: boolean;\n\t/** definition identity → server cache key (`pointer`) learned from past solves. */\n\tprivate readonly serverCacheKeys = new Map<string, string>();\n\n\tprivate readonly onStart?: SolveSchedulerOptions['onStart'];\n\tprivate readonly onSettle?: SolveSchedulerOptions['onSettle'];\n\tprivate readonly onSuperseded?: SolveSchedulerOptions['onSuperseded'];\n\n\tprivate readonly subscribers = new Set<() => void>();\n\n\tprivate readonly inFlight = new Set<InFlightItem>();\n\tprivate pendingForLatestWins: PendingItem | null = null;\n\tprivate readonly fifoQueue: PendingItem[] = [];\n\n\tprivate _lastResult: GrasshopperComputeResponse | null = null;\n\tprivate _lastError: ComputeError | null = null;\n\tprivate _lastDurationMs: number | null = null;\n\n\t/** Ordinal handed to each solve() call. */\n\tprivate solveSeq = 0;\n\t/** seq of the solve that last wrote _lastResult/_lastError — see writeLastState. */\n\tprivate lastStateSeq = 0;\n\n\tprivate disposed = false;\n\n\tconstructor(\n\t\texecutor: SolveExecutor,\n\t\tbaseConfig: GrasshopperComputeConfig,\n\t\toptions: SolveSchedulerOptions = {},\n\t\tcacheKeyExecutor?: CacheKeyExecutor\n\t) {\n\t\tthis.executor = executor;\n\t\tthis.cacheKeyExecutor = cacheKeyExecutor;\n\t\tthis.baseConfig = baseConfig;\n\t\tthis.mode = options.mode ?? 'latest-wins';\n\t\tthis.maxConcurrent = Math.max(1, options.maxConcurrent ?? (this.mode === 'parallel' ? 4 : 1));\n\t\t// Backpressure bounds — only meaningful for queue/parallel (latest-wins has\n\t\t// depth 1). A non-positive maxQueueDepth is treated as unbounded rather than\n\t\t// \"reject everything\", which would silently break the scheduler.\n\t\tthis.maxQueueDepth =\n\t\t\toptions.maxQueueDepth !== undefined && options.maxQueueDepth > 0\n\t\t\t\t? Math.floor(options.maxQueueDepth)\n\t\t\t\t: undefined;\n\t\tthis.queueWaitMs =\n\t\t\toptions.queueWaitMs !== undefined && options.queueWaitMs > 0\n\t\t\t\t? options.queueWaitMs\n\t\t\t\t: undefined;\n\t\tthis.timeoutMs = options.timeoutMs;\n\t\tthis.retry = options.retry;\n\n\t\tconst cacheOpt = options.cache;\n\t\tconst cacheConfig = typeof cacheOpt === 'object' ? cacheOpt : null;\n\t\tthis.cacheMaxBytes = Math.max(0, cacheConfig?.maxBytes ?? 0);\n\t\t// A zero budget retains nothing, so treat it as off rather than running a\n\t\t// cache that can only ever miss.\n\t\tthis.cacheEnabled = this.cacheMaxBytes > 0;\n\t\tthis.cacheTtl = cacheConfig?.ttlMs ?? 0;\n\t\tthis.cacheErroredSolves = cacheConfig?.cacheErroredSolves ?? true;\n\n\t\t// On by default when the client wired a cache-key executor — it's a pure\n\t\t// win for reusable definitions and falls back safely on a miss.\n\t\tthis.reuseServerDefinitionCache =\n\t\t\t!!cacheKeyExecutor && (options.reuseServerDefinitionCache ?? true);\n\n\t\tthis.onStart = options.onStart;\n\t\tthis.onSettle = options.onSettle;\n\t\tthis.onSuperseded = options.onSuperseded;\n\t}\n\n\tget isSolving(): boolean {\n\t\treturn this.inFlight.size > 0;\n\t}\n\n\tget hasPending(): boolean {\n\t\treturn this.pendingForLatestWins !== null || this.fifoQueue.length > 0;\n\t}\n\n\tget inFlightCount(): number {\n\t\treturn this.inFlight.size;\n\t}\n\n\tget queueDepth(): number {\n\t\treturn this.fifoQueue.length + (this.pendingForLatestWins ? 1 : 0);\n\t}\n\n\tget lastResult(): GrasshopperComputeResponse | null {\n\t\treturn this._lastResult;\n\t}\n\n\tget lastError(): ComputeError | null {\n\t\treturn this._lastError;\n\t}\n\n\tget lastDurationMs(): number | null {\n\t\treturn this._lastDurationMs;\n\t}\n\n\t/**\n\t * Adjust how many solves may run at once, for when the compute server's worker\n\t * pool changes size after this scheduler was built.\n\t *\n\t * Raising it drains queued work immediately. Lowering it never interrupts work\n\t * already in flight — those finish above the new limit, and the cap applies from\n\t * the next dispatch. Values below 1 are clamped, since 0 would wedge the queue.\n\t */\n\tsetMaxConcurrent(value: number): void {\n\t\tconst next = Math.max(1, Math.floor(value));\n\t\tif (next === this.maxConcurrent) return;\n\t\tconst raised = next > this.maxConcurrent;\n\t\tthis.maxConcurrent = next;\n\t\tif (raised) this.drainNext();\n\t}\n\n\t/** Current concurrency cap — reflects any {@link setMaxConcurrent} adjustment. */\n\tgetMaxConcurrent(): number {\n\t\treturn this.maxConcurrent;\n\t}\n\n\t/** Subscribe to state changes. */\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.subscribers.add(listener);\n\t\treturn () => this.subscribers.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tfor (const listener of this.subscribers) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch (err) {\n\t\t\t\tgetLogger().error('[SolveScheduler] subscriber threw:', err);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Schedule a solve. Returns a promise that:\n\t * - Resolves with the compute response on success.\n\t * - Rejects with `ComputeError` on failure.\n\t * - Rejects with `code: ErrorCodes.SUPERSEDED` when the call was canceled because\n\t *   newer values arrived (latest-wins mode).\n\t * - Rejects with `code: ErrorCodes.ABORTED` when the call was canceled via\n\t *   caller-supplied signal or `cancelAll()`.\n\t * - Rejects with `code: ErrorCodes.QUEUE_FULL` when `maxQueueDepth` is set and\n\t *   the queue was already full (backpressure; `statusCode: 503`).\n\t * - Rejects with `code: ErrorCodes.QUEUE_TIMEOUT` when `queueWaitMs` is set and\n\t *   the call sat queued longer than that before starting (`statusCode: 503`).\n\t *\n\t * Caller-supplied `signal` cancels just this call (rejects with `ABORTED`) —\n\t * including while the call is still queued, before execution starts.\n\t *\n\t * A {@link DefinitionRef} definition is keyed by its `key` (result cache and\n\t * server-pointer map alike) without materializing bytes — `load()` runs only\n\t * when an upload is unavoidable. Its immutability contract is trusted here:\n\t * a reused key serves the other content's cached solve.\n\t *\n\t * Responses served from the cache (and via `lastResult`) are shared objects,\n\t * not copies — treat them as immutable. Mutating one poisons every later\n\t * cache hit for that key.\n\t *\n\t * Partial-success contract: unlike `GrasshopperClient.solve()`, which throws\n\t * `COMPUTATION_ERROR` when the response carries solver errors, this RESOLVES\n\t * with the response as-is — check `response.errors` yourself if a partial\n\t * success must not be treated as a result (see `cacheErroredSolves` for the\n\t * caching side of the same distinction).\n\t */\n\tsolve(\n\t\tdefinition: SolveDefinition,\n\t\tdataTree: DataTree[],\n\t\toptions?: { signal?: AbortSignal }\n\t): Promise<GrasshopperComputeResponse> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew ComputeError(\n\t\t\t\t\t'SolveScheduler has been disposed and cannot be used',\n\t\t\t\t\tErrorCodes.INVALID_STATE\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Hash the definition once here; the hash is both part of the solve key\n\t\t// and (threaded via the pending item) the server-cache-key map's key, so\n\t\t// runExecutor never re-hashes the potentially multi-MB definition.\n\t\tconst definitionHash = hashDefinition(definition);\n\t\tconst key = hashSolveInputForDefinition(definitionHash, dataTree);\n\t\tconst seq = ++this.solveSeq;\n\t\tconst ctx: SolveContext = {\n\t\t\tkey,\n\t\t\tenqueuedAt: Date.now(),\n\t\t\tstartedAt: null\n\t\t};\n\n\t\t// An already-aborted signal rejects before anything else — including the\n\t\t// cache: the documented contract is ABORTED, not a result.\n\t\tif (options?.signal?.aborted) {\n\t\t\treturn Promise.reject(this.makeAbortError(ctx));\n\t\t}\n\n\t\t// Cache hit — return synchronously-resolved promise\n\t\tif (this.cacheEnabled) {\n\t\t\tconst cached = this.readCache(key);\n\t\t\tif (cached) {\n\t\t\t\t// This call is now the newest result. In latest-wins mode that means\n\t\t\t\t// any older in-flight/pending solve is stale — supersede it, or its\n\t\t\t\t// later completion would overwrite this hit and snap the UI back.\n\t\t\t\tif (this.mode === 'latest-wins') {\n\t\t\t\t\tthis.supersedeCurrent();\n\t\t\t\t}\n\t\t\t\tconst result: SolveResult = {\n\t\t\t\t\tstatus: 'success',\n\t\t\t\t\tresponse: cached,\n\t\t\t\t\tdurationMs: 0,\n\t\t\t\t\tfromCache: true\n\t\t\t\t};\n\t\t\t\tthis.writeLastState(seq, { result: cached, durationMs: 0 });\n\t\t\t\tthis.runHook(this.onStart, ctx);\n\t\t\t\tthis.runHook(this.onSettle, ctx, result);\n\t\t\t\tthis.notify();\n\t\t\t\treturn Promise.resolve(cached);\n\t\t\t}\n\t\t}\n\n\t\treturn new Promise<GrasshopperComputeResponse>((resolve, reject) => {\n\t\t\tconst item: PendingItem = {\n\t\t\t\tdefinition,\n\t\t\t\tdataTree,\n\t\t\t\tdefinitionHash,\n\t\t\t\tctx,\n\t\t\t\tseq,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t\texternalSignal: options?.signal\n\t\t\t};\n\n\t\t\t// A signal firing while the item waits in a queue must settle it as\n\t\t\t// ABORTED and drop it — not leave it to run a full solve anyway. The\n\t\t\t// listener is removed when execution starts or the item settles.\n\t\t\tif (item.externalSignal) {\n\t\t\t\titem.queuedAbortHandler = () => this.abortQueuedItem(item);\n\t\t\t\titem.externalSignal.addEventListener('abort', item.queuedAbortHandler, { once: true });\n\t\t\t}\n\n\t\t\tthis.enqueue(item);\n\t\t});\n\t}\n\n\t/**\n\t * Record last-result state, but only if no newer solve has written since —\n\t * a slow solve settling late must not overwrite the state a newer solve\n\t * (or cache hit) already published.\n\t */\n\tprivate writeLastState(\n\t\tseq: number,\n\t\tstate:\n\t\t\t| { result: GrasshopperComputeResponse; durationMs: number }\n\t\t\t| { error: ComputeError; durationMs: number }\n\t): boolean {\n\t\tif (seq < this.lastStateSeq) return false;\n\t\tthis.lastStateSeq = seq;\n\t\tif ('result' in state) {\n\t\t\tthis._lastResult = state.result;\n\t\t\tthis._lastError = null;\n\t\t} else {\n\t\t\tthis._lastError = state.error;\n\t\t}\n\t\tthis._lastDurationMs = state.durationMs;\n\t\treturn true;\n\t}\n\n\t/** latest-wins: supersede the pending item and abort everything in flight. */\n\tprivate supersedeCurrent(): void {\n\t\tif (this.pendingForLatestWins) {\n\t\t\tthis.supersede(this.pendingForLatestWins);\n\t\t\tthis.pendingForLatestWins = null;\n\t\t}\n\t\tfor (const inflight of this.inFlight) {\n\t\t\tthis.supersede(inflight);\n\t\t\tinflight.controller.abort();\n\t\t}\n\t}\n\n\t/** Settle a still-queued item as ABORTED and remove it from its queue. */\n\tprivate abortQueuedItem(item: PendingItem): void {\n\t\tif (this.pendingForLatestWins === item) {\n\t\t\tthis.pendingForLatestWins = null;\n\t\t}\n\t\tconst queued = this.fifoQueue.indexOf(item);\n\t\tif (queued >= 0) this.fifoQueue.splice(queued, 1);\n\n\t\tthis.rejectAsAborted(item);\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Backpressure: settle an incoming item as QUEUE_FULL without ever enqueuing\n\t * it. The structured context lets an HTTP layer map it to 503 + Retry-After.\n\t */\n\tprivate shedAsQueueFull(item: PendingItem): void {\n\t\tconst err = new ComputeError(\n\t\t\t'Solve queue is full; request rejected (backpressure)',\n\t\t\tErrorCodes.QUEUE_FULL,\n\t\t\t{\n\t\t\t\tstatusCode: 503,\n\t\t\t\tcontext: {\n\t\t\t\t\tkey: item.ctx.key,\n\t\t\t\t\tqueueDepth: this.fifoQueue.length,\n\t\t\t\t\tmaxQueueDepth: this.maxQueueDepth\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\tif (this.settleError(item, err)) {\n\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs: 0 });\n\t\t}\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Arm the queue-wait deadline for an item about to be queued. If the item is\n\t * still waiting when it fires, it's rejected as QUEUE_TIMEOUT and removed from\n\t * the queue. Cleared on execute/settle via {@link clearQueueWaitTimer}.\n\t */\n\tprivate armQueueWaitTimer(item: PendingItem): void {\n\t\tif (this.queueWaitMs === undefined) return;\n\t\tconst waitMs = this.queueWaitMs;\n\t\titem.queueWaitTimer = setTimeout(() => {\n\t\t\t// Only meaningful if it's still queued and unsettled.\n\t\t\tif (item.settled) return;\n\t\t\tconst queued = this.fifoQueue.indexOf(item);\n\t\t\tif (queued >= 0) this.fifoQueue.splice(queued, 1);\n\n\t\t\tconst err = new ComputeError(\n\t\t\t\t`Solve waited longer than ${waitMs}ms in queue; rejected (backpressure)`,\n\t\t\t\tErrorCodes.QUEUE_TIMEOUT,\n\t\t\t\t{\n\t\t\t\t\tstatusCode: 503,\n\t\t\t\t\tcontext: {\n\t\t\t\t\t\tkey: item.ctx.key,\n\t\t\t\t\t\twaitedMs: Date.now() - item.ctx.enqueuedAt,\n\t\t\t\t\t\tqueueWaitMs: waitMs\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t\tif (this.settleError(item, err)) {\n\t\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs: 0 });\n\t\t\t}\n\t\t\tthis.notify();\n\t\t}, waitMs);\n\t}\n\n\t/** Clear a queued item's wait-deadline timer, if one is pending. */\n\tprivate clearQueueWaitTimer(item: PendingItem): void {\n\t\tif (item.queueWaitTimer !== undefined) {\n\t\t\tclearTimeout(item.queueWaitTimer);\n\t\t\titem.queueWaitTimer = undefined;\n\t\t}\n\t}\n\n\tprivate enqueue(item: PendingItem): void {\n\t\tswitch (this.mode) {\n\t\t\tcase 'latest-wins': {\n\t\t\t\t// Reject any pending / abort any in-flight one as superseded\n\t\t\t\tthis.supersedeCurrent();\n\t\t\t\t// Run immediately if no slot is taken\n\t\t\t\tif (this.inFlight.size === 0) {\n\t\t\t\t\tthis.execute(item);\n\t\t\t\t} else {\n\t\t\t\t\tthis.pendingForLatestWins = item;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'queue':\n\t\t\tcase 'parallel': {\n\t\t\t\t// Same dispatch logic — the modes differ only in `maxConcurrent`'s\n\t\t\t\t// default (1 for queue, 4 for parallel), set in the constructor.\n\t\t\t\tif (this.inFlight.size < this.maxConcurrent) {\n\t\t\t\t\tthis.execute(item);\n\t\t\t\t} else if (\n\t\t\t\t\tthis.maxQueueDepth !== undefined &&\n\t\t\t\t\tthis.fifoQueue.length >= this.maxQueueDepth\n\t\t\t\t) {\n\t\t\t\t\t// Backpressure: the queue is full. Shed the *newest* call (this one)\n\t\t\t\t\t// so already-accepted work keeps its place, and give the caller an\n\t\t\t\t\t// immediate, honest QUEUE_FULL rather than an unbounded wait.\n\t\t\t\t\tthis.shedAsQueueFull(item);\n\t\t\t\t} else {\n\t\t\t\t\tthis.armQueueWaitTimer(item);\n\t\t\t\t\tthis.fifoQueue.push(item);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tprivate async execute(item: PendingItem): Promise<void> {\n\t\tconst controller = new AbortController();\n\t\t// Attach the controller to the SAME object (not a spread copy) so every\n\t\t// settle path — supersede, cancelAll, executor success/error — shares one\n\t\t// `settled` slot. A copy would split that state and fire onSettle twice.\n\t\tconst inflight = item as InFlightItem;\n\t\tinflight.controller = controller;\n\t\tthis.inFlight.add(inflight);\n\t\titem.ctx.startedAt = Date.now();\n\n\t\t// The item is now running: it can no longer time out in the queue.\n\t\tthis.clearQueueWaitTimer(item);\n\t\t// Hand abort handling over from the queued-phase listener to the\n\t\t// in-flight controller.\n\t\tthis.removeQueuedAbortHandler(item);\n\t\tconst externalAbortHandler = () => controller.abort();\n\t\titem.externalSignal?.addEventListener('abort', externalAbortHandler, { once: true });\n\n\t\tthis.runHook(this.onStart, item.ctx);\n\t\tthis.notify();\n\n\t\tconst startTime = performance.now();\n\t\ttry {\n\t\t\tconst config: GrasshopperComputeConfig = {\n\t\t\t\t...this.baseConfig,\n\t\t\t\tsignal: controller.signal,\n\t\t\t\t...(this.timeoutMs !== undefined && { timeoutMs: this.timeoutMs }),\n\t\t\t\t...(this.retry !== undefined && { retry: this.retry })\n\t\t\t};\n\n\t\t\tconst { response, definitionReuploaded } = await this.runExecutor(\n\t\t\t\titem.definition,\n\t\t\t\titem.dataTree,\n\t\t\t\titem.definitionHash,\n\t\t\t\tconfig\n\t\t\t);\n\t\t\tconst durationMs = performance.now() - startTime;\n\n\t\t\tif (this.cacheEnabled) this.writeCache(item.ctx.key, response);\n\n\t\t\t// Already superseded mid-flight — drop the late success silently.\n\t\t\tif (!this.settleSuccess(item, response)) return;\n\n\t\t\tthis.writeLastState(item.seq, { result: response, durationMs });\n\n\t\t\tthis.runHook(this.onSettle, item.ctx, {\n\t\t\t\tstatus: 'success',\n\t\t\t\tresponse,\n\t\t\t\tdurationMs,\n\t\t\t\tfromCache: false,\n\t\t\t\tdefinitionReuploaded\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst durationMs = performance.now() - startTime;\n\t\t\t// Resolve the error against the (possibly already-settled) item *before*\n\t\t\t// settling: if this was superseded mid-flight, normalizeExecutionError\n\t\t\t// returns the original cause, and _lastError should reflect it — unless\n\t\t\t// a newer solve already published state, which this late settle must\n\t\t\t// not clobber (writeLastState's seq guard).\n\t\t\tconst err = this.normalizeExecutionError(error, inflight);\n\n\t\t\tthis.writeLastState(item.seq, { error: err, durationMs });\n\n\t\t\tif (this.settleError(item, err)) {\n\t\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs });\n\t\t\t}\n\t\t} finally {\n\t\t\titem.externalSignal?.removeEventListener('abort', externalAbortHandler);\n\t\t\tthis.inFlight.delete(inflight);\n\t\t\tthis.drainNext();\n\t\t\tthis.notify();\n\t\t}\n\t}\n\n\t/**\n\t * Run the solve, using the server-definition-cache fast path when it's\n\t * enabled and the definition is reusable. Learns/updates the definition's\n\t * server cache key from the result so later solves can reference it.\n\t *\n\t * `definitionHash` is the {@link hashDefinition} result already computed at\n\t * `solve()` entry — threaded through rather than recomputed, so each solve\n\t * pays exactly one linear pass over the definition (issue 57).\n\t */\n\tprivate async runExecutor(\n\t\tdefinition: SolveDefinition,\n\t\tdataTree: DataTree[],\n\t\tdefinitionHash: string,\n\t\tconfig: GrasshopperComputeConfig\n\t): Promise<{ response: GrasshopperComputeResponse; definitionReuploaded?: boolean }> {\n\t\tif (\n\t\t\t!this.cacheKeyExecutor ||\n\t\t\t!this.reuseServerDefinitionCache ||\n\t\t\t!isReusableDefinition(definition)\n\t\t) {\n\t\t\treturn { response: await this.executor(definition, dataTree, config) };\n\t\t}\n\n\t\tconst defKey = definitionHash;\n\t\tconst knownKey = this.serverCacheKeys.get(defKey) ?? null;\n\n\t\tconst result = await this.cacheKeyExecutor(definition, dataTree, knownKey, config);\n\n\t\t// Record the server's (possibly refreshed) key for next time; drop a stale\n\t\t// one if the server stopped returning a key. Re-set on a hit to refresh\n\t\t// insertion order so the bounded eviction below is LRU-ish.\n\t\tif (result.cacheKey) {\n\t\t\tthis.serverCacheKeys.delete(defKey);\n\t\t\tthis.serverCacheKeys.set(defKey, result.cacheKey);\n\t\t\twhile (this.serverCacheKeys.size > SERVER_CACHE_KEYS_MAX) {\n\t\t\t\tconst oldest = this.serverCacheKeys.keys().next().value;\n\t\t\t\tif (oldest === undefined) break;\n\t\t\t\tthis.serverCacheKeys.delete(oldest);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.serverCacheKeys.delete(defKey);\n\t\t}\n\n\t\treturn { response: result.response, definitionReuploaded: result.missed };\n\t}\n\n\tprivate drainNext(): void {\n\t\tif (this.disposed) return;\n\n\t\t// latest-wins: promote pending if no in-flight\n\t\tif (this.mode === 'latest-wins') {\n\t\t\tif (this.pendingForLatestWins && this.inFlight.size === 0) {\n\t\t\t\tconst next = this.pendingForLatestWins;\n\t\t\t\tthis.pendingForLatestWins = null;\n\t\t\t\tthis.execute(next);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// queue / parallel: pull from FIFO until at capacity\n\t\twhile (this.fifoQueue.length > 0 && this.inFlight.size < this.maxConcurrent) {\n\t\t\tconst next = this.fifoQueue.shift()!;\n\t\t\tthis.execute(next);\n\t\t}\n\t}\n\n\tprivate supersede(item: PendingItem): void {\n\t\tconst err = new ComputeError('Superseded by newer solve', ErrorCodes.SUPERSEDED, {\n\t\t\tcontext: { key: item.ctx.key, enqueuedAt: item.ctx.enqueuedAt }\n\t\t});\n\t\tif (this.settleError(item, err)) {\n\t\t\tthis.runHook(this.onSuperseded, item.ctx);\n\t\t}\n\t}\n\n\tprivate makeAbortError(ctx: SolveContext): ComputeError {\n\t\treturn new ComputeError('Request aborted by caller', ErrorCodes.ABORTED, {\n\t\t\tcontext: { key: ctx.key, enqueuedAt: ctx.enqueuedAt }\n\t\t});\n\t}\n\n\t/**\n\t * Settle a pending/in-flight item exactly once with an error.\n\t *\n\t * A solve promise can be settled from four concurrent sources — the executor\n\t * resolving, the executor rejecting, `supersede`, and `cancelAll` — and a JS\n\t * promise silently ignores a second settle. This guard is the single place the\n\t * settle-once invariant lives: it makes the *first* settle win and reports\n\t * whether this call was that winner, so callers fire their own hook only when\n\t * they actually settled. Any new settle path must go through here (or\n\t * {@link settleSuccess}) so the guard can't be forgotten.\n\t *\n\t * @returns `true` if this call settled the item; `false` if it was already settled.\n\t */\n\tprivate settleError(item: PendingItem, err: ComputeError): boolean {\n\t\tif (item.settled) return false;\n\t\titem.settled = { error: err };\n\t\tthis.clearQueueWaitTimer(item);\n\t\tthis.removeQueuedAbortHandler(item);\n\t\titem.reject(err);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Settle a pending/in-flight item exactly once with a successful response.\n\t * The success counterpart to {@link settleError}; see it for the invariant.\n\t *\n\t * @returns `true` if this call settled the item; `false` if it was already settled.\n\t */\n\tprivate settleSuccess(item: PendingItem, response: GrasshopperComputeResponse): boolean {\n\t\tif (item.settled) return false;\n\t\titem.settled = { ok: true };\n\t\tthis.clearQueueWaitTimer(item);\n\t\tthis.removeQueuedAbortHandler(item);\n\t\titem.resolve(response);\n\t\treturn true;\n\t}\n\n\t/** Detach the queued-phase abort listener, if one is still attached. */\n\tprivate removeQueuedAbortHandler(item: PendingItem): void {\n\t\tif (item.queuedAbortHandler && item.externalSignal) {\n\t\t\titem.externalSignal.removeEventListener('abort', item.queuedAbortHandler);\n\t\t}\n\t\titem.queuedAbortHandler = undefined;\n\t}\n\n\tprivate isAbortLikeError(error: unknown): boolean {\n\t\tif (error instanceof Error) {\n\t\t\tif (error.name === 'AbortError') return true;\n\t\t\tif (typeof DOMException !== 'undefined' && error instanceof DOMException) {\n\t\t\t\treturn error.name === 'AbortError';\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate normalizeExecutionError(error: unknown, item: InFlightItem): ComputeError {\n\t\t// If the item was already settled (e.g. by supersede), return that error so\n\t\t// _lastError reflects the original cause rather than the downstream abort.\n\t\tif (item.settled && 'error' in item.settled) {\n\t\t\treturn item.settled.error;\n\t\t}\n\n\t\tif (error instanceof ComputeError) return error;\n\n\t\tif (this.isAbortLikeError(error)) {\n\t\t\treturn this.makeAbortError(item.ctx);\n\t\t}\n\n\t\treturn new ComputeError(\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\tErrorCodes.UNKNOWN_ERROR,\n\t\t\t{ originalError: error instanceof Error ? error : new Error(String(error)) }\n\t\t);\n\t}\n\n\t// --------------------------------------------------------------------------\n\t// Cancellation\n\t// --------------------------------------------------------------------------\n\n\t/** Cancel everything — in-flight and pending. */\n\tcancelAll(): void {\n\t\t// Reject pending\n\t\tif (this.pendingForLatestWins) {\n\t\t\tthis.rejectAsAborted(this.pendingForLatestWins);\n\t\t\tthis.pendingForLatestWins = null;\n\t\t}\n\t\twhile (this.fifoQueue.length > 0) {\n\t\t\tconst item = this.fifoQueue.shift()!;\n\t\t\tthis.rejectAsAborted(item);\n\t\t}\n\t\t// Abort in-flight — their finally blocks will reject their promises\n\t\tfor (const inflight of this.inFlight) {\n\t\t\tconst err = this.makeAbortError(inflight.ctx);\n\t\t\tif (this.settleError(inflight, err)) {\n\t\t\t\tthis.runHook(this.onSettle, inflight.ctx, {\n\t\t\t\t\tstatus: 'error',\n\t\t\t\t\terror: err,\n\t\t\t\t\t// startedAt is a Date.now() timestamp — measure with the same clock.\n\t\t\t\t\tdurationMs: inflight.ctx.startedAt ? Date.now() - inflight.ctx.startedAt : 0\n\t\t\t\t});\n\t\t\t}\n\t\t\tinflight.controller.abort();\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tprivate rejectAsAborted(item: PendingItem): void {\n\t\tconst err = this.makeAbortError(item.ctx);\n\t\t// Queued items get the same settle hook as in-flight ones, so consumers\n\t\t// pairing solve() calls with settles don't leak \"in progress\" indicators.\n\t\tif (this.settleError(item, err)) {\n\t\t\tthis.runHook(this.onSettle, item.ctx, { status: 'error', error: err, durationMs: 0 });\n\t\t}\n\t}\n\n\t// --------------------------------------------------------------------------\n\t// Cache\n\t// --------------------------------------------------------------------------\n\n\tprivate readCache(key: string): GrasshopperComputeResponse | null {\n\t\tif (!this.cacheEnabled) return null;\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) {\n\t\t\tthis.cacheMisses += 1;\n\t\t\treturn null;\n\t\t}\n\t\tif (this.cacheTtl > 0 && Date.now() - entry.insertedAt > this.cacheTtl) {\n\t\t\tthis.dropCacheEntry(key);\n\t\t\t// An expired entry is a miss, not a separate outcome — the solve runs\n\t\t\t// either way, which is what a hit rate is measuring.\n\t\t\tthis.cacheMisses += 1;\n\t\t\treturn null;\n\t\t}\n\t\t// LRU touch\n\t\tthis.cache.delete(key);\n\t\tthis.cache.set(key, entry);\n\t\tthis.cacheHits += 1;\n\t\treturn entry.response;\n\t}\n\n\tprivate writeCache(key: string, response: GrasshopperComputeResponse): void {\n\t\tif (!this.cacheEnabled) return;\n\t\t// Case-insensitive read: stock mcneel servers serialize `Errors`, and a\n\t\t// casing miss here would cache errored solves despite the opt-out.\n\t\tconst solveErrors = readField<unknown[]>(response, 'errors');\n\t\tif (!this.cacheErroredSolves && Array.isArray(solveErrors) && solveErrors.length > 0) return;\n\t\t// Prefer the wire-size hint recorded at the fetch boundary; a response\n\t\t// that never crossed the fetch layer (custom executor, tests) pays a\n\t\t// one-off stringify here — once per fresh solve, never per hit.\n\t\tconst sizeBytes = getResponseWireSize(response) ?? estimateResponseSize(response);\n\t\t// An entry larger than the whole byte budget would evict everything\n\t\t// (including itself) — serve it through, retain nothing.\n\t\tif (sizeBytes > this.cacheMaxBytes) return;\n\t\tthis.dropCacheEntry(key); // replace-in-place: release the old copy's bytes\n\t\tthis.cache.set(key, { response, insertedAt: Date.now(), sizeBytes });\n\t\tthis.cacheBytes += sizeBytes;\n\t\twhile (this.cacheBytes > this.cacheMaxBytes && this.cache.size > 0) {\n\t\t\tconst oldest = this.cache.keys().next().value;\n\t\t\tif (oldest === undefined) break;\n\t\t\tthis.dropCacheEntry(oldest);\n\t\t\tthis.cacheEvictions += 1;\n\t\t}\n\t}\n\n\t/** Remove one cache entry and release its bytes from the running total. */\n\tprivate dropCacheEntry(key: string): void {\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) return;\n\t\tthis.cache.delete(key);\n\t\tthis.cacheBytes -= entry.sizeBytes;\n\t}\n\n\tclearCache(): void {\n\t\tthis.cache.clear();\n\t\tthis.cacheBytes = 0;\n\t}\n\n\t/**\n\t * Observability snapshot of the solve cache: current size plus lifetime\n\t * hit/miss/eviction counters.\n\t *\n\t * `hits`/`misses` count cache CONSULTATIONS, so `hits / (hits + misses)` is the\n\t * hit rate. A TTL-expired entry counts as a miss (the solve runs either way).\n\t * `evictions` counts only entries dropped under size/byte pressure — not\n\t * replace-in-place writes or TTL expiry, which are not capacity signals.\n\t * Counters are cumulative and survive `clearCache()`.\n\t */\n\tcacheStats(): {\n\t\tentries: number;\n\t\tbytes: number;\n\t\thits: number;\n\t\tmisses: number;\n\t\tevictions: number;\n\t} {\n\t\treturn {\n\t\t\tentries: this.cache.size,\n\t\t\tbytes: this.cacheBytes,\n\t\t\thits: this.cacheHits,\n\t\t\tmisses: this.cacheMisses,\n\t\t\tevictions: this.cacheEvictions\n\t\t};\n\t}\n\n\t// --------------------------------------------------------------------------\n\t// Lifecycle\n\t// --------------------------------------------------------------------------\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tthis.cancelAll();\n\t\tthis.subscribers.clear();\n\t\tthis.clearCache();\n\t}\n\n\tprivate runHook<H extends (...args: any[]) => void>(\n\t\thook: H | undefined,\n\t\t...args: Parameters<H>\n\t): void {\n\t\tif (!hook) return;\n\t\ttry {\n\t\t\thook(...args);\n\t\t} catch (err) {\n\t\t\tgetLogger().error('[SolveScheduler] hook threw:', err);\n\t\t}\n\t}\n}\n","import { ErrorCodes, ComputeError } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { readField } from '@/core/utils/read-field';\nimport ComputeServerStats from '../server/compute-server-stats';\nimport { validateServerUrl } from '@/core/server/validate-server-url';\nimport { classifyProbeFailure, type ProbeFailure } from '@/core/server/classify-probe-failure';\nimport { ComputeConfig, RetryPolicy } from '@/core/types';\n\nimport { fetchDefinitionIO, fetchParsedDefinitionIO, solveGrasshopperDefinition } from '..';\nimport { solveByCacheKey, solveGrasshopperDefinitionWithCacheKey } from '../solve';\nimport { GrasshopperComputeConfig, GrasshopperComputeResponse, DataTree } from '../types';\nimport { isDefinitionRef, type SolveDefinition } from '@/core/definition-ref';\nimport {\n\tSolveScheduler,\n\tSolveSchedulerOptions,\n\tCacheKeyExecutor\n} from '../scheduler/solve-scheduler';\n\n/**\n * Per-call options that override the client's default ComputeConfig values.\n *\n * Use these for per-request control without mutating the client config:\n * - `signal` — cancel a specific solve (e.g. when a slider value is superseded)\n * - `timeoutMs` — extend timeout for a long-running solve, or pass `0` to disable\n * - `retry` — override retry policy for this call only\n */\nexport interface SolveOptions {\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n\tretry?: RetryPolicy;\n}\n\n/** Compact description of a definition for error context — never the full payload. */\nfunction describeDefinition(definition: SolveDefinition): string {\n\tif (isDefinitionRef(definition)) return `ref:${definition.key}`;\n\tif (typeof definition === 'string' && definition.length < 200) return definition;\n\treturn '...content...';\n}\n\n/** One input parameter's footprint in the error-context summary (issue 83). */\nexport interface DataTreeSummaryEntry {\n\t/** The input's `ParamName` (`<unnamed>` when absent). */\n\tparam: string;\n\t/** Total items across all branches. */\n\titems: number;\n\t/** Approximate payload size: summed `data` string lengths across items. */\n\tbytes: number;\n}\n\n/**\n * Compact summary of the input data tree for error context — never the full\n * payload (issue 83). Trees can embed multi-MB geometry/base64; attaching them\n * to thrown errors pins those buffers in every logger/telemetry/error-boundary\n * that retains the error. Param names, item counts, and byte sizes are enough\n * to correlate a failure with its inputs.\n *\n * Reads `ParamName`/`InnerTree` case-insensitively and never throws — error\n * construction must not fail on a malformed tree.\n */\nfunction summarizeDataTree(dataTree: DataTree[]): DataTreeSummaryEntry[] {\n\tif (!Array.isArray(dataTree)) return [];\n\n\treturn dataTree.map((tree) => {\n\t\tlet items = 0;\n\t\tlet bytes = 0;\n\n\t\tconst innerTree = readField<Record<string, unknown>>(tree, 'innerTree');\n\t\tif (innerTree && typeof innerTree === 'object') {\n\t\t\tfor (const branch of Object.values(innerTree)) {\n\t\t\t\tif (!Array.isArray(branch)) continue;\n\t\t\t\titems += branch.length;\n\t\t\t\tfor (const item of branch) {\n\t\t\t\t\tconst data = (item as { data?: unknown } | null)?.data;\n\t\t\t\t\tif (typeof data === 'string') bytes += data.length;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { param: readField<string>(tree, 'paramName') ?? '<unnamed>', items, bytes };\n\t});\n}\n\n/**\n * GrasshopperClient provides a simple API for interacting with a Rhino Compute server and grasshopper.\n *\n * @public This is the recommended high-level API for Rhino Compute operations.\n *\n * **Security Warning:**\n * Using this client in a browser environment exposes your server URL and API key to users.\n * For production, use this library server-side or proxy requests through your own backend.\n *\n * @example\n * ```typescript\n * const client = await GrasshopperClient.create({\n *   serverUrl: 'http://localhost:6500',\n *   apiKey: 'your-api-key'\n * });\n *\n * try {\n *   const result = await client.solve(definitionUrl, { x: 1, y: 2 });\n * } finally {\n *   await client.dispose(); // Clean up resources\n * }\n * ```\n */\nexport default class GrasshopperClient {\n\tprivate readonly config: GrasshopperComputeConfig;\n\tpublic readonly serverStats: ComputeServerStats;\n\tprivate disposed = false;\n\n\t/**\n\t * Per-probe timeout for the `create()` liveness gate. A healthy `GET /` on the\n\t * proxy answers in milliseconds; this bound only ever applies to a server that\n\t * is not answering, where every extra second is multiplied by the retry count\n\t * and paid by whoever is waiting on the solve.\n\t */\n\tprivate static readonly CREATE_PROBE_TIMEOUT_MS = 2000;\n\n\tprivate constructor(config: GrasshopperComputeConfig) {\n\t\tthis.config = this.normalizeComputeConfig(config);\n\t\tthis.serverStats = new ComputeServerStats(this.config.serverUrl, this.config.apiKey);\n\t}\n\n\t/**\n\t * Creates and initializes a GrasshopperClient with server validation.\n\t *\n\t * The pre-flight liveness probe (a GET on the proxy root `/`) is a\n\t * single-sample boolean gate that reads a cold or briefly-busy-but-up server\n\t * as offline. To avoid failing construction on that transient class, the probe\n\t * is retried with a short exponential backoff before giving up.\n\t *\n\t * Retries stop early when {@link classifyProbeFailure} says waiting cannot\n\t * change the answer — connection refused, or a 401/403. This runs in front of\n\t * a user who clicked Solve, so the retry ladder must not spend its whole\n\t * budget confirming that a machine is switched off.\n\t *\n\t * Each probe is bounded by {@link CREATE_PROBE_TIMEOUT_MS} rather than the\n\t * stats default: a healthy `GET /` answers in milliseconds, so a longer\n\t * per-probe timeout only multiplies the wait when the server is unreachable.\n\t *\n\t * @throws {ComputeError} with code NETWORK_ERROR if the server stays\n\t *   unreachable across all attempts. `context.probeVerdict` carries the\n\t *   {@link ProbeVerdict} so callers can render a specific cause.\n\t * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid\n\t */\n\tstatic async create(config: GrasshopperComputeConfig): Promise<GrasshopperClient> {\n\t\tconst client = new GrasshopperClient(config);\n\n\t\t// A single liveness miss isn't authoritative — a cold/busy-but-up\n\t\t// server flickers non-200. Retry a few times with backoff before failing.\n\t\tconst attempts = Math.max(1, (config.retry?.attempts ?? 2) + 1);\n\t\tconst baseDelayMs = config.retry?.baseDelayMs ?? 250;\n\t\tconst maxDelayMs = config.retry?.maxDelayMs ?? 1000;\n\n\t\tlet lastProbe: Awaited<ReturnType<ComputeServerStats['probeServer']>> | undefined;\n\t\tlet failure: ProbeFailure | null = null;\n\t\tlet used = 0;\n\t\tfor (let attempt = 0; attempt < attempts; attempt++) {\n\t\t\tused = attempt + 1;\n\t\t\tlastProbe = await client.serverStats.probeServer(GrasshopperClient.CREATE_PROBE_TIMEOUT_MS);\n\t\t\tif (lastProbe.online) {\n\t\t\t\treturn client;\n\t\t\t}\n\n\t\t\tfailure = classifyProbeFailure(lastProbe);\n\t\t\t// Nothing about a refused connection or a rejected key improves by\n\t\t\t// asking again — fail now and let the caller say why.\n\t\t\tif (failure && !failure.retryable) break;\n\n\t\t\tif (attempt < attempts - 1) {\n\t\t\t\tconst delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t}\n\t\t}\n\n\t\tawait client.dispose();\n\t\tconst status = lastProbe?.status;\n\t\tconst message = failure\n\t\t\t? `Rhino Compute server is not available. ${failure.summary}`\n\t\t\t: 'Rhino Compute server is not online';\n\t\tthrow new ComputeError(message, ErrorCodes.NETWORK_ERROR, {\n\t\t\t...(status !== undefined && { statusCode: status }),\n\t\t\tcontext: {\n\t\t\t\tserverUrl: client.config.serverUrl,\n\t\t\t\t// What we actually spent, not the ceiling — an early break makes\n\t\t\t\t// these differ, and the gap is the useful part when reading a log.\n\t\t\t\tattempts: used,\n\t\t\t\tmaxAttempts: attempts,\n\t\t\t\t...(failure && { probeVerdict: failure.verdict, probeRetryable: failure.retryable }),\n\t\t\t\t...(status !== undefined && { lastProbeStatus: status }),\n\t\t\t\t...(lastProbe?.error !== undefined && { lastProbeError: lastProbe.error })\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Gets the client's configuration.\n\t * Useful for passing to lower-level functions.\n\t */\n\tpublic getConfig(): GrasshopperComputeConfig {\n\t\tthis.ensureNotDisposed();\n\t\treturn { ...this.config };\n\t}\n\n\t/**\n\t * Get input/output parameters of a Grasshopper definition.\n\t */\n\tpublic async getIO(definition: string | Uint8Array) {\n\t\tthis.ensureNotDisposed();\n\t\treturn fetchParsedDefinitionIO(definition, this.config);\n\t}\n\n\tpublic async getRawIO(definition: string | Uint8Array) {\n\t\tthis.ensureNotDisposed();\n\t\treturn fetchDefinitionIO(definition, this.config);\n\t}\n\n\t/**\n\t * Run a compute job with a Grasshopper definition.\n\t *\n\t * @throws {ComputeError} with code INVALID_INPUT if definition is empty\n\t * @throws {ComputeError} with code NETWORK_ERROR if server is offline\n\t * @throws {ComputeError} with code COMPUTATION_ERROR if computation fails.\n\t *   On a partial-success response (some outputs computed, some errored) the\n\t *   error's `context.values` carries the outputs that did compute — pass\n\t *   `{ values } as GrasshopperComputeResponse` to the response processors to\n\t *   render them. `context.inputSummary` describes the inputs (param names,\n\t *   item counts, byte sizes) without pinning the full data tree.\n\t */\n\tpublic async solve(\n\t\tdefinition: SolveDefinition,\n\t\tdataTree: DataTree[],\n\t\toptions?: SolveOptions\n\t): Promise<GrasshopperComputeResponse> {\n\t\tthis.ensureNotDisposed();\n\n\t\ttry {\n\t\t\t// Validate inputs\n\t\t\tif (typeof definition === 'string' && !definition?.trim()) {\n\t\t\t\tthrow new ComputeError('Definition URL/content is required', ErrorCodes.INVALID_INPUT, {\n\t\t\t\t\tcontext: { receivedUrl: definition }\n\t\t\t\t});\n\t\t\t} else if (definition instanceof Uint8Array && definition.length === 0) {\n\t\t\t\tthrow new ComputeError('Definition content is empty', ErrorCodes.INVALID_INPUT);\n\t\t\t} else if (isDefinitionRef(definition) && !definition.key.trim()) {\n\t\t\t\tthrow new ComputeError('DefinitionRef key is empty', ErrorCodes.INVALID_INPUT);\n\t\t\t}\n\n\t\t\t// Per-call options override the client's stored config for this request only\n\t\t\tconst effectiveConfig: GrasshopperComputeConfig = {\n\t\t\t\t...this.config,\n\t\t\t\t...(options?.signal !== undefined && { signal: options.signal }),\n\t\t\t\t...(options?.timeoutMs !== undefined && { timeoutMs: options.timeoutMs }),\n\t\t\t\t...(options?.retry !== undefined && { retry: options.retry })\n\t\t\t};\n\n\t\t\t// Skip the redundant pre-flight healthcheck — fetchCompute already surfaces\n\t\t\t// network failures with a NETWORK_ERROR code, so adding a roundtrip here only\n\t\t\t// doubles latency on every solve.\n\t\t\tconst result = await solveGrasshopperDefinition(dataTree, definition, effectiveConfig);\n\n\t\t\t// Compute may return a partial-success response (HTTP 500 with a body\n\t\t\t// containing both `values` and `errors`/`warnings`). Surface that as a\n\t\t\t// COMPUTATION_ERROR so callers don't silently consume a broken result.\n\t\t\t// Read case-insensitively — stock mcneel servers serialize `Errors`.\n\t\t\tconst solveErrors = readField<unknown[]>(result, 'errors');\n\t\t\tif (Array.isArray(solveErrors) && solveErrors.length > 0) {\n\t\t\t\tthrow new ComputeError(\n\t\t\t\t\tsolveErrors.map(String).join('; ') || 'Computation failed',\n\t\t\t\t\tErrorCodes.COMPUTATION_ERROR,\n\t\t\t\t\t{\n\t\t\t\t\t\tcontext: {\n\t\t\t\t\t\t\tdefinition: describeDefinition(definition),\n\t\t\t\t\t\t\t// Summary only — attaching the full dataTree would pin\n\t\t\t\t\t\t\t// multi-MB input buffers in telemetry (issue 83).\n\t\t\t\t\t\t\tinputSummary: summarizeDataTree(dataTree),\n\t\t\t\t\t\t\terrors: solveErrors,\n\t\t\t\t\t\t\twarnings: readField<unknown[]>(result, 'warnings'),\n\t\t\t\t\t\t\t// The outputs that DID compute (issue 63): the transport\n\t\t\t\t\t\t\t// parses partial values out of the 500 body, so hand them\n\t\t\t\t\t\t\t// to callers who want to render what succeeded and inspect\n\t\t\t\t\t\t\t// what failed (e.g. via getValues on { values }).\n\t\t\t\t\t\t\tvalues: result.values\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tif (this.config.debug) {\n\t\t\t\tgetLogger().error('Compute failed:', error);\n\t\t\t}\n\n\t\t\tif (error instanceof ComputeError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tthrow new ComputeError(\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\tErrorCodes.COMPUTATION_ERROR,\n\t\t\t\t{\n\t\t\t\t\tcontext: {\n\t\t\t\t\t\tdefinition: describeDefinition(definition),\n\t\t\t\t\t\t// Summary only — never the full dataTree (issue 83).\n\t\t\t\t\t\tinputSummary: summarizeDataTree(dataTree)\n\t\t\t\t\t},\n\t\t\t\t\toriginalError: error instanceof Error ? error : new Error(String(error))\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Create a scheduler bound to this client. Use a scheduler for any UI surface\n\t * that fires solves frequently (sliders, live editors) or that needs cancel\n\t * semantics, response caching, or state observability.\n\t *\n\t * Multiple schedulers can be created from a single client — typically one per\n\t * UI surface so their queues stay independent.\n\t *\n\t * @example\n\t * ```ts\n\t * const sliderScheduler = client.createScheduler({ mode: 'latest-wins' });\n\t * const submitScheduler = client.createScheduler({ mode: 'queue', timeoutMs: 0, retry: { attempts: 1 } });\n\t * ```\n\t */\n\tpublic createScheduler(options?: SolveSchedulerOptions): SolveScheduler {\n\t\tthis.ensureNotDisposed();\n\t\tconst executor = (\n\t\t\tdefinition: SolveDefinition,\n\t\t\tdataTree: DataTree[],\n\t\t\tconfig: GrasshopperComputeConfig\n\t\t) => solveGrasshopperDefinition(dataTree, definition, config);\n\n\t\t// Cache-key-aware executor: solve by `pointer: cacheKey` when known (skips\n\t\t// re-uploading large definitions), capturing/refreshing the key and\n\t\t// falling back to a full upload on a server cache miss.\n\t\tconst cacheKeyExecutor: CacheKeyExecutor = (definition, dataTree, cacheKey, config) =>\n\t\t\tcacheKey === null\n\t\t\t\t? solveGrasshopperDefinitionWithCacheKey(dataTree, definition, config).then((r) => ({\n\t\t\t\t\t\t...r,\n\t\t\t\t\t\tmissed: false\n\t\t\t\t\t}))\n\t\t\t\t: solveByCacheKey(dataTree, cacheKey, definition, config);\n\n\t\treturn new SolveScheduler(executor, this.config, options, cacheKeyExecutor);\n\t}\n\n\t/**\n\t * Disposes of client resources.\n\t * Call this when you're done using the client.\n\t */\n\tpublic async dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\t\tawait this.serverStats.dispose();\n\t}\n\n\t/**\n\t * Ensures the client hasn't been disposed.\n\t */\n\tprivate ensureNotDisposed(): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t'GrasshopperClient has been disposed and cannot be used',\n\t\t\t\tErrorCodes.INVALID_STATE\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Validates and normalizes a compute configuration.\n\t *\n\t * @throws {ComputeError} with code INVALID_CONFIG if configuration is invalid\n\t */\n\tprivate normalizeComputeConfig<T extends ComputeConfig | GrasshopperComputeConfig>(config: T): T {\n\t\treturn {\n\t\t\t...config,\n\t\t\tserverUrl: validateServerUrl(config.serverUrl),\n\t\t\tapiKey: config.apiKey,\n\t\t\tauthToken: config.authToken,\n\t\t\tdebug: config.debug ?? false,\n\t\t\tsuppressBrowserWarning: config.suppressBrowserWarning\n\t\t} as T;\n\t}\n}\n","import type { RhinoModule } from 'rhino3dm';\nimport { getLogger } from '@/core';\n\n// -----------------------------------------------------------------------------\n// Decoder Types\n// -----------------------------------------------------------------------------\n\ntype RhinoDecoder = (rhino: RhinoModule, data: unknown) => unknown;\n\nconst decoderRegistry = new Map<string, RhinoDecoder>();\n\n// -----------------------------------------------------------------------------\n// Registration\n// -----------------------------------------------------------------------------\n\nexport function registerDecoder(typeName: string, decoder: RhinoDecoder): void {\n\tdecoderRegistry.set(typeName, decoder);\n}\n\nregisterDecoder('Rhino.Geometry.Point3d', (rhino, data) => {\n\tconst d = data as any;\n\tif (!d || typeof d.X !== 'number') return null;\n\treturn new rhino.Point([d.X, d.Y, d.Z]);\n});\n\nregisterDecoder('Rhino.Geometry.Line', (rhino, data) => {\n\tconst d = data as any;\n\tif (!d || !d.From || !d.To) return null;\n\treturn new rhino.Line([d.From.X, d.From.Y, d.From.Z], [d.To.X, d.To.Y, d.To.Z]);\n});\n\n// -----------------------------------------------------------------------------\n// Utility Functions\n// -----------------------------------------------------------------------------\n\nfunction findDecoder(rhinoType: string): RhinoDecoder | undefined {\n\tif (decoderRegistry.has(rhinoType)) return decoderRegistry.get(rhinoType);\n\tfor (const [key, dec] of decoderRegistry) {\n\t\tif (rhinoType.startsWith(key)) return dec;\n\t}\n\treturn undefined;\n}\n\n/**\n * Whether the parsed item looks like a rhino3dm serialization envelope\n * (`{version, archive3dm, opennurbs, data: \"<base64>\"}`). `CommonObject.decode` expects the WHOLE\n * envelope — unwrapping `.data` first hands it the bare base64 string, which throws or decodes to\n * garbage (see the correct usage in display-items-parser.ts).\n */\nfunction isDecodableEnvelope(parsedData: unknown): parsedData is object {\n\treturn Boolean(\n\t\tparsedData && typeof parsedData === 'object' && typeof (parsedData as any).data === 'string'\n\t);\n}\n\n// -----------------------------------------------------------------------------\n// Geometry Decoding\n// -----------------------------------------------------------------------------\n\n/**\n * Sentinel returned by {@link decodeRhinoGeometry} when a decode was attempted\n * and failed hard (a registered decoder or `CommonObject.decode` threw). Carries\n * the original payload so callers can log or retry. Use\n * {@link isRhinoDecodeError} to detect it.\n */\nexport interface RhinoDecodeError {\n\t__decodeError: true;\n\t/** The Rhino type name the failed decode was attempted for. */\n\ttype: string;\n\t/** The original (undecoded) payload. */\n\traw: unknown;\n}\n\n/** Type guard for the {@link RhinoDecodeError} sentinel. */\nexport function isRhinoDecodeError(value: unknown): value is RhinoDecodeError {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as RhinoDecodeError).__decodeError === true\n\t);\n}\n\nfunction makeDecodeError(rhinoType: string, raw: unknown): RhinoDecodeError {\n\treturn { __decodeError: true, type: rhinoType, raw };\n}\n\n/**\n * Decode one typed payload from a solve response into a rhino3dm object.\n *\n * Failure signaling is a single coherent scheme (issue 85) — every call yields\n * exactly one of three outcomes:\n *\n * 1. **Decoded value** — a registered decoder recognized the shape, or the\n *    payload was a rhino3dm serialization envelope and `CommonObject.decode`\n *    succeeded.\n * 2. **Raw passthrough** (`=== parsedData`) — no decode path *applied*: no\n *    decoder matched the type (or the matched decoder returned `null`, meaning\n *    \"not my shape\") and the payload is not a decodable envelope. Not an error;\n *    the caller keeps the parsed JSON as-is.\n * 3. **{@link RhinoDecodeError} sentinel** — a decode was *attempted and threw*\n *    (a registered decoder threw and no envelope fallback could recover, or\n *    `CommonObject.decode` threw). Detect with {@link isRhinoDecodeError}.\n *\n * A decoder returning `null` is a soft miss, never an error: it falls through\n * to the envelope fallback. This matters for prefix collisions — e.g.\n * `Rhino.Geometry.LineCurve` matches the `Rhino.Geometry.Line` decoder but is\n * a CommonObject envelope, not a `{From, To}` pair, and must reach\n * `CommonObject.decode`.\n */\nexport function decodeRhinoGeometry(\n\tparsedData: unknown,\n\trhinoType: string,\n\trhino: RhinoModule\n): unknown {\n\tconst decoder = findDecoder(rhinoType);\n\t// A throwing decoder is a hard failure (unlike a null return, which is a\n\t// soft \"not my shape\" miss) — remember it so that when no envelope fallback\n\t// can recover we return the error sentinel instead of silently passing the\n\t// raw payload through as if no decode had ever been attempted.\n\tlet decoderThrew = false;\n\tif (decoder) {\n\t\ttry {\n\t\t\tconst decoded = decoder(rhino, parsedData);\n\t\t\tif (decoded != null) return decoded;\n\t\t} catch (error) {\n\t\t\tgetLogger().warn(`Failed to decode Rhino type ${rhinoType}:`, error);\n\t\t\tdecoderThrew = true;\n\t\t}\n\t}\n\n\t// Fallback using CommonObject.decode — fed the full envelope, not the unwrapped payload.\n\ttry {\n\t\tif (isDecodableEnvelope(parsedData)) return rhino.CommonObject.decode(parsedData);\n\t} catch (error) {\n\t\tgetLogger().warn(`Failed to decode ${rhinoType} with CommonObject:`, error);\n\t\treturn makeDecodeError(rhinoType, parsedData);\n\t}\n\n\treturn decoderThrew ? makeDecodeError(rhinoType, parsedData) : parsedData;\n}\n\n// -----------------------------------------------------------------------------\n// Disposal\n// -----------------------------------------------------------------------------\n\n/**\n * Free every rhino3dm WASM object reachable from `value`.\n *\n * rhino3dm objects are emscripten bindings — JS GC never reclaims their WASM\n * heap allocation, so everything decoded from a solve response (`getValues`,\n * `getValue`, `decodeRhinoObject`) must be deleted explicitly or the heap\n * grows monotonically across solves (e.g. a UI decoding per slider tick).\n *\n * Walks arrays and plain objects recursively; anything exposing a `delete()`\n * method is treated as a WASM binding and freed (skipped if already deleted).\n * Safe to call more than once and on values containing no WASM objects.\n */\nexport function disposeRhinoObjects(value: unknown): void {\n\t// Aliased references (the same decoded object aggregated under two keys)\n\t// must only be deleted once.\n\tconst seen = new WeakSet<object>();\n\n\tconst walk = (v: unknown): void => {\n\t\tif (!v || typeof v !== 'object') return;\n\t\tif (seen.has(v)) return;\n\t\tseen.add(v);\n\n\t\tconst del = (v as { delete?: unknown }).delete;\n\t\tif (typeof del === 'function') {\n\t\t\tconst isDeleted = (v as { isDeleted?: () => boolean }).isDeleted;\n\t\t\tif (typeof isDeleted !== 'function' || !isDeleted.call(v)) {\n\t\t\t\t(del as () => void).call(v);\n\t\t\t}\n\t\t\treturn; // a WASM binding's internals are not ours to walk\n\t\t}\n\n\t\tif (Array.isArray(v)) {\n\t\t\tfor (const item of v) walk(item);\n\t\t\treturn;\n\t\t}\n\t\t// Only walk plain containers — class instances other than WASM bindings\n\t\t// (Dates, typed arrays, ...) hold nothing decodable.\n\t\tconst proto = Object.getPrototypeOf(v);\n\t\tif (proto === Object.prototype || proto === null) {\n\t\t\tfor (const item of Object.values(v)) walk(item);\n\t\t}\n\t};\n\n\twalk(value);\n}\n\n// -----------------------------------------------------------------------------\n// Object Decoder\n// -----------------------------------------------------------------------------\n\nexport interface DecodeRhinoOptions {\n\tkeys?: string[];\n\tskipKeys?: string[];\n\tdeep?: boolean;\n}\n\nexport function decodeRhinoObject<T extends Record<string, unknown>>(\n\tobj: T,\n\trhino: RhinoModule,\n\toptions: DecodeRhinoOptions = {}\n): T {\n\tconst { keys, skipKeys, deep } = options;\n\tconst out: Record<string, unknown> = { ...obj };\n\n\tconst shouldProcessKey = (k: string) => {\n\t\tif (skipKeys?.includes(k)) return false;\n\t\tif (keys && !keys.includes(k)) return false;\n\t\treturn true;\n\t};\n\n\tfor (const [key, value] of Object.entries(obj)) {\n\t\tif (!shouldProcessKey(key)) continue;\n\t\tif (!value || typeof value !== 'object') continue;\n\n\t\tconst v: any = value;\n\t\tconst maybeType = !Array.isArray(v) && typeof v.type === 'string' ? v.type : undefined;\n\n\t\tif (maybeType) {\n\t\t\tout[key] = decodeRhinoGeometry(v, maybeType, rhino);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (deep) {\n\t\t\t// Arrays must stay arrays (issue 61): recursing into decodeRhinoObject\n\t\t\t// would object-spread `[a, b]` into `{0: a, 1: b}`, breaking\n\t\t\t// Array.isArray/.map downstream. Map over elements instead.\n\t\t\tout[key] = Array.isArray(v)\n\t\t\t\t? decodeDeepArray(v, rhino, options)\n\t\t\t\t: decodeRhinoObject(v as any, rhino, options);\n\t\t}\n\t}\n\n\treturn out as T;\n}\n\n/**\n * Deep-mode helper: decode every element of an array while preserving the\n * array shape. Type-tagged elements decode via {@link decodeRhinoGeometry},\n * nested arrays recurse, plain objects recurse through\n * {@link decodeRhinoObject} (so `keys`/`skipKeys` filtering still applies to\n * their fields), and primitives pass through untouched.\n */\nfunction decodeDeepArray(\n\tarr: unknown[],\n\trhino: RhinoModule,\n\toptions: DecodeRhinoOptions\n): unknown[] {\n\treturn arr.map((el) => {\n\t\tif (!el || typeof el !== 'object') return el;\n\t\tif (Array.isArray(el)) return decodeDeepArray(el, rhino, options);\n\t\tconst maybeType = typeof (el as any).type === 'string' ? (el as any).type : undefined;\n\t\tif (maybeType) return decodeRhinoGeometry(el, maybeType, rhino);\n\t\treturn decodeRhinoObject(el as Record<string, unknown>, rhino, options);\n\t});\n}\n","import { FileData } from '@/core/files/types';\nimport { hasField, readField } from '@/core/utils/read-field';\nimport { GrasshopperComputeResponse, DataItem } from '../../types';\nimport { decodeRhinoGeometry, disposeRhinoObjects } from './rhino-decoder';\n\nexport interface ParsedContext {\n\t[key: string]: any;\n}\n\nexport interface GetValuesOptions {\n\tparseValues?: boolean;\n\trhino?: any;\n\t/**\n\t * If true, only include values of type System.String in the result.\n\t * Non-string types are filtered out.\n\t */\n\tstringOnly?: boolean;\n}\n\nexport interface GetValuesResult<T = ParsedContext> {\n\tvalues: T;\n\t/**\n\t * Free every rhino3dm WASM object decoded into `values`. When a `rhino`\n\t * instance was passed, decoded geometry lives on the WASM heap and is never\n\t * garbage-collected — call this once the values are consumed, or the heap\n\t * grows monotonically across solves. Idempotent; a no-op when nothing was\n\t * decoded. `values` must not be used after disposal.\n\t */\n\tdispose: () => void;\n}\n\n// -----------------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------------\n\nconst SYSTEM_TYPES = {\n\tSTRING: 'System.String',\n\tINT: 'System.Int32',\n\tDOUBLE: 'System.Double',\n\tBOOL: 'System.Boolean'\n};\n\nconst RHINO_GEOMETRY_PREFIX = 'Rhino.Geometry.';\n\nconst EXCLUDED_TYPES = ['WebDisplay'];\nconst FILE_DATA_TYPE = 'FileData';\n\n/** Checks if a type is excluded by EXCLUDED_TYPES. */\nfunction isExcludedType(type: string): boolean {\n\treturn EXCLUDED_TYPES.some((t) => type.includes(t));\n}\n\nfunction tryDecodeJSON(value: string): any {\n\tif (typeof value !== 'string') return value;\n\n\tconst trimmed = value.trim();\n\tconst looksJson = trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('\"');\n\tif (!looksJson) return value;\n\n\ttry {\n\t\tconst first = JSON.parse(trimmed);\n\t\tif (typeof first === 'string') {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(first);\n\t\t\t} catch {\n\t\t\t\treturn first;\n\t\t\t}\n\t\t}\n\t\treturn first;\n\t} catch {\n\t\treturn value;\n\t}\n}\n\nfunction decodeBySystemType(raw: any, type: string, rhino?: any): any {\n\tswitch (type) {\n\t\tcase SYSTEM_TYPES.STRING:\n\t\t\tif (typeof raw !== 'string') return raw;\n\t\t\treturn raw.replace(/^\"(.*)\"$/, '$1');\n\n\t\tcase SYSTEM_TYPES.INT:\n\t\t\treturn Number.parseInt(raw, 10);\n\n\t\tcase SYSTEM_TYPES.DOUBLE:\n\t\t\treturn Number.parseFloat(raw);\n\n\t\tcase SYSTEM_TYPES.BOOL: {\n\t\t\tconst str = String(raw).toLowerCase();\n\t\t\treturn str === 'true';\n\t\t}\n\n\t\tdefault:\n\t\t\tif (rhino && type.startsWith(RHINO_GEOMETRY_PREFIX)) {\n\t\t\t\treturn decodeRhinoGeometry(raw, type, rhino);\n\t\t\t}\n\t\t\treturn raw;\n\t}\n}\n\n/**\n * Per-item memo of the (potentially double) JSON.parse in {@link tryDecodeJSON}\n * (issue 84). Response items are immutable wire data, so the parse result is\n * stable; repeated `getValues()`/`getValue()` calls over the same response — a\n * common pattern (read one param, then another) — otherwise re-run up to two\n * full `JSON.parse` passes per item over potentially multi-MB envelope strings.\n *\n * Keyed weakly on the item object, so the cache lives exactly as long as the\n * response it belongs to. Consequence: parsed JSON *objects* are shared across\n * reads of the same response — callers must treat them as read-only. Rhino\n * geometry is NOT shared: `decodeRhinoGeometry` constructs a fresh WASM object\n * per call, so `dispose()` on one read never invalidates another.\n */\nconst decodedJsonCache = new WeakMap<DataItem, unknown>();\n\nfunction decodeItemJSON(item: DataItem): unknown {\n\tif (decodedJsonCache.has(item)) return decodedJsonCache.get(item);\n\tconst parsed = tryDecodeJSON(item.data);\n\tdecodedJsonCache.set(item, parsed);\n\treturn parsed;\n}\n\n// Main extractor — assumes type has already been filtered through isExcludedType\n// at the call site. Returning a sentinel from here would pollute the aggregated\n// arrays in getValues / getValue when multiple branches are mixed.\nfunction extractItemValue(item: DataItem, type: string, parseValues: boolean, rhino?: any): any {\n\tif (typeof item.data !== 'string') return item.data;\n\n\tconst raw = parseValues ? decodeItemJSON(item) : item.data;\n\tif (parseValues && type.includes(FILE_DATA_TYPE)) {\n\t\treturn asFileData(raw) ?? raw;\n\t}\n\n\treturn decodeBySystemType(raw, type, rhino);\n}\n\n/**\n * Lenient read of the `isBase64Encoded` wire flag: some server branches\n * serialize booleans as strings (`\"true\"`/`\"True\"`), which must still count as\n * base64 rather than silently dropping the file (issue 95). Mirrors the check\n * in `handle-files.ts` — the two must agree, or a file accepted here decodes\n * wrongly there.\n */\nconst isBase64Flag = (flag: unknown): boolean =>\n\tflag === true || (typeof flag === 'string' && flag.trim().toLowerCase() === 'true');\n\n/**\n * Normalizing type guard for {@link FileData}. The Compute server emits these\n * as JSON blobs inside `FileData`-typed values; this checks that the parsed\n * shape has every required field before we trust it, then returns it in the\n * camelCase shape {@link FileData} documents.\n *\n * Fields are read case-insensitively via {@link readField}: mcneel-branch\n * servers serialize PascalCase (`FileName`, `Data`, `IsBase64Encoded`, …)\n * while the VektorNode fork uses camelCase — both must survive (issue 95).\n * Reading them strictly here dropped every file from a PascalCase response\n * *before* the tolerant decoder in `handle-files.ts` ever saw it, so downloads\n * silently produced an empty archive.\n *\n * @returns The normalized file, or `null` when the shape is not a `FileData`.\n */\nfunction asFileData(value: unknown): FileData | null {\n\tif (!value || typeof value !== 'object') return null;\n\n\tconst fileName = readField<unknown>(value, 'fileName');\n\tconst fileType = readField<unknown>(value, 'fileType');\n\tconst subFolder = readField<unknown>(value, 'subFolder');\n\tconst flag = readField<unknown>(value, 'isBase64Encoded');\n\n\tif (typeof fileName !== 'string' || typeof fileType !== 'string') return null;\n\tif (typeof subFolder !== 'string') return null;\n\t// Presence, not type: `data` may legitimately be '' and is validated downstream.\n\tif (!hasField(value, 'data')) return null;\n\t// Reject a flag that is neither a boolean nor a boolean-ish string, rather\n\t// than coercing arbitrary values to `false` and mis-decoding the payload.\n\tif (typeof flag !== 'boolean' && typeof flag !== 'string') return null;\n\n\tconst metadata = readField<Record<string, string>>(value, 'metadata');\n\n\treturn {\n\t\tfileName,\n\t\tfileType,\n\t\tdata: readField<string>(value, 'data') as string,\n\t\tisBase64Encoded: isBase64Flag(flag),\n\t\tsubFolder,\n\t\t...(metadata ? { metadata } : {})\n\t};\n}\n\n// Traversal helpers\n\n/**\n * The response's params array, read case-insensitively (`values` vs `Values`\n * across server branches) with a null guard. Partial-success and warnings-only\n * responses can arrive with `values` missing entirely — treat that as \"no\n * params\" instead of crashing (issue 62), mirroring the defense in solve.ts.\n */\nfunction getResponseParams(response: GrasshopperComputeResponse): unknown[] {\n\tconst values = readField<unknown[]>(response, 'values');\n\treturn Array.isArray(values) ? values : [];\n}\n\n/** A param's item type, tolerating items whose `type` is missing/non-string. */\nfunction itemType(item: DataItem): string {\n\treturn typeof item.type === 'string' ? item.type : '';\n}\n\n/**\n * Iterates over every data item within a Grasshopper tree structure.\n *\n * Tolerates a missing/null tree (params in warnings-only partial successes and\n * the e2e fixtures ship without `InnerTree` — issue 62) and skips non-object\n * items so handlers never see `null`.\n *\n * @param tree - The Grasshopper tree structure containing branches of items.\n * @param handler - A callback function invoked for each {@link DataItem} found within the tree branches.\n */\nfunction forEachTreeItem(tree: unknown, handler: (item: DataItem) => void) {\n\tif (!tree || typeof tree !== 'object') return;\n\tfor (const list of Object.values(tree)) {\n\t\tif (Array.isArray(list)) {\n\t\t\tfor (const item of list) {\n\t\t\t\tif (item && typeof item === 'object') handler(item as DataItem);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// -----------------------------------------------------------------------------\n// Public API\n// -----------------------------------------------------------------------------\n\n/**\n * Read all output values from a Grasshopper Compute response, keyed by parameter\n * name (or ID when `byId`). Duplicate keys aggregate into an array.\n *\n * `ParamName`/`InnerTree` are read case-insensitively (server branches differ in\n * casing) and params without an `InnerTree` are skipped rather than crashing.\n *\n * Parsed (non-geometry) JSON values are memoized per response item — repeated\n * reads of the same response return the same object identity, so treat parsed\n * values as read-only. Geometry decoded via `rhino` is always freshly\n * constructed; free it with `dispose()`.\n *\n * @param options.parseValues - Parse complex data types into JS objects (default true).\n * @param options.rhino - Rhino3dm instance for geometry decoding.\n * @param options.stringOnly - Keep only string-typed items.\n */\nexport function getValues<T = ParsedContext>(\n\tresponse: GrasshopperComputeResponse,\n\tbyId: boolean = false,\n\toptions: GetValuesOptions = {}\n): GetValuesResult<T> {\n\tconst { parseValues = true, rhino, stringOnly = false } = options;\n\tconst result: ParsedContext = {};\n\t// Keys holding an aggregation array (vs. a single value that happens to BE an array,\n\t// e.g. parsed JSON `[1,2,3]`) — `Array.isArray(result[key])` can't tell those apart.\n\tconst aggregated = new Set<string>();\n\n\tfor (const param of getResponseParams(response)) {\n\t\tconst paramName = readField<string>(param, 'paramName');\n\t\tforEachTreeItem(readField(param, 'innerTree'), (item) => {\n\t\t\tconst type = itemType(item);\n\t\t\t// Skip excluded types (e.g. WebDisplay) entirely — leaving them in\n\t\t\t// would write null into the aggregated result.\n\t\t\tif (isExcludedType(type)) return;\n\t\t\t// Skip non-string types if stringOnly is enabled\n\t\t\tif (stringOnly && type !== SYSTEM_TYPES.STRING) return;\n\n\t\t\tconst key = byId ? item.id : paramName;\n\t\t\tif (!key) return;\n\n\t\t\tconst value = extractItemValue(item, type, parseValues, rhino);\n\n\t\t\tif (!(key in result)) {\n\t\t\t\tresult[key] = value;\n\t\t\t} else if (aggregated.has(key)) {\n\t\t\t\tresult[key].push(value);\n\t\t\t} else {\n\t\t\t\tresult[key] = [result[key], value];\n\t\t\t\taggregated.add(key);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn { values: result as T, dispose: () => disposeRhinoObjects(result) };\n}\n\n/** Decode every file-data item in a response into {@link FileData} objects. */\nexport function extractFileData(response: GrasshopperComputeResponse): FileData[] {\n\tconst output: FileData[] = [];\n\n\tfor (const param of getResponseParams(response)) {\n\t\tforEachTreeItem(readField(param, 'innerTree'), (item) => {\n\t\t\tif (!itemType(item).includes(FILE_DATA_TYPE)) return;\n\n\t\t\tconst file = asFileData(decodeItemJSON(item));\n\t\t\tif (file) {\n\t\t\t\toutput.push(file);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn output;\n}\n\n/**\n * Read one parameter's value(s) from a response — `byName` matches a `ParamName`,\n * `byId` matches an item ID. Returns `undefined` if absent, a single value for one\n * match, or an array for several.\n *\n * `ParamName`/`InnerTree` are read case-insensitively with null guards, and the\n * scan is a single pass that stops at the first matching parameter (issue 84 —\n * previously `byId` walked every tree twice). Parsed non-geometry JSON values\n * are memoized per response item; treat them as read-only.\n *\n * When a `rhino` instance is passed, decoded geometry lives on the WASM heap —\n * free it with {@link disposeRhinoObjects} once consumed.\n *\n * @param parseOptions.parseValues - Parse raw data into formatted values (default true).\n * @param parseOptions.rhino - Rhino3dm instance for geometry decoding.\n * @param parseOptions.stringOnly - Keep only string-typed items.\n */\nexport function getValue(\n\tresponse: GrasshopperComputeResponse,\n\toptions: { byName: string } | { byId: string },\n\tparseOptions: GetValuesOptions = {}\n): any {\n\tconst { parseValues = true, rhino, stringOnly = false } = parseOptions;\n\n\t// Single pass with early exit: the first param that matches (by name, or by\n\t// containing an item with the target id) is THE target — collect from it and\n\t// return without scanning the remaining trees.\n\tfor (const param of getResponseParams(response)) {\n\t\tconst tree = readField(param, 'innerTree');\n\n\t\tlet matched = false;\n\t\tif ('byName' in options) {\n\t\t\tif (readField<string>(param, 'paramName') !== options.byName) continue;\n\t\t\tmatched = true;\n\t\t}\n\n\t\tconst collected: any[] = [];\n\t\tforEachTreeItem(tree, (item) => {\n\t\t\tif ('byId' in options) {\n\t\t\t\tif (item.id !== options.byId) return;\n\t\t\t\t// The param owning this id is the target even if every matching\n\t\t\t\t// item is filtered out below (preserves the previous two-pass\n\t\t\t\t// semantics: found-but-filtered → undefined, not keep-scanning).\n\t\t\t\tmatched = true;\n\t\t\t}\n\t\t\tconst type = itemType(item);\n\t\t\t// Skip excluded types (e.g. WebDisplay) entirely.\n\t\t\tif (isExcludedType(type)) return;\n\t\t\t// Skip non-string types if stringOnly is enabled\n\t\t\tif (stringOnly && type !== SYSTEM_TYPES.STRING) return;\n\t\t\tcollected.push(extractItemValue(item, type, parseValues, rhino));\n\t\t});\n\n\t\tif (matched) {\n\t\t\tif (collected.length === 0) return undefined;\n\t\t\tif (collected.length === 1) return collected[0];\n\t\t\treturn collected;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import { downloadFileData } from '@/core/files/handle-files';\nimport { FileBaseInfo, FileData } from '@/core/files/types';\n\nimport { GrasshopperComputeResponse } from '../types';\n\nimport {\n\textractFileData,\n\tgetValue,\n\tgetValues,\n\tGetValuesOptions,\n\tGetValuesResult,\n\tParsedContext\n} from '../io/output/response-processors';\n\n/**\n * High-level wrapper for interacting with Grasshopper Compute responses.\n *\n * This class exposes a clean, consistent API for accessing parsed values,\n * geometry, and produced files. It is designed to be the primary interface\n * when working with Grasshopper results in client applications.\n */\nexport default class GrasshopperResponseProcessor {\n\tconstructor(\n\t\t/**\n\t\t * The raw compute response. Public so callers can hand it to a renderer-side parser — e.g.\n\t\t * `getThreeMeshesFromComputeResponse` in `@selvajs/visualization/parse`, which replaced this\n\t\t * class's former `extractMeshesFromResponse()`.\n\t\t */\n\t\tpublic readonly response: GrasshopperComputeResponse,\n\t\t/**\n\t\t * Retained for call-signature compatibility. Its only consumer was the removed\n\t\t * `extractMeshesFromResponse()`, which merged it into the parse options; pass `debug`\n\t\t * directly to the parser in `@selvajs/visualization/parse` instead.\n\t\t */\n\t\tpublic readonly debug: boolean = false\n\t) {}\n\n\t/**\n\t * Extract all values in the response.\n\t *\n\t * @typeParam T - Expected structure of the return value. Defaults to a simple key/value map. (later cast as needed)\n\t * @param byId - Key by parameter ID instead of name. Requires the VektorNode rhino.compute branch.\n\t * @param options - Controls parsing behavior such as Rhino geometry decoding.\n\t * @returns Parsed Grasshopper output values.\n\t *\n\t * @example\n\t * ```ts\n\t * const processor = new GrasshopperResponseProcessor(response);\n\t * const { values } = processor.getValues();\n\t * ```\n\t *\n\t */\n\tpublic getValues<T = ParsedContext>(\n\t\tbyId: boolean = false,\n\t\toptions: GetValuesOptions = {}\n\t): GetValuesResult<T> {\n\t\treturn getValues<T>(this.response, byId, options);\n\t}\n\n\t/**\n\t * Retrieve a specific value by parameter name or ID.\n\t *\n\t * @param selector - `{ byName }` for the human-readable name, `{ byId }` for the parameter GUID.\n\t * @param options - Parsing configuration (e.g. disable parsing or enable Rhino).\n\t * @returns Single parsed value, array of values, or undefined if the parameter is absent.\n\t *\n\t * `byId` requires the VektorNode rhino.compute branch.\n\t *\n\t * @example\n\t * ```ts\n\t * const schema = processor.getValue({ byName: 'Schema' });\n\t * const output = processor.getValue({ byId: 'a4be1c1e-23f9-4c27-b942-7f3bb2c45c6f' });\n\t * ```\n\t */\n\tpublic getValue(\n\t\tselector: { byName: string } | { byId: string },\n\t\toptions?: GetValuesOptions\n\t): any {\n\t\treturn getValue(this.response, selector, options);\n\t}\n\n\t/**\n\t * REMOVED: `extractMeshesFromResponse()`. Mesh decoding lives in `@selvajs/visualization` now, so\n\t * this package stays pure solve/data and carries no `three` dependency. Call the parser directly\n\t * with the raw response:\n\t *\n\t * ```ts\n\t * import { getThreeMeshesFromComputeResponse } from '@selvajs/visualization/parse';\n\t *\n\t * const meshes = await getThreeMeshesFromComputeResponse(processor.response, { rhino });\n\t * scene.add(...meshes);\n\t * ```\n\t */\n\n\tprivate getFileData(): FileData[] {\n\t\treturn extractFileData(this.response);\n\t}\n\n\t/**\n\t * Download all files generated by Grasshopper, optionally including\n\t * additional user-provided files.\n\t *\n\t * Files are grouped under the specified folder name when downloaded.\n\t *\n\t * @param folderName - Name for the download directory.\n\t * @param additionalFiles - Extra files to package (single file, array, or null).\n\t *\n\t * @example\n\t * ```ts\n\t * await processor.getAndDownloadFiles('gh-output');\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * const extra = { name: 'notes.txt', data: 'Example' };\n\t * await processor.getAndDownloadFiles('project', extra);\n\t * ```\n\t */\n\tpublic async getAndDownloadFiles(\n\t\tfolderName: string,\n\t\tadditionalFiles?: FileBaseInfo[] | FileBaseInfo | null\n\t): Promise<void> {\n\t\tconst files = this.getFileData();\n\t\tawait downloadFileData(files, folderName, additionalFiles);\n\t}\n}\n","import type { DataTreeDefault } from '../types';\n\n/**\n * Canonical matcher for a Grasshopper branch path key like `{0}`, `{0;1}`, or\n * the root path `{}`. This is the single source of truth for the branch-path\n * shape (`DataTreePath`); anything testing \"does this string name a branch?\"\n * should use this rather than re-inlining the regex.\n *\n * Matches `{}`, `{0}`, `{0;1;2}`, and negative indices like `{-1;2}` (GH_Path\n * allows them) — but NOT empty segments like `{0;}` / `{;}` / `{0;;1}`, which\n * would otherwise split to phantom 0s (`Number('') === 0`).\n */\nexport const TREE_PATH_RE = /^\\{(-?\\d+(?:;-?\\d+)*)?\\}$/;\n\n/**\n * Membership test for a {@link DataTreeDefault}: an object keyed entirely by\n * branch paths, each mapping to an array of values. This is the one predicate\n * both the input-type parsers (to pass a tree-access default through untouched)\n * and `TreeBuilder` (to dispatch it to `fromDataTreeDefault`) ask — so the two\n * agree by construction on exactly which values are trees.\n */\nexport function isDataTreeDefault(value: unknown): value is DataTreeDefault {\n\tif (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\tconst entries = Object.entries(value);\n\treturn (\n\t\tentries.length > 0 &&\n\t\tentries.every(([key, val]) => TREE_PATH_RE.test(key) && Array.isArray(val))\n\t);\n}\n","// Numeric step-size and precision handling for slider-backed inputs.\n\nimport type { InputParamSchema } from '../../types';\n\nexport function applyRounding(value: number, decimalPlaces: number, tolerance: number): number {\n\tconst rounded = Number(value.toFixed(decimalPlaces));\n\tif (Math.abs(value - rounded) < tolerance) return rounded;\n\treturn value;\n}\n\nexport function getInputStepSize(value: number, roundingTolerance: number): number {\n\tif (!Number.isFinite(value)) return 0.1;\n\tif (value === 0) return 0.1;\n\n\tconst abs = Math.abs(value);\n\n\tif (abs >= 1) {\n\t\tconst str = String(value);\n\t\tconst decimalPart = str.split('.')[1];\n\t\tif (decimalPart && decimalPart.length > 0) {\n\t\t\tconst decimals = Math.min(decimalPart.length, 12);\n\t\t\tconst step = Math.pow(10, -decimals);\n\t\t\tconst rounded = Number(step.toFixed(decimals));\n\t\t\treturn Math.abs(rounded - step) < roundingTolerance ? rounded : step;\n\t\t}\n\t\treturn 1;\n\t}\n\n\t// Handle exponential notation\n\tconst s = String(value);\n\tconst expMatch = s.toLowerCase().match(/e(-?\\d+)/);\n\tif (expMatch) {\n\t\tconst exp = Number(expMatch[1]);\n\t\tif (exp < 0 || s.toLowerCase().includes('e-')) {\n\t\t\tconst absExp = Math.abs(exp);\n\t\t\tconst step = Math.pow(10, -absExp);\n\t\t\tconst rounded = Number(step.toFixed(absExp));\n\t\t\treturn Math.abs(rounded - step) < roundingTolerance ? rounded : step;\n\t\t}\n\t\treturn 0.1;\n\t}\n\n\t// Handle standard decimal notation\n\tconst MAX_DECIMALS = 12;\n\tconst fixed = abs.toFixed(MAX_DECIMALS);\n\tconst trimmed = fixed.replace(/0+$/, '');\n\tconst decimals = Math.min((trimmed.split('.')[1] || '').length, MAX_DECIMALS);\n\n\tif (decimals === 0) return 0.1;\n\n\tconst step = Math.pow(10, -decimals);\n\tconst rounded = Number(step.toFixed(decimals));\n\treturn Math.abs(rounded - step) < roundingTolerance ? rounded : step;\n}\n\n/**\n * A server-authored `stepSize` (read off the wire by `normalizeInputSchema`),\n * or `undefined` when absent/unusable — callers fall back to the heuristic.\n */\nexport function serverStepSize(schema: InputParamSchema): number | undefined {\n\treturn typeof schema.stepSize === 'number' &&\n\t\tNumber.isFinite(schema.stepSize) &&\n\t\tschema.stepSize > 0\n\t\t? schema.stepSize\n\t\t: undefined;\n}\n\n/**\n * Computes the coerced default + stepSize for a Number/Integer input.\n * Mirrors the old `processNumericInput`, plus: a server-provided\n * `schema.stepSize` is honored verbatim; the default/min/max heuristic is\n * only the fallback when the server didn't author one.\n */\n","// Value transformers: coerce a schema's raw `default` to a typed value.\n\nimport { getLogger } from '@/core';\n\n/**\n * Coerce one raw default value to `T`, or return `null` when it can't be\n * coerced. Transformers never throw — the caller decides whether a `null`\n * is filtered (array items) or surfaced as a parse error (scalars).\n */\nexport type ValueTransformer<T> = (value: unknown) => T | null;\n\n/**\n * Coerce a schema's `default` through a transformer, mirroring the old\n * `processInputValue`: arrays map+filter (empty → undefined), scalars\n * transform-or-(undefined|preserve). Returns the new default value rather than\n * mutating.\n */\nexport function coerceDefault<T>(\n\tvalue: unknown,\n\ttransform: ValueTransformer<T>,\n\tsetUndefinedOnEmpty: boolean\n): unknown {\n\tif (value === undefined || value === null) {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst processed = value.map(transform).filter((v): v is T => v !== null);\n\t\treturn processed.length > 0 ? processed : undefined;\n\t}\n\n\tconst transformed = transform(value);\n\tif (transformed !== null) {\n\t\treturn transformed;\n\t}\n\treturn setUndefinedOnEmpty ? undefined : value;\n}\n\n/**\n * @internal Shared with `normalize-default.ts` so tree-access items are parsed\n * with the exact same rules as scalar/array defaults (issue: the tree path used\n * to hand-roll `Number(data)`, turning a blank double into `0`).\n */\nexport const numericTransformer: ValueTransformer<number> = (value) => {\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : null;\n\tif (typeof value === 'string') {\n\t\tconst trimmed = value.trim();\n\t\t// `Number('')` is 0, so reject empty/whitespace before coercing — an empty\n\t\t// default should drop to null, not silently become 0.\n\t\tif (trimmed === '') return null;\n\t\t// `Number()` also accepts hex/binary/octal literals ('0x10' → 16) and\n\t\t// 'Infinity' — neither is a value a Grasshopper numeric default can\n\t\t// legitimately hold (Infinity even survives applyRounding). Only decimal\n\t\t// and exponent notation are accepted.\n\t\tif (/^[+-]?0[xbo]/i.test(trimmed)) return null;\n\t\tconst parsed = Number(trimmed);\n\t\treturn Number.isFinite(parsed) ? parsed : null;\n\t}\n\treturn null;\n};\n\n/**\n * @internal Shared with `normalize-default.ts` (see {@link numericTransformer}).\n * Trims like the numeric transformer and follows the {@link ValueTransformer}\n * contract: `null` on a bad value, never a throw — bad array items are filtered\n * like non-string junk, and the boolean parser surfaces bad scalars itself.\n */\nexport const booleanTransformer: ValueTransformer<boolean> = (value) => {\n\tif (typeof value === 'boolean') return value;\n\tif (typeof value === 'string') {\n\t\tconst lower = value.trim().toLowerCase();\n\t\tif (lower === 'true') return true;\n\t\tif (lower === 'false') return false;\n\t}\n\treturn null;\n};\n\nexport const textTransformer: ValueTransformer<string> = (value) => {\n\tif (typeof value === 'string') {\n\t\tif (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"'))\n\t\t\treturn value.slice(1, -1);\n\t\t// Unbalanced leading quote: strip only the quote, not the last character.\n\t\tif (value.startsWith('\"')) return value.slice(1);\n\t\treturn value;\n\t}\n\treturn null;\n};\n\nexport const colorTransformer: ValueTransformer<string> = (value) => {\n\tif (typeof value === 'string') {\n\t\tlet cleaned = value.trim();\n\t\tif (cleaned.startsWith('\"') && cleaned.endsWith('\"')) {\n\t\t\tcleaned = cleaned.slice(1, -1).trim();\n\t\t}\n\t\treturn cleaned;\n\t}\n\treturn null;\n};\n\nexport function objectTransformer(inputName: string): ValueTransformer<object> {\n\treturn (value) => {\n\t\tif (typeof value === 'object' && value !== null) return value;\n\t\tif (typeof value === 'string' && value.trim() !== '') {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(value);\n\t\t\t\tif (typeof parsed === 'object' && parsed !== null) return parsed;\n\t\t\t\tgetLogger().warn(`Parsed value for input ${inputName} is not an object`);\n\t\t\t\treturn null;\n\t\t\t} catch (err) {\n\t\t\t\tgetLogger().warn(`Failed to parse object value \"${value}\" for input ${inputName}`, err);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t};\n}\n","import { ComputeError, ErrorCodes, type ErrorCode } from '@/core/errors';\nimport { getLogger } from '@/core';\nimport { isDataTreeDefault } from '../../data-tree/tree-path';\nimport type {\n\tBaseInputType,\n\tBooleanInputType,\n\tColorInputType,\n\tFileInputType,\n\tGeometryInputType,\n\tInputParam,\n\tInputParamSchema,\n\tNumericInputType,\n\tTextInputType,\n\tValueListInputType\n} from '../../types';\n\n/**\n * @internal The input-type parser seam.\n *\n * One adapter per Grasshopper param type. A parser owns EVERYTHING about its\n * type: value coercion, type-specific fields (e.g. numeric step size), the\n * typed-param construction, and its own safe fallback when input is bad. New\n * param types plug in by adding an entry to {@link INPUT_TYPE_PARSERS}.\n *\n * Parsers are pure: they read from a (already-`normalizeDefault`'d) schema and\n * return a typed param. They do not mutate the schema. `parse` throws a\n * {@link ComputeError} on recoverable bad input; the registry boundary\n * catches it and pairs it with `fallback`.\n */\n/**\n * A recoverable oddity a parser found while still succeeding (e.g. a ValueList\n * default not present in its values map). Reported through the optional `warn`\n * callback and surfaced to clients on the same `parseErrors` channel as\n * MALFORMED_DEFAULT warnings.\n */\nexport interface ParserWarning {\n\tcode: ErrorCode;\n\tmessage: string;\n}\n\nexport interface InputTypeParser<T extends InputParam = InputParam> {\n\t/** Canonical paramType(s) this parser owns, e.g. ['Number','Integer']. */\n\treadonly types: readonly string[];\n\t/**\n\t * Schema (with normalized default) → typed param. Throws on bad input;\n\t * calls `warn` for oddities the parse recovered from.\n\t */\n\tparse(schema: InputParamSchema, base: BaseInputType, warn?: (warning: ParserWarning) => void): T;\n\t/** This type's safe fallback param when {@link parse} throws. */\n\tfallback(schema: InputParamSchema, base: BaseInputType): T;\n}\n\nimport { applyRounding, getInputStepSize, serverStepSize } from './numeric-rounding';\nimport {\n\tbooleanTransformer,\n\tcoerceDefault,\n\tcolorTransformer,\n\tnumericTransformer,\n\tobjectTransformer,\n\ttextTransformer\n} from './transformers';\n\n// Re-exported for `normalize-default.ts`, which coerces raw defaults before parsing.\nexport { booleanTransformer, numericTransformer };\n\nfunction computeNumeric(\n\tschema: InputParamSchema,\n\troundingTolerance = 1e-8\n): { default: NumericInputType['default']; stepSize: number } {\n\tconst isIntegerType = schema.paramType === 'Integer';\n\tconst serverStep = serverStepSize(schema);\n\n\t// A tree-access default is a DataTreeDefault keyed by branch paths; pass it\n\t// through untouched (numeric constraints are applied later by TreeBuilder).\n\t// Without this guard the scalar numericTransformer mangles the tree object to\n\t// `undefined`, silently dropping a tree-access slider's default. Sharing\n\t// `isDataTreeDefault` with TreeBuilder guarantees we pass through exactly the\n\t// values it will treat as trees — no looser, no stricter.\n\tif (isDataTreeDefault(schema.default)) {\n\t\treturn {\n\t\t\tdefault: schema.default as NumericInputType['default'],\n\t\t\tstepSize: serverStep ?? (isIntegerType ? 1 : 0.1)\n\t\t};\n\t}\n\n\t// A scalar string default that isn't blank and doesn't parse (locale comma\n\t// '1,5', 'Infinity', hex, plain junk) is bad input — surface it as a parse\n\t// error instead of silently collapsing the default to `undefined`. Blank\n\t// strings still mean \"no default\" (deliberate, see numericTransformer);\n\t// array items keep the filter semantics.\n\tif (\n\t\ttypeof schema.default === 'string' &&\n\t\tschema.default.trim() !== '' &&\n\t\tnumericTransformer(schema.default) === null\n\t) {\n\t\tthrow new ComputeError(\n\t\t\t`Invalid numeric default \"${schema.default}\" for input \"${schema.name || 'unknown'}\"`,\n\t\t\tErrorCodes.VALIDATION_ERROR,\n\t\t\t{ context: { inputName: schema.name, default: schema.default } }\n\t\t);\n\t}\n\n\tlet value = coerceDefault(schema.default, numericTransformer, true);\n\n\tif (isIntegerType) {\n\t\tif (Array.isArray(value)) {\n\t\t\tvalue = value.map((val) => (typeof val === 'number' ? Math.round(val) : val));\n\t\t} else if (typeof value === 'number') {\n\t\t\tvalue = Math.round(value);\n\t\t}\n\t\treturn { default: value as NumericInputType['default'], stepSize: serverStep ?? 1 };\n\t}\n\n\tconst firstValue = Array.isArray(value) ? value[0] : value;\n\n\tlet stepSource: number | undefined;\n\tif (typeof firstValue === 'number' && Number.isFinite(firstValue) && firstValue !== 0) {\n\t\tstepSource = firstValue;\n\t} else if (\n\t\ttypeof schema.minimum === 'number' &&\n\t\tNumber.isFinite(schema.minimum) &&\n\t\tschema.minimum !== 0\n\t) {\n\t\tstepSource = schema.minimum;\n\t} else if (\n\t\ttypeof schema.maximum === 'number' &&\n\t\tNumber.isFinite(schema.maximum) &&\n\t\tschema.maximum !== 0\n\t) {\n\t\tstepSource = schema.maximum;\n\t}\n\n\tconst stepSize =\n\t\tserverStep ??\n\t\t(stepSource !== undefined ? getInputStepSize(stepSource, roundingTolerance) : 0.1);\n\n\t// Apply precision to all numeric values\n\tlet decimalPlaces = 0;\n\tconst stepStr = String(stepSize);\n\tconst expMatch = stepStr.toLowerCase().match(/e(-?\\d+)/);\n\tif (expMatch) {\n\t\tdecimalPlaces = Math.abs(Number(expMatch[1]));\n\t} else {\n\t\tdecimalPlaces = stepStr.split('.')[1]?.length ?? 0;\n\t}\n\n\tif (\n\t\tdecimalPlaces === 0 &&\n\t\ttypeof firstValue === 'number' &&\n\t\tfirstValue !== 0 &&\n\t\tMath.abs(firstValue) < 1\n\t) {\n\t\tconst inferred = Math.ceil(-Math.log10(Math.abs(firstValue)));\n\t\tif (Number.isFinite(inferred) && inferred > 0) {\n\t\t\tdecimalPlaces = inferred;\n\t\t}\n\t}\n\n\tdecimalPlaces = Math.min(Math.max(decimalPlaces, 0), 12);\n\n\tif (Array.isArray(value)) {\n\t\tvalue = value.map((val) =>\n\t\t\ttypeof val === 'number' ? applyRounding(val, decimalPlaces, roundingTolerance) : val\n\t\t);\n\t} else if (typeof value === 'number') {\n\t\tvalue = applyRounding(value, decimalPlaces, roundingTolerance);\n\t}\n\n\treturn { default: value as NumericInputType['default'], stepSize };\n}\n\n// ============================================================================\n// Parsers — one per type\n// ============================================================================\n\nconst numericParser: InputTypeParser<NumericInputType> = {\n\ttypes: ['Number', 'Integer'],\n\tparse(schema, base) {\n\t\tconst { default: def, stepSize } = computeNumeric(schema);\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: schema.paramType as 'Number' | 'Integer',\n\t\t\tminimum: schema.minimum,\n\t\t\tmaximum: schema.maximum,\n\t\t\tatLeast: schema.atLeast,\n\t\t\tatMost: schema.atMost,\n\t\t\tstepSize,\n\t\t\tdefault: def\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\t// The safe default must respect the input's own floor: with minimum > 0 a\n\t\t// plain 0 would render a slider default below its own range.\n\t\tconst safeValue =\n\t\t\ttypeof schema.minimum === 'number' && Number.isFinite(schema.minimum) && schema.minimum > 0\n\t\t\t\t? schema.minimum\n\t\t\t\t: 0;\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: schema.paramType as 'Number' | 'Integer',\n\t\t\tminimum: schema.minimum,\n\t\t\tmaximum: schema.maximum,\n\t\t\tatLeast: schema.atLeast,\n\t\t\tatMost: schema.atMost,\n\t\t\t// The parse path always sets a stepSize — the fallback param must too.\n\t\t\tstepSize: serverStepSize(schema) ?? (schema.paramType === 'Integer' ? 1 : 0.1),\n\t\t\tdefault: isList ? [safeValue] : safeValue\n\t\t};\n\t}\n};\n\nconst booleanParser: InputTypeParser<BooleanInputType> = {\n\ttypes: ['Boolean'],\n\tparse(schema, base) {\n\t\t// Tree-access defaults pass through untouched for TreeBuilder, same\n\t\t// rationale as computeNumeric's guard.\n\t\tif (isDataTreeDefault(schema.default)) {\n\t\t\treturn {\n\t\t\t\t...base,\n\t\t\t\tparamType: 'Boolean',\n\t\t\t\tdefault: schema.default as BooleanInputType['default']\n\t\t\t};\n\t\t}\n\t\tconst value = coerceDefault(schema.default, booleanTransformer, false);\n\t\t// The transformer follows the ValueTransformer contract (null on bad input,\n\t\t// never a throw), so bad ARRAY items are filtered like non-string junk —\n\t\t// one 'maybe' in ['true','maybe'] no longer aborts the whole array. A bad\n\t\t// SCALAR survives coercion verbatim (setUndefinedOnEmpty=false); surface it\n\t\t// as a parse error instead of shipping a non-boolean default.\n\t\tif (\n\t\t\tvalue !== undefined &&\n\t\t\tvalue !== null &&\n\t\t\ttypeof value !== 'boolean' &&\n\t\t\t!Array.isArray(value)\n\t\t) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t`Invalid boolean default \"${String(value)}\" for input \"${schema.name || 'unknown'}\"`,\n\t\t\t\tErrorCodes.VALIDATION_ERROR,\n\t\t\t\t{ context: { inputName: schema.name, default: schema.default } }\n\t\t\t);\n\t\t}\n\t\treturn { ...base, paramType: 'Boolean', default: value as BooleanInputType['default'] };\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Boolean', default: isList ? [false] : false };\n\t}\n};\n\nconst textParser: InputTypeParser<TextInputType> = {\n\ttypes: ['Text'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(schema.default, textTransformer, false);\n\t\treturn { ...base, paramType: 'Text', default: value as TextInputType['default'] };\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Text', default: isList ? [''] : '' };\n\t}\n};\n\nconst valueListParser: InputTypeParser<ValueListInputType> = {\n\ttypes: ['ValueList'],\n\tparse(schema, base, warn) {\n\t\tif (\n\t\t\t!schema.values ||\n\t\t\ttypeof schema.values !== 'object' ||\n\t\t\tObject.keys(schema.values).length === 0\n\t\t) {\n\t\t\tthrow ComputeError.missingValues(schema.nickname || 'unnamed', 'ValueList');\n\t\t}\n\n\t\tlet defaultValue = schema.default as string | undefined;\n\t\tif (schema.default !== undefined && schema.default !== null) {\n\t\t\t// A tree/array-shaped default can't index the values map — `String()`\n\t\t\t// would silently turn it into '[object Object]'. Reject it properly.\n\t\t\tif (typeof schema.default === 'object') {\n\t\t\t\tthrow new ComputeError(\n\t\t\t\t\t`ValueList input \"${schema.nickname || 'unnamed'}\" default is not a string-able value`,\n\t\t\t\t\tErrorCodes.VALIDATION_ERROR,\n\t\t\t\t\t{ context: { inputName: schema.name, default: schema.default } }\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst defaultLower = String(schema.default).toLowerCase();\n\t\t\t// Membership is case-insensitive, but downstream lookups (`values[default]`)\n\t\t\t// are not — return the canonical-cased key on a match.\n\t\t\tconst match = Object.keys(schema.values).find((key) => key.toLowerCase() === defaultLower);\n\t\t\tif (match !== undefined) {\n\t\t\t\tdefaultValue = match;\n\t\t\t} else {\n\t\t\t\t// Out-of-range default only warns — it still succeeds (pinned\n\t\t\t\t// behavior). Reported through `warn` too, so it reaches the client's\n\t\t\t\t// parseErrors instead of living only in the logger.\n\t\t\t\tconst message = `ValueList input \"${schema.nickname || 'unnamed'}\" default value \"${schema.default}\" is not in available values`;\n\t\t\t\tgetLogger().warn(message);\n\t\t\t\twarn?.({ code: ErrorCodes.VALIDATION_ERROR, message });\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'ValueList',\n\t\t\tvalues: schema.values as Record<string, string>,\n\t\t\tdefault: defaultValue\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\t// A ValueList only falls back when its values map is missing/empty or its\n\t\t// default couldn't be interpreted — either way the default can't be\n\t\t// validated against the map, so drop it (never fabricate `[undefined]` or\n\t\t// keep a value provably absent from an empty map).\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'ValueList',\n\t\t\tvalues: schema.values && typeof schema.values === 'object' ? schema.values : {},\n\t\t\tdefault: undefined\n\t\t};\n\t}\n};\n\nconst geometryParser: InputTypeParser<GeometryInputType> = {\n\ttypes: ['Geometry'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(\n\t\t\tschema.default,\n\t\t\tobjectTransformer(schema.nickname || 'unnamed'),\n\t\t\ttrue\n\t\t);\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'Geometry',\n\t\t\tdefault: value as GeometryInputType['default']\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Geometry', default: isList ? [null] : (null as any) };\n\t}\n};\n\nconst fileParser: InputTypeParser<FileInputType> = {\n\ttypes: ['File'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(\n\t\t\tschema.default,\n\t\t\tobjectTransformer(schema.nickname || 'unnamed'),\n\t\t\ttrue\n\t\t);\n\t\treturn {\n\t\t\t...base,\n\t\t\tparamType: 'File',\n\t\t\tacceptedFormats: schema.acceptedFormats,\n\t\t\tdefault: value as FileInputType['default']\n\t\t};\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'File', default: isList ? [null] : (null as any) };\n\t}\n};\n\nconst colorParser: InputTypeParser<ColorInputType> = {\n\ttypes: ['Color'],\n\tparse(schema, base) {\n\t\tconst value = coerceDefault(schema.default, colorTransformer, false);\n\t\treturn { ...base, paramType: 'Color', default: value as ColorInputType['default'] };\n\t},\n\tfallback(schema, base) {\n\t\tconst isList = (schema.atMost ?? 1) > 1;\n\t\treturn { ...base, paramType: 'Color', default: isList ? ['0, 0, 0'] : '0, 0, 0' };\n\t}\n};\n\n// ============================================================================\n// Registry\n// ============================================================================\n\nconst ALL_PARSERS: InputTypeParser[] = [\n\tnumericParser,\n\tbooleanParser,\n\ttextParser,\n\tvalueListParser,\n\tgeometryParser,\n\tfileParser,\n\tcolorParser\n];\n\n/** Registry keyed by canonical paramType. */\nexport const INPUT_TYPE_PARSERS: ReadonlyMap<string, InputTypeParser> = new Map(\n\tALL_PARSERS.flatMap((parser) => parser.types.map((type) => [type, parser] as const))\n);\n\n/**\n * The Geometry parser is the registry's fallback for an unknown paramType,\n * matching the old `createSafeDefault` default branch (geometry-shaped null).\n */\nexport const UNKNOWN_TYPE_FALLBACK: InputTypeParser = geometryParser;\n","import { getLogger } from '@/core';\nimport { readField, hasField } from '@/core/utils/read-field';\nimport { booleanTransformer, numericTransformer } from './input-type-parsers';\nimport type { InputParamSchema } from '../../types';\n\n/** A non-fatal reason normalizeDefault couldn't interpret a raw default. */\nexport interface NormalizeDefaultWarning {\n\tcode: 'MALFORMED_DEFAULT';\n\tmessage: string;\n}\n\n/**\n * Read an item's `data` / `type` case-insensitively. Items are lowercase\n * (`data`/`type`) on every known server branch — they carry `[JsonProperty]` —\n * but reading them defensively costs nothing and guards against future drift.\n */\nfunction itemData(item: unknown): unknown {\n\treturn readField(item, 'data');\n}\nfunction itemType(item: unknown): string | undefined {\n\treturn readField<string>(item, 'type');\n}\n\n/**\n * Wire item types parsed as numbers in a tree-access default. Matches the\n * numeric CLR types Grasshopper serializes — the old code only handled\n * `Double`/`Int32`, leaving `Single`/`Int64`/`Decimal` items as strings inside\n * a `DefaultValue<number>` tree.\n */\nconst NUMERIC_ITEM_TYPES = new Set([\n\t'System.Double',\n\t'System.Single',\n\t'System.Decimal',\n\t'System.Int32',\n\t'System.Int64'\n]);\n\n/** Integral wire item types — rounded like the scalar Integer path. */\nconst INTEGER_ITEM_TYPES = new Set(['System.Int32', 'System.Int64']);\n\n/**\n * Normalizes a raw input's `default` by flattening the innerTree structure.\n * Reads `default` keys case-insensitively (casing varies by server branch).\n * Returns the normalized schema plus an optional `warning` for malformed defaults.\n */\nexport function normalizeDefaultWithWarning(input: InputParamSchema): {\n\tschema: InputParamSchema;\n\twarning?: NormalizeDefaultWarning;\n} {\n\tif (typeof input.default !== 'object' || input.default === null) {\n\t\treturn { schema: input };\n\t}\n\n\t// An array default is already in the shape the per-type parsers expect\n\t// (coerceDefault maps arrays) — `processInputs` is public and documents this\n\t// shape. It must NOT fall into the malformed branch below just because\n\t// `typeof [] === 'object'`.\n\tif (Array.isArray(input.default)) {\n\t\treturn { schema: input };\n\t}\n\n\tif (!hasField(input.default, 'innerTree')) {\n\t\tconst message = `Input \"${input.name ?? 'unknown'}\" default had an unrecognized shape (no innerTree key); the default was dropped.`;\n\t\tgetLogger().warn('Unexpected structure in input.default:', input.default);\n\t\treturn {\n\t\t\tschema: { ...input, default: null },\n\t\t\twarning: { code: 'MALFORMED_DEFAULT', message }\n\t\t};\n\t}\n\n\tconst innerTree = readField<Record<string, unknown>>(input.default, 'innerTree') ?? {};\n\n\t// If innerTree is empty, set default to undefined\n\tif (Object.keys(innerTree).length === 0) {\n\t\treturn { schema: { ...input, default: undefined } };\n\t}\n\n\t// If treeAccess is true or atMost > 1, preserve the tree structure\n\tif (input.treeAccess || (input.atMost && input.atMost > 1)) {\n\t\t// Convert each branch to an array of parsed data. Items are parsed with the\n\t\t// SAME transformers as scalar defaults (issue: this path used to hand-roll\n\t\t// `Number(data)` / `data === 'true'`, so a blank double became 0 and any\n\t\t// junk boolean silently became false).\n\t\tconst tree: Record<string, any[]> = {};\n\t\tconst invalidItems: string[] = [];\n\t\tfor (const [branch, items] of Object.entries(innerTree)) {\n\t\t\t// Mirror the flatten path's Array.isArray guard: a non-array branch value must degrade to\n\t\t\t// a MALFORMED_DEFAULT warning, not a raw TypeError that aborts the whole definition-IO fetch.\n\t\t\tif (!Array.isArray(items)) {\n\t\t\t\tconst message = `Input \"${input.name ?? 'unknown'}\" default had a non-array innerTree branch (\"${branch}\"); the default was dropped.`;\n\t\t\t\tgetLogger().warn('Unexpected structure in input.default innerTree:', input.default);\n\t\t\t\treturn {\n\t\t\t\t\tschema: { ...input, default: null },\n\t\t\t\t\twarning: { code: 'MALFORMED_DEFAULT', message }\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst parsed: any[] = [];\n\t\t\tfor (const item of items as any[]) {\n\t\t\t\tconst data = itemData(item);\n\t\t\t\tconst type = itemType(item);\n\t\t\t\tif (type && NUMERIC_ITEM_TYPES.has(type)) {\n\t\t\t\t\t// Blank means \"no value\" — drop it silently, mirroring the scalar\n\t\t\t\t\t// path where a blank string default becomes `undefined`, never 0.\n\t\t\t\t\tif (typeof data === 'string' && data.trim() === '') continue;\n\t\t\t\t\tconst num = numericTransformer(data);\n\t\t\t\t\tif (num === null) {\n\t\t\t\t\t\tinvalidItems.push(`\"${String(data)}\" (${type})`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tparsed.push(INTEGER_ITEM_TYPES.has(type) ? Math.round(num) : num);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (type === 'System.Boolean') {\n\t\t\t\t\t// Strict true/false parsing — 'maybe'/'1'/'' are dropped and\n\t\t\t\t\t// surfaced, not silently coerced to false.\n\t\t\t\t\tconst bool = booleanTransformer(data);\n\t\t\t\t\tif (bool === null) {\n\t\t\t\t\t\tinvalidItems.push(`\"${String(data)}\" (${type})`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tparsed.push(bool);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Only geometry is JSON-encoded on the wire. A `System.String`\n\t\t\t\t// must stay a string: value-list labels routinely start with\n\t\t\t\t// `[`/`{` (e.g. `[1,2,3]`), and JSON-parsing them would put a\n\t\t\t\t// non-string into the leaf `data`, which the Rhino.Compute\n\t\t\t\t// fork's Newtonsoft reader rejects (\"Unexpected character ... [\").\n\t\t\t\tif (typeof data === 'string' && type?.startsWith('Rhino.Geometry')) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsed.push(JSON.parse(data));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tparsed.push(data);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tparsed.push(data);\n\t\t\t}\n\t\t\ttree[branch] = parsed;\n\t\t}\n\n\t\t// Unparseable items are dropped but NOT silently: the good values survive\n\t\t// in the tree, and the drop is surfaced through the warning channel so it\n\t\t// reaches the client's parseErrors.\n\t\tlet warning: NormalizeDefaultWarning | undefined;\n\t\tif (invalidItems.length > 0) {\n\t\t\tconst message = `Input \"${input.name ?? 'unknown'}\" default contained ${invalidItems.length} tree value(s) that could not be parsed and were dropped: ${invalidItems.join(', ')}.`;\n\t\t\tgetLogger().warn(message);\n\t\t\twarning = { code: 'MALFORMED_DEFAULT', message };\n\t\t}\n\t\treturn { schema: { ...input, default: tree }, ...(warning && { warning }) };\n\t}\n\n\t// Otherwise, flatten all values as before\n\tconst allValues: any[] = [];\n\tfor (const items of Object.values(innerTree)) {\n\t\tif (Array.isArray(items)) {\n\t\t\titems.forEach((item) => {\n\t\t\t\tif (item && typeof item === 'object' && hasField(item, 'data')) {\n\t\t\t\t\tallValues.push(itemData(item));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\tif (allValues.length === 0) {\n\t\treturn { schema: { ...input, default: undefined } };\n\t} else if (allValues.length === 1) {\n\t\treturn { schema: { ...input, default: allValues[0] } };\n\t} else {\n\t\treturn { schema: { ...input, default: allValues } };\n\t}\n}\n","import { ComputeError } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\n\nimport { normalizeDefaultWithWarning } from './normalize-default';\nimport { INPUT_TYPE_PARSERS, UNKNOWN_TYPE_FALLBACK } from './input-type-parsers';\n\nimport type { BaseInputType, InputParam, InputParamSchema, InputParseError } from '../../types';\n\n/** Canonical paramType for each supported type, keyed by its lowercased form. */\nconst CANONICAL_PARAM_TYPES = new Map(\n\t[...INPUT_TYPE_PARSERS.keys()].map((key) => [key.toLowerCase(), key])\n);\n\n/**\n * Returns the canonical casing for a paramType (e.g. \"valuelist\" → \"ValueList\"),\n * or the original value unchanged when it isn't a known type so the\n * unknown-paramType error still surfaces downstream.\n */\nfunction canonicalizeParamType(paramType: string): string {\n\treturn CANONICAL_PARAM_TYPES.get(paramType?.toLowerCase()) ?? paramType;\n}\n\n/**\n * Parse one raw Grasshopper input schema into a typed {@link InputParam}.\n * Validation failures are swallowed and replaced with a safe default; use\n * {@link processInputWithError} to receive them.\n */\nexport function processInput(rawInput: InputParamSchema): InputParam {\n\treturn processInputWithError(rawInput).input;\n}\n\n/**\n * Like {@link processInput}, but reports validation failures back to the caller\n * instead of swallowing them with a logger warning.\n *\n * On success: `{ input, error: undefined }`.\n * On a recoverable validation failure: `{ input: <safe default>, error: {...} }`.\n *\n * A single input can report several entries — a malformed default\n * (normalization warning), warnings the type parser recovered from (e.g. a\n * ValueList default not in its values map), and a parser error. `errors`\n * carries every one; `error` remains the primary one (the parser error when\n * present, else the default warning) for convenience.\n *\n * Error entries report the RAW declared `paramType` per the\n * {@link InputParseError} docs — not the canonicalized casing — so clients can\n * match on the casing they sent.\n *\n * Unexpected (non-ComputeError) failures still throw — they indicate a\n * programming bug, not bad user input.\n *\n * @internal Used by {@link processInputsWithErrors} / {@link fetchParsedDefinitionIO}.\n */\nexport function processInputWithError(rawInput: InputParamSchema): {\n\tinput: InputParam;\n\terror?: InputParseError;\n\terrors?: InputParseError[];\n} {\n\tconst baseInput: BaseInputType = {\n\t\tdescription: rawInput.description,\n\t\tname: rawInput.name,\n\t\tnickname: rawInput.nickname,\n\t\ttreeAccess: rawInput.treeAccess,\n\t\t// `null`/absent means \"no group\" — keep it absent instead of collapsing it\n\t\t// to '' and erasing the absent-vs-empty distinction.\n\t\tgroupName: rawInput.groupName ?? undefined,\n\t\tid: rawInput.id\n\t};\n\n\t// Normalize paramType to its canonical casing so callers can send any case\n\t// (e.g. Selva schemas emit lowercase \"valueList\" while the plugin reports\n\t// \"ValueList\"). The registry is keyed by canonical type.\n\tconst paramType = canonicalizeParamType(rawInput.paramType);\n\n\t// Shared, type-independent step: flatten the raw innerTree default into the\n\t// shape the per-type parsers expect (pure — does not mutate rawInput). An\n\t// unrecognized default shape nulls the value AND returns a warning so the\n\t// drop is surfaced to the client via parseErrors instead of vanishing.\n\tlet schema: InputParamSchema = { ...rawInput, paramType };\n\tlet defaultWarningError: InputParseError | undefined;\n\ttry {\n\t\tconst { schema: normalized, warning } = normalizeDefaultWithWarning(schema);\n\t\tschema = normalized;\n\t\tdefaultWarningError = warning && {\n\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\t// InputParseError.paramType is documented as the RAW declared type.\n\t\t\tparamType: rawInput.paramType,\n\t\t\tmessage: warning.message,\n\t\t\tcode: warning.code\n\t\t};\n\t} catch (error) {\n\t\t// A default too malformed for the normalizer to even walk is bad server/user data, not a\n\t\t// programming bug — null it and report per-input rather than aborting the whole\n\t\t// definition-IO fetch over one input.\n\t\tschema = { ...schema, default: null };\n\t\tdefaultWarningError = {\n\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\tparamType: rawInput.paramType,\n\t\t\tmessage: `Input \"${rawInput.name ?? 'unknown'}\" default could not be normalized and was dropped: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`,\n\t\t\tcode: 'MALFORMED_DEFAULT'\n\t\t};\n\t}\n\tconst parser = INPUT_TYPE_PARSERS.get(paramType);\n\n\t// Recoverable oddities a parser reports while still succeeding (e.g. a\n\t// ValueList default not in its values map) — surfaced like default warnings.\n\tconst parserWarnings: InputParseError[] = [];\n\tconst warn = (warning: { code: string; message: string }) =>\n\t\tparserWarnings.push({\n\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\tparamType: rawInput.paramType,\n\t\t\tmessage: warning.message,\n\t\t\tcode: warning.code\n\t\t});\n\n\ttry {\n\t\tif (!parser) {\n\t\t\tthrow ComputeError.unknownParamType(paramType, rawInput.name);\n\t\t}\n\t\t// Malformed-default and parser warnings ride through on the\n\t\t// otherwise-successful parse.\n\t\tconst input = parser.parse(schema, baseInput, warn);\n\t\tconst warnings = defaultWarningError\n\t\t\t? [defaultWarningError, ...parserWarnings]\n\t\t\t: parserWarnings;\n\t\treturn {\n\t\t\tinput,\n\t\t\terror: defaultWarningError,\n\t\t\t...(warnings.length > 0 && { errors: warnings })\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof ComputeError) {\n\t\t\tgetLogger().error(`Validation error for input ${rawInput.name || 'unknown'}:`, error.message);\n\t\t\tconst parserError: InputParseError = {\n\t\t\t\tinputName: rawInput.name || 'unknown',\n\t\t\t\tparamType: rawInput.paramType,\n\t\t\t\tmessage: error.message,\n\t\t\t\tcode: error.code\n\t\t\t};\n\t\t\t// The parser owns its own fallback; an unknown type falls back to the\n\t\t\t// geometry-shaped safe default (matching the old behavior). EVERY\n\t\t\t// failure is reported when the default warning, parser warnings, and\n\t\t\t// the parser error occurred on the same input — none may be shadowed.\n\t\t\treturn {\n\t\t\t\tinput: (parser ?? UNKNOWN_TYPE_FALLBACK).fallback(schema, baseInput),\n\t\t\t\terror: parserError,\n\t\t\t\terrors: [\n\t\t\t\t\t...(defaultWarningError ? [defaultWarningError] : []),\n\t\t\t\t\t...parserWarnings,\n\t\t\t\t\tparserError\n\t\t\t\t]\n\t\t\t};\n\t\t}\n\n\t\t// Unexpected failure — surface it.\n\t\tthrow new ComputeError(\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t'VALIDATION_ERROR',\n\t\t\t{\n\t\t\t\tcontext: { paramName: rawInput.name, paramType },\n\t\t\t\toriginalError: error instanceof Error ? error : new Error(String(error))\n\t\t\t}\n\t\t);\n\t}\n}\n\n/**\n * Parse an array of raw input schemas into typed {@link InputParam}s, each via\n * {@link processInput}. Use {@link processInputsWithErrors} to also collect the\n * inputs that failed validation.\n */\nexport function processInputs(rawInputs: InputParamSchema[]): InputParam[] {\n\treturn processInputsWithErrors(rawInputs).inputs;\n}\n\n/**\n * Like {@link processInputs}, but additionally returns a list of inputs that\n * failed validation and were filled with a safe default.\n *\n * @internal Used by {@link fetchParsedDefinitionIO}.\n */\nexport function processInputsWithErrors(rawInputs: InputParamSchema[]): {\n\tinputs: InputParam[];\n\tparseErrors: InputParseError[];\n} {\n\tconst inputs: InputParam[] = [];\n\tconst parseErrors: InputParseError[] = [];\n\tfor (const raw of rawInputs) {\n\t\tconst { input, errors } = processInputWithError(raw);\n\t\tinputs.push(input);\n\t\tif (errors) parseErrors.push(...errors);\n\t}\n\treturn { inputs, parseErrors };\n}\n","import { readField } from '@/core/utils/read-field';\nimport type { InputParamSchema, OutputParamSchema } from '../types';\n\n/**\n * @internal Canonicalize a raw `/io` param record's field CASING.\n *\n * ## Why this exists\n *\n * The Rhino Compute `/io` response is only partially camelCased, and how much\n * depends on the server branch:\n *\n * - mcneel 8.x/9.x and the upstream-tracking `8.x.selva` branch keep the IO\n *   schema close to the raw C# classes, which carry few/no `[JsonProperty]`\n *   attributes — so most per-param fields serialize PascalCase (`ParamType`,\n *   `Minimum`, `Name`, `Default`, …), with only `id` / `groupName` / `values`\n *   lowercased.\n * - The VektorNode Compute8 fork added `[JsonProperty(\"camelCase\")]` to every\n *   field, so the same record arrives fully camelCase.\n *\n * The per-type parsers ({@link INPUT_TYPE_PARSERS}) and base-field extraction\n * read fields straight through (`schema.paramType`, `schema.minimum`, …). On a\n * PascalCase server those reads all miss, so every input parses as an unknown\n * type with a `null` default — the definition looks like it has no usable\n * inputs. Normalizing the casing ONCE here, at the parse boundary, lets the\n * whole downstream pipeline stay branch-agnostic without threading `readField`\n * through every parser.\n *\n * ## What it does NOT touch\n *\n * Only the top-level FIELD KEYS are canonicalized. The VALUES are passed through\n * verbatim — in particular `default` (the nested DataTree, whose `InnerTree` /\n * item casing is handled separately and case-insensitively by\n * `normalizeDefault`) and `values` (user-authored dropdown label keys like\n * \"Option A\", which a naive deep camelCase pass would mangle to \"optionA\" — the\n * exact regression that motivated removing the old global `camelcaseKeys`).\n *\n * ## Missing wire fields\n *\n * `InputParamSchema` declares several fields required, but a degraded server\n * response can omit any of them. Rather than casting a hole into the type\n * (`as string` on a possibly-`undefined` read), each required field gets an\n * honest fallback that keeps the declared type true AND lets the failure\n * surface where it can be reported:\n *\n * - `paramType` → `''`: an empty type is unknown to the parser registry, so\n *   the input degrades to a safe fallback WITH an unknown-paramType\n *   `parseErrors` entry — a missing required wire field becomes a visible\n *   per-input parse error instead of a downstream `undefined.toLowerCase()`.\n * - `name`/`id`/`description` → `''` (a blank name reports as `'unknown'`).\n * - `treeAccess` → `false`, `atLeast`/`atMost` → `1`: Grasshopper's\n *   item-access defaults.\n * - `groupName` → `null`, NOT `''`: the type is `string | null` precisely to\n *   distinguish \"no group\" from \"empty group name\".\n * - `stepSize` is optional; a non-numeric wire value is dropped rather than\n *   passed through typed as `number`.\n */\nexport function normalizeInputSchema(raw: unknown): InputParamSchema {\n\tconst stepSize = readField<unknown>(raw, 'stepSize');\n\treturn {\n\t\tid: readField<string>(raw, 'id') ?? '',\n\t\tname: readField<string>(raw, 'name') ?? '',\n\t\tnickname: readField<string | null>(raw, 'nickname') ?? null,\n\t\tdescription: readField<string>(raw, 'description') ?? '',\n\t\tparamType: readField<string>(raw, 'paramType') ?? '',\n\t\ttreeAccess: readField<boolean>(raw, 'treeAccess') ?? false,\n\t\tminimum: readField<number | null>(raw, 'minimum') ?? null,\n\t\tmaximum: readField<number | null>(raw, 'maximum') ?? null,\n\t\tatLeast: readField<number>(raw, 'atLeast') ?? 1,\n\t\tatMost: readField<number>(raw, 'atMost') ?? 1,\n\t\tstepSize: typeof stepSize === 'number' && Number.isFinite(stepSize) ? stepSize : undefined,\n\t\tdefault: readField(raw, 'default'),\n\t\tvalues: readField<Record<string, string>>(raw, 'values'),\n\t\tacceptedFormats: readField<string[]>(raw, 'acceptedFormats'),\n\t\tgroupName: readField<string | null>(raw, 'groupName') ?? null\n\t};\n}\n\n/**\n * @internal Canonicalize a raw `/io` output record's field casing.\n *\n * Same rationale as {@link normalizeInputSchema}.\n */\nexport function normalizeOutputSchema(raw: unknown): OutputParamSchema {\n\treturn {\n\t\tname: readField<string>(raw, 'name') ?? '',\n\t\tnickname: readField<string | null>(raw, 'nickname') ?? null,\n\t\tparamType: readField<string>(raw, 'paramType') ?? '',\n\t\tid: readField<string>(raw, 'id') ?? ''\n\t};\n}\n","import { ComputeConfig, ComputeError, ErrorCodes } from '@/core';\nimport { fetchCompute } from '@/core/compute-fetch/compute-fetch';\nimport { readField } from '@/core/utils/read-field';\nimport { warnIfClientSide } from '@/core/utils/warnings';\nimport { prepareGrasshopperArgs, withGrasshopperErrorCodes } from '../solve';\n\nimport { GrasshopperParsedIO, GrasshopperParsedIORaw, IoResponseSchema } from '../types';\n\nimport { processInputsWithErrors } from './input/input-processors';\nimport { normalizeInputSchema, normalizeOutputSchema } from './normalize-schema';\n\n/**\n * Fetches raw input/output schemas from a Grasshopper definition.\n *\n * \"Raw\" means no per-type parsing (no default coercion, no discriminated-union\n * typing) — but NOT byte-for-byte wire data. The response IS normalized:\n * per-param field KEYS are canonicalized to camelCase across server branches\n * ({@link normalizeInputSchema} / {@link normalizeOutputSchema}, with honest\n * fallbacks for missing required fields), missing/non-array `inputs`/`outputs`\n * coerce to `[]`, and server load diagnostics surface as\n * `loadWarnings`/`loadErrors` (coerced to strings). Field VALUES — notably\n * `default` and the `values` dropdown-label map — pass through verbatim.\n *\n * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array)\n * @param config - Compute configuration (server URL, API key, etc.)\n * @returns Key-normalized inputs and outputs with no per-type processing\n * @throws {ComputeError} If fetch fails or response is invalid\n *\n * @public Use `fetchParsedDefinitionIO()` for processed, type-safe inputs\n */\nexport async function fetchDefinitionIO(\n\tdefinition: string | Uint8Array,\n\tconfig: ComputeConfig\n): Promise<GrasshopperParsedIORaw> {\n\tconst args = prepareGrasshopperArgs(definition, []);\n\tconst payload: { algo?: string | null; pointer?: string | null } = {};\n\tif (args.algo) payload.algo = args.algo;\n\tif (args.pointer) payload.pointer = args.pointer;\n\n\tif (!payload.algo && !payload.pointer) {\n\t\tthrow new ComputeError(\n\t\t\t'Definition must resolve to either a URL pointer or base64 algo',\n\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t{ context: { definition } }\n\t\t);\n\t}\n\n\tconst response = await fetchCompute<IoResponseSchema>(\n\t\t'io',\n\t\tpayload,\n\t\twithGrasshopperErrorCodes(config)\n\t);\n\n\tif (!response || typeof response !== 'object') {\n\t\tthrow new ComputeError('Invalid IO response structure', ErrorCodes.INVALID_INPUT, {\n\t\t\tcontext: { response, definition }\n\t\t});\n\t}\n\n\t// The `/io` response is only partially camelCased, and how much depends on the\n\t// server branch. Upstream-tracking branches (mcneel 8.x/9.x, `8.x.selva`) keep\n\t// the C# classes close to source — they carry few/no `[JsonProperty]`, so the\n\t// top-level wrapper is PascalCase `Inputs` / `Outputs` and per-param fields are\n\t// `ParamType` / `Minimum` / … The VektorNode Compute8 fork camelCases every\n\t// field. So we read every field we depend on case-insensitively via `readField`\n\t// rather than straight-through. A deep `camelcaseKeys` pass is NOT an option: it\n\t// mangled user-authored value-list label keys (\"Option A\" → \"optionA\") and item\n\t// `data` JSON — which is why per-field reads exist instead (per-input field\n\t// normalization lives in normalize-schema.ts; the nested `default` DataTree is\n\t// handled by normalize-default.ts).\n\t//\n\t// The server also reports definition-LOAD diagnostics on the IO response\n\t// (`errors`/`warnings` — e.g. a missing plugin that left inputs unresolved).\n\t// Surface them so a degraded input list comes with an explanation instead of\n\t// silently looking empty. Only attach when non-empty to keep the common\n\t// happy-path result clean.\n\tconst loadWarnings = nonEmptyStrings(readField(response, 'warnings'));\n\tconst loadErrors = nonEmptyStrings(readField(response, 'errors'));\n\n\t// Read the top-level Inputs/Outputs case-insensitively, then guard to arrays.\n\t// A server fault can also return a 200 whose body omits these (e.g. a load\n\t// failure surfacing as malformed-success), and the downstream `for...of` in\n\t// processInputsWithErrors throws \"inputs is not iterable\". Array.isArray (not\n\t// `?? []`) is deliberate: the symptom is non-iterability, so a non-array truthy\n\t// value (`{}`, a string) must coerce to `[]` too. The loadErrors/loadWarnings\n\t// surfaced above explain *why* a list came back empty.\n\tconst rawInputs = readField(response, 'inputs');\n\tconst rawOutputs = readField(response, 'outputs');\n\treturn {\n\t\tinputs: Array.isArray(rawInputs) ? rawInputs.map(normalizeInputSchema) : [],\n\t\toutputs: Array.isArray(rawOutputs) ? rawOutputs.map(normalizeOutputSchema) : [],\n\t\t...(loadWarnings && { loadWarnings }),\n\t\t...(loadErrors && { loadErrors })\n\t};\n}\n\n/**\n * Coerce a server `errors`/`warnings` array (typed `any[]`) into a clean\n * `string[]`, or `undefined` when there's nothing to report.\n *\n * Non-string diagnostics are KEPT, not dropped: a server fork reporting errors\n * as `{ message }` objects must not yield a mysteriously empty inputs list with\n * zero explanation (the exact failure this surfacing exists to prevent).\n * Objects coerce to their `message` field when it's a non-blank string, else\n * to JSON; other primitives via `String(...)`. Only `null`/`undefined` and\n * blank entries are discarded.\n */\nfunction nonEmptyStrings(value: unknown): string[] | undefined {\n\tif (!Array.isArray(value)) return undefined;\n\tconst cleaned = value.map(coerceDiagnostic).filter((v): v is string => v !== undefined);\n\treturn cleaned.length > 0 ? cleaned : undefined;\n}\n\n/** One diagnostic entry → non-blank string, or `undefined` when there's nothing to say. */\nfunction coerceDiagnostic(value: unknown): string | undefined {\n\tif (value === null || value === undefined) return undefined;\n\tlet text: string;\n\tif (typeof value === 'string') {\n\t\ttext = value;\n\t} else if (typeof value === 'object') {\n\t\tconst message = readField<unknown>(value, 'message');\n\t\tif (typeof message === 'string' && message.trim().length > 0) {\n\t\t\ttext = message;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\ttext = JSON.stringify(value) ?? String(value);\n\t\t\t} catch {\n\t\t\t\ttext = String(value);\n\t\t\t}\n\t\t}\n\t} else {\n\t\ttext = String(value);\n\t}\n\treturn text.trim().length > 0 ? text : undefined;\n}\n\n/**\n * Fetches and processes input/output schemas from a Grasshopper definition.\n * Returns strongly-typed, validated input parameters ready for use.\n *\n * @public This is the recommended way to fetch definition I/O schemas.\n *\n * @param definition - The Grasshopper definition (URL, base64 string, or Uint8Array)\n * @param config - Compute configuration (server URL, API key, etc.)\n * @returns Processed inputs with discriminated union types and outputs\n * @throws {ComputeError} If fetch fails or response is invalid\n *\n * @example\n * ```typescript\n * const { inputs, outputs } = await fetchParsedDefinitionIO(\n *   'https://example.com/definition.gh',\n *   { serverUrl: 'https://compute.rhino3d.com', apiKey: 'YOUR_KEY' }\n * );\n *\n * // Inputs are now strongly typed\n * inputs.forEach(input => {\n *   if (input.paramType === 'Number') {\n *     console.log(input.minimum, input.maximum); // TypeScript knows these exist\n *   }\n * });\n * ```\n */\nexport async function fetchParsedDefinitionIO(\n\tdefinition: string | Uint8Array,\n\tconfig: ComputeConfig\n): Promise<GrasshopperParsedIO> {\n\twarnIfClientSide('fetchParsedDefinitionIO', config.suppressBrowserWarning);\n\n\tconst {\n\t\tinputs: rawInputs,\n\t\toutputs,\n\t\tloadWarnings,\n\t\tloadErrors\n\t} = await fetchDefinitionIO(definition, config);\n\tconst { inputs, parseErrors } = processInputsWithErrors(rawInputs);\n\n\treturn {\n\t\tinputs,\n\t\toutputs,\n\t\t...(parseErrors.length > 0 && { parseErrors }),\n\t\t...(loadWarnings && { loadWarnings }),\n\t\t...(loadErrors && { loadErrors })\n\t};\n}\n","import { readField } from '@/core/utils/read-field';\n\n/** One entry of the schema endpoint's response, after wrapper-key normalization. */\nexport interface SchemaEndpointResult<TSchema = unknown> {\n\t/** Schemas embedded in that file. Absent when the file yielded none. */\n\tschemas?: TSchema[];\n\t/** Per-file diagnosis. Compute reports this and still answers 200. */\n\terror?: string;\n}\n\n/**\n * Read compute's `/grasshopper/schema` body: `[{ FileName, Schemas }]` per\n * uploaded file, or a bare object for a single file.\n *\n * Use this rather than unwrapping the body by hand. The wrapper's casing varies\n * by server branch — mcneel serializes `FileName`/`Schemas`, the VektorNode fork\n * `fileName`/`schemas` — so a fixed-key read silently yields `undefined` against\n * half the servers, and the endpoint answers 200 either way. The failure looks\n * like \"this definition has no schemas\", which sends you debugging the wrong\n * thing entirely.\n *\n * A blanket key-rewrite (the old `camelcaseKeys` approach) is NOT the fix: it\n * reaches inside the schemas and mangles user-authored names — `\"Display3d\"` →\n * `\"display3d\"`, value-list labels like `\"Option A\"` → `\"optionA\"`. Only the two\n * wrapper keys are read here; schema CONTENTS pass through untouched, which is\n * why `TSchema` is a pass-through type parameter this module never inspects.\n *\n * @typeParam TSchema - Your schema type (e.g. `UISchema`). Not inspected.\n * @param raw - The parsed JSON body.\n */\nexport function readSchemaResults<TSchema = unknown>(\n\traw: unknown\n): SchemaEndpointResult<TSchema>[] {\n\tconst entries = Array.isArray(raw) ? raw : [raw];\n\treturn entries.map((entry) => ({\n\t\tschemas: readField<TSchema[]>(entry, 'schemas'),\n\t\terror: readField<string>(entry, 'error')\n\t}));\n}\n","/**\n * Canonicalize a `/grasshopper/schema` body's key CASING.\n *\n * ## Why this exists\n *\n * Compute serializes the plugin's `UISchema` POCO, whose camelCase wire names\n * live in Newtonsoft `[JsonProperty]` attributes. `Selva.gha` ILRepack-merges\n * Newtonsoft into itself, so those attributes have type\n * `Selva!Newtonsoft.Json.JsonPropertyAttribute`. When the serializer that runs is\n * compute's OWN Newtonsoft assembly, it does not recognize that type as its own\n * attribute, reads no attributes at all, and falls back to raw CLR member names —\n * emitting `Inputs`/`Layout`/`SchemaVersion`.\n *\n * Nothing throws on the wire. Every consumer reads `schema.inputs` as\n * `undefined`, so the definition renders with no inputs, and `schemaVersion`\n * reads `undefined` — which also silently disables the newer-plugin version gate.\n *\n * This is the schema-body counterpart to {@link normalizeInputSchema}, which\n * solves the same split for the `/io` endpoint's param records.\n *\n * ## Why a casing rule, and not a key allowlist\n *\n * The wire names are the CLR names run through Newtonsoft's camelCase strategy,\n * so reproducing that strategy covers every key. An allowlist would have to\n * enumerate every structural key in `UISchema` and drift out of date each time\n * the schema gains a field, failing silently and in exactly this way again.\n *\n * ## What it does NOT touch\n *\n * `options`, `defaultOptions` and `values` hold USER-AUTHORED keys — dropdown\n * labels like `\"Standart Beschichtung\"`, `\"Use 10 Elements instead\"`, `\"True\"`.\n * Those maps are copied verbatim; only the key naming them is canonicalized.\n * Rewriting their contents is the exact regression that motivated deleting the\n * old global `camelcaseKeys` pass (see `read-field.ts`), which mangled\n * `\"Option A\"` into `\"optionA\"` and silently changed what a definition solved\n * with.\n *\n * Values are never inspected — only object keys are rewritten.\n */\n\n/**\n * Maps whose KEYS are authored by the definition's author, not by the schema\n * format. Descending into these corrupts user data.\n */\nconst USER_AUTHORED_MAPS = new Set(['options', 'defaultOptions', 'values']);\n\nconst isUpper = (ch: string): boolean => ch >= 'A' && ch <= 'Z';\n\n/**\n * Mirrors Newtonsoft's `CamelCaseNamingStrategy`, which is what the plugin's own\n * serializer would have produced: a leading run of capitals is lowercased whole,\n * except that the last capital stays if a lowercase word follows it. So\n * `Inputs` → `inputs`, `UV` → `uv`, `UVMapping` → `uvMapping`.\n *\n * A naive first-character-only rule yields `uVMapping`, a key nothing reads —\n * the same silent-undefined failure this module exists to prevent.\n */\nfunction toCamelCase(key: string): string {\n\tif (key.length === 0 || !isUpper(key[0])) return key;\n\n\tlet run = 0;\n\twhile (run < key.length && isUpper(key[run])) run++;\n\t// `UVMapping`: the `M` opens the next word, so it keeps its capital.\n\tif (run > 1 && run < key.length) run--;\n\n\treturn key.slice(0, run).toLowerCase() + key.slice(run);\n}\n\n/**\n * Returns a schema whose structural keys are camelCase, regardless of which\n * Newtonsoft serialized it. A body that is already camelCase passes through\n * unchanged in shape (it is still rebuilt, so the result is always a fresh\n * object the caller may mutate).\n *\n * Safe to call on any parsed JSON value; non-objects are returned as-is.\n */\nexport function normalizeUISchemaCasing<T>(raw: T): T {\n\treturn normalizeValue(raw) as T;\n}\n\nfunction normalizeValue(value: unknown): unknown {\n\tif (Array.isArray(value)) return value.map(normalizeValue);\n\tif (value === null || typeof value !== 'object') return value;\n\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n\t\tconst canonical = toCamelCase(key);\n\t\t// Copy the map verbatim: its keys are the author's, not the format's.\n\t\tout[canonical] = USER_AUTHORED_MAPS.has(canonical) ? child : normalizeValue(child);\n\t}\n\treturn out;\n}\n","import { DataTreeDefault, DataTreePath, InputParam, DataTree } from '../types';\nimport { ComputeError, ErrorCodes, getLogger } from '@/core';\nimport { isDataTreeDefault, TREE_PATH_RE } from './tree-path';\n\n/**\n * Value types that can be stored in a DataTree\n */\nexport type DataTreeValue = string | number | boolean | object | null;\n\n/**\n * Simple data item for compute requests (not to be confused with DataItem interface for responses).\n * Note: While TypeScript defines this as string, Rhino Compute accepts boolean/number primitives in JSON.\n */\ninterface ComputeDataItem {\n\tdata: string | boolean | number;\n}\n\n/**\n * InnerTree data structure for compute requests.\n */\ntype ComputeInnerTreeData = {\n\t[path in DataTreePath]: ComputeDataItem[];\n};\n\n/**\n * Standalone TreeBuilder class for constructing Grasshopper TreeBuilder structures.\n * Does not depend on RhinoCompute library.\n *\n * @example\n * ```ts\n * const tree = new TreeBuilder('MyParam')\n *   .append([0], [1, 2, 3])\n *   .append([1], [4, 5])\n *   .toComputeFormat();\n * ```\n */\nexport class TreeBuilder {\n\tprivate innerTree: ComputeInnerTreeData;\n\tprivate paramName: string;\n\n\tconstructor(paramName: string) {\n\t\tthis.paramName = paramName;\n\t\tthis.innerTree = {} as ComputeInnerTreeData;\n\t}\n\n\t/**\n\t * Append values to a specific path in the tree.\n\t *\n\t * @param path - Array of integers representing the branch path (e.g., [0], [0, 1])\n\t * @param items - Values to append at this path\n\t * @returns this for method chaining\n\t */\n\tpublic append(path: number[], items: DataTreeValue[]): this {\n\t\tconst pathKey = TreeBuilder.formatPathString(path);\n\n\t\tif (!this.innerTree[pathKey]) {\n\t\t\tthis.innerTree[pathKey] = [];\n\t\t}\n\n\t\tconst dataItems: ComputeDataItem[] = items.map((item) => ({\n\t\t\tdata: TreeBuilder.serializeValue(item)\n\t\t}));\n\n\t\tthis.innerTree[pathKey].push(...dataItems);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Append a single value to a path.\n\t *\n\t * @param path - Branch path\n\t * @param item - Single value to append\n\t * @returns this for method chaining\n\t */\n\tpublic appendSingle(path: number[], item: DataTreeValue): this {\n\t\treturn this.append(path, [item]);\n\t}\n\n\t/**\n\t * Set values from a DataTreeDefault structure.\n\t * Replaces any existing tree data.\n\t *\n\t * @param treeData - TreeBuilder structure with path keys like \"{0;1}\"\n\t * @returns this for method chaining\n\t */\n\tpublic fromDataTreeDefault(treeData: DataTreeDefault): this {\n\t\tthis.innerTree = {} as ComputeInnerTreeData;\n\n\t\tfor (const [pathStr, items] of Object.entries(treeData)) {\n\t\t\tif (!Array.isArray(items)) continue;\n\t\t\tconst path = TreeBuilder.parsePathString(pathStr);\n\t\t\tthis.append(path, items);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Append flattened values to path [0].\n\t * Useful for simple flat inputs.\n\t *\n\t * @param values - Single value or array of values\n\t * @returns this for method chaining\n\t */\n\tpublic appendFlat(values: DataTreeValue | DataTreeValue[]): this {\n\t\tconst items = Array.isArray(values) ? values : [values];\n\t\treturn this.append([0], items);\n\t}\n\n\t/**\n\t * Get the flattened list of all values in the tree.\n\t *\n\t * @returns Array of all values across all branches\n\t */\n\tpublic flatten(): DataTreeValue[] {\n\t\tconst result: DataTreeValue[] = [];\n\n\t\tfor (const items of Object.values(this.innerTree)) {\n\t\t\tif (Array.isArray(items)) {\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tresult.push(TreeBuilder.deserializeValue(item.data));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get all paths in the tree.\n\t *\n\t * @returns Array of path strings\n\t */\n\tpublic getPaths(): DataTreePath[] {\n\t\treturn Object.keys(this.innerTree) as DataTreePath[];\n\t}\n\n\t/**\n\t * Get values at a specific path.\n\t *\n\t * @param path - Path to retrieve values from\n\t * @returns Array of values or undefined if path doesn't exist\n\t */\n\tpublic getPath(path: number[]): DataTreeValue[] | undefined {\n\t\tconst pathKey = TreeBuilder.formatPathString(path);\n\t\tconst items = this.innerTree[pathKey];\n\t\tif (!items) return undefined;\n\t\treturn items.map((item: ComputeDataItem) => TreeBuilder.deserializeValue(item.data));\n\t}\n\n\t/**\n\t * Convert to format compatible with Grasshopper Compute API.\n\t *\n\t * @returns InnerTree object ready for compute\n\t */\n\tpublic toComputeFormat(): DataTree {\n\t\treturn {\n\t\t\tParamName: this.paramName,\n\t\t\tInnerTree: this.innerTree as any // Cast to any because request format differs from response type\n\t\t};\n\t}\n\n\t/**\n\t * Get the raw InnerTree data structure.\n\t *\n\t * @returns InnerTree data\n\t */\n\tpublic getInnerTree(): ComputeInnerTreeData {\n\t\treturn this.innerTree;\n\t}\n\n\t/**\n\t * Get the parameter name.\n\t *\n\t * @returns Parameter name\n\t */\n\tpublic getParamName(): string {\n\t\treturn this.paramName;\n\t}\n\n\t/**\n\t * Create DataTrees from an array of InputParam definitions.\n\t * Handles tree access, numeric constraints, and value parsing.\n\t *\n\t * @param inputs - Array of input parameter definitions\n\t * @returns Array of InnerTree instances ready for compute\n\t *\n\t * @example\n\t * ```ts\n\t * const trees = TreeBuilder.fromInputParams(inputs);\n\t * ```\n\t */\n\tpublic static fromInputParams(inputs: InputParam[]): DataTree[] {\n\t\treturn inputs\n\t\t\t.filter((input) => TreeBuilder.hasValidValue(input.default))\n\t\t\t.map((input) => {\n\t\t\t\tconst tree = new TreeBuilder(input.nickname || 'unnamed');\n\t\t\t\tconst value = input.default;\n\n\t\t\t\t// Handle tree access (complex TreeBuilder structure)\n\t\t\t\tif (input.treeAccess && isDataTreeDefault(value)) {\n\t\t\t\t\ttree.fromDataTreeDefault(value);\n\n\t\t\t\t\t// Apply numeric constraints to tree items\n\t\t\t\t\tif (TreeBuilder.isNumericInput(input)) {\n\t\t\t\t\t\ttree.applyNumericConstraints(input.minimum, input.maximum, input.nickname || 'unnamed');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Handle flat inputs\n\t\t\t\telse {\n\t\t\t\t\tconst values = Array.isArray(value) ? value : [value];\n\t\t\t\t\tconst processed = TreeBuilder.processValues(values, input);\n\t\t\t\t\ttree.appendFlat(processed);\n\t\t\t\t}\n\n\t\t\t\treturn tree.toComputeFormat();\n\t\t\t});\n\t}\n\n\t/**\n\t * Create a TreeBuilder from a single InputParam.\n\t *\n\t * @param input - Input parameter definition\n\t * @returns InnerTree ready for compute or undefined if value is invalid\n\t */\n\tpublic static fromInputParam(input: InputParam): DataTree | undefined {\n\t\tif (!TreeBuilder.hasValidValue(input.default)) return undefined;\n\n\t\tconst trees = TreeBuilder.fromInputParams([input]);\n\t\treturn trees[0];\n\t}\n\n\t/**\n\t * Set or replace a parameter value within a TreeBuilder or InnerTree array.\n\t *\n\t * Supports both high-level `DataTree[]` instances and low-level `InnerTree[]` format.\n\t *\n\t * **Architecture Note:**\n\t * - Use with `DataTree[]` when building/modifying before computation\n\t * - Use with `InnerTree[]` when modifying compute API results\n\t * - `DataTree` is the high-level builder; `InnerTree` is the Rhino Compute format\n\t *\n\t * Copy-on-write: returns a new array; the caller's array is never mutated.\n\t *\n\t * @overload For TreeBuilder instances (high-level builder)\n\t * @param trees - Array of TreeBuilder instances to read from (not mutated)\n\t * @param paramName - The parameter name to set or replace\n\t * @param newValue - The new value (scalar, array, or TreeBuilder structure)\n\t * @returns A new TreeBuilder array with the updated parameter\n\t *\n\t * @overload For compiled InnerTree (low-level API format)\n\t * @param trees - The compiled InnerTree array (typically from `client.solve()`; not mutated)\n\t * @param paramName - The parameter name to set or replace\n\t * @param newValue - The new value (scalar, array, or TreeBuilder structure)\n\t * @returns A new InnerTree array with the updated parameter\n\t *\n\t * @example\n\t * ```ts\n\t * // With TreeBuilder instances (high-level)\n\t * let trees = [new TreeBuilder('X'), new TreeBuilder('Y')];\n\t * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42);\n\t * const result = await client.solve(definitionUrl,\n\t *   trees.map(t => t.toComputeFormat())\n\t * );\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * // With InnerTree format (low-level, from API)\n\t * let trees = await client.solve(definitionUrl, initialInputs);\n\t * trees = TreeBuilder.replaceTreeValue(trees, 'X', 42);\n\t * trees = TreeBuilder.replaceTreeValue(trees, 'Y', [1, 2, 3]);\n\t * ```\n\t */\n\tpublic static replaceTreeValue(\n\t\ttrees: TreeBuilder[],\n\t\tparamName: string,\n\t\tnewValue: DataTreeValue\n\t): TreeBuilder[];\n\tpublic static replaceTreeValue(\n\t\ttrees: DataTree[],\n\t\tparamName: string,\n\t\tnewValue: DataTreeValue\n\t): DataTree[];\n\tpublic static replaceTreeValue(\n\t\ttrees: TreeBuilder[] | DataTree[],\n\t\tparamName: string,\n\t\tnewValue: DataTreeValue\n\t): TreeBuilder[] | DataTree[] {\n\t\tconst isBuilderArray = trees.length > 0 && trees[0] instanceof TreeBuilder;\n\t\tconst builder = TreeBuilder.buildFromValue(paramName, newValue);\n\n\t\tif (isBuilderArray) {\n\t\t\t// Copy-on-write: never mutate the caller's array (e.g. pristine\n\t\t\t// defaults from fromInputParams must survive slider updates).\n\t\t\tconst builders = (trees as TreeBuilder[]).slice();\n\t\t\tconst idx = builders.findIndex((t) => t.getParamName() === paramName);\n\t\t\tif (idx !== -1) builders[idx] = builder;\n\t\t\telse builders.push(builder);\n\t\t\treturn builders;\n\t\t}\n\n\t\t// Empty arrays land here too — see the \"empty array\" characterization\n\t\t// test in data-tree.test.ts: pins the current behavior of returning the\n\t\t// compute-format shape rather than a TreeBuilder.\n\t\tconst dataTrees = (trees as DataTree[]).slice();\n\t\tconst compiled = builder.toComputeFormat();\n\t\tconst idx = dataTrees.findIndex((t) => t.ParamName === paramName);\n\t\tif (idx !== -1) dataTrees[idx] = compiled;\n\t\telse dataTrees.push(compiled);\n\t\treturn dataTrees;\n\t}\n\n\t/**\n\t * Build a TreeBuilder from a single value, dispatching on shape:\n\t * DataTreeDefault structure, array, or scalar.\n\t */\n\tprivate static buildFromValue(paramName: string, value: DataTreeValue): TreeBuilder {\n\t\tconst tree = new TreeBuilder(paramName);\n\t\tif (isDataTreeDefault(value)) {\n\t\t\ttree.fromDataTreeDefault(value);\n\t\t} else {\n\t\t\ttree.appendFlat(value);\n\t\t}\n\t\treturn tree;\n\t}\n\n\t/**\n\t * Extract a value from a TreeBuilder or InnerTree array by parameter name.\n\t *\n\t * Automatically unwraps single values for convenience.\n\t * Works with both high-level `DataTree[]` instances and low-level `InnerTree[]` format.\n\t *\n\t * **Architecture Note:**\n\t * - Use with `DataTree[]` to read builder instances\n\t * - Use with `InnerTree[]` to read compute API responses\n\t * - Return behavior is consistent across both formats\n\t *\n\t * **Return Value Behavior:**\n\t * - Single value → unwrapped (returns `5` not `[5]`)\n\t * - Multiple values → array of values\n\t * - Not found → `null`\n\t *\n\t * @overload For TreeBuilder instances\n\t * @param trees - Array of TreeBuilder instances to read from\n\t * @param paramName - The parameter name to retrieve\n\t * @returns The unwrapped value, array of values, or null if parameter not found\n\t *\n\t * @overload For compiled InnerTree\n\t * @param trees - The compiled InnerTree array (typically from `client.solve()`)\n\t * @param paramName - The parameter name to retrieve\n\t * @returns The unwrapped value, array of values, or null if parameter not found\n\t *\n\t * @example\n\t * ```ts\n\t * // With TreeBuilder instances\n\t * const trees = [new TreeBuilder('X'), new TreeBuilder('Y')];\n\t * trees[0].appendFlat(42);\n\t * const x = TreeBuilder.getTreeValue(trees, 'X'); // Returns 42\n\t * ```\n\t *\n\t * @example\n\t * ```ts\n\t * // With InnerTree from compute results\n\t * const result = await client.solve(definitionUrl, inputs);\n\t * const x = TreeBuilder.getTreeValue(result, 'X'); // Returns 42 (not [42])\n\t * const points = TreeBuilder.getTreeValue(result, 'Points'); // Returns [point1, point2, ...]\n\t * ```\n\t */\n\tpublic static getTreeValue(trees: TreeBuilder[], paramName: string): DataTreeValue | null;\n\tpublic static getTreeValue(trees: DataTree[], paramName: string): DataTreeValue | null;\n\tpublic static getTreeValue(\n\t\ttrees: TreeBuilder[] | DataTree[],\n\t\tparamName: string\n\t): DataTreeValue | null {\n\t\tconst isBuilderArray = trees.length > 0 && trees[0] instanceof TreeBuilder;\n\n\t\tconst values = isBuilderArray\n\t\t\t? TreeBuilder.readFromBuilders(trees as TreeBuilder[], paramName)\n\t\t\t: TreeBuilder.readFromDataTrees(trees as DataTree[], paramName);\n\n\t\tif (values === null) return null;\n\t\tif (values.length === 0) return null;\n\t\tif (values.length === 1) return values[0];\n\t\treturn values;\n\t}\n\n\t/**\n\t * Read all values for `paramName` across every branch of the matching builder.\n\t * Returns null when the builder isn't found.\n\t */\n\tprivate static readFromBuilders(\n\t\tbuilders: TreeBuilder[],\n\t\tparamName: string\n\t): DataTreeValue[] | null {\n\t\tconst tree = builders.find((t) => t.getParamName() === paramName);\n\t\treturn tree ? tree.flatten() : null;\n\t}\n\n\t/**\n\t * Read values from the first branch of the matching compiled InnerTree\n\t * (multi-branch responses are not flattened — current semantics, pinned by\n\t * the \"reads from the first branch path only\" test).\n\t */\n\tprivate static readFromDataTrees(\n\t\tdataTrees: DataTree[],\n\t\tparamName: string\n\t): DataTreeValue[] | null {\n\t\tconst tree = dataTrees.find((t) => t.ParamName === paramName);\n\t\tif (!tree?.InnerTree) return null;\n\n\t\tconst firstKey = Object.keys(tree.InnerTree)[0];\n\t\tif (!firstKey) return null;\n\n\t\t// @ts-expect-error - Dynamic key access on innerTree\n\t\tconst items = tree.InnerTree[firstKey];\n\n\t\tif (Array.isArray(items)) {\n\t\t\t// Preserve nulls (legitimate GH items) so indices don't shift; an\n\t\t\t// item without `data` deserializes to null in place. \"Param not\n\t\t\t// found\" is signalled only by a missing/empty tree, never by nulls.\n\t\t\treturn items.map((item) =>\n\t\t\t\titem?.data !== undefined ? TreeBuilder.deserializeValue(item.data) : null\n\t\t\t);\n\t\t}\n\n\t\tif (items?.data !== undefined) return [TreeBuilder.deserializeValue(items.data)];\n\t\treturn items !== undefined ? [items as DataTreeValue] : null;\n\t}\n\n\t/**\n\t * Parse a TreeBuilder path string like \"{0;1;2}\" into [0, 1, 2].\n\t * Negative indices (\"{-1;2}\") and the root path \"{}\" are valid.\n\t *\n\t * @param pathStr - Path string\n\t * @returns Array of path indices\n\t * @throws {ComputeError} `INVALID_INPUT` when the path string is not a valid\n\t *   Grasshopper branch path. Malformed keys must never silently collapse to a\n\t *   default branch — two distinct unparseable keys would merge their items\n\t *   into one branch.\n\t */\n\tpublic static parsePathString(pathStr: string): number[] {\n\t\t// Allow the legitimate root path \"{}\" alongside \"{0;1;2}\" / \"{-1;2}\"\n\t\tconst match = pathStr.match(TREE_PATH_RE);\n\t\tif (!match) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t`Invalid Grasshopper tree path: \"${pathStr}\". ` +\n\t\t\t\t\t`Expected \"{}\", \"{0}\", or \"{0;1;2}\" (negative indices allowed, no empty segments).`,\n\t\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t\t{ context: { pathStr } }\n\t\t\t);\n\t\t}\n\t\t// Root path \"{}\" — the (optional) capture group is undefined/empty.\n\t\tif (!match[1]) return [];\n\t\treturn match[1].split(';').map(Number);\n\t}\n\n\t/**\n\t * Format a path array into TreeBuilder path string format.\n\t *\n\t * @param path - Path as number array\n\t * @returns Formatted path string like \"{0;1;2}\"\n\t */\n\tpublic static formatPathString(path: number[]): DataTreePath {\n\t\treturn `{${path.join(';')}}` as DataTreePath;\n\t}\n\n\t/** Apply numeric constraints to all tree values. */\n\tprivate applyNumericConstraints(\n\t\tmin: number | null | undefined,\n\t\tmax: number | null | undefined,\n\t\tinputName: string\n\t): void {\n\t\tfor (const items of Object.values(this.innerTree)) {\n\t\t\tif (!Array.isArray(items)) continue;\n\n\t\t\tfor (const item of items) {\n\t\t\t\tconst value = TreeBuilder.deserializeValue(item.data);\n\t\t\t\tif (typeof value === 'number') {\n\t\t\t\t\tconst clamped = TreeBuilder.clampValue(value, min, max, inputName);\n\t\t\t\t\titem.data = TreeBuilder.serializeValue(clamped);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Serialize a value for compute requests.\n\t * Preserves booleans and numbers as primitives for proper Grasshopper parameter handling.\n\t */\n\tprivate static serializeValue(value: DataTreeValue): string | boolean | number {\n\t\tif (typeof value === 'boolean') return value;\n\t\tif (typeof value === 'number') return value;\n\t\tif (typeof value === 'string') return value;\n\t\tif (typeof value === 'object' && value !== null) {\n\t\t\treturn JSON.stringify(value);\n\t\t}\n\t\treturn String(value);\n\t}\n\n\t/**\n\t * Deserialize a value back to its original type.\n\t * Handles both string-encoded values and primitive values.\n\t */\n\tprivate static deserializeValue(data: string | boolean | number): DataTreeValue {\n\t\t// If already a primitive type, return as-is\n\t\tif (typeof data === 'boolean') return data;\n\t\tif (typeof data === 'number') return data;\n\n\t\t// Handle string values\n\t\tif (typeof data !== 'string') return data;\n\n\t\t// Try to parse as JSON first\n\t\tif (data.startsWith('{') || data.startsWith('[')) {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(data);\n\t\t\t} catch {\n\t\t\t\treturn data;\n\t\t\t}\n\t\t}\n\t\t// Coerce to number only when the string is the *canonical* form of a\n\t\t// finite number, i.e. it round-trips exactly (String(Number(s)) === s).\n\t\t// API responses encode numbers as strings ('42', '3.14', '1e+21'), and\n\t\t// those must come back numeric — but non-canonical numeric-looking\n\t\t// strings ('007', '1e5', 'Infinity', '', '  5') were strings on the\n\t\t// wire and must stay strings.\n\t\tconst num = Number(data);\n\t\tif (Number.isFinite(num) && String(num) === data) {\n\t\t\treturn num;\n\t\t}\n\t\t// Try to parse as boolean\n\t\tif (data === 'true') return true;\n\t\tif (data === 'false') return false;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Check if a value is valid for inclusion in a DataTree.\n\t */\n\tprivate static hasValidValue(value: unknown): boolean {\n\t\tif (value === undefined || value === null) return false;\n\t\tif (typeof value === 'string') return true;\n\t\tif (Array.isArray(value) && value.length === 0) return false;\n\t\tif (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0)\n\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Check if input is numeric type.\n\t */\n\tprivate static isNumericInput(input: InputParam): input is InputParam & {\n\t\tparamType: 'Number' | 'Integer';\n\t\tminimum?: number | null;\n\t\tmaximum?: number | null;\n\t} {\n\t\treturn input.paramType === 'Number' || input.paramType === 'Integer';\n\t}\n\n\t/**\n\t * Process array of values based on input type.\n\t */\n\tprivate static processValues(values: DataTreeValue[], input: InputParam): DataTreeValue[] {\n\t\treturn values\n\t\t\t.map((val) => {\n\t\t\t\t// Apply numeric constraints\n\t\t\t\tif (TreeBuilder.isNumericInput(input) && typeof val === 'number') {\n\t\t\t\t\treturn TreeBuilder.clampValue(\n\t\t\t\t\t\tval,\n\t\t\t\t\t\tinput.minimum,\n\t\t\t\t\t\tinput.maximum,\n\t\t\t\t\t\tinput.nickname || 'unnamed'\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Keep objects and strings as-is (serialization happens in append)\n\t\t\t\treturn val;\n\t\t\t})\n\t\t\t.filter((v) => v !== null && v !== undefined);\n\t}\n\n\t/**\n\t * Clamp numeric value to constraints.\n\t */\n\tprivate static clampValue(\n\t\tvalue: number,\n\t\tmin: number | null | undefined,\n\t\tmax: number | null | undefined,\n\t\tinputName: string\n\t): number {\n\t\tlet result = value;\n\n\t\tif (min !== null && min !== undefined && result < min) {\n\t\t\tgetLogger().warn(`${inputName}: ${value} below min ${min}, clamping`);\n\t\t\tresult = min;\n\t\t}\n\t\tif (max !== null && max !== undefined && result > max) {\n\t\t\tgetLogger().warn(`${inputName}: ${value} above max ${max}, clamping`);\n\t\t\tresult = max;\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"],"mappings":"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"}