npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@silverprotocol/core

v0.12.11

Published

AgJSON core — the typed schema (AgInput / AgEvent / AgMessage), the Normalizer interface, and the normative reduce().

Readme


Every agent framework — Claude Agent SDK, OpenAI Agents SDK, Google ADK, LangGraph, Vercel AI SDK — streams its own shape of events. Build a client, a UI, or a tool that works across more than one and you end up writing a bespoke adapter per framework.

AgJSON is the wire format that ends that. It normalizes any framework's native event stream into one typed, versioned, forward-compatible shape — so a client written once works with every framework a normalizer exists for.

What's in core

  • The schema — AgInput, AgEvent, AgMessage, AgReduceResult, all as discriminated unions (typed, never any) with Zod validators.
  • The Normalizer contract — the stateful, per-invoke push(native): AgEvent[] / flush(): AgEvent[] interface every @silverprotocol/<framework> package implements.
  • reduce() — the normative fold from an AgEvent stream into the messages / turns / artifacts object graph your client renders.

Install

npm install @silverprotocol/core

To actually produce AgJSON from a framework, add its normalizer:

npm install @silverprotocol/claude-agent-sdk
# or @silverprotocol/openai-agents / @silverprotocol/google-adk

Consume AgJSON

core is the consumer side — validate an incoming event stream and fold it into the object graph, with the same code regardless of which framework produced it:

import { ingestAgEvents, Reducer, AGJSON_VERSION } from "@silverprotocol/core";

const events = ingestAgEvents(rawWireObjects, {
  onReject: ({ input, reason }) => console.warn("not an AgJSON event:", reason, input),
});

const reducer = new Reducer();
for (const ev of events) reducer.push(ev);

const { messages, turns, artifacts, memory } = reducer.result();
console.log(AGJSON_VERSION, messages);

ingestAgEvents validates raw wire objects against AgEvent and never throws (the consumer posture). Unknown fields pass through at every depth. An event it cannot validate (a newer event type, an unknown enum value, a malformed known type) is not folded: it rides in place as {type: "ext.agjson.ignored", seq, ignoredType, raw}, which keeps its seq slot so the Reducer never mistakes it for a gap. raw is for live inspection only; don't persist it. Input that is not an event at all (not an object, no string type, no number seq) returns nothing and goes to the optional onReject callback. Reducer folds the validated stream into the normative snapshot; AGJSON_VERSION is the wire version this build implements (1.0.0-draft.8).

Stored records and inputs

Messages and memory records you read back from storage, and inputs a client sends, follow the same forward-compatible posture (AgJSON draft.4 §0.2):

import { readStoredAgMessages, readStoredAgMemoryRecords, checkAgInput } from "@silverprotocol/core";

const { value: messages, reports } = readStoredAgMessages(rowsFromYourStore);
for (const r of reports) console.warn("unreadable", r.path, r.ignoredType, r.raw);

const checked = checkAgInput(requestBody);
if (!checked.ok) return reply(400, { code: checked.code, path: checked.path }); // "unknown-value" | "malformed" | "major-mismatch"
  • readStoredAgMessage(s) and readStoredAgMemoryRecords omit what they cannot read and report it: a content element that fails at any depth is omitted whole, and a record that fails is omitted from its array. Each report carries its path, the element's type when it has one, and the verbatim value, so inserting every report back at its index rebuilds the stored record. Unknown fields pass through at every depth; nothing is coerced. What they return is a view: keep storing and forwarding the record as received, never the view. A record carried inside an event keeps the event rule above: a messages.snapshot with one unknown block is ignored whole, so it cannot resync a parked live fold, while the same message read from storage keeps its readable blocks.
  • checkAgInput rejects the whole input, before you act on any of it, when it fails the schema in any way other than an unknown field, and names one path and one class. unknown-value: a string outside a closed set this version defines (a kind, an answer status, a reasoning effort, a block type in messages, run.system, run.context or results[].content, …). major-mismatch: another major version. malformed: everything else, including a missing value, a wrong JSON type, a non-object input, or a protocol other than "agjson". It judges protocol first, then version, then the rest; it checks the envelope members whatever the kind, and when the rest has both kinds of problem the class is malformed. An accepted input comes back with its unknown fields intact.
  • validateHitlAnswer rejects an answer whose status is not a defined one (unknown-status), so an unrecognized status is never read as a grant.

Persisting a fold

A turn record's displayRequired[] carries grounding UI that the provider requires a display to render (for example Google Search Suggestions), and the provider's terms may not allow storing it. Persist toPersistable(result), the same fold with every turn record's displayRequired omitted and nothing else changed, unless the grounding provider's terms permit storing displayRequired (SPEC §13.3, "Grounding records and the host"). Those terms can also limit how long a turn's sources[] and grounded content are kept and who they are re-displayed to; toPersistable leaves them in the fold, so apply the terms to them yourself.

import { toPersistable } from "@silverprotocol/core";

await store.save(toPersistable(reducer.result()));

Memory records follow the same rule. A memory.write of scope agent, user or skill records a write to the producer's own cross-thread store, not a promise that you keep it. toPersistable keeps scope thread memory and omits every other record unless you declare the non-thread scopes you persist (SPEC §8.0 host obligation 7), and each omission is reported to you by toPersistableWithReport; toPersistable returns the projection alone:

import { toPersistableWithReport } from "@silverprotocol/core";

const { result, omitted } = toPersistableWithReport(reducer.result(), { memoryScopes: ["user"] });
for (const o of omitted) log.info("memory not persisted", o.scope, o.key);
await store.save(result);

If you emit agent.capabilities, declare the same list there as memoryScopes.

A memory record carries the _meta of the write that set it. A value write replaces the record whole, _meta included, and a patch replaces _meta only when the write carries one. A member of the write that the record does not declare is not carried.

Reducer.push keeps its own copy of each event, and result() and toPersistable return a new copy on every call, so a host that mutates an event after pushing it, or mutates a result, never changes the fold. Core makes these copies itself and does not need a global structuredClone. One exception: a Date, Map, Set, RegExp, Error, ArrayBuffer, typed array or boxed primitive that a host places in an event it pushes is not copied. The fold, every result() and toPersistable share that object with the host's event, so a host that mutates it after push() sees the change in the fold. No AgJSON value holds one: an event read from the wire or emitted by a normalizer never does.

Produce AgJSON

Turn a framework's native stream into AgJSON with its normalizer — the output is framework-neutral, ready to send over the wire, persist, or feed straight to the Reducer above:

// `query` is the Claude Agent SDK's own streaming call — you keep using
// your framework as-is; the normalizer just translates what it emits.
import { query } from "@anthropic-ai/claude-agent-sdk";
import { createClaudeNormalizer } from "@silverprotocol/claude-agent-sdk";

const n = createClaudeNormalizer();
const agEvents = [];
for await (const native of query({ prompt: "call the echo tool" }))
  agEvents.push(...n.push(native)); // one native event → 0+ AgEvents
agEvents.push(...n.flush());        // seal anything still open

API stability

Symbols tagged @beta in the type declarations are outside the 1.x promise: they may change in a minor release. Two kinds are tagged:

  • In core, the producer helpers for writing a normalizer: StreamAssembler and its types (AssemblerCheckpoint, SeqlessEvent, the *Fields types), withAtomicPush with its options and its error pair (NORMALIZER_ERROR_MESSAGE, normalizerErrorCode), and the JSON-safety helpers with their issue types and markers (toJsonValue*, isJsonValue, JsonSafeIssue*, JSON_SAFE_*).
  • In the framework packages, an exported type that restates the upstream SDK's native shapes. It follows the package's peer range. Each package's own API (its createXNormalizer(), the options it takes and its helpers) carries the 1.x promise.

Learn more

License

MIT