var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { try { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; } catch (e) { throw mod = 0, e; } }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // node_modules/partial-json/dist/options.js var require_options = __commonJS({ "node_modules/partial-json/dist/options.js"(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Allow = exports.ALL = exports.COLLECTION = exports.ATOM = exports.SPECIAL = exports.INF = exports._INFINITY = exports.INFINITY = exports.NAN = exports.BOOL = exports.NULL = exports.OBJ = exports.ARR = exports.NUM = exports.STR = void 0; exports.STR = 1; exports.NUM = 2; exports.ARR = 4; exports.OBJ = 8; exports.NULL = 16; exports.BOOL = 32; exports.NAN = 64; exports.INFINITY = 128; exports._INFINITY = 256; exports.INF = exports.INFINITY | exports._INFINITY; exports.SPECIAL = exports.NULL | exports.BOOL | exports.INF | exports.NAN; exports.ATOM = exports.STR | exports.NUM | exports.SPECIAL; exports.COLLECTION = exports.ARR | exports.OBJ; exports.ALL = exports.ATOM | exports.COLLECTION; exports.Allow = { STR: exports.STR, NUM: exports.NUM, ARR: exports.ARR, OBJ: exports.OBJ, NULL: exports.NULL, BOOL: exports.BOOL, NAN: exports.NAN, INFINITY: exports.INFINITY, _INFINITY: exports._INFINITY, INF: exports.INF, SPECIAL: exports.SPECIAL, ATOM: exports.ATOM, COLLECTION: exports.COLLECTION, ALL: exports.ALL }; exports.default = exports.Allow; } }); // node_modules/partial-json/dist/index.js var require_dist = __commonJS({ "node_modules/partial-json/dist/index.js"(exports) { "use strict"; var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) { if (k2 === void 0) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === void 0) k2 = k; o[k2] = m[k]; })); var __exportStar = exports && exports.__exportStar || function(m, exports2) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Allow = exports.MalformedJSON = exports.PartialJSON = exports.parseJSON = exports.parse = void 0; var options_1 = require_options(); Object.defineProperty(exports, "Allow", { enumerable: true, get: function() { return options_1.Allow; } }); __exportStar(require_options(), exports); var PartialJSON = class extends Error { }; exports.PartialJSON = PartialJSON; var MalformedJSON = class extends Error { }; exports.MalformedJSON = MalformedJSON; function parseJSON(jsonString, allowPartial = options_1.Allow.ALL) { if (typeof jsonString !== "string") { throw new TypeError(`expecting str, got ${typeof jsonString}`); } if (!jsonString.trim()) { throw new Error(`${jsonString} is empty`); } return _parseJSON(jsonString.trim(), allowPartial); } exports.parseJSON = parseJSON; var _parseJSON = (jsonString, allow) => { const length = jsonString.length; let index = 0; const markPartialJSON = (msg) => { throw new PartialJSON(`${msg} at position ${index}`); }; const throwMalformedError = (msg) => { throw new MalformedJSON(`${msg} at position ${index}`); }; const parseAny = () => { skipBlank(); if (index >= length) markPartialJSON("Unexpected end of input"); if (jsonString[index] === '"') return parseStr(); if (jsonString[index] === "{") return parseObj(); if (jsonString[index] === "[") return parseArr(); if (jsonString.substring(index, index + 4) === "null" || options_1.Allow.NULL & allow && length - index < 4 && "null".startsWith(jsonString.substring(index))) { index += 4; return null; } if (jsonString.substring(index, index + 4) === "true" || options_1.Allow.BOOL & allow && length - index < 4 && "true".startsWith(jsonString.substring(index))) { index += 4; return true; } if (jsonString.substring(index, index + 5) === "false" || options_1.Allow.BOOL & allow && length - index < 5 && "false".startsWith(jsonString.substring(index))) { index += 5; return false; } if (jsonString.substring(index, index + 8) === "Infinity" || options_1.Allow.INFINITY & allow && length - index < 8 && "Infinity".startsWith(jsonString.substring(index))) { index += 8; return Infinity; } if (jsonString.substring(index, index + 9) === "-Infinity" || options_1.Allow._INFINITY & allow && 1 < length - index && length - index < 9 && "-Infinity".startsWith(jsonString.substring(index))) { index += 9; return -Infinity; } if (jsonString.substring(index, index + 3) === "NaN" || options_1.Allow.NAN & allow && length - index < 3 && "NaN".startsWith(jsonString.substring(index))) { index += 3; return NaN; } return parseNum(); }; const parseStr = () => { const start = index; let escape = false; index++; while (index < length && (jsonString[index] !== '"' || escape && jsonString[index - 1] === "\\")) { escape = jsonString[index] === "\\" ? !escape : false; index++; } if (jsonString.charAt(index) == '"') { try { return JSON.parse(jsonString.substring(start, ++index - Number(escape))); } catch (e) { throwMalformedError(String(e)); } } else if (options_1.Allow.STR & allow) { try { return JSON.parse(jsonString.substring(start, index - Number(escape)) + '"'); } catch (e) { return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("\\")) + '"'); } } markPartialJSON("Unterminated string literal"); }; const parseObj = () => { index++; skipBlank(); const obj = {}; try { while (jsonString[index] !== "}") { skipBlank(); if (index >= length && options_1.Allow.OBJ & allow) return obj; const key = parseStr(); skipBlank(); index++; try { const value = parseAny(); obj[key] = value; } catch (e) { if (options_1.Allow.OBJ & allow) return obj; else throw e; } skipBlank(); if (jsonString[index] === ",") index++; } } catch (e) { if (options_1.Allow.OBJ & allow) return obj; else markPartialJSON("Expected '}' at end of object"); } index++; return obj; }; const parseArr = () => { index++; const arr = []; try { while (jsonString[index] !== "]") { arr.push(parseAny()); skipBlank(); if (jsonString[index] === ",") { index++; } } } catch (e) { if (options_1.Allow.ARR & allow) { return arr; } markPartialJSON("Expected ']' at end of array"); } index++; return arr; }; const parseNum = () => { if (index === 0) { if (jsonString === "-") throwMalformedError("Not sure what '-' is"); try { return JSON.parse(jsonString); } catch (e) { if (options_1.Allow.NUM & allow) try { return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf("e"))); } catch (e2) { } throwMalformedError(String(e)); } } const start = index; if (jsonString[index] === "-") index++; while (jsonString[index] && ",]}".indexOf(jsonString[index]) === -1) index++; if (index == length && !(options_1.Allow.NUM & allow)) markPartialJSON("Unterminated number literal"); try { return JSON.parse(jsonString.substring(start, index)); } catch (e) { if (jsonString.substring(start, index) === "-") markPartialJSON("Not sure what '-' is"); try { return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("e"))); } catch (e2) { throwMalformedError(String(e2)); } } }; const skipBlank = () => { while (index < length && " \n\r ".includes(jsonString[index])) { index++; } }; return parseAny(); }; var parse = parseJSON; exports.parse = parse; } }); // node_modules/@earendil-works/pi-ai/dist/utils/event-stream.js var FifoQueue = class { incoming = []; outgoing = []; get length() { return this.incoming.length + this.outgoing.length; } enqueue(value) { this.incoming.push(value); } dequeue() { if (this.outgoing.length === 0) { while (this.incoming.length > 0) { this.outgoing.push(this.incoming.pop()); } } return this.outgoing.pop(); } }; var EventStream = class { queue = new FifoQueue(); waiting = new FifoQueue(); done = false; finalResultPromise; resolveFinalResult; isComplete; extractResult; constructor(isComplete, extractResult) { this.isComplete = isComplete; this.extractResult = extractResult; this.finalResultPromise = new Promise((resolve) => { this.resolveFinalResult = resolve; }); } push(event) { if (this.done) return; if (this.isComplete(event)) { this.done = true; this.resolveFinalResult(this.extractResult(event)); } const waiter = this.waiting.dequeue(); if (waiter) { waiter({ value: event, done: false }); } else { this.queue.enqueue(event); } } end(result) { this.done = true; if (result !== void 0) { this.resolveFinalResult(result); } while (this.waiting.length > 0) { const waiter = this.waiting.dequeue(); waiter({ value: void 0, done: true }); } } async *[Symbol.asyncIterator]() { while (true) { if (this.queue.length > 0) { yield this.queue.dequeue(); } else if (this.done) { return; } else { const result = await new Promise((resolve) => this.waiting.enqueue(resolve)); if (result.done) return; yield result.value; } } } result() { return this.finalResultPromise; } }; var AssistantMessageEventStream = class extends EventStream { #startedAt = Date.now(); #startedAtMonotonic = performance.now(); constructor() { super((event) => event.type === "done" || event.type === "error", (event) => { if (event.type === "done") { return event.message; } else if (event.type === "error") { return event.error; } throw new Error("Unexpected event type for final result"); }); } push(event) { if (event.type === "done") this.#time(event.message); else if (event.type === "error") this.#time(event.error); super.push(event); } end(result) { if (result !== void 0) this.#time(result); super.end(result); } #time(message) { if (this.done || message.durationMs !== void 0 || message.timestamp < this.#startedAt) return; message.durationMs = Math.max(0, Math.round(performance.now() - this.#startedAtMonotonic)); } }; function createAssistantMessageEventStream() { return new AssistantMessageEventStream(); } // node_modules/@earendil-works/pi-ai/dist/api/lazy.js function createSetupErrorMessage(model, error, timestamp) { return { role: "assistant", content: [], api: model.api, provider: model.provider, model: model.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp }; } function hasResult(source) { return typeof source.result === "function"; } async function forwardStream(target, source, observer, observeResult) { let result; for await (const event of source) { target.push(event); observer?.onEvent?.(event); if (event.type === "done") result = event.message; else if (event.type === "error") result = event.error; } if (hasResult(source)) result = await source.result(); target.end(result); observeResult(result); } function lazyStream(model, setup, observer) { const startedAt = Date.now(); const outer = new AssistantMessageEventStream(); let observedResult = false; const observeResult = (result) => { if (observedResult) return; observedResult = true; observer?.onResult?.(result); }; setup().then((inner) => forwardStream(outer, inner, observer, observeResult)).catch((error) => { const message = createSetupErrorMessage(model, error, startedAt); const event = { type: "error", reason: "error", error: message }; outer.push(event); observer?.onEvent?.(event); outer.end(message); observeResult(message); }); return outer; } // node_modules/@earendil-works/pi-ai/dist/utils/diagnostics.js function formatThrownValue(value) { if (value instanceof Error) return value.message || value.name; if (typeof value === "string") return value; return String(value); } // node_modules/@earendil-works/pi-ai/dist/utils/models-error.js var ModelsError = class extends Error { code; constructor(code, message, options) { super(withCauseDetail(message, options?.cause), options); this.name = "ModelsError"; this.code = code; } }; function withCauseDetail(message, cause) { if (cause === void 0 || cause === null) return message; const detail = formatThrownValue(cause).trim(); if (!detail || message.includes(detail)) return message; return `${message}: ${detail}`; } // node_modules/@earendil-works/pi-ai/dist/auth/resolve.js var DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 5 * 60 * 1e3; // node_modules/@earendil-works/pi-ai/dist/utils/text.js function contentText(content, separator = "\n") { if (typeof content === "string") return content; return content.filter((block) => block.type === "text").map((block) => block.text).join(separator); } function getSystemMessageText(message) { const parts = [contentText(message.content)]; for (const text of Object.values(message.sections ?? {})) { if (text !== null) parts.push(text); } return parts.filter((part) => part.length > 0).join("\n\n"); } // node_modules/@earendil-works/pi-ai/dist/utils/model-operations.js function getModelType(model) { return model.type ?? "chat"; } function isModelType(model, type) { return getModelType(model) === type; } function imageErrorResult(model, error, aborted = false) { return { api: model.api, provider: model.provider, model: model.id, output: [], stopReason: aborted ? "aborted" : "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp: Date.now() }; } function classifierErrorResult(model, error, aborted = false) { return { api: model.api, provider: model.provider, model: model.id, answers: {}, stopReason: aborted ? "aborted" : "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp: Date.now() }; } // node_modules/@earendil-works/pi-ai/dist/models.js var KNOWN_MODEL_TYPES = { chat: true, image: true, classifier: true }; function hasKnownModelType(model) { return Object.hasOwn(KNOWN_MODEL_TYPES, getModelType(model)); } function createProvider(input) { const single = input.api && typeof input.api.stream === "function" ? input.api : void 0; const byApi = single || !input.api ? void 0 : input.api; const images = input.images; const classifiers = input.classifiers; const streams = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== void 0); const imageImplementations = Object.values(images ?? {}).filter((entry) => entry !== void 0); const classifierImplementations = Object.values(classifiers ?? {}).filter((entry) => entry !== void 0); if (streams.length === 0 && imageImplementations.length === 0 && classifierImplementations.length === 0) { throw new Error(`Provider ${input.id}: at least one of "api", "images", or "classifiers" is required.`); } const baselineModels = input.models; let dynamicModels = []; const fetchModels = input.fetchModels; const currentModels = () => { const merged = [...baselineModels]; for (const model of dynamicModels) { const index = merged.findIndex((entry) => getModelType(entry) === getModelType(model) && entry.id === model.id); if (index >= 0) merged[index] = model; else merged.push(model); } return merged; }; const apiFor = (model) => single ?? byApi?.[model.api]; const dispatch = (model, run) => { const streams2 = apiFor(model); if (!streams2) { return lazyStream(model, async () => { throw new ModelsError("stream", `Provider ${input.id} has no API implementation for "${model.api}"`); }); } return run(streams2); }; const provider = { id: input.id, name: input.name ?? input.id, baseUrl: input.baseUrl, headers: input.headers, auth: input.auth, getModels: () => currentModels().filter((model) => isModelType(model, "chat")), getAllModels: currentModels, refreshModels: fetchModels ? async (context) => { if (context.stored) { const restored = context.stored.models.filter((model) => model.provider === input.id).map((model) => model); if (!await context.publish({ update: () => { dynamicModels = restored; } })) { return; } } if (!context.allowNetwork || context.signal.aborted) return; const fetched = await fetchModels(context); if (context.signal.aborted) return; const refreshed = fetched.filter(hasKnownModelType); await context.publish({ persist: { models: refreshed, checkedAt: Date.now() }, update: () => { dynamicModels = refreshed; } }); } : void 0, filterModels: input.filterModels, filterAllModels: input.filterAllModels, stream: (model, context, options) => dispatch(model, (streams2) => streams2.stream(model, context, options)), streamSimple: (model, context, options) => dispatch(model, (streams2) => streams2.streamSimple(model, context, options)) }; if (streams.some((entry) => entry.fetchDeferred !== void 0)) { provider.fetchDeferred = (model, handle, options) => lazyStream(model, async () => { const implementation = apiFor(model); if (!implementation?.fetchDeferred) { throw new ModelsError("provider", `Provider ${input.id} does not support deferred responses for "${model.api}"`); } return implementation.fetchDeferred(model, handle, options); }); } if (streams.some((entry) => entry.cancelDeferred !== void 0)) { provider.cancelDeferred = async (model, handle, options) => { const implementation = apiFor(model); if (!implementation?.cancelDeferred) { throw new ModelsError("provider", `Provider ${input.id} cannot cancel deferred responses for "${model.api}"`); } await implementation.cancelDeferred(model, handle, options); }; } if (images && imageImplementations.length > 0) { provider.generateImages = async (model, context, options) => { const implementation = images[model.api]; if (!implementation) { return imageErrorResult(model, new ModelsError("provider", `Provider ${input.id} has no image generation implementation for "${model.api}"`)); } return implementation.generateImages(model, context, options); }; } if (classifiers && classifierImplementations.length > 0) { provider.classify = async (model, context, options) => { const implementation = classifiers[model.api]; if (!implementation) { return classifierErrorResult(model, new ModelsError("provider", `Provider ${input.id} has no classifier implementation for "${model.api}"`)); } return implementation.classify(model, context, options); }; } return provider; } // node_modules/@earendil-works/pi-ai/dist/utils/json-parse.js var import_partial_json = __toESM(require_dist(), 1); var VALID_JSON_ESCAPES = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t"]); function isControlCharacter(char) { const codePoint = char.codePointAt(0); return codePoint !== void 0 && codePoint >= 0 && codePoint <= 31; } function escapeControlCharacter(char) { switch (char) { case "\b": return "\\b"; case "\f": return "\\f"; case "\n": return "\\n"; case "\r": return "\\r"; case " ": return "\\t"; default: return `\\u${char.codePointAt(0)?.toString(16).padStart(4, "0") ?? "0000"}`; } } function repairJson(json) { let repaired = ""; let inString = false; for (let index = 0; index < json.length; index++) { const char = json[index]; if (!inString) { repaired += char; if (char === '"') { inString = true; } continue; } if (char === '"') { repaired += char; inString = false; continue; } if (char === "\\") { const nextChar = json[index + 1]; if (nextChar === void 0) { repaired += "\\\\"; continue; } if (nextChar === "u") { const unicodeDigits = json.slice(index + 2, index + 6); if (/^[0-9a-fA-F]{4}$/.test(unicodeDigits)) { repaired += `\\u${unicodeDigits}`; index += 5; continue; } } if (VALID_JSON_ESCAPES.has(nextChar)) { repaired += `\\${nextChar}`; index += 1; continue; } repaired += "\\\\"; continue; } repaired += isControlCharacter(char) ? escapeControlCharacter(char) : char; } return repaired; } function parseJsonWithRepair(json) { try { return JSON.parse(json); } catch (error) { const repairedJson = repairJson(json); if (repairedJson !== json) { return JSON.parse(repairedJson); } throw error; } } function parseToolCallArguments(json) { if (!json?.trim()) return { arguments: {} }; try { const parsed = parseJsonWithRepair(json); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { throw new Error("Tool arguments must be a JSON object"); } return { arguments: parsed }; } catch (error) { return { arguments: {}, argumentsError: `${error instanceof Error ? error.message : String(error)} Received arguments: ${json}` }; } } // node_modules/@earendil-works/pi-ai/dist/utils/streaming-json.js var MAX_REPORTED_DEPTH = 32; var ROOT_SLOT = { kind: "root" }; var CREDIT_PER_APPEND = 256; var CREDIT_PER_CHARACTER = 8; var MAX_PENDING_SCALAR_LENGTH = 64; var NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/; var NUMBER_WITHOUT_EXPONENT_DIGITS = /^(-?(?:0|[1-9]\d*)(?:\.\d+)?)[eE][+-]?$/; var LITERALS = [ ["true", true], ["false", false], ["null", null] ]; var QUOTE = 34; var BACKSLASH = 92; var StreamingJsonParser = class { #root = void 0; #done = false; #stack = []; #string = void 0; #scalar = ""; #scalarSlot = void 0; /** Sum of `size + 1` over the open path: what a fresh snapshot copies. */ #pathCost = 0; #credit = 0; #dirty = false; #snapshot = void 0; /** Changes of the piece being read. */ #changes = []; /** * Read the next piece of text; returns the changes it made, in order. Costs time linear in `text.length`. */ append(text) { this.#credit += CREDIT_PER_APPEND + CREDIT_PER_CHARACTER * text.length; this.#changes = []; if (this.#string !== void 0) this.#string.change = void 0; let index = 0; while (index < text.length && !this.#done) { const open = this.#string; if (open !== void 0) index = this.#readString(open, text, index); else if (this.#scalar !== "") index = this.#readScalar(text, index); else this.#structural(text[index++]); } return this.#changes; } /** * The value of the text read so far: `undefined` before a value starts, otherwise a plain JSON value that never * changes afterwards. Returns the previous snapshot while the text read since did not change the value, or while a * fresh copy of the open path is not yet paid for (see the module comment). */ snapshot() { if (!this.#dirty) return this.#snapshot; const pending = this.#pendingScalar(); const cost = this.#pathCost + (pending === void 0 ? 0 : 1); if (cost > this.#credit) return this.#snapshot; this.#credit -= cost; this.#dirty = false; this.#snapshot = this.#copyOpenPath(pending); return this.#snapshot; } /** Read string characters from `start` until the string ends or the text does; returns the next index. */ #readString(open, text, start) { let runStart = start; for (let index = start; index < text.length; index++) { const code = text.charCodeAt(index); if (open.escape !== "") { if (this.#continueEscape(open, text[index])) { runStart = index + 1; continue; } runStart = index; } if (code === QUOTE) { if (index > runStart) this.#appendString(open, text.slice(runStart, index)); this.#endString(open); return index + 1; } if (code === BACKSLASH) { if (index > runStart) this.#appendString(open, text.slice(runStart, index)); open.escape = "\\"; runStart = index + 1; } } if (text.length > runStart) this.#appendString(open, text.slice(runStart)); return text.length; } /** Read number or literal characters from `start` up to the next delimiter; returns the delimiter's index. */ #readScalar(text, start) { let end = start; while (end < text.length && !isDelimiter(text[end])) end++; if (end > start) { this.#scalar += text.slice(start, end); if (this.#scalarSlot !== void 0) this.#dirty = true; } if (end < text.length) this.#endScalar(); return end; } /** Handle one character outside strings. */ #structural(char) { switch (char) { case " ": case " ": case "\n": case "\r": return; case '"': this.#startString(); return; case "{": case "[": this.#openContainer(char === "{" ? {} : []); return; case "}": case "]": this.#closeContainer(); return; case ":": { const frame = this.#stack.at(-1); if (frame !== void 0 && !Array.isArray(frame.container) && frame.phase === "colon") frame.phase = "value"; return; } case ",": { const frame = this.#stack.at(-1); if (frame !== void 0) frame.phase = Array.isArray(frame.container) ? "value" : "key"; return; } default: this.#scalar = char; this.#scalarSlot = this.#valueSlot(); if (this.#scalarSlot !== void 0) this.#dirty = true; } } /** Where a value starting now goes, or `undefined` when this position takes no value. */ #valueSlot() { const frame = this.#stack.at(-1); if (frame === void 0) return this.#root === void 0 ? ROOT_SLOT : void 0; if (Array.isArray(frame.container)) return { kind: "element", index: frame.container.length }; if ((frame.phase === "value" || frame.phase === "colon") && frame.key !== void 0) { return { kind: "member", key: frame.key }; } return void 0; } #startString() { const frame = this.#stack.at(-1); if (frame !== void 0 && !Array.isArray(frame.container) && (frame.phase === "key" || frame.phase === "comma")) { this.#string = { target: "key", slot: void 0, text: "", escape: "", path: void 0, change: void 0 }; return; } const slot = this.#valueSlot(); const path = this.#reportedPath(slot); this.#string = { target: "value", slot, text: "", escape: "", path, change: void 0 }; this.#place(slot, ""); if (path !== void 0) this.#string.change = this.#report({ kind: "set", path, value: "" }); } /** The path of a value at `slot` in the innermost open container, when its changes are reported. */ #reportedPath(slot) { if (slot === void 0) return void 0; if (slot.kind === "root") return []; const frame = this.#stack.at(-1); if (!frame.reportsInside) return void 0; return [...frame.path, slot.kind === "member" ? slot.key : slot.index]; } #report(change) { this.#changes.push(change); return change; } /** Continue an escape with `char`; returns false when `char` is not part of it and must be read normally. */ #continueEscape(open, char) { if (open.escape === "\\") { const decoded = SIMPLE_ESCAPES[char]; if (decoded !== void 0) { open.escape = ""; this.#appendString(open, decoded); return true; } if (char === "u") { open.escape = "\\u"; return true; } open.escape = ""; this.#appendString(open, "\\"); return false; } if (isHexDigit(char)) { open.escape += char; if (open.escape.length < 6) return true; const decoded = String.fromCharCode(Number.parseInt(open.escape.slice(2), 16)); open.escape = ""; this.#appendString(open, decoded); return true; } const literal = open.escape; open.escape = ""; this.#appendString(open, literal); return false; } #appendString(open, text) { open.text += text; if (open.target !== "value") return; this.#place(open.slot, open.text); if (open.path === void 0) return; const change = open.change; if (change !== void 0 && this.#changes.at(-1) === change) { if (change.kind === "set") change.value = change.value + text; else change.text += text; return; } open.change = this.#report({ kind: "append", path: open.path, text }); } #endString(open) { this.#string = void 0; const frame = this.#stack.at(-1); if (open.target === "key") { if (frame === void 0) return; frame.key = open.text; frame.phase = "colon"; return; } this.#valueEnded(frame); } #endScalar() { const token = this.#scalar; const slot = this.#scalarSlot; this.#scalar = ""; this.#scalarSlot = void 0; const value = scalarValue(token); if (value !== void 0) { this.#place(slot, value); const path = this.#reportedPath(slot); if (path !== void 0) this.#report({ kind: "set", path, value }); } this.#valueEnded(this.#stack.at(-1)); } /** The value of the unterminated scalar at the end of the text, if it shows. */ #pendingScalar() { const slot = this.#scalarSlot; const token = this.#scalar; if (slot === void 0 || token === "" || token.length > MAX_PENDING_SCALAR_LENGTH) return void 0; const value = pendingScalarValue(token); return value === void 0 ? void 0 : { slot, value }; } #openContainer(container) { const slot = this.#valueSlot(); const path = this.#reportedPath(slot); this.#place(slot, container); this.#stack.push({ container, slot, path, reportsInside: path !== void 0 && path.length < MAX_REPORTED_DEPTH, size: 0, key: void 0, phase: Array.isArray(container) ? "value" : "key" }); this.#pathCost += 1; this.#dirty = true; if (path !== void 0) this.#report({ kind: "set", path, value: Array.isArray(container) ? [] : {} }); } #closeContainer() { const frame = this.#stack.pop(); if (frame === void 0) return; this.#pathCost -= frame.size + 1; this.#dirty = true; if (frame.path !== void 0 && !frame.reportsInside) { this.#report({ kind: "set", path: frame.path, value: frame.container }); } this.#valueEnded(this.#stack.at(-1)); } /** A value in `frame`, or the root value when there is no frame, has ended. */ #valueEnded(frame) { if (frame === void 0) { if (this.#root !== void 0) this.#done = true; return; } frame.phase = "comma"; } /** Set the value at `slot` in the open path; nothing for a value in an invalid position. */ #place(slot, value) { if (slot === void 0) return; this.#dirty = true; if (slot.kind === "root") { this.#root = value; return; } const frame = this.#stack.at(-1); if (slot.kind === "element") { const elements = frame.container; if (slot.index === elements.length) { frame.size++; this.#pathCost++; } elements[slot.index] = value; return; } const members = frame.container; if (!Object.hasOwn(members, slot.key)) { frame.size++; this.#pathCost++; } setMember(members, slot.key, value); } /** A fresh snapshot: copies of the open containers, innermost first, each holding the copy of its open child. */ #copyOpenPath(pending) { if (this.#stack.length === 0) return pending?.slot.kind === "root" ? pending.value : this.#root; let child; let childSlot; for (let depth = this.#stack.length - 1; depth >= 0; depth--) { const frame = this.#stack[depth]; const copy = Array.isArray(frame.container) ? frame.container.slice() : { ...frame.container }; if (depth === this.#stack.length - 1 && pending !== void 0) setSlot(copy, pending.slot, pending.value); if (child !== void 0 && childSlot !== void 0) setSlot(copy, childSlot, child); child = copy; childSlot = frame.slot; } return childSlot?.kind === "root" ? child : this.#root; } }; var ToolCallArgumentsStream = class { #text = ""; #parser = new StreamingJsonParser(); #snapshot = void 0; #arguments = {}; /** The raw argument text received so far. */ get text() { return this.#text; } /** Append a piece of argument text; returns the partial arguments, a new object whenever they changed. */ append(delta) { this.#text += delta; this.#parser.append(delta); return this.#partial(); } /** * Take the provider's complete argument text. When it continues the text so far, returns the missing piece as * `delta`; otherwise starts over with `text`. Either way returns the partial arguments of `text`. */ replace(text) { if (text.startsWith(this.#text)) { const delta = text.slice(this.#text.length); return { delta, arguments: delta === "" ? this.#partial() : this.append(delta) }; } this.#text = ""; this.#parser = new StreamingJsonParser(); return { delta: void 0, arguments: this.append(text) }; } /** The final arguments: one full parse of the complete text, which reports malformed arguments. */ finish() { return parseToolCallArguments(this.#text); } #partial() { const snapshot = this.#parser.snapshot(); if (snapshot === this.#snapshot) return this.#arguments; this.#snapshot = snapshot; this.#arguments = typeof snapshot === "object" && snapshot !== null && !Array.isArray(snapshot) ? snapshot : {}; return this.#arguments; } }; var SIMPLE_ESCAPES = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " }; function isDelimiter(char) { return char === " " || char === " " || char === "\n" || char === "\r" || char === "," || char === ":" || char === "{" || char === "}" || char === "[" || char === "]" || char === '"'; } function isHexDigit(char) { return char >= "0" && char <= "9" || char >= "a" && char <= "f" || char >= "A" && char <= "F"; } function scalarValue(token) { for (const [literal, value] of LITERALS) if (token === literal) return value; return NUMBER.test(token) ? Number(token) : void 0; } function pendingScalarValue(token) { for (const [literal, value] of LITERALS) if (literal.startsWith(token)) return value; if (NUMBER.test(token)) return Number(token); const mantissa = NUMBER_WITHOUT_EXPONENT_DIGITS.exec(token); return mantissa === null ? void 0 : Number(mantissa[1]); } function setSlot(container, slot, value) { if (slot.kind === "element") container[slot.index] = value; else if (slot.kind === "member") setMember(container, slot.key, value); } function setMember(members, key, value) { if (key === "__proto__") { Object.defineProperty(members, key, { value, writable: true, enumerable: true, configurable: true }); } else { members[key] = value; } } // node_modules/@earendil-works/pi-ai/dist/providers/faux.js var DEFAULT_API = "faux"; var DEFAULT_PROVIDER = "faux"; var DEFAULT_MODEL_ID = "faux-1"; var DEFAULT_MODEL_NAME = "Faux Model"; var DEFAULT_BASE_URL = "http://localhost:0"; var DEFAULT_MIN_TOKEN_SIZE = 3; var DEFAULT_MAX_TOKEN_SIZE = 5; var DEFAULT_USAGE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }; function fauxText(text) { return { type: "text", text }; } function fauxToolCall(name, arguments_, options = {}) { return { type: "toolCall", id: options.id ?? randomId("tool"), name, arguments: arguments_ }; } function normalizeFauxAssistantContent(content) { if (typeof content === "string") { return [fauxText(content)]; } return Array.isArray(content) ? content : [content]; } function fauxAssistantMessage(content, options = {}) { return { role: "assistant", content: normalizeFauxAssistantContent(content), api: DEFAULT_API, provider: DEFAULT_PROVIDER, model: DEFAULT_MODEL_ID, usage: DEFAULT_USAGE, stopReason: options.stopReason ?? "stop", ...options.deferred === void 0 ? {} : { deferred: options.deferred }, ...options.errorMessage === void 0 ? {} : { errorMessage: options.errorMessage }, ...options.responseId === void 0 ? {} : { responseId: options.responseId }, timestamp: options.timestamp ?? Date.now() }; } function estimateTokens(text) { return Math.ceil(text.length / 4); } function randomId(prefix) { return `${prefix}:${Date.now()}:${Math.random().toString(36).slice(2)}`; } function contentToText(content) { if (typeof content === "string") { return content; } return content.map((block) => { if (block.type === "text") { return block.text; } return `[image:${block.mimeType}:${block.data.length}]`; }).join("\n"); } function assistantContentToText(content) { return content.map((block) => { if (block.type === "text") { return block.text; } if (block.type === "thinking") { return block.thinking; } return `${block.name}:${JSON.stringify(block.arguments)}`; }).join("\n"); } function toolResultToText(message) { return [message.toolName, ...message.content.map((block) => contentToText([block]))].join("\n"); } function messageToText(message) { if (message.role === "system") { return [ getSystemMessageText(message), ...message.toolsRemoved?.map((tool) => `tool-:${JSON.stringify(tool)}`) ?? [], ...message.toolsAdded?.map((tool) => `tool+:${JSON.stringify(tool)}`) ?? [] ].filter((part) => part.length > 0).join("\n"); } if (message.role === "user") { return contentToText(message.content); } if (message.role === "assistant") { return assistantContentToText(message.content); } return toolResultToText(message); } function joinedLength(messages, count = messages.length) { let length = count > 0 ? (count - 1) * 2 : 0; for (let index = 0; index < count; index++) length += messages[index].length; return length; } function commonPromptPrefixLength(previous, current) { let index = 0; while (index < previous.length && index < current.length && previous[index] === current[index]) index++; const rest = (messages) => index === messages.length ? "" : (index > 0 ? "\n\n" : "") + messages.slice(index).join("\n\n"); return joinedLength(previous, index) + commonPrefixLength(rest(previous), rest(current)); } function commonPrefixLength(a, b) { const length = Math.min(a.length, b.length); let index = 0; while (index < length && a[index] === b[index]) { index++; } return index; } function withUsageEstimate(message, context, options, promptCache) { const prompt = context.messages.map((message2) => `${message2.role}:${messageToText(message2)}`); const promptLength = joinedLength(prompt); const promptTokens = Math.ceil(promptLength / 4); const outputTokens = estimateTokens(assistantContentToText(message.content)); let input = promptTokens; let cacheRead = 0; let cacheWrite = 0; const sessionId = options?.sessionId; if (sessionId && options?.cacheRetention !== "none") { const previousPrompt = promptCache.get(sessionId); if (previousPrompt) { const cachedChars = commonPromptPrefixLength(previousPrompt, prompt); cacheRead = Math.ceil(cachedChars / 4); cacheWrite = Math.max(0, promptTokens - cacheRead); } else { cacheWrite = promptTokens; } input = 0; promptCache.set(sessionId, prompt); } return { ...message, usage: { input, output: outputTokens, cacheRead, cacheWrite, totalTokens: input + outputTokens + cacheRead + cacheWrite, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } }; } function splitStringByTokenSize(text, minTokenSize, maxTokenSize) { const chunks = []; let index = 0; while (index < text.length) { const tokenSize = minTokenSize + Math.floor(Math.random() * (maxTokenSize - minTokenSize + 1)); const charSize = Math.max(1, tokenSize * 4); chunks.push(text.slice(index, index + charSize)); index += charSize; } return chunks.length > 0 ? chunks : [""]; } function cloneMessage(message, api, provider, modelId) { const cloned = structuredClone(message); return { ...cloned, api, provider, model: modelId, timestamp: cloned.timestamp ?? Date.now(), usage: cloned.usage ?? DEFAULT_USAGE }; } function createDeferredMessage(model, handle) { return { role: "assistant", content: [], api: model.api, provider: model.provider, model: model.id, usage: DEFAULT_USAGE, stopReason: "deferred", deferred: handle, timestamp: Date.now() }; } function createErrorMessage(error, api, provider, modelId) { return { role: "assistant", content: [], api, provider, model: modelId, usage: DEFAULT_USAGE, stopReason: "error", errorMessage: error instanceof Error ? error.message : String(error), timestamp: Date.now() }; } function createAbortedMessage(partial) { return { ...partial, stopReason: "aborted", errorMessage: "Request was aborted", timestamp: Date.now() }; } function scheduleChunk(chunk, tokensPerSecond) { if (!tokensPerSecond || tokensPerSecond <= 0) { return new Promise((resolve) => queueMicrotask(resolve)); } const delayMs = estimateTokens(chunk) / tokensPerSecond * 1e3; return new Promise((resolve) => setTimeout(resolve, delayMs)); } async function streamWithDeltas(stream, message, minTokenSize, maxTokenSize, tokensPerSecond, signal) { const partial = { ...message, content: [], stopReason: "pending" }; if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } stream.push({ type: "start", partial: { ...partial } }); for (let index = 0; index < message.content.length; index++) { if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } const block = message.content[index]; if (block.type === "thinking") { partial.content = [...partial.content, { type: "thinking", thinking: "" }]; stream.push({ type: "thinking_start", contentIndex: index, partial: { ...partial } }); for (const chunk of splitStringByTokenSize(block.thinking, minTokenSize, maxTokenSize)) { await scheduleChunk(chunk, tokensPerSecond); if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } partial.content[index].thinking += chunk; stream.push({ type: "thinking_delta", contentIndex: index, delta: chunk, partial: { ...partial } }); } stream.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: { ...partial } }); continue; } if (block.type === "text") { partial.content = [...partial.content, { type: "text", text: "" }]; stream.push({ type: "text_start", contentIndex: index, partial: { ...partial } }); for (const chunk of splitStringByTokenSize(block.text, minTokenSize, maxTokenSize)) { await scheduleChunk(chunk, tokensPerSecond); if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } partial.content[index].text += chunk; stream.push({ type: "text_delta", contentIndex: index, delta: chunk, partial: { ...partial } }); } stream.push({ type: "text_end", contentIndex: index, content: block.text, partial: { ...partial } }); continue; } partial.content = [...partial.content, { type: "toolCall", id: block.id, name: block.name, arguments: {} }]; stream.push({ type: "toolcall_start", contentIndex: index, partial: { ...partial } }); const toolArguments = new ToolCallArgumentsStream(); for (const chunk of splitStringByTokenSize(JSON.stringify(block.arguments), minTokenSize, maxTokenSize)) { await scheduleChunk(chunk, tokensPerSecond); if (signal?.aborted) { const aborted = createAbortedMessage(partial); stream.push({ type: "error", reason: "aborted", error: aborted }); stream.end(aborted); return; } partial.content[index].arguments = toolArguments.append(chunk); stream.push({ type: "toolcall_delta", contentIndex: index, delta: chunk, partial: { ...partial } }); } partial.content[index].arguments = block.arguments; stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: { ...partial } }); } if (message.stopReason === "pending") { throw new Error("Faux response ended without a stop reason"); } if (message.stopReason === "error" || message.stopReason === "aborted") { stream.push({ type: "error", reason: message.stopReason, error: message }); stream.end(message); return; } stream.push({ type: "done", reason: message.stopReason, message }); stream.end(message); } function createFauxCore(options) { const api = options.api ?? randomId(DEFAULT_API); const provider = options.provider ?? DEFAULT_PROVIDER; const minTokenSize = Math.max(1, Math.min(options.tokenSize?.min ?? DEFAULT_MIN_TOKEN_SIZE, options.tokenSize?.max ?? DEFAULT_MAX_TOKEN_SIZE)); const maxTokenSize = Math.max(minTokenSize, options.tokenSize?.max ?? DEFAULT_MAX_TOKEN_SIZE); let pendingResponses = []; const tokensPerSecond = options.tokensPerSecond; const state = { callCount: 0, deferredFetchCount: 0, cancelledDeferred: [] }; const promptCache = /* @__PURE__ */ new Map(); const deferredResponses = /* @__PURE__ */ new Map(); const modelDefinitions = options.models?.length ? options.models : [ { id: DEFAULT_MODEL_ID, name: DEFAULT_MODEL_NAME, reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128e3, maxTokens: 16384 } ]; const models = modelDefinitions.map((definition) => ({ id: definition.id, name: definition.name ?? definition.id, api, provider, baseUrl: DEFAULT_BASE_URL, reasoning: definition.reasoning ?? false, input: definition.input ?? ["text", "image"], inputLimits: definition.inputLimits, cost: definition.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: definition.contextWindow ?? 128e3, maxTokens: definition.maxTokens ?? 16384 })); const resolveResponse = async (step, context, streamOptions, requestModel) => { const resolved = typeof step === "function" ? await step(context, streamOptions, state, requestModel) : step; return withUsageEstimate(cloneMessage(resolved, api, provider, requestModel.id), context, streamOptions, promptCache); }; const stream = (requestModel, context, streamOptions) => { const outer = createAssistantMessageEventStream(); const step = pendingResponses.shift(); state.callCount++; queueMicrotask(async () => { try { await streamOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); if (!step) { let message2 = createErrorMessage(new Error("No more faux responses queued"), api, provider, requestModel.id); message2 = withUsageEstimate(message2, context, streamOptions, promptCache); outer.push({ type: "error", reason: "error", error: message2 }); outer.end(message2); return; } if (streamOptions?.deferred) { const handle = { provider: requestModel.provider, modelId: requestModel.id, api: requestModel.api, id: randomId("deferred"), ...options.deferred?.pollAfterMs !== void 0 ? { pollAfterMs: options.deferred.pollAfterMs } : {} }; deferredResponses.set(handle.id, { handle, step, context, options: streamOptions, model: requestModel, pendingFetches: Math.max(0, Math.floor(options.deferred?.pendingFetches ?? 0)), cancelled: false }); await streamWithDeltas(outer, createDeferredMessage(requestModel, handle), minTokenSize, maxTokenSize, tokensPerSecond, streamOptions.signal); return; } const message = await resolveResponse(step, context, streamOptions, requestModel); await streamWithDeltas(outer, message, minTokenSize, maxTokenSize, tokensPerSecond, streamOptions?.signal); } catch (error) { const message = createErrorMessage(error, api, provider, requestModel.id); outer.push({ type: "error", reason: "error", error: message }); outer.end(message); } }); return outer; }; const streamSimple = (streamModel, context, streamOptions) => stream(streamModel, context, streamOptions); const fetchDeferred = (requestModel, handle, fetchOptions) => { const outer = createAssistantMessageEventStream(); state.deferredFetchCount++; queueMicrotask(async () => { try { await fetchOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); const entry = deferredResponses.get(handle.id); if (!entry || entry.handle.provider !== handle.provider || entry.handle.modelId !== handle.modelId || entry.handle.api !== handle.api) { throw new Error(`Unknown faux deferred response: ${handle.id}`); } if (entry.cancelled) throw new Error(`Faux deferred response was cancelled: ${handle.id}`); if (entry.pendingFetches > 0) { entry.pendingFetches--; await streamWithDeltas(outer, createDeferredMessage(requestModel, entry.handle), minTokenSize, maxTokenSize, tokensPerSecond, fetchOptions?.signal); return; } if (!entry.final) { const { deferred: _deferred, signal: _submissionSignal, onResponse: _submissionOnResponse, ...submissionOptions } = entry.options ?? {}; try { entry.final = await resolveResponse(entry.step, entry.context, submissionOptions, entry.model); } catch (error) { entry.final = createErrorMessage(error, api, provider, entry.model.id); } } await streamWithDeltas(outer, entry.final, minTokenSize, maxTokenSize, tokensPerSecond, fetchOptions?.signal); } catch (error) { const message = createErrorMessage(error, api, provider, requestModel.id); outer.push({ type: "error", reason: "error", error: message }); outer.end(message); } }); return outer; }; const cancelDeferred = async (requestModel, handle, cancelOptions) => { state.cancelledDeferred.push(structuredClone(handle)); const entry = deferredResponses.get(handle.id); if (entry) entry.cancelled = true; await cancelOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); }; function getModel(requestedModelId) { if (!requestedModelId) { return models[0]; } return models.find((candidate) => candidate.id === requestedModelId); } return { api, provider, models, stream, streamSimple, fetchDeferred, cancelDeferred, getModel, state, setResponses(responses) { pendingResponses = [...responses]; }, appendResponses(responses) { pendingResponses.push(...responses); }, getPendingResponseCount() { return pendingResponses.length; } }; } function fauxProvider(options = {}) { const core = createFauxCore(options); const provider = createProvider({ id: core.provider, auth: { apiKey: { name: "Faux", resolve: async () => ({ auth: {} }) } }, models: core.models, api: { stream: core.stream, streamSimple: core.streamSimple, fetchDeferred: core.fetchDeferred, cancelDeferred: core.cancelDeferred } }); return { provider, api: core.api, models: core.models, getModel: core.getModel, state: core.state, setResponses: core.setResponses, appendResponses: core.appendResponses, getPendingResponseCount: core.getPendingResponseCount }; } // p4-test.ts var main = `fn greeting(name: &str) -> String { format!("hello {name}") } fn main() { let Some(name) = std::env::args().nth(1) else { std::process::exit(7) }; println!("{}", serde_json::json!({"greeting": greeting(&name)})); } #[test] fn greeting_test() { assert_eq!(greeting("world"), "hello world", "P4_expected_failure"); } `; var write = (path, text) => `cat > ${path} <<'P4_FILE' ${text} P4_FILE `; var phases = [ `set -e mkdir -p /src/demo-cli/src /src/demo-node /tmp cd /src/demo-cli if test -d target; then cargo clean; fi ${write("Cargo.toml", '[package]\nname="demo-cli"\nversion="0.1.0"\nedition="2024"\n[workspace]\n')} ${write("src/main.rs", main.replace('"hello world", "P4_expected_failure"', '"hello wrong", "P4_expected_failure"'))} cargo add serde_json cargo fmt set +e cargo test -- --nocapture > /tmp/p4-fail.stdout 2> /tmp/p4-fail.stderr code=$? set -e cat /tmp/p4-fail.stdout /tmp/p4-fail.stderr test "$code" = 101 case "$(cat /tmp/p4-fail.stderr)" in *P4_expected_failure*) ;; *) exit 1 ;; esac printf 'P4_PHASE_0_OK '`, `set -e cd /src/demo-cli ${write("src/main.rs", main)} cargo fmt cargo test -- --nocapture cargo build --release test -f target/wasm32-wasip2/release/demo-cli.wasm sh -c '/src/demo-cli/target/wasm32-wasip2/release/demo-cli.wasm world' > /tmp/p4-cli.json case "$(cat /tmp/p4-cli.json)" in *'"greeting":"hello world"'*) ;; *) exit 1 ;; esac set +e sh -c '/src/demo-cli/target/wasm32-wasip2/release/demo-cli.wasm' code=$? set -e test "$code" = 7 printf 'P4_PHASE_1_OK '`, `set -e ${write("/src/demo-node/package.json", '{"scripts":{"test":"node test.cjs"}}')} ${write("/src/demo-node/test.cjs", 'const fs = require("node:fs");\nrequire("node:assert/strict").equal(2 + 2, 4);\nfs.writeFileSync("result.txt", "P4 npm test passed\\n");')} id=$(box create --ttl 600) printf '%s ' "$id" > /tmp/p4-box-id trap 'box rm "$id"' EXIT box put "$id" /src/demo-node /work box exec "$id" --timeout 120 -- sh -c 'cd /work && npm test' box get "$id" /work/result.txt /src/demo-node/result.txt test "$(cat /src/demo-node/result.txt)" = 'P4 npm test passed' box rm "$id" trap - EXIT printf 'P4_PHASE_2_OK '`, `set -e undo list --conversation 1 > /tmp/p4-undo.json box ls > /tmp/p4-boxes printf 'P4_PHASE_3_OK '` ]; function p4(host) { const faux = fauxProvider({ provider: "openai", models: [{ id: "gpt-6.1-sol" }], tokensPerSecond: 1e6 }); const respond = (transcript) => { faux.appendResponses([respond]); const messages = transcript.messages; const input = messages.findLast((message) => message.role === "user"); const text = (message) => typeof message.content === "string" ? message.content : (message.content ?? []).filter((part) => part.type === "text").map((part) => part.text).join("\n"); const round = /P4 start (\d+)/.exec(text(input))?.[1]; if (!round) throw new Error(`Unexpected P4 input: ${JSON.stringify(input)}`); const last = messages.findLast((message) => message.role !== "system"); let phase = 0; if (last.role !== "user") { const prior = /p4-\d+-(\d+)/.exec(last.toolCallId)?.[1]; if (last.isError || prior === void 0 || !text(last).includes(`P4_PHASE_${prior}_OK`)) throw new Error(`P4 bash failed: ${JSON.stringify(last)}`); phase = Number(prior) + 1; } if (phase === phases.length) return fauxAssistantMessage(`P4_COMPLETE_${round}`); return fauxAssistantMessage([fauxToolCall("bash", { command: phases[phase], timeout: 300 }, { id: `p4-${round}-${phase}` })], { stopReason: "toolUse" }); }; faux.setResponses([respond]); host.models().setProvider(faux.provider); return { name: "p4" }; } export { p4 as default };