Skip to main content
  • Version 0.1.0 · Purity pure zero-dep (browser-safe)
  • Purpose Parse runtime/Gateway message frames into typed text/thinking/tool blocks, and define the canonical TranscriptEntry shape its consumers order and store.
  • Workspace deps none
  • External deps none (devDependency: @clawboo/tsconfig)
A single flat barrel, no subpath exports. All consumers import from @clawboo/protocol.

Public API

Functions

Text extraction

  • extractText(message: unknown): string | null, pull display text from a message; strips the [[reply_to_current]] assistant prefix + thinking tags (assistant role), or the channel envelope + appended exec-approval-wait policy (other roles).
  • extractThinking(message: unknown): string | null, extract reasoning from block content, direct fields (thinking/analysis/reasoning/…), or tagged streams.
  • extractThinkingFromTaggedText(text: string): string, extract content between closed <think>/<analysis>/<thought>/<antthinking> tags only.
  • extractThinkingFromTaggedStream(text: string): string, like above but also returns the tail after a still-open thinking tag (streaming in progress).

Tool extraction

  • extractToolCalls(message: unknown): ToolCallRecord[], read type: 'toolCall' items from array content → { id?, name?, arguments }.
  • extractToolResult(message: unknown): ToolResultRecord | null, read a role: 'toolResult' | 'tool' message → { toolCallId?, toolName?, details, isError?, text }.
  • extractToolLines(message: unknown): string[], format any tool calls + result as [[tool]]/[[tool-result]] markdown lines.

Markdown formatting & parsing

  • formatToolCallMarkdown(call: ToolCallRecord): string, render a tool call as a [[tool]] line with a fenced JSON args block.
  • formatToolResultMarkdown(result: ToolResultRecord): string, render a tool result as a [[tool-result]] line with status meta + fenced text body.
  • parseToolMarkdown(line: string): ParsedToolMarkdown, split a [[tool]]/[[tool-result]] line into { kind, label, body }.
  • formatMetaMarkdown(meta): string, serialize { role, timestamp, thinkingDurationMs? } to a [[meta]] JSON line.
  • parseMetaMarkdown(line): { role, timestamp, thinkingDurationMs? } | null, parse a [[meta]] line back; null on invalid/missing role or non-positive timestamp.

Markdown type guards

  • isTraceMarkdown(line: string): boolean, line starts with [[trace]].
  • isToolMarkdown(line: string): boolean, line starts with [[tool]] or [[tool-result]].
  • isMetaMarkdown(line: string): boolean, line starts with [[meta]].

UI-metadata helpers

  • stripUiMetadata(text: string): string, remove reset/system-event/project-path injections, [message_id:…] tags, and the channel envelope.
  • isHeartbeatPrompt(text: string): boolean, text is a Read HEARTBEAT.md… prompt or carries a heartbeat-file-path line.

Main parser

  • parseMessage(raw: unknown): ParsedMessage, full parse → { text, thinking, toolCalls, toolResults, metadata }.

Agent helpers

  • isAgentFileName(value: string): value is AgentFileName, type guard over AGENT_FILE_NAMES.

Types & interfaces

  • ToolCall: { id?, name, arguments: Record<string, unknown> }.
  • ToolResult: { toolCallId?, name, output, isError?, details? }.
  • MessageMeta: { role?, timestamp?, thinkingDurationMs? }.
  • ParsedMessage: { text, thinking, toolCalls, toolResults, metadata }, the parseMessage return.
  • ParsedToolMarkdown: { kind: 'call' | 'result', label, body }.
  • ToolResultRecord: { toolCallId?, toolName?, details, isError?, text? }, the extractToolResult shape.
  • TranscriptEntryKind: 'meta' | 'user' | 'assistant' | 'thinking' | 'tool'.
  • TranscriptEntryRole: 'user' | 'assistant' | 'tool' | 'system' | 'other'.
  • TranscriptEntrySource: 'local-send' | 'runtime-chat' | 'runtime-agent' | 'history' | 'legacy'.
  • TranscriptEntry: the canonical transcript row: { entryId, role, kind, text, sessionKey, runId, source, timestampMs, sequenceKey, confirmed, fingerprint }. fingerprint is an opaque per-entry id, not a content hash — every producer mints a fresh UUID, and nothing derives identity from it. Dedup keys off entryId first; the web store then applies a second, exact-frame signature (kind|role|timestampMs|text), so a re-delivered frame is still collapsed even though it arrives with a fresh entryId. A repeated message that differs in any of those four fields is kept.
  • AgentFileName: union of the 7 AGENT_FILE_NAMES literals.
  • AgentFileMeta: { title, hint }.
ToolCallRecord is referenced in several function signatures above (formatToolCallMarkdown, extractToolCalls) but is a module-local type, not exported from the barrel.

Constants

  • AGENT_FILE_NAMES: readonly ['AGENTS.md', 'SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md', 'MEMORY.md'].
  • AGENT_FILE_META: Record<AgentFileName, AgentFileMeta>: { title, hint } per file.
  • AGENT_FILE_PLACEHOLDERS: Record<AgentFileName, string>: editor placeholder text per file.

Used by

  • @clawboo/events, Bridge parsers reuse extractText / parseMessage / isReasoningStream shapes for the policy pipeline.
  • @clawboo/adapter-openclaw, maps Gateway frames → RuntimeEvent via the pure parseChatPayload/parseMessage/extractText helpers.
  • apps/web chat + group-chat surfaces, parseToolMarkdown, the [[tool]]/[[trace]]/[[meta]] guards, the TranscriptEntry shape, and AGENT_FILE_NAMES / AGENT_FILE_META for the agent file editor.
  • @clawboo/agent-registry, mirrors AGENT_FILE_NAMES locally to stay dependency-free (does not import this package).

Source

Barrel: packages/protocol/src/index.ts (single flat module; no subpath exports in package.json).

See also

Last modified on August 6, 2026