@whitesands/agent-player 0.0.0-stage → 0.2.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/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +117 -2
- package/dist/index.cjs +983 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +70 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +938 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.cjs +50 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +110 -0
- package/dist/protocol.d.ts +110 -0
- package/dist/protocol.js +21 -0
- package/dist/protocol.js.map +1 -0
- package/dist/testing.cjs +320 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +91 -0
- package/dist/testing.d.ts +91 -0
- package/dist/testing.js +290 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-DCIs4-gP.d.cts +117 -0
- package/dist/types-DIF8LgGI.d.ts +117 -0
- package/package.json +54 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
// src/agent-server.ts
|
|
2
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var PlatformAuthError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "PlatformAuthError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var ProtocolVersionError = class extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "ProtocolVersionError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var TurnFailedError = class extends Error {
|
|
19
|
+
attempts;
|
|
20
|
+
constructor(message, attempts = []) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "TurnFailedError";
|
|
23
|
+
this.attempts = attempts;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var TurnTimeoutError = class extends Error {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "TurnTimeoutError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ConnectionLostError = class extends Error {
|
|
33
|
+
constructor(message = "Connection to the platform was lost") {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "ConnectionLostError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/logger.ts
|
|
40
|
+
function log(logger, level, message, extra) {
|
|
41
|
+
logger?.[level]?.(message, extra);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/platform-connection.ts
|
|
45
|
+
import { randomUUID } from "crypto";
|
|
46
|
+
import WebSocket from "ws";
|
|
47
|
+
|
|
48
|
+
// src/protocol.ts
|
|
49
|
+
var PROTOCOL_VERSION = 1;
|
|
50
|
+
var SDK_VERSION = "0.1.0";
|
|
51
|
+
var SESSION_RESUME_GRACE_MS = 3e4;
|
|
52
|
+
var DEFAULT_AGENT_INSTRUCTIONS = [
|
|
53
|
+
"You are a remote game agent. Tools are provided by the game.",
|
|
54
|
+
"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.",
|
|
55
|
+
"After wait returns, call exactly one action tool (not see, not wait).",
|
|
56
|
+
"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed."
|
|
57
|
+
].join("\n");
|
|
58
|
+
function assertNever(value) {
|
|
59
|
+
throw new Error(`Unhandled protocol message: ${JSON.stringify(value)}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/ws-transport.ts
|
|
63
|
+
var SessionMuxTransport = class {
|
|
64
|
+
onclose;
|
|
65
|
+
onerror;
|
|
66
|
+
onmessage;
|
|
67
|
+
sessionId;
|
|
68
|
+
sid;
|
|
69
|
+
sendEnvelope;
|
|
70
|
+
started = false;
|
|
71
|
+
closed = false;
|
|
72
|
+
constructor(sessionId, sendEnvelope) {
|
|
73
|
+
this.sid = sessionId;
|
|
74
|
+
this.sessionId = sessionId;
|
|
75
|
+
this.sendEnvelope = sendEnvelope;
|
|
76
|
+
}
|
|
77
|
+
async start() {
|
|
78
|
+
if (this.closed) throw new Error("Transport already closed");
|
|
79
|
+
this.started = true;
|
|
80
|
+
}
|
|
81
|
+
async close() {
|
|
82
|
+
if (this.closed) return;
|
|
83
|
+
this.closed = true;
|
|
84
|
+
this.onclose?.();
|
|
85
|
+
}
|
|
86
|
+
async send(message) {
|
|
87
|
+
if (this.closed) throw new Error("Transport closed");
|
|
88
|
+
this.sendEnvelope({ type: "mcp", sessionId: this.sid, message });
|
|
89
|
+
}
|
|
90
|
+
/** Deliver an inbound MCP message from the platform. */
|
|
91
|
+
receive(message) {
|
|
92
|
+
if (this.closed || !this.started) return;
|
|
93
|
+
try {
|
|
94
|
+
this.onmessage?.(message);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/platform-connection.ts
|
|
102
|
+
var HEARTBEAT_MS = 15e3;
|
|
103
|
+
var PONG_TIMEOUT_MS = 1e4;
|
|
104
|
+
var MAX_RECONNECT_ATTEMPTS = 8;
|
|
105
|
+
var PlatformConnection = class {
|
|
106
|
+
ws = null;
|
|
107
|
+
url;
|
|
108
|
+
platformKey;
|
|
109
|
+
clientId = randomUUID();
|
|
110
|
+
handlers;
|
|
111
|
+
logger;
|
|
112
|
+
pending = /* @__PURE__ */ new Map();
|
|
113
|
+
outbox = [];
|
|
114
|
+
accountName = null;
|
|
115
|
+
closed = false;
|
|
116
|
+
everConnected = false;
|
|
117
|
+
reconnecting = false;
|
|
118
|
+
reconnectAttempt = 0;
|
|
119
|
+
reconnectTimer;
|
|
120
|
+
heartbeat;
|
|
121
|
+
pongTimer;
|
|
122
|
+
pendingPong = null;
|
|
123
|
+
constructor(url, platformKey, handlers, logger) {
|
|
124
|
+
this.url = url;
|
|
125
|
+
this.platformKey = platformKey;
|
|
126
|
+
this.handlers = handlers;
|
|
127
|
+
this.logger = logger;
|
|
128
|
+
}
|
|
129
|
+
get connected() {
|
|
130
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
131
|
+
}
|
|
132
|
+
async connect() {
|
|
133
|
+
if (this.closed) throw new Error("Platform connection is closed");
|
|
134
|
+
if (this.connected && this.accountName) return this.accountName;
|
|
135
|
+
const account = await this.openSocket();
|
|
136
|
+
this.everConnected = true;
|
|
137
|
+
this.reconnectAttempt = 0;
|
|
138
|
+
return account;
|
|
139
|
+
}
|
|
140
|
+
send(envelope) {
|
|
141
|
+
if (this.connected && this.ws) {
|
|
142
|
+
this.ws.send(JSON.stringify(envelope));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (envelope.type === "mcp" || envelope.type === "ping") {
|
|
146
|
+
this.outbox.push(envelope);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
throw new Error("Not connected to platform");
|
|
150
|
+
}
|
|
151
|
+
waitForSessionEvent(sessionId, kind, timeoutMs = 15e3) {
|
|
152
|
+
const key = `session:${sessionId}:${kind}`;
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const timer = setTimeout(() => {
|
|
155
|
+
this.pending.delete(key);
|
|
156
|
+
reject(new Error(`Timed out waiting for session ${kind}`));
|
|
157
|
+
}, timeoutMs);
|
|
158
|
+
this.pending.set(key, {
|
|
159
|
+
resolve: (envelope) => {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
resolve(envelope);
|
|
162
|
+
},
|
|
163
|
+
reject: (error) => {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
reject(error);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
createTransport(sessionId) {
|
|
171
|
+
return new SessionMuxTransport(sessionId, (envelope) => this.send(envelope));
|
|
172
|
+
}
|
|
173
|
+
async close() {
|
|
174
|
+
this.closed = true;
|
|
175
|
+
this.stopHeartbeat();
|
|
176
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
177
|
+
this.reconnectTimer = void 0;
|
|
178
|
+
this.reconnecting = false;
|
|
179
|
+
this.failWaiters(new Error("platform connection closed"));
|
|
180
|
+
if (!this.ws) return;
|
|
181
|
+
const ws = this.ws;
|
|
182
|
+
this.ws = null;
|
|
183
|
+
await new Promise((resolve) => {
|
|
184
|
+
ws.once("close", () => resolve());
|
|
185
|
+
ws.close();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
openSocket() {
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const ws = new WebSocket(this.url);
|
|
191
|
+
this.ws = ws;
|
|
192
|
+
let settled = false;
|
|
193
|
+
const fail = (error) => {
|
|
194
|
+
if (settled) return;
|
|
195
|
+
settled = true;
|
|
196
|
+
reject(error);
|
|
197
|
+
};
|
|
198
|
+
ws.on("open", () => {
|
|
199
|
+
ws.send(JSON.stringify({
|
|
200
|
+
type: "hello",
|
|
201
|
+
platformKey: this.platformKey,
|
|
202
|
+
clientId: this.clientId,
|
|
203
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
204
|
+
sdkVersion: SDK_VERSION
|
|
205
|
+
}));
|
|
206
|
+
});
|
|
207
|
+
ws.on("message", (data) => {
|
|
208
|
+
let envelope;
|
|
209
|
+
try {
|
|
210
|
+
envelope = JSON.parse(data.toString());
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const parsed = error instanceof Error ? error : new Error("Invalid platform message");
|
|
213
|
+
this.handlers.onError?.(parsed);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!settled && (envelope.type === "hello_ok" || envelope.type === "hello_err")) {
|
|
217
|
+
settled = true;
|
|
218
|
+
if (envelope.type === "hello_err") {
|
|
219
|
+
const error = envelope.code === "PROTOCOL" ? new ProtocolVersionError(envelope.message) : new PlatformAuthError(envelope.message);
|
|
220
|
+
this.closed = true;
|
|
221
|
+
reject(error);
|
|
222
|
+
ws.close();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
this.accountName = envelope.accountName;
|
|
226
|
+
this.startHeartbeat();
|
|
227
|
+
this.flushOutbox();
|
|
228
|
+
resolve(envelope.accountName);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (envelope.type === "pong") {
|
|
232
|
+
if (this.pendingPong === envelope.id) {
|
|
233
|
+
this.pendingPong = null;
|
|
234
|
+
if (this.pongTimer) clearTimeout(this.pongTimer);
|
|
235
|
+
this.pongTimer = void 0;
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.resolveSessionWaiter(envelope);
|
|
240
|
+
this.handlers.onEnvelope(envelope);
|
|
241
|
+
});
|
|
242
|
+
ws.on("error", (error) => {
|
|
243
|
+
this.handlers.onError?.(error);
|
|
244
|
+
fail(error);
|
|
245
|
+
});
|
|
246
|
+
ws.on("close", () => {
|
|
247
|
+
this.stopHeartbeat();
|
|
248
|
+
if (this.ws === ws) this.ws = null;
|
|
249
|
+
if (!settled) fail(new Error("platform connection closed before hello"));
|
|
250
|
+
if (this.closed || !this.everConnected) {
|
|
251
|
+
this.failWaiters(new Error("platform connection closed"));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
this.handlers.onDisconnect?.();
|
|
255
|
+
this.scheduleReconnect();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
resolveSessionWaiter(envelope) {
|
|
260
|
+
if (envelope.type === "session_started" || envelope.type === "session_stopped") {
|
|
261
|
+
const kind = envelope.type === "session_started" ? "start" : "stop";
|
|
262
|
+
const waiter = this.pending.get(`session:${envelope.sessionId}:${kind}`);
|
|
263
|
+
if (!waiter) return;
|
|
264
|
+
this.pending.delete(`session:${envelope.sessionId}:${kind}`);
|
|
265
|
+
waiter.resolve(envelope);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (envelope.type === "session_resumed" || envelope.type === "resume_err") {
|
|
269
|
+
const waiter = this.pending.get(`session:${envelope.sessionId}:resume`);
|
|
270
|
+
if (!waiter) return;
|
|
271
|
+
this.pending.delete(`session:${envelope.sessionId}:resume`);
|
|
272
|
+
if (envelope.type === "resume_err") {
|
|
273
|
+
waiter.reject(new ConnectionLostError(envelope.message));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
waiter.resolve(envelope);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
scheduleReconnect() {
|
|
280
|
+
if (this.closed || this.reconnecting) return;
|
|
281
|
+
if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
|
282
|
+
this.closed = true;
|
|
283
|
+
const error = new ConnectionLostError("Could not reconnect to the platform");
|
|
284
|
+
this.handlers.onError?.(error);
|
|
285
|
+
this.failWaiters(error);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const delay = Math.min(SESSION_RESUME_GRACE_MS, 250 * 2 ** this.reconnectAttempt);
|
|
289
|
+
this.reconnectAttempt += 1;
|
|
290
|
+
this.reconnecting = true;
|
|
291
|
+
log(this.logger, "warn", `platform connection dropped; retrying in ${delay}ms`);
|
|
292
|
+
this.reconnectTimer = setTimeout(() => {
|
|
293
|
+
this.reconnectTimer = void 0;
|
|
294
|
+
void this.reconnect();
|
|
295
|
+
}, delay);
|
|
296
|
+
this.reconnectTimer.unref?.();
|
|
297
|
+
}
|
|
298
|
+
async reconnect() {
|
|
299
|
+
try {
|
|
300
|
+
await this.openSocket();
|
|
301
|
+
this.everConnected = true;
|
|
302
|
+
this.reconnectAttempt = 0;
|
|
303
|
+
this.reconnecting = false;
|
|
304
|
+
const sessions = this.handlers.sessions?.() ?? [];
|
|
305
|
+
for (const session of sessions) {
|
|
306
|
+
const resumed = this.waitForSessionEvent(session.sessionId, "resume");
|
|
307
|
+
this.send({
|
|
308
|
+
type: "resume_session",
|
|
309
|
+
sessionId: session.sessionId,
|
|
310
|
+
sessionToken: session.sessionToken
|
|
311
|
+
});
|
|
312
|
+
await resumed;
|
|
313
|
+
}
|
|
314
|
+
this.handlers.onReconnect?.();
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.reconnecting = false;
|
|
317
|
+
if (this.closed) return;
|
|
318
|
+
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
319
|
+
if (wrapped instanceof PlatformAuthError || wrapped instanceof ProtocolVersionError) {
|
|
320
|
+
this.closed = true;
|
|
321
|
+
this.handlers.onError?.(wrapped);
|
|
322
|
+
this.failWaiters(wrapped);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this.scheduleReconnect();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
flushOutbox() {
|
|
329
|
+
if (!this.ws || !this.connected) return;
|
|
330
|
+
const pending = this.outbox.splice(0, this.outbox.length);
|
|
331
|
+
for (const envelope of pending) {
|
|
332
|
+
this.ws.send(JSON.stringify(envelope));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
startHeartbeat() {
|
|
336
|
+
this.stopHeartbeat();
|
|
337
|
+
this.heartbeat = setInterval(() => {
|
|
338
|
+
if (!this.connected) return;
|
|
339
|
+
const id = randomUUID();
|
|
340
|
+
this.pendingPong = id;
|
|
341
|
+
try {
|
|
342
|
+
this.send({ type: "ping", id });
|
|
343
|
+
} catch {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.pongTimer = setTimeout(() => {
|
|
347
|
+
if (this.pendingPong === id) {
|
|
348
|
+
log(this.logger, "warn", "platform heartbeat timed out");
|
|
349
|
+
this.ws?.terminate();
|
|
350
|
+
}
|
|
351
|
+
}, PONG_TIMEOUT_MS);
|
|
352
|
+
this.pongTimer.unref?.();
|
|
353
|
+
}, HEARTBEAT_MS);
|
|
354
|
+
this.heartbeat.unref?.();
|
|
355
|
+
}
|
|
356
|
+
stopHeartbeat() {
|
|
357
|
+
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
358
|
+
if (this.pongTimer) clearTimeout(this.pongTimer);
|
|
359
|
+
this.heartbeat = void 0;
|
|
360
|
+
this.pongTimer = void 0;
|
|
361
|
+
this.pendingPong = null;
|
|
362
|
+
}
|
|
363
|
+
failWaiters(error) {
|
|
364
|
+
for (const [, waiter] of this.pending) waiter.reject(error);
|
|
365
|
+
this.pending.clear();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/reflect.ts
|
|
370
|
+
import { z } from "zod";
|
|
371
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
372
|
+
var SKIP = /* @__PURE__ */ new Set(["constructor"]);
|
|
373
|
+
var SEE_DESCRIPTION = "Read the current game view for this player. Prefer wait, which returns this after the game grants a turn.";
|
|
374
|
+
var WAIT_DESCRIPTION = "Block until the game calls turn() for this session, then return the current see() view.";
|
|
375
|
+
function getParamNames(fn) {
|
|
376
|
+
const src = fn.toString().replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
377
|
+
const header = src.match(/(?:async\s+)?(?:function\s*)?(?:\w*)\s*\(([^)]*)\)/) ?? src.match(/^(?:async\s*)?\(?([^)=]*)\)?\s*=>/);
|
|
378
|
+
if (!header) return [];
|
|
379
|
+
const raw = header[1]?.trim() ?? "";
|
|
380
|
+
if (!raw) return [];
|
|
381
|
+
return raw.split(",").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
382
|
+
const cleaned = part.replace(/\s*=\s*[^,]+$/, "").trim();
|
|
383
|
+
const name = cleaned.split(/[:\s]/)[0]?.replace(/^\.\.\./, "") ?? "";
|
|
384
|
+
return name;
|
|
385
|
+
}).filter((name) => name.length > 0 && name !== "this");
|
|
386
|
+
}
|
|
387
|
+
function collectMethodNames(instance) {
|
|
388
|
+
const names = /* @__PURE__ */ new Set();
|
|
389
|
+
let proto = Object.getPrototypeOf(instance);
|
|
390
|
+
while (proto && proto !== Object.prototype) {
|
|
391
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
392
|
+
if (SKIP.has(name) || name.startsWith("_")) continue;
|
|
393
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
394
|
+
if (!desc || typeof desc.value !== "function") continue;
|
|
395
|
+
names.add(name);
|
|
396
|
+
}
|
|
397
|
+
proto = Object.getPrototypeOf(proto);
|
|
398
|
+
}
|
|
399
|
+
return [...names].sort();
|
|
400
|
+
}
|
|
401
|
+
function readMetadata(instance) {
|
|
402
|
+
const ctor = instance.constructor;
|
|
403
|
+
return ctor.agent;
|
|
404
|
+
}
|
|
405
|
+
function toJsonSchema(schema) {
|
|
406
|
+
const json = zodToJsonSchema(schema, { target: "jsonSchema7", $refStrategy: "none" });
|
|
407
|
+
delete json.$schema;
|
|
408
|
+
return json;
|
|
409
|
+
}
|
|
410
|
+
function formatZodError(error) {
|
|
411
|
+
return error.issues.map((issue) => {
|
|
412
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "arguments";
|
|
413
|
+
return `${path}: ${issue.message}`;
|
|
414
|
+
}).join("; ");
|
|
415
|
+
}
|
|
416
|
+
function buildTool(instance, name, description, spec) {
|
|
417
|
+
const fn = instance[name];
|
|
418
|
+
if (typeof fn !== "function") {
|
|
419
|
+
throw new Error(`Expected method ${name}`);
|
|
420
|
+
}
|
|
421
|
+
const paramNames = spec?.args ? Object.keys(spec.args) : getParamNames(fn);
|
|
422
|
+
const shape = spec?.args ?? Object.fromEntries(paramNames.map((param) => [param, z.any()]));
|
|
423
|
+
const argsSchema = paramNames.length > 0 || spec?.args ? z.object(shape) : void 0;
|
|
424
|
+
const inputSchema = spec?.args && argsSchema ? toJsonSchema(argsSchema) : {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: Object.fromEntries(paramNames.map((param) => [param, {}])),
|
|
427
|
+
...paramNames.length > 0 ? { required: paramNames } : {}
|
|
428
|
+
};
|
|
429
|
+
return {
|
|
430
|
+
name,
|
|
431
|
+
description,
|
|
432
|
+
paramNames,
|
|
433
|
+
...argsSchema ? { argsSchema } : {},
|
|
434
|
+
descriptor: { name, description, inputSchema },
|
|
435
|
+
async invoke(args) {
|
|
436
|
+
let parsed = args ?? {};
|
|
437
|
+
if (argsSchema) {
|
|
438
|
+
const result = argsSchema.safeParse(args ?? {});
|
|
439
|
+
if (!result.success) {
|
|
440
|
+
throw new Error(formatZodError(result.error));
|
|
441
|
+
}
|
|
442
|
+
parsed = result.data;
|
|
443
|
+
}
|
|
444
|
+
const ordered = paramNames.map((param) => parsed[param]);
|
|
445
|
+
return await Promise.resolve(fn.apply(instance, ordered));
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function reflectPlayer(instance) {
|
|
450
|
+
const names = collectMethodNames(instance);
|
|
451
|
+
if (!names.includes("see")) {
|
|
452
|
+
throw new Error("Player must implement a public see() method");
|
|
453
|
+
}
|
|
454
|
+
const metadata = readMetadata(instance);
|
|
455
|
+
const declared = metadata?.actions ?? {};
|
|
456
|
+
const className = instance.constructor?.name || "Player";
|
|
457
|
+
for (const name of Object.keys(declared)) {
|
|
458
|
+
if (!names.includes(name) || name === "see") {
|
|
459
|
+
throw new Error(`Declared action "${name}" is not a public method on ${className}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const see = buildTool(instance, "see", SEE_DESCRIPTION);
|
|
463
|
+
const actionNames = names.filter((name) => name !== "see");
|
|
464
|
+
const actions = actionNames.map((name) => {
|
|
465
|
+
const spec = declared[name];
|
|
466
|
+
const description = spec?.description ?? `Action: ${name}`;
|
|
467
|
+
return buildTool(instance, name, description, spec);
|
|
468
|
+
});
|
|
469
|
+
const wait = {
|
|
470
|
+
name: "wait",
|
|
471
|
+
description: WAIT_DESCRIPTION,
|
|
472
|
+
inputSchema: { type: "object", properties: {} }
|
|
473
|
+
};
|
|
474
|
+
return {
|
|
475
|
+
...metadata?.description ? { description: metadata.description } : {},
|
|
476
|
+
see,
|
|
477
|
+
actions,
|
|
478
|
+
tools: [see.descriptor, ...actions.map((action) => action.descriptor), wait]
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/session-mcp.ts
|
|
483
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
484
|
+
function jsonResult(payload, isError = false) {
|
|
485
|
+
return {
|
|
486
|
+
isError,
|
|
487
|
+
content: [{ type: "text", text: JSON.stringify(payload) }]
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function toolError(error) {
|
|
491
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
492
|
+
return jsonResult({ error: message }, true);
|
|
493
|
+
}
|
|
494
|
+
function createSessionMcpServer(player, gate, options) {
|
|
495
|
+
const server = new McpServer(
|
|
496
|
+
{ name: options?.name ?? "agent-player", version: SDK_VERSION },
|
|
497
|
+
{ capabilities: { tools: {} } }
|
|
498
|
+
);
|
|
499
|
+
server.registerTool(
|
|
500
|
+
"see",
|
|
501
|
+
{ description: player.see.description },
|
|
502
|
+
async () => {
|
|
503
|
+
try {
|
|
504
|
+
const view = await player.see.invoke({});
|
|
505
|
+
return jsonResult(view);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
return toolError(error);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
server.registerTool(
|
|
512
|
+
"wait",
|
|
513
|
+
{ description: WAIT_DESCRIPTION },
|
|
514
|
+
async () => {
|
|
515
|
+
try {
|
|
516
|
+
await gate.waitForTurn();
|
|
517
|
+
const view = await player.see.invoke({});
|
|
518
|
+
return jsonResult(view);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return toolError(error);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
for (const action of player.actions) {
|
|
525
|
+
const handler = async (args) => {
|
|
526
|
+
try {
|
|
527
|
+
const result = await action.invoke(args);
|
|
528
|
+
return jsonResult(result ?? { ok: true });
|
|
529
|
+
} catch (error) {
|
|
530
|
+
return toolError(error);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
if (action.argsSchema) {
|
|
534
|
+
server.registerTool(
|
|
535
|
+
action.name,
|
|
536
|
+
{
|
|
537
|
+
description: action.description,
|
|
538
|
+
inputSchema: action.argsSchema.shape
|
|
539
|
+
},
|
|
540
|
+
async (args) => handler(args)
|
|
541
|
+
);
|
|
542
|
+
} else {
|
|
543
|
+
server.registerTool(
|
|
544
|
+
action.name,
|
|
545
|
+
{ description: action.description },
|
|
546
|
+
async () => handler({})
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return server;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/turn-gate.ts
|
|
554
|
+
function abortError(signal) {
|
|
555
|
+
const reason = signal.reason;
|
|
556
|
+
if (reason instanceof Error) return reason;
|
|
557
|
+
if (typeof reason === "string" && reason.length > 0) return new Error(reason);
|
|
558
|
+
return new Error("turn aborted");
|
|
559
|
+
}
|
|
560
|
+
var TurnGate = class {
|
|
561
|
+
waitResolve = null;
|
|
562
|
+
waitPromise = null;
|
|
563
|
+
turnResolve = null;
|
|
564
|
+
turnReject = null;
|
|
565
|
+
turnPromise = null;
|
|
566
|
+
released = false;
|
|
567
|
+
/** Called by the wait tool. Blocks until turn() releases this session. */
|
|
568
|
+
async waitForTurn() {
|
|
569
|
+
if (this.released) {
|
|
570
|
+
this.released = false;
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (!this.waitPromise) {
|
|
574
|
+
this.waitPromise = new Promise((resolve) => {
|
|
575
|
+
this.waitResolve = resolve;
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
await this.waitPromise;
|
|
579
|
+
this.waitPromise = null;
|
|
580
|
+
this.waitResolve = null;
|
|
581
|
+
this.released = false;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Unblock wait and wait for the platform's turn report.
|
|
585
|
+
* Rejects on timeout or abort; those do not produce a TurnOutcome.
|
|
586
|
+
*/
|
|
587
|
+
async runTurn(options) {
|
|
588
|
+
if (this.turnPromise) {
|
|
589
|
+
throw new Error("turn() already in progress for this session");
|
|
590
|
+
}
|
|
591
|
+
if (options?.signal?.aborted) {
|
|
592
|
+
throw abortError(options.signal);
|
|
593
|
+
}
|
|
594
|
+
let timeoutId;
|
|
595
|
+
let onAbort;
|
|
596
|
+
const signal = options?.signal;
|
|
597
|
+
this.turnPromise = new Promise((resolve, reject) => {
|
|
598
|
+
this.turnResolve = resolve;
|
|
599
|
+
this.turnReject = reject;
|
|
600
|
+
if (options?.timeoutMs != null) {
|
|
601
|
+
timeoutId = setTimeout(() => {
|
|
602
|
+
this.rejectTurn(new TurnTimeoutError(`turn() timed out after ${options.timeoutMs}ms`));
|
|
603
|
+
}, options.timeoutMs);
|
|
604
|
+
}
|
|
605
|
+
if (signal) {
|
|
606
|
+
if (signal.aborted) {
|
|
607
|
+
this.rejectTurn(abortError(signal));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
onAbort = () => this.rejectTurn(abortError(signal));
|
|
611
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
this.released = true;
|
|
615
|
+
const release = this.waitResolve;
|
|
616
|
+
this.waitResolve = null;
|
|
617
|
+
this.waitPromise = null;
|
|
618
|
+
release?.();
|
|
619
|
+
try {
|
|
620
|
+
return await this.turnPromise;
|
|
621
|
+
} finally {
|
|
622
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
623
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
624
|
+
this.turnPromise = null;
|
|
625
|
+
this.turnResolve = null;
|
|
626
|
+
this.turnReject = null;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/** Platform finished one action cycle (success or second failure). */
|
|
630
|
+
completeTurn(outcome) {
|
|
631
|
+
const resolve = this.turnResolve;
|
|
632
|
+
if (!resolve) return;
|
|
633
|
+
this.turnResolve = null;
|
|
634
|
+
this.turnReject = null;
|
|
635
|
+
resolve(outcome);
|
|
636
|
+
}
|
|
637
|
+
/** Fail an in-flight turn, for example when the connection cannot be resumed. */
|
|
638
|
+
failTurn(error) {
|
|
639
|
+
this.rejectTurn(error);
|
|
640
|
+
}
|
|
641
|
+
reset() {
|
|
642
|
+
this.waitResolve?.();
|
|
643
|
+
this.completeTurn({ ok: false, error: "session stopped" });
|
|
644
|
+
this.waitResolve = null;
|
|
645
|
+
this.waitPromise = null;
|
|
646
|
+
this.released = false;
|
|
647
|
+
}
|
|
648
|
+
rejectTurn(error) {
|
|
649
|
+
const reject = this.turnReject;
|
|
650
|
+
if (!reject) return;
|
|
651
|
+
this.turnResolve = null;
|
|
652
|
+
this.turnReject = null;
|
|
653
|
+
reject(error);
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// src/agent-server.ts
|
|
658
|
+
var DEFAULT_TURN_TIMEOUT_MS = 6e4;
|
|
659
|
+
var DEFAULT_PLATFORM_URL = "ws://127.0.0.1:8787";
|
|
660
|
+
var AgentServer = class {
|
|
661
|
+
options;
|
|
662
|
+
platformKey;
|
|
663
|
+
platformUrl;
|
|
664
|
+
logger;
|
|
665
|
+
connection = null;
|
|
666
|
+
sessions = /* @__PURE__ */ new Map();
|
|
667
|
+
listeners = /* @__PURE__ */ new Set();
|
|
668
|
+
inline;
|
|
669
|
+
started = false;
|
|
670
|
+
closing = false;
|
|
671
|
+
constructor(options = {}) {
|
|
672
|
+
const platformKey = options.platformKey ?? process.env.WSI_PLATFORM_KEY;
|
|
673
|
+
if (!options.inline && !platformKey) {
|
|
674
|
+
throw new Error("Set WSI_PLATFORM_KEY or pass platformKey, unless an inline platform is provided");
|
|
675
|
+
}
|
|
676
|
+
this.options = options;
|
|
677
|
+
this.platformKey = platformKey || "inline";
|
|
678
|
+
this.platformUrl = options.platformUrl ?? process.env.WSI_PLATFORM_URL ?? DEFAULT_PLATFORM_URL;
|
|
679
|
+
this.logger = options.logger;
|
|
680
|
+
this.inline = options.inline;
|
|
681
|
+
if (options.onEvent) this.listeners.add(options.onEvent);
|
|
682
|
+
}
|
|
683
|
+
/** Ensure the outbound platform connection. No-op for an inline platform. */
|
|
684
|
+
async connect() {
|
|
685
|
+
if (this.inline || this.started) {
|
|
686
|
+
this.started = true;
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.connection = new PlatformConnection(this.platformUrl, this.platformKey, {
|
|
690
|
+
onEnvelope: (envelope) => this.handleEnvelope(envelope),
|
|
691
|
+
onDisconnect: () => this.emit({ type: "disconnected" }),
|
|
692
|
+
onReconnect: () => this.emit({ type: "reconnected" }),
|
|
693
|
+
onError: (error) => {
|
|
694
|
+
log(this.logger, "error", error.message);
|
|
695
|
+
if (error instanceof ConnectionLostError) this.failInflight(error);
|
|
696
|
+
},
|
|
697
|
+
sessions: () => [...this.sessions.values()].map((session) => ({
|
|
698
|
+
sessionId: session.id,
|
|
699
|
+
sessionToken: session.token
|
|
700
|
+
}))
|
|
701
|
+
}, this.logger);
|
|
702
|
+
await this.connection.connect();
|
|
703
|
+
this.started = true;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Reflect the player into tools, register a session, and return a handle.
|
|
707
|
+
* `turn()` on that handle lets the remote model act once.
|
|
708
|
+
*/
|
|
709
|
+
async start(instance, options) {
|
|
710
|
+
await this.connect();
|
|
711
|
+
const reflected = reflectPlayer(instance);
|
|
712
|
+
const sessionId = randomUUID2();
|
|
713
|
+
const sessionToken = randomUUID2();
|
|
714
|
+
const gate = new TurnGate();
|
|
715
|
+
const name = options?.name ?? instance.constructor?.name ?? "player";
|
|
716
|
+
const server = createSessionMcpServer(reflected, gate, {
|
|
717
|
+
name: `agent-player:${name}`
|
|
718
|
+
});
|
|
719
|
+
const instructions = [this.options.instructions ?? DEFAULT_AGENT_INSTRUCTIONS, options?.instructions].filter((part) => Boolean(part && part.length > 0)).join("\n\n");
|
|
720
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
721
|
+
const record = {
|
|
722
|
+
id: sessionId,
|
|
723
|
+
token: sessionToken,
|
|
724
|
+
name,
|
|
725
|
+
gate,
|
|
726
|
+
server,
|
|
727
|
+
transport: void 0,
|
|
728
|
+
listeners
|
|
729
|
+
};
|
|
730
|
+
const onTurnResult = (result) => {
|
|
731
|
+
gate.completeTurn(result);
|
|
732
|
+
};
|
|
733
|
+
const onEvent = (event) => this.emit(event);
|
|
734
|
+
if (this.inline) {
|
|
735
|
+
const [serverTransport, clientTransport] = InMemoryTransport.createLinkedPair();
|
|
736
|
+
await server.connect(serverTransport);
|
|
737
|
+
record.transport = serverTransport;
|
|
738
|
+
this.sessions.set(sessionId, record);
|
|
739
|
+
await this.inline.startSession({
|
|
740
|
+
sessionId,
|
|
741
|
+
sessionToken,
|
|
742
|
+
name,
|
|
743
|
+
tools: reflected.tools,
|
|
744
|
+
instructions,
|
|
745
|
+
...options?.goal ? { goal: options.goal } : {},
|
|
746
|
+
...this.logger ? { logger: this.logger } : {},
|
|
747
|
+
transport: clientTransport,
|
|
748
|
+
onTurnResult,
|
|
749
|
+
onEvent
|
|
750
|
+
});
|
|
751
|
+
} else {
|
|
752
|
+
if (!this.connection) throw new Error("Not connected");
|
|
753
|
+
const mux = this.connection.createTransport(sessionId);
|
|
754
|
+
await server.connect(mux);
|
|
755
|
+
record.transport = mux;
|
|
756
|
+
record.mux = mux;
|
|
757
|
+
this.sessions.set(sessionId, record);
|
|
758
|
+
const started = this.connection.waitForSessionEvent(sessionId, "start");
|
|
759
|
+
this.connection.send({
|
|
760
|
+
type: "start_session",
|
|
761
|
+
sessionId,
|
|
762
|
+
sessionToken,
|
|
763
|
+
name,
|
|
764
|
+
tools: reflected.tools,
|
|
765
|
+
instructions,
|
|
766
|
+
...options?.goal ? { goal: options.goal } : {}
|
|
767
|
+
});
|
|
768
|
+
await started;
|
|
769
|
+
}
|
|
770
|
+
return {
|
|
771
|
+
sessionId,
|
|
772
|
+
turn: (turnOptions) => this.turn(sessionId, turnOptions),
|
|
773
|
+
stop: () => this.stopSession(sessionId),
|
|
774
|
+
on: (listener) => {
|
|
775
|
+
listeners.add(listener);
|
|
776
|
+
return () => listeners.delete(listener);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
/** Subscribe to every session plus connection events. Returns unsubscribe. */
|
|
781
|
+
on(listener) {
|
|
782
|
+
this.listeners.add(listener);
|
|
783
|
+
return () => this.listeners.delete(listener);
|
|
784
|
+
}
|
|
785
|
+
async turn(sessionId, options) {
|
|
786
|
+
const session = this.sessions.get(sessionId);
|
|
787
|
+
if (!session) throw new Error(`Unknown session ${sessionId}`);
|
|
788
|
+
const timeoutMs = options?.timeoutMs ?? this.options.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;
|
|
789
|
+
let outcome;
|
|
790
|
+
try {
|
|
791
|
+
outcome = await session.gate.runTurn({
|
|
792
|
+
timeoutMs,
|
|
793
|
+
...options?.signal ? { signal: options.signal } : {}
|
|
794
|
+
});
|
|
795
|
+
} catch (error) {
|
|
796
|
+
if (error instanceof ConnectionLostError) throw error;
|
|
797
|
+
throw error;
|
|
798
|
+
}
|
|
799
|
+
if (!outcome.ok) {
|
|
800
|
+
if (outcome.connectionLost) {
|
|
801
|
+
throw new ConnectionLostError(outcome.error ?? "Connection to the platform was lost");
|
|
802
|
+
}
|
|
803
|
+
throw new TurnFailedError(outcome.error ?? "turn failed", outcome.attemptDetails ?? []);
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
action: outcome.action ?? "unknown",
|
|
807
|
+
args: outcome.args ?? {},
|
|
808
|
+
result: outcome.result,
|
|
809
|
+
attempts: outcome.attempts ?? 1,
|
|
810
|
+
usage: outcome.usage ?? { promptTokens: 0, completionTokens: 0 }
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
emit(event) {
|
|
814
|
+
for (const listener of this.listeners) listener(event);
|
|
815
|
+
if (event.type === "disconnected" || event.type === "reconnected") return;
|
|
816
|
+
const session = this.sessions.get(event.sessionId);
|
|
817
|
+
if (!session) return;
|
|
818
|
+
for (const listener of session.listeners) listener(event);
|
|
819
|
+
}
|
|
820
|
+
failInflight(error) {
|
|
821
|
+
for (const session of this.sessions.values()) {
|
|
822
|
+
session.gate.failTurn(error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
handleEnvelope(envelope) {
|
|
826
|
+
switch (envelope.type) {
|
|
827
|
+
case "mcp": {
|
|
828
|
+
const session = this.sessions.get(envelope.sessionId);
|
|
829
|
+
session?.mux?.receive(envelope.message);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
case "turn_result": {
|
|
833
|
+
const session = this.sessions.get(envelope.sessionId);
|
|
834
|
+
session?.gate.completeTurn(envelope);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
case "session_event": {
|
|
838
|
+
if (envelope.event === "action") {
|
|
839
|
+
this.emit({
|
|
840
|
+
type: "action",
|
|
841
|
+
sessionId: envelope.sessionId,
|
|
842
|
+
action: envelope.action ?? "unknown",
|
|
843
|
+
args: envelope.args ?? {}
|
|
844
|
+
});
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (envelope.event === "retry") {
|
|
848
|
+
this.emit({
|
|
849
|
+
type: "retry",
|
|
850
|
+
sessionId: envelope.sessionId,
|
|
851
|
+
action: envelope.action ?? "unknown",
|
|
852
|
+
args: envelope.args ?? {},
|
|
853
|
+
error: envelope.error ?? "action failed"
|
|
854
|
+
});
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
this.emit({
|
|
858
|
+
type: "turnFailed",
|
|
859
|
+
sessionId: envelope.sessionId,
|
|
860
|
+
error: envelope.error ?? "turn failed",
|
|
861
|
+
...envelope.action ? { action: envelope.action } : {},
|
|
862
|
+
...envelope.args ? { args: envelope.args } : {}
|
|
863
|
+
});
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
case "error":
|
|
867
|
+
log(this.logger, "error", envelope.message);
|
|
868
|
+
return;
|
|
869
|
+
case "session_started":
|
|
870
|
+
case "session_stopped":
|
|
871
|
+
case "session_resumed":
|
|
872
|
+
case "resume_err":
|
|
873
|
+
case "hello_ok":
|
|
874
|
+
case "hello_err":
|
|
875
|
+
case "hello":
|
|
876
|
+
case "start_session":
|
|
877
|
+
case "stop_session":
|
|
878
|
+
case "ping":
|
|
879
|
+
case "pong":
|
|
880
|
+
case "resume_session":
|
|
881
|
+
return;
|
|
882
|
+
default: {
|
|
883
|
+
assertNever(envelope);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async stopSession(sessionId) {
|
|
888
|
+
const session = this.sessions.get(sessionId);
|
|
889
|
+
if (!session) return;
|
|
890
|
+
this.sessions.delete(sessionId);
|
|
891
|
+
session.gate.reset();
|
|
892
|
+
if (this.inline) {
|
|
893
|
+
await this.inline.stopSession(sessionId);
|
|
894
|
+
} else if (this.connection) {
|
|
895
|
+
const stopped = this.connection.waitForSessionEvent(sessionId, "stop").catch(() => null);
|
|
896
|
+
try {
|
|
897
|
+
this.connection.send({ type: "stop_session", sessionId });
|
|
898
|
+
} catch {
|
|
899
|
+
}
|
|
900
|
+
await stopped;
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
await session.server.close();
|
|
904
|
+
} catch {
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/** Stop every session and close the platform connection. */
|
|
908
|
+
async close() {
|
|
909
|
+
if (this.closing) return;
|
|
910
|
+
this.closing = true;
|
|
911
|
+
const ids = [...this.sessions.keys()];
|
|
912
|
+
for (const id of ids) {
|
|
913
|
+
await this.stopSession(id);
|
|
914
|
+
}
|
|
915
|
+
if (this.inline?.close) await this.inline.close();
|
|
916
|
+
if (this.connection) {
|
|
917
|
+
await this.connection.close();
|
|
918
|
+
this.connection = null;
|
|
919
|
+
}
|
|
920
|
+
this.started = false;
|
|
921
|
+
this.closing = false;
|
|
922
|
+
}
|
|
923
|
+
async [Symbol.asyncDispose]() {
|
|
924
|
+
await this.close();
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
export {
|
|
928
|
+
AgentServer,
|
|
929
|
+
ConnectionLostError,
|
|
930
|
+
DEFAULT_AGENT_INSTRUCTIONS,
|
|
931
|
+
PROTOCOL_VERSION,
|
|
932
|
+
PlatformAuthError,
|
|
933
|
+
ProtocolVersionError,
|
|
934
|
+
SDK_VERSION,
|
|
935
|
+
TurnFailedError,
|
|
936
|
+
TurnTimeoutError
|
|
937
|
+
};
|
|
938
|
+
//# sourceMappingURL=index.js.map
|