@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/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // src/world-events.ts
2
2
  var PLAYER_ADDED_EVENT = "world:player_added";
3
- var WORLD_STRUCTURES_EVENT = "world:structures";
4
3
  var WORLD_INTERACTION_EVENT = "world:interaction";
5
4
  var WORLD_AGENT_SIGNAL_EVENT = "world:agent_signal";
6
5
  var WORLD_JOURNEY_EVENT = "world:journey";
@@ -131,6 +130,207 @@ function langchainRegistration(agent) {
131
130
  };
132
131
  }
133
132
 
133
+ // src/lib/parse-occupant-row.ts
134
+ function parseAgentOccupantRow(raw) {
135
+ if (typeof raw.agentId !== "string" || typeof raw.name !== "string") {
136
+ throw new Error("occupant: agent needs agentId and name");
137
+ }
138
+ if (typeof raw.x !== "number" || typeof raw.y !== "number") {
139
+ throw new Error("occupant: agent needs numeric x and y");
140
+ }
141
+ const base = {
142
+ kind: "agent",
143
+ agentId: raw.agentId,
144
+ name: raw.name,
145
+ x: raw.x,
146
+ y: raw.y
147
+ };
148
+ let platform;
149
+ if (typeof raw.platform === "string") {
150
+ platform = raw.platform;
151
+ } else if (typeof raw.agentType === "string") {
152
+ platform = raw.agentType;
153
+ }
154
+ if (platform !== void 0) {
155
+ return { ...base, platform };
156
+ }
157
+ return base;
158
+ }
159
+ function parseMcpOccupantRow(raw) {
160
+ if (typeof raw.id !== "string" || typeof raw.name !== "string") {
161
+ throw new Error("occupant: mcp needs id and name");
162
+ }
163
+ if (typeof raw.x !== "number" || typeof raw.y !== "number") {
164
+ throw new Error("occupant: mcp needs numeric x and y");
165
+ }
166
+ const base = {
167
+ kind: "mcp",
168
+ id: raw.id,
169
+ name: raw.name,
170
+ x: raw.x,
171
+ y: raw.y
172
+ };
173
+ if (typeof raw.url === "string") {
174
+ return { ...base, url: raw.url };
175
+ }
176
+ return base;
177
+ }
178
+
179
+ // src/lib/world-chain-keys.ts
180
+ var PLAYER_CHAIN_GENESIS_STABLE_KEY = "__genesis__";
181
+ var PLAYER_CHAIN_HEADER_STABLE_KEY = "__header__";
182
+
183
+ // src/lib/player-chain-merge.ts
184
+ function isRecord(v) {
185
+ return typeof v === "object" && v !== null;
186
+ }
187
+ function stableOccupantSortKey(occ) {
188
+ if (occ.kind === "agent") {
189
+ return `agent:${occ.agentId}`;
190
+ }
191
+ return `mcp:${occ.id}`;
192
+ }
193
+ function sortNodeRefsForSerializedFetch(nodes) {
194
+ const removed = nodes.filter((n) => n.removed === true);
195
+ const rest = nodes.filter((n) => n.removed !== true);
196
+ removed.sort((a, b) => b.leafIndex - a.leafIndex);
197
+ rest.sort((a, b) => a.leafIndex - b.leafIndex);
198
+ return [...removed, ...rest];
199
+ }
200
+ function parsePlayerChainFanoutNotify(raw) {
201
+ if (!isRecord(raw)) {
202
+ return void 0;
203
+ }
204
+ if (typeof raw.updatedAt !== "string" || raw.updatedAt.length === 0) {
205
+ return void 0;
206
+ }
207
+ if (!Array.isArray(raw.nodes)) {
208
+ return void 0;
209
+ }
210
+ const nodes = [];
211
+ for (const row of raw.nodes) {
212
+ if (!isRecord(row)) {
213
+ return void 0;
214
+ }
215
+ if (typeof row.stableKey !== "string" || row.stableKey.length === 0) {
216
+ return void 0;
217
+ }
218
+ if (typeof row.leafIndex !== "number" || !Number.isFinite(row.leafIndex)) {
219
+ return void 0;
220
+ }
221
+ const ref = {
222
+ stableKey: row.stableKey,
223
+ leafIndex: row.leafIndex
224
+ };
225
+ if (row.removed === true) {
226
+ ref.removed = true;
227
+ }
228
+ if (typeof row.updatedAt === "string" && row.updatedAt.length > 0) {
229
+ ref.updatedAt = row.updatedAt;
230
+ }
231
+ nodes.push(ref);
232
+ }
233
+ return { updatedAt: raw.updatedAt, nodes };
234
+ }
235
+ function parsePlayerChainFanoutNotifyFromSsePayload(sseData) {
236
+ if (!isRecord(sseData)) {
237
+ return void 0;
238
+ }
239
+ return parsePlayerChainFanoutNotify(sseData.playerChainNotify);
240
+ }
241
+ function parsePlayerChainNodeRpcBody(json) {
242
+ if (!isRecord(json) || !isRecord(json.node)) {
243
+ throw new Error("getPlayerChainNode: invalid response shape");
244
+ }
245
+ const n = json.node;
246
+ if (n.kind === "genesis") {
247
+ if (n.stableKey !== PLAYER_CHAIN_GENESIS_STABLE_KEY || typeof n.text !== "string") {
248
+ throw new Error("getPlayerChainNode: invalid genesis node");
249
+ }
250
+ return {
251
+ kind: "genesis",
252
+ stableKey: PLAYER_CHAIN_GENESIS_STABLE_KEY,
253
+ text: n.text
254
+ };
255
+ }
256
+ if (n.kind === "header") {
257
+ if (n.stableKey !== PLAYER_CHAIN_HEADER_STABLE_KEY || typeof n.sid !== "string") {
258
+ throw new Error("getPlayerChainNode: invalid header node");
259
+ }
260
+ const b = n.bounds;
261
+ if (!isRecord(b)) {
262
+ throw new Error("getPlayerChainNode: invalid header bounds");
263
+ }
264
+ const { minX, minY, maxX, maxY } = b;
265
+ if (typeof minX !== "number" || typeof minY !== "number" || typeof maxX !== "number" || typeof maxY !== "number") {
266
+ throw new Error("getPlayerChainNode: invalid header bounds");
267
+ }
268
+ return {
269
+ kind: "header",
270
+ stableKey: PLAYER_CHAIN_HEADER_STABLE_KEY,
271
+ sid: n.sid,
272
+ bounds: { minX, minY, maxX, maxY }
273
+ };
274
+ }
275
+ if (n.kind !== "occupant") {
276
+ throw new Error("getPlayerChainNode: unknown node kind");
277
+ }
278
+ if (typeof n.stableKey !== "string" || n.stableKey.length === 0) {
279
+ throw new Error("getPlayerChainNode: invalid occupant stableKey");
280
+ }
281
+ if (n.removed === true) {
282
+ return { kind: "occupant", stableKey: n.stableKey, removed: true };
283
+ }
284
+ const occ = n.occupant;
285
+ if (!isRecord(occ) || occ.kind !== "agent" && occ.kind !== "mcp") {
286
+ throw new Error("getPlayerChainNode: invalid occupant payload");
287
+ }
288
+ const occupant = occ.kind === "agent" ? parseAgentOccupantRow(occ) : parseMcpOccupantRow(occ);
289
+ return {
290
+ kind: "occupant",
291
+ stableKey: n.stableKey,
292
+ removed: false,
293
+ occupant
294
+ };
295
+ }
296
+ function mergeSnapshotWithPlayerChainNode(snapshot, node) {
297
+ if (node.kind === "genesis") {
298
+ return snapshot;
299
+ }
300
+ if (node.kind === "header") {
301
+ return {
302
+ ...snapshot,
303
+ sid: node.sid,
304
+ worldMap: {
305
+ ...snapshot.worldMap,
306
+ bounds: node.bounds
307
+ }
308
+ };
309
+ }
310
+ if (node.removed) {
311
+ return {
312
+ ...snapshot,
313
+ worldMap: {
314
+ ...snapshot.worldMap,
315
+ occupants: snapshot.worldMap.occupants.filter(
316
+ (o) => stableOccupantSortKey(o) !== node.stableKey
317
+ )
318
+ }
319
+ };
320
+ }
321
+ const key = stableOccupantSortKey(node.occupant);
322
+ const occupants = snapshot.worldMap.occupants.filter(
323
+ (o) => stableOccupantSortKey(o) !== key
324
+ );
325
+ return {
326
+ ...snapshot,
327
+ worldMap: {
328
+ ...snapshot.worldMap,
329
+ occupants: [...occupants, node.occupant]
330
+ }
331
+ };
332
+ }
333
+
134
334
  // src/lib/remote-play-world.ts
135
335
  function formatMissingApiKeyError() {
136
336
  return [
@@ -144,43 +344,104 @@ function formatMissingApiKeyError() {
144
344
  function normalizeBaseUrl(url) {
145
345
  return url.replace(/\/$/, "");
146
346
  }
147
- function isRecord(v) {
347
+ function isRecord2(v) {
148
348
  return typeof v === "object" && v !== null;
149
349
  }
150
- var STRUCTURE_KINDS = [
151
- "home",
152
- "tool",
153
- "api",
154
- "database",
155
- "model"
156
- ];
157
- function isWorldStructureKind(s) {
158
- return STRUCTURE_KINDS.includes(s);
350
+ function parseBounds(raw) {
351
+ if (!isRecord2(raw)) {
352
+ throw new Error("getWorldSnapshot: worldMap.bounds must be an object");
353
+ }
354
+ const { minX, minY, maxX, maxY } = raw;
355
+ if (typeof minX !== "number" || typeof minY !== "number" || typeof maxX !== "number" || typeof maxY !== "number") {
356
+ throw new Error(
357
+ "getWorldSnapshot: bounds need numeric minX, minY, maxX, maxY"
358
+ );
359
+ }
360
+ return { minX, minY, maxX, maxY };
159
361
  }
160
- function parseWorldStructure(x) {
161
- if (!isRecord(x)) return null;
162
- if (typeof x.id !== "string" || typeof x.kind !== "string") return null;
163
- if (!isWorldStructureKind(x.kind)) return null;
164
- if (typeof x.x !== "number" || typeof x.y !== "number") return null;
165
- const out = {
166
- id: x.id,
167
- kind: x.kind,
168
- x: x.x,
169
- y: x.y
170
- };
171
- if (typeof x.toolName === "string") out.toolName = x.toolName;
172
- if (typeof x.label === "string") out.label = x.label;
173
- return out;
362
+ function parseWorldMap(raw) {
363
+ if (!isRecord2(raw)) {
364
+ throw new Error("getWorldSnapshot: worldMap must be an object");
365
+ }
366
+ const bounds = parseBounds(raw.bounds);
367
+ const occ = raw.occupants;
368
+ if (!Array.isArray(occ)) {
369
+ throw new Error("getWorldSnapshot: worldMap.occupants must be an array");
370
+ }
371
+ const occupants = [];
372
+ const coordKeys = /* @__PURE__ */ new Set();
373
+ for (const row of occ) {
374
+ if (!isRecord2(row) || row.kind !== "agent" && row.kind !== "mcp") {
375
+ throw new Error("getWorldSnapshot: each occupant must have kind agent or mcp");
376
+ }
377
+ const xy = typeof row.x === "number" && typeof row.y === "number" ? `${row.x},${row.y}` : "";
378
+ if (xy.length === 0) {
379
+ throw new Error("getWorldSnapshot: occupant missing coordinates");
380
+ }
381
+ if (coordKeys.has(xy)) {
382
+ throw new Error("getWorldSnapshot: duplicate world map coordinate");
383
+ }
384
+ coordKeys.add(xy);
385
+ if (row.kind === "agent") {
386
+ occupants.push(parseAgentOccupantRow(row));
387
+ } else {
388
+ occupants.push(parseMcpOccupantRow(row));
389
+ }
390
+ }
391
+ return { bounds, occupants };
174
392
  }
175
- function parseStructures(v) {
176
- if (!Array.isArray(v)) return [];
177
- const out = [];
178
- for (const x of v) {
179
- const row = parseWorldStructure(x);
180
- if (row !== null) out.push(row);
393
+ function parseAgentPlaySnapshot(snapshot) {
394
+ if (!isRecord2(snapshot) || typeof snapshot.sid !== "string") {
395
+ throw new Error("getWorldSnapshot: invalid snapshot");
396
+ }
397
+ const worldMap = parseWorldMap(snapshot.worldMap);
398
+ const out = { sid: snapshot.sid, worldMap };
399
+ if ("mcpServers" in snapshot && Array.isArray(snapshot.mcpServers)) {
400
+ const servers = [];
401
+ for (const m of snapshot.mcpServers) {
402
+ if (!isRecord2(m) || typeof m.id !== "string" || typeof m.name !== "string") {
403
+ continue;
404
+ }
405
+ const row = {
406
+ id: m.id,
407
+ name: m.name
408
+ };
409
+ if (typeof m.url === "string") row.url = m.url;
410
+ servers.push(row);
411
+ }
412
+ if (servers.length > 0) out.mcpServers = servers;
181
413
  }
182
414
  return out;
183
415
  }
416
+ function parseRegisteredAgentSummary(raw) {
417
+ if (!isRecord2(raw)) {
418
+ throw new Error("addPlayer: registeredAgent missing");
419
+ }
420
+ if (typeof raw.agentId !== "string" || typeof raw.name !== "string") {
421
+ throw new Error("addPlayer: registeredAgent.agentId and name required");
422
+ }
423
+ if (!Array.isArray(raw.toolNames)) {
424
+ throw new Error("addPlayer: registeredAgent.toolNames must be an array");
425
+ }
426
+ const toolNames = [];
427
+ for (const t of raw.toolNames) {
428
+ if (typeof t !== "string") {
429
+ throw new Error("addPlayer: registeredAgent.toolNames must be strings");
430
+ }
431
+ toolNames.push(t);
432
+ }
433
+ if (typeof raw.zoneCount !== "number" || typeof raw.yieldCount !== "number" || typeof raw.flagged !== "boolean") {
434
+ throw new Error("addPlayer: registeredAgent counters invalid");
435
+ }
436
+ return {
437
+ agentId: raw.agentId,
438
+ name: raw.name,
439
+ toolNames,
440
+ zoneCount: raw.zoneCount,
441
+ yieldCount: raw.yieldCount,
442
+ flagged: raw.flagged
443
+ };
444
+ }
184
445
  function formatInvalidHoldSecondsError() {
185
446
  return [
186
447
  "RemotePlayWorld.hold().for(seconds): seconds must be a finite number.",
@@ -189,24 +450,12 @@ function formatInvalidHoldSecondsError() {
189
450
  ].join("\n");
190
451
  }
191
452
  var RemotePlayWorld = class {
192
- /** Normalized {@link RemotePlayWorldOptions.baseUrl} (no trailing slash). Used for `fetch` base. */
193
453
  apiBase;
194
- /** Trimmed account API key; sent on `addPlayer` and implied for RPC auth expectations. */
195
454
  apiKey;
196
- /** Optional bearer token merged into request headers when set. */
197
455
  authToken;
198
- /** Session id from `GET /api/agent-play/session`; `null` until {@link RemotePlayWorld.start} succeeds. */
199
456
  sid = null;
200
- /** When true, {@link RemotePlayWorld.close} is a no-op and listeners already ran. */
201
457
  closed = false;
202
- /** Unsubscribe callbacks registered via {@link RemotePlayWorld.onClose}. */
203
458
  closeListeners = /* @__PURE__ */ new Set();
204
- /**
205
- * @param options - Base URL, API key, and optional auth token.
206
- * @throws Error if `apiKey` is missing or whitespace-only (see {@link formatMissingApiKeyError}).
207
- *
208
- * @remarks **Callees:** {@link formatMissingApiKeyError}, {@link normalizeBaseUrl}.
209
- */
210
459
  constructor(options) {
211
460
  if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
212
461
  throw new Error(formatMissingApiKeyError());
@@ -215,25 +464,12 @@ var RemotePlayWorld = class {
215
464
  this.apiKey = options.apiKey.trim();
216
465
  this.authToken = options.authToken;
217
466
  }
218
- /**
219
- * Registers a one-shot listener invoked when {@link RemotePlayWorld.close} runs (e.g. process shutdown).
220
- *
221
- * @param handler - Synchronous callback; errors are swallowed.
222
- * @returns Unsubscribe function that removes this `handler` from the set.
223
- *
224
- * @remarks **Callers:** user code. **Callees:** `Set.prototype.add` / `delete`.
225
- */
226
467
  onClose(handler) {
227
468
  this.closeListeners.add(handler);
228
469
  return () => {
229
470
  this.closeListeners.delete(handler);
230
471
  };
231
472
  }
232
- /**
233
- * Returns a helper that sleeps for wall-clock seconds (useful for `await world.hold().for(3600)`).
234
- *
235
- * @remarks **Callers:** user code. **Callees:** {@link formatInvalidHoldSecondsError}.
236
- */
237
473
  hold() {
238
474
  return {
239
475
  for: async (seconds) => {
@@ -247,38 +483,25 @@ var RemotePlayWorld = class {
247
483
  }
248
484
  };
249
485
  }
250
- /**
251
- * Authorization header map for requests that only need session cookie or bearer auth.
252
- *
253
- * @internal
254
- * @remarks **Callers:** {@link RemotePlayWorld.start}, {@link RemotePlayWorld.addPlayer} (via headers),
255
- * {@link RemotePlayWorld.registerMcp}, and any future GET-only calls.
256
- */
257
486
  authHeaders() {
258
487
  if (this.authToken === void 0) return {};
259
488
  return { Authorization: `Bearer ${this.authToken}` };
260
489
  }
261
- /**
262
- * Headers for JSON `POST` bodies (RPC, players, MCP).
263
- *
264
- * @internal
265
- * @remarks **Callers:** {@link RemotePlayWorld.addPlayer}, {@link RemotePlayWorld.rpc}, {@link RemotePlayWorld.registerMcp}.
266
- * **Callees:** {@link authHeaders}.
267
- */
268
490
  jsonHeaders() {
269
491
  return {
270
492
  "content-type": "application/json",
271
493
  ...this.authHeaders()
272
494
  };
273
495
  }
274
- /**
275
- * Creates a session and stores `sid` from `GET /api/agent-play/session`.
276
- *
277
- * @throws Error if the response is not OK or JSON lacks a non-empty `sid` string.
278
- *
279
- * @remarks **Callers:** user code. **Callees:** `fetch`, {@link isRecord}.
280
- */
281
- async start() {
496
+ mergeAuthFetch(input, init) {
497
+ const headers = new Headers(init?.headers);
498
+ const auth = this.authHeaders();
499
+ for (const [k, v] of Object.entries(auth)) {
500
+ headers.set(k, v);
501
+ }
502
+ return fetch(input, { ...init, headers });
503
+ }
504
+ async connect() {
282
505
  const res = await fetch(`${this.apiBase}/api/agent-play/session`, {
283
506
  headers: this.authHeaders()
284
507
  });
@@ -286,16 +509,11 @@ var RemotePlayWorld = class {
286
509
  throw new Error(`session failed: ${res.status}`);
287
510
  }
288
511
  const json = await res.json();
289
- if (!isRecord(json) || typeof json.sid !== "string" || json.sid.length === 0) {
512
+ if (!isRecord2(json) || typeof json.sid !== "string" || json.sid.length === 0) {
290
513
  throw new Error("session: invalid response");
291
514
  }
292
515
  this.sid = json.sid;
293
516
  }
294
- /**
295
- * Marks the client closed and invokes all {@link onClose} listeners once.
296
- *
297
- * @remarks **Callers:** user code. **Callees:** `Array.from` over {@link closeListeners}.
298
- */
299
517
  async close() {
300
518
  if (this.closed) {
301
519
  return;
@@ -308,38 +526,119 @@ var RemotePlayWorld = class {
308
526
  }
309
527
  }
310
528
  }
311
- /**
312
- * @returns Current session id.
313
- * @throws Error if {@link RemotePlayWorld.start} has not been called successfully.
314
- *
315
- * @remarks **Callers:** user code. **Callees:** none.
316
- */
317
529
  getSessionId() {
318
530
  if (this.sid === null) {
319
- throw new Error("RemotePlayWorld.start() must be called first");
531
+ throw new Error("RemotePlayWorld.connect() must be called first");
320
532
  }
321
533
  return this.sid;
322
534
  }
323
- /**
324
- * @returns Absolute watch URL for the session (`/agent-play/watch` on `apiBase`). Query `sid` is not appended;
325
- * consumers append `?sid=` from {@link getSessionId} when needed.
326
- *
327
- * @remarks **Callers:** user code. **Callees:** `URL` constructor.
328
- */
329
535
  getPreviewUrl() {
330
536
  const u = new URL("/agent-play/watch", this.apiBase);
331
537
  u.search = "";
332
538
  return u.toString();
333
539
  }
540
+ async getWorldSnapshot() {
541
+ const res = await fetch(`${this.apiBase}/api/agent-play/sdk/rpc`, {
542
+ method: "POST",
543
+ headers: this.jsonHeaders(),
544
+ body: JSON.stringify({ op: "getWorldSnapshot", payload: {} })
545
+ });
546
+ const text = await res.text();
547
+ if (!res.ok) {
548
+ throw new Error(`getWorldSnapshot: ${res.status} ${text}`);
549
+ }
550
+ let json;
551
+ try {
552
+ json = JSON.parse(text);
553
+ } catch {
554
+ throw new Error("getWorldSnapshot: invalid JSON");
555
+ }
556
+ if (!isRecord2(json) || !("snapshot" in json)) {
557
+ throw new Error("getWorldSnapshot: invalid response shape");
558
+ }
559
+ return parseAgentPlaySnapshot(json.snapshot);
560
+ }
561
+ /**
562
+ * Fetches one player-chain node (genesis, header, occupant row, or removal) for `stableKey`, same snapshot scope as {@link RemotePlayWorld.getWorldSnapshot}.
563
+ */
564
+ async getPlayerChainNode(stableKey) {
565
+ const trimmed = stableKey.trim();
566
+ if (trimmed.length === 0) {
567
+ throw new Error("getPlayerChainNode: stableKey is required");
568
+ }
569
+ const res = await fetch(`${this.apiBase}/api/agent-play/sdk/rpc`, {
570
+ method: "POST",
571
+ headers: this.jsonHeaders(),
572
+ body: JSON.stringify({
573
+ op: "getPlayerChainNode",
574
+ payload: { stableKey: trimmed }
575
+ })
576
+ });
577
+ const text = await res.text();
578
+ if (!res.ok) {
579
+ throw new Error(`getPlayerChainNode: ${res.status} ${text}`);
580
+ }
581
+ let json;
582
+ try {
583
+ json = JSON.parse(text);
584
+ } catch {
585
+ throw new Error("getPlayerChainNode: invalid JSON");
586
+ }
587
+ return parsePlayerChainNodeRpcBody(json);
588
+ }
334
589
  /**
335
- * Registers a player agent with the server for the current session.
336
- *
337
- * @param input - Name, type, `agent` registration from {@link langchainRegistration}, optional `agentId`.
338
- * @returns Resolved player row with `previewUrl` and `structures`.
339
- * @throws Error on HTTP errors or malformed JSON.
340
- *
341
- * @remarks **Callers:** user code. **Callees:** {@link getSessionId}, `fetch`, `JSON.parse`, {@link parseStructures}.
590
+ * 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}.
342
591
  */
592
+ subscribeWorldState(callbacks) {
593
+ let closeSource = null;
594
+ const task = (async () => {
595
+ try {
596
+ const { createEventSource } = await import("eventsource-client");
597
+ let snapshot = await this.getWorldSnapshot();
598
+ callbacks.onSnapshot(snapshot);
599
+ const source = createEventSource({
600
+ url: `${this.apiBase}/api/agent-play/events?sid=${encodeURIComponent(
601
+ this.getSessionId()
602
+ )}`,
603
+ fetch: (input, init) => this.mergeAuthFetch(input, init)
604
+ });
605
+ closeSource = () => {
606
+ source.close();
607
+ };
608
+ for await (const msg of source) {
609
+ if (typeof msg.data !== "string") {
610
+ continue;
611
+ }
612
+ let data;
613
+ try {
614
+ data = JSON.parse(msg.data);
615
+ } catch {
616
+ continue;
617
+ }
618
+ const notify = parsePlayerChainFanoutNotifyFromSsePayload(data);
619
+ if (notify === void 0 || notify.nodes.length === 0) {
620
+ continue;
621
+ }
622
+ const ordered = sortNodeRefsForSerializedFetch(notify.nodes);
623
+ for (const ref of ordered) {
624
+ const node = await this.getPlayerChainNode(ref.stableKey);
625
+ snapshot = mergeSnapshotWithPlayerChainNode(snapshot, node);
626
+ }
627
+ callbacks.onSnapshot(snapshot);
628
+ }
629
+ } catch (e) {
630
+ callbacks.onError?.(
631
+ e instanceof Error ? e : new Error(String(e))
632
+ );
633
+ }
634
+ })();
635
+ return {
636
+ close: () => {
637
+ closeSource?.();
638
+ void task;
639
+ }
640
+ };
641
+ }
343
642
  async addPlayer(input) {
344
643
  const sid = this.getSessionId();
345
644
  const url = `${this.apiBase}/api/agent-play/players?sid=${encodeURIComponent(sid)}`;
@@ -358,21 +657,21 @@ var RemotePlayWorld = class {
358
657
  if (!res.ok) {
359
658
  throw new Error(`addPlayer: ${res.status} ${bodyText}`);
360
659
  }
361
- let json;
660
+ let body;
362
661
  try {
363
- json = JSON.parse(bodyText);
662
+ body = JSON.parse(bodyText);
364
663
  } catch {
365
664
  throw new Error("addPlayer: invalid JSON");
366
665
  }
367
- if (!isRecord(json)) {
666
+ if (!isRecord2(body)) {
368
667
  throw new Error("addPlayer: invalid response shape");
369
668
  }
370
- const playerId = json.playerId;
371
- const previewUrl = json.previewUrl;
669
+ const playerId = body.playerId;
670
+ const previewUrl = body.previewUrl;
372
671
  if (typeof playerId !== "string" || typeof previewUrl !== "string") {
373
672
  throw new Error("addPlayer: missing playerId or previewUrl");
374
673
  }
375
- const structures = parseStructures(json.structures);
674
+ const registeredAgent = parseRegisteredAgentSummary(body.registeredAgent);
376
675
  const now = /* @__PURE__ */ new Date();
377
676
  return {
378
677
  id: playerId,
@@ -381,14 +680,9 @@ var RemotePlayWorld = class {
381
680
  createdAt: now,
382
681
  updatedAt: now,
383
682
  previewUrl,
384
- structures
683
+ registeredAgent
385
684
  };
386
685
  }
387
- /**
388
- * Appends a chat-style interaction line for a player (RPC `recordInteraction`).
389
- *
390
- * @remarks **Callers:** user code. **Callees:** {@link rpc}.
391
- */
392
686
  async recordInteraction(input) {
393
687
  await this.rpc("recordInteraction", {
394
688
  playerId: input.playerId,
@@ -396,29 +690,9 @@ var RemotePlayWorld = class {
396
690
  text: input.text
397
691
  });
398
692
  }
399
- /**
400
- * Records a full journey for a player (RPC `recordJourney`).
401
- *
402
- * @remarks **Callers:** user code. **Callees:** {@link rpc}.
403
- */
404
693
  async recordJourney(playerId, journey) {
405
694
  await this.rpc("recordJourney", { playerId, journey });
406
695
  }
407
- /**
408
- * Re-syncs layout structures from an ordered tool name list (RPC `syncPlayerStructuresFromTools`).
409
- *
410
- * @remarks **Callers:** user code. **Callees:** {@link rpc}.
411
- */
412
- async syncPlayerStructuresFromTools(playerId, toolNames) {
413
- await this.rpc("syncPlayerStructuresFromTools", { playerId, toolNames });
414
- }
415
- /**
416
- * Registers an MCP server metadata row for the session (HTTP POST, not the same as RPC `op`).
417
- *
418
- * @returns New registration id string from JSON `{ id }`.
419
- *
420
- * @remarks **Callers:** user code. **Callees:** `fetch`, {@link getSessionId}, {@link jsonHeaders}.
421
- */
422
696
  async registerMcp(options) {
423
697
  const sid = this.getSessionId();
424
698
  const url = `${this.apiBase}/api/agent-play/mcp/register?sid=${encodeURIComponent(sid)}`;
@@ -441,18 +715,11 @@ var RemotePlayWorld = class {
441
715
  } catch {
442
716
  throw new Error("registerMcp: invalid JSON");
443
717
  }
444
- if (!isRecord(json) || typeof json.id !== "string") {
718
+ if (!isRecord2(json) || typeof json.id !== "string") {
445
719
  throw new Error("registerMcp: invalid response");
446
720
  }
447
721
  return json.id;
448
722
  }
449
- /**
450
- * Posts `{ op, payload }` to `/api/agent-play/sdk/rpc?sid=...`.
451
- *
452
- * @internal
453
- * @remarks **Callers:** {@link recordInteraction}, {@link recordJourney}, {@link syncPlayerStructuresFromTools}.
454
- * **Callees:** {@link getSessionId}, `fetch`, {@link jsonHeaders}.
455
- */
456
723
  async rpc(op, payload) {
457
724
  const sid = this.getSessionId();
458
725
  const url = `${this.apiBase}/api/agent-play/sdk/rpc?sid=${encodeURIComponent(sid)}`;
@@ -469,17 +736,23 @@ var RemotePlayWorld = class {
469
736
  };
470
737
  export {
471
738
  PLAYER_ADDED_EVENT,
739
+ PLAYER_CHAIN_GENESIS_STABLE_KEY,
740
+ PLAYER_CHAIN_HEADER_STABLE_KEY,
472
741
  RemotePlayWorld,
473
742
  WORLD_AGENT_SIGNAL_EVENT,
474
743
  WORLD_INTERACTION_EVENT,
475
744
  WORLD_JOURNEY_EVENT,
476
- WORLD_STRUCTURES_EVENT,
477
745
  agentPlayDebug,
478
746
  boundsContain,
479
747
  clampWorldPosition,
480
748
  configureAgentPlayDebug,
481
749
  isAgentPlayDebugEnabled,
482
750
  langchainRegistration,
483
- resetAgentPlayDebug
751
+ mergeSnapshotWithPlayerChainNode,
752
+ parsePlayerChainFanoutNotify,
753
+ parsePlayerChainFanoutNotifyFromSsePayload,
754
+ parsePlayerChainNodeRpcBody,
755
+ resetAgentPlayDebug,
756
+ sortNodeRefsForSerializedFetch
484
757
  };
485
758
  //# sourceMappingURL=index.js.map