diff options
| author | Adam Malczewski <[email protected]> | 2026-06-06 18:55:53 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-06 18:55:53 +0900 |
| commit | 22936857685c318b71752d625808100b1a96e63e (patch) | |
| tree | 5e10a73d616c206e3820a8d8568e5f3d4c8a302e /packages/wire/src | |
| parent | 969afc45f895230fe3da1c737f18e64452efc8f2 (diff) | |
| download | dispatch-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.ts | 221 |
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; +} |
