@ctxprotocol/sdk 0.5.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -17
- package/dist/client/index.cjs +257 -10
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +217 -17
- package/dist/client/index.d.ts +217 -17
- package/dist/client/index.js +257 -11
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +295 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -17
- package/dist/index.d.ts +34 -17
- package/dist/index.js +295 -13
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -64,10 +64,36 @@ yarn add @ctxprotocol/sdk
|
|
|
64
64
|
Before using the API, complete setup at [ctxprotocol.com](https://ctxprotocol.com):
|
|
65
65
|
|
|
66
66
|
1. **Sign in** — Creates your embedded wallet
|
|
67
|
-
2. **
|
|
67
|
+
2. **Set spending cap** — Approve USDC spending on the ContextRouter (one-time setup)
|
|
68
68
|
3. **Fund wallet** — Add USDC for tool execution fees
|
|
69
69
|
4. **Generate API key** — In Settings page
|
|
70
70
|
|
|
71
|
+
## Two Modes: Precision vs Intelligence
|
|
72
|
+
|
|
73
|
+
The SDK offers two payment models to serve different use cases:
|
|
74
|
+
|
|
75
|
+
| Mode | Method | Payment Model | Use Case |
|
|
76
|
+
|------|--------|---------------|----------|
|
|
77
|
+
| **Execute** | `client.tools.execute()` | Pay-per-request | Simple data fetches, predictable costs, building custom pipelines |
|
|
78
|
+
| **Query** | `client.query.run()` | Pay-per-response | Complex questions, multi-tool synthesis, curated intelligence |
|
|
79
|
+
|
|
80
|
+
**Execute mode** gives you raw data and full control — one tool, one call, one payment:
|
|
81
|
+
```typescript
|
|
82
|
+
const result = await client.tools.execute({
|
|
83
|
+
toolId: "tool-uuid",
|
|
84
|
+
toolName: "whale_transactions",
|
|
85
|
+
args: { chain: "base", limit: 20 },
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Query mode** gives you curated answers — the server handles tool discovery, multi-tool orchestration (up to 100 MCP calls per tool), self-healing retries, and AI synthesis for one flat fee:
|
|
90
|
+
```typescript
|
|
91
|
+
const answer = await client.query.run("What are the top whale movements on Base?");
|
|
92
|
+
console.log(answer.response); // AI-synthesized answer
|
|
93
|
+
console.log(answer.toolsUsed); // Which tools were used
|
|
94
|
+
console.log(answer.cost); // Cost breakdown
|
|
95
|
+
```
|
|
96
|
+
|
|
71
97
|
## Quick Start
|
|
72
98
|
|
|
73
99
|
```typescript
|
|
@@ -77,16 +103,17 @@ const client = new ContextClient({
|
|
|
77
103
|
apiKey: "sk_live_...",
|
|
78
104
|
});
|
|
79
105
|
|
|
80
|
-
//
|
|
81
|
-
const
|
|
106
|
+
// Pay-per-response: Ask a question, get a curated answer
|
|
107
|
+
const answer = await client.query.run("What are the top whale movements on Base?");
|
|
108
|
+
console.log(answer.response);
|
|
82
109
|
|
|
83
|
-
// Execute a tool
|
|
110
|
+
// Pay-per-request: Execute a specific tool for raw data
|
|
111
|
+
const tools = await client.discovery.search("gas prices");
|
|
84
112
|
const result = await client.tools.execute({
|
|
85
113
|
toolId: tools[0].id,
|
|
86
114
|
toolName: tools[0].mcpTools[0].name,
|
|
87
115
|
args: { chainId: 1 },
|
|
88
116
|
});
|
|
89
|
-
|
|
90
117
|
console.log(result.result);
|
|
91
118
|
```
|
|
92
119
|
|
|
@@ -265,11 +292,11 @@ Get featured/popular tools.
|
|
|
265
292
|
const featured = await client.discovery.getFeatured(5);
|
|
266
293
|
```
|
|
267
294
|
|
|
268
|
-
### Tools
|
|
295
|
+
### Tools (Pay-Per-Request)
|
|
269
296
|
|
|
270
297
|
#### `client.tools.execute(options)`
|
|
271
298
|
|
|
272
|
-
Execute a tool method.
|
|
299
|
+
Execute a single tool method. One call, one payment, raw result.
|
|
273
300
|
|
|
274
301
|
```typescript
|
|
275
302
|
const result = await client.tools.execute({
|
|
@@ -279,6 +306,48 @@ const result = await client.tools.execute({
|
|
|
279
306
|
});
|
|
280
307
|
```
|
|
281
308
|
|
|
309
|
+
### Query (Pay-Per-Response)
|
|
310
|
+
|
|
311
|
+
#### `client.query.run(options)`
|
|
312
|
+
|
|
313
|
+
Run an agentic query. The server discovers tools, executes the full pipeline (up to 100 MCP calls per tool), and returns an AI-synthesized answer.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Simple string
|
|
317
|
+
const answer = await client.query.run("What are the top whale movements on Base?");
|
|
318
|
+
|
|
319
|
+
// With options
|
|
320
|
+
const answer = await client.query.run({
|
|
321
|
+
query: "Analyze whale activity on Base",
|
|
322
|
+
tools: ["tool-uuid-1", "tool-uuid-2"], // optional — auto-discover if omitted
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
console.log(answer.response); // AI-synthesized text
|
|
326
|
+
console.log(answer.toolsUsed); // [{ id, name, skillCalls }]
|
|
327
|
+
console.log(answer.cost); // { modelCostUsd, toolCostUsd, totalCostUsd }
|
|
328
|
+
console.log(answer.durationMs); // Total time
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### `client.query.stream(options)`
|
|
332
|
+
|
|
333
|
+
Same as `run()` but streams events in real-time via SSE.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
for await (const event of client.query.stream("What are the top whale movements?")) {
|
|
337
|
+
switch (event.type) {
|
|
338
|
+
case "tool-status":
|
|
339
|
+
console.log(`Tool ${event.tool.name}: ${event.status}`);
|
|
340
|
+
break;
|
|
341
|
+
case "text-delta":
|
|
342
|
+
process.stdout.write(event.delta);
|
|
343
|
+
break;
|
|
344
|
+
case "done":
|
|
345
|
+
console.log("\nTotal cost:", event.result.cost.totalCostUsd);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
282
351
|
## Types
|
|
283
352
|
|
|
284
353
|
```typescript
|
|
@@ -296,6 +365,11 @@ import type {
|
|
|
296
365
|
McpTool,
|
|
297
366
|
ExecuteOptions,
|
|
298
367
|
ExecutionResult,
|
|
368
|
+
// Query types (pay-per-response)
|
|
369
|
+
QueryOptions,
|
|
370
|
+
QueryResult,
|
|
371
|
+
QueryCost,
|
|
372
|
+
QueryStreamEvent,
|
|
299
373
|
ContextErrorCode,
|
|
300
374
|
// Auth types (for MCP server contributors)
|
|
301
375
|
VerifyRequestOptions,
|
|
@@ -333,7 +407,7 @@ interface McpTool {
|
|
|
333
407
|
}
|
|
334
408
|
```
|
|
335
409
|
|
|
336
|
-
### ExecutionResult
|
|
410
|
+
### ExecutionResult (Pay-Per-Request)
|
|
337
411
|
|
|
338
412
|
```typescript
|
|
339
413
|
interface ExecutionResult<T = unknown> {
|
|
@@ -343,6 +417,17 @@ interface ExecutionResult<T = unknown> {
|
|
|
343
417
|
}
|
|
344
418
|
```
|
|
345
419
|
|
|
420
|
+
### QueryResult (Pay-Per-Response)
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
interface QueryResult {
|
|
424
|
+
response: string; // AI-synthesized answer
|
|
425
|
+
toolsUsed: QueryToolUsage[]; // Tools used: { id, name, skillCalls }
|
|
426
|
+
cost: QueryCost; // { modelCostUsd, toolCostUsd, totalCostUsd }
|
|
427
|
+
durationMs: number;
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
346
431
|
### Context Requirement Types (MCP Server Contributors)
|
|
347
432
|
|
|
348
433
|
```typescript
|
|
@@ -386,8 +471,8 @@ try {
|
|
|
386
471
|
console.log("Setup required:", error.helpUrl);
|
|
387
472
|
break;
|
|
388
473
|
case "insufficient_allowance":
|
|
389
|
-
// User needs to
|
|
390
|
-
console.log("
|
|
474
|
+
// User needs to set a spending cap
|
|
475
|
+
console.log("Set spending cap:", error.helpUrl);
|
|
391
476
|
break;
|
|
392
477
|
case "payment_failed":
|
|
393
478
|
// Insufficient USDC balance
|
|
@@ -407,7 +492,7 @@ try {
|
|
|
407
492
|
| ------------------------ | ---------------------------------------- | ----------------------------------- |
|
|
408
493
|
| `unauthorized` | Invalid API key | Check configuration |
|
|
409
494
|
| `no_wallet` | Wallet not set up | Direct user to `helpUrl` |
|
|
410
|
-
| `insufficient_allowance` |
|
|
495
|
+
| `insufficient_allowance` | Spending cap not set | Direct user to `helpUrl` |
|
|
411
496
|
| `payment_failed` | USDC payment failed | Check balance |
|
|
412
497
|
| `execution_failed` | Tool error | Feed error to LLM for retry |
|
|
413
498
|
|
|
@@ -426,7 +511,7 @@ By adding 1 line of code to verify a JWT, Context saves you from building:
|
|
|
426
511
|
- Refund and dispute logic
|
|
427
512
|
|
|
428
513
|
**The "Stripe Webhook" Analogy:**
|
|
429
|
-
Developers are used to verifying signatures for Stripe Webhooks or GitHub Apps. Context works the same way. When we send a request saying "Execute Tool (
|
|
514
|
+
Developers are used to verifying signatures for Stripe Webhooks or GitHub Apps. Context works the same way. When we send a request saying "Execute Tool (Authorized)", you verify the signature. Without this, anyone could curl your endpoint and drain your resources.
|
|
430
515
|
|
|
431
516
|
### Quick Implementation (1 Line)
|
|
432
517
|
|
|
@@ -707,12 +792,13 @@ pnpm add -D @types/express
|
|
|
707
792
|
|
|
708
793
|
## Payment Flow
|
|
709
794
|
|
|
710
|
-
|
|
795
|
+
Context uses **deferred settlement** — tools execute first, and payment is settled in the background after the result is returned:
|
|
711
796
|
|
|
712
|
-
1. Your
|
|
713
|
-
2.
|
|
714
|
-
3.
|
|
715
|
-
4.
|
|
797
|
+
1. Your USDC spending cap (ERC-20 allowance on ContextRouter) is verified
|
|
798
|
+
2. Tool executes and returns results
|
|
799
|
+
3. Payment is settled server-side via the operator wallet (no client-side transactions)
|
|
800
|
+
4. **90%** goes to the tool developer, **10%** goes to the protocol
|
|
801
|
+
5. If execution fails, tool fees are **waived** — you only pay for successful results
|
|
716
802
|
|
|
717
803
|
## Documentation
|
|
718
804
|
|
package/dist/client/index.cjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/client/types.ts
|
|
4
|
-
var ContextError = class extends Error {
|
|
4
|
+
var ContextError = class _ContextError extends Error {
|
|
5
5
|
constructor(message, code, statusCode, helpUrl) {
|
|
6
6
|
super(message);
|
|
7
7
|
this.code = code;
|
|
8
8
|
this.statusCode = statusCode;
|
|
9
9
|
this.helpUrl = helpUrl;
|
|
10
10
|
this.name = "ContextError";
|
|
11
|
+
Object.setPrototypeOf(this, _ContextError.prototype);
|
|
11
12
|
}
|
|
12
13
|
};
|
|
13
14
|
|
|
@@ -40,7 +41,7 @@ var Discovery = class {
|
|
|
40
41
|
}
|
|
41
42
|
const queryString = params.toString();
|
|
42
43
|
const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
|
|
43
|
-
const response = await this.client.
|
|
44
|
+
const response = await this.client._fetch(endpoint);
|
|
44
45
|
return response.tools;
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
@@ -74,8 +75,8 @@ var Tools = class {
|
|
|
74
75
|
* @returns The execution result with the tool's output data
|
|
75
76
|
*
|
|
76
77
|
* @throws {ContextError} With code `no_wallet` if wallet not set up
|
|
77
|
-
* @throws {ContextError} With code `insufficient_allowance` if
|
|
78
|
-
* @throws {ContextError} With code `payment_failed` if
|
|
78
|
+
* @throws {ContextError} With code `insufficient_allowance` if spending cap not set
|
|
79
|
+
* @throws {ContextError} With code `payment_failed` if payment settlement fails
|
|
79
80
|
* @throws {ContextError} With code `execution_failed` if tool execution fails
|
|
80
81
|
*
|
|
81
82
|
* @example
|
|
@@ -97,7 +98,7 @@ var Tools = class {
|
|
|
97
98
|
*/
|
|
98
99
|
async execute(options) {
|
|
99
100
|
const { toolId, toolName, args } = options;
|
|
100
|
-
const response = await this.client.
|
|
101
|
+
const response = await this.client._fetch(
|
|
101
102
|
"/api/v1/tools/execute",
|
|
102
103
|
{
|
|
103
104
|
method: "POST",
|
|
@@ -108,7 +109,8 @@ var Tools = class {
|
|
|
108
109
|
throw new ContextError(
|
|
109
110
|
response.error,
|
|
110
111
|
response.code,
|
|
111
|
-
|
|
112
|
+
void 0,
|
|
113
|
+
// Don't hardcode - this was a 200 OK with error body
|
|
112
114
|
response.helpUrl
|
|
113
115
|
);
|
|
114
116
|
}
|
|
@@ -123,18 +125,174 @@ var Tools = class {
|
|
|
123
125
|
}
|
|
124
126
|
};
|
|
125
127
|
|
|
128
|
+
// src/client/resources/query.ts
|
|
129
|
+
var Query = class {
|
|
130
|
+
constructor(client) {
|
|
131
|
+
this.client = client;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run an agentic query and wait for the full response.
|
|
135
|
+
*
|
|
136
|
+
* The server discovers relevant tools (or uses the ones you specify),
|
|
137
|
+
* executes the full agentic pipeline (up to 100 MCP calls per tool),
|
|
138
|
+
* and returns an AI-synthesized answer. Payment is settled after
|
|
139
|
+
* successful execution via deferred settlement.
|
|
140
|
+
*
|
|
141
|
+
* @param options - Query options or a plain string question
|
|
142
|
+
* @returns The complete query result with response text, tools used, and cost
|
|
143
|
+
*
|
|
144
|
+
* @throws {ContextError} With code `no_wallet` if wallet not set up
|
|
145
|
+
* @throws {ContextError} With code `insufficient_allowance` if spending cap not set
|
|
146
|
+
* @throws {ContextError} With code `payment_failed` if payment settlement fails
|
|
147
|
+
* @throws {ContextError} With code `execution_failed` if the agentic pipeline fails
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // Simple question — server discovers tools automatically
|
|
152
|
+
* const answer = await client.query.run("What are the top whale movements on Base?");
|
|
153
|
+
* console.log(answer.response); // AI-synthesized answer
|
|
154
|
+
* console.log(answer.toolsUsed); // Which tools were used
|
|
155
|
+
* console.log(answer.cost); // Cost breakdown
|
|
156
|
+
*
|
|
157
|
+
* // With specific tools (Manual Mode)
|
|
158
|
+
* const answer = await client.query.run({
|
|
159
|
+
* query: "Analyze whale activity",
|
|
160
|
+
* tools: ["tool-uuid-1", "tool-uuid-2"],
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
async run(options) {
|
|
165
|
+
const opts = typeof options === "string" ? { query: options } : options;
|
|
166
|
+
const response = await this.client._fetch(
|
|
167
|
+
"/api/v1/query",
|
|
168
|
+
{
|
|
169
|
+
method: "POST",
|
|
170
|
+
body: JSON.stringify({
|
|
171
|
+
query: opts.query,
|
|
172
|
+
tools: opts.tools,
|
|
173
|
+
stream: false
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
if ("error" in response) {
|
|
178
|
+
throw new ContextError(
|
|
179
|
+
response.error,
|
|
180
|
+
response.code,
|
|
181
|
+
void 0,
|
|
182
|
+
response.helpUrl
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (response.success) {
|
|
186
|
+
return {
|
|
187
|
+
response: response.response,
|
|
188
|
+
toolsUsed: response.toolsUsed,
|
|
189
|
+
cost: response.cost,
|
|
190
|
+
durationMs: response.durationMs
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
throw new ContextError("Unexpected response format from query API");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Run an agentic query with streaming. Returns an async iterable that
|
|
197
|
+
* yields events as the server processes the query in real-time.
|
|
198
|
+
*
|
|
199
|
+
* Event types:
|
|
200
|
+
* - `tool-status` — A tool started executing or changed status
|
|
201
|
+
* - `text-delta` — A chunk of the AI response text
|
|
202
|
+
* - `done` — The full response is complete (includes final `QueryResult`)
|
|
203
|
+
*
|
|
204
|
+
* @param options - Query options or a plain string question
|
|
205
|
+
* @returns An async iterable of stream events
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* for await (const event of client.query.stream("What are the top whale movements?")) {
|
|
210
|
+
* switch (event.type) {
|
|
211
|
+
* case "tool-status":
|
|
212
|
+
* console.log(`Tool ${event.tool.name}: ${event.status}`);
|
|
213
|
+
* break;
|
|
214
|
+
* case "text-delta":
|
|
215
|
+
* process.stdout.write(event.delta);
|
|
216
|
+
* break;
|
|
217
|
+
* case "done":
|
|
218
|
+
* console.log("\nCost:", event.result.cost.totalCostUsd);
|
|
219
|
+
* break;
|
|
220
|
+
* }
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
async *stream(options) {
|
|
225
|
+
const opts = typeof options === "string" ? { query: options } : options;
|
|
226
|
+
const response = await this.client._fetchRaw("/api/v1/query", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
query: opts.query,
|
|
230
|
+
tools: opts.tools,
|
|
231
|
+
stream: true
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
const body = response.body;
|
|
235
|
+
if (!body) {
|
|
236
|
+
throw new ContextError("No response body for streaming query");
|
|
237
|
+
}
|
|
238
|
+
const reader = body.getReader();
|
|
239
|
+
const decoder = new TextDecoder();
|
|
240
|
+
let buffer = "";
|
|
241
|
+
try {
|
|
242
|
+
while (true) {
|
|
243
|
+
const { done, value } = await reader.read();
|
|
244
|
+
if (done) break;
|
|
245
|
+
buffer += decoder.decode(value, { stream: true });
|
|
246
|
+
const lines = buffer.split("\n");
|
|
247
|
+
buffer = lines.pop() ?? "";
|
|
248
|
+
for (const line of lines) {
|
|
249
|
+
const trimmed = line.trim();
|
|
250
|
+
if (trimmed.startsWith("data: ")) {
|
|
251
|
+
const data = trimmed.slice(6);
|
|
252
|
+
if (data === "[DONE]") return;
|
|
253
|
+
try {
|
|
254
|
+
yield JSON.parse(data);
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (buffer.trim().startsWith("data: ")) {
|
|
261
|
+
const data = buffer.trim().slice(6);
|
|
262
|
+
if (data !== "[DONE]") {
|
|
263
|
+
try {
|
|
264
|
+
yield JSON.parse(data);
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
reader.releaseLock();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
126
275
|
// src/client/client.ts
|
|
127
276
|
var ContextClient = class {
|
|
128
277
|
apiKey;
|
|
129
278
|
baseUrl;
|
|
279
|
+
_closed = false;
|
|
130
280
|
/**
|
|
131
281
|
* Discovery resource for searching tools
|
|
132
282
|
*/
|
|
133
283
|
discovery;
|
|
134
284
|
/**
|
|
135
|
-
* Tools resource for executing tools
|
|
285
|
+
* Tools resource for executing tools (pay-per-request)
|
|
136
286
|
*/
|
|
137
287
|
tools;
|
|
288
|
+
/**
|
|
289
|
+
* Query resource for agentic queries (pay-per-response).
|
|
290
|
+
*
|
|
291
|
+
* Unlike `tools.execute()` which calls a single tool once, `query` sends
|
|
292
|
+
* a natural-language question and lets the server handle tool discovery,
|
|
293
|
+
* multi-tool orchestration, self-healing, and AI synthesis — one flat fee.
|
|
294
|
+
*/
|
|
295
|
+
query;
|
|
138
296
|
/**
|
|
139
297
|
* Creates a new Context Protocol client
|
|
140
298
|
*
|
|
@@ -150,14 +308,102 @@ var ContextClient = class {
|
|
|
150
308
|
this.baseUrl = (options.baseUrl ?? "https://ctxprotocol.com").replace(/\/$/, "");
|
|
151
309
|
this.discovery = new Discovery(this);
|
|
152
310
|
this.tools = new Tools(this);
|
|
311
|
+
this.query = new Query(this);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Close the client and clean up resources.
|
|
315
|
+
* After calling close(), any in-flight requests may be aborted.
|
|
316
|
+
*/
|
|
317
|
+
close() {
|
|
318
|
+
this._closed = true;
|
|
153
319
|
}
|
|
154
320
|
/**
|
|
155
321
|
* Internal method for making authenticated HTTP requests
|
|
156
|
-
*
|
|
322
|
+
* Includes timeout (30s) and retry with exponential backoff for transient errors
|
|
157
323
|
*
|
|
158
324
|
* @internal
|
|
159
325
|
*/
|
|
160
|
-
async
|
|
326
|
+
async _fetch(endpoint, options = {}) {
|
|
327
|
+
if (this._closed) {
|
|
328
|
+
throw new ContextError("Client has been closed");
|
|
329
|
+
}
|
|
330
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
331
|
+
const maxRetries = 3;
|
|
332
|
+
const timeoutMs = 3e4;
|
|
333
|
+
let lastError;
|
|
334
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
335
|
+
const controller = new AbortController();
|
|
336
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
337
|
+
try {
|
|
338
|
+
const response = await fetch(url, {
|
|
339
|
+
...options,
|
|
340
|
+
signal: controller.signal,
|
|
341
|
+
headers: {
|
|
342
|
+
"Content-Type": "application/json",
|
|
343
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
344
|
+
...options.headers
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
clearTimeout(timeout);
|
|
348
|
+
if (!response.ok) {
|
|
349
|
+
if (response.status >= 500 && attempt < maxRetries) {
|
|
350
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
351
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
355
|
+
let errorCode;
|
|
356
|
+
let helpUrl;
|
|
357
|
+
try {
|
|
358
|
+
const errorBody = await response.json();
|
|
359
|
+
if (errorBody.error) {
|
|
360
|
+
errorMessage = errorBody.error;
|
|
361
|
+
errorCode = errorBody.code;
|
|
362
|
+
helpUrl = errorBody.helpUrl;
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
367
|
+
}
|
|
368
|
+
return response.json();
|
|
369
|
+
} catch (error) {
|
|
370
|
+
clearTimeout(timeout);
|
|
371
|
+
if (error instanceof ContextError) {
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
375
|
+
const isRetryable = lastError.name === "AbortError" || lastError.message.includes("fetch failed") || lastError.message.includes("ECONNRESET") || lastError.message.includes("ETIMEDOUT");
|
|
376
|
+
if (isRetryable && attempt < maxRetries) {
|
|
377
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
378
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (lastError.name === "AbortError") {
|
|
382
|
+
throw new ContextError(
|
|
383
|
+
`Request timed out after ${timeoutMs / 1e3}s`,
|
|
384
|
+
void 0,
|
|
385
|
+
408
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
throw new ContextError(
|
|
389
|
+
lastError.message,
|
|
390
|
+
void 0,
|
|
391
|
+
void 0
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
throw lastError ?? new ContextError("Request failed after retries");
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Internal method for making authenticated HTTP requests that returns
|
|
399
|
+
* the raw Response object. Used for streaming endpoints (SSE).
|
|
400
|
+
*
|
|
401
|
+
* @internal
|
|
402
|
+
*/
|
|
403
|
+
async _fetchRaw(endpoint, options = {}) {
|
|
404
|
+
if (this._closed) {
|
|
405
|
+
throw new ContextError("Client has been closed");
|
|
406
|
+
}
|
|
161
407
|
const url = `${this.baseUrl}${endpoint}`;
|
|
162
408
|
const response = await fetch(url, {
|
|
163
409
|
...options,
|
|
@@ -182,13 +428,14 @@ var ContextClient = class {
|
|
|
182
428
|
}
|
|
183
429
|
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
184
430
|
}
|
|
185
|
-
return response
|
|
431
|
+
return response;
|
|
186
432
|
}
|
|
187
433
|
};
|
|
188
434
|
|
|
189
435
|
exports.ContextClient = ContextClient;
|
|
190
436
|
exports.ContextError = ContextError;
|
|
191
437
|
exports.Discovery = Discovery;
|
|
438
|
+
exports.Query = Query;
|
|
192
439
|
exports.Tools = Tools;
|
|
193
440
|
//# sourceMappingURL=index.cjs.map
|
|
194
441
|
//# sourceMappingURL=index.cjs.map
|