@selvajs/compute 5.0.0-beta.5 → 5.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"core-yOYyTPZD.cjs","names":[],"sources":["../src/core/errors.ts","../src/core/utils/logger.ts","../src/core/utils/read-field.ts","../src/core/server/validate-server-url.ts","../src/core/compute-fetch/request.ts","../src/core/utils/encoding.ts","../src/core/compute-fetch/read-body.ts","../src/core/compute-fetch/server-timing.ts","../src/core/compute-fetch/signal.ts","../src/core/compute-fetch/wire-size.ts","../src/core/compute-fetch/response.ts","../src/core/server/classify-probe-failure.ts","../src/core/compute-fetch/retry.ts","../src/core/compute-fetch/compute-fetch.ts","../src/core/definition-ref.ts","../src/core/files/sub-folder.ts","../src/core/files/handle-files.ts"],"sourcesContent":["export const ErrorCodes = {\n\t/** Connection failure, a gateway status (502/503/504), or a 2xx body cut off mid-stream. */\n\tNETWORK_ERROR: 'NETWORK_ERROR',\n\t/** HTTP 401 or 403: the server rejected the credentials. */\n\tAUTH_ERROR: 'AUTH_ERROR',\n\t/** The server rejected the request (HTTP 400, 413), or an input failed a client-side check. */\n\tVALIDATION_ERROR: 'VALIDATION_ERROR',\n\t/** The server failed the solve (HTTP 500), or the solve reported errors. */\n\tCOMPUTATION_ERROR: 'COMPUTATION_ERROR',\n\t/** No response within `ComputeConfig.timeoutMs`. */\n\tTIMEOUT_ERROR: 'TIMEOUT_ERROR',\n\t/** A browser fetch failed with no response: most often CORS, which the browser hides. */\n\tCORS_ERROR: 'CORS_ERROR',\n\t/** HTTP 404: the endpoint doesn't exist on this server. Never retried. */\n\tNOT_FOUND: 'NOT_FOUND',\n\t/** HTTP 429. Retried, honouring `Retry-After`, when the policy allows (`RetryPolicy.retryOn429`). */\n\tRATE_LIMIT: 'RATE_LIMIT',\n\t/** A 2xx that declared a non-JSON body, e.g. a captive portal or proxy login page. Never retried. */\n\tINVALID_RESPONSE: 'INVALID_RESPONSE',\n\t/**\n\t * The response body passed `ComputeConfig.maxResponseBytes`; the download was cancelled there.\n\t * Never retried. `context` carries `{ maxResponseBytes }`.\n\t */\n\tRESPONSE_TOO_LARGE: 'RESPONSE_TOO_LARGE',\n\t/** Any failure not classified by another code. */\n\tUNKNOWN_ERROR: 'UNKNOWN_ERROR',\n\t/** An object was used after `dispose()`, or a file download failed part-way. */\n\tINVALID_STATE: 'INVALID_STATE',\n\t/** A caller-supplied argument is unusable: an empty definition, an unserializable body. */\n\tINVALID_INPUT: 'INVALID_INPUT',\n\t/**\n\t * Bad configuration: an invalid `serverUrl`, a server that answers with a redirect, an\n\t * incomplete logger, an interval out of range.\n\t */\n\tINVALID_CONFIG: 'INVALID_CONFIG',\n\t/** A browser-only API (file download) was called outside a browser. */\n\tBROWSER_ONLY: 'BROWSER_ONLY',\n\t/** The runtime has no base64 codec: neither `Buffer` nor `atob`/`btoa`. */\n\tENVIRONMENT_ERROR: 'ENVIRONMENT_ERROR',\n\t/** A string that should be base64 isn't. */\n\tENCODING_ERROR: 'ENCODING_ERROR',\n\t/** An input's `default` had a shape the normalizer didn't recognize. */\n\tMALFORMED_DEFAULT: 'MALFORMED_DEFAULT',\n\t/** Latest-wins scheduling: a newer call replaced this one. */\n\tSUPERSEDED: 'SUPERSEDED',\n\t/** The scheduler or a caller-supplied `AbortSignal` aborted this call. */\n\tABORTED: 'ABORTED',\n\t/**\n\t * The queue was at `maxQueueDepth`, so the call was rejected without queueing. Retryable after a\n\t * back-off. `context` carries `{ queueDepth, maxQueueDepth }`.\n\t */\n\tQUEUE_FULL: 'QUEUE_FULL',\n\t/**\n\t * The call waited longer than `queueWaitMs` without starting, and was rejected rather than run\n\t * stale. Retryable. `context` carries `{ waitedMs, queueWaitMs }`.\n\t */\n\tQUEUE_TIMEOUT: 'QUEUE_TIMEOUT',\n\t/**\n\t * The server no longer holds the definition a `pointer` named (evicted, restarted, another\n\t * child). Retry with the full definition. Read from the error body's\n\t * `code: \"definition_not_cached\"`.\n\t */\n\tDEFINITION_NOT_CACHED: 'DEFINITION_NOT_CACHED'\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Error with a typed `code`: switch on it rather than on the message. */\nexport class ComputeError extends Error {\n\tpublic readonly code: ErrorCode;\n\tpublic readonly statusCode?: number;\n\tpublic readonly context?: Record<string, unknown>;\n\tpublic readonly originalError?: Error;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: ErrorCode = ErrorCodes.UNKNOWN_ERROR,\n\t\toptions?: { statusCode?: number; context?: Record<string, unknown>; originalError?: unknown }\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'ComputeError';\n\t\tthis.code = code;\n\t\tthis.statusCode = options?.statusCode;\n\t\tthis.context = options?.context;\n\t\tif (options?.originalError !== undefined) {\n\t\t\tconst original = options.originalError;\n\t\t\tthis.originalError = original instanceof Error ? original : new Error(String(original));\n\t\t\t(this as { cause?: unknown }).cause = this.originalError;\n\t\t}\n\t}\n}\n","import { ComputeError, ErrorCodes } from '../errors';\n\nexport interface Logger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nconst silent: Logger = {\n\tdebug() {},\n\tinfo() {},\n\twarn() {},\n\terror() {}\n};\n\nlet internalLogger: Logger = silent;\n\nexport function getLogger(): Logger {\n\treturn internalLogger;\n}\n\n/**\n * Route the package's logs to `logger`; `null` silences them (the default).\n *\n * @throws {ComputeError} `INVALID_CONFIG` if the logger lacks one of the four methods, rather\n * than a \"debug is not a function\" later at an unrelated call site.\n *\n * @example\n * ```typescript\n * import { setLogger } from '@selvajs/compute/core';\n *\n * setLogger(console);\n * setLogger({\n * debug: (msg, ...args) => myLogger.debug(msg, ...args),\n * info: (msg, ...args) => myLogger.info(msg, ...args),\n * warn: (msg, ...args) => myLogger.warn(msg, ...args),\n * error: (msg, ...args) => myLogger.error(msg, ...args)\n * });\n * setLogger(null);\n * ```\n */\nexport function setLogger(logger: Logger | Console | null): void {\n\tif (logger === null) {\n\t\tinternalLogger = silent;\n\t\treturn;\n\t}\n\n\tconst missing = (['debug', 'info', 'warn', 'error'] as const).filter(\n\t\t(method) => typeof (logger as unknown as Record<string, unknown>)[method] !== 'function'\n\t);\n\tif (missing.length > 0) {\n\t\tthrow new ComputeError(\n\t\t\t`Logger is missing required method(s): ${missing.join(', ')}. A logger must implement debug, info, warn and error.`,\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { missingMethods: missing } }\n\t\t);\n\t}\n\n\tinternalLogger = logger as Logger;\n}\n\n/** Shorthand for `setLogger(console)`. */\nexport function enableDebugLogging(): void {\n\tsetLogger(console);\n}\n","/**\n * Case-insensitive field reads for wire payloads.\n *\n * Rhino Compute branches serialize the same field in different casings. mcneel 8.x/9.x send the IO\n * schema PascalCase (`ParamType`, `Default`, `InnerTree`): those C# classes carry no\n * `[JsonProperty]`. The VektorNode fork sends camelCase, except inside the `default` DataTree\n * (`ParamName`, `InnerTree`), an external type it can't attribute. So one response can mix casings.\n * Read the fields you need through here and leave the rest verbatim: rewriting every key corrupts\n * user-authored ones such as value-list labels.\n */\n\n/** Own keys only: a payload probed for `constructor` must not find `Object.prototype`'s. */\nfunction findOwnKey(obj: unknown, name: string): string | undefined {\n\tif (!obj || typeof obj !== 'object') return undefined;\n\tif (Object.prototype.hasOwnProperty.call(obj, name)) return name;\n\tconst lower = name.toLowerCase();\n\treturn Object.keys(obj).find((key) => key.toLowerCase() === lower);\n}\n\n/**\n * Read `name` in any casing, preferring an exact-case match. `undefined` when no own key matches or\n * `obj` isn't an object.\n */\nexport function readField<T = unknown>(obj: unknown, name: string): T | undefined {\n\tconst key = findOwnKey(obj, name);\n\treturn key === undefined ? undefined : ((obj as Record<string, unknown>)[key] as T);\n}\n\n/** Whether `name` is present in any casing, even with a null or undefined value. */\nexport function hasField(obj: unknown, name: string): boolean {\n\treturn findOwnKey(obj, name) !== undefined;\n}\n","import { ComputeError, ErrorCodes } from '@/core/errors';\n\n/**\n * The public McNeel endpoint, blocked by default: users must point at their own server. Its raw IP\n * isn't blocked: it sits behind a load balancer with no stable address to pin.\n */\nexport const DEFAULT_BLOCKED_HOST = 'compute.rhino3d.com';\n\nexport interface ValidateServerUrlOptions {\n\t/**\n\t * Hostnames rejected as a `serverUrl` — a backend's shared public endpoint,\n\t * which callers must not point at. Defaults to `[DEFAULT_BLOCKED_HOST]`; pass\n\t * `[]` to block nothing. Compared lowercased with any trailing dot stripped.\n\t */\n\tblockedHosts?: readonly string[];\n}\n\n/**\n * Validate a compute `serverUrl` and return it trimmed, without trailing slashes. Every entry point\n * that takes a server URL calls this, so a URL is accepted or rejected the same way everywhere.\n *\n * @throws {ComputeError} `INVALID_CONFIG` when the URL is empty, isn't http(s), doesn't parse,\n * embeds credentials, has a query or fragment, or names a blocked host.\n */\nexport function validateServerUrl(raw: string, options?: ValidateServerUrlOptions): string {\n\tconst trimmed = raw?.trim() ?? '';\n\tif (!trimmed) {\n\t\tthrow new ComputeError('serverUrl is required', ErrorCodes.INVALID_CONFIG, {\n\t\t\tcontext: { receivedServerUrl: raw }\n\t\t});\n\t}\n\n\tconst invalid = (reason: string, originalError?: unknown) =>\n\t\tnew ComputeError(`Invalid serverUrl: \"${trimmed}\". ${reason}`, ErrorCodes.INVALID_CONFIG, {\n\t\t\tcontext: { receivedServerUrl: raw },\n\t\t\toriginalError\n\t\t});\n\n\tif (!/^https?:\\/\\//i.test(trimmed)) {\n\t\tthrow invalid(\n\t\t\t'Must start with \"http://\" or \"https://\". ' +\n\t\t\t\t'For example: \"http://localhost:6500\" or \"https://example.com\"'\n\t\t);\n\t}\n\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(trimmed);\n\t} catch (err) {\n\t\tthrow invalid(\n\t\t\t`Must be a valid URL. Received error: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\terr\n\t\t);\n\t}\n\n\tif (parsed.username !== '' || parsed.password !== '') {\n\t\tthrow invalid(\n\t\t\t'Must not embed credentials (user:pass@host) — ' +\n\t\t\t\t'fetch rejects credentialed URLs at request time. Pass the API key separately.'\n\t\t);\n\t}\n\n\t// A string check: a bare trailing \"?\" or \"#\" parses to an empty search/hash but still corrupts\n\t// the endpoint paths appended to this URL.\n\tif (trimmed.includes('?') || trimmed.includes('#')) {\n\t\tthrow invalid(\n\t\t\t'Must not contain a query string or fragment — ' +\n\t\t\t\t'endpoint paths are appended to this URL (e.g. \"${serverUrl}/version\"), ' +\n\t\t\t\t'and a \"?\" or \"#\" suffix would corrupt every request path.'\n\t\t);\n\t}\n\n\t// Lowercase + strip any trailing dot so the FQDN form can't bypass the block.\n\tconst hostname = trimTrailing(parsed.hostname.toLowerCase(), '.');\n\tconst blocked = (options?.blockedHosts ?? [DEFAULT_BLOCKED_HOST]).map((h) =>\n\t\ttrimTrailing(h.toLowerCase(), '.')\n\t);\n\tif (blocked.includes(hostname)) {\n\t\tthrow new ComputeError(\n\t\t\t'serverUrl must be set to your Compute server URL. The shared public endpoint is not allowed.',\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { receivedServerUrl: raw } }\n\t\t);\n\t}\n\n\treturn trimTrailing(trimmed, '/');\n}\n\n/** A loop, not `/x+$/`: that regex retries from every position of a long run and goes quadratic. */\nfunction trimTrailing(value: string, char: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && value[end - 1] === char) end--;\n\treturn value.slice(0, end);\n}\n","import { getLogger } from '../utils/logger';\n\nimport type { ComputeAuth, ComputeConfig } from '../types';\n\n/** `serverUrl` as `validateServerUrl` returns it: no trailing slash. */\nexport function buildUrl(endpoint: string, serverUrl: string): string {\n\treturn `${serverUrl}/${endpoint.replace(/^\\/+/, '')}`;\n}\n\nfunction isLocalhost(serverUrl: string): boolean {\n\ttry {\n\t\t// `hostname` (not `host`) strips the port; IPv6 hostnames keep their\n\t\t// brackets, so `http://[::1]:6500` yields `[::1]`.\n\t\tconst hostname = new URL(serverUrl).hostname.toLowerCase();\n\t\treturn hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n\t} catch {\n\t\treturn /(localhost|127\\.0\\.0\\.1|\\[::1\\])/i.test(serverUrl);\n\t}\n}\n\n/** Server URLs already warned about missing auth — warn once per server, not per request. */\nconst warnedNoAuth = new Set<string>();\n\nconst DEFAULT_API_KEY_HEADER = 'RhinoComputeKey';\n\n/**\n * Caller headers plus credentials. Every request to the server builds its headers here,\n * so a liveness probe authenticates exactly like the solve it gates.\n */\nexport function buildAuthHeaders(auth: ComputeAuth): Record<string, string> {\n\treturn mergeUnder(auth.headers, credentialHeaders(auth));\n}\n\nexport function buildHeaders(requestId: string, config: ComputeConfig): HeadersInit {\n\tconst headers = mergeUnder(config.headers, {\n\t\t...credentialHeaders(config),\n\t\t'X-Request-ID': requestId,\n\t\t'Content-Type': 'application/json'\n\t});\n\n\tif (\n\t\t!config.apiKey &&\n\t\t!config.authToken &&\n\t\t!warnedNoAuth.has(config.serverUrl) &&\n\t\t!isLocalhost(config.serverUrl)\n\t) {\n\t\twarnedNoAuth.add(config.serverUrl);\n\t\tgetLogger().warn(\n\t\t\t`⚠️ [Compute] Request [${requestId}] targets remote server (${config.serverUrl}) but no API key or auth token is configured. Requests may fail or be rate-limited. (warned once per server)`\n\t\t);\n\t}\n\n\treturn headers;\n}\n\nfunction credentialHeaders(auth: ComputeAuth): Record<string, string> {\n\treturn {\n\t\t...(auth.authToken && { Authorization: auth.authToken }),\n\t\t...(auth.apiKey && { [auth.apiKeyHeader ?? DEFAULT_API_KEY_HEADER]: auth.apiKey })\n\t};\n}\n\n/**\n * Header names are case-insensitive and fetch joins duplicates, so a caller key\n * matching a transport key in any case is dropped rather than sent alongside it.\n */\nfunction mergeUnder(\n\tcaller: Record<string, string> | undefined,\n\ttransport: Record<string, string>\n): Record<string, string> {\n\tconst taken = new Set(Object.keys(transport).map((name) => name.toLowerCase()));\n\tconst kept = Object.entries(caller ?? {}).filter(([name]) => !taken.has(name.toLowerCase()));\n\treturn { ...Object.fromEntries(kept), ...transport };\n}\n\nexport function generateRequestId(): string {\n\treturn `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n}\n","import { ComputeError, ErrorCodes } from '../errors';\n\nfunction getNodeBuffer(): typeof Buffer | undefined {\n\tconst buf = (globalThis as { Buffer?: typeof Buffer }).Buffer;\n\treturn typeof buf === 'function' ? buf : undefined;\n}\n\nexport function encodeStringToBase64(str: string): string {\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\treturn Buffer.from(str, 'utf-8').toString('base64');\n\t}\n\treturn base64ByteArray(new TextEncoder().encode(str));\n}\n\n/**\n * WHATWG forgiving-base64 normalization: strip ASCII whitespace, then drop trailing padding where\n * the spec allows it (total length a multiple of 4). `null` when the result can't be base64.\n */\nfunction normalizeBase64(str: string): string | null {\n\tconst data = stripBase64(str);\n\treturn data === null || /[^A-Za-z0-9+/]/.test(data) ? null : data;\n}\n\n/** {@link normalizeBase64} without the alphabet check, which the decoders make faster on their own. */\nfunction stripBase64(str: string): string | null {\n\tlet data = str.replace(/[\\t\\n\\f\\r ]/g, '');\n\tif (data.length % 4 === 0) data = data.replace(/={1,2}$/, '');\n\treturn data.length % 4 === 1 ? null : data;\n}\n\n/** Shortest string treated as base64 (≈ 48 bytes): human-written strings rarely reach it. */\nexport const BASE64_DETECT_MIN_LENGTH = 64;\n\nconst B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n/**\n * Whether an untyped string is base64 content: the canonical padded form when confident, else\n * `null` (treat it as plain text). A heuristic; pass a `Uint8Array` when it must be exact.\n */\nexport function detectBase64Payload(str: string): string | null {\n\tconst data = normalizeBase64(str);\n\tif (data === null || data.length < BASE64_DETECT_MIN_LENGTH) return null;\n\n\t// An encoder leaves the final character's unused low bits zero; otherwise re-encoding the\n\t// decoded bytes wouldn't reproduce the input.\n\tconst rem = data.length % 4;\n\tconst lastValue = B64_ALPHABET.indexOf(data.charAt(data.length - 1));\n\tif (rem === 2 && (lastValue & 0x0f) !== 0) return null;\n\tif (rem === 3 && (lastValue & 0x03) !== 0) return null;\n\n\treturn rem === 0 ? data : data + '=='.slice(0, 4 - rem);\n}\n\n/**\n * Decode base64 to bytes, normalizing and validating the same way in Node and browsers.\n * `packages/visualization/src/shared/encoding.ts` keeps a copy that throws its own error class;\n * change both together.\n *\n * @throws {ComputeError} `ENCODING_ERROR` if invalid, or `ENVIRONMENT_ERROR` if the runtime has no decoder\n */\nexport function decodeBase64ToBinary(base64File: string): Uint8Array {\n\tconst data = stripBase64(base64File);\n\tconst bytes = data === null ? null : decodeStripped(data);\n\tif (bytes === null) {\n\t\tthrow new ComputeError('Invalid base64 input.', ErrorCodes.ENCODING_ERROR, {\n\t\t\tcontext: { inputLength: base64File.length }\n\t\t});\n\t}\n\treturn bytes;\n}\n\n/** Decode {@link stripBase64} output; `null` when a character is outside the alphabet. */\nfunction decodeStripped(data: string): Uint8Array | null {\n\t// Buffer over atob in Node: faster, and skips the latin-1 string detour.\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\t// Buffer decodes leniently: `-` and `_` as base64url, a non-ASCII char as its low byte, and\n\t\t// any other stray char is skipped. Rule out the first two; a skip leaves the result short.\n\t\t// Several times faster than an alphabet regex over a multi-MB payload.\n\t\tif (Buffer.byteLength(data, 'utf8') !== data.length) return null;\n\t\tif (data.includes('-') || data.includes('_')) return null;\n\t\tconst decoded = Buffer.from(data, 'base64');\n\t\tif (decoded.length !== Math.floor((data.length * 3) / 4)) return null;\n\t\t// A small result is a view over Node's shared 8 KiB pool slab: copy it out, or anyone\n\t\t// touching `.buffer` sees unrelated pooled bytes. A large one owns its memory outright.\n\t\tconst ownsBuffer = decoded.byteOffset === 0 && decoded.buffer.byteLength === decoded.length;\n\t\treturn ownsBuffer ? new Uint8Array(decoded.buffer, 0, decoded.length) : new Uint8Array(decoded);\n\t}\n\tif (typeof globalThis.atob === 'function') {\n\t\tlet binary: string;\n\t\ttry {\n\t\t\tbinary = globalThis.atob(data);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t\tconst bytes = new Uint8Array(binary.length);\n\t\tfor (let i = 0; i < binary.length; i++) {\n\t\t\tbytes[i] = binary.charCodeAt(i) & 0xff;\n\t\t}\n\t\treturn bytes;\n\t}\n\n\tthrow new ComputeError(\n\t\t'Base64 decoding not supported in this environment.',\n\t\tErrorCodes.ENVIRONMENT_ERROR,\n\t\t{ context: { environmentInfo: 'atob or Buffer not available' } }\n\t);\n}\n\n/** UTF-8 byte length without allocating the encoded copy `TextEncoder.encode` would. */\nexport function utf8ByteLength(str: string): number {\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\treturn Buffer.byteLength(str, 'utf-8');\n\t}\n\tlet bytes = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst code = str.charCodeAt(i);\n\t\tif (code < 0x80) {\n\t\t\tbytes += 1;\n\t\t} else if (code < 0x800) {\n\t\t\tbytes += 2;\n\t\t} else if (\n\t\t\tcode >= 0xd800 &&\n\t\t\tcode <= 0xdbff &&\n\t\t\ti + 1 < str.length &&\n\t\t\t(str.charCodeAt(i + 1) & 0xfc00) === 0xdc00\n\t\t) {\n\t\t\t// Surrogate pair → one 4-byte code point; lone surrogates fall through\n\t\t\t// to 3 bytes (the replacement-character encoding TextEncoder emits).\n\t\t\tbytes += 4;\n\t\t\ti++;\n\t\t} else {\n\t\t\tbytes += 3;\n\t\t}\n\t}\n\treturn bytes;\n}\n\nexport function base64ByteArray(bytes: Uint8Array): string {\n\tconst Buffer = getNodeBuffer();\n\tif (Buffer) {\n\t\treturn Buffer.from(bytes).toString('base64');\n\t}\n\tif (typeof globalThis.btoa === 'function') {\n\t\t// Chunked so memory doesn't peak at ~3× the input. CHUNK must be a multiple of 3: every\n\t\t// non-final chunk then encodes without padding, and the joined pieces equal a one-shot\n\t\t// encode. 32766 = 3 × 10922, well under fromCharCode.apply's argument limit.\n\t\tconst CHUNK = 32766;\n\t\tconst parts: string[] = [];\n\t\tfor (let i = 0; i < bytes.length; i += CHUNK) {\n\t\t\tparts.push(\n\t\t\t\tglobalThis.btoa(\n\t\t\t\t\tString.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK) as unknown as number[])\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\treturn parts.join('');\n\t}\n\tthrow new ComputeError(\n\t\t'Base64 encoding not supported in this environment.',\n\t\tErrorCodes.ENVIRONMENT_ERROR,\n\t\t{ context: { environmentInfo: 'btoa or Buffer not available' } }\n\t);\n}\n","import { utf8ByteLength } from '../utils/encoding';\n\n/**\n * Cap on an error body: it is read for its `code` and message only. A 500 is read in full instead,\n * since a partial success carries the computed values.\n */\nexport const ERROR_BODY_MAX_BYTES = 1 << 20;\n\nexport interface BodyText {\n\ttext: string;\n\t/** The body passed the limit. `text` holds only what arrived before the cut. */\n\ttruncated: boolean;\n}\n\n/** Read a body as text, cancelling the download once it passes `limit` UTF-8 bytes. */\nexport async function readBodyText(response: Response, limit = Infinity): Promise<BodyText> {\n\tif (limit === Infinity) return { text: await response.text(), truncated: false };\n\n\t// A body without a stream can't be cut short: read it, then apply the limit.\n\tif (!response.body) {\n\t\tconst text = await response.text();\n\t\treturn { text, truncated: utf8ByteLength(text) > limit };\n\t}\n\n\t// Refuse a declared oversize body unread. Not when compressed: the header then counts the\n\t// compressed bytes, not the ones read.\n\tconst declared = Number(response.headers.get('Content-Length'));\n\tif (!response.headers.has('Content-Encoding') && declared > limit) {\n\t\tawait response.body.cancel().catch(() => {});\n\t\treturn { text: '', truncated: true };\n\t}\n\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst parts: string[] = [];\n\tlet received = 0;\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\treceived += value.byteLength;\n\t\tparts.push(decoder.decode(value, { stream: true }));\n\t\tif (received > limit) {\n\t\t\tawait reader.cancel().catch(() => {});\n\t\t\treturn { text: parts.join(''), truncated: true };\n\t\t}\n\t}\n\tparts.push(decoder.decode());\n\treturn { text: parts.join(''), truncated: false };\n}\n","import { getLogger } from '../utils/logger';\n\nimport type { ServerTiming } from '../types';\n\n/**\n * Parse a W3C Server Timing header, as the solve endpoint emits it:\n * `decode;dur=3, solve;dur=120, gh;dur=110, encode;dur=8, recomputed;desc=\"4/97\"`, or\n * `cache;desc=\"hit\"` when the server answered from its result cache. Null when it carries no\n * recognised metric.\n */\nexport function parseServerTiming(headerValue: string | null): ServerTiming | null {\n\tif (!headerValue) return null;\n\tconst timing: ServerTiming = { raw: headerValue };\n\tlet sawMetric = false;\n\tfor (const part of headerValue.split(',')) {\n\t\tconst [rawName, ...params] = part.trim().split(';');\n\t\tconst name = rawName.trim().toLowerCase();\n\t\tif (name === 'decode' || name === 'solve' || name === 'encode' || name === 'gh') {\n\t\t\tconst dur = Number(param(params, 'dur'));\n\t\t\tif (!Number.isFinite(dur)) continue;\n\t\t\ttiming[name] = dur;\n\t\t\tsawMetric = true;\n\t\t} else if (name === 'recomputed') {\n\t\t\tconst [ran, total] = (param(params, 'desc') ?? '').split('/').map(Number);\n\t\t\tif (!Number.isInteger(ran) || !Number.isInteger(total)) continue;\n\t\t\ttiming.recomputed = { ran, total };\n\t\t\tsawMetric = true;\n\t\t} else if (name === 'cache' && param(params, 'desc') === 'hit') {\n\t\t\ttiming.cacheHit = true;\n\t\t\tsawMetric = true;\n\t\t}\n\t}\n\treturn sawMetric ? timing : null;\n}\n\nfunction param(params: string[], key: string): string | undefined {\n\tfor (const p of params) {\n\t\tconst [k, ...v] = p.split('=');\n\t\tif (k.trim().toLowerCase() !== key) continue;\n\t\tconst value = v.join('=').trim();\n\t\treturn value.replace(/^\"(.*)\"$/, '$1');\n\t}\n\treturn undefined;\n}\n\nexport function fireServerTiming(\n\tresponse: Response,\n\trequestId: string,\n\tonServerTiming: ((timing: ServerTiming, requestId: string) => void) | undefined,\n\tdebug?: boolean\n): void {\n\tif (!onServerTiming) return;\n\tconst timing = parseServerTiming(response.headers.get('Server-Timing'));\n\tif (!timing) return;\n\ttry {\n\t\tonServerTiming(timing, requestId);\n\t} catch (err) {\n\t\tif (debug) getLogger().debug(` onServerTiming callback threw: ${err}`);\n\t}\n}\n","/** What fetch, or a body read, rejects with when its signal fires: a caller abort or a timeout. */\nexport function isAbortError(error: unknown): error is Error {\n\treturn error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError');\n}\n\n/**\n * Compose a caller-supplied AbortSignal with an optional timeout; `undefined` when neither was\n * given. `AbortSignal.timeout` rather than `setTimeout`: its timer isn't throttled in a hidden tab.\n */\nexport function composeSignal(\n\tcallerSignal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined\n): { signal: AbortSignal | undefined; cleanup: () => void } {\n\tconst noCleanup = () => {};\n\tif (typeof timeoutMs !== 'number' || timeoutMs <= 0) {\n\t\treturn { signal: callerSignal, cleanup: noCleanup };\n\t}\n\n\tconst timeoutSignal = AbortSignal.timeout(timeoutMs);\n\tif (!callerSignal) return { signal: timeoutSignal, cleanup: noCleanup };\n\n\t// Not AbortSignal.any: it can't unregister its link on the caller's signal, so a long-lived\n\t// signal reused across solves would collect one registration per attempt.\n\tconst ctrl = new AbortController();\n\tconst sources = [callerSignal, timeoutSignal];\n\t// Forwarding the reason keeps fetch's rejection name ('TimeoutError' vs 'AbortError').\n\tconst onAbort = function (this: AbortSignal) {\n\t\tctrl.abort(this.reason);\n\t};\n\n\tfor (const s of sources) {\n\t\tif (s.aborted) {\n\t\t\tctrl.abort(s.reason);\n\t\t\tbreak;\n\t\t}\n\t\ts.addEventListener('abort', onAbort, { once: true });\n\t}\n\n\treturn {\n\t\tsignal: ctrl.signal,\n\t\tcleanup: () => {\n\t\t\tfor (const s of sources) s.removeEventListener('abort', onAbort);\n\t\t}\n\t};\n}\n","/**\n * A response's JSON text length, so byte-budgeted caches never re-serialize a huge tree. A WeakMap\n * rather than a field keeps `JSON.stringify(response)` clean; it is keyed by identity, so a derived\n * copy must re-register. `.length` stands in for UTF-8 bytes: the JSON is near-pure ASCII.\n */\n\nconst wireSizes = new WeakMap<object, number>();\n\n/** Record `response`'s wire size. No-op for non-object/null values. */\nexport function setResponseWireSize(response: unknown, size: number): void {\n\tif (typeof response !== 'object' || response === null) return;\n\twireSizes.set(response, size);\n}\n\n/** The wire size recorded for `response`, or undefined when never registered. */\nexport function getResponseWireSize(response: unknown): number | undefined {\n\tif (typeof response !== 'object' || response === null) return undefined;\n\treturn wireSizes.get(response);\n}\n","import { ComputeError, ErrorCodes, type ErrorCode } from '../errors';\nimport { getLogger } from '../utils/logger';\nimport { readField } from '../utils/read-field';\nimport { fireServerTiming } from './server-timing';\nimport { ERROR_BODY_MAX_BYTES, readBodyText } from './read-body';\nimport { isAbortError } from './signal';\nimport { setResponseWireSize } from './wire-size';\n\nimport type { ComputeConfig } from '../types';\n\nexport interface RequestContext {\n\tfullUrl: string;\n\trequestId: string;\n\trequestSize: number;\n\tconfig: ComputeConfig;\n}\n\n/** Upper bound for the raw server body stored on error `context.responseBody`. */\nconst MAX_CONTEXT_BODY_CHARS = 4096;\n\nfunction truncateBody(body: string): string {\n\tif (body.length <= MAX_CONTEXT_BODY_CHARS) return body;\n\treturn `${body.slice(0, MAX_CONTEXT_BODY_CHARS)}… [truncated ${body.length - MAX_CONTEXT_BODY_CHARS} chars]`;\n}\n\n/**\n * `rawBody` is what came over the wire and goes on `context.responseBody`; `detail` may be a\n * message rewritten from it and only feeds the error message.\n */\nfunction throwHttpError(\n\tresponse: Response,\n\tctx: RequestContext,\n\trawBody: string,\n\tdetail: string,\n\tserverCode: string | undefined\n): never {\n\tconst { status, statusText } = response;\n\tconst { fullUrl, requestId, requestSize, config } = ctx;\n\n\t// fromEntries defines own keys, so a header named `__proto__` can't replace the prototype.\n\tconst responseHeaders = Object.fromEntries(response.headers.entries());\n\n\tconst trimmed = detail.trim();\n\tconst bodyHint = trimmed ? ` — ${trimmed.slice(0, 200)}${trimmed.length > 200 ? '…' : ''}` : '';\n\n\tconst context = {\n\t\turl: fullUrl,\n\t\trequestId,\n\t\tmethod: 'POST',\n\t\trequestSize,\n\t\tserverUrl: config.serverUrl,\n\t\tresponseBody: rawBody ? truncateBody(rawBody) : undefined,\n\t\tresponseHeaders\n\t};\n\n\tconst errorMap: Record<number, { message: string; code: ErrorCode }> = {\n\t\t400: {\n\t\t\tmessage: `Bad request: ${statusText}${bodyHint}`,\n\t\t\tcode: ErrorCodes.VALIDATION_ERROR\n\t\t},\n\t\t401: { message: `HTTP ${status}: ${statusText}${bodyHint}`, code: ErrorCodes.AUTH_ERROR },\n\t\t403: { message: `HTTP ${status}: ${statusText}${bodyHint}`, code: ErrorCodes.AUTH_ERROR },\n\t\t404: { message: `Endpoint not found: ${fullUrl}`, code: ErrorCodes.NOT_FOUND },\n\t\t413: {\n\t\t\tmessage: `Request too large: ${(requestSize / 1024).toFixed(2)}KB`,\n\t\t\tcode: ErrorCodes.VALIDATION_ERROR\n\t\t},\n\t\t429: { message: `Rate limit exceeded${bodyHint}`, code: ErrorCodes.RATE_LIMIT },\n\t\t500: { message: `Server error: ${statusText}${bodyHint}`, code: ErrorCodes.COMPUTATION_ERROR },\n\t\t502: {\n\t\t\tmessage: `Bad gateway: ${statusText}${bodyHint}`,\n\t\t\tcode: ErrorCodes.NETWORK_ERROR\n\t\t},\n\t\t503: {\n\t\t\tmessage: `Service unavailable: ${statusText}${bodyHint}`,\n\t\t\tcode: ErrorCodes.NETWORK_ERROR\n\t\t},\n\t\t504: {\n\t\t\tmessage: `Gateway timeout: ${statusText}${bodyHint}`,\n\t\t\tcode: ErrorCodes.NETWORK_ERROR\n\t\t}\n\t};\n\n\tconst error = errorMap[status] || {\n\t\tmessage: `HTTP ${status}: ${statusText}${bodyHint}`,\n\t\tcode: ErrorCodes.UNKNOWN_ERROR\n\t};\n\n\tconst codes = config.serverErrorCodes;\n\t// The code is server-supplied: `constructor` must not resolve through the prototype.\n\tconst serverMapped =\n\t\tserverCode && codes && Object.prototype.hasOwnProperty.call(codes, serverCode)\n\t\t\t? codes[serverCode]\n\t\t\t: undefined;\n\n\tthrow new ComputeError(error.message, serverMapped ?? error.code, {\n\t\tstatusCode: status,\n\t\tcontext\n\t});\n}\n\n/** A 3xx, or a browser's `redirect: 'manual'` stand-in for one, which hides the status. */\nexport function isRedirect(response: Response): boolean {\n\treturn response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400);\n}\n\n/** Following a redirect would send the API key header, which fetch keeps cross-origin, to the target. */\nfunction redirectError(response: Response, ctx: RequestContext): ComputeError {\n\tconst location = response.headers.get('Location') ?? undefined;\n\treturn new ComputeError(\n\t\t`Server redirected the request${location ? ` to ${location}` : ''}. Redirects are not followed, ` +\n\t\t\t'so credentials never leave serverUrl: set serverUrl to the address it redirects to.',\n\t\tErrorCodes.INVALID_CONFIG,\n\t\t{\n\t\t\t...(response.status > 0 && { statusCode: response.status }),\n\t\t\tcontext: { url: ctx.fullUrl, requestId: ctx.requestId, location }\n\t\t}\n\t);\n}\n\nfunction tooLargeError(response: Response, ctx: RequestContext): ComputeError {\n\tconst { maxResponseBytes } = ctx.config;\n\treturn new ComputeError(\n\t\t`Response body exceeded maxResponseBytes (${maxResponseBytes} bytes)`,\n\t\tErrorCodes.RESPONSE_TOO_LARGE,\n\t\t{\n\t\t\tstatusCode: response.status,\n\t\t\tcontext: { url: ctx.fullUrl, requestId: ctx.requestId, maxResponseBytes }\n\t\t}\n\t);\n}\n\nexport async function handleResponse(\n\tresponse: Response,\n\tctx: RequestContext,\n\tstartTime: number\n): Promise<unknown> {\n\tconst { requestId, config } = ctx;\n\tconst { debug, onServerTiming, maxResponseBytes = Infinity } = config;\n\tconst responseTime = Math.round(performance.now() - startTime);\n\n\tif (isRedirect(response)) {\n\t\tawait response.body?.cancel().catch(() => {});\n\t\tthrow redirectError(response, ctx);\n\t}\n\n\tif (!response.ok) {\n\t\tconst isServerError = response.status === 500;\n\t\tconst { text: rawBody, truncated } = await readBodyText(\n\t\t\tresponse,\n\t\t\tisServerError ? maxResponseBytes : Math.min(ERROR_BODY_MAX_BYTES, maxResponseBytes)\n\t\t);\n\t\tif (truncated && isServerError) throw tooLargeError(response, ctx);\n\n\t\tif (debug) {\n\t\t\tconst logger = getLogger();\n\t\t\tlogger.debug(\n\t\t\t\t`❌ Request [${requestId}] failed with HTTP ${response.status} in ${responseTime}ms`\n\t\t\t);\n\t\t\tlogger.debug(` URL: ${ctx.fullUrl}`);\n\t\t\tlogger.debug(` Status: ${response.status} ${response.statusText}`);\n\t\t\tif (rawBody) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t` Response body: ${rawBody.substring(0, 500)}${rawBody.length > 500 ? '...' : ''}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet parsed: any;\n\t\tlet isJson = !truncated;\n\t\tif (isJson) {\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(rawBody);\n\t\t\t} catch (e) {\n\t\t\t\tisJson = false;\n\t\t\t\tif (debug && isServerError) {\n\t\t\t\t\tgetLogger().debug(` Failed to parse error body as JSON: ${e}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Any error status can carry a `code`, not just 500.\n\t\tconst serverCode: string | undefined =\n\t\t\ttypeof parsed?.code === 'string' ? parsed.code : undefined;\n\t\tlet detail = rawBody;\n\n\t\tif (isServerError && isJson) {\n\t\t\t// Values plus errors or warnings: the solve ran, so this is a partial success.\n\t\t\tconst values = readField(parsed, 'values');\n\t\t\tconst solveErrors = readField<unknown[]>(parsed, 'errors');\n\t\t\tconst solveWarnings = readField<unknown[]>(parsed, 'warnings');\n\t\t\tif (values && (solveErrors || solveWarnings)) {\n\t\t\t\tsetResponseWireSize(parsed, rawBody.length);\n\t\t\t\tif (debug) {\n\t\t\t\t\tconst logger = getLogger();\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`⚠️ Request [${requestId}] completed with solver errors in ${responseTime}ms`\n\t\t\t\t\t);\n\t\t\t\t\tif (solveErrors && solveErrors.length > 0) {\n\t\t\t\t\t\tlogger.debug(` Errors: ${JSON.stringify(solveErrors, null, 2)}`);\n\t\t\t\t\t}\n\t\t\t\t\tif (solveWarnings && solveWarnings.length > 0) {\n\t\t\t\t\t\tlogger.debug(` Warnings: ${JSON.stringify(solveWarnings, null, 2)}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfireServerTiming(response, requestId, onServerTiming, debug);\n\t\t\t\treturn parsed;\n\t\t\t}\n\n\t\t\t// Compute8's exception handler sends `{ error, message: \"<category>: <detail>\",\n\t\t\t// stackTrace? }`; older servers sent PascalCase `Message`/`ExceptionType`/`StackTrace`.\n\t\t\tconst serverMessage =\n\t\t\t\t(typeof parsed?.message === 'string' && parsed.message) ||\n\t\t\t\t(typeof parsed?.Message === 'string' && parsed.Message) ||\n\t\t\t\t'';\n\t\t\tconst exceptionType =\n\t\t\t\t(typeof parsed?.ExceptionType === 'string' && parsed.ExceptionType) || '';\n\t\t\tconst stack = parsed?.stackTrace ?? parsed?.StackTrace;\n\t\t\tconst stackStr = Array.isArray(stack) ? stack.join('\\n') : stack || '';\n\n\t\t\tif (serverMessage) {\n\t\t\t\tconst prefix = exceptionType ? `${exceptionType}: ` : '';\n\t\t\t\tdetail = `${prefix}${serverMessage}${stackStr ? `\\n${stackStr}` : ''}`;\n\t\t\t} else if (parsed?.error) {\n\t\t\t\tdetail =\n\t\t\t\t\ttypeof parsed.error === 'string' ? parsed.error : JSON.stringify(parsed.error, null, 2);\n\t\t\t}\n\t\t}\n\n\t\tthrowHttpError(response, ctx, rawBody, detail, serverCode);\n\t}\n\n\tif (debug) getLogger().debug(`✅ Request [${requestId}] completed in ${responseTime}ms`);\n\n\tfireServerTiming(response, requestId, onServerTiming, debug);\n\n\ttry {\n\t\t// Text, then parse, so the wire size rides along for byte-budgeted caches.\n\t\tconst { text: rawBody, truncated } = await readBodyText(response, maxResponseBytes);\n\t\tif (truncated) throw tooLargeError(response, ctx);\n\t\tconst parsed = JSON.parse(rawBody);\n\t\tsetResponseWireSize(parsed, rawBody.length);\n\t\treturn parsed;\n\t} catch (error) {\n\t\t// The signal stays armed while the body streams: an abort here is the caller's or the\n\t\t// timeout's, not a truncated body.\n\t\tif (isAbortError(error) || error instanceof ComputeError) throw error;\n\t\t// A declared non-JSON body (captive portal, proxy login page) comes back identical on a\n\t\t// retry. Anything else that fails to parse was most likely cut off mid-stream.\n\t\tconst contentType = (response.headers.get('Content-Type') ?? '').toLowerCase();\n\t\tconst declaredNonJson = contentType !== '' && !contentType.includes('json');\n\t\tif (declaredNonJson) {\n\t\t\tthrow new ComputeError(\n\t\t\t\t`Server returned a non-JSON response (Content-Type: ${contentType}) — check the server URL / proxy configuration`,\n\t\t\t\tErrorCodes.INVALID_RESPONSE,\n\t\t\t\t{\n\t\t\t\t\tstatusCode: response.status,\n\t\t\t\t\tcontext: { url: ctx.fullUrl, requestId, contentType },\n\t\t\t\t\toriginalError: error\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\tthrow new ComputeError('Failed to parse JSON response', ErrorCodes.NETWORK_ERROR, {\n\t\t\tstatusCode: response.status,\n\t\t\tcontext: { url: ctx.fullUrl, requestId },\n\t\t\toriginalError: error\n\t\t});\n\t}\n}\n","/**\n * Why a liveness probe failed, and whether waiting can change the answer.\n *\n * `retryable` is the whole point: a powered-off VM and a booting one both read\n * as \"offline\" from a single probe, but only one of them will ever come up. A\n * caller that can't tell them apart has to assume the optimistic case and burn\n * its full retry window on a machine that is simply off.\n */\nexport type ProbeVerdict =\n\t/** Nothing is listening on the port — the host answered, and said no. */\n\t| 'refused'\n\t/** The hostname does not resolve. */\n\t| 'dns'\n\t/** No answer within the timeout: a booting host swallows packets rather than refusing them. */\n\t| 'timeout'\n\t/** The server answered, but rejected the probe's credentials (401/403). */\n\t| 'unauthorized'\n\t/** The server answered with a non-2xx that isn't an auth rejection. */\n\t| 'http_error'\n\t/** Connection failed in a way we can't attribute. */\n\t| 'unknown';\n\nexport interface ProbeFailure {\n\tverdict: ProbeVerdict;\n\t/** Whether retrying the same probe could plausibly succeed later. */\n\tretryable: boolean;\n\t/** Operator-facing sentence. Never includes the API key. */\n\tsummary: string;\n}\n\n/**\n * `ECONNREFUSED` arrives spelled differently depending on the runtime and how\n * many layers wrapped it: Node puts the code on the error, undici nests it under\n * `cause`, and browsers collapse everything into an opaque \"Failed to fetch\".\n * Matching the stringified probe error covers all of them without the caller\n * having to normalize first.\n */\nconst REFUSED_PATTERN = /ECONNREFUSED|ERR_CONNECTION_REFUSED|connection refused/i;\nconst DNS_PATTERN = /ENOTFOUND|EAI_AGAIN|getaddrinfo|ERR_NAME_NOT_RESOLVED|dns/i;\nconst TIMEOUT_PATTERN =\n\t/TimeoutError|ETIMEDOUT|ECONNRESET|EHOSTUNREACH|ENETUNREACH|abort|timed? ?out/i;\n\n/**\n * Classify a {@link ComputeServerStats.probeServer} result.\n *\n * A DNS failure is treated as retryable even though a missing record won't fix\n * itself: `EAI_AGAIN` is a *timeout* talking to the resolver, and the two are\n * not distinguishable from the error alone. Retrying a genuinely-wrong hostname\n * costs one window; giving up on a resolver hiccup breaks a server that is fine.\n *\n * @param probe - The `{ online, status?, error? }` a probe returned. An `online`\n * probe is not a failure and yields `null`.\n */\nexport function classifyProbeFailure(probe: {\n\tonline: boolean;\n\tstatus?: number;\n\terror?: string;\n}): ProbeFailure | null {\n\tif (probe.online) return null;\n\n\tif (probe.status !== undefined) {\n\t\tif (probe.status === 401 || probe.status === 403) {\n\t\t\treturn {\n\t\t\t\tverdict: 'unauthorized',\n\t\t\t\tretryable: false,\n\t\t\t\tsummary: `The server rejected the liveness probe with HTTP ${probe.status} — check the API key.`\n\t\t\t};\n\t\t}\n\t\t// 5xx from a proxy in front of a starting child does clear on its own;\n\t\t// a 4xx means we asked for something this server will never serve.\n\t\tconst retryable = probe.status >= 500;\n\t\treturn {\n\t\t\tverdict: 'http_error',\n\t\t\tretryable,\n\t\t\tsummary: retryable\n\t\t\t\t? `The server answered HTTP ${probe.status} — it may still be starting up.`\n\t\t\t\t: `The server answered HTTP ${probe.status}, which will not change on retry.`\n\t\t};\n\t}\n\n\tconst error = probe.error ?? '';\n\n\tif (REFUSED_PATTERN.test(error)) {\n\t\treturn {\n\t\t\tverdict: 'refused',\n\t\t\tretryable: false,\n\t\t\tsummary:\n\t\t\t\t'Connection refused — the host is reachable but nothing is listening on that port. ' +\n\t\t\t\t'Rhino.Compute is not running, or the URL names the wrong port.'\n\t\t};\n\t}\n\n\tif (DNS_PATTERN.test(error)) {\n\t\treturn {\n\t\t\tverdict: 'dns',\n\t\t\tretryable: true,\n\t\t\tsummary: 'The server hostname could not be resolved — check the URL and DNS.'\n\t\t};\n\t}\n\n\tif (TIMEOUT_PATTERN.test(error)) {\n\t\treturn {\n\t\t\tverdict: 'timeout',\n\t\t\tretryable: true,\n\t\t\tsummary:\n\t\t\t\t'No response before the timeout — the machine may be powered off, blocked by a ' +\n\t\t\t\t'firewall, or still booting.'\n\t\t};\n\t}\n\n\treturn {\n\t\tverdict: 'unknown',\n\t\tretryable: true,\n\t\tsummary: error ? `The connection failed: ${error}` : 'The connection failed.'\n\t};\n}\n","import type { RetryPolicy } from '../types';\n\nconst DEFAULT_RETRY: Required<RetryPolicy> = {\n\tattempts: 0,\n\tbaseDelayMs: 500,\n\tmaxDelayMs: 30_000,\n\tretryOn429: true\n};\n\nconst RETRYABLE_STATUS = new Set([502, 503, 504]);\n\n/**\n * Ceiling on a server-sent `Retry-After` wait. The server's window outranks\n * `RetryPolicy.maxDelayMs` (retrying sooner all but guarantees another 429), but a bad or hostile\n * header must not park the client for minutes. A `maxDelayMs` above this cap raises it.\n */\nconst RETRY_AFTER_CAP_MS = 60_000;\n\nexport function resolveRetryPolicy(policy: RetryPolicy | undefined): Required<RetryPolicy> {\n\tif (!policy) return DEFAULT_RETRY;\n\treturn {\n\t\tattempts: policy.attempts ?? DEFAULT_RETRY.attempts,\n\t\tbaseDelayMs: policy.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,\n\t\tmaxDelayMs: policy.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,\n\t\tretryOn429: policy.retryOn429 ?? DEFAULT_RETRY.retryOn429\n\t};\n}\n\nexport function isRetryableStatus(status: number, policy: Required<RetryPolicy>): boolean {\n\treturn RETRYABLE_STATUS.has(status) || (policy.retryOn429 && status === 429);\n}\n\n/** Delay before the next attempt: the server's `Retry-After` when it sent one, else backoff. */\nexport function retryDelay(\n\tretryAfter: string | null,\n\tattempt: number,\n\tpolicy: Required<RetryPolicy>\n): number {\n\tconst retryAfterMs = parseRetryAfter(retryAfter);\n\treturn retryAfterMs === null\n\t\t? backoffDelay(attempt, policy)\n\t\t: Math.min(retryAfterMs, Math.max(policy.maxDelayMs, RETRY_AFTER_CAP_MS));\n}\n\n/** A `Retry-After` value (seconds or HTTP-date) in ms; null when missing or unparseable. */\nfunction parseRetryAfter(headerValue: string | null): number | null {\n\tif (!headerValue) return null;\n\tconst seconds = Number(headerValue);\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;\n\tconst dateMs = Date.parse(headerValue);\n\tif (Number.isFinite(dateMs)) {\n\t\tconst delta = dateMs - Date.now();\n\t\treturn delta > 0 ? delta : 0;\n\t}\n\treturn null;\n}\n\nexport function backoffDelay(attempt: number, policy: Required<RetryPolicy>): number {\n\tconst exponential = policy.baseDelayMs * Math.pow(2, attempt);\n\tconst jitter = Math.random() * policy.baseDelayMs;\n\treturn Math.min(exponential + jitter, policy.maxDelayMs);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new DOMException('Aborted', 'AbortError'));\n\t\t\treturn;\n\t\t}\n\t\tconst id = setTimeout(() => {\n\t\t\tsignal?.removeEventListener('abort', onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(id);\n\t\t\treject(new DOMException('Aborted', 'AbortError'));\n\t\t};\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t});\n}\n","import { ComputeError, ErrorCodes } from '../errors';\nimport { utf8ByteLength } from '../utils/encoding';\nimport { getLogger } from '../utils/logger';\nimport { validateServerUrl } from '../server/validate-server-url';\nimport { ERROR_BODY_MAX_BYTES, readBodyText } from './read-body';\nimport { buildUrl, buildHeaders, generateRequestId } from './request';\nimport { handleResponse, type RequestContext } from './response';\nimport { backoffDelay, isRetryableStatus, resolveRetryPolicy, retryDelay, sleep } from './retry';\nimport { composeSignal, isAbortError } from './signal';\n\nimport type { ComputeConfig, RetryPolicy } from '../types';\n\ninterface AttemptContext extends RequestContext {\n\tendpoint: string;\n\tbody: string;\n\theaders: HeadersInit;\n\terrorContext: Record<string, unknown>;\n}\n\ntype AttemptOutcome =\n\t| { ok: true; value: unknown }\n\t| { ok: false; cause: ComputeError; retryable: boolean; delayMs?: number };\n\nfunction abortedByCaller(ctx: AttemptContext, originalError: Error): ComputeError {\n\treturn new ComputeError('Request aborted by caller', ErrorCodes.ABORTED, {\n\t\tcontext: ctx.errorContext,\n\t\toriginalError\n\t});\n}\n\nasync function attemptFetch(\n\tctx: AttemptContext,\n\tpolicy: Required<RetryPolicy>,\n\tattempt: number,\n\tisFinal: boolean\n): Promise<AttemptOutcome> {\n\tconst { signal, cleanup } = composeSignal(ctx.config.signal, ctx.config.timeoutMs);\n\tconst startTime = performance.now();\n\n\ttry {\n\t\tconst response = await fetch(ctx.fullUrl, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: ctx.body,\n\t\t\theaders: ctx.headers,\n\t\t\tsignal,\n\t\t\t// handleResponse rejects a redirect rather than let the API key follow it.\n\t\t\tredirect: 'manual'\n\t\t});\n\n\t\tif (!isFinal && isRetryableStatus(response.status, policy)) {\n\t\t\t// Drain so the connection can be reused. The final attempt skips this branch so\n\t\t\t// handleResponse reads the body into the error instead.\n\t\t\tawait readBodyText(response, ERROR_BODY_MAX_BYTES).catch(() => {});\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tretryable: true,\n\t\t\t\tdelayMs: retryDelay(response.headers.get('Retry-After'), attempt, policy),\n\t\t\t\tcause: new ComputeError(\n\t\t\t\t\t`HTTP ${response.status} ${response.statusText} (will retry)`,\n\t\t\t\t\tresponse.status === 429 ? ErrorCodes.RATE_LIMIT : ErrorCodes.NETWORK_ERROR,\n\t\t\t\t\t{ statusCode: response.status, context: ctx.errorContext }\n\t\t\t\t)\n\t\t\t};\n\t\t}\n\n\t\treturn { ok: true, value: await handleResponse(response, ctx, startTime) };\n\t} catch (error) {\n\t\treturn classifyFetchError(error, ctx);\n\t} finally {\n\t\tcleanup();\n\t}\n}\n\nfunction classifyFetchError(error: unknown, ctx: AttemptContext): AttemptOutcome {\n\tif (isAbortError(error)) {\n\t\tif (ctx.config.signal?.aborted) {\n\t\t\treturn { ok: false, retryable: false, cause: abortedByCaller(ctx, error) };\n\t\t}\n\t\t// With no timeout armed, the runtime aborted on its own (e.g. an undici socket teardown):\n\t\t// as transient as a network drop, but not a timeout.\n\t\tconst { timeoutMs } = ctx.config;\n\t\tconst cause =\n\t\t\ttypeof timeoutMs === 'number' && timeoutMs > 0\n\t\t\t\t? new ComputeError(`Request timed out after ${timeoutMs}ms`, ErrorCodes.TIMEOUT_ERROR, {\n\t\t\t\t\t\tcontext: { ...ctx.errorContext, timeoutMs },\n\t\t\t\t\t\toriginalError: error\n\t\t\t\t\t})\n\t\t\t\t: new ComputeError(\n\t\t\t\t\t\t`Request aborted by the runtime (${error.name}): ${error.message}`,\n\t\t\t\t\t\tErrorCodes.NETWORK_ERROR,\n\t\t\t\t\t\t{ context: ctx.errorContext, originalError: error }\n\t\t\t\t\t);\n\t\treturn { ok: false, retryable: true, cause };\n\t}\n\n\tif (error instanceof TypeError) {\n\t\t// In a browser an opaque fetch TypeError is most often CORS, which the browser hides by\n\t\t// design. An offline network throws the same error, so it stays retryable either way.\n\t\tconst inBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';\n\t\tconst cause = inBrowser\n\t\t\t? new ComputeError(\n\t\t\t\t\t`Request failed: ${error.message} — in a browser this usually means a CORS misconfiguration on the server (or a network failure; the browser does not distinguish them)`,\n\t\t\t\t\tErrorCodes.CORS_ERROR,\n\t\t\t\t\t{ context: ctx.errorContext, originalError: error }\n\t\t\t\t)\n\t\t\t: new ComputeError(`Network error: ${error.message}`, ErrorCodes.NETWORK_ERROR, {\n\t\t\t\t\tcontext: ctx.errorContext,\n\t\t\t\t\toriginalError: error\n\t\t\t\t});\n\t\treturn { ok: false, retryable: true, cause };\n\t}\n\n\tif (error instanceof ComputeError) {\n\t\t// From handleResponse: a 2xx NETWORK_ERROR is a body cut off mid-stream.\n\t\tconst status = error.statusCode;\n\t\tconst truncatedSuccess =\n\t\t\terror.code === ErrorCodes.NETWORK_ERROR &&\n\t\t\tstatus !== undefined &&\n\t\t\tstatus >= 200 &&\n\t\t\tstatus < 300;\n\t\treturn { ok: false, retryable: truncatedSuccess, cause: error };\n\t}\n\n\treturn {\n\t\tok: false,\n\t\tretryable: false,\n\t\tcause: new ComputeError(\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\tErrorCodes.UNKNOWN_ERROR,\n\t\t\t{\n\t\t\t\tcontext: ctx.errorContext,\n\t\t\t\toriginalError: error\n\t\t\t}\n\t\t)\n\t};\n}\n\n/**\n * POST `args` as JSON to `<serverUrl>/<endpoint>` with retry, timeout and abort handling, and\n * return the parsed response. Most callers want a higher-level API.\n *\n * `R` names the expected response shape; nothing checks it, so the default `unknown` forces a\n * narrowing.\n *\n * @example\n * const response = await fetchCompute('grasshopper', args, {\n * serverUrl: 'https://my-server.com',\n * timeoutMs: 30_000,\n * retry: { attempts: 2 },\n * signal: controller.signal\n * });\n */\nexport async function fetchCompute<R = unknown>(\n\tendpoint: string,\n\targs: Record<string, any>,\n\tconfig: ComputeConfig\n): Promise<R> {\n\tconst requestId = generateRequestId();\n\t// Fail a circular or BigInt payload as INVALID_INPUT, before anything touches the network.\n\tlet body: string;\n\ttry {\n\t\tbody = JSON.stringify(args);\n\t} catch (error) {\n\t\tthrow new ComputeError(\n\t\t\t`Request body is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\tErrorCodes.INVALID_INPUT,\n\t\t\t{\n\t\t\t\tcontext: { endpoint, requestId },\n\t\t\t\toriginalError: error\n\t\t\t}\n\t\t);\n\t}\n\t// UTF-8 bytes: `body.length` counts UTF-16 code units and undercounts non-ASCII payloads.\n\tconst requestSize = utf8ByteLength(body);\n\t// Shape only: which hosts to block is the backend's call, made where its client is built.\n\tconst serverUrl = validateServerUrl(config.serverUrl, { blockedHosts: [] });\n\tconst { maxResponseBytes } = config;\n\tif (maxResponseBytes !== undefined && !(maxResponseBytes >= 0)) {\n\t\tthrow new ComputeError(\n\t\t\t`maxResponseBytes must be a number of bytes, got ${maxResponseBytes}`,\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { maxResponseBytes } }\n\t\t);\n\t}\n\tconst fullUrl = buildUrl(endpoint, serverUrl);\n\tconst headers = buildHeaders(requestId, config);\n\tconst retryPolicy = resolveRetryPolicy(config.retry);\n\tconst totalAttempts = retryPolicy.attempts + 1;\n\n\tif (config.debug) {\n\t\tconst sizeKb = (requestSize / 1024).toFixed(2);\n\t\tconst emoji = requestSize > 100000 ? '⚠️' : '🚀';\n\t\tgetLogger().debug(\n\t\t\t`${emoji} Starting compute request [${requestId}]: ${endpoint} (${sizeKb}KB)`\n\t\t);\n\t}\n\n\tconst ctx: AttemptContext = {\n\t\tendpoint,\n\t\tbody,\n\t\trequestSize,\n\t\tfullUrl,\n\t\trequestId,\n\t\theaders,\n\t\tconfig,\n\t\terrorContext: { serverUrl, url: fullUrl, endpoint, requestId, requestSize }\n\t};\n\n\tfor (let attempt = 0; ; attempt++) {\n\t\tconst isFinal = attempt >= retryPolicy.attempts;\n\t\tconst result = await attemptFetch(ctx, retryPolicy, attempt, isFinal);\n\n\t\tif (result.ok) return result.value as R;\n\t\tif (isFinal || !result.retryable) throw result.cause;\n\n\t\tconst delayMs = result.delayMs ?? backoffDelay(attempt, retryPolicy);\n\t\tif (config.debug) {\n\t\t\tgetLogger().debug(\n\t\t\t\t`🔁 Request [${requestId}] retrying after ${delayMs}ms (attempt ${attempt + 2}/${totalAttempts}): ${result.cause.message}`\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tawait sleep(delayMs, config.signal);\n\t\t} catch {\n\t\t\tthrow abortedByCaller(ctx, result.cause);\n\t\t}\n\t}\n}\n","/** A definition identified by a stable key, with its bytes loaded only when an upload is unavoidable. */\nexport interface DefinitionRef {\n\t/**\n\t * Identity of immutable bytes, such as a version UUID; never a mutable name or path. Every\n\t * cache trusts the key without looking at the bytes, so two contents sharing a key are served\n\t * each other's solves, with no diagnostic.\n\t */\n\tkey: string;\n\t/** Called when the solve runs, not when it is scheduled: a solve aborted while queued never loads. */\n\tload: () => Promise<Uint8Array>;\n}\n\n/**\n * A URL is sent as a pointer, a string is uploaded as base64 (or encoded text), bytes are\n * uploaded as-is. Strings and bytes are content-hashed for caching; a `DefinitionRef` is keyed\n * by its `key`.\n */\nexport type SolveDefinition = string | Uint8Array | DefinitionRef;\n\nexport function isDefinitionRef(definition: SolveDefinition): definition is DefinitionRef {\n\treturn (\n\t\ttypeof definition === 'object' &&\n\t\t!(definition instanceof Uint8Array) &&\n\t\ttypeof definition.key === 'string' &&\n\t\ttypeof definition.load === 'function'\n\t);\n}\n\n/** A URL is already a server-side reference: it is never uploaded or keyed by server cache key. */\nexport function isDefinitionUrl(definition: SolveDefinition): definition is string {\n\treturn typeof definition === 'string' && /^https?:\\/\\//i.test(definition);\n}\n","import type { FileData } from './types';\n\n/**\n * The `Sub Folder` convention, as authored on Selva's Grasshopper file components.\n *\n * `::` nests, matching Rhino's layer separator (`ROOT::Panels`), and `/` and `\\` are accepted\n * because people type them out of habit. The plugin normalizes to `/` before sending, so these\n * helpers also cover payloads from an older plugin that still emit `::`.\n *\n * The first segment is the **root**, which names the archive rather than becoming a folder inside\n * it: `ROOT::Panels` downloads as `ROOT.zip` containing `Panels/…`. Files sharing a root travel\n * together; distinct roots produce separate archives.\n */\n\n/**\n * Split a `Sub Folder` value into folder segments; `/`, `\\` and `::` all separate.\n *\n * Exported because anything rendering a folder tree has to agree with the archive on where the\n * boundaries are — splitting on `/` alone leaves `Main::Panels` as one literal segment and shows\n * a folder named after the separator.\n */\nexport const subFolderSegments = (subFolder: string | undefined): string[] =>\n\t(subFolder ?? '')\n\t\t.replace(/::/g, '/')\n\t\t.replace(/\\\\/g, '/')\n\t\t.split('/')\n\t\t.map((segment) => segment.trim())\n\t\t.filter((segment) => segment !== '');\n\n/** First `Sub Folder` segment, or `''` when the file sits at the top level. */\nconst rootOf = (file: Pick<FileData, 'subFolder'>): string =>\n\tsubFolderSegments(file.subFolder)[0] ?? '';\n\n/**\n * Everything below the root, as a `/`-joined path. The root names the archive, so repeating it\n * inside would nest it twice.\n */\nexport const pathBelowRoot = (file: Pick<FileData, 'subFolder'>): string =>\n\tsubFolderSegments(file.subFolder).slice(1).join('/');\n\n/**\n * Group files by their `Sub Folder` root, preserving encounter order.\n *\n * Rootless files group under `''` — a caller naming archives supplies its own fallback for that\n * bucket. Useful beyond downloading: a consumer writing to disk gets the same grouping without\n * touching the DOM.\n */\nexport const groupFilesByRoot = <T extends Pick<FileData, 'subFolder'>>(\n\tfiles: readonly T[]\n): Array<{ root: string; files: T[] }> => {\n\tconst groups = new Map<string, T[]>();\n\n\tfor (const file of files) {\n\t\tconst root = rootOf(file);\n\t\tconst bucket = groups.get(root);\n\t\tif (bucket) bucket.push(file);\n\t\telse groups.set(root, [file]);\n\t}\n\n\treturn Array.from(groups, ([root, grouped]) => ({ root, files: grouped }));\n};\n\n/**\n * Make a root usable as a download filename: path separators and the characters Windows forbids\n * would otherwise reach the download attribute verbatim and produce a broken or misdirected save.\n */\nexport const toArchiveName = (root: string): string => {\n\tconst safe = root\n\t\t.replace(/[/\\\\:*?\"<>|]/g, '_')\n\t\t.replace(/\\s+/g, ' ')\n\t\t.trim()\n\t\t.replace(/^\\.+/, '');\n\n\treturn safe === '' ? 'files' : safe;\n};\n","import { ComputeError, ErrorCodes } from '@/core/errors';\nimport { getLogger } from '@/core/utils/logger';\nimport { decodeBase64ToBinary } from '@/core/utils/encoding';\nimport { readField } from '@/core/utils/read-field';\n\nimport { DownloadOptions, FileBaseInfo, FileData, ProcessedFile } from './types';\nimport { groupFilesByRoot, pathBelowRoot, subFolderSegments, toArchiveName } from './sub-folder';\n\n/**\n * Decode the files in a compute response, plus any external `additionalFiles`, without\n * downloading them. Never throws: an unnamed or undecodable item and a failed external fetch are\n * logged and dropped, the rest still resolve. Colliding archive paths are renamed\n * (`model.txt` → `model-2.txt`), so a consumer keying by `path` loses nothing.\n */\nexport const extractFilesFromComputeResponse = async (\n\tdownloadableFiles: FileData[],\n\tadditionalFiles: FileBaseInfo[] | FileBaseInfo | null = null\n): Promise<ProcessedFile[]> => {\n\tconst processedFiles = decodeResponseFiles(downloadableFiles);\n\n\tif (additionalFiles) {\n\t\tconst filesArray = Array.isArray(additionalFiles) ? additionalFiles : [additionalFiles];\n\t\tprocessedFiles.push(...(await fetchRemoteFiles(filesArray)));\n\t}\n\n\tconst taken = new Set<string>();\n\treturn processedFiles.map((file) => {\n\t\tconst path = uniqueArchivePath(file.path, taken);\n\t\ttaken.add(path);\n\t\tif (path === file.path) return file;\n\t\tgetLogger().warn(`Duplicate archive path \"${file.path}\" — storing as \"${path}\".`);\n\t\treturn { ...file, path, fileName: path.slice(path.lastIndexOf('/') + 1) };\n\t});\n};\n\n/** Downloads files from a compute response as a ZIP archive. */\nexport const downloadFileData = async (\n\tdownloadableFiles: FileData[],\n\tfileFoldername: string,\n\tadditionalFiles: FileBaseInfo[] | FileBaseInfo | null = null,\n\toptions: DownloadOptions = {}\n): Promise<void> => {\n\tif (typeof document === 'undefined' || typeof Blob === 'undefined') {\n\t\tthrow new ComputeError(\n\t\t\t'File download functionality is only available in browser environments. This function requires the DOM API (document, Blob).',\n\t\t\tErrorCodes.BROWSER_ONLY,\n\t\t\t{\n\t\t\t\tcontext: {\n\t\t\t\t\tenvironment: typeof window !== 'undefined' ? 'browser (SSR)' : 'Node.js',\n\t\t\t\t\tdocumentAvailable: typeof document !== 'undefined',\n\t\t\t\t\tblobAvailable: typeof Blob !== 'undefined'\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\ttry {\n\t\tconst processedFiles = await extractFilesFromComputeResponse(\n\t\t\tdownloadableFiles,\n\t\t\tadditionalFiles\n\t\t);\n\t\tawait createAndDownloadZip(processedFiles, fileFoldername, options.saveBlob ?? saveFile);\n\t} catch (err) {\n\t\tif (err instanceof ComputeError) {\n\t\t\tthrow err;\n\t\t}\n\t\tthrow new ComputeError(\n\t\t\t'Failed to download files from compute response',\n\t\t\tErrorCodes.INVALID_STATE,\n\t\t\t{\n\t\t\t\tcontext: { originalError: err instanceof Error ? err.message : String(err) },\n\t\t\t\toriginalError: err\n\t\t\t}\n\t\t);\n\t}\n};\n\n/**\n * Download one archive per `Sub Folder` root (see {@link groupFilesByRoot}). Files with no root go\n * into `fallbackName`, so a definition that never sets `Sub Folder` downloads as one archive.\n *\n * Archives are saved one at a time: browsers discard concurrent downloads issued in the same tick.\n * More than one save per gesture may prompt the user for permission.\n *\n * @param downloadableFiles - `FileData` items from the compute response.\n * @param fallbackName - Archive name for files with no `Sub Folder` root.\n * @param additionalFiles - Extra files to package; they carry no root and join the fallback archive.\n * @param options - `saveBlob` replaces the anchor download for every archive.\n */\nexport const downloadFileDataByRoot = async (\n\tdownloadableFiles: FileData[],\n\tfallbackName: string,\n\tadditionalFiles: FileBaseInfo[] | FileBaseInfo | null = null,\n\toptions: DownloadOptions = {}\n): Promise<void> => {\n\tconst groups = groupFilesByRoot(downloadableFiles);\n\n\t// Extras alone still deserve an archive; without this they'd be dropped for having no root.\n\tif (groups.length === 0) {\n\t\tawait downloadFileData([], fallbackName, additionalFiles, options);\n\t\treturn;\n\t}\n\n\t// Extras belong to the definition rather than to any one root, so they ride with the rootless\n\t// archive — or with the first one when every file is rooted, rather than being dropped.\n\tconst extrasRoot = (groups.find((group) => group.root === '') ?? groups[0]).root;\n\n\tfor (const { root, files } of groups) {\n\t\tawait downloadFileData(\n\t\t\troot === '' ? files : files.map((file) => ({ ...file, subFolder: pathBelowRoot(file) })),\n\t\t\troot === '' ? fallbackName : toArchiveName(root),\n\t\t\troot === extrasRoot ? additionalFiles : null,\n\t\t\toptions\n\t\t);\n\t}\n};\n\n/**\n * Zip-slip defense for a server-controlled path: drop `.`, `..` and drive-letter segments so no\n * entry escapes the extraction directory. `''` when nothing safe remains.\n */\nconst sanitizeArchivePath = (raw: string): string =>\n\tsubFolderSegments(raw)\n\t\t.filter((segment) => segment !== '.' && segment !== '..' && !/^[a-zA-Z]:$/.test(segment))\n\t\t.join('/');\n\n/** The `isBase64Encoded` wire flag. Some server branches send booleans as strings (`\"True\"`). */\nexport const isBase64Flag = (flag: unknown): boolean =>\n\tflag === true || (typeof flag === 'string' && flag.trim().toLowerCase() === 'true');\n\nconst decodeResponseFiles = (dataItems: FileData[]): ProcessedFile[] => {\n\tconst processedFiles: ProcessedFile[] = [];\n\n\tdataItems.forEach((item) => {\n\t\tconst rawName = readField<string>(item, 'fileName') ?? '';\n\t\tconst rawType = readField<string>(item, 'fileType') ?? '';\n\t\tconst fileName = sanitizeArchivePath(`${rawName}${rawType}`);\n\t\tif (fileName === '') {\n\t\t\tgetLogger().warn(\n\t\t\t\t`Skipping file with unusable name \"${rawName}${rawType}\": no safe archive path remains after sanitization.`\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst rawSubFolder = readField<string>(item, 'subFolder');\n\t\tconst subFolder = rawSubFolder ? sanitizeArchivePath(rawSubFolder) : '';\n\t\tconst filePath = subFolder !== '' ? `${subFolder}/${fileName}` : fileName;\n\n\t\tconst data = readField<unknown>(item, 'data');\n\t\tif (typeof data !== 'string' || data === '') {\n\t\t\tgetLogger().warn(`Skipping file \"${filePath}\": item carries no usable data.`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst metadata = readField<Record<string, string>>(item, 'metadata');\n\n\t\tif (isBase64Flag(readField<unknown>(item, 'isBase64Encoded'))) {\n\t\t\ttry {\n\t\t\t\tprocessedFiles.push({\n\t\t\t\t\tfileName,\n\t\t\t\t\tcontent: decodeBase64ToBinary(data),\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tsubFolder,\n\t\t\t\t\t...(metadata ? { metadata } : {})\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tgetLogger().warn(`Skipping file \"${filePath}\": base64 decode failed.`, err);\n\t\t\t}\n\t\t} else {\n\t\t\tprocessedFiles.push({\n\t\t\t\tfileName,\n\t\t\t\tcontent: data,\n\t\t\t\tpath: filePath,\n\t\t\t\tsubFolder,\n\t\t\t\t...(metadata ? { metadata } : {})\n\t\t\t});\n\t\t}\n\t});\n\n\treturn processedFiles;\n};\n\n/** One dead URL must degrade the batch, not stall it forever. */\nconst REMOTE_FILE_TIMEOUT_MS = 30_000;\n\nconst fetchRemoteFiles = async (refs: FileBaseInfo[]): Promise<ProcessedFile[]> => {\n\tconst fetched = await Promise.all(\n\t\trefs.map(async (file) => {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(file.filePath, {\n\t\t\t\t\tsignal: AbortSignal.timeout(REMOTE_FILE_TIMEOUT_MS)\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tgetLogger().warn(`Failed to fetch additional file from URL: ${file.filePath}`);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst arrayBuffer = await response.arrayBuffer();\n\t\t\t\tconst safeName = sanitizeArchivePath(file.fileName);\n\t\t\t\tif (safeName === '') {\n\t\t\t\t\tgetLogger().warn(`Skipping fetched file with unusable name: ${file.fileName}`);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst subFolder = file.subFolder ? sanitizeArchivePath(file.subFolder) : '';\n\t\t\t\treturn {\n\t\t\t\t\tfileName: safeName,\n\t\t\t\t\tcontent: new Uint8Array(arrayBuffer),\n\t\t\t\t\tpath: subFolder !== '' ? `${subFolder}/${safeName}` : safeName,\n\t\t\t\t\t// No `metadata`: an external URL carries no GH authoring context.\n\t\t\t\t\tsubFolder\n\t\t\t\t} as ProcessedFile;\n\t\t\t} catch (error) {\n\t\t\t\tgetLogger().error(`Error fetching additional file from URL: ${file.filePath}`, error);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t})\n\t);\n\n\treturn fetched.filter((f): f is ProcessedFile => f !== null);\n};\n\nasync function createAndDownloadZip(\n\tfiles: ProcessedFile[],\n\tzipName: string,\n\tsave: (blob: Blob, fileName: string) => unknown\n): Promise<void> {\n\tconst { zip, strToU8 } = await import('fflate');\n\n\t// Paths are already unique: extractFilesFromComputeResponse renames collisions. fflate\n\t// flattens entries into a plain object, where a root entry named `__proto__` is lost.\n\tconst taken = new Set(files.map((file) => file.path));\n\tconst zipData: Record<string, Uint8Array> = {};\n\tfor (const file of files) {\n\t\tconst path = file.path === '__proto__' ? uniqueArchivePath(file.path, taken) : file.path;\n\t\ttaken.add(path);\n\t\tzipData[path] = typeof file.content === 'string' ? strToU8(file.content) : file.content;\n\t}\n\n\t// Async `zip` deflates on a worker thread; `zipSync` would block the UI on large exports.\n\tconst zipped = await new Promise<Uint8Array>((resolve, reject) => {\n\t\tzip(zipData, { level: 6 }, (err, data) => (err ? reject(err) : resolve(data)));\n\t});\n\n\tconst blob = new Blob([zipped as BlobPart], { type: 'application/zip' });\n\tawait save(blob, `${zipName}.zip`);\n}\n\n/**\n * First archive path not already taken in `taken`, disambiguating with a\n * numeric suffix before the extension: `dir/model.txt` → `dir/model-2.txt`.\n */\nfunction uniqueArchivePath(path: string, taken: ReadonlySet<string>): string {\n\tif (!taken.has(path)) return path;\n\tconst slash = path.lastIndexOf('/');\n\tconst dot = path.lastIndexOf('.');\n\tconst stemEnd = dot > slash ? dot : path.length;\n\tconst stem = path.slice(0, stemEnd);\n\tconst ext = path.slice(stemEnd);\n\tfor (let i = 2; ; i++) {\n\t\tconst candidate = `${stem}-${i}${ext}`;\n\t\tif (!taken.has(candidate)) return candidate;\n\t}\n}\n\nfunction saveFile(blob: Blob, filename: string) {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\t// Firefox requires the anchor to be in the DOM for the click to download.\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\t// Revoking synchronously can abort the download: the browser pins the blob only once it starts.\n\tsetTimeout(() => URL.revokeObjectURL(url), 10_000);\n}\n"],"mappings":"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"}