Agent Event Protocol implementation
npm install @machinemetrics/agent-event-protocolA transport-agnostic event protocol for streaming agent responses. Defines the schema and utilities for real-time communication between AI agents and clients.
``bash`
npm install @machinemetrics/agent-event-protocol
1. Simple - Easy to understand and implement
2. Flexible - Supports future features without breaking changes
3. Type-safe - Strong contracts between client and server
4. Observable - Easy to monitor and debug
5. Transport-agnostic - Same events work over SSE, NDJSON, WebSocket, NATS, etc.
The protocol defines a streaming event schema for agent responses. All events are self-identifying JSON payloads with common fields:
- event - Event type identifierrequestId
- - Correlates all events to the originating requesttimestamp
- - ISO 8601 timestamp for observability
``
┌─────────┐ ┌─────────┐
│ Client │ │ Server │
└────┬────┘ └────┬────┘
│ │
│ POST /v1/agents/{agentName}/stream │
│ { messages, threadId?, context? } │
├─────────────────────────────────────────────>│
│ │
│ metadata: {threadId, requestId, agentId} │
│<─────────────────────────────────────────────┤
│ │
│ text: {blockId, stage: "start"} │
│<─────────────────────────────────────────────┤
│ │
│ text: {blockId, stage: "delta", delta} │
│<─────────────────────────────────────────────┤
│ │
│ text: {blockId, stage: "stop"} │
│<─────────────────────────────────────────────┤
│ │
│ done: {content, usage, finishReason} │
│<─────────────────────────────────────────────┤
│ │
When the agent needs client-side action, the stream pauses and resumes via a new request:
``
┌─────────┐ ┌─────────┐
│ Client │ │ Server │
└────┬────┘ └────┬────┘
│ │
│ POST /stream { messages, threadId? } │
├─────────────────────────────────────────────>│
│ │
│ continuation: {actionId, kind, payload} │
│<─────────────────────────────────────────────┤
│ │
│ done: {finishReason: "action_required"} │
│<─────────────────────────────────────────────┤
│ │
│ (client handles action based on kind) │
│ │
│ POST /stream { messages, threadId, │
│ context: { actionResults: [...] }} │
├─────────────────────────────────────────────>│
│ │
│ (agent continues with result) │
│ │
The protocol defines 9 event types covering the complete lifecycle of an agent response.
| Event | Description |
|-------|-------------|
| metadata | Stream initialization with threadId, requestId, agentId |text
| | Streaming text output with stage lifecycle |thinking
| | Model reasoning/thinking content (extended thinking) |tool_call
| | Tool invocation with streaming arguments |tool_result
| | Tool execution result (status and summary only) |done
| | Stream complete with full content array |error
| | Error condition with code and retry guidance |aborted
| | Client cancellation acknowledgment |continuation
| | Pause for client action (tools, approval, input) |
Each request must end with exactly one terminal event: done, error, or aborted.
Content events (text, thinking, tool_call, tool_result) use a consistent stage pattern:
| Stage | Description |
|-------|-------------|
| start | Block begins, includes blockIndex for ordering |delta
| | Content chunk, includes deltaIndex for ordering within block |stop
| | Block complete, includes final data for that block type |
`typescript`
type AgentRequest = {
messages: InputMessage[]; // New user input
threadId?: string; // Resume existing thread
context?: {
actionResults?: ActionResult[]; // Results from continuation events
[key: string]: unknown; // Additional passthrough data
};
};
Only messages is required. The server loads thread history from storage via threadId.
User Content Blocks (in requests):
- text - Plain text input
Assistant Content Blocks (in responses):
- text - Text outputthinking
- - Model reasoningtool_use
- - Tool invocation with argstool_result
- - Tool result status and summary (no full payloads)
| Code | Description |
|------|-------------|
| auth_failed | Authentication failed |thread_not_found
| | Thread does not exist |thread_access_denied
| | Thread ownership violation |invalid_message_format
| | Invalid request format |message_too_large
| | Message exceeds size limit |rate_limit_exceeded
| | Rate limit hit |stream_timeout
| | Stream timed out |agent_error
| | Agent execution error |tool_error
| | Tool execution error |internal_error
| | Internal server error |invalid_action_id
| | Action ID not found or already resolved |
| Status | Error Codes |
|--------|-------------|
| 400 | invalid_message_format, invalid_action_id |auth_failed
| 401 | |thread_access_denied
| 403 | |thread_not_found
| 404 | |message_too_large
| 413 | |rate_limit_exceeded
| 429 | |internal_error
| 500 | |stream_timeout
| 504 | |
- retryable: true - Client can retry with backoffretryable: false
- - Don't retry, user action required
Server-Sent Events is the default transport implementation.
`
Content-Type: text/event-stream
Connection: keep-alive
Cache-Control: no-cache
event: metadata
data: {"event":"metadata","requestId":"req-1","threadId":"th-1","agentId":"agent","timestamp":"..."}
event: text
data: {"event":"text","requestId":"req-1","messageId":"msg-1","blockId":"b0","stage":"start","blockIndex":0,"timestamp":"..."}
event: text
data: {"event":"text","requestId":"req-1","messageId":"msg-1","blockId":"b0","stage":"delta","delta":"Hello","deltaIndex":0,"timestamp":"..."}
event: done
data: {"event":"done","requestId":"req-1","messageId":"msg-1","threadId":"th-1","content":[...],"finishReason":"stop","timestamp":"..."}
`
Servers should send SSE comment lines (: heartbeat\n\n) every 15 seconds to prevent proxy timeouts.
`typescript
import { encodeSSE, parseSSEChunk } from '@machinemetrics/agent-event-protocol';
// Server: encode events for streaming
const sseData = encodeSSE('text', { event: 'text', ... });
// Client: parse incoming chunks
const { events, remainder } = parseSSEChunk(chunk);
`
Adding new events is backward compatible:
- Clients should ignore unknown event types
- New optional fields can be added to existing events
- No version bump required for additive changes
Breaking changes require new endpoint version:
``
/v1/agents/{agentName}/stream → Current
/v2/agents/{agentName}/stream → Breaking changes
`typescript
import {
// Core types
InputMessage,
AgentRequest,
AgentEvent,
ActionResult,
UserContentBlock,
AssistantContentBlock,
EventStage,
MetadataEvent,
TextEvent,
ThinkingEvent,
ToolCallEvent,
ToolResultEvent,
DoneEvent,
ErrorEvent,
AbortedEvent,
ContinuationEvent,
// Schemas (Zod validators)
AgentRequestSchema,
AgentEventSchema,
// ... all event schemas
// SSE utilities
encodeSSE,
parseSSEChunk,
ParsedEvent,
ParseResult,
// Constants
PROTOCOL_VERSION,
EVENT_TYPES,
} from '@machinemetrics/agent-event-protocol';
`
`bash``
pnpm install
pnpm build # Build the package (ESM + CJS)
pnpm test # Run tests
pnpm typecheck # Type check