@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/dist/index.js CHANGED
@@ -17,27 +17,36 @@ var Discovery = class {
17
17
  constructor(client) {
18
18
  this.client = client;
19
19
  }
20
- /**
21
- * Search for tools matching a query string
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("", limit);
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 { toolId, toolName, args, idempotencyKey } = options;
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({ toolId, toolName, args })
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 ?? "https://ctxprotocol.com").replace(/\/$/, "");
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 (30s) and retry with exponential backoff for transient errors
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 = 3e4;
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 response = await fetch(url, {
423
- ...options,
424
- headers: {
425
- "Content-Type": "application/json",
426
- Authorization: `Bearer ${this.apiKey}`,
427
- ...options.headers
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;