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

@cool-ai/beach-protocol

v0.6.0

Published

Envelope shape, canonical part-type registry, and Agent Card for Beach applications.

Readme

@cool-ai/beach-protocol

Owns the envelope shape, the canonical part-type registry, and the Agent Card — the artefacts a Beach application produces regardless of which edge will deliver them. The envelope is how a participant returns a result through the manifest handler; the part-type registry is an open registry with a canonical core that consumers extend at startup.

Home: cool-ai.org · Documentation: cool-ai.org/docs

Install

npm install @cool-ai/beach-protocol

Concern

@cool-ai/beach-protocol provides:

  • Envelope typesAgentResponseEnvelope as a stream of part events with a settlement marker. Parts arrive progressively during a handler invocation; streaming transports emit them immediately, buffered transports accumulate until conversationState: 'complete'.
  • Envelope builder — assembles an envelope stream from a session's mailbox and the handler's current respond() parts. See https://cool-ai.org/docs/envelope.
  • llm-context translator — runs a fast secondary model (typically Haiku) to translate a handler's conversational response into analytical prose for peer LLMs. Skipped when no peer-LLM originator is present, or when the peer's Agent Card declares it does not consume llm-context.
  • A2A Message Part types — the standard A2A Message shape (role, parts[]) with Beach's partType metadata extensions.
  • Agent Card schema + types — re-exports A2A SDK AgentCard, AgentSkill, AgentInterface, AgentCapabilities, AgentProvider, and AgentExtension from @a2a-js/sdk. Adds BEACH_EXTENSION_URI and AgentCardBeachExtension for Beach-specific extension data. Provides a buildAgentCard() builder and JSON Schema validator.
  • Agent Card publisher middleware — serves /.well-known/agent-card.json (A2A canonical path).
  • Canonical part types — registered against @cool-ai/beach-core's PartTypeRegistry.

Canonical part types registered by this package

@cool-ai/beach-protocol registers the envelope data parts against @cool-ai/beach-core's PartTypeRegistry on its module load. Core itself owns the conversational-signal partTypes (ack, thinking, response, clarify, error) because they don't depend on envelope delivery — see @cool-ai/beach-core.

The parts registered by this package:

| partType | Purpose | |----------|---------| | domain-data | Raw structured data. A2A data part. | | llm-context | Natural-language analysis from the producing agent's LLM, for a peer LLM to reason about the data. | | a2ui-surface | A2UI createSurface / updateDataModel / updateComponents messages. | | artifact | Binary or URL-referenced artifact (file, image, audio). Reference by artifactId; bytes live in @cool-ai/beach-missives's ArtifactStore. | | reasoning-trace | Captured model-native reasoning (Claude thinking blocks etc.). Persistence configurable. | | citation | Source reference bound to a specific path in domain-data. | | approval-request | HITL approval request for a pending tool call. Schema defined below. | | approval-response | The decision (granted / denied) with correlation ID. Schema defined below. | | progress | Structured progress updates for long-running operations (e.g. research sections). | | setState | Handler-initiated state-coordinate transition. Recognised by the EventRouter and applied to session state before delivery. Per-component opt-in via state-machine.json. Stripped from outbound formatters — router instruction, not user content. See state machines guide. |

Consumers register further types against the core registry at startup.

Approval part schemas

Because approvals flow across channels (a chat request may be approved by email reply or WhatsApp "yes"), the parts carry an explicit correlation ID.

// approval-request part `data` shape
interface ApprovalRequest {
  approvalId: string;       // correlation ID — session-scoped unique
  toolName: string;
  toolCallId: string;       // the LLM's own tool-call identifier
  args: Record<string, unknown>;
  handler: string;          // which handler requested the tool
  turn: string;             // handler invocation event ID
  session: string;          // session ID
  expiresAt?: string;       // ISO datetime; auto-deny after
}

// approval-response part `data` shape
interface ApprovalResponse {
  approvalId: string;       // matches the request
  decision: 'granted' | 'denied';
  reason?: string;
  decidedBy?: string;       // user ID, policy name, or auto-approval rule
  decidedAt: string;        // ISO datetime
}

Transport adapters receiving an approval reply (email reply, WhatsApp message, webhook payload) must parse the approvalId from the reply and construct the approval-response part. Conventional encodings:

  • Email: subject token (e.g. [approval:abc123]) or a parseable body line.
  • WhatsApp: approval ID embedded in the button payload.
  • Chat UI: the approval card's action submits the ID explicitly.

setState part schema

import type { SetStatePart } from '@cool-ai/beach-protocol';

// SetStatePart `data` shape — dot-namespaced coordinate patch
//   keys:   '<component>.<coordinate>'
//   values: the new state value (string)
interface SetStatePart {
  partType: 'setState';
  data: Record<string, string>;
}

Emitted by a handler's respond() output when it proposes a state-coordinate transition. The router validates the patch against the configured StateMachineRegistry, applies it to the session state (atomic — all coordinates accept or none do), and strips the part from the handler's reply before delivery. Destinations and outbound formatters never see setState parts.

Per-component opt-in via allowHandlerTransitions: true in state-machine.json — handlers cannot propose transitions for components that don't explicitly enable this source. Rejected parts fire onStateTransitionRejected on the router.

Cross-channel handling:

  • A2A peer envelopes — the part passes through verbatim. A peer Beach receiving the envelope decodes and applies; a non-Beach peer ignores it (no contract).
  • Missive records — recorded as-is. Beach does not re-apply a setState part read from a missive store on replay.
  • Outbound formatters — filtered out before formatting. Domain-data parts and response text are what consumers see.

Delivery rules per transport class

Each part type declares delivery semantics per transport class, registered with the PartTypeRegistry. Example rules:

| partType | Streaming transport | Buffered transport | |----------|---------------------|--------------------| | ack, thinking, progress | Flush immediately | Drop | | response | Stream progressive text deltas | Final value rendered | | domain-data | Once, on availability | Once, at settlement | | a2ui-surface | Each update flushed | Final state rendered (often dropped — buffered transports usually cannot render surfaces) | | artifact | URL-reference preferred | Bytes-as-attachment (e.g. email) | | llm-context | Produced once at turn settlement via Haiku translator; delivered as a single flush (not progressive). | Produced at settlement; delivered as part of the buffered envelope. | | reasoning-trace | Audit-only by default | Audit-only by default | | clarify, error | Flush immediately | Flush immediately | | approval-request | Flush immediately; turn enters suspended state | Flush immediately | | approval-response | Inbound only — inject, resume tool | Same |

Consumer-registered part types declare their own rules on registration.

Slot keys and merge strategy

When calling buildEnvelope(), pass slotKey and mergeStrategy to stamp convergence metadata onto domain-data parts:

buildEnvelope({
  sessionId, eventId, originatorId, conversationState, parts,
  slotKey: 'ta.research-rome',          // optional — stamped as metadata.slotKey on domain-data parts
  mergeStrategy: 'replace',             // optional — 'replace' | 'append' | 'deep-merge'; default 'replace'
});

slotKey is the stable identity of the destination slot. Renderers and A2A peers use it to converge successive updates — new data for the same slot replaces (or merges with) the previous value rather than appending a new entry. The A2A outbound adapter mirrors slotKey and mergeStrategy into A2A part metadata automatically.

mergeStrategy originates from LLMHandlerConfig.domainDataMergeStrategy in @cool-ai/beach-llm; slotKey is the destination slot the consumer supplies. The envelope builder receives both from the consumer who calls buildEnvelope().

Envelope assembly

Conceptually, for each turn:

  1. Collect data-bearing events from the turn's mailbox into a single domain object.
  2. Derive llm-context lazily, only if the turn's originators include at least one peer Agent Card that carries the Beach extension (BEACH_EXTENSION_URI) with envelopeConsumes: ["llm-context"]. Non-Beach peers (Mastra, Google ADK, etc.) do not receive llm-context by default. Otherwise skipped.
  3. Build surfaces from the data using consumer-registered surface templates. If no template matches the data kind, the a2ui-surface part is omitted (peer receives data + llm-context only; local UI falls back to generic rendering).
  4. Deliver parts to each originator via its transport adapter, respecting per-part delivery rules for the transport's class.

See https://cool-ai.org/docs/envelope for the full spec.

Surface template registration

Consumers register surface templates per data kind:

// Conceptual.
registerSurfaceTemplate('flight-results', (data) => a2uiBuilder.list(...));
registerSurfaceTemplate('hotel-results', (data) => a2uiBuilder.list(...));

The envelope builder looks up a template matching the data kind and calls it to produce the a2ui-surface parts.

Built-in approval surface template

@cool-ai/beach-protocol ships a default surface template for approval-request parts. When an approval request enters the envelope, the builder converts it into an A2UI surface composed from Basic Catalog primitives (Card, Column, Text, Button), bound to the approval's approvalId so the approve/deny buttons submit the correct correlation ID. The result is delivered as a normal a2ui-surface part, indistinguishable to the renderer from any other surface.

Consumers can override this default by registering their own approval-request template. The renderer (@cool-ai/beach-a2ui) has no envelope-part knowledge — all approval-specific concerns live in the template at the protocol layer.

Consumer-defined part types

@cool-ai/beach-protocol's canonical part types cover cross-consumer, cross-transport content. Consumers with bespoke rendering needs — stateful itinerary UI, multi-centre timelines, proprietary visualisations — register their own part types against @cool-ai/beach-core's PartTypeRegistry using a namespaced identifier.

Naming convention

Prefix with a consumer slug to avoid registry collisions:

ta.itinerary-slot-state
ta.multi-centre-timeline
baxter.task-board

Beach validates uniqueness within the registry; it does not validate the payload (the consumer owns the payload contract).

Registration

import { partTypeRegistry } from '@cool-ai/beach-core';

partTypeRegistry.register({
  partType: 'ta.itinerary-slot-state',
  deliveryRules: {
    streaming: 'flush',   // send immediately over SSE
    buffered: 'drop',     // drop — transports that cannot render it should not receive it
  },
  allowedTransports: ['sse', 'a2a'],  // excluded from email, MCP
  requiresPeerConsumes: true,          // suppressed for peers that do not declare this type in consumes
});

requiresPeerConsumes: true mirrors how llm-context works: the part is only sent to a peer whose Agent Card lists it in consumes. A peer without the renderer receives domain-data and llm-context for the same turn; the bespoke part is suppressed for that peer only.

Agent Card advertising

Declare Beach-specific capabilities (including bespoke part types the agent produces or consumes) in the Beach extension on the Agent Card:

"capabilities": {
  "extensions": [
    {
      "uri": "https://beach.cool-ai.io/extensions/v1",
      "required": false,
      "params": {
        "envelopeConsumes": ["domain-data", "a2ui-surface", "ta.itinerary-slot-state"]
      }
    }
  ]
}

Beach peers check envelopeConsumes in this extension to determine which part types they accept. Non-Beach peers without the extension receive only the standard A2A Message parts.

Worked example: TA itinerary surface

// 1. Register on startup.
partTypeRegistry.register({
  partType: 'ta.itinerary-slot-state',
  deliveryRules: { streaming: 'flush', buffered: 'drop' },
  allowedTransports: ['sse', 'a2a'],
  requiresPeerConsumes: true,
});

// 2. Produce it in respond().
respond({
  conversationState: 'complete',
  parts: [
    { partType: 'response', text: 'Here is your itinerary.' },
    { partType: 'domain-data', data: packageData },
    { partType: 'ta.itinerary-slot-state', data: itinerarySlotState },
  ],
});

// 3. In the browser, route by partType.
envelopeStream.on('part', (part) => {
  if (part.partType === 'a2ui-surface') a2uiRenderer.handle(part);
  if (part.partType === 'ta.itinerary-slot-state') itineraryRenderer.apply(part.data);
});

a2ui-surface and ta.itinerary-slot-state are siblings: both envelope parts, dispatched by partType, each handled by its own renderer. The envelope builder, transport layer, and @cool-ai/beach-inspect event log treat them identically — dispatching on partType without inspecting the payload.

See a2ui README for renderer-side guidance.

Not in this package

  • The A2UI Basic Catalog renderer or builder primitives (@cool-ai/beach-a2ui).
  • Channel transport (@cool-ai/beach-transport).
  • Session turn management (@cool-ai/beach-session).

Consumers

Any agent responding to external callers with structured data. A pure event-processing agent with no outbound envelopes could skip this, but most Beach-based conversational agents include it.

Related