Skip to main content

Module metadata

Module metadata 

Source
Expand description

Metadata: extra information attached to events, messages, tool calls and resume entries.

Token usage, a trace id, a finish reason — anything an application needs to carry alongside the conversation goes in metadata, an object that is open by key. Before it existed producers hung undeclared properties off events and hoped consumers passed them through; metadata is the declared, typed replacement, and consumers are required to carry it.

§Where it lives

Four places, all optional:

  • every event, declared once on BaseEvent so all 36 types have it;
  • every message, all seven roles;
  • every ToolCall — a tool call is not a message, and several calls can share one parent, so each carries its own;
  • every ResumeEntry, for envelope data about an answer — signatures, routing keys — as opposed to the answer.

§Shape

Any JSON value is allowed under a key, null included. The object itself is absent or an object, never null: an optional field with no value is omitted from the JSON entirely, in every official SDK, and this crate rejects "metadata": null at parse time rather than reading it as absent. Unlike some older optional fields, metadata has no legacy producers to tolerate — the crate-private serde_util::reject_null is the rule. An empty object is valid and means the same as omitting it.

The AGUI_METADATA_KEY ("ag-ui") is reserved for the protocol’s own use. Every other key is yours. Nothing rejects a write to it at runtime — that would contradict open-by-key — but treat it as off limits.

§Merging into messages

A message is assembled from a sequence of events, and the interesting values are only known at the end: a provider does not know its token usage until it has finished generating. So a consumer merges each event’s metadata into the message that event builds, as the sequence arrives, with merge_metadata: last write wins, key by key, and a nested object or array is replaced whole rather than blended. The client’s applier does this for the text, tool-call, activity and reasoning-message families; an event that builds no message — RUN_*, STEP_*, STATE_*, RAW, CUSTOM, REASONING_START/END/ENCRYPTED_VALUE, MESSAGES_SNAPSHOT, SUBAGENT_* — keeps its metadata to itself.

use ag_ui::{JsonObject, merge_metadata};
use serde_json::json;

let start: JsonObject = json!({ "source": "openai", "stage": "start" })
    .as_object().unwrap().clone();
let end: JsonObject = json!({ "stage": "end", "usage": { "output": 340 } })
    .as_object().unwrap().clone();

let merged = merge_metadata(Some(&start), Some(&end)).unwrap();
assert_eq!(merged["source"], "openai");         // nothing later set it
assert_eq!(merged["stage"], "end");             // last write wins
assert_eq!(merged["usage"]["output"], 340);     // arrived only at the end

Constants§

AGUI_METADATA_KEY
The key reserved for the protocol’s own use inside a metadata object.

Functions§

merge_metadata
Folds incoming into existing, key by key, with the last write winning.
merge_metadata_into
The in-place form of merge_metadata, for a consumer folding a stream of events into one message without cloning the accumulated object on every delta.