Runner and PlugsReference
Runner API types
The type declarations of the runner API that the CLI calls, with their TSDoc.
The rest of @blaiseagent/runner: configuration loading, plug loading, persisted-state loading and
runSession. This is the API that @blaiseagent/cli uses; plug authors do not need it.
AgentConfig
export type AgentConfig = z.infer<typeof agentSchema>;BundledPlugTable
/**
* The plugs bundled with the runner, by `blaise:` name. Generated from `src/plugs/bundled`,
* so that a compiled binary can load them: a bundler cannot follow a computed `import()`.
*/
export type BundledPlugTable = Readonly<Record<string, () => Promise<unknown>>>;capacitySchema
/** The most runs this runner holds at once, when the file sets a limit. */
export const capacitySchema: z.ZodNumber;checkExpressions
/**
* Parses every expression found in the string values of `value`. Evaluation is not attempted,
* so this works before the contexts exist. Typos in context names fail here.
*/
export function checkExpressions(
value: unknown,
contexts: readonly ContextName[],
path?: string
): ExpressionIssue[];CONFIG_VERSION
export const CONFIG_VERSION = 1;ConfigError
/** The configuration file is invalid. `issues` lists every problem found, in file order. */
export class ConfigError extends Error {
readonly issues: readonly string[];
constructor(issues: readonly string[], options?: ErrorOptions);
}configJsonSchema
/** JSON Schema for editors, with the same structure as the runtime validation. */
export function configJsonSchema(): Record<string, unknown>;configSchema
export const configSchema: z.ZodObject<
{
version: z.ZodLiteral<1>;
server: z.ZodOptional<
z.ZodObject<
{
url: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodString]>>;
token: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodString]>>;
},
z.core.$strict
>
>;
log: z.ZodOptional<
z.ZodObject<
{
level: z.ZodOptional<
z.ZodUnion<
readonly [
z.ZodEnum<{
debug: "debug";
error: "error";
fatal: "fatal";
info: "info";
trace: "trace";
warn: "warn";
}>,
z.ZodString,
]
>
>;
format: z.ZodOptional<
z.ZodUnion<
readonly [
z.ZodEnum<{
json: "json";
pretty: "pretty";
}>,
z.ZodString,
]
>
>;
},
z.core.$strict
>
>;
labels: z.ZodOptional<
z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>
>;
capacity: z.ZodOptional<z.ZodNumber>;
agent: z.ZodOptional<
z.ZodObject<
{
driver: z.ZodOptional<z.ZodString>;
command: z.ZodOptional<z.ZodString>;
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
},
z.core.$strict
>
>;
setup: z.ZodOptional<
z.ZodArray<
z.ZodObject<
{
id: z.ZodOptional<z.ZodString>;
use: z.ZodString;
with: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
},
z.core.$strict
>
>
>;
},
z.core.$strict
>;DEFAULT_SERVER_URL
export const DEFAULT_SERVER_URL = "https://askblaise.com";defaultModuleLoader
export const defaultModuleLoader: ModuleLoader;describeEntry
/** `plug 4 "db" (blaise:shell)`: how a plug is named in logs and errors. */
export function describeEntry(entry: SetupEntry): string;Directive
/** What Blaise tells a running session. */
export type Directive =evaluateAgent
/** Evaluates the `agent:` section when a run starts, when `session` and `run` exist. */
export function evaluateAgent(config: RunnerConfig, contexts: ExpressionContexts): AgentConfig;evaluateValue
/**
* Evaluates every `${{ }}` in the string values of `value`, recursively. The contexts that are
* present decide which context names the expressions may use.
*/
export function evaluateValue(
value: unknown,
contexts: ExpressionContexts,
path?: string
): JsonValue | undefined;ExpressionContexts
export interface ExpressionContexts {
readonly env: Readonly<Record<string, string | undefined>>;
readonly session?: {
readonly id: string;
readonly executionId: string;
readonly agent: {
readonly name: string;
};
};
readonly run?: {
readonly kind: string;
readonly number: number;
};
readonly setup?: Readonly<
Record<
string,
{
readonly outputs: Readonly<Record<string, string>>;
}
>
>;
}ExpressionIssue
export interface ExpressionIssue {
readonly path: string;
readonly message: string;
}findNonResumable
/** The plugs that cannot handle a `resume` run. Empty means the chain is resumable. */
export function findNonResumable(entries: readonly LoadedEntry[]): LoadedEntry[];hasExpression
export function hasExpression(value: unknown): boolean;inMemoryStateStore
/**
* Persisted plug state kept in the runner's memory. It does not survive the process, so it is
* for tests and for runs of a session that cannot resume.
*/
export function inMemoryStateStore(initial?: readonly StoredPlugState[]): InMemoryStateStore;InMemoryStateStore
export interface InMemoryStateStore extends PlugStateStore {
/** The records as they are now, by plug id. */
readonly records: ReadonlyMap<string, StoredPlugState>;
}isPlug
/**
* Whether a module's default export is a plug made by `definePlug`. A plain `plugApi` field is
* used rather than `instanceof` or a symbol, so that it survives bundling and duplicate copies
* of this package in `node_modules`.
*/
export function isPlug(value: unknown): value is LoadedPlug;isResumable
/** Plugs are resumable unless they say otherwise. */
export function isResumable(plug: Pick<LoadedPlug, "resumable">): boolean;JsonValue
export type JsonValue =
| string
| number
| boolean
| null
| readonly JsonValue[]
| {
readonly [key: string]: JsonValue;
};LABEL_KEY_PATTERN
export const LABEL_KEY_PATTERN: RegExp;LabelMap
/** A label map after evaluation: every key holds a non-empty, de-duplicated list of values. */
export type LabelMap = Record<string, string[]>;labelMapSchema
/**
* The labels after evaluation, normalised: scalars become one-value lists, values are trimmed and
* de-duplicated, and the bounds of the SDK's label map apply.
*/
export const labelMapSchema: z.ZodRecord<
z.ZodString,
z.ZodPipe<
z.ZodPipe<
z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>,
z.ZodTransform<string[], string | string[]>
>,
z.ZodArray<z.ZodString>
>
>;loadConfig
/** Loads, includes, validates and partly evaluates a `blaise.yml`. */
export function loadConfig(path: string, options: LoadConfigOptions): Promise<RunnerConfig>;LoadConfigOptions
export interface LoadConfigOptions {
/** The runner's environment, for `${{ env.* }}` in `server` and `log`. */
readonly env: Readonly<Record<string, string | undefined>>;
}LoadedEntry
export interface LoadedEntry {
readonly entry: SetupEntry;
readonly plug: LoadedPlug;
readonly source: PlugSource;
}LoadedPlug
/**
* A plug as the runner sees it after loading: the author's generics are erased, because the
* runner checks inputs, state and persisted values against the plug's schemas at run time.
* `isPlug` is the single place where a module's export is accepted as this type.
*/
export interface LoadedPlug {
readonly name: string;
readonly description?: string;
readonly inputs?: StandardSchemaV1;
readonly persisted?: StandardSchemaV1;
readonly resumable?: boolean;
readonly onLoad?: (ctx: LoadContext<unknown, unknown>) => unknown;
readonly onMigrate?: (ctx: LoadContext<unknown, undefined>, stored: unknown) => unknown;
readonly onSession?: (
ctx: PlugContext<unknown, unknown, unknown>,
next: (options?: { readonly host?: Host }) => Promise<PauseInfo>
) => Promise<void>;
readonly onTeardown?: (
ctx: PlugContext<unknown, unknown, unknown>,
next: (options?: { readonly host?: Host }) => Promise<TeardownInfo>
) => Promise<void>;
readonly onMessage?: (
ctx: PlugContext<unknown, unknown, unknown>,
message: Message,
next: (message: Message) => Promise<MessageResult<Message>>
) => Promise<MessageResult<Message>> | MessageResult<Message>;
readonly plugApi: {
readonly major: number;
readonly minor: number;
};
}LoadedStates
export interface LoadedStates {
/** By entry index. Entries without a `persisted` schema get an inert state. */
readonly byIndex: ReadonlyMap<number, PersistedState<unknown>>;
readonly orphans: readonly OrphanedState[];
}loadPersistedStates
/** Loads the state of every entry at once. Used where inputs do not depend on earlier plugs. */
export function loadPersistedStates(options: LoadStatesOptions): Promise<LoadedStates>;loadPlugs
/**
* Resolves, imports and checks every plug of the configuration. All problems are collected, so a
* typo in the third entry does not hide a missing package in the fifth. Inputs without
* expressions are validated now; the others when their plug runs.
*/
export function loadPlugs(
config: Pick<RunnerConfig, "setup" | "dir">,
options?: LoadPlugsOptions
): Promise<LoadedEntry[]>;LoadPlugsOptions
export interface LoadPlugsOptions {
readonly bundled?: BundledPlugTable;
readonly loader?: ModuleLoader;
/** Where the runner lives. Defaults to this package. */
readonly runnerDir?: string;
}localHost
/**
* The runner's own machine. Its `fs` is the vfs API over the real file system (so the sync
* methods work), and its processes are children of the runner. Nothing to connect or close.
*/
export function localHost(options?: LocalHostOptions): Host;LOG_FORMATS
export const LOG_FORMATS: readonly ["pretty", "json"];LOG_LEVELS
export const LOG_LEVELS: readonly ["trace", "debug", "info", "warn", "error", "fatal"];LogConfig
export type LogConfig = z.infer<typeof logSchema>;MAX_LABEL_KEY_LENGTH
/**
* Limits and key rules for labels. `@blaiseagent/runner` does not depend on the SDK, so these
* mirror `runner/labels.ts` of `@blaiseagent/sdk`; a test keeps the two in step.
*/
export const MAX_LABEL_KEY_LENGTH = 63;MAX_LABEL_KEYS
export const MAX_LABEL_KEYS = 32;MAX_LABEL_VALUE_LENGTH
export const MAX_LABEL_VALUE_LENGTH = 128;MAX_LABEL_VALUES_PER_KEY
export const MAX_LABEL_VALUES_PER_KEY = 32;MAX_RUNNER_CAPACITY
/**
* Bound of a declared capacity. Mirrors `RUNNER_MAX_CAPACITY` of `@blaiseagent/sdk`, which
* `@blaiseagent/runner` does not depend on; a test in the CLI keeps the two in step.
*/
export const MAX_RUNNER_CAPACITY = 1000;ModuleLoader
/** Resolves and imports modules. Replaceable in tests. */
export interface ModuleLoader {
/**
* The absolute path `specifier` resolves to when imported from a module in `fromDir`, or
* `undefined` when it cannot be found.
*/
resolve(specifier: string, fromDir: string): string | undefined;
import(path: string): Promise<unknown>;
}nonResumableMessage
/** The error `listen` raises when the agent pauses idle sessions but a plug cannot resume. */
export function nonResumableMessage(entry: LoadedEntry): string;OpenedStates
/** The records of a session, fetched once, handed to each stateful plug when its turn comes. */
export interface OpenedStates {
/** Records that no plug of the configuration owns. Known as soon as the records are fetched. */
readonly orphans: readonly OrphanedState[];
/**
* The plug's own validated (or migrated) value. Stateless plugs get an inert state. Throws
* `PlugStateError` when the record cannot be validated or migrated (the run then fails).
*/
load(
loaded: LoadedEntry,
migrateContext: LoadContext<unknown, undefined>
): Promise<PersistedState<unknown>>;
}openStates
/**
* Fetches the records of a session in one call. The engine loads each plug's state lazily, when
* the plug before it calls `next()`, so that `onMigrate` sees inputs that may depend on the
* outputs of earlier plugs.
*/
export function openStates(
store: PlugStateStore,
entries: readonly LoadedEntry[]
): Promise<OpenedStates>;OrphanedState
/** A record that no plug of the current configuration owns. Reported, never deleted. */
export interface OrphanedState extends StoredPlugState {
readonly reason: OrphanReason;
/** The plug now at that `id`, for `replaced`. */
readonly currentPlug?: string;
}OrphanReason
export type OrphanReason =PlugContractError
/** A plug broke the contract of a handler (for example, `next()` called twice). */
export class PlugContractError extends Error {
constructor(message: string, options?: ErrorOptions);
}PlugLoadError
/** One or more plugs could not be resolved, loaded or accepted. */
export class PlugLoadError extends Error {
readonly issues: readonly string[];
constructor(issues: readonly string[], options?: ErrorOptions);
}PlugSource
export type PlugSource = "bundled" | "package" | "file";PlugStateError
/** Persisted plug state could not be loaded, validated or migrated. */
export class PlugStateError extends Error {
constructor(message: string, options?: ErrorOptions);
}PlugStateStore
/** The Blaise-facing side of persisted state. */
export interface PlugStateStore {
/** All the records of the session, fetched once before a run. */
loadAll(): Promise<readonly StoredPlugState[]>;
/** Resolves when the write is durable. */
set(record: StoredPlugState): Promise<void>;
clear(id: string): Promise<void>;
}RawConfig
export type RawConfig = z.infer<typeof configSchema>;referencedSetupIds
/** The ids used as `setup.<id>` in the expressions of a value. */
export function referencedSetupIds(value: unknown): string[];ReportedMessage
/** One ACP message, as the transcript records it. */
export interface ReportedMessage {
readonly direction: "to_agent" | "from_agent";
readonly method: string;
readonly payload: object;
readonly protocolVersion: string;
/** Correlates `tool_call` updates and permission requests. */
readonly toolCallId?: string;
/** The plug that answered a request itself instead of passing it on. */
readonly answeredBy?: string;
}RunnerConfig
export interface RunnerConfig {
/** Absolute path of the file that was loaded. */
readonly file: string;
/** Its directory: local `use:` paths and packages resolve from here. */
readonly dir: string;
readonly server: ServerConfig;
readonly log: LogConfig;
/** What this runner declares about itself, evaluated and normalised. */
readonly labels: LabelMap;
/** Most runs this runner holds at once; `undefined` when the file sets no limit. */
readonly capacity?: number;
/** Not evaluated yet: see `evaluateAgent`. */
readonly agent: AgentConfig;
readonly setup: readonly SetupEntry[];
}runnerConfigSchema
/**
* The shape of a `RunnerConfig`, for checking one that crossed a process boundary (the listener
* sends its loaded configuration to the `run` child). Optional keys stand for properties that
* serialization drops when they are `undefined`.
*/
export const runnerConfigSchema: z.ZodObject<
{
file: z.ZodString;
dir: z.ZodString;
server: z.ZodObject<
{
url: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodString]>>;
token: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodString]>>;
},
z.core.$strict
>;
log: z.ZodObject<
{
level: z.ZodOptional<
z.ZodUnion<
readonly [
z.ZodEnum<{
debug: "debug";
error: "error";
fatal: "fatal";
info: "info";
trace: "trace";
warn: "warn";
}>,
z.ZodString,
]
>
>;
format: z.ZodOptional<
z.ZodUnion<
readonly [
z.ZodEnum<{
json: "json";
pretty: "pretty";
}>,
z.ZodString,
]
>
>;
},
z.core.$strict
>;
labels: z.ZodRecord<
z.ZodString,
z.ZodPipe<
z.ZodPipe<
z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>,
z.ZodTransform<string[], string | string[]>
>,
z.ZodArray<z.ZodString>
>
>;
capacity: z.ZodOptional<z.ZodNumber>;
agent: z.ZodObject<
{
driver: z.ZodOptional<z.ZodString>;
command: z.ZodOptional<z.ZodString>;
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
},
z.core.$strict
>;
setup: z.ZodArray<
z.ZodObject<
{
index: z.ZodNumber;
id: z.ZodOptional<z.ZodString>;
use: z.ZodString;
with: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
},
z.core.$strip
>
>;
},
z.core.$strip
>;RunResult
export interface RunResult {
/** How the agent run ended. Absent for a dedicated `teardown` run. */
readonly pause?: PauseInfo;
/** Set when the session was torn down: in this run, after the pause, or in a `teardown` run. */
readonly teardown?: TeardownInfo;
/**
* Whether the teardown itself failed (a plug threw, or broke the `next()` rules). A session that
* ended with `reason: "failed"` and was torn down without trouble is not a failed teardown.
*/
readonly teardownFailed?: boolean;
/** Whether every plug can handle a `resume` run. Reported to Blaise with every run. */
readonly resumable: boolean;
readonly acpSessionId?: string;
/**
* Preset values the agent could not honour (it has no such setting, or no such value), one
* sentence each. Never dropped silently: they are logged and meant for the run's report.
*/
readonly presetIssues?: readonly string[];
}runSession
/**
* Runs one run of a session: a `start` or `resume` run attaches the plugs, runs the agent until
* Blaise stops it, and pauses the plugs; if the session cannot resume, the plugs are torn down
* right after. A `teardown` run only tears down. Plug failures are reported in the result (pause
* or teardown reason `failed`); the promise rejects only when the run cannot be set up at all
* (for example, the state store is unreachable).
*/
export function runSession(options: RunSessionOptions): Promise<RunResult>;RunSessionOptions
export interface RunSessionOptions {
/** The loaded plugs of `setup:`, in order. */
readonly entries: readonly LoadedEntry[];
readonly session: SessionInfo;
readonly run: RunInfo;
/** The agent before any plug has changed it: the adapter's command, `agent:` of the config. */
readonly agent: AgentSpawnSpec;
readonly link: SessionLink;
readonly store: PlugStateStore;
/** The runner's environment, without Blaise credentials. */
readonly env: Readonly<Record<string, string | undefined>>;
readonly logger: (scope: string) => PlugLogger;
/** The ACP session of an earlier run: set for `resume` runs. */
readonly acpSessionId?: string;
/** Why a dedicated `teardown` run happens. Default: `expired`. */
readonly teardownReason?: Exclude<EndReason, "idle">;
/** The runner's machine. Default: the real one. */
readonly local?: Host;
readonly signal?: AbortSignal;
/**
* Persisted records no plug owns any more, reported before the plugs load their state (an empty
* list when there are none, so an earlier report can be replaced). They are logged unless this
* is set.
*/
readonly onOrphans?: (orphans: readonly OrphanedState[]) => void | Promise<void>;
/**
* Called once the plugs have paused, before a teardown that follows (a session that cannot
* resume is torn down at once). Blaise learns here that the run ended and whether the session
* may resume, while the process still has the teardown to do. If it throws, the run fails
* with the error before the plugs are torn down.
*/
readonly onPause?: (
pause: PauseInfo,
result: {
readonly acpSessionId?: string;
readonly pendingPrompts: ContentBlock[][];
}
) => Promise<void>;
readonly clientInfo?: {
readonly name: string;
readonly version: string;
};
readonly killTimeoutMs?: number;
/** The agent's preset: its model and effort are set on the ACP session. */
readonly preset?: SessionPreset;
}ServerConfig
export type ServerConfig = z.infer<typeof serverSchema>;SessionLink
/**
* The Blaise side of a session. The runner depends on this interface only, so it does not
* depend on the SDK or on how the CLI reaches Blaise (an oRPC WebSocket today).
*/
export interface SessionLink {
/** Records a message in the transcript. Failures stop the run so the transcript is never silently lost. */
report(message: ReportedMessage): Promise<void>;
/** Asks Blaise for a human's decision on a permission request that no plug answered. */
requestPermission(
request: RequestPermissionRequest,
protocolVersion: string
): Promise<RequestPermissionResponse>;
/** The directives of this run, until the signal aborts or Blaise closes the stream. */
directives(signal: AbortSignal): AsyncIterable<Directive>;
/** Remembers the ACP session id so that a later `resume` run can `session/load` it. */
saveAcpSessionId(id: string): Promise<void>;
}SessionPreset
/** The preset a run is configured with, as Blaise sends it. */
export interface SessionPreset {
/** Version of the preset block. Newer than {@link SUPPORTED_PRESET_VERSION} adds fields unknown here. */
readonly version?: number;
readonly model?: string;
readonly effort?: string;
/** Settings the agent asks to restore to its driver default. */
readonly resetToDefault?: readonly ("model" | "effort")[];
}SetupEntry
export interface SetupEntry {
/** 0-based position in `setup:` after includes are spliced. */
readonly index: number;
readonly id: string | undefined;
readonly use: string;
readonly with: Readonly<Record<string, unknown>> | undefined;
}splitTemplate
/**
* Splits a string into text and expression parts. A `}}` inside a single-quoted string literal
* (with `''` as the escape) does not close the expression.
*/
export function splitTemplate(input: string): TemplatePart[];StoredPlugState
/** A record as Blaise stores it. */
export interface StoredPlugState {
readonly id: string;
/** Name of the plug that wrote it (`definePlug({ name })`). */
readonly plug: string;
readonly value: unknown;
}SUPPORTED_PRESET_VERSION
/** The newest preset version this runner understands. */
export const SUPPORTED_PRESET_VERSION = 2;