Documentation
¶
Overview ¶
Package display is the trust boundary for Phase-26's typed-display protocol (GAP-1 / HARDEN-08). It is the ONLY component allowed to turn an untrusted structured tool result into a rich-renderable typed Payload: every other tier (the AG-UI translator, the cockpit DisplayRouter) treats a Payload as already trusted. A tool with no normalizer here returns (Payload{}, false) so the caller falls back to the raw, escaped tool card (D-FALLBACK) — typed rich rendering is reserved for results this package recognized.
The package is PURE DATA: Normalize and the per-tool normalizers do no I/O, hold no state across turns (the per-turn source Registry is the caller's to own), and have no side effects. The same normalizer feeds the live SSE path AND the replay path (D-06): both consume the tool's result-preview shape, so a reopened thread re-derives byte-identical displays.
Two safety invariants live here:
- system_event (systemevent.go) consumes ONLY the web/errors.go sanitize()- classified reason or the swarm Status enum — never free-form error text, so no SSRF internal (resolved IP / internal host / redirect chain) can reach a user-visible card (D-07).
- the source Registry (sources.go) owns the [n]→source map with stable URL-keyed RefIDs, so the model can only cite a number the registry rendered (D-05) — it cannot reference a hallucinated source.
Index ¶
Constants ¶
const ( StatusOK = "ok" StatusFailed = "failed" StatusNeedsUserInput = "needs_user_input" )
Status values a swarm ChildReport carries (mirror of swarm.Status*, report.go :18-23). Redeclared here for the same cycle-break reason ChildReport is (see payload.go) — the strings match the swarm package byte-for-byte.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Artifact ¶
type Artifact struct {
Filename string `json:"filename"`
SizeBytes int64 `json:"size_bytes,omitempty"`
Path string `json:"path,omitempty"`
}
Artifact is a local file output chip (filename + size + path); it reuses the existing aura.artifact / ArtifactDelta delivery plumbing on the channel side.
type Chart ¶
type Chart struct {
XLabels []string `json:"x_labels"`
YValues []float64 `json:"y_values"`
XAxisLabel string `json:"x_axis_label,omitempty"`
}
Chart is the zero-dep SVG / table-as-bars MVP shape (D-02), swap-ready for a real charting lib later.
type ChildReport ¶
type ChildReport struct {
GoalIndex int `json:"goal_index"`
ChildID string `json:"child_id"`
Status string `json:"status"`
Summary string `json:"summary,omitempty"`
Error string `json:"error,omitempty"`
Question string `json:"question,omitempty"`
Options []string `json:"options,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}
ChildReport is the wire-identical mirror of swarm.ChildReport (swarm/report.go :32-41). It is redeclared here rather than imported because swarm imports internal/agent, and event.go (package agent) imports this package — importing swarm here would close an agent→display→swarm→agent cycle. The same redeclare- to-break-a-cycle idiom is used by agent.PauseOption (event.go:135). The JSON tags match swarm.ChildReport byte-for-byte so the swarm_spawn result decodes straight into this shape (D-08 / SWARM-01).
type CodeInput ¶
type CodeInput struct {
Body string
Lang string
ArtifactFilename string
ArtifactSize int64
ArtifactPath string
}
CodeInput is the structured value a code-producing tool (sandbox_exec / shell_exec) hands the normalizer. It is the SAME shape the live path holds and the replay path re-derives from the persisted preview (Pitfall 4): Body is the command output text, Lang the highlight hint, and the optional Artifact* fields name a single file the run produced.
type Document ¶
type Document struct {
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
ContentMD string `json:"content_md"`
}
Document is a web_fetch markdown page (from web.Page), rendered through the sanitized MarkdownText host on the cockpit (D-10).
type Kind ¶
type Kind string
Kind is the typed-display discriminator. Each constant string is the wire literal the cockpit DisplayRouter switches on (web/src/chat/displays/types.ts, 26-02) — renaming one is a wire-contract break, never do it without amending both sides.
const ( // KindWebResult identifies a normalized web search result list payload. KindWebResult Kind = "web_result" // KindDocument identifies a fetched or extracted markdown document payload. KindDocument Kind = "document" // KindCode identifies a shell, sandbox, or code-like text payload. KindCode Kind = "code" // KindLocalArtifact identifies a local file artifact payload. KindLocalArtifact Kind = "local_artifact" // KindTable identifies a structured table payload. KindTable Kind = "table" // KindChart identifies a chart-ready numeric payload. KindChart Kind = "chart" // KindSystemEvent identifies a sanitized system status or error payload. KindSystemEvent Kind = "system_event" // KindSwarmReport identifies a swarm child-report payload. KindSwarmReport Kind = "swarm_report" )
type Payload ¶
type Payload struct {
Type Kind `json:"type"`
ToolCallID string `json:"tool_call_id"`
Title string `json:"title,omitempty"`
WebResults []WebItem `json:"web_results,omitempty"`
Document *Document `json:"document,omitempty"`
Code *Code `json:"code,omitempty"`
Artifact *Artifact `json:"artifact,omitempty"`
Table *Table `json:"table,omitempty"`
Chart *Chart `json:"chart,omitempty"`
System *System `json:"system,omitempty"`
Swarm []ChildReport `json:"swarm,omitempty"`
Sources []Source `json:"sources,omitempty"`
}
Payload is the flat tagged union a normalizer emits (R1). It is a struct, not an interface, so decode(encode) is identity and JSON omitempty keeps the wire lean: exactly the per-type field for Type is set, the rest omit. ToolCallID is the sseAdapter correlation key (the reducer attaches the payload to the matching tool part). Sources carries the per-turn registry (D-05) for any payload that consulted web sources; it powers the citation hovercard + Source Explorer downstream.
func Normalize ¶
Normalize is the dispatch (DISP-01 / D-FALLBACK): it maps a structured tool result to a typed Payload, switching on the tool name. An unrecognized tool — or a recognized tool handed a result of the wrong concrete type — returns (Payload{}, false) so the caller renders the raw, escaped tool card. Only a recognized result yields a typed Payload (HARDEN-08).
Normalize builds a fresh per-call Registry, so the returned Payload carries the sources for THIS result alone. A caller threading one source registry across several web_search calls in a turn (for the numbered model preview, D-05) should call the per-tool normalizers with a shared *Registry instead.
result must be the concrete decoded value the live path holds (Pitfall 4):
- web_search -> []web.Result
- web_fetch -> web.Page
- swarm_spawn -> []ChildReport
- shell_exec / sandbox_exec -> CodeInput
- any tool's error -> *web.WebError
func NormalizeToolPreview ¶
NormalizeToolPreview is the SINGLE decode+normalize site shared by the live agent loop and the replay snapshot projection (D-06, "one normalizer for live + replay"). It reverses a source-bearing web tool's model-visible result preview — the exact string the runner persists as the RoleTool turn Content AND streams as the live TOOL_CALL_RESULT — back into the concrete value Normalize switches on, then runs the shared normalizer with the caller-owned per-turn registry. Because live and replay both consume the SAME preview shape, the re-derived Payload is identical by construction (Pitfall 4: no preview-vs-full-bytes drift).
Only the source-bearing web tools this protocol wires are recognized:
- web_search → []web.Result (the adapter marshals {"results":[…]}, web_search.go)
- web_fetch → web.Page (the adapter marshals the Page directly)
Every other tool, an empty preview, an inline {error,…} preview, or malformed JSON returns (Payload{}, false) so the caller keeps the raw escaped card (D-FALLBACK) and the registry is untouched — replay D-FALLBACK == live D-FALLBACK.
func NormalizeWithRegistry ¶
NormalizeWithRegistry is Normalize with an explicit tool-call id (the sseAdapter correlation key) and a caller-owned per-turn source Registry, so several web_search calls in one turn accumulate into one numbered source list (D-05).
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the per-turn source accumulator (D-05). Code — never the model — owns the [n]->source map: a unique normalized URL gets a stable RefID and a stable 1-based Index the first time it is seen, and the same URL returns the same RefID/Index on every later call in the turn. The model can therefore only cite a number the registry rendered (it cannot reference a hallucinated source).
A Registry is NOT safe for concurrent use; each turn owns one and feeds it sequentially as tools complete (the live path and the replay path each build a fresh Registry, so live and replay are identical by construction — Pitfall 4).
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty per-turn source registry.
func (*Registry) Add ¶
Add records a consulted source for rawURL, assigning a stable RefID + 1-based Index on first sight and returning the existing entry's RefID on a repeat. An empty/unparseable URL is skipped (returns ""). cited marks the source as referenced; a later cited=true upgrades a previously-consulted entry (a source the model cited is also one it consulted, never the reverse).
func (*Registry) RenderSourceList ¶
RenderSourceList renders the numbered "[n] Title — url" block the model sees in the tool-result preview (D-05). It is the VOLATILE per-turn data that must ride the tail-inject copy path (prompt.Budget), never messages[0] — putting it in the cached prefix trips the AG-031 KV-drift guard. Returns "" for an empty registry.
type Source ¶
type Source struct {
RefID string `json:"ref_id"`
Index int `json:"index"`
Type Kind `json:"type,omitempty"`
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
Snippet string `json:"snippet,omitempty"`
Confidence float64 `json:"confidence,omitempty"`
Cited bool `json:"cited"`
}
Source is one registry entry (D-05 / R5). RefID is the stable URL-keyed id the model cites; Index is the 1-based display number; Cited distinguishes a source the model referenced inline from one merely consulted (Source Explorer shows all consulted, chips show cited).
type System ¶
type System struct {
Class string `json:"class"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
Severity string `json:"severity,omitempty"`
}
System is the system_event payload (D-07). It carries ONLY classified, safe fields: Class is the stable error/status enum, Reason the sanitized reason string (never a raw internal URL/IP), Message a short safe headline, Severity a render hint. It mirrors web.WebError's safe surface — no sensitive field exists.
type WebItem ¶
type WebItem struct {
Title string `json:"title"`
URL string `json:"url"`
Snippet string `json:"snippet,omitempty"`
Domain string `json:"domain,omitempty"`
Score float64 `json:"score,omitempty"`
PublishedAt *string `json:"published_at,omitempty"`
Thumbnail string `json:"thumbnail,omitempty"`
RefID string `json:"ref_id,omitempty"`
}
WebItem is one web_result hit (from web.Result + ResultMetadata). Domain is the derived URL host; RefID ties the item to its registry Source entry (D-05). The metadata fields stay omitempty so a tier-1 (no-metadata) search renders clean.