display

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 5 Imported by: 0

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

View Source
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 Code

type Code struct {
	Body string `json:"body"`
	Lang string `json:"lang,omitempty"`
}

Code is a sandbox/shell text body with its language tag for lazy highlighting.

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

func Normalize(toolName string, result any) (Payload, bool)

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

func NormalizeToolPreview(toolCallID, toolName, preview string, reg *Registry) (Payload, bool)

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

func NormalizeWithRegistry(toolCallID, toolName string, result any, reg *Registry) (Payload, bool)

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

func (r *Registry) Add(srcType Kind, title, rawURL, snippet string, cited bool) string

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

func (r *Registry) RenderSourceList() string

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.

func (*Registry) Sources

func (r *Registry) Sources() []Source

Sources returns the accumulated registry entries in stable Index order (a copy so the caller cannot mutate the registry's backing slice).

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 Table

type Table struct {
	Columns []string   `json:"columns"`
	Rows    [][]string `json:"rows"`
}

Table is a structured grid (D-14: client sorts/filters/exports it).

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL