summaryrefslogtreecommitdiffhomepage
path: root/packages/wire/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-07 16:07:35 +0900
committerAdam Malczewski <[email protected]>2026-06-07 16:07:35 +0900
commit904c6d7cc882ea6e092f03f9f487d80b75426440 (patch)
tree0b97107a859f8d347071c01a6907c778dd9cd05a /packages/wire/src
parentefddee1edd2924725a4dd240894666ede97b67b9 (diff)
downloaddispatch-904c6d7cc882ea6e092f03f9f487d80b75426440.tar.gz
dispatch-904c6d7cc882ea6e092f03f9f487d80b75426440.zip
feat(wire,kernel,conversation-store): step grouping via stepId for batched tool calls
Expose a per-step grouping key so a client can render a model's batched/parallel tool calls (those emitted in one step) as one unit, on both the live stream and replayed history. Key = branded StepId, derived turnId#stepIndex (0-based). - [email protected]: required stepId on Turn{Tool,ToolResult}Event; optional stepId on Tool{Call,Result}Chunk (generation provenance on the chunk, not the StoredChunk envelope — StoredChunk unchanged). [email protected] (re-export bump). - kernel-runtime: mint stepId per step; stamp on tool chunks + tool events. - conversation-store: chunk-carried stepId round-trips append/load/loadSince for free; reconcile copies it onto synthesized (interrupted) results. - cli: stepId added to event test fixtures (renderer unchanged). typecheck clean; 509 vitest + 89 bun; biome 0/0. FE courier reply + reference snapshots regenerated in ../dispatch-web.
Diffstat (limited to 'packages/wire/src')
-rw-r--r--packages/wire/src/index.ts55
1 files changed, 51 insertions, 4 deletions
diff --git a/packages/wire/src/index.ts b/packages/wire/src/index.ts
index 82fb3ed..90213b4 100644
--- a/packages/wire/src/index.ts
+++ b/packages/wire/src/index.ts
@@ -14,7 +14,16 @@ export type Role = "system" | "user" | "assistant" | "tool";
/** Opaque identifier for a turn (one user→assistant cycle). */
export type TurnId = string & { readonly __brand: "TurnId" };
-/** Opaque identifier for a step (one LLM round-trip within a turn). */
+/**
+ * Opaque identifier for a step (one LLM round-trip within a turn). It is the
+ * authoritative grouping key for the tool calls a model batches together in a
+ * single step (parallel/batched calls): every `tool-call`/`tool-result` event
+ * and every persisted tool chunk (`ToolCallChunk`/`ToolResultChunk`) from the
+ * same step carries the SAME `stepId`, so a client groups a batch purely by
+ * equality — identically on the live stream and in replayed history. Per-turn
+ * unique and gap-free in step order; treat it as opaque (do not parse it). The
+ * runtime derives it deterministically from the turn id + 0-based step index.
+ */
export type StepId = string & { readonly __brand: "StepId" };
/**
@@ -51,6 +60,18 @@ export interface ToolCallChunk {
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;
}
/**
@@ -64,6 +85,15 @@ export interface ToolResultChunk {
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. */
@@ -98,9 +128,11 @@ export interface ChatMessage {
* sync cursor, assigned in append order) and records the `role` of the message
* it belongs to. This makes a flat seq-ordered stream both incrementally
* syncable ("give me chunks after seq N") and regroupable into messages by the
- * client. `chunk` is the pure content unit, unchanged — `Chunk` itself never
- * carries storage metadata (it is also passed to/from the provider, which has
- * no use for a cursor).
+ * client. `chunk` is the content unit — `Chunk` carries no storage/sync cursor
+ * (`seq` lives here on the envelope, not on the chunk, since it is assigned by
+ * the store and the provider has no use for it). A chunk MAY still carry
+ * generation provenance assigned at production time (e.g. a tool chunk's
+ * `stepId`), which is intrinsic to the content and so travels with it.
*/
export interface StoredChunk {
readonly seq: number;
@@ -175,6 +207,14 @@ 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;
@@ -185,6 +225,13 @@ 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;