pyyol 1.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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/adapter.d.ts +24 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +68 -0
- package/dist/adapter.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1325 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +160 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +13 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +175 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/login.d.ts +16 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +107 -0
- package/dist/login.js.map +1 -0
- package/dist/mode.d.ts +12 -0
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +55 -0
- package/dist/mode.js.map +1 -0
- package/dist/models.d.ts +105 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +68 -0
- package/dist/models.js.map +1 -0
- package/dist/rules.d.ts +4 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +26 -0
- package/dist/rules.js.map +1 -0
- package/dist/runtime.d.ts +97 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +372 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +75 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +174 -0
- package/dist/server.js.map +1 -0
- package/dist/signing.d.ts +42 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +115 -0
- package/dist/signing.js.map +1 -0
- package/dist/simulator.d.ts +38 -0
- package/dist/simulator.d.ts.map +1 -0
- package/dist/simulator.js +109 -0
- package/dist/simulator.js.map +1 -0
- package/dist/telemetry.d.ts +81 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +225 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +62 -0
- package/rules/games.md +349 -0
- package/rules/llms-full.txt +985 -0
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { SDK_VERSION } from "./server.js";
|
|
2
|
+
import { Tracer } from "./telemetry.js";
|
|
3
|
+
// Frame types — byte-identical to the Go gateway (internal/agentgw/frame.go).
|
|
4
|
+
const HELLO = "hello", REGISTERED = "registered", PONG = "pong";
|
|
5
|
+
const INITIALIZE = "initialize", TURN = "turn", EVENT = "event", GAME_END = "game_end", ERROR = "error";
|
|
6
|
+
const REGISTER = "register", PING = "ping", RESPONSE = "response";
|
|
7
|
+
export const PROTOCOL_VERSION = "1.0";
|
|
8
|
+
/** POST {apiUrl}/v1/auth/refresh {refresh_token} → {access, refresh} or null.
|
|
9
|
+
* A non-200 (refresh token expired/revoked) → null → terminal (must re-login). */
|
|
10
|
+
async function defaultRefreshHttp(apiUrl, refreshToken) {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(apiUrl.replace(/\/$/, "") + "/v1/auth/refresh", {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { "content-type": "application/json" },
|
|
15
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
return null;
|
|
19
|
+
const j = (await res.json().catch(() => null));
|
|
20
|
+
if (!j?.dashboard_token)
|
|
21
|
+
return null;
|
|
22
|
+
return { access: j.dashboard_token, refresh: j.refresh_token ?? "" };
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function summarizeMove(game, move) {
|
|
29
|
+
if (!move || typeof move !== "object")
|
|
30
|
+
return String(move);
|
|
31
|
+
const m = move;
|
|
32
|
+
if ("card" in m)
|
|
33
|
+
return `card ${m.card}${m.round !== undefined ? ` (round ${m.round})` : ""}`;
|
|
34
|
+
if ("action" in m)
|
|
35
|
+
return `${m.action}${m.target !== undefined ? ` → ${m.target}` : ""}`;
|
|
36
|
+
return JSON.stringify(m);
|
|
37
|
+
}
|
|
38
|
+
function summarizeResult(result) {
|
|
39
|
+
if (!result || typeof result !== "object")
|
|
40
|
+
return "game finished";
|
|
41
|
+
const outer = result;
|
|
42
|
+
// The gateway may nest the outcome under `result` (with a viewer-relative winner
|
|
43
|
+
// label like "you"/"opponent"/"tie"); unwrap it like the Python SDK does.
|
|
44
|
+
const inner = (outer.result && typeof outer.result === "object" ? outer.result : outer);
|
|
45
|
+
const bits = [];
|
|
46
|
+
if (inner.winner !== undefined && inner.winner !== null)
|
|
47
|
+
bits.push(`winner: ${inner.winner}`);
|
|
48
|
+
for (const k of ["coins_delta", "your_coins", "coins"]) {
|
|
49
|
+
if (inner[k]) {
|
|
50
|
+
bits.push(`${k}=${inner[k]}`);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return "game finished" + (bits.length ? " · " + bits.join(" · ") : "");
|
|
55
|
+
}
|
|
56
|
+
/** Terminal connector failure (e.g. auth rejected) — not retried. */
|
|
57
|
+
export class ConnectorError extends Error {
|
|
58
|
+
}
|
|
59
|
+
/** Internal: the access token was just refreshed; reconnect immediately with the
|
|
60
|
+
* new token (not a transport error, so no backoff / scary log). */
|
|
61
|
+
class RefreshRetry extends Error {
|
|
62
|
+
}
|
|
63
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
64
|
+
/** Parse a dotted version into a comparable number array; unparseable ⇒ [0] so a
|
|
65
|
+
* garbage latest_sdk never triggers a spurious nudge. */
|
|
66
|
+
function versionParts(v) {
|
|
67
|
+
const core = v.trim().replace(/^v/, "").split(/[-+]/)[0];
|
|
68
|
+
const parts = core.split(".").map((p) => Number(p));
|
|
69
|
+
return parts.some((n) => !Number.isFinite(n)) ? [0] : parts;
|
|
70
|
+
}
|
|
71
|
+
/** True if a is strictly older than b (numeric field-by-field). */
|
|
72
|
+
function isOlder(a, b) {
|
|
73
|
+
const pa = versionParts(a);
|
|
74
|
+
const pb = versionParts(b);
|
|
75
|
+
const n = Math.max(pa.length, pb.length);
|
|
76
|
+
for (let i = 0; i < n; i++) {
|
|
77
|
+
const x = pa[i] ?? 0;
|
|
78
|
+
const y = pb[i] ?? 0;
|
|
79
|
+
if (x !== y)
|
|
80
|
+
return x < y;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
export class RuntimeConnector {
|
|
85
|
+
agent;
|
|
86
|
+
opts;
|
|
87
|
+
stopped = false;
|
|
88
|
+
nudged = false; // print the "upgrade available" notice at most once
|
|
89
|
+
registered = false; // true once this session's register succeeded
|
|
90
|
+
refreshAttempts = 0; // per-connection guard against a refresh loop
|
|
91
|
+
// Opt-in Pyyol Lens telemetry (no-op unless PYYOL_LENS_ENDPOINT+KEY set).
|
|
92
|
+
// Correlated to the match trace so the agent's model/tool calls render with
|
|
93
|
+
// the platform's authoritative gateway spans.
|
|
94
|
+
tracer;
|
|
95
|
+
constructor(agent, opts) {
|
|
96
|
+
this.agent = agent;
|
|
97
|
+
this.opts = opts;
|
|
98
|
+
this.tracer = Tracer.fromEnv({ agentId: opts.agentId, service: opts.name });
|
|
99
|
+
}
|
|
100
|
+
/** The gateway echoes the newest published version on the registered frame.
|
|
101
|
+
* Print a one-line upgrade hint at most once (not on every reconnect). */
|
|
102
|
+
maybeNudge(latest) {
|
|
103
|
+
if (this.nudged || typeof latest !== "string" || !latest)
|
|
104
|
+
return;
|
|
105
|
+
if (isOlder(SDK_VERSION, latest)) {
|
|
106
|
+
this.nudged = true;
|
|
107
|
+
const notify = this.opts.onNotice ?? ((m) => console.warn(m));
|
|
108
|
+
notify(`a new pyyol ${latest} is available (you have ${SDK_VERSION}) — upgrade with \`npm update pyyol\``);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Connect and serve until stopped, reconnecting with exponential backoff. */
|
|
112
|
+
async run() {
|
|
113
|
+
let backoff = 1000;
|
|
114
|
+
const maxBackoff = this.opts.maxBackoffMs ?? 30000;
|
|
115
|
+
while (!this.stopped) {
|
|
116
|
+
try {
|
|
117
|
+
await this.session();
|
|
118
|
+
backoff = 1000;
|
|
119
|
+
if (this.opts.reconnect === false)
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
if (e instanceof RefreshRetry) {
|
|
124
|
+
// Token was just refreshed — reconnect right away with the new one, no
|
|
125
|
+
// backoff and no "connection lost" noise (even in one-shot mode).
|
|
126
|
+
this.feed("reauth", "access token refreshed — reconnecting");
|
|
127
|
+
backoff = 1000;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (e instanceof ConnectorError)
|
|
131
|
+
throw e; // terminal (auth) — don't spin
|
|
132
|
+
if (this.opts.reconnect === false || this.stopped)
|
|
133
|
+
throw e;
|
|
134
|
+
// Surface the retry so a bad/absent network doesn't look like a frozen
|
|
135
|
+
// terminal (the reason is often "can't reach the platform").
|
|
136
|
+
this.feed("reconnecting", `${e.message} — retrying in ${Math.round(backoff / 1000)}s`);
|
|
137
|
+
await sleep(backoff);
|
|
138
|
+
backoff = Math.min(backoff * 2, maxBackoff);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
stop() {
|
|
143
|
+
this.stopped = true;
|
|
144
|
+
void this.tracer.close();
|
|
145
|
+
}
|
|
146
|
+
/** Emit a live-feed line to the CLI/console, if a sink was provided. */
|
|
147
|
+
feed(kind, detail) {
|
|
148
|
+
this.opts.onFeed?.(kind, detail);
|
|
149
|
+
}
|
|
150
|
+
/** Warn (once) when connecting over cleartext ws:// to a non-local host — the
|
|
151
|
+
* register token is sent in the clear (mirrors the Python connector). */
|
|
152
|
+
securityCheck() {
|
|
153
|
+
try {
|
|
154
|
+
const u = new URL(this.opts.url);
|
|
155
|
+
if (u.protocol === "ws:") {
|
|
156
|
+
const h = u.hostname.toLowerCase();
|
|
157
|
+
if (!(h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost"))) {
|
|
158
|
+
// Security warning goes straight to stderr — NOT through onNotice, which is
|
|
159
|
+
// reserved for the SDK upgrade nudge (a host app may route that to its UI).
|
|
160
|
+
console.warn(`WARNING: connecting over insecure ws:// to ${h} — your token is sent in cleartext; use wss://`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* unparseable url — the connect below will surface it */
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** One connection lifetime: resolves on a clean stop, rejects on transport loss. */
|
|
169
|
+
/** Spend the refresh token for a fresh access token; returns true on success
|
|
170
|
+
* (opts.token rotated + persisted). Bounded per connection to avoid a loop. */
|
|
171
|
+
async tryRefresh() {
|
|
172
|
+
const rt = this.opts.refreshToken;
|
|
173
|
+
const api = this.opts.apiUrl;
|
|
174
|
+
if (!rt || !api || this.refreshAttempts >= 2)
|
|
175
|
+
return false;
|
|
176
|
+
this.refreshAttempts++;
|
|
177
|
+
let pair;
|
|
178
|
+
try {
|
|
179
|
+
pair = await (this.opts.refreshHttp ?? defaultRefreshHttp)(api, rt);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if (!pair?.access)
|
|
185
|
+
return false;
|
|
186
|
+
this.opts.token = pair.access;
|
|
187
|
+
if (pair.refresh)
|
|
188
|
+
this.opts.refreshToken = pair.refresh;
|
|
189
|
+
if (this.opts.onTokens) {
|
|
190
|
+
try {
|
|
191
|
+
await this.opts.onTokens(pair.access, this.opts.refreshToken ?? "");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* persistence is best-effort */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
session() {
|
|
200
|
+
this.securityCheck();
|
|
201
|
+
this.registered = false;
|
|
202
|
+
const WS = this.opts.WebSocketImpl ?? globalThis.WebSocket;
|
|
203
|
+
if (!WS)
|
|
204
|
+
throw new ConnectorError("no WebSocket implementation (Node >=22 or pass WebSocketImpl)");
|
|
205
|
+
let host = this.opts.url;
|
|
206
|
+
try {
|
|
207
|
+
host = new URL(this.opts.url).host;
|
|
208
|
+
}
|
|
209
|
+
catch { /* keep raw */ }
|
|
210
|
+
this.feed("connecting", host);
|
|
211
|
+
const ws = new WS(this.opts.url);
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
let hb;
|
|
214
|
+
let settled = false;
|
|
215
|
+
let opened = false;
|
|
216
|
+
let queue = Promise.resolve(); // serialize frame dispatch (FIFO)
|
|
217
|
+
const send = (f) => ws.send(JSON.stringify(f));
|
|
218
|
+
// Bound the OPENING handshake — the WHATWG WebSocket has no connect timeout, so
|
|
219
|
+
// a dead-air link (accepts TCP, never upgrades) would hang forever. On timeout
|
|
220
|
+
// we fail with a transient error → run() reconnects.
|
|
221
|
+
const openTimer = setTimeout(() => {
|
|
222
|
+
if (!opened)
|
|
223
|
+
fail(new Error("connect timed out (platform unreachable)"));
|
|
224
|
+
}, this.opts.connectTimeoutMs ?? 10000);
|
|
225
|
+
// Liveness watchdog — a window with zero frames means the link is (half-)dead.
|
|
226
|
+
// Rearmed on every received frame below; a healthy socket sees gateway PINGs
|
|
227
|
+
// well inside the window, so it only fires when the link has actually gone away.
|
|
228
|
+
const hbMs = this.opts.heartbeatMs ?? 10000;
|
|
229
|
+
const liveWindow = this.opts.livenessTimeoutMs ?? Math.max(30000, hbMs * 3);
|
|
230
|
+
let liveTimer;
|
|
231
|
+
const armLive = () => {
|
|
232
|
+
if (liveTimer)
|
|
233
|
+
clearTimeout(liveTimer);
|
|
234
|
+
liveTimer = setTimeout(() => fail(new Error("liveness timeout — no frames received (link half-open?)")), liveWindow);
|
|
235
|
+
};
|
|
236
|
+
const cleanup = () => {
|
|
237
|
+
clearTimeout(openTimer);
|
|
238
|
+
if (liveTimer)
|
|
239
|
+
clearTimeout(liveTimer);
|
|
240
|
+
if (hb)
|
|
241
|
+
clearInterval(hb);
|
|
242
|
+
try {
|
|
243
|
+
ws.close();
|
|
244
|
+
}
|
|
245
|
+
catch { /* already closing */ }
|
|
246
|
+
};
|
|
247
|
+
const fail = (e) => { if (!settled) {
|
|
248
|
+
settled = true;
|
|
249
|
+
cleanup();
|
|
250
|
+
reject(e);
|
|
251
|
+
} };
|
|
252
|
+
const done = () => { if (!settled) {
|
|
253
|
+
settled = true;
|
|
254
|
+
cleanup();
|
|
255
|
+
resolve();
|
|
256
|
+
} };
|
|
257
|
+
armLive(); // start the clock now; the first HELLO should arrive promptly
|
|
258
|
+
ws.addEventListener("open", () => { opened = true; });
|
|
259
|
+
ws.addEventListener("message", (ev) => {
|
|
260
|
+
armLive(); // any frame (of any type) proves the link is alive → reset
|
|
261
|
+
let frame;
|
|
262
|
+
try {
|
|
263
|
+
frame = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Chain onto the queue so frames are handled strictly one-at-a-time, in
|
|
269
|
+
// arrival order — responses can't be sent out of order under a burst.
|
|
270
|
+
queue = queue.then(async () => {
|
|
271
|
+
if (settled)
|
|
272
|
+
return;
|
|
273
|
+
try {
|
|
274
|
+
await this.dispatch(frame, send, () => {
|
|
275
|
+
hb = setInterval(() => send({ t: PING }), this.opts.heartbeatMs ?? 10000);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
fail(e instanceof Error ? e : new Error(String(e)));
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
ws.addEventListener("close", () => {
|
|
284
|
+
// Drain any queued frame dispatches BEFORE settling, so a close that arrives
|
|
285
|
+
// right after the last frame doesn't cut off its handling (e.g. a final turn
|
|
286
|
+
// response). In one-shot mode (or after stop()) a close is a clean end;
|
|
287
|
+
// otherwise it is a transport loss that run() turns into a reconnect.
|
|
288
|
+
queue = queue.then(() => {
|
|
289
|
+
if (this.stopped || this.opts.reconnect === false)
|
|
290
|
+
done();
|
|
291
|
+
else
|
|
292
|
+
fail(new Error("connection closed"));
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
ws.addEventListener("error", () => { });
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async dispatch(frame, send, onRegistered) {
|
|
299
|
+
switch (frame.t) {
|
|
300
|
+
case HELLO:
|
|
301
|
+
send({
|
|
302
|
+
t: REGISTER, agent_id: this.opts.agentId ?? "", token: this.opts.token ?? "",
|
|
303
|
+
agent_name: this.opts.name ?? "pyyol-agent", version: this.opts.version ?? "1.0.0",
|
|
304
|
+
games: this.opts.games ?? [], sdk_version: SDK_VERSION, sdk_language: "js",
|
|
305
|
+
});
|
|
306
|
+
break;
|
|
307
|
+
case REGISTERED:
|
|
308
|
+
this.registered = true;
|
|
309
|
+
this.refreshAttempts = 0; // a good register clears the refresh guard
|
|
310
|
+
this.maybeNudge(frame.latest_sdk);
|
|
311
|
+
this.feed("connected", `as ${frame.agent_id ?? this.opts.agentId ?? ""} · games=${(this.opts.games ?? []).join(",")}`);
|
|
312
|
+
onRegistered();
|
|
313
|
+
break;
|
|
314
|
+
case ERROR:
|
|
315
|
+
if (!this.registered) {
|
|
316
|
+
// Register rejected — almost always an expired access token. If we hold a
|
|
317
|
+
// refresh token, spend it and reconnect; only terminal when refresh fails.
|
|
318
|
+
if (await this.tryRefresh())
|
|
319
|
+
throw new RefreshRetry();
|
|
320
|
+
throw new ConnectorError(`register rejected: ${frame.error} (${frame.reason ?? ""})`);
|
|
321
|
+
}
|
|
322
|
+
// Mid-session gateway error — surface it, not terminal (mirrors Python).
|
|
323
|
+
this.feed("error", `${frame.error} (${frame.reason ?? ""})`);
|
|
324
|
+
break;
|
|
325
|
+
case PING:
|
|
326
|
+
send({ t: PONG, id: frame.id ?? "" });
|
|
327
|
+
break;
|
|
328
|
+
case PONG:
|
|
329
|
+
break;
|
|
330
|
+
case TURN: {
|
|
331
|
+
const view = frame.payload ?? {};
|
|
332
|
+
// Bracket the developer's handler in a Lens span; inside decideTurn the
|
|
333
|
+
// author can reach it via pyyol.currentSpan() to record model/tool calls.
|
|
334
|
+
const { status, body } = await this.tracer.runTurn({
|
|
335
|
+
matchId: view.match_id ?? "",
|
|
336
|
+
game: view.game ?? "",
|
|
337
|
+
round: Number(view.round ?? 0) || 0,
|
|
338
|
+
agentId: this.opts.agentId,
|
|
339
|
+
}, () => this.agent.decideTurn(view));
|
|
340
|
+
if (status === 200) {
|
|
341
|
+
send({ t: RESPONSE, id: frame.id ?? "", payload: body });
|
|
342
|
+
this.feed("turn", summarizeMove(frame.payload?.game ?? "", body));
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
send({ t: RESPONSE, id: frame.id ?? "", error: body?.error ?? "handler_error" });
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case INITIALIZE: {
|
|
350
|
+
const ack = await this.agent.ackInitialize(frame.payload ?? {});
|
|
351
|
+
send({ t: RESPONSE, id: frame.id ?? "", payload: ack });
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case EVENT:
|
|
355
|
+
await this.agent.notifyEvent({
|
|
356
|
+
match_id: frame.match_id ?? "", game: frame.game ?? "",
|
|
357
|
+
seq: frame.seq ?? 0, type: frame.kind ?? "", payload: frame.payload,
|
|
358
|
+
});
|
|
359
|
+
this.feed("event", `${frame.kind ?? "event"}${frame.seq !== undefined ? ` seq=${frame.seq}` : ""}`);
|
|
360
|
+
break;
|
|
361
|
+
case GAME_END:
|
|
362
|
+
await this.agent.notifyGameEnd({
|
|
363
|
+
match_id: frame.match_id ?? "", game: frame.game ?? "", result: frame.payload,
|
|
364
|
+
});
|
|
365
|
+
this.feed("game_end", summarizeResult(frame.payload));
|
|
366
|
+
break;
|
|
367
|
+
default:
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Pyyol agent server.
|
|
3
|
+
*
|
|
4
|
+
* You register decision handlers; the SDK owns the wire protocol — routing,
|
|
5
|
+
* signature verification, replay protection, payload parsing, and response
|
|
6
|
+
* serialization. It contains no game strategy: your turn handler returns the move.
|
|
7
|
+
*
|
|
8
|
+
* import { Agent } from "pyyol";
|
|
9
|
+
*
|
|
10
|
+
* const agent = new Agent({ secret: process.env.PYYOL_SECRET });
|
|
11
|
+
* agent.onTurn("goofspiel", (v) => ({ round: v.round, card: Math.min(...v.legal_actions) }));
|
|
12
|
+
* agent.serve(9099);
|
|
13
|
+
*
|
|
14
|
+
* Routes served (base path comes from your manifest `endpoint.url`; the rest are
|
|
15
|
+
* its siblings): GET /health, POST /handshake, /initialize, <endpoint> (turn),
|
|
16
|
+
* /event, /game-end.
|
|
17
|
+
*/
|
|
18
|
+
import { createServer } from "node:http";
|
|
19
|
+
import { EventNotification, GameEndNotification, InitializeRequest, Move, TurnView } from "./models.js";
|
|
20
|
+
import { Headers } from "./signing.js";
|
|
21
|
+
import { SDK_VERSION } from "./version.js";
|
|
22
|
+
export { SDK_VERSION };
|
|
23
|
+
export interface AgentOptions {
|
|
24
|
+
/** Manifest endpoint secret — the shared key the platform signs with. When set
|
|
25
|
+
* (recommended) every POST is signature-verified with replay protection. */
|
|
26
|
+
secret?: string;
|
|
27
|
+
supportedGames?: string[];
|
|
28
|
+
name?: string;
|
|
29
|
+
skewSeconds?: number;
|
|
30
|
+
/** Force verification on/off; defaults to on iff a secret is set. */
|
|
31
|
+
verify?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface DispatchResult {
|
|
34
|
+
status: number;
|
|
35
|
+
body: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
type TurnHandler = (view: TurnView) => Move | Promise<Move>;
|
|
38
|
+
export declare class Agent {
|
|
39
|
+
readonly secret: string;
|
|
40
|
+
readonly supportedGames: string[];
|
|
41
|
+
readonly name: string;
|
|
42
|
+
private skew;
|
|
43
|
+
private verify;
|
|
44
|
+
private replay;
|
|
45
|
+
private turnHandlers;
|
|
46
|
+
private defaultTurn?;
|
|
47
|
+
private initHandler?;
|
|
48
|
+
private eventHandler?;
|
|
49
|
+
private gameEndHandler?;
|
|
50
|
+
constructor(opts?: AgentOptions);
|
|
51
|
+
/** Register the per-turn decision handler. Pass a game to scope it, or omit for
|
|
52
|
+
* a catch-all used when no game-specific handler is set. */
|
|
53
|
+
onTurn(game: string | TurnHandler, handler?: TurnHandler): this;
|
|
54
|
+
onInitialize(fn: (r: InitializeRequest) => unknown): this;
|
|
55
|
+
onEvent(fn: (n: EventNotification) => unknown): this;
|
|
56
|
+
onGameEnd(fn: (n: GameEndNotification) => unknown): this;
|
|
57
|
+
/** Route one request and return `{ status, body }`. Framework-agnostic — call it
|
|
58
|
+
* from the built-in server or any web framework you already run. `path` must be
|
|
59
|
+
* the request path as received (no query). */
|
|
60
|
+
handle(method: string, path: string, headers: Headers, body: Buffer): Promise<DispatchResult>;
|
|
61
|
+
private handleTurn;
|
|
62
|
+
/** Run the turn handler for a raw view dict; returns `{status, body}`. */
|
|
63
|
+
decideTurn(view: Record<string, unknown>): Promise<DispatchResult>;
|
|
64
|
+
/** Run the initialize handler and return the ack object. */
|
|
65
|
+
ackInitialize(data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
66
|
+
notifyEvent(data: Record<string, unknown>): Promise<void>;
|
|
67
|
+
notifyGameEnd(data: Record<string, unknown>): Promise<void>;
|
|
68
|
+
/** Connect to the platform and serve matches over an outbound WebSocket — the
|
|
69
|
+
* Beta local-runtime path (no inbound endpoint; works behind NAT). Blocks
|
|
70
|
+
* until stopped; reconnects automatically. */
|
|
71
|
+
run(opts: import("./runtime.js").RuntimeOptions): Promise<void>;
|
|
72
|
+
/** Run an HTTP server until the process exits. Zero dependencies. */
|
|
73
|
+
serve(port?: number, host?: string): ReturnType<typeof createServer>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Pyyol agent server.
|
|
3
|
+
*
|
|
4
|
+
* You register decision handlers; the SDK owns the wire protocol — routing,
|
|
5
|
+
* signature verification, replay protection, payload parsing, and response
|
|
6
|
+
* serialization. It contains no game strategy: your turn handler returns the move.
|
|
7
|
+
*
|
|
8
|
+
* import { Agent } from "pyyol";
|
|
9
|
+
*
|
|
10
|
+
* const agent = new Agent({ secret: process.env.PYYOL_SECRET });
|
|
11
|
+
* agent.onTurn("goofspiel", (v) => ({ round: v.round, card: Math.min(...v.legal_actions) }));
|
|
12
|
+
* agent.serve(9099);
|
|
13
|
+
*
|
|
14
|
+
* Routes served (base path comes from your manifest `endpoint.url`; the rest are
|
|
15
|
+
* its siblings): GET /health, POST /handshake, /initialize, <endpoint> (turn),
|
|
16
|
+
* /event, /game-end.
|
|
17
|
+
*/
|
|
18
|
+
import { createServer } from "node:http";
|
|
19
|
+
import { SUPPORTED_GAMES, parseView, } from "./models.js";
|
|
20
|
+
import { ReplayGuard, VerificationError, verifyRequest } from "./signing.js";
|
|
21
|
+
// Single source of truth: the generated version.ts (from package.json). Imported
|
|
22
|
+
// for local use below and re-exported so `import { SDK_VERSION } from "pyyol"` works.
|
|
23
|
+
import { SDK_VERSION } from "./version.js";
|
|
24
|
+
export { SDK_VERSION };
|
|
25
|
+
export class Agent {
|
|
26
|
+
secret;
|
|
27
|
+
supportedGames;
|
|
28
|
+
name;
|
|
29
|
+
skew;
|
|
30
|
+
verify;
|
|
31
|
+
replay = new ReplayGuard();
|
|
32
|
+
turnHandlers = new Map();
|
|
33
|
+
defaultTurn;
|
|
34
|
+
initHandler;
|
|
35
|
+
eventHandler;
|
|
36
|
+
gameEndHandler;
|
|
37
|
+
constructor(opts = {}) {
|
|
38
|
+
this.secret = opts.secret ?? "";
|
|
39
|
+
this.supportedGames = opts.supportedGames ?? [...SUPPORTED_GAMES];
|
|
40
|
+
this.name = opts.name ?? "pyyol-agent";
|
|
41
|
+
this.skew = opts.skewSeconds ?? 300;
|
|
42
|
+
this.verify = opts.verify ?? Boolean(this.secret);
|
|
43
|
+
}
|
|
44
|
+
/** Register the per-turn decision handler. Pass a game to scope it, or omit for
|
|
45
|
+
* a catch-all used when no game-specific handler is set. */
|
|
46
|
+
onTurn(game, handler) {
|
|
47
|
+
if (typeof game === "function")
|
|
48
|
+
this.defaultTurn = game;
|
|
49
|
+
else if (handler)
|
|
50
|
+
this.turnHandlers.set(game, handler);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
onInitialize(fn) { this.initHandler = fn; return this; }
|
|
54
|
+
onEvent(fn) { this.eventHandler = fn; return this; }
|
|
55
|
+
onGameEnd(fn) { this.gameEndHandler = fn; return this; }
|
|
56
|
+
/** Route one request and return `{ status, body }`. Framework-agnostic — call it
|
|
57
|
+
* from the built-in server or any web framework you already run. `path` must be
|
|
58
|
+
* the request path as received (no query). */
|
|
59
|
+
async handle(method, path, headers, body) {
|
|
60
|
+
method = method.toUpperCase();
|
|
61
|
+
const suffix = path.replace(/\/+$/, "").split("/").pop() ?? "";
|
|
62
|
+
// Health is an unauthenticated liveness probe — never signature-checked.
|
|
63
|
+
if (method === "GET" && suffix === "health") {
|
|
64
|
+
return { status: 200, body: { status: "healthy", agent: this.name, version: SDK_VERSION } };
|
|
65
|
+
}
|
|
66
|
+
if (method !== "POST")
|
|
67
|
+
return { status: 405, body: { error: "method_not_allowed" } };
|
|
68
|
+
if (this.verify) {
|
|
69
|
+
try {
|
|
70
|
+
verifyRequest(this.secret, headers, method, path, body, { skewSeconds: this.skew, replayGuard: this.replay });
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
const reason = e instanceof VerificationError ? e.reason : "bad_signature";
|
|
74
|
+
return { status: 401, body: { error: "unauthorized", reason } };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const data = loadJson(body);
|
|
78
|
+
if (suffix === "handshake") {
|
|
79
|
+
return { status: 200, body: { accepted: true, sdkVersion: SDK_VERSION, supportedGames: this.supportedGames } };
|
|
80
|
+
}
|
|
81
|
+
if (suffix === "initialize") {
|
|
82
|
+
const ack = this.initHandler ? await this.initHandler(data) : undefined;
|
|
83
|
+
if (ack && typeof ack === "object")
|
|
84
|
+
return { status: 200, body: ack };
|
|
85
|
+
return { status: 200, body: { ready: true, display_name: this.name } };
|
|
86
|
+
}
|
|
87
|
+
if (suffix === "event") {
|
|
88
|
+
if (this.eventHandler)
|
|
89
|
+
await this.eventHandler(data);
|
|
90
|
+
return { status: 200, body: { ok: true } };
|
|
91
|
+
}
|
|
92
|
+
if (suffix === "game-end") {
|
|
93
|
+
if (this.gameEndHandler)
|
|
94
|
+
await this.gameEndHandler(data);
|
|
95
|
+
return { status: 200, body: { ok: true } };
|
|
96
|
+
}
|
|
97
|
+
return this.handleTurn(data);
|
|
98
|
+
}
|
|
99
|
+
async handleTurn(data) {
|
|
100
|
+
const game = typeof data.game === "string" ? data.game : "";
|
|
101
|
+
const handler = this.turnHandlers.get(game) ?? this.defaultTurn;
|
|
102
|
+
if (!handler)
|
|
103
|
+
return { status: 501, body: { error: "no_turn_handler", game } };
|
|
104
|
+
try {
|
|
105
|
+
const move = await handler(parseView(data));
|
|
106
|
+
return { status: 200, body: move ?? {} };
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { status: 500, body: { error: "handler_error" } };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// --- shared handler invocation (used by both the HTTP path and the socket
|
|
113
|
+
// RuntimeConnector, so both transports run identical decision logic) ---
|
|
114
|
+
/** Run the turn handler for a raw view dict; returns `{status, body}`. */
|
|
115
|
+
decideTurn(view) {
|
|
116
|
+
return this.handleTurn(view);
|
|
117
|
+
}
|
|
118
|
+
/** Run the initialize handler and return the ack object. */
|
|
119
|
+
async ackInitialize(data) {
|
|
120
|
+
const ack = this.initHandler ? await this.initHandler(data) : undefined;
|
|
121
|
+
return ack && typeof ack === "object" ? ack : { ready: true, display_name: this.name };
|
|
122
|
+
}
|
|
123
|
+
async notifyEvent(data) {
|
|
124
|
+
if (this.eventHandler)
|
|
125
|
+
await this.eventHandler(data);
|
|
126
|
+
}
|
|
127
|
+
async notifyGameEnd(data) {
|
|
128
|
+
if (this.gameEndHandler)
|
|
129
|
+
await this.gameEndHandler(data);
|
|
130
|
+
}
|
|
131
|
+
/** Connect to the platform and serve matches over an outbound WebSocket — the
|
|
132
|
+
* Beta local-runtime path (no inbound endpoint; works behind NAT). Blocks
|
|
133
|
+
* until stopped; reconnects automatically. */
|
|
134
|
+
async run(opts) {
|
|
135
|
+
const { RuntimeConnector } = await import("./runtime.js");
|
|
136
|
+
await new RuntimeConnector(this, {
|
|
137
|
+
name: this.name,
|
|
138
|
+
games: this.supportedGames,
|
|
139
|
+
...opts,
|
|
140
|
+
}).run();
|
|
141
|
+
}
|
|
142
|
+
/** Run an HTTP server until the process exits. Zero dependencies. */
|
|
143
|
+
serve(port = 9099, host = "127.0.0.1") {
|
|
144
|
+
const server = createServer((req, res) => {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
req.on("data", (c) => chunks.push(c));
|
|
147
|
+
req.on("end", async () => {
|
|
148
|
+
const body = Buffer.concat(chunks);
|
|
149
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
150
|
+
const { status, body: payload } = await this.handle(req.method ?? "GET", path, req.headers, body);
|
|
151
|
+
const out = Buffer.from(JSON.stringify(payload));
|
|
152
|
+
res.writeHead(status, { "Content-Type": "application/json", "Content-Length": String(out.length) });
|
|
153
|
+
res.end(out);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
server.listen(port, host, () => {
|
|
157
|
+
// eslint-disable-next-line no-console
|
|
158
|
+
console.log(`pyyol agent "${this.name}" listening on http://${host}:${port} (verify=${this.verify})`);
|
|
159
|
+
});
|
|
160
|
+
return server;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function loadJson(body) {
|
|
164
|
+
if (!body || body.length === 0)
|
|
165
|
+
return {};
|
|
166
|
+
try {
|
|
167
|
+
const obj = JSON.parse(body.toString("utf8"));
|
|
168
|
+
return obj && typeof obj === "object" ? obj : {};
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=server.js.map
|