summaryrefslogtreecommitdiffhomepage
path: root/packages/wire/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-06 18:55:53 +0900
committerAdam Malczewski <[email protected]>2026-06-06 18:55:53 +0900
commit22936857685c318b71752d625808100b1a96e63e (patch)
tree5e10a73d616c206e3820a8d8568e5f3d4c8a302e /packages/wire/src
parent969afc45f895230fe3da1c737f18e64452efc8f2 (diff)
downloaddispatch-22936857685c318b71752d625808100b1a96e63e.tar.gz
dispatch-22936857685c318b71752d625808100b1a96e63e.zip
feat(frontend,wire): surface system (FE slice 1) + @dispatch/wire types-only split (B2)
FE slice 1 — backend-declared, frontend-agnostic surface system (verified live): new types-only @dispatch/ui-contract (SurfaceSpec / field kinds / region / ActionRef / catalog), surface-registry (typed service handle), transport-ws (Bun WS :24205, path-agnostic upgrade), surface-loaded-extensions (first real surface); kernel HostAPI.getExtensions; host-bin wiring; bin/up. Harness: retire AGENTS 'backend only', ORCHESTRATOR §3/§7/§8, frontend-design.md locked. B2 — wire-types split (chat-slice prerequisite): new types-only @dispatch/wire single-sources the wire ABI (AgentEvent + 11 variants; conversation model Chunk/ChatMessage/Role/TurnId/StepId + 6 chunk variants; Usage) with zero @dispatch/* deps. @dispatch/kernel re-exports via shims so its public surface is byte-identical (zero consumer blast radius). transport-contract re-exports AgentEvent from @dispatch/wire and drops its @dispatch/kernel dependency, so HTTP clients (the web frontend) consume the wire without the kernel runtime. tsc -b + biome clean; 460 vitest + 77 bun pass.
Diffstat (limited to 'packages/wire/src')
-rw-r--r--packages/wire/src/index.ts221
1 files changed, 221 insertions, 0 deletions
diff --git a/packages/wire/src/index.ts b/packages/wire/src/index.ts
new file mode 100644
index 0000000..d2ea341
--- /dev/null
+++ b/packages/wire/src/index.ts
@@ -0,0 +1,221 @@
+/**
+ * @dispatch/wire — pure wire types shared by the kernel, the transport
+ * contract, and out-of-repo clients (the web frontend).
+ *
+ * Types ONLY: zero runtime, zero `@dispatch/*` dependencies, so a client can
+ * depend on the wire without pulling the kernel runtime.
+ */
+
+// ─── Conversation model ─────────────────────────────────────────────────────
+
+/** Who produced a message. */
+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). */
+export type StepId = string & { readonly __brand: "StepId" };
+
+/**
+ * A chunk is one ordered piece of a message — the atomic unit of the
+ * append-only conversation log. Discriminated by `type`.
+ */
+export type Chunk =
+ | 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;
+}
+
+/** A piece of model reasoning / thinking content (e.g. extended thinking). */
+export interface ThinkingChunk {
+ readonly type: "thinking";
+ readonly text: string;
+}
+
+/**
+ * A model's request to run a tool. The kernel routes by `name`; the tool
+ * implementation never sees this directly — it receives parsed `input` via
+ * `ToolContract.execute`.
+ */
+export interface ToolCallChunk {
+ readonly type: "tool-call";
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly input: unknown;
+}
+
+/**
+ * The result of a tool execution, attributed to the originating tool-call id.
+ * The kernel guarantees every tool-call chunk gets exactly one result chunk
+ * (synthesized if interrupted — see reconcile).
+ */
+export interface ToolResultChunk {
+ readonly type: "tool-result";
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly content: string;
+ readonly isError: boolean;
+}
+
+/** An error that occurred during generation or tool dispatch. */
+export interface ErrorChunk {
+ readonly type: "error";
+ readonly message: string;
+ readonly code?: string;
+}
+
+/**
+ * A system-injected message (e.g. system prompt, context assembly output).
+ * Kept distinct from text so the log records provenance.
+ */
+export interface SystemChunk {
+ readonly type: "system";
+ readonly text: string;
+}
+
+/**
+ * A chat message: a role plus an ordered sequence of chunks. Messages are the
+ * unit passed to and from the provider; chunks are the unit persisted and
+ * rendered.
+ */
+export interface ChatMessage {
+ readonly role: Role;
+ readonly chunks: readonly Chunk[];
+}
+
+// ─── Usage ──────────────────────────────────────────────────────────────────
+
+/**
+ * Token usage counters for a single step. All fields are counts of tokens.
+ * 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;
+}
+
+// ─── Outward events ─────────────────────────────────────────────────────────
+
+/**
+ * The union of all events the runtime emits outward during a turn.
+ * Consumers (transport, persistence, notifications) pattern-match on `type`.
+ */
+export type AgentEvent =
+ | StatusEvent
+ | TurnStartEvent
+ | TurnTextDeltaEvent
+ | TurnReasoningDeltaEvent
+ | TurnToolCallEvent
+ | TurnToolResultEvent
+ | TurnToolOutputEvent
+ | TurnUsageEvent
+ | TurnErrorEvent
+ | TurnDoneEvent
+ | TurnSealedEvent;
+
+/** Status change for a conversation (e.g. idle → running). */
+export interface StatusEvent {
+ 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;
+}
+
+/** 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;
+}
+
+/** Incremental reasoning / thinking content during a turn. */
+export interface TurnReasoningDeltaEvent {
+ 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;
+ 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;
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly content: string;
+ readonly isError: boolean;
+}
+
+/** 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";
+}
+
+/** Token usage for the current step or turn. */
+export interface TurnUsageEvent {
+ readonly type: "usage";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly usage: Usage;
+}
+
+/** An error occurred during the turn. */
+export interface TurnErrorEvent {
+ readonly type: "error";
+ readonly conversationId: string;
+ readonly turnId: string;
+ readonly message: string;
+ 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;
+}
+
+/**
+ * The turn has been sealed — all chunks persisted, history is final.
+ * 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;
+}