agents
Version:
A home for your AI agents
546 lines (544 loc) • 20.5 kB
TypeScript
import {
_ as LifecycleJobContext,
n as CapabilityStartContext,
s as LifecycleCapability,
v as LifecycleJobOutcome
} from "../../capability-runner-BiVUtjqC.js";
import { OpenCodeWorkerd } from "@opencode/sdk/workerd";
//#region src/harness/opencode/types.d.ts
/**
* `OpenCodeHarness`'s own types: how the harness addresses sessions and
* reports operations. They match `PiHarness`'s, field for field. Anything
* OpenCode already publishes (prompt content, transcript messages, events)
* is OpenCode's type, used directly.
*/
/** The OpenCode host the factory returns: `OpenCodeWorkerd.create`'s result. */
type OpenCode = OpenCodeWorkerd.Interface;
type PromptRequest = Parameters<OpenCode["sessions"]["prompt"]>[0];
/**
* What a prompt says: text, or text with OpenCode's attachments (files,
* agent and skill mentions, metadata).
*/
type OpenCodeInput =
| string
| Omit<PromptRequest, "sessionID" | "id" | "delivery" | "resume">;
/**
* One message of an OpenCode transcript: user, assistant, idle markers,
* compactions, and the rest of OpenCode's message kinds.
*/
type OpenCodeMessage = Awaited<
ReturnType<OpenCode["sessions"]["context"]>
>[number];
/**
* One of OpenCode's own events: durable transcript events and live deltas.
* `session.events()` translates these into `OpenCodeSessionEvent`s.
*/
type OpenCodeEvent =
ReturnType<OpenCode["events"]["subscribe"]> extends AsyncIterable<infer E>
? E
: never;
/**
* One of a session's durable events, as `session.log()` replays them. Each
* carries its sequence number in `durable.seq`.
*/
type OpenCodeLogEvent =
ReturnType<OpenCode["sessions"]["log"]> extends AsyncIterable<infer E>
? E
: never;
/** An OpenCode session, addressed by its id. The root is `"ses_root"`. */
type OpenCodeSessionId = string;
/**
* What a submission does when the session is already running.
*
* - `followUp` (default): answered after the current run, as its own run.
* - `steer`: joins the running work at its next step.
*/
type OpenCodeWhenBusy = "followUp" | "steer";
/** Options for `submit` and `prompt`. */
type OpenCodeSubmitOptions = {
/** Default: the root session. */ readonly session?: OpenCodeSessionId;
/**
* Idempotency key. Submitting the same id twice returns the same receipt.
* Letters, digits, `.`, `_`, `~` and `-`; default a random UUID.
*/
readonly operationId?: string;
readonly whenBusy?: OpenCodeWhenBusy;
};
/** Selects a session. */
type OpenCodeSessionOptions = {
/** Default: the root session. */ readonly session?: OpenCodeSessionId;
};
/** Returned once a submission is durable. It says nothing about the model yet. */
type OpenCodeReceipt = {
readonly operationId: string;
readonly session: OpenCodeSessionId /** False when this operation id was already submitted. */;
readonly accepted: boolean;
};
/** How one operation ended. */
type OpenCodeOperationResult = {
readonly operationId: string;
readonly session: OpenCodeSessionId /** `done`: answered. `unanswered`: failed, interrupted, or withdrawn. */;
readonly status: "done" | "unanswered";
/**
* Why an unanswered operation ended: `failed` (with OpenCode's error
* message when it has one), `interrupted`, or `not_found`.
*/
readonly reason?: string /** The final assistant text, when answered. */;
readonly text?: string;
};
/** An operation's result and the session's transcript after it. */
type OpenCodePromptResponse = OpenCodeOperationResult & {
/** The session's active transcript after the operation. */ readonly messages: readonly OpenCodeMessage[];
};
/** A submission OpenCode has not settled yet. */
type OpenCodePendingOperation = {
readonly operationId: string;
readonly session: OpenCodeSessionId /** `queued` in OpenCode's inbox, or `running` as part of the current run. */;
readonly status: "queued" | "running";
};
/** One session, as `sessions.list()` reports it. */
type OpenCodeSessionInfo = {
readonly id: OpenCodeSessionId /** The session this one was forked from, or the one that spawned it. */;
readonly parent?: OpenCodeSessionId /** The title OpenCode generated for the session, once it has one. */;
readonly title?: string /** Running, input waiting, or a turn a restart cut off, about to resume. */;
readonly busy: boolean;
};
/** A permission OpenCode asked for, waiting on a reply. */
type OpenCodePermission = {
readonly id: string;
readonly session: OpenCodeSessionId /** What OpenCode wants to do, such as `edit` or `webfetch`. */;
readonly action: string /** What the action applies to, such as paths or URLs. */;
readonly resources: readonly string[];
readonly message?: string;
};
/** How an assistant message's step ended, as OpenCode reports it. */
type OpenCodeFinish =
| "stop"
| "length"
| "tool-calls"
| "content-filter"
| "error"
| "unknown";
/**
* One event on a session's stream, translated from OpenCode's events into
* the same kinds of event `PiHarness` and `ContainerHarness` report.
* `messageId` is OpenCode's assistant message id. Text and reasoning are
* addressed by `ordinal`, their position among the message's parts of the
* same kind.
*/
type OpenCodeSessionEvent =
/**
* An input is in OpenCode's inbox. Every input goes through it, so this
* comes before `operation_start` even when the session is idle.
*/
| {
readonly type: "operation_queued";
readonly operationId: string;
} /** A queued input was withdrawn before it ran. */
| {
readonly type: "operation_withdrawn";
readonly operationId: string;
} /** An input joined the transcript; the next assistant message answers it. */
| {
readonly type: "operation_start";
readonly operationId: string;
} /** An input has its answer: the same result `wait()` returns. */
| {
readonly type: "operation_end";
readonly result: OpenCodeOperationResult;
} /** OpenCode started a run. One run answers every input it drains. */
| {
readonly type: "run_start";
}
| {
readonly type: "run_end";
readonly outcome: "succeeded" | "failed" | "interrupted";
readonly error?: string;
}
| {
readonly type: "message_start";
readonly messageId: string;
readonly agent: string;
readonly model: {
readonly providerID: string;
readonly id: string;
};
}
| {
readonly type: "text_delta";
readonly messageId: string;
readonly ordinal: number;
readonly delta: string;
} /** A text part is complete; `text` is the whole part. */
| {
readonly type: "text_end";
readonly messageId: string;
readonly ordinal: number;
readonly text: string;
}
| {
readonly type: "reasoning_delta";
readonly messageId: string;
readonly ordinal: number;
readonly delta: string;
}
| {
readonly type: "reasoning_end";
readonly messageId: string;
readonly ordinal: number;
readonly text: string;
} /** The model called a tool and OpenCode has its whole input. */
| {
readonly type: "tool_start";
readonly messageId: string;
readonly toolCallId: string;
readonly name: string;
readonly input: Readonly<Record<string, unknown>>;
}
| {
readonly type: "tool_end";
readonly messageId: string;
readonly toolCallId: string;
readonly name: string;
readonly error: boolean /** The tool's text output, or its error message. */;
readonly output: string;
}
| {
readonly type: "message_end";
readonly messageId: string;
readonly finish: OpenCodeFinish;
readonly error?: string;
} /** A model call failed and OpenCode will try again at `at`. */
| {
readonly type: "retry_scheduled";
readonly messageId: string;
readonly attempt: number;
readonly at: number;
readonly error: string;
}
| {
readonly type: "compaction_start";
readonly reason: "auto" | "manual";
}
/**
* OpenCode compacted the session: `messages()` is now shorter, and
* `history()` has a compaction message. Read the transcript again.
*/
| {
readonly type: "transcript_reset";
readonly reason: "compaction";
}
| {
readonly type: "permission_asked";
readonly permission: OpenCodePermission;
}
| {
readonly type: "permission_replied";
readonly permissionId: string;
readonly reply: "once" | "always" | "reject";
}
/**
* Something went wrong outside a model step: `compaction_failed`, or
* `events_failed` when OpenCode's event stream ended with an error. No
* more events follow `events_failed`; open a new stream.
*/
| {
readonly type: "fault";
readonly code: "compaction_failed" | "events_failed";
readonly message: string;
};
/** A session's state when its event stream opens. */
type OpenCodeSessionSnapshot = {
readonly session: OpenCodeSessionId /** The whole transcript, compacted messages included: `history()`. */;
readonly messages: readonly OpenCodeMessage[];
readonly pending: readonly OpenCodePendingOperation[];
readonly busy: boolean /** Permissions OpenCode is waiting on. */;
readonly permissions: readonly OpenCodePermission[];
};
/**
* A session's events: the snapshot taken when the stream opened, then every
* event after it, in batches, including any that arrived before `start()`.
* An event may repeat what the snapshot already shows; messages are keyed
* by id. Live: events reach listeners in the isolate that runs the harness,
* and the stream holds OpenCode open until `stop()`. Call `stop()` when
* done, started or not.
*/
type OpenCodeEventStream = {
readonly snapshot: OpenCodeSessionSnapshot /** Start delivering events. Call once. */;
start(
listener: (events: readonly OpenCodeSessionEvent[]) => void
): void /** Stop delivering events and release OpenCode. */;
stop(): Promise<void>;
};
//#endregion
//#region src/harness/opencode/harness.d.ts
/** The root session's id. OpenCode session ids start with `ses`. */
declare const ROOT_SESSION: OpenCodeSessionId;
/** What the factory is handed: the one thing only the harness can build. */
type OpenCodeHarnessContext = {
/**
* This object's storage, with OpenCode's tables moved under the
* `opencode_` prefix so they cannot collide with the SDK's or the host's.
* Pass it as `OpenCodeWorkerd.create({ storage })`.
*/
readonly storage: DurableObjectStorage;
};
/**
* Opens OpenCode. The harness adopts what it returns and closes it on
* `dispose()`.
*
* ```ts
* opencode: ({ storage }) =>
* OpenCodeWorkerd.create({ storage, plugins: [ai.plugin] })
* ```
*/
type OpenCodeHarnessFactory = (
context: OpenCodeHarnessContext
) => OpenCode | Promise<OpenCode>;
/** A model, as OpenCode references one; `agents/models/opencode` makes them. */
type OpenCodeModel = {
readonly providerID: string;
readonly id: string;
};
/**
* Applied to a session when the harness creates it. Without a model, a
* session uses OpenCode's default model, if its config names one.
*/
type OpenCodeSessionDefaults = {
/** Model for new sessions. Change one session's with `session.setModel`. */ readonly model?: OpenCodeModel /** OpenCode agent for new sessions, such as `build`. */;
readonly agent?: string;
};
/** `OpenCodeHarness`'s options. Only `opencode`, which opens it, is required. */
type OpenCodeHarnessOptions = {
/** Opens OpenCode over the storage this object prepared. */ readonly opencode: OpenCodeHarnessFactory /** What a new session starts with. */;
readonly defaults?: OpenCodeSessionDefaults;
};
/**
* 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.
*/
declare class OpenCodeHarness extends LifecycleCapability {
#private;
/** Every session in this object. */
readonly sessions: OpenCodeSessions;
/**
* @param options - How to open OpenCode, and what new sessions start with.
*/
constructor(options: OpenCodeHarnessOptions);
/** Open OpenCode, which resumes interrupted turns, and wake running sessions. */
onStart(_context: CapabilityStartContext): Promise<void>;
/** Run one step of a session's wake job. */
onJob(context: LifecycleJobContext): Promise<LifecycleJobOutcome | void>;
/** Close OpenCode's in-memory resources. Durable state is untouched. */
dispose(): Promise<void>;
/**
* A handle on one session. No I/O until you call it.
*
* @param id - The session; default the root session.
*/
session(id?: OpenCodeSessionId): OpenCodeSession;
/** Submit a prompt and wait for its answer. */
prompt(
input: OpenCodeInput,
options?: OpenCodeSubmitOptions
): Promise<OpenCodePromptResponse>;
/** Durably submit a prompt. Resolves before the model runs. */
submit(
input: OpenCodeInput,
options?: OpenCodeSubmitOptions
): Promise<OpenCodeReceipt>;
/** Stop one operation, or everything running in a session. */
abort(
options?: OpenCodeSessionOptions & {
readonly operationId?: string;
}
): Promise<boolean>;
/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
wait(
operationId: string,
options?: OpenCodeSessionOptions & {
readonly signal?: AbortSignal;
}
): Promise<OpenCodeOperationResult>;
/** The session's active transcript, as OpenCode's messages. */
messages(options?: OpenCodeSessionOptions): Promise<OpenCodeMessage[]>;
/** Submissions OpenCode has not settled yet, oldest first. */
pending(
options?: OpenCodeSessionOptions
): Promise<OpenCodePendingOperation[]>;
/**
* 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(): Promise<OpenCode>;
/**
* @internal Run `call` with OpenCode open. The host is not closed while
* any call holds it.
*/
use<T>(call: (opencode: OpenCode) => Promise<T>): Promise<T>;
/**
* @internal Hold OpenCode open until the returned release is called, for
* work that outlives one call, such as an event stream.
*/
lease(): () => Promise<void>;
/** @internal Admit an input to OpenCode once, behind the session's wake. */
enqueue(
session: OpenCodeSessionId,
input: OpenCodeInput,
options: OpenCodeSubmitOptions
): Promise<OpenCodeReceipt>;
/** @internal Withdraw a queued input, or interrupt the run it joined. */
withdraw(session: OpenCodeSessionId, operationId: string): Promise<boolean>;
/** @internal Withdraw every queued input and interrupt the running work. */
interrupt(session: OpenCodeSessionId): Promise<void>;
/** @internal Wait for OpenCode to settle an operation, by its id. */
settled(
session: OpenCodeSessionId,
operationId: string,
signal?: AbortSignal
): Promise<OpenCodeOperationResult>;
/** @internal Create a session with the harness defaults. */
create(options?: {
readonly id?: OpenCodeSessionId;
}): Promise<OpenCodeSessionId>;
}
/** One OpenCode session, addressed through the harness. */
declare class OpenCodeSession {
#private;
/** The session's id. */
readonly id: OpenCodeSessionId;
/**
* @param harness - The harness that hosts the session.
* @param id - The session's id.
*/
constructor(harness: OpenCodeHarness, id: OpenCodeSessionId);
/** Durably submit a prompt. Resolves before the model runs. */
submit(
input: OpenCodeInput,
options?: OpenCodeSubmitOptions
): Promise<OpenCodeReceipt>;
/** Submit and wait for the answer and the updated transcript. */
prompt(
input: OpenCodeInput,
options?: OpenCodeSubmitOptions
): Promise<OpenCodePromptResponse>;
/** Join the running work at its next step. */
steer(
input: OpenCodeInput,
options?: Omit<OpenCodeSubmitOptions, "whenBusy">
): Promise<OpenCodeReceipt>;
/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
wait(
operationId: string,
signal?: AbortSignal
): Promise<OpenCodeOperationResult>;
/**
* 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.
*/
abort(operationId?: string): Promise<boolean>;
/** Change this session's model; `agents/models/opencode` makes them. */
setModel(model: {
readonly providerID: string;
readonly id: string;
}): Promise<void>;
/** Change this session's OpenCode agent, such as `build` or `plan`. */
setAgent(agent: string): Promise<void>;
/** The active transcript: OpenCode's messages since the newest compaction. */
messages(): Promise<OpenCodeMessage[]>;
/**
* 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(): Promise<OpenCodeMessage[]>;
/**
* 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()`.
*/
events(): Promise<OpenCodeEventStream>;
/**
* 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.
*/
log(options?: {
readonly after?: number;
readonly follow?: boolean;
readonly signal?: AbortSignal;
}): AsyncIterable<OpenCodeLogEvent>;
/**
* Whether the session has work: running, input waiting, or a turn a
* restart cut off that OpenCode is about to resume.
*/
busy(): Promise<boolean>;
}
/** Every OpenCode session in this object. */
declare class OpenCodeSessions {
#private;
/** @param harness - The harness that hosts the sessions. */
constructor(harness: OpenCodeHarness);
/** A handle on one session. No I/O until you call it. */
get(id: OpenCodeSessionId): OpenCodeSession;
/** A new top-level session, configured like the root. */
create(): Promise<OpenCodeSession>;
/** A new session that sees `from`'s history up to now. */
fork(from: OpenCodeSessionId): Promise<OpenCodeSession>;
/** Every session, including subagent sessions OpenCode spawned. */
list(): Promise<OpenCodeSessionInfo[]>;
}
//#endregion
export {
type OpenCode,
type OpenCodeEvent,
type OpenCodeEventStream,
type OpenCodeFinish,
OpenCodeHarness,
type OpenCodeHarnessContext,
type OpenCodeHarnessFactory,
type OpenCodeHarnessOptions,
type OpenCodeInput,
type OpenCodeLogEvent,
type OpenCodeMessage,
type OpenCodeModel,
type OpenCodeOperationResult,
type OpenCodePendingOperation,
type OpenCodePermission,
type OpenCodePromptResponse,
type OpenCodeReceipt,
OpenCodeSession,
type OpenCodeSessionDefaults,
type OpenCodeSessionEvent,
type OpenCodeSessionId,
type OpenCodeSessionInfo,
type OpenCodeSessionOptions,
type OpenCodeSessionSnapshot,
OpenCodeSessions,
type OpenCodeSubmitOptions,
type OpenCodeWhenBusy,
ROOT_SESSION
};
//# sourceMappingURL=index.d.ts.map