@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/dist/client/index.d.ts
CHANGED
|
@@ -48,15 +48,23 @@ interface Tool {
|
|
|
48
48
|
category?: string;
|
|
49
49
|
/** Whether the tool is verified by Context Protocol */
|
|
50
50
|
isVerified?: boolean;
|
|
51
|
+
/** Tool type - currently always "mcp" */
|
|
52
|
+
kind?: string;
|
|
51
53
|
/**
|
|
52
54
|
* Available MCP tool methods
|
|
53
55
|
* Use items from this array as `toolName` when executing
|
|
54
56
|
*/
|
|
55
57
|
mcpTools?: McpTool[];
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/** Total number of queries processed */
|
|
59
|
+
totalQueries?: number;
|
|
60
|
+
/** Success rate percentage (0-100) */
|
|
61
|
+
successRate?: string;
|
|
62
|
+
/** Uptime percentage (0-100) */
|
|
63
|
+
uptimePercent?: string;
|
|
64
|
+
/** Total USDC staked by the developer */
|
|
65
|
+
totalStaked?: string;
|
|
66
|
+
/** Whether the tool has "Proven" status (100+ queries, >95% success, >98% uptime) */
|
|
67
|
+
isProven?: boolean;
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
62
70
|
* Response from the tools search endpoint
|
|
@@ -133,10 +141,101 @@ interface ExecutionResult<T = unknown> {
|
|
|
133
141
|
/** Execution duration in milliseconds */
|
|
134
142
|
durationMs: number;
|
|
135
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Options for the agentic query endpoint (pay-per-response).
|
|
146
|
+
*
|
|
147
|
+
* Unlike `execute()` which calls a single tool once, `query()` sends a
|
|
148
|
+
* natural-language question and lets the server handle tool discovery,
|
|
149
|
+
* multi-tool orchestration, self-healing retries, and AI synthesis.
|
|
150
|
+
* One flat fee covers up to 100 MCP skill calls per tool.
|
|
151
|
+
*/
|
|
152
|
+
interface QueryOptions {
|
|
153
|
+
/** The natural-language question to answer */
|
|
154
|
+
query: string;
|
|
155
|
+
/**
|
|
156
|
+
* Optional tool IDs to use. When omitted the server discovers tools
|
|
157
|
+
* automatically (Auto Mode). When provided, only these tools are used
|
|
158
|
+
* (Manual Mode).
|
|
159
|
+
*/
|
|
160
|
+
tools?: string[];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Information about a tool that was used during a query response
|
|
164
|
+
*/
|
|
165
|
+
interface QueryToolUsage {
|
|
166
|
+
/** Tool ID */
|
|
167
|
+
id: string;
|
|
168
|
+
/** Tool name */
|
|
169
|
+
name: string;
|
|
170
|
+
/** Number of MCP skill calls made for this tool */
|
|
171
|
+
skillCalls: number;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Cost breakdown for a query response.
|
|
175
|
+
* All values are strings representing USD amounts.
|
|
176
|
+
*/
|
|
177
|
+
interface QueryCost {
|
|
178
|
+
/** AI model inference cost */
|
|
179
|
+
modelCostUsd: string;
|
|
180
|
+
/** Sum of all tool fees */
|
|
181
|
+
toolCostUsd: string;
|
|
182
|
+
/** Total cost (model + tools) */
|
|
183
|
+
totalCostUsd: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The resolved result of a pay-per-response query
|
|
187
|
+
*/
|
|
188
|
+
interface QueryResult {
|
|
189
|
+
/** The AI-synthesized response text */
|
|
190
|
+
response: string;
|
|
191
|
+
/** Tools that were used to answer the query */
|
|
192
|
+
toolsUsed: QueryToolUsage[];
|
|
193
|
+
/** Cost breakdown */
|
|
194
|
+
cost: QueryCost;
|
|
195
|
+
/** Total duration in milliseconds */
|
|
196
|
+
durationMs: number;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Successful response from the /api/v1/query endpoint
|
|
200
|
+
*/
|
|
201
|
+
interface QueryApiSuccessResponse {
|
|
202
|
+
success: true;
|
|
203
|
+
response: string;
|
|
204
|
+
toolsUsed: QueryToolUsage[];
|
|
205
|
+
cost: QueryCost;
|
|
206
|
+
durationMs: number;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Raw API response from the query endpoint
|
|
210
|
+
*/
|
|
211
|
+
type QueryApiResponse = QueryApiSuccessResponse | ExecuteApiErrorResponse;
|
|
212
|
+
/** Emitted when a tool starts or changes execution status */
|
|
213
|
+
interface QueryStreamToolStatusEvent {
|
|
214
|
+
type: "tool-status";
|
|
215
|
+
tool: {
|
|
216
|
+
id: string;
|
|
217
|
+
name: string;
|
|
218
|
+
};
|
|
219
|
+
status: string;
|
|
220
|
+
}
|
|
221
|
+
/** Emitted for each chunk of the AI response text */
|
|
222
|
+
interface QueryStreamTextDeltaEvent {
|
|
223
|
+
type: "text-delta";
|
|
224
|
+
delta: string;
|
|
225
|
+
}
|
|
226
|
+
/** Emitted when the full response is complete */
|
|
227
|
+
interface QueryStreamDoneEvent {
|
|
228
|
+
type: "done";
|
|
229
|
+
result: QueryResult;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Union of all events emitted during a streaming query
|
|
233
|
+
*/
|
|
234
|
+
type QueryStreamEvent = QueryStreamToolStatusEvent | QueryStreamTextDeltaEvent | QueryStreamDoneEvent;
|
|
136
235
|
/**
|
|
137
236
|
* Specific error codes returned by the Context Protocol API
|
|
138
237
|
*/
|
|
139
|
-
type ContextErrorCode = "unauthorized" | "no_wallet" | "insufficient_allowance" | "payment_failed" | "execution_failed";
|
|
238
|
+
type ContextErrorCode = "unauthorized" | "no_wallet" | "insufficient_allowance" | "payment_failed" | "execution_failed" | "query_failed";
|
|
140
239
|
/**
|
|
141
240
|
* Error thrown by the Context Protocol client
|
|
142
241
|
*/
|
|
@@ -198,8 +297,8 @@ declare class Tools {
|
|
|
198
297
|
* @returns The execution result with the tool's output data
|
|
199
298
|
*
|
|
200
299
|
* @throws {ContextError} With code `no_wallet` if wallet not set up
|
|
201
|
-
* @throws {ContextError} With code `insufficient_allowance` if
|
|
202
|
-
* @throws {ContextError} With code `payment_failed` if
|
|
300
|
+
* @throws {ContextError} With code `insufficient_allowance` if spending cap not set
|
|
301
|
+
* @throws {ContextError} With code `payment_failed` if payment settlement fails
|
|
203
302
|
* @throws {ContextError} With code `execution_failed` if tool execution fails
|
|
204
303
|
*
|
|
205
304
|
* @example
|
|
@@ -222,6 +321,85 @@ declare class Tools {
|
|
|
222
321
|
execute<T = unknown>(options: ExecuteOptions): Promise<ExecutionResult<T>>;
|
|
223
322
|
}
|
|
224
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Query resource for pay-per-response agentic queries.
|
|
326
|
+
*
|
|
327
|
+
* Unlike `tools.execute()` which calls a single tool once (pay-per-request),
|
|
328
|
+
* the Query resource sends a natural-language question and lets the server
|
|
329
|
+
* handle tool discovery, multi-tool orchestration, self-healing retries,
|
|
330
|
+
* completeness checks, and AI synthesis — all for one flat fee.
|
|
331
|
+
*
|
|
332
|
+
* This is the "prepared meal" vs "raw ingredients" distinction:
|
|
333
|
+
* - `tools.execute()` = raw data, full control, predictable cost
|
|
334
|
+
* - `query.run()` / `query.stream()` = curated intelligence, one payment
|
|
335
|
+
*/
|
|
336
|
+
declare class Query {
|
|
337
|
+
private client;
|
|
338
|
+
constructor(client: ContextClient);
|
|
339
|
+
/**
|
|
340
|
+
* Run an agentic query and wait for the full response.
|
|
341
|
+
*
|
|
342
|
+
* The server discovers relevant tools (or uses the ones you specify),
|
|
343
|
+
* executes the full agentic pipeline (up to 100 MCP calls per tool),
|
|
344
|
+
* and returns an AI-synthesized answer. Payment is settled after
|
|
345
|
+
* successful execution via deferred settlement.
|
|
346
|
+
*
|
|
347
|
+
* @param options - Query options or a plain string question
|
|
348
|
+
* @returns The complete query result with response text, tools used, and cost
|
|
349
|
+
*
|
|
350
|
+
* @throws {ContextError} With code `no_wallet` if wallet not set up
|
|
351
|
+
* @throws {ContextError} With code `insufficient_allowance` if spending cap not set
|
|
352
|
+
* @throws {ContextError} With code `payment_failed` if payment settlement fails
|
|
353
|
+
* @throws {ContextError} With code `execution_failed` if the agentic pipeline fails
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```typescript
|
|
357
|
+
* // Simple question — server discovers tools automatically
|
|
358
|
+
* const answer = await client.query.run("What are the top whale movements on Base?");
|
|
359
|
+
* console.log(answer.response); // AI-synthesized answer
|
|
360
|
+
* console.log(answer.toolsUsed); // Which tools were used
|
|
361
|
+
* console.log(answer.cost); // Cost breakdown
|
|
362
|
+
*
|
|
363
|
+
* // With specific tools (Manual Mode)
|
|
364
|
+
* const answer = await client.query.run({
|
|
365
|
+
* query: "Analyze whale activity",
|
|
366
|
+
* tools: ["tool-uuid-1", "tool-uuid-2"],
|
|
367
|
+
* });
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
run(options: QueryOptions | string): Promise<QueryResult>;
|
|
371
|
+
/**
|
|
372
|
+
* Run an agentic query with streaming. Returns an async iterable that
|
|
373
|
+
* yields events as the server processes the query in real-time.
|
|
374
|
+
*
|
|
375
|
+
* Event types:
|
|
376
|
+
* - `tool-status` — A tool started executing or changed status
|
|
377
|
+
* - `text-delta` — A chunk of the AI response text
|
|
378
|
+
* - `done` — The full response is complete (includes final `QueryResult`)
|
|
379
|
+
*
|
|
380
|
+
* @param options - Query options or a plain string question
|
|
381
|
+
* @returns An async iterable of stream events
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* for await (const event of client.query.stream("What are the top whale movements?")) {
|
|
386
|
+
* switch (event.type) {
|
|
387
|
+
* case "tool-status":
|
|
388
|
+
* console.log(`Tool ${event.tool.name}: ${event.status}`);
|
|
389
|
+
* break;
|
|
390
|
+
* case "text-delta":
|
|
391
|
+
* process.stdout.write(event.delta);
|
|
392
|
+
* break;
|
|
393
|
+
* case "done":
|
|
394
|
+
* console.log("\nCost:", event.result.cost.totalCostUsd);
|
|
395
|
+
* break;
|
|
396
|
+
* }
|
|
397
|
+
* }
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
stream(options: QueryOptions | string): AsyncGenerator<QueryStreamEvent>;
|
|
401
|
+
}
|
|
402
|
+
|
|
225
403
|
/**
|
|
226
404
|
* The official TypeScript client for the Context Protocol.
|
|
227
405
|
*
|
|
@@ -235,28 +413,38 @@ declare class Tools {
|
|
|
235
413
|
* apiKey: "sk_live_..."
|
|
236
414
|
* });
|
|
237
415
|
*
|
|
238
|
-
* //
|
|
239
|
-
* const tools = await client.discovery.search("gas prices");
|
|
240
|
-
*
|
|
241
|
-
* // Execute a tool method
|
|
416
|
+
* // Pay-per-request: Execute a specific tool
|
|
242
417
|
* const result = await client.tools.execute({
|
|
243
|
-
* toolId:
|
|
244
|
-
* toolName:
|
|
418
|
+
* toolId: "tool-uuid",
|
|
419
|
+
* toolName: "get_gas_prices",
|
|
245
420
|
* args: { chainId: 1 }
|
|
246
421
|
* });
|
|
422
|
+
*
|
|
423
|
+
* // Pay-per-response: Ask a question, get a curated answer
|
|
424
|
+
* const answer = await client.query.run("What are the top whale movements on Base?");
|
|
425
|
+
* console.log(answer.response);
|
|
247
426
|
* ```
|
|
248
427
|
*/
|
|
249
428
|
declare class ContextClient {
|
|
250
429
|
private readonly apiKey;
|
|
251
430
|
private readonly baseUrl;
|
|
431
|
+
private _closed;
|
|
252
432
|
/**
|
|
253
433
|
* Discovery resource for searching tools
|
|
254
434
|
*/
|
|
255
435
|
readonly discovery: Discovery;
|
|
256
436
|
/**
|
|
257
|
-
* Tools resource for executing tools
|
|
437
|
+
* Tools resource for executing tools (pay-per-request)
|
|
258
438
|
*/
|
|
259
439
|
readonly tools: Tools;
|
|
440
|
+
/**
|
|
441
|
+
* Query resource for agentic queries (pay-per-response).
|
|
442
|
+
*
|
|
443
|
+
* Unlike `tools.execute()` which calls a single tool once, `query` sends
|
|
444
|
+
* a natural-language question and lets the server handle tool discovery,
|
|
445
|
+
* multi-tool orchestration, self-healing, and AI synthesis — one flat fee.
|
|
446
|
+
*/
|
|
447
|
+
readonly query: Query;
|
|
260
448
|
/**
|
|
261
449
|
* Creates a new Context Protocol client
|
|
262
450
|
*
|
|
@@ -265,13 +453,25 @@ declare class ContextClient {
|
|
|
265
453
|
* @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)
|
|
266
454
|
*/
|
|
267
455
|
constructor(options: ContextClientOptions);
|
|
456
|
+
/**
|
|
457
|
+
* Close the client and clean up resources.
|
|
458
|
+
* After calling close(), any in-flight requests may be aborted.
|
|
459
|
+
*/
|
|
460
|
+
close(): void;
|
|
268
461
|
/**
|
|
269
462
|
* Internal method for making authenticated HTTP requests
|
|
270
|
-
*
|
|
463
|
+
* Includes timeout (30s) and retry with exponential backoff for transient errors
|
|
464
|
+
*
|
|
465
|
+
* @internal
|
|
466
|
+
*/
|
|
467
|
+
_fetch<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
468
|
+
/**
|
|
469
|
+
* Internal method for making authenticated HTTP requests that returns
|
|
470
|
+
* the raw Response object. Used for streaming endpoints (SSE).
|
|
271
471
|
*
|
|
272
472
|
* @internal
|
|
273
473
|
*/
|
|
274
|
-
|
|
474
|
+
_fetchRaw(endpoint: string, options?: RequestInit): Promise<Response>;
|
|
275
475
|
}
|
|
276
476
|
|
|
277
|
-
export { ContextClient, type ContextClientOptions, ContextError, type ContextErrorCode, Discovery, type ExecuteApiErrorResponse, type ExecuteApiResponse, type ExecuteApiSuccessResponse, type ExecuteOptions, type ExecutionResult, type McpTool, type SearchOptions, type SearchResponse, type Tool, Tools };
|
|
477
|
+
export { ContextClient, type ContextClientOptions, ContextError, type ContextErrorCode, Discovery, type ExecuteApiErrorResponse, type ExecuteApiResponse, type ExecuteApiSuccessResponse, type ExecuteOptions, type ExecutionResult, type McpTool, Query, type QueryApiResponse, type QueryApiSuccessResponse, type QueryCost, type QueryOptions, type QueryResult, type QueryStreamDoneEvent, type QueryStreamEvent, type QueryStreamTextDeltaEvent, type QueryStreamToolStatusEvent, type QueryToolUsage, type SearchOptions, type SearchResponse, type Tool, Tools };
|
package/dist/client/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/client/types.ts
|
|
2
|
-
var ContextError = class extends Error {
|
|
2
|
+
var ContextError = class _ContextError extends Error {
|
|
3
3
|
constructor(message, code, statusCode, helpUrl) {
|
|
4
4
|
super(message);
|
|
5
5
|
this.code = code;
|
|
6
6
|
this.statusCode = statusCode;
|
|
7
7
|
this.helpUrl = helpUrl;
|
|
8
8
|
this.name = "ContextError";
|
|
9
|
+
Object.setPrototypeOf(this, _ContextError.prototype);
|
|
9
10
|
}
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -38,7 +39,7 @@ var Discovery = class {
|
|
|
38
39
|
}
|
|
39
40
|
const queryString = params.toString();
|
|
40
41
|
const endpoint = `/api/v1/tools/search${queryString ? `?${queryString}` : ""}`;
|
|
41
|
-
const response = await this.client.
|
|
42
|
+
const response = await this.client._fetch(endpoint);
|
|
42
43
|
return response.tools;
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
@@ -72,8 +73,8 @@ var Tools = class {
|
|
|
72
73
|
* @returns The execution result with the tool's output data
|
|
73
74
|
*
|
|
74
75
|
* @throws {ContextError} With code `no_wallet` if wallet not set up
|
|
75
|
-
* @throws {ContextError} With code `insufficient_allowance` if
|
|
76
|
-
* @throws {ContextError} With code `payment_failed` if
|
|
76
|
+
* @throws {ContextError} With code `insufficient_allowance` if spending cap not set
|
|
77
|
+
* @throws {ContextError} With code `payment_failed` if payment settlement fails
|
|
77
78
|
* @throws {ContextError} With code `execution_failed` if tool execution fails
|
|
78
79
|
*
|
|
79
80
|
* @example
|
|
@@ -95,7 +96,7 @@ var Tools = class {
|
|
|
95
96
|
*/
|
|
96
97
|
async execute(options) {
|
|
97
98
|
const { toolId, toolName, args } = options;
|
|
98
|
-
const response = await this.client.
|
|
99
|
+
const response = await this.client._fetch(
|
|
99
100
|
"/api/v1/tools/execute",
|
|
100
101
|
{
|
|
101
102
|
method: "POST",
|
|
@@ -106,7 +107,8 @@ var Tools = class {
|
|
|
106
107
|
throw new ContextError(
|
|
107
108
|
response.error,
|
|
108
109
|
response.code,
|
|
109
|
-
|
|
110
|
+
void 0,
|
|
111
|
+
// Don't hardcode - this was a 200 OK with error body
|
|
110
112
|
response.helpUrl
|
|
111
113
|
);
|
|
112
114
|
}
|
|
@@ -121,18 +123,174 @@ var Tools = class {
|
|
|
121
123
|
}
|
|
122
124
|
};
|
|
123
125
|
|
|
126
|
+
// src/client/resources/query.ts
|
|
127
|
+
var Query = class {
|
|
128
|
+
constructor(client) {
|
|
129
|
+
this.client = client;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Run an agentic query and wait for the full response.
|
|
133
|
+
*
|
|
134
|
+
* The server discovers relevant tools (or uses the ones you specify),
|
|
135
|
+
* executes the full agentic pipeline (up to 100 MCP calls per tool),
|
|
136
|
+
* and returns an AI-synthesized answer. Payment is settled after
|
|
137
|
+
* successful execution via deferred settlement.
|
|
138
|
+
*
|
|
139
|
+
* @param options - Query options or a plain string question
|
|
140
|
+
* @returns The complete query result with response text, tools used, and cost
|
|
141
|
+
*
|
|
142
|
+
* @throws {ContextError} With code `no_wallet` if wallet not set up
|
|
143
|
+
* @throws {ContextError} With code `insufficient_allowance` if spending cap not set
|
|
144
|
+
* @throws {ContextError} With code `payment_failed` if payment settlement fails
|
|
145
|
+
* @throws {ContextError} With code `execution_failed` if the agentic pipeline fails
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* // Simple question — server discovers tools automatically
|
|
150
|
+
* const answer = await client.query.run("What are the top whale movements on Base?");
|
|
151
|
+
* console.log(answer.response); // AI-synthesized answer
|
|
152
|
+
* console.log(answer.toolsUsed); // Which tools were used
|
|
153
|
+
* console.log(answer.cost); // Cost breakdown
|
|
154
|
+
*
|
|
155
|
+
* // With specific tools (Manual Mode)
|
|
156
|
+
* const answer = await client.query.run({
|
|
157
|
+
* query: "Analyze whale activity",
|
|
158
|
+
* tools: ["tool-uuid-1", "tool-uuid-2"],
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async run(options) {
|
|
163
|
+
const opts = typeof options === "string" ? { query: options } : options;
|
|
164
|
+
const response = await this.client._fetch(
|
|
165
|
+
"/api/v1/query",
|
|
166
|
+
{
|
|
167
|
+
method: "POST",
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
query: opts.query,
|
|
170
|
+
tools: opts.tools,
|
|
171
|
+
stream: false
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
if ("error" in response) {
|
|
176
|
+
throw new ContextError(
|
|
177
|
+
response.error,
|
|
178
|
+
response.code,
|
|
179
|
+
void 0,
|
|
180
|
+
response.helpUrl
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (response.success) {
|
|
184
|
+
return {
|
|
185
|
+
response: response.response,
|
|
186
|
+
toolsUsed: response.toolsUsed,
|
|
187
|
+
cost: response.cost,
|
|
188
|
+
durationMs: response.durationMs
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
throw new ContextError("Unexpected response format from query API");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Run an agentic query with streaming. Returns an async iterable that
|
|
195
|
+
* yields events as the server processes the query in real-time.
|
|
196
|
+
*
|
|
197
|
+
* Event types:
|
|
198
|
+
* - `tool-status` — A tool started executing or changed status
|
|
199
|
+
* - `text-delta` — A chunk of the AI response text
|
|
200
|
+
* - `done` — The full response is complete (includes final `QueryResult`)
|
|
201
|
+
*
|
|
202
|
+
* @param options - Query options or a plain string question
|
|
203
|
+
* @returns An async iterable of stream events
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* for await (const event of client.query.stream("What are the top whale movements?")) {
|
|
208
|
+
* switch (event.type) {
|
|
209
|
+
* case "tool-status":
|
|
210
|
+
* console.log(`Tool ${event.tool.name}: ${event.status}`);
|
|
211
|
+
* break;
|
|
212
|
+
* case "text-delta":
|
|
213
|
+
* process.stdout.write(event.delta);
|
|
214
|
+
* break;
|
|
215
|
+
* case "done":
|
|
216
|
+
* console.log("\nCost:", event.result.cost.totalCostUsd);
|
|
217
|
+
* break;
|
|
218
|
+
* }
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
async *stream(options) {
|
|
223
|
+
const opts = typeof options === "string" ? { query: options } : options;
|
|
224
|
+
const response = await this.client._fetchRaw("/api/v1/query", {
|
|
225
|
+
method: "POST",
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
query: opts.query,
|
|
228
|
+
tools: opts.tools,
|
|
229
|
+
stream: true
|
|
230
|
+
})
|
|
231
|
+
});
|
|
232
|
+
const body = response.body;
|
|
233
|
+
if (!body) {
|
|
234
|
+
throw new ContextError("No response body for streaming query");
|
|
235
|
+
}
|
|
236
|
+
const reader = body.getReader();
|
|
237
|
+
const decoder = new TextDecoder();
|
|
238
|
+
let buffer = "";
|
|
239
|
+
try {
|
|
240
|
+
while (true) {
|
|
241
|
+
const { done, value } = await reader.read();
|
|
242
|
+
if (done) break;
|
|
243
|
+
buffer += decoder.decode(value, { stream: true });
|
|
244
|
+
const lines = buffer.split("\n");
|
|
245
|
+
buffer = lines.pop() ?? "";
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
const trimmed = line.trim();
|
|
248
|
+
if (trimmed.startsWith("data: ")) {
|
|
249
|
+
const data = trimmed.slice(6);
|
|
250
|
+
if (data === "[DONE]") return;
|
|
251
|
+
try {
|
|
252
|
+
yield JSON.parse(data);
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (buffer.trim().startsWith("data: ")) {
|
|
259
|
+
const data = buffer.trim().slice(6);
|
|
260
|
+
if (data !== "[DONE]") {
|
|
261
|
+
try {
|
|
262
|
+
yield JSON.parse(data);
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} finally {
|
|
268
|
+
reader.releaseLock();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
124
273
|
// src/client/client.ts
|
|
125
274
|
var ContextClient = class {
|
|
126
275
|
apiKey;
|
|
127
276
|
baseUrl;
|
|
277
|
+
_closed = false;
|
|
128
278
|
/**
|
|
129
279
|
* Discovery resource for searching tools
|
|
130
280
|
*/
|
|
131
281
|
discovery;
|
|
132
282
|
/**
|
|
133
|
-
* Tools resource for executing tools
|
|
283
|
+
* Tools resource for executing tools (pay-per-request)
|
|
134
284
|
*/
|
|
135
285
|
tools;
|
|
286
|
+
/**
|
|
287
|
+
* Query resource for agentic queries (pay-per-response).
|
|
288
|
+
*
|
|
289
|
+
* Unlike `tools.execute()` which calls a single tool once, `query` sends
|
|
290
|
+
* a natural-language question and lets the server handle tool discovery,
|
|
291
|
+
* multi-tool orchestration, self-healing, and AI synthesis — one flat fee.
|
|
292
|
+
*/
|
|
293
|
+
query;
|
|
136
294
|
/**
|
|
137
295
|
* Creates a new Context Protocol client
|
|
138
296
|
*
|
|
@@ -148,14 +306,102 @@ var ContextClient = class {
|
|
|
148
306
|
this.baseUrl = (options.baseUrl ?? "https://ctxprotocol.com").replace(/\/$/, "");
|
|
149
307
|
this.discovery = new Discovery(this);
|
|
150
308
|
this.tools = new Tools(this);
|
|
309
|
+
this.query = new Query(this);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Close the client and clean up resources.
|
|
313
|
+
* After calling close(), any in-flight requests may be aborted.
|
|
314
|
+
*/
|
|
315
|
+
close() {
|
|
316
|
+
this._closed = true;
|
|
151
317
|
}
|
|
152
318
|
/**
|
|
153
319
|
* Internal method for making authenticated HTTP requests
|
|
154
|
-
*
|
|
320
|
+
* Includes timeout (30s) and retry with exponential backoff for transient errors
|
|
155
321
|
*
|
|
156
322
|
* @internal
|
|
157
323
|
*/
|
|
158
|
-
async
|
|
324
|
+
async _fetch(endpoint, options = {}) {
|
|
325
|
+
if (this._closed) {
|
|
326
|
+
throw new ContextError("Client has been closed");
|
|
327
|
+
}
|
|
328
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
329
|
+
const maxRetries = 3;
|
|
330
|
+
const timeoutMs = 3e4;
|
|
331
|
+
let lastError;
|
|
332
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
333
|
+
const controller = new AbortController();
|
|
334
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
335
|
+
try {
|
|
336
|
+
const response = await fetch(url, {
|
|
337
|
+
...options,
|
|
338
|
+
signal: controller.signal,
|
|
339
|
+
headers: {
|
|
340
|
+
"Content-Type": "application/json",
|
|
341
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
342
|
+
...options.headers
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
clearTimeout(timeout);
|
|
346
|
+
if (!response.ok) {
|
|
347
|
+
if (response.status >= 500 && attempt < maxRetries) {
|
|
348
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
349
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
353
|
+
let errorCode;
|
|
354
|
+
let helpUrl;
|
|
355
|
+
try {
|
|
356
|
+
const errorBody = await response.json();
|
|
357
|
+
if (errorBody.error) {
|
|
358
|
+
errorMessage = errorBody.error;
|
|
359
|
+
errorCode = errorBody.code;
|
|
360
|
+
helpUrl = errorBody.helpUrl;
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
365
|
+
}
|
|
366
|
+
return response.json();
|
|
367
|
+
} catch (error) {
|
|
368
|
+
clearTimeout(timeout);
|
|
369
|
+
if (error instanceof ContextError) {
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
373
|
+
const isRetryable = lastError.name === "AbortError" || lastError.message.includes("fetch failed") || lastError.message.includes("ECONNRESET") || lastError.message.includes("ETIMEDOUT");
|
|
374
|
+
if (isRetryable && attempt < maxRetries) {
|
|
375
|
+
const delay = Math.min(1e3 * 2 ** attempt, 1e4);
|
|
376
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (lastError.name === "AbortError") {
|
|
380
|
+
throw new ContextError(
|
|
381
|
+
`Request timed out after ${timeoutMs / 1e3}s`,
|
|
382
|
+
void 0,
|
|
383
|
+
408
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
throw new ContextError(
|
|
387
|
+
lastError.message,
|
|
388
|
+
void 0,
|
|
389
|
+
void 0
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
throw lastError ?? new ContextError("Request failed after retries");
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Internal method for making authenticated HTTP requests that returns
|
|
397
|
+
* the raw Response object. Used for streaming endpoints (SSE).
|
|
398
|
+
*
|
|
399
|
+
* @internal
|
|
400
|
+
*/
|
|
401
|
+
async _fetchRaw(endpoint, options = {}) {
|
|
402
|
+
if (this._closed) {
|
|
403
|
+
throw new ContextError("Client has been closed");
|
|
404
|
+
}
|
|
159
405
|
const url = `${this.baseUrl}${endpoint}`;
|
|
160
406
|
const response = await fetch(url, {
|
|
161
407
|
...options,
|
|
@@ -180,10 +426,10 @@ var ContextClient = class {
|
|
|
180
426
|
}
|
|
181
427
|
throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
|
|
182
428
|
}
|
|
183
|
-
return response
|
|
429
|
+
return response;
|
|
184
430
|
}
|
|
185
431
|
};
|
|
186
432
|
|
|
187
|
-
export { ContextClient, ContextError, Discovery, Tools };
|
|
433
|
+
export { ContextClient, ContextError, Discovery, Query, Tools };
|
|
188
434
|
//# sourceMappingURL=index.js.map
|
|
189
435
|
//# sourceMappingURL=index.js.map
|