@ctxprotocol/sdk 0.8.1 → 0.8.3
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 +128 -31
- package/dist/client/index.cjs +166 -32
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +155 -17
- package/dist/client/index.d.ts +155 -17
- package/dist/client/index.js +166 -32
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +166 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +166 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,13 +28,13 @@ We're funding the initial supply of MCP Tools for the Context Marketplace. **Bec
|
|
|
28
28
|
- **🔌 One Interface, Everything:** Stop integrating APIs one by one. Use a single SDK to access any tool in the marketplace.
|
|
29
29
|
- **🧠 Zero-Ops:** We're a gateway to the best MCP tools. Just send the JSON and get the result.
|
|
30
30
|
- **⚡️ Agentic Discovery:** Your Agent can search the marketplace at runtime to find tools it didn't know it needed.
|
|
31
|
-
- **💸
|
|
31
|
+
- **💸 Dual-Surface Economics:** Use Query for pay-per-response intelligence or Execute for session-budgeted method calls.
|
|
32
32
|
|
|
33
33
|
## Who Is This SDK For?
|
|
34
34
|
|
|
35
35
|
| Role | What You Use |
|
|
36
36
|
|------|--------------|
|
|
37
|
-
| **AI Agent Developer** | `@ctxprotocol/sdk` — Query
|
|
37
|
+
| **AI Agent Developer** | `@ctxprotocol/sdk` — Query curated answers or Execute with explicit method pricing + sessions |
|
|
38
38
|
| **Tool Contributor (Data Broker)** | `@modelcontextprotocol/sdk` + `@ctxprotocol/sdk` — Standard MCP server + security middleware |
|
|
39
39
|
|
|
40
40
|
**For AI Agent Developers:** Use this SDK to search the marketplace, execute tools, and handle micro-payments.
|
|
@@ -72,25 +72,36 @@ Before using the API, complete setup at [ctxprotocol.com](https://ctxprotocol.co
|
|
|
72
72
|
|
|
73
73
|
The SDK offers two payment models to serve different use cases:
|
|
74
74
|
|
|
75
|
-
| Mode | Method | Payment Model | Use Case |
|
|
76
|
-
|
|
77
|
-
| **Execute** | `client.tools.execute()` |
|
|
78
|
-
| **Query** | `client.query.run()` | Pay-per-response | Complex questions, multi-tool synthesis, curated intelligence |
|
|
75
|
+
| Mode | Method | Payment Model | Settlement Shape | Use Case |
|
|
76
|
+
|------|--------|---------------|------------------|----------|
|
|
77
|
+
| **Execute** | `client.tools.execute()` | Per execute call | Session accrual + deferred batch flush | Deterministic pipelines, raw outputs, explicit spend envelopes |
|
|
78
|
+
| **Query** | `client.query.run()` | Pay-per-response | Deferred post-response | Complex questions, multi-tool synthesis, curated intelligence |
|
|
79
79
|
|
|
80
|
-
**Execute mode** gives you raw data and full control
|
|
80
|
+
**Execute mode** gives you raw data and full control with explicit method pricing and session budgets:
|
|
81
81
|
```typescript
|
|
82
|
+
const session = await client.tools.startSession({ maxSpendUsd: "2.00" });
|
|
83
|
+
const executeTools = await client.discovery.search({
|
|
84
|
+
query: "whale transactions",
|
|
85
|
+
mode: "execute",
|
|
86
|
+
surface: "execute",
|
|
87
|
+
requireExecutePricing: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
82
90
|
const result = await client.tools.execute({
|
|
83
|
-
toolId:
|
|
84
|
-
toolName:
|
|
91
|
+
toolId: executeTools[0].id,
|
|
92
|
+
toolName: executeTools[0].mcpTools[0].name,
|
|
85
93
|
args: { chain: "base", limit: 20 },
|
|
94
|
+
sessionId: session.session.sessionId ?? undefined,
|
|
86
95
|
});
|
|
96
|
+
console.log(result.session); // methodPrice, spent, remaining, maxSpend, ...
|
|
87
97
|
```
|
|
88
98
|
|
|
89
|
-
**Query mode** gives you curated answers — the server handles tool discovery, multi-tool orchestration (up to 100 MCP calls per
|
|
99
|
+
**Query mode** gives you curated answers — the server handles answer-safe tool discovery, multi-tool orchestration (up to 100 MCP calls per response turn), self-healing retries, completeness checks, model-aware context budgeting, and AI synthesis for one flat fee:
|
|
90
100
|
```typescript
|
|
91
101
|
const answer = await client.query.run({
|
|
92
102
|
query: "What are the top whale movements on Base?",
|
|
93
103
|
modelId: "glm-model", // optional: choose a supported model
|
|
104
|
+
queryDepth: "auto", // optional: fast | auto | deep
|
|
94
105
|
includeDataUrl: true, // optional: persist full execution data to blob
|
|
95
106
|
});
|
|
96
107
|
console.log(answer.response); // AI-synthesized answer
|
|
@@ -99,6 +110,11 @@ console.log(answer.cost); // Cost breakdown
|
|
|
99
110
|
console.log(answer.dataUrl); // Optional blob URL with full data
|
|
100
111
|
```
|
|
101
112
|
|
|
113
|
+
> Mixed listings are first-class: one listing can expose methods to both surfaces. Methods without `_meta.pricing.executeUsd` remain query-only until priced.
|
|
114
|
+
>
|
|
115
|
+
> Compatibility: SDK/API payload fields such as `price` and `pricePerQuery` are retained for backward compatibility. In Query mode, they represent listing-level **price per response turn**.
|
|
116
|
+
> A future major release can add response-named aliases (for example, `pricePerResponse`) before deprecating legacy names.
|
|
117
|
+
|
|
102
118
|
## Quick Start
|
|
103
119
|
|
|
104
120
|
```typescript
|
|
@@ -112,16 +128,25 @@ const client = new ContextClient({
|
|
|
112
128
|
const answer = await client.query.run("What are the top whale movements on Base?");
|
|
113
129
|
console.log(answer.response);
|
|
114
130
|
|
|
115
|
-
//
|
|
116
|
-
const tools = await client.discovery.search(
|
|
131
|
+
// Execute surface: require explicit execute pricing
|
|
132
|
+
const tools = await client.discovery.search({
|
|
133
|
+
query: "gas prices",
|
|
134
|
+
mode: "execute",
|
|
135
|
+
surface: "execute",
|
|
136
|
+
requireExecutePricing: true,
|
|
137
|
+
});
|
|
138
|
+
const session = await client.tools.startSession({ maxSpendUsd: "1.00" });
|
|
117
139
|
const result = await client.tools.execute({
|
|
118
140
|
toolId: tools[0].id,
|
|
119
141
|
toolName: tools[0].mcpTools[0].name,
|
|
120
142
|
args: { chainId: 1 },
|
|
143
|
+
sessionId: session.session.sessionId ?? undefined,
|
|
121
144
|
});
|
|
122
145
|
console.log(result.result);
|
|
123
146
|
```
|
|
124
147
|
|
|
148
|
+
See the runnable dual-surface example in [`examples/client/src/index.ts`](./examples/client/src/index.ts).
|
|
149
|
+
|
|
125
150
|
---
|
|
126
151
|
|
|
127
152
|
## The Agentic Pattern: How to Build Autonomous Bots
|
|
@@ -274,10 +299,12 @@ If you can answer without a tool, just respond normally.`;
|
|
|
274
299
|
|
|
275
300
|
### Client Options
|
|
276
301
|
|
|
277
|
-
| Option
|
|
278
|
-
|
|
|
279
|
-
| `apiKey`
|
|
280
|
-
| `baseUrl`
|
|
302
|
+
| Option | Type | Required | Default | Description |
|
|
303
|
+
| ------------------ | -------- | -------- | ------------------------------ | --------------------------------------------- |
|
|
304
|
+
| `apiKey` | `string` | Yes | — | Your Context Protocol API key |
|
|
305
|
+
| `baseUrl` | `string` | No | `https://www.ctxprotocol.com` | API base URL (for development) |
|
|
306
|
+
| `requestTimeoutMs` | `number` | No | `300000` | Timeout for non-streaming API calls |
|
|
307
|
+
| `streamTimeoutMs` | `number` | No | `600000` | Timeout for establishing streaming API calls |
|
|
281
308
|
|
|
282
309
|
```typescript
|
|
283
310
|
// Production
|
|
@@ -289,6 +316,8 @@ const client = new ContextClient({
|
|
|
289
316
|
const client = new ContextClient({
|
|
290
317
|
apiKey: "sk_test_...",
|
|
291
318
|
baseUrl: "http://localhost:3000",
|
|
319
|
+
requestTimeoutMs: 420_000,
|
|
320
|
+
streamTimeoutMs: 840_000,
|
|
292
321
|
});
|
|
293
322
|
```
|
|
294
323
|
|
|
@@ -297,40 +326,84 @@ const client = new ContextClient({
|
|
|
297
326
|
### Discovery
|
|
298
327
|
|
|
299
328
|
#### `client.discovery.search(query, limit?)`
|
|
329
|
+
#### `client.discovery.search(options)`
|
|
300
330
|
|
|
301
|
-
Search for tools
|
|
331
|
+
Search for tools with optional surface-aware filters.
|
|
302
332
|
|
|
303
333
|
```typescript
|
|
304
334
|
const tools = await client.discovery.search("ethereum gas", 10);
|
|
335
|
+
|
|
336
|
+
const executeTools = await client.discovery.search({
|
|
337
|
+
query: "ethereum gas",
|
|
338
|
+
mode: "execute",
|
|
339
|
+
surface: "execute",
|
|
340
|
+
requireExecutePricing: true,
|
|
341
|
+
});
|
|
305
342
|
```
|
|
306
343
|
|
|
307
|
-
#### `client.discovery.getFeatured(limit?)`
|
|
344
|
+
#### `client.discovery.getFeatured(limit?, options?)`
|
|
308
345
|
|
|
309
346
|
Get featured/popular tools.
|
|
310
347
|
|
|
311
348
|
```typescript
|
|
312
349
|
const featured = await client.discovery.getFeatured(5);
|
|
350
|
+
const featuredExecute = await client.discovery.getFeatured(5, {
|
|
351
|
+
mode: "execute",
|
|
352
|
+
requireExecutePricing: true,
|
|
353
|
+
});
|
|
313
354
|
```
|
|
314
355
|
|
|
315
|
-
### Tools (
|
|
356
|
+
### Tools (Execute Surface)
|
|
357
|
+
|
|
358
|
+
Session lifecycle helpers use the canonical execute-scoped API contract:
|
|
359
|
+
`/api/v1/tools/execute/sessions...`
|
|
316
360
|
|
|
317
361
|
#### `client.tools.execute(options)`
|
|
318
362
|
|
|
319
|
-
Execute a single tool method.
|
|
363
|
+
Execute a single tool method. Execute calls can run inside session budgets.
|
|
320
364
|
|
|
321
365
|
```typescript
|
|
366
|
+
const session = await client.tools.startSession({ maxSpendUsd: "2.50" });
|
|
367
|
+
|
|
322
368
|
const result = await client.tools.execute({
|
|
323
369
|
toolId: "uuid-of-tool",
|
|
324
370
|
toolName: "get_gas_prices",
|
|
325
371
|
args: { chainId: 1 },
|
|
372
|
+
sessionId: session.session.sessionId ?? undefined,
|
|
326
373
|
});
|
|
374
|
+
|
|
375
|
+
console.log(result.method.executePriceUsd);
|
|
376
|
+
console.log(result.session);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### `client.tools.startSession({ maxSpendUsd })`
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const started = await client.tools.startSession({ maxSpendUsd: "5.00" });
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### `client.tools.getSession(sessionId)`
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
const status = await client.tools.getSession("sess_123");
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### `client.tools.closeSession(sessionId)`
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const closed = await client.tools.closeSession("sess_123");
|
|
327
395
|
```
|
|
328
396
|
|
|
329
397
|
### Query (Pay-Per-Response)
|
|
330
398
|
|
|
331
399
|
#### `client.query.run(options)`
|
|
332
400
|
|
|
333
|
-
Run an agentic query. The server discovers tools, executes the full pipeline (up to 100 MCP calls per
|
|
401
|
+
Run an agentic query. The server discovers answer-safe tools, executes the full pipeline (up to 100 MCP calls per response turn), applies model-aware mediator/data budgeting, and returns an AI-synthesized answer.
|
|
402
|
+
|
|
403
|
+
`queryDepth` controls orchestration depth:
|
|
404
|
+
- `fast`: lower-latency path for simple lookups.
|
|
405
|
+
- `auto`: server routes to either `fast` or `deep` from query intent + selected tool complexity.
|
|
406
|
+
- `deep`: completeness-oriented path (default when omitted).
|
|
334
407
|
|
|
335
408
|
```typescript
|
|
336
409
|
// Simple string
|
|
@@ -341,6 +414,7 @@ const answer = await client.query.run({
|
|
|
341
414
|
query: "Analyze whale activity on Base",
|
|
342
415
|
tools: ["tool-uuid-1", "tool-uuid-2"], // optional — auto-discover if omitted
|
|
343
416
|
modelId: "kimi-model-thinking", // optional
|
|
417
|
+
queryDepth: "auto", // optional: fast | auto | deep
|
|
344
418
|
includeData: true, // optional: include execution data inline
|
|
345
419
|
includeDataUrl: true, // optional: include blob URL for full data
|
|
346
420
|
});
|
|
@@ -353,12 +427,17 @@ console.log(answer.data); // Optional execution data (when includeData=t
|
|
|
353
427
|
console.log(answer.dataUrl); // Optional blob URL (when includeDataUrl=true)
|
|
354
428
|
```
|
|
355
429
|
|
|
430
|
+
When retrieval-first synthesis rollout is enabled server-side, full-data or truncation-sensitive query requests can switch to retrieval-first context assembly using private stage artifacts and canonical execution data slices. `includeData` and `includeDataUrl` continue to reference the same canonical dataset used for synthesis.
|
|
431
|
+
|
|
356
432
|
#### `client.query.stream(options)`
|
|
357
433
|
|
|
358
434
|
Same as `run()` but streams events in real-time via SSE.
|
|
359
435
|
|
|
360
436
|
```typescript
|
|
361
|
-
for await (const event of client.query.stream(
|
|
437
|
+
for await (const event of client.query.stream({
|
|
438
|
+
query: "What are the top whale movements?",
|
|
439
|
+
queryDepth: "fast",
|
|
440
|
+
})) {
|
|
362
441
|
switch (event.type) {
|
|
363
442
|
case "tool-status":
|
|
364
443
|
console.log(`Tool ${event.tool.name}: ${event.status}`);
|
|
@@ -414,7 +493,7 @@ interface Tool {
|
|
|
414
493
|
id: string;
|
|
415
494
|
name: string;
|
|
416
495
|
description: string;
|
|
417
|
-
price: string;
|
|
496
|
+
price: string; // listing-level response price metadata (legacy field name)
|
|
418
497
|
category?: string;
|
|
419
498
|
isVerified?: boolean;
|
|
420
499
|
mcpTools?: McpTool[];
|
|
@@ -427,17 +506,35 @@ interface Tool {
|
|
|
427
506
|
interface McpTool {
|
|
428
507
|
name: string;
|
|
429
508
|
description: string;
|
|
430
|
-
inputSchema?: Record<string, unknown>;
|
|
431
|
-
outputSchema?: Record<string, unknown>;
|
|
509
|
+
inputSchema?: Record<string, unknown>; // JSON Schema for arguments
|
|
510
|
+
outputSchema?: Record<string, unknown>; // JSON Schema for response
|
|
511
|
+
_meta?: {
|
|
512
|
+
surface?: "answer" | "execute" | "both";
|
|
513
|
+
queryEligible?: boolean;
|
|
514
|
+
latencyClass?: "instant" | "fast" | "slow" | "streaming";
|
|
515
|
+
pricing?: { executeUsd?: string; queryUsd?: string };
|
|
516
|
+
};
|
|
517
|
+
executeEligible?: boolean;
|
|
518
|
+
executePriceUsd?: string | null;
|
|
432
519
|
}
|
|
433
520
|
```
|
|
434
521
|
|
|
435
|
-
### ExecutionResult (
|
|
522
|
+
### ExecutionResult (Execute Surface)
|
|
436
523
|
|
|
437
524
|
```typescript
|
|
438
525
|
interface ExecutionResult<T = unknown> {
|
|
526
|
+
mode: "execute";
|
|
439
527
|
result: T;
|
|
440
528
|
tool: { id: string; name: string };
|
|
529
|
+
method: { name: string; executePriceUsd: string };
|
|
530
|
+
session: {
|
|
531
|
+
sessionId: string | null;
|
|
532
|
+
methodPrice: string;
|
|
533
|
+
spent: string;
|
|
534
|
+
remaining: string | null;
|
|
535
|
+
maxSpend: string | null;
|
|
536
|
+
status?: "open" | "closed" | "expired";
|
|
537
|
+
};
|
|
441
538
|
durationMs: number;
|
|
442
539
|
}
|
|
443
540
|
```
|
|
@@ -832,13 +929,13 @@ pnpm add -D @types/express
|
|
|
832
929
|
|
|
833
930
|
## Payment Flow
|
|
834
931
|
|
|
835
|
-
Context uses
|
|
932
|
+
Context uses surface-aware deferred settlement:
|
|
836
933
|
|
|
837
|
-
1.
|
|
838
|
-
2.
|
|
839
|
-
3.
|
|
934
|
+
1. **Query surface** settles after each response turn
|
|
935
|
+
2. **Execute surface** accrues per-call spend into execute sessions, then flushes batches
|
|
936
|
+
3. Your USDC spending cap (ERC-20 allowance on ContextRouter) is still the global ceiling
|
|
840
937
|
4. **90%** goes to the tool developer, **10%** goes to the protocol
|
|
841
|
-
5.
|
|
938
|
+
5. Session responses expose `methodPrice`, `spent`, `remaining`, and `maxSpend` on each execute call
|
|
842
939
|
|
|
843
940
|
## Documentation
|
|
844
941
|
|
package/dist/client/index.cjs
CHANGED
|
@@ -17,27 +17,36 @@ var Discovery = class {
|
|
|
17
17
|
constructor(client) {
|
|
18
18
|
this.client = client;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
* @param query - The search query (e.g., "gas prices", "nft metadata")
|
|
24
|
-
* @param limit - Maximum number of results (1-50, default 10)
|
|
25
|
-
* @returns Array of matching tools
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* const tools = await client.discovery.search("gas prices");
|
|
30
|
-
* console.log(tools[0].name); // "Gas Price Oracle"
|
|
31
|
-
* console.log(tools[0].mcpTools); // Available methods
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
async search(query, limit) {
|
|
20
|
+
async search(queryOrOptions, limit) {
|
|
21
|
+
const options = typeof queryOrOptions === "string" ? { query: queryOrOptions, limit } : queryOrOptions;
|
|
35
22
|
const params = new URLSearchParams();
|
|
23
|
+
const query = options.query ?? "";
|
|
36
24
|
if (query) {
|
|
37
25
|
params.set("q", query);
|
|
38
26
|
}
|
|
39
|
-
if (limit !== void 0) {
|
|
40
|
-
params.set("limit", String(limit));
|
|
27
|
+
if (options.limit !== void 0) {
|
|
28
|
+
params.set("limit", String(options.limit));
|
|
29
|
+
}
|
|
30
|
+
if (options.mode) {
|
|
31
|
+
params.set("mode", options.mode);
|
|
32
|
+
}
|
|
33
|
+
if (options.surface) {
|
|
34
|
+
params.set("surface", options.surface);
|
|
35
|
+
}
|
|
36
|
+
if (options.queryEligible !== void 0) {
|
|
37
|
+
params.set("queryEligible", String(options.queryEligible));
|
|
38
|
+
}
|
|
39
|
+
if (options.requireExecutePricing !== void 0) {
|
|
40
|
+
params.set(
|
|
41
|
+
"requireExecutePricing",
|
|
42
|
+
String(options.requireExecutePricing)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (options.excludeLatencyClasses && options.excludeLatencyClasses.length > 0) {
|
|
46
|
+
params.set("excludeLatency", options.excludeLatencyClasses.join(","));
|
|
47
|
+
}
|
|
48
|
+
if (options.excludeSlow !== void 0) {
|
|
49
|
+
params.set("excludeSlow", String(options.excludeSlow));
|
|
41
50
|
}
|
|
42
51
|
const queryString = params.toString();
|
|
43
52
|
const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
|
|
@@ -55,8 +64,12 @@ var Discovery = class {
|
|
|
55
64
|
* const featured = await client.discovery.getFeatured(5);
|
|
56
65
|
* ```
|
|
57
66
|
*/
|
|
58
|
-
async getFeatured(limit) {
|
|
59
|
-
return this.search(
|
|
67
|
+
async getFeatured(limit, options) {
|
|
68
|
+
return this.search({
|
|
69
|
+
...options ?? {},
|
|
70
|
+
query: "",
|
|
71
|
+
...limit !== void 0 ? { limit } : {}
|
|
72
|
+
});
|
|
60
73
|
}
|
|
61
74
|
};
|
|
62
75
|
|
|
@@ -97,14 +110,31 @@ var Tools = class {
|
|
|
97
110
|
* ```
|
|
98
111
|
*/
|
|
99
112
|
async execute(options) {
|
|
100
|
-
const {
|
|
113
|
+
const {
|
|
114
|
+
toolId,
|
|
115
|
+
toolName,
|
|
116
|
+
args,
|
|
117
|
+
idempotencyKey,
|
|
118
|
+
mode,
|
|
119
|
+
sessionId,
|
|
120
|
+
maxSpendUsd,
|
|
121
|
+
closeSession
|
|
122
|
+
} = options;
|
|
101
123
|
const headers = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0;
|
|
102
124
|
const response = await this.client._fetch(
|
|
103
125
|
"/api/v1/tools/execute",
|
|
104
126
|
{
|
|
105
127
|
method: "POST",
|
|
106
128
|
headers,
|
|
107
|
-
body: JSON.stringify({
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
toolId,
|
|
131
|
+
toolName,
|
|
132
|
+
args,
|
|
133
|
+
mode: mode ?? "execute",
|
|
134
|
+
sessionId,
|
|
135
|
+
maxSpendUsd,
|
|
136
|
+
closeSession
|
|
137
|
+
})
|
|
108
138
|
}
|
|
109
139
|
);
|
|
110
140
|
if ("error" in response) {
|
|
@@ -118,13 +148,79 @@ var Tools = class {
|
|
|
118
148
|
}
|
|
119
149
|
if (response.success) {
|
|
120
150
|
return {
|
|
151
|
+
mode: response.mode,
|
|
121
152
|
result: response.result,
|
|
122
153
|
tool: response.tool,
|
|
154
|
+
method: response.method,
|
|
155
|
+
session: response.session,
|
|
123
156
|
durationMs: response.durationMs
|
|
124
157
|
};
|
|
125
158
|
}
|
|
126
159
|
throw new ContextError("Unexpected response format from API");
|
|
127
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Start an execute session with a max spend budget.
|
|
163
|
+
*/
|
|
164
|
+
async startSession(options) {
|
|
165
|
+
const response = await this.client._fetch(
|
|
166
|
+
"/api/v1/tools/execute/sessions",
|
|
167
|
+
{
|
|
168
|
+
method: "POST",
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
mode: "execute",
|
|
171
|
+
maxSpendUsd: options.maxSpendUsd
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
return this.resolveSessionLifecycleResponse(response);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Fetch current execute session status by ID.
|
|
179
|
+
*/
|
|
180
|
+
async getSession(sessionId) {
|
|
181
|
+
if (!sessionId) {
|
|
182
|
+
throw new ContextError("sessionId is required");
|
|
183
|
+
}
|
|
184
|
+
const encodedSessionId = encodeURIComponent(sessionId);
|
|
185
|
+
const response = await this.client._fetch(
|
|
186
|
+
`/api/v1/tools/execute/sessions/${encodedSessionId}`
|
|
187
|
+
);
|
|
188
|
+
return this.resolveSessionLifecycleResponse(response);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Close an execute session by ID.
|
|
192
|
+
*/
|
|
193
|
+
async closeSession(sessionId) {
|
|
194
|
+
if (!sessionId) {
|
|
195
|
+
throw new ContextError("sessionId is required");
|
|
196
|
+
}
|
|
197
|
+
const encodedSessionId = encodeURIComponent(sessionId);
|
|
198
|
+
const response = await this.client._fetch(
|
|
199
|
+
`/api/v1/tools/execute/sessions/${encodedSessionId}/close`,
|
|
200
|
+
{
|
|
201
|
+
method: "POST",
|
|
202
|
+
body: JSON.stringify({ mode: "execute" })
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
return this.resolveSessionLifecycleResponse(response);
|
|
206
|
+
}
|
|
207
|
+
resolveSessionLifecycleResponse(response) {
|
|
208
|
+
if ("error" in response) {
|
|
209
|
+
throw new ContextError(
|
|
210
|
+
response.error,
|
|
211
|
+
response.code,
|
|
212
|
+
void 0,
|
|
213
|
+
response.helpUrl
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (response.success) {
|
|
217
|
+
return {
|
|
218
|
+
mode: response.mode,
|
|
219
|
+
session: response.session
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
throw new ContextError("Unexpected response format from API");
|
|
223
|
+
}
|
|
128
224
|
};
|
|
129
225
|
|
|
130
226
|
// src/client/resources/query.ts
|
|
@@ -177,6 +273,7 @@ var Query = class {
|
|
|
177
273
|
modelId: opts.modelId,
|
|
178
274
|
includeData: opts.includeData,
|
|
179
275
|
includeDataUrl: opts.includeDataUrl,
|
|
276
|
+
queryDepth: opts.queryDepth,
|
|
180
277
|
stream: false
|
|
181
278
|
})
|
|
182
279
|
}
|
|
@@ -242,6 +339,7 @@ var Query = class {
|
|
|
242
339
|
modelId: opts.modelId,
|
|
243
340
|
includeData: opts.includeData,
|
|
244
341
|
includeDataUrl: opts.includeDataUrl,
|
|
342
|
+
queryDepth: opts.queryDepth,
|
|
245
343
|
stream: true
|
|
246
344
|
})
|
|
247
345
|
});
|
|
@@ -287,9 +385,14 @@ var Query = class {
|
|
|
287
385
|
};
|
|
288
386
|
|
|
289
387
|
// src/client/client.ts
|
|
388
|
+
var DEFAULT_BASE_URL = "https://www.ctxprotocol.com";
|
|
389
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e5;
|
|
390
|
+
var DEFAULT_STREAM_TIMEOUT_MS = 6e5;
|
|
290
391
|
var ContextClient = class {
|
|
291
392
|
apiKey;
|
|
292
393
|
baseUrl;
|
|
394
|
+
requestTimeoutMs;
|
|
395
|
+
streamTimeoutMs;
|
|
293
396
|
_closed = false;
|
|
294
397
|
/**
|
|
295
398
|
* Discovery resource for searching tools
|
|
@@ -312,14 +415,26 @@ var ContextClient = class {
|
|
|
312
415
|
*
|
|
313
416
|
* @param options - Client configuration options
|
|
314
417
|
* @param options.apiKey - Your Context Protocol API key (format: sk_live_...)
|
|
315
|
-
* @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)
|
|
418
|
+
* @param options.baseUrl - Optional base URL override (defaults to https://www.ctxprotocol.com)
|
|
419
|
+
* @param options.requestTimeoutMs - Optional timeout for non-streaming requests (default 300000ms)
|
|
420
|
+
* @param options.streamTimeoutMs - Optional timeout for establishing stream requests (default 600000ms)
|
|
316
421
|
*/
|
|
317
422
|
constructor(options) {
|
|
318
423
|
if (!options.apiKey) {
|
|
319
424
|
throw new ContextError("API key is required");
|
|
320
425
|
}
|
|
426
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
427
|
+
const streamTimeoutMs = options.streamTimeoutMs ?? DEFAULT_STREAM_TIMEOUT_MS;
|
|
428
|
+
if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) {
|
|
429
|
+
throw new ContextError("requestTimeoutMs must be a positive number");
|
|
430
|
+
}
|
|
431
|
+
if (!Number.isFinite(streamTimeoutMs) || streamTimeoutMs <= 0) {
|
|
432
|
+
throw new ContextError("streamTimeoutMs must be a positive number");
|
|
433
|
+
}
|
|
321
434
|
this.apiKey = options.apiKey;
|
|
322
|
-
this.baseUrl = (options.baseUrl ??
|
|
435
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
436
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
437
|
+
this.streamTimeoutMs = streamTimeoutMs;
|
|
323
438
|
this.discovery = new Discovery(this);
|
|
324
439
|
this.tools = new Tools(this);
|
|
325
440
|
this.query = new Query(this);
|
|
@@ -333,7 +448,7 @@ var ContextClient = class {
|
|
|
333
448
|
}
|
|
334
449
|
/**
|
|
335
450
|
* Internal method for making authenticated HTTP requests
|
|
336
|
-
* Includes timeout
|
|
451
|
+
* Includes timeout and retry with exponential backoff for transient errors
|
|
337
452
|
*
|
|
338
453
|
* @internal
|
|
339
454
|
*/
|
|
@@ -343,7 +458,7 @@ var ContextClient = class {
|
|
|
343
458
|
}
|
|
344
459
|
const url = `${this.baseUrl}${endpoint}`;
|
|
345
460
|
const maxRetries = 3;
|
|
346
|
-
const timeoutMs =
|
|
461
|
+
const timeoutMs = this.requestTimeoutMs;
|
|
347
462
|
let lastError;
|
|
348
463
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
349
464
|
const controller = new AbortController();
|
|
@@ -411,6 +526,7 @@ var ContextClient = class {
|
|
|
411
526
|
/**
|
|
412
527
|
* Internal method for making authenticated HTTP requests that returns
|
|
413
528
|
* the raw Response object. Used for streaming endpoints (SSE).
|
|
529
|
+
* Includes a configurable timeout for stream setup.
|
|
414
530
|
*
|
|
415
531
|
* @internal
|
|
416
532
|
*/
|
|
@@ -419,14 +535,32 @@ var ContextClient = class {
|
|
|
419
535
|
throw new ContextError("Client has been closed");
|
|
420
536
|
}
|
|
421
537
|
const url = `${this.baseUrl}${endpoint}`;
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
...options
|
|
538
|
+
const controller = new AbortController();
|
|
539
|
+
const timeout = setTimeout(() => controller.abort(), this.streamTimeoutMs);
|
|
540
|
+
let response;
|
|
541
|
+
try {
|
|
542
|
+
response = await fetch(url, {
|
|
543
|
+
...options,
|
|
544
|
+
signal: controller.signal,
|
|
545
|
+
headers: {
|
|
546
|
+
"Content-Type": "application/json",
|
|
547
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
548
|
+
...options.headers
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
} catch (error) {
|
|
552
|
+
clearTimeout(timeout);
|
|
553
|
+
const lastError = error instanceof Error ? error : new Error(String(error));
|
|
554
|
+
if (lastError.name === "AbortError") {
|
|
555
|
+
throw new ContextError(
|
|
556
|
+
`Streaming request timed out after ${this.streamTimeoutMs / 1e3}s`,
|
|
557
|
+
void 0,
|
|
558
|
+
408
|
|
559
|
+
);
|
|
428
560
|
}
|
|
429
|
-
|
|
561
|
+
throw new ContextError(lastError.message);
|
|
562
|
+
}
|
|
563
|
+
clearTimeout(timeout);
|
|
430
564
|
if (!response.ok) {
|
|
431
565
|
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
432
566
|
let errorCode;
|