UNPKG

agents

Version:

A home for your AI agents

1,402 lines • 54.2 kB
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../classPrivateFieldGet2-DZBYAB34.js";
import { t as _classPrivateMethodInitSpec } from "../../classPrivateMethodInitSpec-qMjJ6sHQ.js";
import { t as LifecycleCapability } from "../../capability-DBAKc6UZ.js";
//#region src/harness/table-prefix.ts
/**
* A view of `storage` whose SQL is rewritten into `prefix`'s namespace.
* Everything other than `sql.exec` is the original storage, so transactions
* and the rest of the API behave as before.
*
* @param storage - The Durable Object's storage.
* @param prefix - The namespace, such as `opencode_`. Letters, digits and
*   underscores, starting with a letter; never `_cf_`, which Durable Objects
*   reserve.
* @returns The same storage, with `sql.exec` rewritten.
* @throws When `prefix` is not a valid prefix: a defect in the caller.
*/
function prefixTables(storage, prefix) {
	const rewriter = new TablePrefixRewriter(storage.sql, prefix);
	const sql = new Proxy(storage.sql, { get(target, property) {
		if (property === "exec") return rewriter.exec;
		const value = Reflect.get(target, property, target);
		return typeof value === "function" ? value.bind(target) : value;
	} });
	return new Proxy(storage, { get(target, property) {
		if (property === "sql") return sql;
		const value = Reflect.get(target, property, target);
		return typeof value === "function" ? value.bind(target) : value;
	} });
}
/** Words after which an identifier names a table or index in DDL. */
const DDL_KEYWORDS = /* @__PURE__ */ new Set([
	"TABLE",
	"INDEX",
	"REFERENCES"
]);
/** Words after which an identifier names a table in DML. */
const DML_KEYWORDS = /* @__PURE__ */ new Set([
	"FROM",
	"JOIN",
	"INTO",
	"UPDATE"
]);
/** Words that may follow `sqlite_master` without being its alias. */
const CLAUSE_KEYWORDS = /* @__PURE__ */ new Set([
	"WHERE",
	"ORDER",
	"GROUP",
	"LIMIT",
	"JOIN",
	"LEFT",
	"INNER",
	"CROSS",
	"ON",
	"UNION",
	"EXCEPT",
	"INTERSECT",
	"HAVING",
	"WINDOW"
]);
const MASTER_TABLES = /* @__PURE__ */ new Set(["sqlite_master", "sqlite_schema"]);
/** PRAGMAs whose argument is a table or index name. */
const SCHEMA_PRAGMAS = /* @__PURE__ */ new Set([
	"table_info",
	"table_xinfo",
	"index_list",
	"index_info",
	"index_xinfo",
	"foreign_key_list",
	"foreign_key_check",
	"integrity_check",
	"quick_check"
]);
/**
* Bare words that follow a table keyword without naming a table:
* `TABLE IF NOT EXISTS`, `INSERT OR REPLACE INTO`, `FROM (SELECT …)`.
*/
const NOT_NAMES = /* @__PURE__ */ new Set([
	"IF",
	"NOT",
	"EXISTS",
	"SELECT",
	"VALUES",
	"OR"
]);
var _sql = /* @__PURE__ */ new WeakMap();
var _prefix = /* @__PURE__ */ new WeakMap();
var _masterView = /* @__PURE__ */ new WeakMap();
var _names = /* @__PURE__ */ new WeakMap();
var _cache = /* @__PURE__ */ new WeakMap();
var _TablePrefixRewriter_brand = /* @__PURE__ */ new WeakSet();
var TablePrefixRewriter = class {
	constructor(sql, prefix) {
		_classPrivateMethodInitSpec(this, _TablePrefixRewriter_brand);
		_classPrivateFieldInitSpec(this, _sql, void 0);
		_classPrivateFieldInitSpec(this, _prefix, void 0);
		_classPrivateFieldInitSpec(this, _masterView, void 0);
		_classPrivateFieldInitSpec(this, _names, void 0);
		_classPrivateFieldInitSpec(this, _cache, /* @__PURE__ */ new Map());
		this.exec = (query, ...bindings) => _classPrivateFieldGet2(_sql, this).exec(this.rewrite(query), ...bindings);
		if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(prefix)) throw new Error(`Invalid table prefix ${JSON.stringify(prefix)}`);
		if (prefix.toLowerCase().startsWith("_cf_")) throw new Error("A table prefix must not start with _cf_");
		_classPrivateFieldSet2(_sql, this, sql);
		_classPrivateFieldSet2(_prefix, this, prefix);
		const length = prefix.length;
		_classPrivateFieldSet2(_masterView, this, `(SELECT type, substr(name, ${length + 1}) AS name, substr(tbl_name, ${length + 1}) AS tbl_name, rootpage, sql FROM sqlite_master WHERE substr(name, 1, ${length}) = '${prefix}')`);
	}
	rewrite(query) {
		const cached = _classPrivateFieldGet2(_cache, this).get(query);
		if (cached !== void 0) return cached;
		const names = _assertClassBrand(_TablePrefixRewriter_brand, this, _known$1).call(this);
		const before = names.size;
		const rewritten = _assertClassBrand(_TablePrefixRewriter_brand, this, _rewrite).call(this, tokenize(query), names);
		if (names.size === before) _classPrivateFieldGet2(_cache, this).set(query, rewritten);
		else _classPrivateFieldGet2(_cache, this).clear();
		return rewritten;
	}
};
function _known$1() {
	if (_classPrivateFieldGet2(_names, this)) return _classPrivateFieldGet2(_names, this);
	const rows = _classPrivateFieldGet2(_sql, this).exec("SELECT name FROM sqlite_master WHERE substr(name, 1, ?) = ?", _classPrivateFieldGet2(_prefix, this).length, _classPrivateFieldGet2(_prefix, this)).toArray();
	_classPrivateFieldSet2(_names, this, new Set(rows.map((row) => row.name.slice(_classPrivateFieldGet2(_prefix, this).length).toLowerCase())));
	return _classPrivateFieldGet2(_names, this);
}
function _rewrite(tokens, names) {
	const significant = tokens.map((token, index) => ({
		token,
		index
	})).filter(({ token }) => token.kind !== "space");
	const out = tokens.map((token) => token.text);
	const firstWord = upper(significant[0]?.token);
	const isCreateIndex = firstWord === "CREATE" && significant.slice(1, 4).some(({ token }) => upper(token) === "INDEX");
	for (let i = 0; i < significant.length; i++) {
		const { token, index } = significant[i];
		if (token.kind !== "word" && token.kind !== "quoted") continue;
		if (token.kind === "word" && NOT_NAMES.has(token.text.toUpperCase())) continue;
		const lower = (token.kind === "word" ? token.text : token.name).toLowerCase();
		const previous = significant[i - 1]?.token;
		const next = significant[i + 1]?.token;
		if (previous?.text === ".") continue;
		const position = tablePosition(significant, i, isCreateIndex);
		if (position === "dml" && MASTER_TABLES.has(lower)) {
			out[index] = upper(next) === "AS" || next !== void 0 && (next.kind === "word" || next.kind === "quoted") && !CLAUSE_KEYWORDS.has(upper(next)) ? _classPrivateFieldGet2(_masterView, this) : `${_classPrivateFieldGet2(_masterView, this)} AS ${token.text}`;
			continue;
		}
		if (lower.startsWith("sqlite_")) continue;
		if (position === "ddl") {
			names.add(lower);
			out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
			continue;
		}
		if (position === "dml" && names.has(lower)) {
			out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
			continue;
		}
		if (next?.text === "." && significant[i + 2] !== void 0 && names.has(lower)) {
			out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
			continue;
		}
		if (lower.startsWith("pragma_") && next?.text === "(") {
			const argument = significant[i + 2];
			if (argument?.token.kind === "string") {
				const inner = argument.token.text.slice(1, -1);
				if (names.has(inner.toLowerCase())) out[argument.index] = `'${_classPrivateFieldGet2(_prefix, this)}${inner}'`;
			}
		}
		if (firstWord === "PRAGMA" && previous?.text === "(" && names.has(lower)) out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
	}
	const pragma = significant[2]?.token.text === "." ? significant[3]?.token : significant[1]?.token;
	const pragmaName = pragma?.kind === "word" ? pragma.text : pragma?.kind === "quoted" ? pragma.name : "";
	if (firstWord === "PRAGMA" && SCHEMA_PRAGMAS.has(pragmaName.toLowerCase())) for (let i = 1; i < significant.length; i++) {
		const { token, index } = significant[i];
		if (token.kind !== "string" || significant[i - 1]?.token.text !== "(") continue;
		const inner = token.text.slice(1, -1);
		if (names.has(inner.toLowerCase())) out[index] = `'${_classPrivateFieldGet2(_prefix, this)}${inner}'`;
	}
	return out.join("");
}
function _prefixed(token) {
	return token.kind === "word" ? `${_classPrivateFieldGet2(_prefix, this)}${token.text}` : `${token.open}${_classPrivateFieldGet2(_prefix, this)}${token.name}${token.close}`;
}
/** Whether the identifier at `i` names a table in DDL, DML, or neither. */
function tablePosition(significant, i, isCreateIndex) {
	let j = i - 1;
	if (upper(significant[j]?.token) === "EXISTS") {
		j -= 1;
		if (upper(significant[j]?.token) === "NOT") j -= 1;
		if (upper(significant[j]?.token) !== "IF") return void 0;
		j -= 1;
	}
	const keyword = upper(significant[j]?.token);
	if (DDL_KEYWORDS.has(keyword)) return "ddl";
	if (keyword === "TO" && upper(significant[j - 1]?.token) === "RENAME") return "ddl";
	if (keyword === "ON" && isCreateIndex) return "ddl";
	if (DML_KEYWORDS.has(keyword)) return "dml";
}
function upper(token) {
	return token?.kind === "word" ? token.text.toUpperCase() : "";
}
/**
* Splits SQL into identifiers, literals, and everything else. Comments are
* kept as whitespace so positions stay intact.
*/
function tokenize(sql) {
	const tokens = [];
	let i = 0;
	while (i < sql.length) {
		const char = sql[i];
		if (/\s/.test(char)) {
			let j = i + 1;
			while (j < sql.length && /\s/.test(sql[j])) j++;
			tokens.push({
				kind: "space",
				text: sql.slice(i, j)
			});
			i = j;
		} else if (char === "-" && sql[i + 1] === "-") {
			const end = sql.indexOf("\n", i);
			const j = end === -1 ? sql.length : end;
			tokens.push({
				kind: "space",
				text: sql.slice(i, j)
			});
			i = j;
		} else if (char === "/" && sql[i + 1] === "*") {
			const end = sql.indexOf("*/", i + 2);
			const j = end === -1 ? sql.length : end + 2;
			tokens.push({
				kind: "space",
				text: sql.slice(i, j)
			});
			i = j;
		} else if (char === "'") {
			const j = closing(sql, i, "'");
			tokens.push({
				kind: "string",
				text: sql.slice(i, j)
			});
			i = j;
		} else if (char === "\"" || char === "`" || char === "[") {
			const close = char === "[" ? "]" : char;
			const j = closing(sql, i, close);
			const text = sql.slice(i, j);
			const name = text.slice(1, -1).replaceAll(close === "]" ? "]]" : close + close, close);
			tokens.push({
				kind: "quoted",
				text,
				name,
				open: char,
				close
			});
			i = j;
		} else if (/[A-Za-z_]/.test(char)) {
			let j = i + 1;
			while (j < sql.length && /[A-Za-z0-9_$]/.test(sql[j])) j++;
			tokens.push({
				kind: "word",
				text: sql.slice(i, j)
			});
			i = j;
		} else {
			tokens.push({
				kind: "punct",
				text: char
			});
			i++;
		}
	}
	return tokens;
}
/** The index just past the quote that closes the one at `start`. */
function closing(sql, start, close) {
	let j = start + 1;
	while (j < sql.length) {
		if (sql[j] === close) {
			if (close !== "]" && sql[j + 1] === close) {
				j += 2;
				continue;
			}
			return j + 1;
		}
		j++;
	}
	return sql.length;
}
//#endregion
//#region src/harness/opencode/operation-id.ts
const OPERATION_ID = /^[A-Za-z0-9._~-]+$/;
/** OpenCode's id for the user message an operation submits. */
function messageIdOf(operationId) {
	if (!OPERATION_ID.test(operationId)) throw new TypeError(`Invalid operation id ${JSON.stringify(operationId)}: use letters, digits, ".", "_", "~" and "-"`);
	return `msg_${operationId}`;
}
/** The operation id of a message or inbox item, the inverse of `messageIdOf`. */
function operationIdOf(messageId) {
	return messageId.startsWith("msg_") ? messageId.slice(4) : messageId;
}
//#endregion
//#region src/harness/opencode/events.ts
/** The text parts of a tool's result, joined. */
function toolText(content) {
	return (content ?? []).map((part) => "text" in part ? part.text : "").join("\n");
}
/** The session an OpenCode event belongs to, if it belongs to one. */
function sessionIdOf(event) {
	const data = event.data;
	return typeof data === "object" && data !== null && "sessionID" in data && typeof data.sessionID === "string" ? data.sessionID : void 0;
}
function answerOf(message) {
	if (message.type !== "assistant") return void 0;
	const text = /* @__PURE__ */ new Map();
	for (const part of message.content) if (part.type === "text") text.set(text.size, part.text);
	return {
		id: message.id,
		text,
		error: message.error?.message
	};
}
var _session = /* @__PURE__ */ new WeakMap();
var _toolNames = /* @__PURE__ */ new WeakMap();
var _answering = /* @__PURE__ */ new WeakMap();
var _ended = /* @__PURE__ */ new WeakMap();
var _answered = /* @__PURE__ */ new WeakMap();
var _answer = /* @__PURE__ */ new WeakMap();
var _SessionEventProjector_brand = /* @__PURE__ */ new WeakSet();
/**
* Translates one session's OpenCode events into `OpenCodeSessionEvent`s.
*
* OpenCode's events leave out some of what the translated ones carry, so
* the projector keeps it: tool names (`session.tool.called` has only the
* call id), and which inputs the current answer is for, so it can report
* `operation_end`. It starts from the stream's snapshot, so a stream opened
* mid-run knows the run's inputs and tools.
*
* `operation_end` follows the rule `wait()` uses on the transcript: an
* input's answer ends at the next input that joins after an assistant
* message, or at the end of the run.
*
* Events that arrive while the snapshot is read are projected after it, so
* some repeat what the snapshot shows. Projection is idempotent for those:
* an input the projector already knows is not started or ended again.
*/
var SessionEventProjector = class {
	constructor(snapshot) {
		_classPrivateMethodInitSpec(this, _SessionEventProjector_brand);
		_classPrivateFieldInitSpec(this, _session, void 0);
		_classPrivateFieldInitSpec(this, _toolNames, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _answering, []);
		_classPrivateFieldInitSpec(this, _ended, /* @__PURE__ */ new Set());
		_classPrivateFieldInitSpec(this, _answered, false);
		_classPrivateFieldInitSpec(this, _answer, void 0);
		_classPrivateFieldSet2(_session, this, snapshot.session);
		for (const message of snapshot.messages) {
			if (message.type !== "assistant") continue;
			for (const part of message.content) if (part.type === "tool") _classPrivateFieldGet2(_toolNames, this).set(part.id, part.name);
		}
		_classPrivateFieldSet2(_answering, this, snapshot.pending.filter((operation) => operation.status === "running").map((operation) => operation.operationId));
		if (_classPrivateFieldGet2(_answering, this).length > 0) {
			const last = snapshot.messages.at(-1);
			_classPrivateFieldSet2(_answer, this, last === void 0 ? void 0 : answerOf(last));
			_classPrivateFieldSet2(_answered, this, _classPrivateFieldGet2(_answer, this) !== void 0);
		}
	}
	/** The translated events for one OpenCode event; often none. */
	project(event) {
		switch (event.type) {
			case "session.inbox.enqueued": return event.data.item.type === "user" ? [{
				type: "operation_queued",
				operationId: operationIdOf(event.data.inboxID)
			}] : [];
			case "session.inbox.cancelled": return [{
				type: "operation_withdrawn",
				operationId: operationIdOf(event.data.inboxID)
			}];
			case "session.inbox.delivered": {
				const operationId = operationIdOf(event.data.inboxID);
				if (_classPrivateFieldGet2(_answering, this).includes(operationId) || _classPrivateFieldGet2(_ended, this).has(operationId)) return [];
				const ended = _classPrivateFieldGet2(_answered, this) ? _assertClassBrand(_SessionEventProjector_brand, this, _settle).call(this, "succeeded") : [];
				_classPrivateFieldGet2(_answering, this).push(operationId);
				return [...ended, {
					type: "operation_start",
					operationId
				}];
			}
			case "session.execution.started":
				if (_classPrivateFieldGet2(_answering, this).length === 0) {
					_classPrivateFieldSet2(_answer, this, void 0);
					_classPrivateFieldSet2(_answered, this, false);
				}
				return [{ type: "run_start" }];
			case "session.execution.succeeded": return [..._assertClassBrand(_SessionEventProjector_brand, this, _settle).call(this, "succeeded"), runEnd("succeeded")];
			case "session.execution.failed": return [..._assertClassBrand(_SessionEventProjector_brand, this, _settle).call(this, "failed", event.data.error.message), runEnd("failed", event.data.error.message)];
			case "session.execution.interrupted": return [..._assertClassBrand(_SessionEventProjector_brand, this, _settle).call(this, "interrupted"), runEnd("interrupted")];
			case "session.step.started":
				_classPrivateFieldSet2(_answered, this, true);
				_classPrivateFieldSet2(_answer, this, {
					id: event.data.assistantMessageID,
					text: /* @__PURE__ */ new Map(),
					error: void 0
				});
				return [{
					type: "message_start",
					messageId: event.data.assistantMessageID,
					agent: event.data.agent,
					model: {
						providerID: event.data.model.providerID,
						id: event.data.model.id
					}
				}];
			case "session.step.ended": return [{
				type: "message_end",
				messageId: event.data.assistantMessageID,
				finish: event.data.finish
			}];
			case "session.step.failed":
				if (_classPrivateFieldGet2(_answer, this)?.id === event.data.assistantMessageID) _classPrivateFieldGet2(_answer, this).error = event.data.error.message;
				return [{
					type: "message_end",
					messageId: event.data.assistantMessageID,
					finish: "error",
					error: event.data.error.message
				}];
			case "session.text.delta":
			case "session.reasoning.delta": return [{
				type: event.type === "session.text.delta" ? "text_delta" : "reasoning_delta",
				messageId: event.data.assistantMessageID,
				ordinal: event.data.ordinal,
				delta: event.data.delta
			}];
			case "session.text.ended":
				if (_classPrivateFieldGet2(_answer, this)?.id === event.data.assistantMessageID) _classPrivateFieldGet2(_answer, this).text.set(event.data.ordinal, event.data.text);
				return [{
					type: "text_end",
					messageId: event.data.assistantMessageID,
					ordinal: event.data.ordinal,
					text: event.data.text
				}];
			case "session.reasoning.ended": return [{
				type: "reasoning_end",
				messageId: event.data.assistantMessageID,
				ordinal: event.data.ordinal,
				text: event.data.text
			}];
			case "session.tool.input.started":
				_classPrivateFieldGet2(_toolNames, this).set(event.data.id, event.data.name);
				return [];
			case "session.tool.called": return [{
				type: "tool_start",
				messageId: event.data.assistantMessageID,
				toolCallId: event.data.id,
				name: _classPrivateFieldGet2(_toolNames, this).get(event.data.id) ?? "",
				input: event.data.input
			}];
			case "session.tool.success": return [{
				type: "tool_end",
				messageId: event.data.assistantMessageID,
				toolCallId: event.data.id,
				name: _classPrivateFieldGet2(_toolNames, this).get(event.data.id) ?? "",
				error: false,
				output: toolText(event.data.content)
			}];
			case "session.tool.failed": return [{
				type: "tool_end",
				messageId: event.data.assistantMessageID,
				toolCallId: event.data.id,
				name: _classPrivateFieldGet2(_toolNames, this).get(event.data.id) ?? "",
				error: true,
				output: event.data.error.message
			}];
			case "session.retry.scheduled": return [{
				type: "retry_scheduled",
				messageId: event.data.assistantMessageID,
				attempt: event.data.attempt,
				at: event.data.at,
				error: event.data.error.message
			}];
			case "session.compaction.started": return [{
				type: "compaction_start",
				reason: event.data.reason
			}];
			case "session.compaction.ended": return [{
				type: "transcript_reset",
				reason: "compaction"
			}];
			case "session.compaction.failed": return [{
				type: "fault",
				code: "compaction_failed",
				message: event.data.error.message
			}];
			case "permission.asked": return [{
				type: "permission_asked",
				permission: {
					id: event.data.id,
					session: event.data.sessionID,
					action: event.data.action,
					resources: event.data.resources,
					...event.data.message === void 0 ? {} : { message: event.data.message }
				}
			}];
			case "permission.replied": return [{
				type: "permission_replied",
				permissionId: event.data.requestID,
				reply: event.data.reply
			}];
			default: return [];
		}
	}
};
/** End every input the latest answer was for. */
function _settle(outcome, runError) {
	const answer = _classPrivateFieldGet2(_answer, this);
	const text = [...(answer?.text ?? /* @__PURE__ */ new Map()).entries()].sort(([a], [b]) => a - b).map(([, part]) => part).join("");
	const error = answer?.error ?? runError;
	const ended = _classPrivateFieldGet2(_answering, this).map((operationId) => {
		_classPrivateFieldGet2(_ended, this).add(operationId);
		return {
			type: "operation_end",
			result: resultOf(_classPrivateFieldGet2(_session, this), operationId, outcome, text, error)
		};
	});
	_classPrivateFieldSet2(_answering, this, []);
	_classPrivateFieldSet2(_answered, this, false);
	_classPrivateFieldSet2(_answer, this, void 0);
	return ended;
}
function runEnd(outcome, error) {
	return {
		type: "run_end",
		outcome,
		...error === void 0 ? {} : { error }
	};
}
/** The result `wait()` would return for the same ending. */
function resultOf(session, operationId, outcome, text, error) {
	if (outcome === "succeeded" && error === void 0) return {
		operationId,
		session,
		status: "done",
		text
	};
	return {
		operationId,
		session,
		status: "unanswered",
		reason: outcome === "interrupted" ? "interrupted" : error ?? "failed"
	};
}
//#endregion
//#region src/harness/opencode/harness.ts
/** The root session's id. OpenCode session ids start with `ses`. */
const ROOT_SESSION = "ses_root";
/** Every table and index OpenCode creates is stored under this prefix. */
const TABLE_PREFIX = "opencode_";
/**
* Longest one wake waits on OpenCode. It waits inside an alarm invocation,
* which has a 15 minute wall-time limit, so a longer run is waited on across
* several alarms.
*/
const WAIT_BUDGET_MS = 10 * 6e4;
/**
* The wake job's heartbeat while it waits. If the object is evicted, the job
* is still due and its alarm restarts the object.
*/
const HEARTBEAT_MS = 3e4;
/**
* How long OpenCode stays open after the harness last used it. An open
* OpenCode host runs periodic background work (catalog refreshes, local
* provider discovery, cleanups) whose timers keep the object in memory, so
* the harness closes it once nothing is running and reopens it on the next
* call.
*/
const IDLE_CLOSE_MS = 3e4;
/** Messages read per page when looking an operation up. */
const MESSAGE_PAGE = 50;
const WAKE_FN = "wake";
const CLOSE_FN = "close";
const CLOSE_JOB_ID = "opencode-close";
function wakeJobId(session) {
	return `opencode-wake:${session}`;
}
function sessionOfJob(payload) {
	return typeof payload === "object" && payload !== null && "session" in payload && typeof payload.session === "string" ? payload.session : void 0;
}
/** Whether an OpenCode client error is the tagged error `tag`. */
function isTagged(error, tag) {
	return typeof error === "object" && error !== null && "_tag" in error && error._tag === tag;
}
/** OpenCode's client errors are tagged objects; give them a message. */
function describe(error) {
	if (error instanceof Error) return error.message;
	if (typeof error === "object" && error !== null) return `${"_tag" in error ? String(error._tag) : "Error"}${"message" in error && typeof error.message === "string" ? `: ${error.message}` : ""}`;
	return String(error);
}
function textOf(message) {
	if (message?.type !== "assistant") return "";
	return message.content.map((part) => part.type === "text" ? part.text : "").join("");
}
var _options = /* @__PURE__ */ new WeakMap();
var _waits = /* @__PURE__ */ new WeakMap();
var _admitting = /* @__PURE__ */ new WeakMap();
var _waitBudgetMs = /* @__PURE__ */ new WeakMap();
var _heartbeatMs = /* @__PURE__ */ new WeakMap();
var _idleCloseMs = /* @__PURE__ */ new WeakMap();
var _leases = /* @__PURE__ */ new WeakMap();
var _opening = /* @__PURE__ */ new WeakMap();
var _OpenCodeHarness_brand = /* @__PURE__ */ new WeakSet();
/**
* OpenCode v2 hosted in a Durable Object, behind the same harness interface
* as `PiHarness`: `harness.prompt()`, `harness.sessions`, `harness.session(id)`.
* How a session reaches a client (sockets, SSE, RPC) is the host's glue,
* built on `session.events()` and `session.log()`.
*
* OpenCode owns everything about a run: the transcript, the inbox of steers
* and follow-ups, model and tool steps, retries, and crash recovery. It keeps
* all of it in its own tables in this object's SQLite database, under the
* `opencode_` prefix.
*
* What OpenCode cannot do on a Durable Object is wake itself: it resumes an
* interrupted turn when it boots, but an evicted object does not boot until
* something calls it. The harness is that wake, with one Lifecycle job per
* session. Input goes to OpenCode once, in `submit()`, after the session's
* job is scheduled. The job waits while the session runs, rescheduling itself
* as a heartbeat, and completes when the session is idle with nothing left
* to do. An eviction mid-run leaves the job due, so its alarm restarts the
* object, OpenCode boots and resumes the turn, and the job waits again.
*
* @experimental The API may change between releases.
*/
var OpenCodeHarness = class extends LifecycleCapability {
	/**
	* @param options - How to open OpenCode, and what new sessions start with.
	*/
	constructor(options) {
		super("opencode-harness");
		_classPrivateMethodInitSpec(this, _OpenCodeHarness_brand);
		_classPrivateFieldInitSpec(this, _options, void 0);
		_classPrivateFieldInitSpec(this, _waits, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _admitting, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _waitBudgetMs, WAIT_BUDGET_MS);
		_classPrivateFieldInitSpec(this, _heartbeatMs, HEARTBEAT_MS);
		_classPrivateFieldInitSpec(this, _idleCloseMs, IDLE_CLOSE_MS);
		_classPrivateFieldInitSpec(this, _leases, 0);
		_classPrivateFieldInitSpec(this, _opening, void 0);
		_classPrivateFieldSet2(_options, this, options);
		this.sessions = new OpenCodeSessions(this);
	}
	/** Open OpenCode, which resumes interrupted turns, and wake running sessions. */
	async onStart(_context) {
		const opencode = await _assertClassBrand(_OpenCodeHarness_brand, this, _open).call(this);
		for (const session of Object.keys(await opencode.sessions.active())) await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
		await _assertClassBrand(_OpenCodeHarness_brand, this, _scheduleClose).call(this);
	}
	/** Run one step of a session's wake job. */
	async onJob(context) {
		if (context.job.fn === CLOSE_FN) return _assertClassBrand(_OpenCodeHarness_brand, this, _closeStep).call(this);
		if (context.job.fn !== WAKE_FN) return;
		const session = sessionOfJob(context.job.payload);
		if (session !== void 0) return _assertClassBrand(_OpenCodeHarness_brand, this, _wakeStep).call(this, session);
	}
	/** Close OpenCode's in-memory resources. Durable state is untouched. */
	async dispose() {
		const opening = _classPrivateFieldGet2(_opening, this);
		_classPrivateFieldSet2(_opening, this, void 0);
		await (await opening?.catch(() => void 0))?.close();
	}
	/**
	* A handle on one session. No I/O until you call it.
	*
	* @param id - The session; default the root session.
	*/
	session(id = ROOT_SESSION) {
		return new OpenCodeSession(this, id);
	}
	/** Submit a prompt and wait for its answer. */
	prompt(input, options = {}) {
		return this.session(options.session).prompt(input, options);
	}
	/** Durably submit a prompt. Resolves before the model runs. */
	submit(input, options = {}) {
		return this.session(options.session).submit(input, options);
	}
	/** Stop one operation, or everything running in a session. */
	abort(options = {}) {
		return this.session(options.session).abort(options.operationId);
	}
	/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
	wait(operationId, options = {}) {
		return this.session(options.session).wait(operationId, options.signal);
	}
	/** The session's active transcript, as OpenCode's messages. */
	messages(options = {}) {
		return this.session(options.session).messages();
	}
	/** Submissions OpenCode has not settled yet, oldest first. */
	pending(options = {}) {
		return this.use((opencode) => _assertClassBrand(_OpenCodeHarness_brand, this, _pending).call(this, opencode, options.session));
	}
	/**
	* The opened OpenCode host, for anything the interface does not cover.
	* The harness closes an idle host, so call this again each time rather
	* than keeping the result; work done through it does not keep it open.
	*/
	opencode() {
		return this.use(async (opencode) => opencode);
	}
	/**
	* @internal Run `call` with OpenCode open. The host is not closed while
	* any call holds it.
	*/
	async use(call) {
		const release = this.lease();
		try {
			return await call(await _assertClassBrand(_OpenCodeHarness_brand, this, _open).call(this));
		} finally {
			await release();
		}
	}
	/**
	* @internal Hold OpenCode open until the returned release is called, for
	* work that outlives one call, such as an event stream.
	*/
	lease() {
		_classPrivateFieldSet2(_leases, this, _classPrivateFieldGet2(_leases, this) + 1);
		let released = false;
		return async () => {
			if (released) return;
			released = true;
			_classPrivateFieldSet2(_leases, this, _classPrivateFieldGet2(_leases, this) - 1);
			if (_classPrivateFieldGet2(_leases, this) === 0) await _assertClassBrand(_OpenCodeHarness_brand, this, _scheduleClose).call(this);
		};
	}
	/** @internal Admit an input to OpenCode once, behind the session's wake. */
	async enqueue(session, input, options) {
		const operationId = options.operationId ?? crypto.randomUUID();
		const messageId = messageIdOf(operationId);
		return this.use(async (opencode) => {
			const content = typeof input === "string" ? { text: input } : input;
			_classPrivateFieldGet2(_admitting, this).set(session, (_classPrivateFieldGet2(_admitting, this).get(session) ?? 0) + 1);
			let accepted;
			try {
				await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
				accepted = !await _assertClassBrand(_OpenCodeHarness_brand, this, _known).call(this, opencode, session, messageId);
				await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.prompt({
					...content,
					sessionID: session,
					id: messageId,
					delivery: options.whenBusy === "steer" ? "steer" : "queue",
					resume: true
				}));
			} finally {
				const left = (_classPrivateFieldGet2(_admitting, this).get(session) ?? 1) - 1;
				if (left === 0) _classPrivateFieldGet2(_admitting, this).delete(session);
				else _classPrivateFieldGet2(_admitting, this).set(session, left);
			}
			await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
			return {
				operationId,
				session,
				accepted
			};
		});
	}
	/** @internal Withdraw a queued input, or interrupt the run it joined. */
	async withdraw(session, operationId) {
		const messageId = messageIdOf(operationId);
		return this.use(async (opencode) => {
			switch ((await _assertClassBrand(_OpenCodeHarness_brand, this, _state).call(this, opencode, session, messageId))._tag) {
				case "queued":
					await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.cancel({
						sessionID: session,
						inboxID: messageId
					}));
					return true;
				case "running": return (await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.interrupt({ sessionID: session }))).interrupted;
				case "missing":
				case "settled": return false;
			}
		});
	}
	/** @internal Withdraw every queued input and interrupt the running work. */
	interrupt(session) {
		return this.use(async (opencode) => {
			for (const item of await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.list({ sessionID: session }))) await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.cancel({
				sessionID: session,
				inboxID: item.id
			}));
			await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.interrupt({ sessionID: session }));
		});
	}
	/** @internal Wait for OpenCode to settle an operation, by its id. */
	async settled(session, operationId, signal) {
		const messageId = messageIdOf(operationId);
		return this.use(async (opencode) => {
			for (;;) {
				signal?.throwIfAborted();
				const state = await _assertClassBrand(_OpenCodeHarness_brand, this, _state).call(this, opencode, session, messageId);
				switch (state._tag) {
					case "missing": return {
						operationId,
						session,
						status: "unanswered",
						reason: "not_found"
					};
					case "settled": return state.outcome === "succeeded" ? {
						operationId,
						session,
						status: "done",
						text: state.text
					} : {
						operationId,
						session,
						status: "unanswered",
						reason: state.outcome === "failed" ? state.error ?? "failed" : "interrupted"
					};
					case "queued":
					case "running":
						await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.wait({ sessionID: session }, signal ? { signal } : void 0));
						if ((await opencode.sessions.active())[session] === void 0) {
							await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
							await delay(_classPrivateFieldGet2(_heartbeatMs, this) / 30, signal);
						}
				}
			}
		});
	}
	/** @internal Create a session with the harness defaults. */
	async create(options = {}) {
		return this.use((opencode) => _assertClassBrand(_OpenCodeHarness_brand, this, _createSession).call(this, opencode, options.id));
	}
};
async function _pending(opencode, only) {
	const sessions = only === void 0 ? (await listSessions(opencode)).map((info) => info.id) : [only];
	const active = await opencode.sessions.active();
	const pending = [];
	for (const session of sessions) {
		if (active[session] !== void 0 || await unsettled(opencode, session)) for (const operationId of await _assertClassBrand(_OpenCodeHarness_brand, this, _running).call(this, opencode, session)) pending.push({
			operationId,
			session,
			status: "running"
		});
		for (const item of await opencode.sessions.inbox.list({ sessionID: session })) pending.push({
			operationId: operationIdOf(item.id),
			session,
			status: "queued"
		});
	}
	return pending;
}
/** Call OpenCode, turning its tagged errors into `Error`s. */
async function _call(call) {
	try {
		return await call();
	} catch (error) {
		if (error instanceof Error) throw error;
		throw new Error(`OpenCode: ${describe(error)}`, { cause: error });
	}
}
/** The session's info, or undefined when it does not exist. */
async function _info(opencode, session) {
	try {
		return await opencode.sessions.get({ sessionID: session });
	} catch (error) {
		if (isTagged(error, "SessionNotFoundError")) return void 0;
		throw new Error(`OpenCode: ${describe(error)}`, { cause: error });
	}
}
async function _createSession(opencode, id) {
	const defaults = _classPrivateFieldGet2(_options, this).defaults;
	return (await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.create({
		...id === void 0 ? {} : { id },
		...defaults?.model === void 0 ? {} : { model: {
			providerID: defaults.model.providerID,
			id: defaults.model.id
		} },
		...defaults?.agent === void 0 ? {} : { agent: defaults.agent }
	}))).id;
}
/** Schedule the session's wake job now, or pull it forward. */
function _wake(session, time = Date.now()) {
	return this.lifecycle.jobs.push({
		id: wakeJobId(session),
		fn: WAKE_FN,
		time,
		payload: { session },
		singleflight: true,
		recoveryLoop: true
	});
}
/**
* One run of a session's wake job. It never admits anything new: it waits
* while the session runs and completes when it is idle with nothing left
* to do. OpenCode does all the work in between.
*/
async function _wakeStep(session) {
	const heartbeat = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
	if (_classPrivateFieldGet2(_waits, this).has(session)) return heartbeat;
	const opencode = await _assertClassBrand(_OpenCodeHarness_brand, this, _open).call(this);
	if (await _assertClassBrand(_OpenCodeHarness_brand, this, _info).call(this, opencode, session) === void 0) return void 0;
	const inbox = await opencode.sessions.inbox.list({ sessionID: session });
	const open = await unsettled(opencode, session);
	if (!((await opencode.sessions.active())[session] !== void 0)) {
		const first = inbox.find((item) => item.type !== "move");
		if (first !== void 0) await _assertClassBrand(_OpenCodeHarness_brand, this, _ring).call(this, opencode, session, first);
		else if (open) return heartbeat;
		else return _classPrivateFieldGet2(_admitting, this).has(session) ? heartbeat : void 0;
	}
	const wait = _assertClassBrand(_OpenCodeHarness_brand, this, _waitForIdle).call(this, opencode, session).finally(() => {
		_classPrivateFieldGet2(_waits, this).delete(session);
		_assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
	});
	_classPrivateFieldGet2(_waits, this).set(session, wait);
	this.lifecycle.trackAlarmWork(wait);
	return heartbeat;
}
async function _waitForIdle(opencode, session) {
	const budget = new AbortController();
	const timer = setTimeout(() => budget.abort(), _classPrivateFieldGet2(_waitBudgetMs, this));
	try {
		await opencode.sessions.wait({ sessionID: session }, { signal: budget.signal });
	} catch (error) {
		if (!budget.signal.aborted) this.lifecycle.events.emit("opencode:wake_error", {
			session,
			error: describe(error)
		});
	} finally {
		clearTimeout(timer);
	}
}
/** Re-admit an inbox item by its id, which starts the session again. */
async function _ring(opencode, session, item) {
	switch (item.type) {
		case "user":
			await opencode.sessions.prompt({
				text: item.payload.text,
				sessionID: session,
				id: item.id,
				delivery: item.delivery,
				resume: true
			});
			return;
		case "synthetic":
			await opencode.sessions.synthetic({
				text: item.payload.text,
				sessionID: session,
				id: item.id,
				delivery: item.delivery,
				resume: true
			});
			return;
		case "compaction":
			await opencode.sessions.compact({
				sessionID: session,
				id: item.id,
				delivery: item.delivery
			});
			return;
		case "move": return;
	}
}
/** Schedule the idle close, unless one is already due late enough. */
async function _scheduleClose() {
	const time = Date.now() + _classPrivateFieldGet2(_idleCloseMs, this);
	const pending = this.lifecycle.jobs.get(CLOSE_JOB_ID);
	if (pending !== void 0 && pending.time >= time - _classPrivateFieldGet2(_idleCloseMs, this) / 2) return;
	await this.lifecycle.jobs.push({
		id: CLOSE_JOB_ID,
		fn: CLOSE_FN,
		time,
		payload: {},
		singleflight: true
	});
}
/** Close OpenCode if nothing is using it or running in it. */
async function _closeStep() {
	const opening = _classPrivateFieldGet2(_opening, this);
	if (opening === void 0) return void 0;
	const later = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_idleCloseMs, this) };
	const busy = () => _classPrivateFieldGet2(_leases, this) > 0 || _classPrivateFieldGet2(_waits, this).size > 0 || _classPrivateFieldGet2(_admitting, this).size > 0 || this.lifecycle.jobs.list().some((job) => job.fn === WAKE_FN);
	if (busy()) return later;
	const opencode = await opening.catch(() => void 0);
	if (opencode === void 0) return void 0;
	if (Object.keys(await opencode.sessions.active()).length > 0) return later;
	if (busy() || _classPrivateFieldGet2(_opening, this) !== opening) return later;
	_classPrivateFieldSet2(_opening, this, void 0);
	await opencode.close();
}
/**
* OpenCode, opened. Waits for Lifecycle startup first, so it is only ever
* opened inside it: startup holds the input gate (`blockConcurrencyWhile`)
* and awaits the open in `onStart`, so an open begun before startup would
* be awaited behind a closed gate and never finish.
*/
async function _open() {
	await this.lifecycle.ready();
	_classPrivateFieldGet2(_opening, this) ?? _classPrivateFieldSet2(_opening, this, _assertClassBrand(_OpenCodeHarness_brand, this, _doOpen).call(this).catch((error) => {
		_classPrivateFieldSet2(_opening, this, void 0);
		throw error;
	}));
	return _classPrivateFieldGet2(_opening, this);
}
async function _doOpen() {
	const storage = prefixTables(this.lifecycle.storage, TABLE_PREFIX);
	const opencode = await _classPrivateFieldGet2(_options, this).opencode({ storage });
	try {
		await opencode.sessions.get({ sessionID: ROOT_SESSION });
	} catch (error) {
		if (!isTagged(error, "SessionNotFoundError")) {
			await opencode.close();
			throw new Error(`OpenCode: ${describe(error)}`, { cause: error });
		}
		await _assertClassBrand(_OpenCodeHarness_brand, this, _createSession).call(this, opencode, ROOT_SESSION);
	}
	return opencode;
}
/** Whether OpenCode has the message, as an inbox item or in the transcript. */
async function _known(opencode, session, messageId) {
	return (await _assertClassBrand(_OpenCodeHarness_brand, this, _state).call(this, opencode, session, messageId))._tag !== "missing";
}
/**
* Where an operation is. Reads the transcript newest first, back to the
* operation's message, then finds the first idle marker after it: the end
* of the run that answered it.
*/
async function _state(opencode, session, messageId) {
	if ((await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.list({ sessionID: session }))).some((item) => item.id === messageId)) return { _tag: "queued" };
	const after = await _assertClassBrand(_OpenCodeHarness_brand, this, _messagesAfter).call(this, opencode, session, messageId);
	if (after === void 0) return { _tag: "missing" };
	let last;
	for (const message of after) {
		if (message.type === "idle") return settledState(message.outcome, last);
		if (message.type === "user" && last !== void 0) return settledState("succeeded", last);
		if (message.type === "assistant") last = message;
	}
	return { _tag: "running" };
}
/**
* The messages after `messageId`, oldest first, or undefined when the
* transcript has no such message.
*/
async function _messagesAfter(opencode, session, messageId) {
	const newer = [];
	let cursor;
	for (;;) {
		const page = await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.message.list(cursor === void 0 ? {
			sessionID: session,
			order: "desc",
			limit: MESSAGE_PAGE
		} : {
			sessionID: session,
			cursor
		}));
		for (const message of page.data) {
			if (message.id === messageId) return newer.reverse();
			newer.push(message);
		}
		const next = page.cursor.next;
		if (next === void 0 || next === null) return void 0;
		cursor = next;
	}
}
/**
* Operations the running turn is answering: the newest user messages, back
* to the assistant message or idle marker before them. Earlier inputs of
* the same run already have their answer.
*/
async function _running(opencode, session) {
	const running = [];
	let cursor;
	for (;;) {
		const page = await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.message.list(cursor === void 0 ? {
			sessionID: session,
			order: "desc",
			limit: MESSAGE_PAGE
		} : {
			sessionID: session,
			cursor
		}));
		for (const message of page.data) if (message.type === "user") running.push(operationIdOf(message.id));
		else if (message.type === "idle" || message.type === "assistant" && running.length > 0) return running.reverse();
		const next = page.cursor.next;
		if (next === void 0 || next === null) return running.reverse();
		cursor = next;
	}
}
/**
* Whether the transcript ends in a turn with no idle marker after it: a run
* in progress, or one a restart cut off that OpenCode resumes on boot.
*/
async function unsettled(opencode, session) {
	let cursor;
	for (;;) {
		const page = await opencode.message.list(cursor === void 0 ? {
			sessionID: session,
			order: "desc",
			limit: MESSAGE_PAGE
		} : {
			sessionID: session,
			cursor
		});
		for (const message of page.data) {
			if (message.type === "idle") return false;
			if (message.type === "user" || message.type === "synthetic") return true;
		}
		const next = page.cursor.next;
		if (next === void 0 || next === null) return false;
		cursor = next;
	}
}
/**
* Whether the session has work: running, input waiting in its inbox, or a
* turn a restart cut off, which OpenCode resumes in the background on boot
* and so is not running yet.
*/
async function sessionBusy(opencode, session, active) {
	if (active[session] !== void 0) return true;
	return (await opencode.sessions.inbox.list({ sessionID: session })).length > 0 || await unsettled(opencode, session);
}
/** Every session, with whether it has work. */
async function listSessions(opencode) {
	const active = await opencode.sessions.active();
	const sessions = [];
	let cursor;
	for (;;) {
		const page = await opencode.sessions.list(cursor === void 0 ? {} : { cursor });
		for (const info of page.data) {
			const parent = info.fork?.sessionID ?? info.parentID;
			sessions.push({
				id: info.id,
				...parent === void 0 ? {} : { parent },
				...info.title === void 0 ? {} : { title: info.title },
				busy: await sessionBusy(opencode, info.id, active)
			});
		}
		const next = page.cursor.next;
		if (next === void 0 || next === null) return sessions;
		cursor = next;
	}
}
/** An operation's end, from the last assistant message of its run. */
function settledState(outcome, last) {
	const error = last?.type === "assistant" ? last.error?.message : void 0;
	return {
		_tag: "settled",
		outcome: error !== void 0 && outcome === "succeeded" ? "failed" : outcome,
		text: textOf(last),
		error
	};
}
/** Resolve after `ms`, or reject when `signal` aborts. */
function delay(ms, signal) {
	return new Promise((resolve, reject) => {
		const timer = setTimeout(resolve, ms);
		signal?.addEventListener("abort", () => {
			clearTimeout(timer);
			reject(signal.reason);
		}, { once: true });
	});
}
var _harness = /* @__PURE__ */ new WeakMap();
var _OpenCodeSession_brand = /* @__PURE__ */ new WeakSet();
/** One OpenCode session, addressed through the harness. */
var OpenCodeSession = class {
	/**
	* @param harness - The harness that hosts the session.
	* @param id - The session's id.
	*/
	constructor(harness, id) {
		_classPrivateMethodInitSpec(this, _OpenCodeSession_brand);
		_classPrivateFieldInitSpec(this, _harness, void 0);
		_classPrivateFieldSet2(_harness, this, harness);
		this.id = id;
	}
	/** Durably submit a prompt. Resolves before the model runs. */
	submit(input, options = {}) {
		return _classPrivateFieldGet2(_harness, this).enqueue(this.id, input, options);
	}
	/** Submit and wait for the answer and the updated transcript. */
	async prompt(input, options = {}) {
		const receipt = await this.submit(input, options);
		return {
			...await this.wait(receipt.operationId),
			messages: await this.messages()
		};
	}
	/** Join the running work at its next step. */
	steer(input, options = {}) {
		return this.submit(input, {
			...options,
			whenBusy: "steer"
		});
	}
	/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
	wait(operationId, signal) {
		return _classPrivateFieldGet2(_harness, this).settled(this.id, operationId, signal);
	}
	/**
	* Withdraw one queued operation (or interrupt the run it joined), or, with
	* no id, abort the session: OpenCode's queued inputs are withdrawn and the
	* running work is interrupted.
	*/
	async abort(operationId) {
		if (operationId !== void 0) return _classPrivateFieldGet2(_harness, this).withdraw(this.id, operationId);
		await _classPrivateFieldGet2(_harness, this).interrupt(this.id);
		return true;
	}
	/** Change this session's model; `agents/models/opencode` makes them. */
	async setModel(model) {
		await _classPrivateFieldGet2(_harness, this).use((opencode) => opencode.sessions.switchModel({
			sessionID: this.id,
			model: {
				providerID: model.providerID,
				id: model.id
			}
		}));
	}
	/** Change this session's OpenCode agent, such as `build` or `plan`. */
	async setAgent(agent) {
		await _classPrivateFieldGet2(_harness, this).use((opencode) => opencode.sessions.switchAgent({
			sessionID: this.id,
			agent
		}));
	}
	/** The active transcript: OpenCode's messages since the newest compaction. */
	async messages() {
		return _classPrivateFieldGet2(_harness, this).use((opencode) => opencode.sessions.context({ sessionID: this.id }));
	}
	/**
	* The whole transcript, oldest first, compacted messages included.
	* OpenCode compacts a long session on its own; `messages()` is what the
	* model sees after that, this is everything the session has said.
	*/
	history() {
		return _classPrivateFieldGet2(_harness, this).use(async (opencode) => {
			const messages = [];
			let cursor;
			for (;;) {
				const page = await opencode.message.list(cursor === void 0 ? {
					sessionID: this.id,
					order: "asc",
					limit: 100
				} : {
					sessionID: this.id,
					cursor
				});
				messages.push(...page.data);
				const next = page.cursor.next;
				if (next === void 0 || next === null) return messages;
				cursor = next;
			}
		});
	}
	/**
	* The session's state now, then everything that happens to it after, as
	* `OpenCodeSessionEvent`s: the same kinds of event `PiHarness` reports,
	* live text deltas included. The stream holds OpenCode open until
	* `stop()`. For OpenCode's own events, durable and resumable by sequence
	* number, use `log()`.
	*/
	async events() {
		const release = _classPrivateFieldGet2(_harness, this).lease();
		const controller = new AbortController();
		const early = [];
		const pending = [];
		let projector;
		let listener;
		let stopped = false;
		const deliver = (events) => {
			if (events.length === 0 || stopped) return;
			if (listener === void 0) pending.push(...events);
			else listener(events);
		};
		let attached;
		const subscribed = new Promise((resolve) => {
			attached = resolve;
		});
		let pump = Promise.resolve();
		try {
			const opencode = await _classPrivateFieldGet2(_harness, this).opencode();
			pump = (async () => {
				try {
					for await (const event of opencode.events.subscribe({ signal: controller.signal })) {
						if (event.type === "server.connected") attached();
						if (sessionIdOf(event) !== this.id) continue;
						if (projector === void 0) early.push(event);
						else deliver(projector.project(event));
					}
				} catch (error) {
					if (!controller.signal.aborted) deliver([{
						type: "fault",
						code: "events_failed",
						message: describe(error)
					}]);
				} finally {
					attached();
				}
			})();
			await subscribed;
			const snapshot = await _assertClassBrand(_OpenCodeSession_brand, this, _snapshot).call(this, opencode);
			projector = new SessionEventProjector(snapshot);
			for (const event of early.splice(0)) deliver(projector.project(event));
			let stopping;
			return {
				snapshot,
				start: (next) => {
					if (listener !== void 0) throw new Error("OpenCodeEventStream.start() was already called");
					if (stopping !== void 0) return;
					listener = next;
					deliver(pending.splice(0));
				},
				stop: () => {
					stopping ??= (async () => {
						stopped = true;
						pending.length = 0;
						controller.abort();
						await pump;
						await release();
					})();
					return stopping;
				}
			};
		} catch (error) {
			controller.abort();
			await pump;
			await release();
			throw error;
		}
	}
	/**
	* The session's durable events after sequence number `after` (from the
	* start by default), then, with `follow`, new ones as they are committed,
	* until `signal` aborts.
	*/
	async *log(options = {}) {
		const release = _classPrivateFieldGet2(_harness, this).lease();
		try {
			yield* (await _classPrivateFieldGet2(_harness, this).opencode()).sessions.log({
				sessionID: this.id,
				...options.after === void 0 ? {} : { after: options.after },
				...options.follow === void 0 ? {} : { follow: options.follow }
			}, options.signal ? { signal: options.signal } : void 0);
		} finally {
			await release();
		}
	}
	/**
	* Whether the session has work: running, input waiting, or a turn a
	* restart cut off that OpenCode is about to resume.
	*/
	async busy() {
		return _classPrivateFieldGet2(_harness, this).use(async (opencode) => sessionBusy(opencode, this.id, await opencode.sessions.active()));
	}
};
async function _snapshot(opencode) {
	const [messages, busy, pending, permissions] = await Promise.all([
		this.history(),
		this.busy(),
		_classPrivateFieldGet2(_harness, this).pending({ session: this.id }),
		opencode.permission.list({ sessionID: this.id })
	]);
	return {
		session: this.id,
		messages,
		busy,
		pending,
		permissions: permissions.map((request) => ({
			id: request.id,
			session: request.sessionID,
			action: request.action,
			resources: request.resources,
			...request.message === void 0 ? {} : { message: request.message }
		}))
	};
}
var _harness2 = /* @__PURE__ */ new WeakMap();
/** Every OpenCode session in this object. */
var OpenCodeSessions = class {
	/** @param harness - The harness that hosts the sessions. */
	constructor(harness) {
		_classPrivateFieldInitSpec(this, _harness2, void 0);
		_classPrivateFieldSet2(_harness2, this, harness);
	}
	/** A handle on one session. No I/O until you call it. */
	get(id) {
		return _classPrivateFieldGet2(_harness2, this).session(id);
	}
	/** A new top-level session, configured like the root. */
	async create() {
		return _classPrivateFieldGet2(_harness2, this).session(await _classPrivateFieldGet2(_harness2, this).create());
	}
	/** A new session that sees `from`'s history up to now. */
	async fork(from) {
		const fork = await _classPrivateFieldGet2(_harness2, this).use((opencode) => opencode.sessions.fork({ sessionID: from }));
		return _classPrivateFieldGet2(_harness2, this).session(fork.id);
	}
	/** Every session, including subagent sessions OpenCode spawned. */
	list() {
		return _classPrivateFieldGet2(_harness2, this).use(listSessions);
	}
};
//#endregion
export { OpenCodeHarness, OpenCodeSession, OpenCodeSessions, ROOT_SESSION };

//# sourceMappingURL=index.js.map