@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 +2 -0
- package/dist/index.d.ts +145 -166
- package/dist/index.js +419 -146
- package/dist/index.js.map +1 -1
- package/examples/01-remote-web-ui-langchain.ts +5 -5
- package/examples/02-remote-two-players-langchain.ts +10 -11
- package/examples/README.md +2 -2
- package/package.json +1 -1
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
|
|
347
|
+
function isRecord2(v) {
|
|
148
348
|
return typeof v === "object" && v !== null;
|
|
149
349
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
161
|
-
if (!
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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 (!
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
660
|
+
let body;
|
|
362
661
|
try {
|
|
363
|
-
|
|
662
|
+
body = JSON.parse(bodyText);
|
|
364
663
|
} catch {
|
|
365
664
|
throw new Error("addPlayer: invalid JSON");
|
|
366
665
|
}
|
|
367
|
-
if (!
|
|
666
|
+
if (!isRecord2(body)) {
|
|
368
667
|
throw new Error("addPlayer: invalid response shape");
|
|
369
668
|
}
|
|
370
|
-
const playerId =
|
|
371
|
-
const 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
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
751
|
+
mergeSnapshotWithPlayerChainNode,
|
|
752
|
+
parsePlayerChainFanoutNotify,
|
|
753
|
+
parsePlayerChainFanoutNotifyFromSsePayload,
|
|
754
|
+
parsePlayerChainNodeRpcBody,
|
|
755
|
+
resetAgentPlayDebug,
|
|
756
|
+
sortNodeRefsForSerializedFetch
|
|
484
757
|
};
|
|
485
758
|
//# sourceMappingURL=index.js.map
|