llm-chess-mcp 0.4.4 → 0.4.5

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.
Files changed (46) hide show
  1. package/README.md +17 -2
  2. package/dist/chess.d.ts +15 -0
  3. package/dist/chess.js +145 -4
  4. package/dist/cli.d.ts +11 -0
  5. package/dist/cli.js +17 -3
  6. package/dist/engines/stockfish.d.ts +50 -0
  7. package/dist/engines/stockfish.js +49 -5
  8. package/dist/env.d.ts +1 -0
  9. package/dist/errors.d.ts +5 -0
  10. package/dist/eval.d.ts +24 -0
  11. package/dist/explorer.d.ts +64 -0
  12. package/dist/explorer.js +169 -24
  13. package/dist/games.d.ts +33 -0
  14. package/dist/games.js +10 -25
  15. package/dist/http-work.d.ts +8 -0
  16. package/dist/http.d.ts +32 -0
  17. package/dist/http.js +109 -32
  18. package/dist/index.d.ts +6 -0
  19. package/dist/index.js +18 -16
  20. package/dist/intents.d.ts +39 -0
  21. package/dist/intents.js +45 -30
  22. package/dist/maia3/inference.d.ts +7 -0
  23. package/dist/maia3/inference.js +2 -0
  24. package/dist/maia3/mirror.d.ts +3 -0
  25. package/dist/maia3/tokenize.d.ts +5 -0
  26. package/dist/maia3/vocab.d.ts +3 -0
  27. package/dist/server.d.ts +3 -0
  28. package/dist/server.js +18 -2
  29. package/dist/services.d.ts +21 -0
  30. package/dist/services.js +19 -3
  31. package/dist/tool-inputs.d.ts +235 -0
  32. package/dist/tool-meta.d.ts +141 -0
  33. package/dist/tool-names.d.ts +2 -0
  34. package/dist/tool-result.d.ts +13 -0
  35. package/dist/tool-result.js +7 -2
  36. package/dist/tool-schemas.d.ts +870 -0
  37. package/dist/tool-schemas.js +0 -7
  38. package/dist/tools/analysis.d.ts +5 -0
  39. package/dist/tools/candidates.d.ts +5 -0
  40. package/dist/tools/candidates.js +19 -3
  41. package/dist/tools/explorer.d.ts +5 -0
  42. package/dist/tools/game.d.ts +5 -0
  43. package/dist/tools/game.js +6 -3
  44. package/dist/types.d.ts +44 -0
  45. package/docs/architecture.md +18 -9
  46. package/package.json +13 -2
package/README.md CHANGED
@@ -23,7 +23,7 @@ provided separately.
23
23
 
24
24
  ## Install
25
25
 
26
- Requires Node.js 20 or newer.
26
+ Requires Node.js 20.3 or newer.
27
27
 
28
28
  No install needed — run it directly with `npx`:
29
29
 
@@ -93,6 +93,19 @@ HTTP options:
93
93
  --allowed-host <host> Allowed Host/Origin hostname; repeat as needed
94
94
  ```
95
95
 
96
+ The package also exposes a typed ESM API:
97
+
98
+ ```js
99
+ import { serveHttp } from "llm-chess-mcp";
100
+
101
+ const server = await serveHttp({ port: 3000, bodyTimeoutMs: 15_000 });
102
+ await server.close();
103
+ ```
104
+
105
+ `bodyTimeoutMs` limits HTTP body upload time; it is not a whole-tool deadline.
106
+ The deprecated `requestTimeoutMs` alias remains supported when `bodyTimeoutMs`
107
+ is omitted.
108
+
96
109
  Binding to `0.0.0.0` or `::` requires at least one `--allowed-host`. HTTP mode
97
110
  does not provide authentication or TLS; use a trusted network or an
98
111
  authenticated reverse proxy when exposing it beyond localhost. Origin values
@@ -373,12 +386,14 @@ rejected:
373
386
  - `move_evaluate` accepts at most 10 moves per call.
374
387
  - Imported PGNs are limited to 1 MiB and 4,096 plies.
375
388
  - Stockfish accepts up to 32 active or queued analyses.
389
+ - Lichess Explorer requests run one at a time and share 429 cooldowns.
376
390
  - HTTP retains at most 64 MCP sessions; sessions with no active request expire
377
391
  after 30 minutes. An open GET/SSE stream keeps its session active.
378
392
  - HTTP accepts bodies up to 2 MiB. It permits 16 concurrent POSTs and downstream
379
393
  compute/network jobs process-wide, with two of each per session. Work keeps
380
394
  its slot after a raw disconnect until it settles. HTTP also caps connections
381
- at 128 and applies bounded header, upload, socket, and keep-alive timeouts.
395
+ at 128 and applies a 15-second body upload deadline plus bounded header,
396
+ socket, and keep-alive timeouts.
382
397
 
383
398
  Programmatic users can override the HTTP limits through `HttpServerOptions`.
384
399
  These safeguards do not replace public-edge quotas: a public deployment must
@@ -0,0 +1,15 @@
1
+ import { Chess } from "chess.js";
2
+ import type { Move } from "chess.js";
3
+ import type { ChessState, DrawResult } from "./types.js";
4
+ export declare const MAX_EVALUATED_MOVES = 10;
5
+ export declare const MAX_PGN_BYTES: number;
6
+ export declare const MAX_PGN_PLIES = 4096;
7
+ export declare function assertSafeFenCounters(fen: string): void;
8
+ export declare function snapshotChess(chess: Chess): Chess;
9
+ export declare function drawResult(chess: Chess): DrawResult | null;
10
+ export declare function pgnOf(chess: Chess): string;
11
+ export declare function parseImportedPgn(pgn: string): Chess;
12
+ export declare function stateOf(chess: Chess, revision: number): ChessState;
13
+ export declare function parseMove(chess: Chess, move: string): Move;
14
+ export declare function playParsedMove(chess: Chess, move: Move): Move;
15
+ export declare function pvToSan(chess: Chess, pv: readonly string[]): string[];
package/dist/chess.js CHANGED
@@ -3,15 +3,45 @@ import { ChessError } from "./errors.js";
3
3
  export const MAX_EVALUATED_MOVES = 10;
4
4
  export const MAX_PGN_BYTES = 1024 * 1024;
5
5
  export const MAX_PGN_PLIES = 4096;
6
+ const PGN_RESULTS = ["1-0", "0-1", "1/2-1/2", "*"];
6
7
  function moveDescriptor(move) {
7
8
  const base = { from: move.from, to: move.to };
8
9
  return move.promotion ? { ...base, promotion: move.promotion } : base;
9
10
  }
11
+ function isSafeDecimal(value, minimum) {
12
+ return (/^(?:0|[1-9]\d*)$/.test(value) &&
13
+ Number.isSafeInteger(Number(value)) &&
14
+ Number(value) >= minimum);
15
+ }
16
+ export function assertSafeFenCounters(fen) {
17
+ const fields = fen.split(/\s+/);
18
+ if (fields.length >= 5 && !isSafeDecimal(fields[4] ?? "", 0)) {
19
+ throw new ChessError("INVALID_FEN", "FEN halfmove clock must be a non-negative safe decimal integer");
20
+ }
21
+ if (fields.length >= 6 && !isSafeDecimal(fields[5] ?? "", 1)) {
22
+ throw new ChessError("INVALID_FEN", "FEN fullmove number must be a positive safe decimal integer");
23
+ }
24
+ }
10
25
  export function snapshotChess(chess) {
11
26
  const history = chess.history({ verbose: true });
12
- const snapshot = new Chess(history[0]?.before ?? chess.fen());
13
- for (const move of history)
27
+ const initialFen = history[0]?.before ?? chess.fen();
28
+ assertSafeFenCounters(initialFen);
29
+ const snapshot = new Chess(initialFen);
30
+ const comments = new Map(chess.getComments().map(({ fen, comment }) => [fen, comment]));
31
+ for (const [key, value] of Object.entries(chess.getHeaders())) {
32
+ snapshot.setHeader(key, value);
33
+ }
34
+ const restoreComment = () => {
35
+ const comment = comments.get(snapshot.fen());
36
+ if (comment !== undefined)
37
+ snapshot.setComment(comment);
38
+ };
39
+ restoreComment();
40
+ for (const move of history) {
14
41
  snapshot.move(moveDescriptor(move));
42
+ restoreComment();
43
+ }
44
+ assertSafeFenCounters(snapshot.fen());
15
45
  return snapshot;
16
46
  }
17
47
  export function drawResult(chess) {
@@ -27,10 +57,117 @@ export function drawResult(chess) {
27
57
  return "fifty_move_rule";
28
58
  return "draw";
29
59
  }
60
+ function withoutPgnComments(pgn) {
61
+ let result = "";
62
+ let braceComment = false;
63
+ let lineComment = false;
64
+ let quoted = false;
65
+ let escaped = false;
66
+ for (const char of pgn) {
67
+ if (braceComment) {
68
+ if (char === "}")
69
+ braceComment = false;
70
+ result += char === "\n" || char === "\r" ? char : " ";
71
+ continue;
72
+ }
73
+ if (lineComment) {
74
+ if (char === "\n" || char === "\r") {
75
+ lineComment = false;
76
+ result += char;
77
+ }
78
+ else {
79
+ result += " ";
80
+ }
81
+ continue;
82
+ }
83
+ if (quoted) {
84
+ result += char;
85
+ if (escaped)
86
+ escaped = false;
87
+ else if (char === "\\")
88
+ escaped = true;
89
+ else if (char === '"')
90
+ quoted = false;
91
+ continue;
92
+ }
93
+ if (char === '"') {
94
+ quoted = true;
95
+ result += char;
96
+ continue;
97
+ }
98
+ if (char === "{") {
99
+ braceComment = true;
100
+ result += " ";
101
+ }
102
+ else if (char === ";") {
103
+ lineComment = true;
104
+ result += " ";
105
+ }
106
+ else {
107
+ result += char;
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ function isPgnResult(value) {
113
+ return PGN_RESULTS.includes(value);
114
+ }
115
+ function declaredPgnResult(pgn) {
116
+ const visiblePgn = withoutPgnComments(pgn);
117
+ const headerResults = [
118
+ ...visiblePgn.matchAll(/^\s*\[\s*Result\s+"((?:\\.|[^"\\])*)"\s*\]\s*$/gm),
119
+ ].map((match) => match[1] ?? "");
120
+ const movetext = visiblePgn.replace(/^\s*\[[^\r\n]*\]\s*$/gm, "");
121
+ const markers = [
122
+ ...movetext.matchAll(/(?:^|\s)(1-0|0-1|1\/2-1\/2|\*)(?=\s|$)/g),
123
+ ].map((match) => match[1] ?? "");
124
+ const results = [...headerResults, ...markers];
125
+ if (!results.every(isPgnResult)) {
126
+ throw new ChessError("INVALID_PGN", "invalid PGN result");
127
+ }
128
+ const result = results[0];
129
+ if (results.some((value) => value !== result)) {
130
+ throw new ChessError("INVALID_PGN", "PGN result header and marker disagree");
131
+ }
132
+ return result;
133
+ }
134
+ function validatePgnFenCounters(pgn) {
135
+ for (const match of withoutPgnComments(pgn).matchAll(/^\s*\[\s*FEN\s+"((?:\\.|[^"\\])*)"\s*\]\s*$/gim)) {
136
+ assertSafeFenCounters(match[1] ?? "");
137
+ }
138
+ }
139
+ function validateResultForPosition(chess, result) {
140
+ if (result === undefined)
141
+ return;
142
+ if (chess.isCheckmate()) {
143
+ const expected = chess.turn() === "w" ? "0-1" : "1-0";
144
+ if (result === expected)
145
+ return;
146
+ throw new ChessError("INVALID_PGN", `checkmate result must be ${expected}`);
147
+ }
148
+ if ((chess.isStalemate() || chess.isInsufficientMaterial()) &&
149
+ (result === "1-0" || result === "0-1")) {
150
+ throw new ChessError("INVALID_PGN", "a drawn position cannot have a decisive result");
151
+ }
152
+ }
153
+ export function pgnOf(chess) {
154
+ const result = chess.isCheckmate()
155
+ ? (chess.turn() === "w" ? "0-1" : "1-0")
156
+ : chess.isDraw() && (!chess.getHeaders().Result || chess.getHeaders().Result === "*")
157
+ ? "1/2-1/2"
158
+ : undefined;
159
+ if (result === undefined)
160
+ return chess.pgn();
161
+ const snapshot = snapshotChess(chess);
162
+ snapshot.setHeader("Result", result);
163
+ return snapshot.pgn();
164
+ }
30
165
  export function parseImportedPgn(pgn) {
31
166
  if (Buffer.byteLength(pgn, "utf8") > MAX_PGN_BYTES) {
32
167
  throw new ChessError("PGN_TOO_LARGE", `PGN exceeds the ${MAX_PGN_BYTES}-byte limit`);
33
168
  }
169
+ validatePgnFenCounters(pgn);
170
+ const result = declaredPgnResult(pgn);
34
171
  let chess;
35
172
  try {
36
173
  chess = new Chess();
@@ -39,9 +176,11 @@ export function parseImportedPgn(pgn) {
39
176
  catch {
40
177
  throw new ChessError("INVALID_PGN", "invalid or illegal PGN");
41
178
  }
179
+ assertSafeFenCounters(chess.fen());
42
180
  if (chess.history().length > MAX_PGN_PLIES) {
43
181
  throw new ChessError("PGN_TOO_MANY_MOVES", `PGN exceeds the ${MAX_PGN_PLIES}-ply limit`);
44
182
  }
183
+ validateResultForPosition(chess, result);
45
184
  return chess;
46
185
  }
47
186
  export function stateOf(chess, revision) {
@@ -71,8 +210,10 @@ export function stateOf(chess, revision) {
71
210
  }
72
211
  export function parseMove(chess, move) {
73
212
  const legal = chess.moves({ verbose: true });
74
- const san = move.replace(/[+#]$/, "");
75
- const found = legal.find((candidate) => candidate.san.replace(/[+#]$/, "") === san) ??
213
+ const found = legal.find((candidate) => candidate.san === move) ??
214
+ (!/[+#]$/.test(move)
215
+ ? legal.find((candidate) => candidate.san.replace(/[+#]$/, "") === move)
216
+ : undefined) ??
76
217
  legal.find((candidate) => candidate.lan === move);
77
218
  if (!found)
78
219
  throw new ChessError("ILLEGAL_MOVE", `illegal move: ${move}`);
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type TransportKind = "stdio" | "http";
2
+ export type CliOptions = {
3
+ transport: TransportKind;
4
+ host: string;
5
+ port: number;
6
+ path: string;
7
+ allowedHosts: string[];
8
+ help: boolean;
9
+ };
10
+ export declare const HELP = "Usage: llm-chess-mcp [options]\n\nOptions:\n --transport <stdio|http> Transport to use (default: stdio)\n --http Shortcut for --transport http\n --host <host> HTTP bind host (default: 127.0.0.1)\n --port <port> HTTP listen port (default: 3000)\n --path <path> HTTP endpoint path (default: /mcp)\n --allowed-host <host> Allowed HTTP Host/Origin hostname (repeatable)\n -h, --help Show this help\n";
11
+ export declare function parseCli(args: string[]): CliOptions;
package/dist/cli.js CHANGED
@@ -20,6 +20,20 @@ function splitOption(arg) {
20
20
  const index = arg.indexOf("=");
21
21
  return index === -1 ? null : [arg.slice(0, index), arg.slice(index + 1)];
22
22
  }
23
+ function isCanonicalHttpPath(path) {
24
+ if (!path.startsWith("/") ||
25
+ path.startsWith("//") ||
26
+ path.includes("?") ||
27
+ path.includes("#")) {
28
+ return false;
29
+ }
30
+ try {
31
+ return new URL(path, "http://localhost").pathname === path;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
23
37
  export function parseCli(args) {
24
38
  let transport = "stdio";
25
39
  let host = "127.0.0.1";
@@ -89,11 +103,11 @@ export function parseCli(args) {
89
103
  if (!Number.isInteger(port) || port < 1 || port > 65_535) {
90
104
  throw new Error("--port must be between 1 and 65535");
91
105
  }
92
- if (!path.startsWith("/") || path.includes("?") || path.includes("#")) {
106
+ if (!isCanonicalHttpPath(path)) {
93
107
  throw new Error("--path must be an absolute URL path without query or fragment");
94
108
  }
95
- if (!host || allowedHosts.some((value) => !value)) {
96
- throw new Error("HTTP hostnames must not be empty");
109
+ if (!host || allowedHosts.some((value) => !value || /[/?#]/.test(value))) {
110
+ throw new Error("HTTP hostnames must be non-empty hostnames");
97
111
  }
98
112
  if (transport === "stdio" && hasHttpOption) {
99
113
  throw new Error("HTTP options require --transport http");
@@ -0,0 +1,50 @@
1
+ import type { SfLine } from "../types.js";
2
+ export declare const STOCKFISH_FLAVORS: readonly ["full", "lite", "single", "lite-single", "single-lite", "asm"];
3
+ export type StockfishFlavor = (typeof STOCKFISH_FLAVORS)[number];
4
+ export type StockfishEngine = {
5
+ listener: ((line: string) => void) | null;
6
+ sendCommand: (cmd: string) => void;
7
+ terminate: () => void;
8
+ };
9
+ export type StockfishInit = (enginePath: string, cb: (err: Error | null, engine: StockfishEngine) => void) => StockfishEngine;
10
+ type Timeouts = {
11
+ init: number;
12
+ handshake: number;
13
+ analyze: number;
14
+ stopGrace: number;
15
+ };
16
+ export type StockfishOptions = {
17
+ init?: StockfishInit;
18
+ flavor?: string;
19
+ maxQueue?: number;
20
+ timeouts?: Partial<Timeouts>;
21
+ };
22
+ export declare function resolveStockfishFlavor(value?: string): StockfishFlavor;
23
+ export declare class Stockfish {
24
+ private session;
25
+ private queue;
26
+ private queueRunning;
27
+ private queueScheduled;
28
+ private queued;
29
+ private quitGeneration;
30
+ private readonly terminated;
31
+ private readonly initEngine;
32
+ private readonly configuredFlavor;
33
+ private readonly maxQueue;
34
+ private readonly timeouts;
35
+ constructor(options?: StockfishOptions);
36
+ private init;
37
+ private handshake;
38
+ private release;
39
+ private scheduleQueue;
40
+ private removeQueued;
41
+ private enqueue;
42
+ analyze(fen: string, depth: number, multipv: number, signal?: AbortSignal): Promise<SfLine[]>;
43
+ private doAnalyze;
44
+ private failSession;
45
+ private invalidateSession;
46
+ private terminate;
47
+ quit(): Promise<void>;
48
+ }
49
+ export declare const stockfish: Stockfish;
50
+ export {};
@@ -48,7 +48,9 @@ function parseWdl(line) {
48
48
  }
49
49
  export class Stockfish {
50
50
  session = null;
51
- queue = Promise.resolve();
51
+ queue = [];
52
+ queueRunning = false;
53
+ queueScheduled = false;
52
54
  queued = 0;
53
55
  quitGeneration = 0;
54
56
  terminated = new WeakSet();
@@ -191,14 +193,44 @@ export class Stockfish {
191
193
  }
192
194
  request.abortListener = null;
193
195
  }
196
+ scheduleQueue() {
197
+ if (this.queueRunning || this.queueScheduled)
198
+ return;
199
+ this.queueScheduled = true;
200
+ queueMicrotask(() => {
201
+ this.queueScheduled = false;
202
+ if (this.queueRunning)
203
+ return;
204
+ const next = this.queue.shift();
205
+ if (!next)
206
+ return;
207
+ if (next.request.cancelled) {
208
+ this.release(next.request);
209
+ this.scheduleQueue();
210
+ return;
211
+ }
212
+ this.queueRunning = true;
213
+ void next.run().finally(() => {
214
+ this.release(next.request);
215
+ this.queueRunning = false;
216
+ this.scheduleQueue();
217
+ });
218
+ });
219
+ }
220
+ removeQueued(request) {
221
+ const index = this.queue.findIndex((item) => item.request === request);
222
+ if (index < 0)
223
+ return false;
224
+ this.queue.splice(index, 1);
225
+ return true;
226
+ }
194
227
  enqueue(request, fn) {
195
228
  if (this.queued >= this.maxQueue) {
196
229
  return false;
197
230
  }
198
231
  this.queued++;
199
- const run = this.queue.then(fn);
200
- this.queue = run.then(() => { }, () => { });
201
- void run.finally(() => this.release(request));
232
+ this.queue.push({ request, run: fn });
233
+ this.scheduleQueue();
202
234
  return true;
203
235
  }
204
236
  analyze(fen, depth, multipv, signal) {
@@ -229,11 +261,18 @@ export class Stockfish {
229
261
  request.cancelled = true;
230
262
  request.cancellation = error;
231
263
  if (request.started) {
232
- request.stop?.(error);
264
+ if (request.stop)
265
+ request.stop(error);
266
+ else {
267
+ request.reject(error);
268
+ this.release(request);
269
+ }
233
270
  }
234
271
  else {
272
+ this.removeQueued(request);
235
273
  request.reject(error);
236
274
  this.release(request);
275
+ this.scheduleQueue();
237
276
  }
238
277
  };
239
278
  request.abortListener = cancel;
@@ -288,6 +327,7 @@ export class Stockfish {
288
327
  const byPv = new Map();
289
328
  let settled = false;
290
329
  let cancellation = null;
330
+ let timeout = null;
291
331
  let stopSent = false;
292
332
  let stopTimer = null;
293
333
  let failTimer = null;
@@ -323,6 +363,8 @@ export class Stockfish {
323
363
  const stop = (error, cancelled) => {
324
364
  if (cancelled)
325
365
  cancellation ??= error;
366
+ else
367
+ timeout ??= error;
326
368
  if (stopSent)
327
369
  return;
328
370
  stopSent = true;
@@ -363,6 +405,8 @@ export class Stockfish {
363
405
  else if (line.startsWith("bestmove")) {
364
406
  if (cancellation)
365
407
  fail(cancellation, false);
408
+ else if (timeout)
409
+ fail(timeout, false);
366
410
  else
367
411
  succeed();
368
412
  }
package/dist/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function loadEnv(path?: string): void;
@@ -0,0 +1,5 @@
1
+ export declare class ChessError extends Error {
2
+ code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export declare function fail(code: string, message: string): never;
package/dist/eval.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { SfLine } from "./types.js";
2
+ export type Eval = {
3
+ type: "cp";
4
+ value: number;
5
+ } | {
6
+ type: "mate";
7
+ plies: number;
8
+ };
9
+ export declare function toEval(line: SfLine): Eval | null;
10
+ export declare function evalToCp(e: Eval): number;
11
+ export declare function negateEval(e: Eval): Eval;
12
+ export declare const CLASSIFICATION: {
13
+ readonly best: 0;
14
+ readonly excellent: 30;
15
+ readonly good: 80;
16
+ readonly inaccuracy: 150;
17
+ readonly mistake: 300;
18
+ };
19
+ export declare function classifyCpLoss(cpLoss: number): string;
20
+ export type AnalysisLevel = "fast" | "normal" | "deep";
21
+ export declare const ANALYSIS_PRESETS: Record<AnalysisLevel, {
22
+ depth: number;
23
+ multipv: number;
24
+ }>;
@@ -0,0 +1,64 @@
1
+ import { Chess } from "chess.js";
2
+ import { z } from "zod/v4";
3
+ import type { LichessMove } from "./types.js";
4
+ export declare const LICHESS_SPEEDS: readonly ["ultraBullet", "bullet", "blitz", "rapid", "classical", "correspondence"];
5
+ export declare const LICHESS_RATINGS: readonly [0, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500];
6
+ export type LichessSpeed = (typeof LICHESS_SPEEDS)[number];
7
+ export type LichessRating = (typeof LICHESS_RATINGS)[number];
8
+ export declare const lichessSpeedSchema: z.ZodEnum<{
9
+ blitz: "blitz";
10
+ bullet: "bullet";
11
+ classical: "classical";
12
+ correspondence: "correspondence";
13
+ rapid: "rapid";
14
+ ultraBullet: "ultraBullet";
15
+ }>;
16
+ export declare const lichessRatingSchema: z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1000>, z.ZodLiteral<1200>, z.ZodLiteral<1400>, z.ZodLiteral<1600>, z.ZodLiteral<1800>, z.ZodLiteral<2000>, z.ZodLiteral<2200>, z.ZodLiteral<2500>]>;
17
+ export declare const EXPLORER_ATTEMPT_TIMEOUT_MS = 5000;
18
+ export declare const EXPLORER_MAX_ATTEMPTS = 2;
19
+ export declare const EXPLORER_DEFAULT_RETRY_DELAY_MS = 250;
20
+ export declare const EXPLORER_TOTAL_TIMEOUT_MS = 12000;
21
+ export declare const EXPLORER_RATE_LIMIT_COOLDOWN_MS = 60000;
22
+ export declare const EXPLORER_ERROR_KINDS: readonly ["disabled", "invalid_input", "timeout", "network", "auth", "rate_limited", "upstream", "http", "invalid_response"];
23
+ export type ExplorerErrorKind = (typeof EXPLORER_ERROR_KINDS)[number];
24
+ export declare class ExplorerError extends Error {
25
+ readonly kind: ExplorerErrorKind;
26
+ readonly status?: number | undefined;
27
+ readonly reason: ExplorerErrorKind;
28
+ constructor(kind: ExplorerErrorKind, message: string, status?: number | undefined);
29
+ }
30
+ export interface ExplorerResult {
31
+ db: string;
32
+ white: number;
33
+ draws: number;
34
+ black: number;
35
+ moves: LichessMove[];
36
+ opening: {
37
+ eco: string;
38
+ name: string;
39
+ } | null;
40
+ }
41
+ export type ExplorerFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
42
+ export interface ExplorerRequestOptions {
43
+ fetch?: ExplorerFetch;
44
+ limiter?: ExplorerLimiter;
45
+ sleep?: (ms: number) => Promise<void>;
46
+ timeout?: (ms: number) => AbortSignal;
47
+ signal?: AbortSignal;
48
+ now?: () => number;
49
+ token?: string;
50
+ }
51
+ export declare function explorerEnabled(): boolean;
52
+ export interface ExplorerLimiterOptions {
53
+ callerSignal: AbortSignal | undefined;
54
+ deadline: number;
55
+ now: () => number;
56
+ sleep: (ms: number) => Promise<void>;
57
+ }
58
+ export interface ExplorerLimiter {
59
+ readonly pending: number;
60
+ run<T>(options: ExplorerLimiterOptions, request: () => Promise<T>): Promise<T>;
61
+ cooldown(ms: number, now: number): void;
62
+ }
63
+ export declare function createExplorerLimiter(): ExplorerLimiter;
64
+ export declare function openingExplorer(chess: Chess, db: "lichess" | "masters", speeds: readonly string[], ratings: readonly number[], options?: ExplorerRequestOptions): Promise<ExplorerResult>;