@agent-play/sdk 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Node.js SDK for **Agent Play**: register agents, stream world state, and call the web UI over HTTP (`RemotePlayWorld`, LangChain helpers, SSE, RPC).
4
4
 
5
+ **Incremental world sync** — After `connect()`, call **`getWorldSnapshot()`** for a full parse, or **`subscribeWorldState()`** to follow SSE **`playerChainNotify`** (stable keys + leaf indices) and merge each slice with **`getPlayerChainNode`** using **`mergeSnapshotWithPlayerChainNode`**. Pure merge/parse helpers are exported for custom transports. Server fanout previously carried **`playerChainDelta`** (per-leaf digests); it is now **`playerChainNotify`** + serialized node RPC — a breaking change for anyone parsing Redis payloads or custom SSE clients.
6
+
5
7
  ## Documentation
6
8
 
7
9
  - **[Repository](https://github.com/wilforlan/agent-play)** — source and monorepo layout
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Domain types for sessions, journeys, players, and world structures shared by the SDK and server.
2
+ * Domain types for sessions, journeys, agents, and the shared world map exposed by the SDK and server.
3
3
  */
4
4
  /** Role for a single line in the interaction log / chat stream. */
5
5
  type WorldInteractionRole = "user" | "assistant" | "tool";
@@ -59,16 +59,14 @@ type PlatformAgentInformation = {
59
59
  * Use **`langchainRegistration(agent)`** for `agent` (requires a **`chat_tool`** tool; `assist_*`
60
60
  * tools are indexed for the watch UI).
61
61
  *
62
- * **`apiKey`** is set on **`RemotePlayWorld`** options, not here. With a registered-agent
63
- * repository, you may pass **`agentId`** from **`agent-play create`**, or omit it: the server
64
- * matches an existing agent by **`name`** plus **`agent.toolNames`**, or creates a registered
65
- * agent when under the account limit. Without Redis, omit **`apiKey`** and **`agentId`**.
62
+ * **`agentId`** is required: use an id from **`agent-play create`** when the server uses a repository
63
+ * (with **`apiKey`** from **`RemotePlayWorld`**), or any stable string for local dev without Redis.
66
64
  */
67
65
  type AddPlayerInput = PlatformAgentInformation & {
68
66
  /** Registration from {@link import("./platforms/langchain.js").langchainRegistration}. */
69
67
  agent: LangChainAgentRegistration;
70
- /** Optional explicit registered agent id when using Redis repository. */
71
- agentId?: string;
68
+ /** Registered agent id (or session-local id without Redis). */
69
+ agentId: string;
72
70
  };
73
71
  /** Zone counter event surfaced on snapshots and signals. */
74
72
  type ZoneEventInfo = {
@@ -81,29 +79,19 @@ type YieldEventInfo = {
81
79
  yieldCount: number;
82
80
  at: string;
83
81
  };
84
- /** Kind of world structure tile (home base, tool pad, API, etc.). */
85
- type WorldStructureKind = "home" | "tool" | "api" | "database" | "model";
86
- /**
87
- * One placed structure on the world map for a player.
88
- *
89
- * @property id - Stable id (often derived from tool name / layout).
90
- * @property kind - Visual category.
91
- * @property x, y - Coordinates in world grid units.
92
- * @property toolName - When kind is tool-related, the tool name.
93
- * @property label - Optional human label for the canvas.
94
- */
95
- type WorldStructure = {
96
- id: string;
97
- kind: WorldStructureKind;
98
- x: number;
99
- y: number;
100
- toolName?: string;
101
- label?: string;
82
+ /** Repository-backed summary returned with `addPlayer` when the agent is (or maps to) a stored registration. */
83
+ type RegisteredAgentSummary = {
84
+ agentId: string;
85
+ name: string;
86
+ toolNames: string[];
87
+ zoneCount: number;
88
+ yieldCount: number;
89
+ flagged: boolean;
102
90
  };
103
- /** Result of `addPlayer` including watch URL and laid-out structures. */
91
+ /** Result of `addPlayer` including watch URL and registered-agent metadata from the server. */
104
92
  type RegisteredPlayer = PlayAgentInformation & {
105
93
  previewUrl: string;
106
- structures: WorldStructure[];
94
+ registeredAgent: RegisteredAgentSummary;
107
95
  };
108
96
  /** First step of a journey: user message origin. */
109
97
  type OriginJourneyStep = {
@@ -130,7 +118,7 @@ type JourneyStep = OriginJourneyStep | StructureJourneyStep | DestinationJourney
130
118
  /**
131
119
  * Ordered journey with timestamps; sent to the server via `recordJourney`.
132
120
  *
133
- * @property steps - Ordered path from origin through structures to destination.
121
+ * @property steps - Ordered path from origin through tool steps to destination.
134
122
  * @property startedAt, completedAt - Wall times for the run (client or server).
135
123
  */
136
124
  type Journey = {
@@ -144,12 +132,110 @@ type PositionedStep = JourneyStep & {
144
132
  y?: number;
145
133
  structureId?: string;
146
134
  };
147
- /** Full snapshot row for a journey update (SSE `world:journey`). */
135
+ type AgentPlayWorldMapBounds = {
136
+ minX: number;
137
+ minY: number;
138
+ maxX: number;
139
+ maxY: number;
140
+ };
141
+ /**
142
+ * One agent on the world map. Coordinates are grid positions; the server enforces unique `(x,y)` per occupant.
143
+ */
144
+ type AgentPlayWorldMapAgentOccupant = {
145
+ kind: "agent";
146
+ agentId: string;
147
+ name: string;
148
+ x: number;
149
+ y: number;
150
+ /**
151
+ * Integration label from addPlayer `type` (e.g. `langchain`). Populated from the snapshot field `platform`. The legacy wire field `agentType` is deprecated and accepted only for backward compatibility when parsing JSON.
152
+ */
153
+ platform?: string;
154
+ toolNames?: string[];
155
+ assistToolNames?: string[];
156
+ assistTools?: AssistToolSpec[];
157
+ hasChatTool?: boolean;
158
+ stationary?: boolean;
159
+ lastUpdate?: unknown;
160
+ recentInteractions?: Array<{
161
+ role: WorldInteractionRole;
162
+ text: string;
163
+ at: string;
164
+ seq: number;
165
+ }>;
166
+ zoneCount?: number;
167
+ yieldCount?: number;
168
+ flagged?: boolean;
169
+ onZone?: ZoneEventInfo;
170
+ onYield?: YieldEventInfo;
171
+ };
172
+ /** MCP server shown as a separate map occupant (distinct from LangChain agents). */
173
+ type AgentPlayWorldMapMcpOccupant = {
174
+ kind: "mcp";
175
+ id: string;
176
+ name: string;
177
+ x: number;
178
+ y: number;
179
+ url?: string;
180
+ };
181
+ /** Spatial index: axis-aligned bounds plus every agent and MCP registration placed on the grid. */
182
+ type AgentPlayWorldMap = {
183
+ bounds: AgentPlayWorldMapBounds;
184
+ occupants: (AgentPlayWorldMapAgentOccupant | AgentPlayWorldMapMcpOccupant)[];
185
+ };
186
+ /**
187
+ * Session snapshot from {@link import("./lib/remote-play-world.js").RemotePlayWorld.getWorldSnapshot}.
188
+ * Agents and MCP servers appear only under **`worldMap.occupants`** (no separate `players` list).
189
+ */
190
+ type AgentPlaySnapshot = {
191
+ sid: string;
192
+ worldMap: AgentPlayWorldMap;
193
+ mcpServers?: Array<{
194
+ id: string;
195
+ name: string;
196
+ url?: string;
197
+ }>;
198
+ };
199
+ type PlayerChainNotifyNodeRef = {
200
+ stableKey: string;
201
+ leafIndex: number;
202
+ removed?: boolean;
203
+ updatedAt?: string;
204
+ };
205
+ type PlayerChainFanoutNotify = {
206
+ updatedAt: string;
207
+ nodes: PlayerChainNotifyNodeRef[];
208
+ };
209
+ type PlayerChainGenesisStableKey = "__genesis__";
210
+ type PlayerChainHeaderStableKey = "__header__";
211
+ type PlayerChainGenesisNode = {
212
+ kind: "genesis";
213
+ stableKey: PlayerChainGenesisStableKey;
214
+ text: string;
215
+ };
216
+ type PlayerChainHeaderNode = {
217
+ kind: "header";
218
+ stableKey: PlayerChainHeaderStableKey;
219
+ sid: string;
220
+ bounds: AgentPlayWorldMapBounds;
221
+ };
222
+ type PlayerChainOccupantRemovedNode = {
223
+ kind: "occupant";
224
+ stableKey: string;
225
+ removed: true;
226
+ };
227
+ type PlayerChainOccupantPresentNode = {
228
+ kind: "occupant";
229
+ stableKey: string;
230
+ removed: false;
231
+ occupant: AgentPlayWorldMapAgentOccupant | AgentPlayWorldMapMcpOccupant;
232
+ };
233
+ type PlayerChainNodeResponse = PlayerChainGenesisNode | PlayerChainHeaderNode | PlayerChainOccupantRemovedNode | PlayerChainOccupantPresentNode;
234
+ /** Full journey + path update (SSE `world:journey`); coordinates are embedded in `path` steps. */
148
235
  type WorldJourneyUpdate = {
149
236
  playerId: string;
150
237
  journey: Journey;
151
238
  path: PositionedStep[];
152
- structures: WorldStructure[];
153
239
  };
154
240
 
155
241
  /**
@@ -161,8 +247,6 @@ type WorldJourneyUpdate = {
161
247
 
162
248
  /** Fired when `addPlayer` completes; payload includes snapshot row for the new player. */
163
249
  declare const PLAYER_ADDED_EVENT = "world:player_added";
164
- /** Fired when structures change (sync tools, layout refresh). */
165
- declare const WORLD_STRUCTURES_EVENT = "world:structures";
166
250
  /** Fired for each new chat/interaction line. */
167
251
  declare const WORLD_INTERACTION_EVENT = "world:interaction";
168
252
  /** Lightweight signals (zone, yield, assist, journey metadata, etc.). */
@@ -193,17 +277,6 @@ type WorldInteractionPayload = {
193
277
  at: string;
194
278
  seq: number;
195
279
  };
196
- /**
197
- * Payload for {@link WORLD_STRUCTURES_EVENT}.
198
- *
199
- * @property type - Optional agent platform type string.
200
- */
201
- type WorldStructuresPayload = {
202
- playerId: string;
203
- name: string;
204
- structures: WorldStructure[];
205
- type?: string;
206
- };
207
280
 
208
281
  /**
209
282
  * Axis-aligned rectangle in world coordinates (grid units). Used by the server to clamp paths
@@ -298,165 +371,71 @@ declare function agentPlayDebug(scope: string, message: string, detail?: unknown
298
371
  */
299
372
  declare function langchainRegistration(agent: unknown): LangChainAgentRegistration;
300
373
 
301
- /** Options for {@link RemotePlayWorld}: API origin and credentials for RPC calls. */
302
374
  type RemotePlayWorldOptions = {
303
- /** Web UI base URL (no trailing slash), e.g. `https://host` or `http://127.0.0.1:3000`. */
304
375
  baseUrl: string;
305
- /** Account API key when the server uses `AgentRepository`; use a non-empty placeholder if none. */
306
376
  apiKey: string;
307
- /** Optional bearer token for authenticated routes. */
308
377
  authToken?: string;
309
378
  };
310
- /**
311
- * Return value of {@link RemotePlayWorld.hold}: a delayed promise helper for long-running processes.
312
- */
313
379
  type RemotePlayWorldHold = {
314
- /**
315
- * Sleeps for the given number of seconds (non-negative).
316
- *
317
- * @remarks **Callers:** integration scripts that must keep the Node process alive after `start()`.
318
- * **Callees:** `setTimeout` via `Promise`.
319
- */
320
380
  for: (seconds: number) => Promise<void>;
321
381
  };
322
382
  /**
323
- * HTTP client for Agent Play: starts a session, registers players, records journeys and
324
- * interactions, and syncs structures. Designed for long-running Node processes with
325
- * {@link RemotePlayWorld.hold `hold().for()`} to keep the process alive.
326
- *
327
- * @remarks **Callers:** user code and SDK examples. All public methods except `constructor` require
328
- * {@link RemotePlayWorld.start} to have succeeded first (except `start` itself).
383
+ * HTTP client for the Agent Play web UI: session, snapshot RPC, mutating RPC with `sid`, and optional SSE subscription.
329
384
  *
330
- * **Protocol:** Uses `fetch` to `GET /api/agent-play/session`, `POST /api/agent-play/players`, and
331
- * `POST /api/agent-play/sdk/rpc` with JSON body `{ op, payload }`, plus MCP registration
332
- * `POST /api/agent-play/mcp/register`.
385
+ * Incremental updates: {@link RemotePlayWorld.subscribeWorldState} listens for **`playerChainNotify`** in SSE `data`, then fetches each changed leaf via {@link RemotePlayWorld.getPlayerChainNode} and merges with {@link mergeSnapshotWithPlayerChainNode}.
333
386
  */
334
387
  declare class RemotePlayWorld {
335
- /** Normalized {@link RemotePlayWorldOptions.baseUrl} (no trailing slash). Used for `fetch` base. */
336
388
  private readonly apiBase;
337
- /** Trimmed account API key; sent on `addPlayer` and implied for RPC auth expectations. */
338
389
  private readonly apiKey;
339
- /** Optional bearer token merged into request headers when set. */
340
390
  private readonly authToken;
341
- /** Session id from `GET /api/agent-play/session`; `null` until {@link RemotePlayWorld.start} succeeds. */
342
391
  private sid;
343
- /** When true, {@link RemotePlayWorld.close} is a no-op and listeners already ran. */
344
392
  private closed;
345
- /** Unsubscribe callbacks registered via {@link RemotePlayWorld.onClose}. */
346
393
  private readonly closeListeners;
347
- /**
348
- * @param options - Base URL, API key, and optional auth token.
349
- * @throws Error if `apiKey` is missing or whitespace-only (see {@link formatMissingApiKeyError}).
350
- *
351
- * @remarks **Callees:** {@link formatMissingApiKeyError}, {@link normalizeBaseUrl}.
352
- */
353
394
  constructor(options: RemotePlayWorldOptions);
354
- /**
355
- * Registers a one-shot listener invoked when {@link RemotePlayWorld.close} runs (e.g. process shutdown).
356
- *
357
- * @param handler - Synchronous callback; errors are swallowed.
358
- * @returns Unsubscribe function that removes this `handler` from the set.
359
- *
360
- * @remarks **Callers:** user code. **Callees:** `Set.prototype.add` / `delete`.
361
- */
362
395
  onClose(handler: () => void): () => void;
363
- /**
364
- * Returns a helper that sleeps for wall-clock seconds (useful for `await world.hold().for(3600)`).
365
- *
366
- * @remarks **Callers:** user code. **Callees:** {@link formatInvalidHoldSecondsError}.
367
- */
368
396
  hold(): RemotePlayWorldHold;
369
- /**
370
- * Authorization header map for requests that only need session cookie or bearer auth.
371
- *
372
- * @internal
373
- * @remarks **Callers:** {@link RemotePlayWorld.start}, {@link RemotePlayWorld.addPlayer} (via headers),
374
- * {@link RemotePlayWorld.registerMcp}, and any future GET-only calls.
375
- */
376
397
  private authHeaders;
377
- /**
378
- * Headers for JSON `POST` bodies (RPC, players, MCP).
379
- *
380
- * @internal
381
- * @remarks **Callers:** {@link RemotePlayWorld.addPlayer}, {@link RemotePlayWorld.rpc}, {@link RemotePlayWorld.registerMcp}.
382
- * **Callees:** {@link authHeaders}.
383
- */
384
398
  private jsonHeaders;
385
- /**
386
- * Creates a session and stores `sid` from `GET /api/agent-play/session`.
387
- *
388
- * @throws Error if the response is not OK or JSON lacks a non-empty `sid` string.
389
- *
390
- * @remarks **Callers:** user code. **Callees:** `fetch`, {@link isRecord}.
391
- */
392
- start(): Promise<void>;
393
- /**
394
- * Marks the client closed and invokes all {@link onClose} listeners once.
395
- *
396
- * @remarks **Callers:** user code. **Callees:** `Array.from` over {@link closeListeners}.
397
- */
399
+ private mergeAuthFetch;
400
+ connect(): Promise<void>;
398
401
  close(): Promise<void>;
399
- /**
400
- * @returns Current session id.
401
- * @throws Error if {@link RemotePlayWorld.start} has not been called successfully.
402
- *
403
- * @remarks **Callers:** user code. **Callees:** none.
404
- */
405
402
  getSessionId(): string;
406
- /**
407
- * @returns Absolute watch URL for the session (`/agent-play/watch` on `apiBase`). Query `sid` is not appended;
408
- * consumers append `?sid=` from {@link getSessionId} when needed.
409
- *
410
- * @remarks **Callers:** user code. **Callees:** `URL` constructor.
411
- */
412
403
  getPreviewUrl(): string;
404
+ getWorldSnapshot(): Promise<AgentPlaySnapshot>;
413
405
  /**
414
- * Registers a player agent with the server for the current session.
415
- *
416
- * @param input - Name, type, `agent` registration from {@link langchainRegistration}, optional `agentId`.
417
- * @returns Resolved player row with `previewUrl` and `structures`.
418
- * @throws Error on HTTP errors or malformed JSON.
419
- *
420
- * @remarks **Callers:** user code. **Callees:** {@link getSessionId}, `fetch`, `JSON.parse`, {@link parseStructures}.
406
+ * Fetches one player-chain node (genesis, header, occupant row, or removal) for `stableKey`, same snapshot scope as {@link RemotePlayWorld.getWorldSnapshot}.
421
407
  */
422
- addPlayer(input: AddPlayerInput): Promise<RegisteredPlayer>;
408
+ getPlayerChainNode(stableKey: string): Promise<PlayerChainNodeResponse>;
423
409
  /**
424
- * Appends a chat-style interaction line for a player (RPC `recordInteraction`).
425
- *
426
- * @remarks **Callers:** user code. **Callees:** {@link rpc}.
410
+ * Opens the session SSE stream, emits an initial snapshot from {@link RemotePlayWorld.getWorldSnapshot}, then on each **`playerChainNotify`** merges nodes in deterministic order via {@link RemotePlayWorld.getPlayerChainNode}.
427
411
  */
412
+ subscribeWorldState(callbacks: {
413
+ onSnapshot: (snapshot: AgentPlaySnapshot) => void;
414
+ onError?: (err: Error) => void;
415
+ }): {
416
+ close: () => void;
417
+ };
418
+ addPlayer(input: AddPlayerInput): Promise<RegisteredPlayer>;
428
419
  recordInteraction(input: RecordInteractionInput): Promise<void>;
429
- /**
430
- * Records a full journey for a player (RPC `recordJourney`).
431
- *
432
- * @remarks **Callers:** user code. **Callees:** {@link rpc}.
433
- */
434
420
  recordJourney(playerId: string, journey: Journey): Promise<void>;
435
- /**
436
- * Re-syncs layout structures from an ordered tool name list (RPC `syncPlayerStructuresFromTools`).
437
- *
438
- * @remarks **Callers:** user code. **Callees:** {@link rpc}.
439
- */
440
- syncPlayerStructuresFromTools(playerId: string, toolNames: string[]): Promise<void>;
441
- /**
442
- * Registers an MCP server metadata row for the session (HTTP POST, not the same as RPC `op`).
443
- *
444
- * @returns New registration id string from JSON `{ id }`.
445
- *
446
- * @remarks **Callers:** user code. **Callees:** `fetch`, {@link getSessionId}, {@link jsonHeaders}.
447
- */
448
421
  registerMcp(options: {
449
422
  name: string;
450
423
  url?: string;
451
424
  }): Promise<string>;
452
- /**
453
- * Posts `{ op, payload }` to `/api/agent-play/sdk/rpc?sid=...`.
454
- *
455
- * @internal
456
- * @remarks **Callers:** {@link recordInteraction}, {@link recordJourney}, {@link syncPlayerStructuresFromTools}.
457
- * **Callees:** {@link getSessionId}, `fetch`, {@link jsonHeaders}.
458
- */
459
425
  private rpc;
460
426
  }
461
427
 
462
- export { type AddPlayerInput, type AssistToolSpec, type DestinationJourneyStep, type Journey, type JourneyStep, type LangChainAgentRegistration, type OriginJourneyStep, PLAYER_ADDED_EVENT, type PlatformAgentInformation, type PlayAgentInformation, type PositionedStep, type RecordInteractionInput, type RegisteredPlayer, RemotePlayWorld, type RemotePlayWorldHold, type RemotePlayWorldOptions, type StructureJourneyStep, WORLD_AGENT_SIGNAL_EVENT, WORLD_INTERACTION_EVENT, WORLD_JOURNEY_EVENT, WORLD_STRUCTURES_EVENT, type WorldAgentSignalPayload, type WorldBounds, type WorldInteractionPayload, type WorldInteractionRole, type WorldJourneyUpdate, type WorldStructure, type WorldStructureKind, type WorldStructuresPayload, type YieldEventInfo, type ZoneEventInfo, agentPlayDebug, boundsContain, clampWorldPosition, configureAgentPlayDebug, isAgentPlayDebugEnabled, langchainRegistration, resetAgentPlayDebug };
428
+ /**
429
+ * Parses **`playerChainNotify`** envelopes and merges {@link PlayerChainNodeResponse} slices into {@link AgentPlaySnapshot} (pure functions + fetch ordering for serialized RPC).
430
+ */
431
+
432
+ declare function sortNodeRefsForSerializedFetch(nodes: ReadonlyArray<PlayerChainNotifyNodeRef>): PlayerChainNotifyNodeRef[];
433
+ declare function parsePlayerChainFanoutNotify(raw: unknown): PlayerChainFanoutNotify | undefined;
434
+ declare function parsePlayerChainFanoutNotifyFromSsePayload(sseData: unknown): PlayerChainFanoutNotify | undefined;
435
+ declare function parsePlayerChainNodeRpcBody(json: unknown): PlayerChainNodeResponse;
436
+ declare function mergeSnapshotWithPlayerChainNode(snapshot: AgentPlaySnapshot, node: PlayerChainNodeResponse): AgentPlaySnapshot;
437
+
438
+ declare const PLAYER_CHAIN_GENESIS_STABLE_KEY: "__genesis__";
439
+ declare const PLAYER_CHAIN_HEADER_STABLE_KEY: "__header__";
440
+
441
+ export { type AddPlayerInput, type AgentPlaySnapshot, type AgentPlayWorldMap, type AgentPlayWorldMapAgentOccupant, type AgentPlayWorldMapBounds, type AgentPlayWorldMapMcpOccupant, type AssistToolSpec, type DestinationJourneyStep, type Journey, type JourneyStep, type LangChainAgentRegistration, type OriginJourneyStep, PLAYER_ADDED_EVENT, PLAYER_CHAIN_GENESIS_STABLE_KEY, PLAYER_CHAIN_HEADER_STABLE_KEY, type PlatformAgentInformation, type PlayAgentInformation, type PlayerChainFanoutNotify, type PlayerChainGenesisNode, type PlayerChainHeaderNode, type PlayerChainNodeResponse, type PlayerChainNotifyNodeRef, type PlayerChainOccupantPresentNode, type PlayerChainOccupantRemovedNode, type PositionedStep, type RecordInteractionInput, type RegisteredAgentSummary, type RegisteredPlayer, RemotePlayWorld, type RemotePlayWorldHold, type RemotePlayWorldOptions, type StructureJourneyStep, WORLD_AGENT_SIGNAL_EVENT, WORLD_INTERACTION_EVENT, WORLD_JOURNEY_EVENT, type WorldAgentSignalPayload, type WorldBounds, type WorldInteractionPayload, type WorldInteractionRole, type WorldJourneyUpdate, type YieldEventInfo, type ZoneEventInfo, agentPlayDebug, boundsContain, clampWorldPosition, configureAgentPlayDebug, isAgentPlayDebugEnabled, langchainRegistration, mergeSnapshotWithPlayerChainNode, parsePlayerChainFanoutNotify, parsePlayerChainFanoutNotifyFromSsePayload, parsePlayerChainNodeRpcBody, resetAgentPlayDebug, sortNodeRefsForSerializedFetch };