@helm-protocol/ttt-mcp 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,192 +7,234 @@ var import_http = require("http");
7
7
  var import_zod = require("zod");
8
8
  var import_tools = require("./tools");
9
9
  var import_auth = require("./auth");
10
- const IS_STDIO = !process.env.PORT;
11
- const server = new import_mcp.McpServer({
12
- name: "ttt-mcp",
13
- version: "0.1.0"
14
- });
15
- server.tool(
16
- "pot_generate",
17
- "Generate a Proof of Time for a transaction. Returns potHash, timestamp, stratum, and GRG integrity shards.",
18
- {
19
- txHash: import_zod.z.string().describe("Transaction hash (hex with 0x prefix)"),
20
- chainId: import_zod.z.number().describe("Chain ID (e.g. 8453 for Base, 84532 for Base Sepolia)"),
21
- poolAddress: import_zod.z.string().describe("DEX pool contract address")
22
- },
23
- async (args) => {
24
- if (IS_STDIO) {
25
- const apiKey = (0, import_auth.resolveApiKey)();
26
- const rl = (0, import_auth.checkRateLimit)(apiKey, "stdio");
27
- if (!rl.allowed) {
28
- return {
29
- content: [{ type: "text", text: JSON.stringify({
30
- error: "rate_limit_exceeded",
31
- message: `Free tier: ${import_auth.FREE_TIER_LIMIT} calls/day. Set TTT_API_KEY env var for unlimited access. \u2192 kenosian.com/pricing`,
32
- remaining: 0,
33
- tier: "free"
34
- }) }],
35
- isError: true
36
- };
10
+ var import_server = require("./server");
11
+ async function restoreDAGFromRedis() {
12
+ try {
13
+ await Promise.race([
14
+ import_tools.redis.connect(),
15
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
16
+ ]);
17
+ } catch {
18
+ return 0;
19
+ }
20
+ let cursor = "0";
21
+ let restored = 0;
22
+ const entries = [];
23
+ try {
24
+ do {
25
+ const [next, keys] = await import_tools.redis.scan(cursor, "MATCH", "dag:*", "COUNT", "200");
26
+ cursor = next;
27
+ if (keys.length === 0) continue;
28
+ const values = await import_tools.redis.mget(...keys);
29
+ for (const v of values) {
30
+ if (!v) continue;
31
+ try {
32
+ const entry = JSON.parse(v);
33
+ if (entry.eventId) entries.push(entry);
34
+ } catch {
35
+ }
37
36
  }
38
- }
37
+ } while (cursor !== "0");
38
+ } catch {
39
+ return 0;
40
+ }
41
+ entries.sort((a, b) => a.createdAt - b.createdAt);
42
+ for (const e of entries) {
39
43
  try {
40
- const result = await (0, import_tools.potGenerate)(args);
41
- return {
42
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
43
- };
44
- } catch (err) {
45
- const message = err instanceof Error ? err.message : String(err);
44
+ (0, import_tools.restoreDagEntry)({
45
+ eventId: e.eventId,
46
+ prevEventId: e.prevEventId,
47
+ potHash: e.potHash,
48
+ timestamp: e.timestamp,
49
+ stratum: e.stratum,
50
+ mode: e.mode,
51
+ createdAt: e.createdAt,
52
+ chainId: e.chainId,
53
+ poolAddress: e.poolAddress
54
+ });
55
+ restored++;
56
+ } catch {
57
+ }
58
+ }
59
+ if (restored > 0) {
60
+ console.error(`[ttt-mcp] DAG restored from Redis: ${restored} entries`);
61
+ }
62
+ return restored;
63
+ }
64
+ function toolError(err) {
65
+ if (err instanceof import_server.QuotaExceededError) {
66
+ return {
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: JSON.stringify(
71
+ { error: "quota_exceeded", tier: err.tier, message: err.message, upgradeUrl: err.upgradeUrl },
72
+ null,
73
+ 2
74
+ )
75
+ }
76
+ ],
77
+ isError: true
78
+ };
79
+ }
80
+ return {
81
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
82
+ isError: true
83
+ };
84
+ }
85
+ function toolSuccess(result) {
86
+ const seal = (0, import_tools.tttsFreshnessSeal)();
87
+ if (seal && result !== null && typeof result === "object") {
88
+ const r = result;
89
+ const notice = r._quotaNotice;
90
+ if (notice) {
91
+ const { _quotaNotice: _, ...rest } = r;
46
92
  return {
47
- content: [{ type: "text", text: `Error: ${message}` }],
48
- isError: true
93
+ content: [
94
+ { type: "text", text: JSON.stringify({ ...rest, _tttps_freshness: seal }, null, 2) },
95
+ { type: "text", text: `\u26A0 Quota notice: ${notice}` }
96
+ ]
49
97
  };
50
98
  }
99
+ return { content: [{ type: "text", text: JSON.stringify({ ...result, _tttps_freshness: seal }, null, 2) }] };
51
100
  }
52
- );
53
- server.tool(
54
- "pot_verify",
55
- "Verify a Proof of Time using its hash and GRG shards. Returns validity, mode (turbo/full), and timestamp.",
56
- {
57
- potHash: import_zod.z.string().describe("PoT hash to verify (hex with 0x prefix)"),
58
- grgShards: import_zod.z.array(import_zod.z.string()).describe("Array of hex-encoded GRG integrity shards"),
59
- chainId: import_zod.z.number().describe("EVM chain ID (e.g. 84532 for Base Sepolia)"),
60
- poolAddress: import_zod.z.string().describe("Uniswap V4 pool address (0x-prefixed)")
61
- },
62
- async (args) => {
63
- if (IS_STDIO) {
64
- const apiKey = (0, import_auth.resolveApiKey)();
65
- const rl = (0, import_auth.checkRateLimit)(apiKey, "stdio");
66
- if (!rl.allowed) {
67
- return {
68
- content: [{ type: "text", text: JSON.stringify({
69
- error: "rate_limit_exceeded",
70
- message: `Free tier: ${import_auth.FREE_TIER_LIMIT} calls/day. Set TTT_API_KEY env var for unlimited access. \u2192 kenosian.com/pricing`,
71
- remaining: 0,
72
- tier: "free"
73
- }) }],
74
- isError: true
75
- };
101
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
102
+ }
103
+ function buildMcpServer() {
104
+ const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.3.1" });
105
+ s.tool(
106
+ "pot_generate",
107
+ "Generate a cryptographic Proof of Time timestamp (draft-helmprotocol-tttps, https://datatracker.ietf.org/doc/draft-helmprotocol-tttps/). For Claude Code workflows: use eventId + prevEventId to build a causal chain. For DeFi: use txHash + chainId + poolAddress. Either eventId or txHash is required.",
108
+ {
109
+ eventId: import_zod.z.string().optional().describe("Workflow step identifier (Claude Code). E.g. 'refactor_auth_step1'"),
110
+ prevEventId: import_zod.z.string().optional().describe("Previous step's eventId \u2014 links steps into a causal chain"),
111
+ txHash: import_zod.z.string().optional().describe("Transaction hash (DeFi, hex with 0x prefix)"),
112
+ chainId: import_zod.z.number().optional().describe("EVM chain ID (DeFi, e.g. 8453 for Base)"),
113
+ poolAddress: import_zod.z.string().optional().describe("DEX pool contract address (DeFi)")
114
+ },
115
+ { title: "Generate Proof of Time", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
116
+ async (args) => {
117
+ try {
118
+ const result = await (0, import_tools.potGenerate)(args);
119
+ return toolSuccess(result);
120
+ } catch (err) {
121
+ return toolError(err);
76
122
  }
77
123
  }
78
- try {
79
- const result = await (0, import_tools.potVerify)(args);
80
- return {
81
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
82
- };
83
- } catch (err) {
84
- const message = err instanceof Error ? err.message : String(err);
85
- return {
86
- content: [{ type: "text", text: `Error: ${message}` }],
87
- isError: true
88
- };
124
+ );
125
+ s.tool(
126
+ "pot_verify",
127
+ "Verify a Proof of Time using its hash and integrity shards. Returns validity, mode (turbo/full), and timestamp.",
128
+ {
129
+ potHash: import_zod.z.string().describe("PoT hash to verify (hex with 0x prefix)"),
130
+ grgShards: import_zod.z.array(import_zod.z.string()).describe("Array of hex-encoded cryptographic integrity shards"),
131
+ chainId: import_zod.z.number().describe("EVM chain ID (e.g. 84532 for Base Sepolia)"),
132
+ poolAddress: import_zod.z.string().describe("Uniswap V4 pool address (0x-prefixed)")
133
+ },
134
+ { title: "Verify Proof of Time", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
135
+ async (args) => {
136
+ try {
137
+ const result = await (0, import_tools.potVerify)(args);
138
+ return toolSuccess(result);
139
+ } catch (err) {
140
+ return toolError(err);
141
+ }
89
142
  }
90
- }
91
- );
92
- server.tool(
93
- "pot_query",
94
- "Query Proof of Time history from local log and on-chain subgraph.",
95
- {
96
- startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
97
- endTime: import_zod.z.number().optional().describe("End time (unix ms). Default: now"),
98
- limit: import_zod.z.number().optional().describe("Max entries to return. Default: 100, max: 1000")
99
- },
100
- async (args) => {
101
- if (IS_STDIO) {
102
- const apiKey = (0, import_auth.resolveApiKey)();
103
- const rl = (0, import_auth.checkRateLimit)(apiKey, "stdio");
104
- if (!rl.allowed) {
105
- return {
106
- content: [{ type: "text", text: JSON.stringify({
107
- error: "rate_limit_exceeded",
108
- message: `Free tier: ${import_auth.FREE_TIER_LIMIT} calls/day. Set TTT_API_KEY env var for unlimited access. \u2192 kenosian.com/pricing`,
109
- remaining: 0,
110
- tier: "free"
111
- }) }],
112
- isError: true
113
- };
143
+ );
144
+ s.tool(
145
+ "pot_query",
146
+ "Query Proof of Time records. Use eventId for exact O(1) lookup of a specific workflow step (collision probability 2^-256). Use startTime/endTime for time-range queries.",
147
+ {
148
+ eventId: import_zod.z.string().optional().describe("Exact eventId lookup \u2014 call this at workflow start to restore action history after context compression"),
149
+ startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
150
+ endTime: import_zod.z.number().optional().describe("End time (unix ms). Default: now"),
151
+ limit: import_zod.z.number().optional().describe("Max entries to return. Default: 100, max: 1000")
152
+ },
153
+ { title: "Query Proof of Time Records", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
154
+ async (args) => {
155
+ try {
156
+ const result = await (0, import_tools.potQuery)(args);
157
+ return toolSuccess(result);
158
+ } catch (err) {
159
+ return toolError(err);
114
160
  }
115
161
  }
116
- try {
117
- const result = await (0, import_tools.potQuery)(args);
118
- return {
119
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
120
- };
121
- } catch (err) {
122
- const message = err instanceof Error ? err.message : String(err);
123
- return {
124
- content: [{ type: "text", text: `Error: ${message}` }],
125
- isError: true
126
- };
162
+ );
163
+ s.tool(
164
+ "pot_graph",
165
+ "Traverse the causal chain of workflow steps. Given an eventId, returns the full backward chain (ancestors via prevEventId) and forward chain (steps that follow). Use after context compression to reconstruct the complete workflow timeline.",
166
+ {
167
+ eventId: import_zod.z.string().describe("The workflow step to start traversal from"),
168
+ depth: import_zod.z.number().optional().describe("Max backward traversal depth. Default: 10, max: 100")
169
+ },
170
+ { title: "Traverse PoT Causal Chain", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
171
+ async (args) => {
172
+ try {
173
+ const result = await (0, import_tools.potGraph)(args);
174
+ return toolSuccess(result);
175
+ } catch (err) {
176
+ return toolError(err);
177
+ }
127
178
  }
128
- }
129
- );
130
- server.tool(
131
- "pot_stats",
132
- "Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
133
- {
134
- period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics")
135
- },
136
- async (args) => {
137
- if (IS_STDIO) {
138
- const apiKey = (0, import_auth.resolveApiKey)();
139
- const rl = (0, import_auth.checkRateLimit)(apiKey, "stdio");
140
- if (!rl.allowed) {
141
- return {
142
- content: [{ type: "text", text: JSON.stringify({
143
- error: "rate_limit_exceeded",
144
- message: `Free tier: ${import_auth.FREE_TIER_LIMIT} calls/day. Set TTT_API_KEY env var for unlimited access. \u2192 kenosian.com/pricing`,
145
- remaining: 0,
146
- tier: "free"
147
- }) }],
148
- isError: true
149
- };
179
+ );
180
+ s.tool(
181
+ "pot_stats",
182
+ "Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
183
+ { period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics") },
184
+ { title: "PoT Statistics", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
185
+ async (args) => {
186
+ try {
187
+ const result = await (0, import_tools.potStats)(args);
188
+ return toolSuccess(result);
189
+ } catch (err) {
190
+ return toolError(err);
150
191
  }
151
192
  }
152
- try {
153
- const result = await (0, import_tools.potStats)(args);
154
- return {
155
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
156
- };
157
- } catch (err) {
158
- const message = err instanceof Error ? err.message : String(err);
159
- return {
160
- content: [{ type: "text", text: `Error: ${message}` }],
161
- isError: true
162
- };
193
+ );
194
+ s.tool(
195
+ "pot_health",
196
+ "Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
197
+ {},
198
+ { title: "PoT System Health", readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
199
+ async () => {
200
+ try {
201
+ const result = await (0, import_tools.potHealth)();
202
+ return toolSuccess(result);
203
+ } catch (err) {
204
+ return toolError(err);
205
+ }
163
206
  }
164
- }
165
- );
166
- server.tool(
167
- "pot_health",
168
- "Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
169
- {},
170
- async () => {
171
- try {
172
- const result = await (0, import_tools.potHealth)();
173
- return {
174
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
175
- };
176
- } catch (err) {
177
- const message = err instanceof Error ? err.message : String(err);
178
- return {
179
- content: [{ type: "text", text: `Error: ${message}` }],
180
- isError: true
181
- };
207
+ );
208
+ s.tool(
209
+ "pot_checkpoint",
210
+ "Create a compressed rollup checkpoint of workflow history. Call this periodically to prevent token explosion when recovering from context compression. Returns checkpointId, compressed event history, chainIntact status, and nextCheckpointHint (recommended events before next checkpoint).",
211
+ {
212
+ fromEventId: import_zod.z.string().optional().describe("Start of range by eventId (optional, use with toEventId)"),
213
+ toEventId: import_zod.z.string().optional().describe("End of range by eventId (optional, use with fromEventId)"),
214
+ startTime: import_zod.z.number().optional().describe("Unix ms start time (optional, default: 1h ago)"),
215
+ endTime: import_zod.z.number().optional().describe("Unix ms end time (optional, default: now)"),
216
+ maxTokens: import_zod.z.number().optional().describe("Approximate max tokens for rollup (default: 2000)")
217
+ },
218
+ { title: "Create PoT Checkpoint", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
219
+ async (args) => {
220
+ try {
221
+ const result = await (0, import_tools.potCheckpoint)(args);
222
+ return toolSuccess(result);
223
+ } catch (err) {
224
+ return toolError(err);
225
+ }
182
226
  }
183
- }
184
- );
227
+ );
228
+ return s;
229
+ }
185
230
  async function main() {
231
+ await restoreDAGFromRedis();
186
232
  const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
187
233
  if (port) {
188
- const transport = new import_streamableHttp.StreamableHTTPServerTransport({
189
- sessionIdGenerator: void 0
190
- });
191
- await server.connect(transport);
192
234
  const httpServer = (0, import_http.createServer)(async (req, res) => {
193
235
  if (req.method === "GET" && (req.url === "/health" || req.url === "/ping")) {
194
236
  res.writeHead(200, { "Content-Type": "application/json" });
195
- res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.2.0" }));
237
+ res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.3.1" }));
196
238
  return;
197
239
  }
198
240
  if (req.method === "POST") {
@@ -209,7 +251,8 @@ async function main() {
209
251
  res.end(
210
252
  JSON.stringify({
211
253
  error: "rate_limit_exceeded",
212
- message: "Free tier limit reached (100 calls/day). Add X-API-Key header for unlimited access.",
254
+ message: import_server.FREE_TIER_UPGRADE_MESSAGE,
255
+ upgradeUrl: import_server.UPGRADE_URL,
213
256
  tier: "free"
214
257
  })
215
258
  );
@@ -233,6 +276,9 @@ async function main() {
233
276
  }
234
277
  }
235
278
  try {
279
+ const reqServer = buildMcpServer();
280
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
281
+ await reqServer.connect(transport);
236
282
  await transport.handleRequest(req, res);
237
283
  } catch (err) {
238
284
  if (!res.headersSent) {
@@ -245,8 +291,9 @@ async function main() {
245
291
  console.error(`[ttt-mcp] OpenTTT MCP Server (HTTP) on port ${port}`);
246
292
  });
247
293
  } else {
294
+ const stdioServer = buildMcpServer();
248
295
  const transport = new import_stdio.StdioServerTransport();
249
- await server.connect(transport);
296
+ await stdioServer.connect(transport);
250
297
  console.error("[ttt-mcp] OpenTTT MCP Server running on stdio");
251
298
  }
252
299
  }
package/dist/server.js ADDED
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var server_exports = {};
20
+ __export(server_exports, {
21
+ FREE_TIER_UPGRADE_MESSAGE: () => FREE_TIER_UPGRADE_MESSAGE,
22
+ QuotaExceededError: () => QuotaExceededError,
23
+ SERVER_BASE_URL: () => SERVER_BASE_URL,
24
+ UPGRADE_MESSAGE: () => UPGRADE_MESSAGE,
25
+ UPGRADE_URL: () => UPGRADE_URL,
26
+ delegateToServer: () => delegateToServer
27
+ });
28
+ module.exports = __toCommonJS(server_exports);
29
+ const SERVER_BASE_URL = (process.env.OPENTTT_SERVER_URL?.trim() || "https://api.kenosian.com").replace(/\/+$/, "");
30
+ const UPGRADE_URL = "https://kenosian.com/products/hydra-mcp.html";
31
+ const UPGRADE_MESSAGE = `Plan quota reached. Upgrade your OpenTTT plan at ${UPGRADE_URL} to continue.`;
32
+ const FREE_TIER_UPGRADE_MESSAGE = `Free tier limit reached (${process.env.FREE_TIER_LIMIT ?? "100"} calls/day). Set TTT_API_KEY with a paid plan, or upgrade at ${UPGRADE_URL}.`;
33
+ class QuotaExceededError extends Error {
34
+ upgradeUrl;
35
+ tier;
36
+ constructor(message, tier) {
37
+ super(message);
38
+ this.name = "QuotaExceededError";
39
+ this.upgradeUrl = UPGRADE_URL;
40
+ this.tier = tier;
41
+ }
42
+ }
43
+ function parseQuotaAdvisory(headers) {
44
+ const remaining = headers.get("x-ratelimit-remaining");
45
+ const limit = headers.get("x-ratelimit-limit");
46
+ const warningHeader = headers.get("x-ratelimit-warning");
47
+ const overage = headers.get("x-ratelimit-overage");
48
+ const tier = headers.get("x-ratelimit-tier");
49
+ const advisory = {};
50
+ let hasContent = false;
51
+ if (tier) {
52
+ advisory.tier = tier;
53
+ hasContent = true;
54
+ }
55
+ if (remaining !== null) {
56
+ advisory.remaining = parseInt(remaining, 10);
57
+ hasContent = true;
58
+ }
59
+ if (limit !== null) {
60
+ advisory.limit = parseInt(limit, 10);
61
+ hasContent = true;
62
+ }
63
+ if (warningHeader) {
64
+ advisory.warning = warningHeader;
65
+ hasContent = true;
66
+ } else if (advisory.remaining !== void 0 && advisory.limit !== void 0 && advisory.limit > 0) {
67
+ const usedRatio = 1 - advisory.remaining / advisory.limit;
68
+ if (usedRatio >= 0.8) {
69
+ advisory.warning = `Approaching plan limit: ${advisory.remaining} of ${advisory.limit} calls remaining this period.`;
70
+ hasContent = true;
71
+ }
72
+ }
73
+ if (overage?.toLowerCase() === "true") {
74
+ advisory.overageActive = true;
75
+ hasContent = true;
76
+ }
77
+ return hasContent ? advisory : void 0;
78
+ }
79
+ function buildUrl(path, query) {
80
+ const url = new URL(SERVER_BASE_URL + path);
81
+ if (query) {
82
+ for (const [k, v] of Object.entries(query)) {
83
+ if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
84
+ }
85
+ }
86
+ return url.toString();
87
+ }
88
+ async function delegateToServer(opts) {
89
+ const { apiKey, method, path, body, query, timeoutMs = 8e3 } = opts;
90
+ const url = buildUrl(path, query);
91
+ const controller = new AbortController();
92
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
93
+ let resp;
94
+ try {
95
+ resp = await fetch(url, {
96
+ method,
97
+ headers: {
98
+ "X-TTT-API-Key": apiKey,
99
+ ...method === "POST" ? { "Content-Type": "application/json" } : {}
100
+ },
101
+ ...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {},
102
+ signal: controller.signal
103
+ });
104
+ } finally {
105
+ clearTimeout(timer);
106
+ }
107
+ if (resp.status === 429) {
108
+ throw new QuotaExceededError(UPGRADE_MESSAGE, "paid");
109
+ }
110
+ if (!resp.ok) {
111
+ let detail = `HTTP ${resp.status}`;
112
+ try {
113
+ const j = await resp.json();
114
+ if (j?.error) detail = j.error;
115
+ } catch {
116
+ }
117
+ throw new Error(`openttt-server error: ${detail}`);
118
+ }
119
+ const advisory = parseQuotaAdvisory(resp.headers);
120
+ let data;
121
+ try {
122
+ data = await resp.json();
123
+ } catch {
124
+ data = {};
125
+ }
126
+ return advisory ? { data, advisory } : { data };
127
+ }
128
+ // Annotate the CommonJS export names for ESM import in node:
129
+ 0 && (module.exports = {
130
+ FREE_TIER_UPGRADE_MESSAGE,
131
+ QuotaExceededError,
132
+ SERVER_BASE_URL,
133
+ UPGRADE_MESSAGE,
134
+ UPGRADE_URL,
135
+ delegateToServer
136
+ });