@psci-labs/chat-protocol
v0.1.0
Published
Wire contracts (Zod-typed messages, events, persistence types) shared between @psci-labs/chat-runtime and @psci-labs/chat-ui
Readme
@psci-labs/chat-protocol
Wire contracts (Zod-typed messages, events, persistence types) shared between
@psci-labs/chat-runtime and @psci-labs/chat-ui.
This package owns no behavior — only schemas. Both runtime and UI parse against these so a misshapen wire payload fails at the boundary, not somewhere deep.
You will typically depend on this transitively through chat-runtime or
chat-ui. Install it directly only when you need to reference the schemas
from your own code (e.g. when writing a custom PersistenceAdapter).
pnpm add @psci-labs/chat-protocolExports
Branded identifiers (ids.ts)
SessionId, ThreadId — Zod-branded strings. Distinct at the type level so
you can't accidentally pass one where the other is expected.
Message parts (messages.ts)
TextPart—{ type: 'text', text }ThinkingPart—{ type: 'thinking', text }ImagePart—{ type: 'image', data, mediaType: 'image/*' }ToolCall—{ type: 'tool_call', id, name, input, state? }ToolResult—{ type: 'tool_result', toolCallId, content, isError? }MessagePart— discriminated union of all five
Messages (messages.ts)
UserMessage— content narrowed totext | image | tool_resultAssistantMessage— content narrowed totext | thinking | image | tool_callChatMessage— discriminated union byrole
The role-level narrowing means a tool_call inside a UserMessage (or a
tool_result inside an AssistantMessage) fails to parse — the schema captures
where each part type can legitimately appear.
SDK event envelope (events.ts)
SDKEvent — loose passthrough for events emitted by the Claude Agent SDK (and,
in the future, other harnesses). Only the fields we route on (type,
session_id, subtype) are typed; everything else is preserved as unknown so
SDK shape drift does not break the protocol.
Persistence (persistence.ts)
Checkpoint—{ threadId, sessionId, sdkVersion, state, metadata, updatedAt }SessionMetadata—{ sessionId, threadId, startedAt, previousSessionIds, totalCostUsd?, ... }StoredMessage—{ sessionId, message: ChatMessage }
Checkpoint.sdkVersion is required — explicit guard against silent shape
drift when the agent SDK upgrades and state no longer means what it used to.
Auth (user-context.ts)
UserContext — { userId: non-empty string, attributes?: Record<string, unknown> }.
Auth-agnostic identity passed from the host app's auth integration into the
runtime (getUserContext(req)).
