agents
Version:
A home for your AI agents
332 lines (331 loc) • 10.9 kB
TypeScript
//#region src/harness/container/protocol.d.ts
/**
* The wire between `ContainerHarness` (in a Durable Object) and the harness
* daemon (in the container). Isomorphic and dependency-free: the daemon
* imports it in Node, the harness in Workers.
*
* One WebSocket per session, dialled by the object through
* `ctx.container.getTcpPort(port)` at `/sessions/<id>`. The daemon speaks
* first with `hello`; the object answers with `open` (when the daemon does
* not hold the session yet) and `replay`, then delivers `prompt`s.
*
* Everything the daemon produces for a session is a numbered `frame`. The
* numbers start at 1 for each session in each container, so a frame is
* addressed by `(runtimeId, seq)`. The daemon keeps frames until they are
* acknowledged, so an object that lost its socket (evicted, redeployed)
* replays what it missed from its cursor. A different `runtimeId` in
* `hello` means a different container: its numbering starts again, and
* whatever the old container was doing is gone.
*
* @experimental The wire may change before it stabilizes. The object and the image are
* a matched pair; a version mismatch is reported in `hello`.
*/
/** Bumped on any incompatible change. */
declare const CONTAINER_PROTOCOL_VERSION = 1;
/** The daemon answers `200` here once it accepts connections. */
declare const CONTAINER_HEALTH_PATH = "/healthz";
/** The session socket: `/sessions/<encoded session id>`. */
declare const CONTAINER_SESSION_PATH = "/sessions/";
/** The shared secret the object passes on every request. */
declare const CONTAINER_TOKEN_HEADER = "x-cf-harness-token";
/** The environment variables the object starts the container with. */
declare const CONTAINER_ENV: {
/** Shared secret for `CONTAINER_TOKEN_HEADER`. */ readonly token: "CF_HARNESS_TOKEN" /** This container's id. Echoed in `hello`. */;
readonly runtimeId: "CF_HARNESS_RUNTIME_ID" /** The port the daemon listens on. */;
readonly port: "CF_HARNESS_PORT";
};
/** WebSocket close code: a newer connection for the session replaced this one. */
declare const CLOSE_REPLACED = 4001;
/** WebSocket close code: the request was not authorized. */
declare const CLOSE_UNAUTHORIZED = 4003;
/** A JSON value on the wire. */
type JsonValue =
| string
| number
| boolean
| null
| readonly JsonValue[]
| {
readonly [key: string]: JsonValue;
};
/** One part of a prompt. */
type ContainerInputPart =
| {
readonly type: "text";
readonly text: string;
}
| {
readonly type: "image" /** IANA media type, such as `image/png`. */;
readonly mediaType: string /** Base64 bytes. */;
readonly data: string;
};
/** What a prompt carries: plain text, or parts. */
type ContainerInput = string | readonly ContainerInputPart[];
/** What a submission does when the session is already running. */
type ContainerWhenBusy = "followUp" | "steer";
/**
* Per-session settings the object sends the adapter. `model` is the
* adapter's own model name; `options` is opaque to everything but the
* adapter.
*/
type ContainerSettings = {
readonly model?: string;
readonly options?: JsonValue;
};
/** One part of a transcript message. */
type ContainerPart =
| {
readonly type: "text";
readonly text: string;
}
| {
readonly type: "reasoning";
readonly text: string;
}
| {
readonly type: "image";
readonly mediaType: string;
readonly data: string;
}
| {
readonly type: "tool";
readonly toolCallId: string;
readonly toolName: string;
readonly input: JsonValue;
readonly state: "running" | "done" | "error";
readonly output?: JsonValue;
};
/**
* A transcript message, in the one format every container adapter
* produces. An adapter emits a message again, under the same id, to update
* it (a tool result arriving, for example).
*/
type ContainerMessage = {
readonly id: string;
readonly role: "user" | "assistant";
readonly parts: readonly ContainerPart[] /** The operation the message belongs to. */;
readonly operationId?: string;
readonly createdAt: number;
};
/** What an adapter reports while it runs. */
type ContainerEvent =
| /** Live text for an assistant message that is still being written. */ {
readonly type: "text-delta";
readonly messageId: string;
readonly delta: string;
} /** Live reasoning for an assistant message that is still being written. */
| {
readonly type: "reasoning-delta";
readonly messageId: string;
readonly delta: string;
} /** A finished or updated transcript message. Stored. */
| {
readonly type: "message";
readonly message: ContainerMessage;
} /** Token and cost figures the adapter reports, as the adapter counts them. */
| {
readonly type: "usage";
readonly inputTokens?: number;
readonly outputTokens?: number;
readonly costUsd?: number;
} /** A diagnostic line. */
| {
readonly type: "log";
readonly level: "info" | "warn" | "error";
readonly message: string;
} /** Anything adapter-specific, passed through untouched. */
| {
readonly type: "raw";
readonly value: JsonValue;
};
/** How an operation ended, as the daemon reports it. */
type ContainerOutcome =
| {
readonly status: "done";
readonly text: string;
}
| {
readonly status: "unanswered";
readonly reason: string;
};
/** What the daemon knows about one operation it was given. */
type DaemonOperation =
| {
readonly operationId: string;
readonly status: "queued" | "running";
}
| {
readonly operationId: string;
readonly status: "settled";
readonly outcome: ContainerOutcome;
};
/** One numbered thing the daemon produced for a session. */
type DaemonFrame =
| {
readonly kind: "start";
readonly operationId: string;
}
| {
readonly kind: "event";
readonly operationId: string | null;
readonly event: ContainerEvent;
}
/**
* Adapter resume state to keep. The object stores these entries in order
* and hands all of them back in `open` on a new container.
*/
| {
readonly kind: "persist";
readonly entries: readonly JsonValue[];
}
| {
readonly kind: "settle";
readonly operationId: string;
readonly outcome: ContainerOutcome;
};
/** The adapter running in the container. */
type AdapterInfo = {
readonly id: string;
readonly version: string;
readonly capabilities: readonly string[];
};
/** Daemon → object. */
type DaemonMessage =
| {
readonly type: "hello";
readonly protocol: number;
readonly runtimeId: string;
readonly session: string;
readonly adapter: AdapterInfo /** The generation of the open adapter session, or null if none is open. */;
readonly open:
| number
| null /** The newest frame number for this session in this container. */;
readonly lastSeq: number /** Every operation this container was given for the session. */;
readonly operations: readonly DaemonOperation[];
}
| {
readonly type: "frame";
readonly seq: number;
readonly frame: DaemonFrame;
} /** Every kept frame after the `replay` cursor has been sent. */
| {
readonly type: "caught-up";
readonly lastSeq: number;
}
| {
readonly type: "error";
readonly message: string;
};
/**
* Most JSON characters the object puts in one `restore` message, and the
* daemon in one `persist` frame (a single larger entry travels alone).
* Keeps every message well inside WebSocket message limits.
*/
declare const MAX_CHUNK_CHARS: number;
/** Object → daemon. */
type HostMessage =
/**
* Part of the resume state for the next `open` of `generation`. Sent
* before that `open`, in order, so no single message grows with the
* session's history.
*/
| {
readonly type: "restore";
readonly generation: number;
readonly entries: readonly JsonValue[];
}
/**
* Open the adapter session. Ignored when the same generation is already
* open; a different generation closes the open one first.
*/
| {
readonly type: "open";
readonly generation: number;
readonly settings: ContainerSettings;
/**
* The last of the entries the adapter persisted for this session,
* after any sent in `restore` messages for this generation.
*/
readonly restore: readonly JsonValue[];
} /** Send every kept frame after `after`, then live frames. */
| {
readonly type: "replay";
readonly after: number;
} /** Queue an operation. Idempotent on `operationId`. */
| {
readonly type: "prompt";
readonly operationId: string;
readonly input: ContainerInput;
readonly whenBusy: ContainerWhenBusy;
} /** Stop one operation, or every operation of the session. */
| {
readonly type: "abort";
readonly operationId?: string;
}
| {
readonly type: "configure";
readonly settings: ContainerSettings;
} /** Frames up to `seq` are stored; the daemon may forget them. */
| {
readonly type: "ack";
readonly seq: number;
};
/**
* Parse one message from the daemon.
*
* @param text - The raw WebSocket message.
* @returns The message, or `undefined` when it is not one.
*/
declare function parseDaemonMessage(text: string): DaemonMessage | undefined;
/**
* Parse one message from the object.
*
* @param text - The raw WebSocket message.
* @returns The message, or `undefined` when it is not one.
*/
declare function parseHostMessage(text: string): HostMessage | undefined;
/**
* The text of an input, with image parts left out.
*
* @param input - A prompt's input.
* @returns Its text.
*/
declare function inputText(input: ContainerInput): string;
/**
* Split entries into groups whose JSON stays under `MAX_CHUNK_CHARS`. An
* entry larger than that is a group of its own.
*
* @param entries - The entries, in order.
* @returns The groups, in order. Empty for no entries.
*/
declare function chunkEntries(entries: readonly JsonValue[]): JsonValue[][];
//#endregion
export {
chunkEntries as C,
parseHostMessage as E,
MAX_CHUNK_CHARS as S,
parseDaemonMessage as T,
DaemonFrame as _,
CONTAINER_HEALTH_PATH as a,
HostMessage as b,
CONTAINER_TOKEN_HEADER as c,
ContainerInputPart as d,
ContainerMessage as f,
ContainerWhenBusy as g,
ContainerSettings as h,
CONTAINER_ENV as i,
ContainerEvent as l,
ContainerPart as m,
CLOSE_REPLACED as n,
CONTAINER_PROTOCOL_VERSION as o,
ContainerOutcome as p,
CLOSE_UNAUTHORIZED as r,
CONTAINER_SESSION_PATH as s,
AdapterInfo as t,
ContainerInput as u,
DaemonMessage as v,
inputText as w,
JsonValue as x,
DaemonOperation as y
};
//# sourceMappingURL=protocol-BEgrJYC5.d.ts.map