summaryrefslogtreecommitdiffhomepage
path: root/packages/wire/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-26 22:03:19 +0900
committerAdam Malczewski <[email protected]>2026-06-26 22:23:39 +0900
commit727c98c9dae516a2070eb950410314380a20c974 (patch)
tree52aa1022c54f11770be7e4e2a324f0a8b8b8deec /packages/wire/src
parente59dc11f63b1df51142259bb2c406af8c9c8c2bb (diff)
downloaddispatch-727c98c9dae516a2070eb950410314380a20c974.tar.gz
dispatch-727c98c9dae516a2070eb950410314380a20c974.zip
style: switch from tabs to 2-space indentation
Diffstat (limited to 'packages/wire/src')
-rw-r--r--packages/wire/src/index.test.ts84
-rw-r--r--packages/wire/src/index.ts594
2 files changed, 339 insertions, 339 deletions
diff --git a/packages/wire/src/index.test.ts b/packages/wire/src/index.test.ts
index cd297b7..3f07e00 100644
--- a/packages/wire/src/index.test.ts
+++ b/packages/wire/src/index.test.ts
@@ -11,49 +11,49 @@ import { describe, expect, it } from "vitest";
import type { Computer, ComputerEntry, Workspace } from "./index.js";
describe("@dispatch/wire — Computer / Workspace shapes", () => {
- it("a Computer literal satisfies the Computer type", () => {
- const c: Computer = {
- alias: "myserver",
- hostName: "myserver.example.com",
- port: 22,
- user: "deploy",
- identityFile: null,
- knownHost: true,
- };
- expect(c.alias).toBe("myserver");
- expect(c.port).toBe(22);
- expect(c.identityFile).toBeNull();
- expect(c.knownHost).toBe(true);
- });
+ it("a Computer literal satisfies the Computer type", () => {
+ const c: Computer = {
+ alias: "myserver",
+ hostName: "myserver.example.com",
+ port: 22,
+ user: "deploy",
+ identityFile: null,
+ knownHost: true,
+ };
+ expect(c.alias).toBe("myserver");
+ expect(c.port).toBe(22);
+ expect(c.identityFile).toBeNull();
+ expect(c.knownHost).toBe(true);
+ });
- it("ComputerEntry extends Computer and carries usageCount", () => {
- const entry: ComputerEntry = {
- alias: "buildbox",
- hostName: "buildbox",
- port: 2222,
- user: "root",
- identityFile: "/home/u/.ssh/id_ed25519",
- knownHost: false,
- usageCount: 3,
- };
- // Compile-time proof that ComputerEntry is assignable to Computer.
- const asComputer: Computer = entry;
- expect(asComputer.alias).toBe("buildbox");
- expect(entry.usageCount).toBe(3);
- });
+ it("ComputerEntry extends Computer and carries usageCount", () => {
+ const entry: ComputerEntry = {
+ alias: "buildbox",
+ hostName: "buildbox",
+ port: 2222,
+ user: "root",
+ identityFile: "/home/u/.ssh/id_ed25519",
+ knownHost: false,
+ usageCount: 3,
+ };
+ // Compile-time proof that ComputerEntry is assignable to Computer.
+ const asComputer: Computer = entry;
+ expect(asComputer.alias).toBe("buildbox");
+ expect(entry.usageCount).toBe(3);
+ });
- it("a Workspace carries defaultComputerId (null = local)", () => {
- const remote: Workspace = {
- id: "default",
- title: "Default",
- defaultCwd: null,
- defaultComputerId: "myserver",
- createdAt: 0,
- lastActivityAt: 0,
- };
- expect(remote.defaultComputerId).toBe("myserver");
+ it("a Workspace carries defaultComputerId (null = local)", () => {
+ const remote: Workspace = {
+ id: "default",
+ title: "Default",
+ defaultCwd: null,
+ defaultComputerId: "myserver",
+ createdAt: 0,
+ lastActivityAt: 0,
+ };
+ expect(remote.defaultComputerId).toBe("myserver");
- const local: Workspace = { ...remote, defaultComputerId: null };
- expect(local.defaultComputerId).toBeNull();
- });
+ const local: Workspace = { ...remote, defaultComputerId: null };
+ expect(local.defaultComputerId).toBeNull();
+ });
});
diff --git a/packages/wire/src/index.ts b/packages/wire/src/index.ts
index 8dc3a72..16b7023 100644
--- a/packages/wire/src/index.ts
+++ b/packages/wire/src/index.ts
@@ -31,23 +31,23 @@ export type StepId = string & { readonly __brand: "StepId" };
* append-only conversation log. Discriminated by `type`.
*/
export type Chunk =
- | TextChunk
- | ThinkingChunk
- | ToolCallChunk
- | ToolResultChunk
- | ErrorChunk
- | SystemChunk;
+ | TextChunk
+ | ThinkingChunk
+ | ToolCallChunk
+ | ToolResultChunk
+ | ErrorChunk
+ | SystemChunk;
/** A piece of plain text content from the assistant or user. */
export interface TextChunk {
- readonly type: "text";
- readonly text: string;
+ readonly type: "text";
+ readonly text: string;
}
/** A piece of model reasoning / thinking content (e.g. extended thinking). */
export interface ThinkingChunk {
- readonly type: "thinking";
- readonly text: string;
+ readonly type: "thinking";
+ readonly text: string;
}
/**
@@ -56,22 +56,22 @@ export interface ThinkingChunk {
* `ToolContract.execute`.
*/
export interface ToolCallChunk {
- readonly type: "tool-call";
- readonly toolCallId: string;
- readonly toolName: string;
- readonly input: unknown;
- /**
- * The step that produced this call — generation provenance stamped by the
- * runtime when the model emits the call (NOT storage metadata like `seq`,
- * which is why it lives on the chunk and travels with it through persistence
- * and replay). Tool calls a model batches together in one step share the same
- * `stepId`: the grouping key for rendering a parallel batch as one unit, and
- * equal to the `stepId` on the matching `tool-call` AgentEvent. Optional:
- * absent on chunks reconstructed outside a turn and on rows persisted before
- * this field existed, so a consumer must tolerate its absence (render
- * ungrouped).
- */
- readonly stepId?: StepId;
+ readonly type: "tool-call";
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly input: unknown;
+ /**
+ * The step that produced this call — generation provenance stamped by the
+ * runtime when the model emits the call (NOT storage metadata like `seq`,
+ * which is why it lives on the chunk and travels with it through persistence
+ * and replay). Tool calls a model batches together in one step share the same
+ * `stepId`: the grouping key for rendering a parallel batch as one unit, and
+ * equal to the `stepId` on the matching `tool-call` AgentEvent. Optional:
+ * absent on chunks reconstructed outside a turn and on rows persisted before
+ * this field existed, so a consumer must tolerate its absence (render
+ * ungrouped).
+ */
+ readonly stepId?: StepId;
}
/**
@@ -80,27 +80,27 @@ export interface ToolCallChunk {
* (synthesized if interrupted — see reconcile).
*/
export interface ToolResultChunk {
- readonly type: "tool-result";
- readonly toolCallId: string;
- readonly toolName: string;
- readonly content: string;
- readonly isError: boolean;
- /**
- * The step that produced the originating call — equal to the `stepId` on the
- * matching `tool-call` chunk (same `toolCallId`) and on the `tool-result`
- * AgentEvent, so a consumer groups a step's calls with their results.
- * Generation provenance, not storage metadata (see `ToolCallChunk.stepId`).
- * Optional for the same reasons; `reconcile` copies it from the originating
- * call onto a synthesized (interrupted) result.
- */
- readonly stepId?: StepId;
+ readonly type: "tool-result";
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly content: string;
+ readonly isError: boolean;
+ /**
+ * The step that produced the originating call — equal to the `stepId` on the
+ * matching `tool-call` chunk (same `toolCallId`) and on the `tool-result`
+ * AgentEvent, so a consumer groups a step's calls with their results.
+ * Generation provenance, not storage metadata (see `ToolCallChunk.stepId`).
+ * Optional for the same reasons; `reconcile` copies it from the originating
+ * call onto a synthesized (interrupted) result.
+ */
+ readonly stepId?: StepId;
}
/** An error that occurred during generation or tool dispatch. */
export interface ErrorChunk {
- readonly type: "error";
- readonly message: string;
- readonly code?: string;
+ readonly type: "error";
+ readonly message: string;
+ readonly code?: string;
}
/**
@@ -108,8 +108,8 @@ export interface ErrorChunk {
* Kept distinct from text so the log records provenance.
*/
export interface SystemChunk {
- readonly type: "system";
- readonly text: string;
+ readonly type: "system";
+ readonly text: string;
}
/**
@@ -118,8 +118,8 @@ export interface SystemChunk {
* rendered.
*/
export interface ChatMessage {
- readonly role: Role;
- readonly chunks: readonly Chunk[];
+ readonly role: Role;
+ readonly chunks: readonly Chunk[];
}
/**
@@ -141,9 +141,9 @@ export interface ChatMessage {
* `stepId`), which is intrinsic to the content and so travels with it.
*/
export interface StoredChunk {
- readonly seq: number;
- readonly role: Role;
- readonly chunk: Chunk;
+ readonly seq: number;
+ readonly role: Role;
+ readonly chunk: Chunk;
}
// ─── Reasoning effort ───────────────────────────────────────────────────────
@@ -167,10 +167,10 @@ export type ReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max";
* Cache fields are optional because not all providers expose cache metrics.
*/
export interface Usage {
- readonly inputTokens: number;
- readonly outputTokens: number;
- readonly cacheReadTokens?: number;
- readonly cacheWriteTokens?: number;
+ readonly inputTokens: number;
+ readonly outputTokens: number;
+ readonly cacheReadTokens?: number;
+ readonly cacheWriteTokens?: number;
}
// ─── Persisted metrics ───────────────────────────────────────────────────────
@@ -184,15 +184,15 @@ export interface Usage {
* served by `GET /conversations/:id/metrics`.
*/
export interface StepMetrics {
- readonly stepId: StepId;
- /** The step's token usage (all four counters; cache fields optional per `Usage`). */
- readonly usage: Usage;
- /** Time to first token (stream start → first text/reasoning delta). Optional — see `TurnStepCompleteEvent.ttftMs`. */
- readonly ttftMs?: number;
- /** Decode time (first token → stream end). Optional — see `TurnStepCompleteEvent.decodeMs`. */
- readonly decodeMs?: number;
- /** Total generation time for the step (stream start → stream end). Optional: present only when a clock was available. */
- readonly genTotalMs?: number;
+ readonly stepId: StepId;
+ /** The step's token usage (all four counters; cache fields optional per `Usage`). */
+ readonly usage: Usage;
+ /** Time to first token (stream start → first text/reasoning delta). Optional — see `TurnStepCompleteEvent.ttftMs`. */
+ readonly ttftMs?: number;
+ /** Decode time (first token → stream end). Optional — see `TurnStepCompleteEvent.decodeMs`. */
+ readonly decodeMs?: number;
+ /** Total generation time for the step (stream start → stream end). Optional: present only when a clock was available. */
+ readonly genTotalMs?: number;
}
/**
@@ -205,23 +205,23 @@ export interface StepMetrics {
* on every `AgentEvent`, the join key to the live stream.)
*/
export interface TurnMetrics {
- readonly turnId: string;
- /** Aggregate token usage across all steps in the turn. */
- readonly usage: Usage;
- /** Total wall-clock duration of the turn (turn start → turn end). Optional: present only when a clock was available. */
- readonly durationMs?: number;
- /** Per-step metrics in step order. */
- readonly steps: readonly StepMetrics[];
- /**
- * **Context size** — tokens the conversation occupies as of this turn: the
- * turn's FINAL step `inputTokens + outputTokens` (the last entry of `steps`),
- * NOT the aggregate `usage` (which sums per-step prompts and overcounts a
- * multi-step turn). The persisted, replayable counterpart of
- * `TurnDoneEvent.contextSize` and equal to it for the same turn. A client
- * reopening a past conversation reads the LAST turn's `contextSize` as the
- * current context usage. Optional: absent when no per-step usage was available.
- */
- readonly contextSize?: number;
+ readonly turnId: string;
+ /** Aggregate token usage across all steps in the turn. */
+ readonly usage: Usage;
+ /** Total wall-clock duration of the turn (turn start → turn end). Optional: present only when a clock was available. */
+ readonly durationMs?: number;
+ /** Per-step metrics in step order. */
+ readonly steps: readonly StepMetrics[];
+ /**
+ * **Context size** — tokens the conversation occupies as of this turn: the
+ * turn's FINAL step `inputTokens + outputTokens` (the last entry of `steps`),
+ * NOT the aggregate `usage` (which sums per-step prompts and overcounts a
+ * multi-step turn). The persisted, replayable counterpart of
+ * `TurnDoneEvent.contextSize` and equal to it for the same turn. A client
+ * reopening a past conversation reads the LAST turn's `contextSize` as the
+ * current context usage. Optional: absent when no per-step usage was available.
+ */
+ readonly contextSize?: number;
}
// ─── Message queue + steering ───────────────────────────────────────────────
@@ -234,12 +234,12 @@ export interface TurnMetrics {
* use (so a separate frontend repo can depend on the wire alone to render it).
*/
export interface QueuedMessage {
- /** Stable id (client-visible) for UI keying + dedup. */
- readonly id: string;
- /** The message text the client enqueued. */
- readonly text: string;
- /** When the message was enqueued (epoch-ms). */
- readonly queuedAt: number;
+ /** Stable id (client-visible) for UI keying + dedup. */
+ readonly id: string;
+ /** The message text the client enqueued. */
+ readonly text: string;
+ /** When the message was enqueued (epoch-ms). */
+ readonly queuedAt: number;
}
/**
@@ -252,7 +252,7 @@ export interface QueuedMessage {
* surface clears) and/or when the matching `TurnSteeringEvent` arrives.
*/
export interface QueuePayload {
- readonly messages: readonly QueuedMessage[];
+ readonly messages: readonly QueuedMessage[];
}
// ─── Outward events ─────────────────────────────────────────────────────────
@@ -262,34 +262,34 @@ export interface QueuePayload {
* Consumers (transport, persistence, notifications) pattern-match on `type`.
*/
export type AgentEvent =
- | StatusEvent
- | TurnStartEvent
- | TurnInputEvent
- | TurnTextDeltaEvent
- | TurnReasoningDeltaEvent
- | TurnToolCallEvent
- | TurnToolResultEvent
- | TurnToolOutputEvent
- | TurnUsageEvent
- | TurnStepCompleteEvent
- | TurnErrorEvent
- | TurnProviderRetryEvent
- | TurnDoneEvent
- | TurnSealedEvent
- | TurnSteeringEvent;
+ | StatusEvent
+ | TurnStartEvent
+ | TurnInputEvent
+ | TurnTextDeltaEvent
+ | TurnReasoningDeltaEvent
+ | TurnToolCallEvent
+ | TurnToolResultEvent
+ | TurnToolOutputEvent
+ | TurnUsageEvent
+ | TurnStepCompleteEvent
+ | TurnErrorEvent
+ | TurnProviderRetryEvent
+ | TurnDoneEvent
+ | TurnSealedEvent
+ | TurnSteeringEvent;
/** Status change for a conversation (e.g. idle → running). */
export interface StatusEvent {
- readonly type: "status";
- readonly conversationId: string;
- readonly status: string;
+ readonly type: "status";
+ readonly conversationId: string;
+ readonly status: string;
}
/** A turn has begun. */
export interface TurnStartEvent {
- readonly type: "turn-start";
- readonly conversationId: string;
- readonly turnId: string;
+ readonly type: "turn-start";
+ readonly conversationId: string;
+ readonly turnId: string;
}
/**
@@ -305,93 +305,93 @@ export interface TurnStartEvent {
* directly. Carries the raw prompt `text` (the same text passed to the provider).
*/
export interface TurnInputEvent {
- readonly type: "user-message";
- readonly conversationId: string;
- readonly turnId: string;
- readonly text: string;
+ readonly type: "user-message";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly text: string;
}
/** Incremental text content from the model during a turn. */
export interface TurnTextDeltaEvent {
- readonly type: "text-delta";
- readonly conversationId: string;
- readonly turnId: string;
- readonly delta: string;
+ readonly type: "text-delta";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly delta: string;
}
/** Incremental reasoning / thinking content during a turn. */
export interface TurnReasoningDeltaEvent {
- readonly type: "reasoning-delta";
- readonly conversationId: string;
- readonly turnId: string;
- readonly delta: string;
+ readonly type: "reasoning-delta";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly delta: string;
}
/** The model has requested a tool to be run. */
export interface TurnToolCallEvent {
- readonly type: "tool-call";
- readonly conversationId: string;
- readonly turnId: string;
- /**
- * The step that produced this call. Tool calls a model batches together in
- * one step share the same `stepId` — the grouping key for rendering a
- * parallel batch as one unit. Matches the `stepId` on the matching
- * `tool-result` event and on the persisted tool chunk
- * (`StoredChunk.chunk.stepId`).
- */
- readonly stepId: StepId;
- readonly toolCallId: string;
- readonly toolName: string;
- readonly input: unknown;
+ readonly type: "tool-call";
+ readonly conversationId: string;
+ readonly turnId: string;
+ /**
+ * The step that produced this call. Tool calls a model batches together in
+ * one step share the same `stepId` — the grouping key for rendering a
+ * parallel batch as one unit. Matches the `stepId` on the matching
+ * `tool-result` event and on the persisted tool chunk
+ * (`StoredChunk.chunk.stepId`).
+ */
+ readonly stepId: StepId;
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly input: unknown;
}
/** A tool has completed execution. */
export interface TurnToolResultEvent {
- readonly type: "tool-result";
- readonly conversationId: string;
- readonly turnId: string;
- /**
- * The step that produced the originating call. Equal to the `stepId` on the
- * matching `tool-call` event (same `toolCallId`) and on the persisted tool
- * chunk (`StoredChunk.chunk.stepId`), so a client groups a step's calls with
- * their results.
- */
- readonly stepId: StepId;
- readonly toolCallId: string;
- readonly toolName: string;
- readonly content: string;
- readonly isError: boolean;
- /**
- * How long the tool took to execute (dispatch → result), in milliseconds —
- * the backend's authoritative execution time, distinct from any client-side
- * wall-clock. Optional: present only when the runtime was given a clock.
- */
- readonly durationMs?: number;
+ readonly type: "tool-result";
+ readonly conversationId: string;
+ readonly turnId: string;
+ /**
+ * The step that produced the originating call. Equal to the `stepId` on the
+ * matching `tool-call` event (same `toolCallId`) and on the persisted tool
+ * chunk (`StoredChunk.chunk.stepId`), so a client groups a step's calls with
+ * their results.
+ */
+ readonly stepId: StepId;
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly content: string;
+ readonly isError: boolean;
+ /**
+ * How long the tool took to execute (dispatch → result), in milliseconds —
+ * the backend's authoritative execution time, distinct from any client-side
+ * wall-clock. Optional: present only when the runtime was given a clock.
+ */
+ readonly durationMs?: number;
}
/** Streaming output from a tool execution (e.g. shell stdout/stderr). */
export interface TurnToolOutputEvent {
- readonly type: "tool-output";
- readonly conversationId: string;
- readonly turnId: string;
- readonly toolCallId: string;
- readonly data: string;
- readonly stream: "stdout" | "stderr";
+ readonly type: "tool-output";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly toolCallId: string;
+ readonly data: string;
+ readonly stream: "stdout" | "stderr";
}
/** Token usage for the current step or turn. */
export interface TurnUsageEvent {
- readonly type: "usage";
- readonly conversationId: string;
- readonly turnId: string;
- /**
- * The step this usage report belongs to, so a consumer can attribute tokens
- * per step (and join with the matching `step-complete` timing by `stepId`).
- * Optional: absent when the runtime had no step context, and on usage emitted
- * before this field existed.
- */
- readonly stepId?: StepId;
- readonly usage: Usage;
+ readonly type: "usage";
+ readonly conversationId: string;
+ readonly turnId: string;
+ /**
+ * The step this usage report belongs to, so a consumer can attribute tokens
+ * per step (and join with the matching `step-complete` timing by `stepId`).
+ * Optional: absent when the runtime had no step context, and on usage emitted
+ * before this field existed.
+ */
+ readonly stepId?: StepId;
+ readonly usage: Usage;
}
/**
@@ -404,30 +404,30 @@ export interface TurnUsageEvent {
* content token (text or reasoning) was observed this step.
*/
export interface TurnStepCompleteEvent {
- readonly type: "step-complete";
- readonly conversationId: string;
- readonly turnId: string;
- readonly stepId: StepId;
- /** Time to first token: stream start → first text/reasoning delta. */
- readonly ttftMs?: number;
- /** Decode time: first token → stream end (generation total − TTFT). */
- readonly decodeMs?: number;
- /**
- * Total generation time for the step: stream start → stream end. Present
- * whenever a clock was available, even if no first token was seen (in which
- * case `ttftMs`/`decodeMs` are absent). When a first token was seen,
- * `genTotalMs === ttftMs + decodeMs`.
- */
- readonly genTotalMs?: number;
+ readonly type: "step-complete";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly stepId: StepId;
+ /** Time to first token: stream start → first text/reasoning delta. */
+ readonly ttftMs?: number;
+ /** Decode time: first token → stream end (generation total − TTFT). */
+ readonly decodeMs?: number;
+ /**
+ * Total generation time for the step: stream start → stream end. Present
+ * whenever a clock was available, even if no first token was seen (in which
+ * case `ttftMs`/`decodeMs` are absent). When a first token was seen,
+ * `genTotalMs === ttftMs + decodeMs`.
+ */
+ readonly genTotalMs?: number;
}
/** An error occurred during the turn. */
export interface TurnErrorEvent {
- readonly type: "error";
- readonly conversationId: string;
- readonly turnId: string;
- readonly message: string;
- readonly code?: string;
+ readonly type: "error";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly message: string;
+ readonly code?: string;
}
/**
@@ -442,51 +442,51 @@ export interface TurnErrorEvent {
* before that retry fires.
*/
export interface TurnProviderRetryEvent {
- readonly type: "provider-retry";
- readonly conversationId: string;
- readonly turnId: string;
- /** 0-based: this is the Nth retry about to happen. */
- readonly attempt: number;
- /** ms the client should expect to wait before the retry fires. */
- readonly delayMs: number;
- /** The endpoint's error verbatim (e.g. "HTTP 429: {…overloaded_error…}"). */
- readonly message: string;
- /** The HTTP code when known (e.g. "429"). */
- readonly code?: string;
+ readonly type: "provider-retry";
+ readonly conversationId: string;
+ readonly turnId: string;
+ /** 0-based: this is the Nth retry about to happen. */
+ readonly attempt: number;
+ /** ms the client should expect to wait before the retry fires. */
+ readonly delayMs: number;
+ /** The endpoint's error verbatim (e.g. "HTTP 429: {…overloaded_error…}"). */
+ readonly message: string;
+ /** The HTTP code when known (e.g. "429"). */
+ readonly code?: string;
}
/** The turn has completed (model finished generating). */
export interface TurnDoneEvent {
- readonly type: "done";
- readonly conversationId: string;
- readonly turnId: string;
- readonly reason: string;
- /**
- * Total wall-clock duration of the turn (turn start → turn end), in
- * milliseconds. Optional: present only when the runtime was given a clock.
- */
- readonly durationMs?: number;
- /**
- * Aggregate token usage across all steps in the turn — a convenience total so
- * a consumer need not sum the per-step `usage` events. Optional (absent if the
- * provider reported no usage).
- */
- readonly usage?: Usage;
- /**
- * **Context size** — the number of tokens the conversation now occupies: this
- * (the most recent) turn's FINAL step `inputTokens + outputTokens` (the full
- * prompt sent into the last LLM round-trip plus that round-trip's output). This
- * is the "tokens in context" figure a client renders as the chat's current
- * context usage, and a client treats the LATEST turn's value as the live total.
- *
- * Deliberately NOT the aggregate `usage` above: `usage` SUMS each step's
- * `inputTokens`, which overcounts a multi-step / tool-calling turn because every
- * step re-prefills the growing prompt — the final step's input already includes
- * all prior context, so its input+output is the true occupancy. Optional: absent
- * when no per-step usage was observed this turn (mirrors `usage`). A later field
- * will carry the model's max context-window LIMIT; this is only the current size.
- */
- readonly contextSize?: number;
+ readonly type: "done";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly reason: string;
+ /**
+ * Total wall-clock duration of the turn (turn start → turn end), in
+ * milliseconds. Optional: present only when the runtime was given a clock.
+ */
+ readonly durationMs?: number;
+ /**
+ * Aggregate token usage across all steps in the turn — a convenience total so
+ * a consumer need not sum the per-step `usage` events. Optional (absent if the
+ * provider reported no usage).
+ */
+ readonly usage?: Usage;
+ /**
+ * **Context size** — the number of tokens the conversation now occupies: this
+ * (the most recent) turn's FINAL step `inputTokens + outputTokens` (the full
+ * prompt sent into the last LLM round-trip plus that round-trip's output). This
+ * is the "tokens in context" figure a client renders as the chat's current
+ * context usage, and a client treats the LATEST turn's value as the live total.
+ *
+ * Deliberately NOT the aggregate `usage` above: `usage` SUMS each step's
+ * `inputTokens`, which overcounts a multi-step / tool-calling turn because every
+ * step re-prefills the growing prompt — the final step's input already includes
+ * all prior context, so its input+output is the true occupancy. Optional: absent
+ * when no per-step usage was observed this turn (mirrors `usage`). A later field
+ * will carry the model's max context-window LIMIT; this is only the current size.
+ */
+ readonly contextSize?: number;
}
/**
@@ -494,9 +494,9 @@ export interface TurnDoneEvent {
* This is the hook point for post-turn extensions (compaction, cache-warm).
*/
export interface TurnSealedEvent {
- readonly type: "turn-sealed";
- readonly conversationId: string;
- readonly turnId: string;
+ readonly type: "turn-sealed";
+ readonly conversationId: string;
+ readonly turnId: string;
}
/**
@@ -517,10 +517,10 @@ export interface TurnSealedEvent {
* event per drain; the combined text of all drained messages.
*/
export interface TurnSteeringEvent {
- readonly type: "steering";
- readonly conversationId: string;
- readonly turnId: string;
- readonly text: string;
+ readonly type: "steering";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly text: string;
}
// ─── Conversation metadata ───────────────────────────────────────────────────
@@ -542,23 +542,23 @@ export type ConversationStatus = "active" | "idle" | "closed";
* for cross-device persistence.
*/
export interface ConversationMeta {
- readonly id: string;
- readonly createdAt: number;
- readonly lastActivityAt: number;
- readonly title: string;
- readonly status: ConversationStatus;
- /**
- * The workspace this conversation belongs to. Always present; reads as
- * `"default"` for legacy conversations that were never explicitly assigned.
- * Conversations created with no `workspaceId` default to `"default"`.
- */
- readonly workspaceId: string;
- /**
- * Set on a compacted conversation: points to the archive conversation ID
- * that holds the full pre-compaction history. Absent on conversations
- * that have never been compacted.
- */
- readonly compactedFrom?: string;
+ readonly id: string;
+ readonly createdAt: number;
+ readonly lastActivityAt: number;
+ readonly title: string;
+ readonly status: ConversationStatus;
+ /**
+ * The workspace this conversation belongs to. Always present; reads as
+ * `"default"` for legacy conversations that were never explicitly assigned.
+ * Conversations created with no `workspaceId` default to `"default"`.
+ */
+ readonly workspaceId: string;
+ /**
+ * Set on a compacted conversation: points to the archive conversation ID
+ * that holds the full pre-compaction history. Absent on conversations
+ * that have never been compacted.
+ */
+ readonly compactedFrom?: string;
}
// ─── Compaction ──────────────────────────────────────────────────────────────
@@ -571,10 +571,10 @@ export interface ConversationMeta {
* pre-compaction history (non-destructive — the original history is preserved).
*/
export interface CompactionResult {
- readonly summary: string;
- readonly newConversationId: string;
- readonly messagesSummarized: number;
- readonly messagesKept: number;
+ readonly summary: string;
+ readonly newConversationId: string;
+ readonly messagesSummarized: number;
+ readonly messagesKept: number;
}
// ─── Workspaces ──────────────────────────────────────────────────────────────
@@ -590,24 +590,24 @@ export interface CompactionResult {
* created with no `workspaceId` are assigned to `"default"`.
*/
export interface Workspace {
- /** The URL slug (immutable). Lowercase `[a-z0-9-]`, 1–40 chars. */
- readonly id: string;
- /** Display title (editable). Defaults to `id` on creation. */
- readonly title: string;
- /** The workspace's default cwd, or `null` (fall through to server default). */
- readonly defaultCwd: string | null;
- /**
- * The workspace's default computer — an SSH config `Host` alias that
- * conversations in this workspace inherit when they set no `computerId` of
- * their own. `null` means local (no SSH; today's behavior). The computer
- * analog of `defaultCwd`. Resolved per-conversation by `getEffectiveComputer`
- * (per-conv `computerId` → this → `null`/local).
- */
- readonly defaultComputerId: string | null;
- /** Epoch-ms when the workspace was first created. */
- readonly createdAt: number;
- /** Epoch-ms of the most recent conversation activity in this workspace. */
- readonly lastActivityAt: number;
+ /** The URL slug (immutable). Lowercase `[a-z0-9-]`, 1–40 chars. */
+ readonly id: string;
+ /** Display title (editable). Defaults to `id` on creation. */
+ readonly title: string;
+ /** The workspace's default cwd, or `null` (fall through to server default). */
+ readonly defaultCwd: string | null;
+ /**
+ * The workspace's default computer — an SSH config `Host` alias that
+ * conversations in this workspace inherit when they set no `computerId` of
+ * their own. `null` means local (no SSH; today's behavior). The computer
+ * analog of `defaultCwd`. Resolved per-conversation by `getEffectiveComputer`
+ * (per-conv `computerId` → this → `null`/local).
+ */
+ readonly defaultComputerId: string | null;
+ /** Epoch-ms when the workspace was first created. */
+ readonly createdAt: number;
+ /** Epoch-ms of the most recent conversation activity in this workspace. */
+ readonly lastActivityAt: number;
}
/**
@@ -615,8 +615,8 @@ export interface Workspace {
* plus a conversation count.
*/
export interface WorkspaceEntry extends Workspace {
- /** Number of conversations assigned to this workspace. */
- readonly conversationCount: number;
+ /** Number of conversations assigned to this workspace. */
+ readonly conversationCount: number;
}
// ─── Computers ───────────────────────────────────────────────────────────────
@@ -634,21 +634,21 @@ export interface WorkspaceEntry extends Workspace {
* drives the frontend "known/new" indicator and is read-only.
*/
export interface Computer {
- /** The SSH config `Host` alias — also the `computerId` users select. */
- readonly alias: string;
- /** Resolved `HostName`/IP from the config (falls back to the alias itself). */
- readonly hostName: string;
- /** Resolved port (config `Port`, default 22). */
- readonly port: number;
- /** Resolved user (config `User`, default the current user). */
- readonly user: string;
- /** Resolved `IdentityFile` path (from the config, or `null` = default `~/.ssh/id_*`). */
- readonly identityFile: string | null;
- /**
- * Whether the host's key is already in `~/.ssh/known_hosts` (i.e. previously
- * connected). Drives the frontend "known/new" indicator. Read-only.
- */
- readonly knownHost: boolean;
+ /** The SSH config `Host` alias — also the `computerId` users select. */
+ readonly alias: string;
+ /** Resolved `HostName`/IP from the config (falls back to the alias itself). */
+ readonly hostName: string;
+ /** Resolved port (config `Port`, default 22). */
+ readonly port: number;
+ /** Resolved user (config `User`, default the current user). */
+ readonly user: string;
+ /** Resolved `IdentityFile` path (from the config, or `null` = default `~/.ssh/id_*`). */
+ readonly identityFile: string | null;
+ /**
+ * Whether the host's key is already in `~/.ssh/known_hosts` (i.e. previously
+ * connected). Drives the frontend "known/new" indicator. Read-only.
+ */
+ readonly knownHost: boolean;
}
/**
@@ -656,6 +656,6 @@ export interface Computer {
* a usage count. Parallel to `WorkspaceEntry`.
*/
export interface ComputerEntry extends Computer {
- /** Number of conversations/workspaces whose `computerId` resolves to this alias. */
- readonly usageCount: number;
+ /** Number of conversations/workspaces whose `computerId` resolves to this alias. */
+ readonly usageCount: number;
}