@ctxprotocol/sdk 0.8.4 → 0.9.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/dist/index.cjs CHANGED
@@ -19,6 +19,14 @@ var Discovery = class {
19
19
  constructor(client) {
20
20
  this.client = client;
21
21
  }
22
+ /**
23
+ * Fetch a single marketplace tool by its unique ID.
24
+ */
25
+ async get(toolId) {
26
+ return this.client._fetch(
27
+ `/api/v1/tools/${encodeURIComponent(toolId)}`
28
+ );
29
+ }
22
30
  async search(queryOrOptions, limit) {
23
31
  const options = typeof queryOrOptions === "string" ? { query: queryOrOptions, limit } : queryOrOptions;
24
32
  const params = new URLSearchParams();
@@ -342,7 +350,7 @@ var Query = class {
342
350
  * Run an agentic query and wait for the full response.
343
351
  *
344
352
  * The server discovers relevant tools (or uses the ones you specify),
345
- * executes the full agentic pipeline (up to 100 MCP calls per tool),
353
+ * executes the discovery-first pipeline (up to 100 MCP calls per tool),
346
354
  * and returns an AI-synthesized answer. Payment is settled after
347
355
  * successful execution via deferred settlement.
348
356
  *
@@ -371,48 +379,25 @@ var Query = class {
371
379
  */
372
380
  async run(options) {
373
381
  const opts = typeof options === "string" ? { query: options } : options;
374
- const headers = opts.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0;
375
- const response = await this.client._fetch(
376
- "/api/v1/query",
377
- {
378
- method: "POST",
379
- headers,
380
- body: JSON.stringify({
381
- query: opts.query,
382
- tools: opts.tools,
383
- modelId: opts.modelId,
384
- includeData: opts.includeData,
385
- includeDataUrl: opts.includeDataUrl,
386
- includeDeveloperTrace: opts.includeDeveloperTrace,
387
- queryDepth: opts.queryDepth,
388
- stream: false
389
- })
382
+ let terminalError;
383
+ for await (const event of this.stream(opts)) {
384
+ if (event.type === "error") {
385
+ terminalError = {
386
+ error: event.error,
387
+ ...event.code ? { code: event.code } : {},
388
+ ...event.scope ? { scope: event.scope } : {},
389
+ ...event.reasonCode ? { reasonCode: event.reasonCode } : {}
390
+ };
391
+ continue;
392
+ }
393
+ if (event.type === "done") {
394
+ return event.result;
390
395
  }
391
- );
392
- if ("error" in response) {
393
- throw new ContextError(
394
- response.error,
395
- response.code,
396
- void 0,
397
- response.helpUrl
398
- );
399
396
  }
400
- if (response.success) {
401
- const developerTrace = response.developerTrace ?? (opts.includeDeveloperTrace ? this.buildSyntheticTraceFromRunResult({
402
- toolsUsed: response.toolsUsed,
403
- durationMs: response.durationMs
404
- }) : void 0);
405
- return {
406
- response: response.response,
407
- toolsUsed: response.toolsUsed,
408
- cost: response.cost,
409
- durationMs: response.durationMs,
410
- data: response.data,
411
- dataUrl: response.dataUrl,
412
- developerTrace
413
- };
397
+ if (terminalError) {
398
+ throw new ContextError(terminalError.error, terminalError.code);
414
399
  }
415
- throw new ContextError("Unexpected response format from query API");
400
+ throw new ContextError("Streaming query ended before done event");
416
401
  }
417
402
  /**
418
403
  * Run an agentic query with streaming. Returns an async iterable that
@@ -422,6 +407,7 @@ var Query = class {
422
407
  * - `tool-status` — A tool started executing or changed status
423
408
  * - `text-delta` — A chunk of the AI response text
424
409
  * - `developer-trace` — Runtime trace metadata (when includeDeveloperTrace=true)
410
+ * - `error` — A structured query/runtime error emitted before stream completion
425
411
  * - `done` — The full response is complete (includes final `QueryResult`)
426
412
  *
427
413
  * @param options - Query options or a plain string question
@@ -443,6 +429,9 @@ var Query = class {
443
429
  * case "done":
444
430
  * console.log("\nCost:", event.result.cost.totalCostUsd);
445
431
  * break;
432
+ * case "error":
433
+ * console.error("Stream error:", event.error);
434
+ * break;
446
435
  * }
447
436
  * }
448
437
  * ```
@@ -461,6 +450,7 @@ var Query = class {
461
450
  includeDataUrl: opts.includeDataUrl,
462
451
  includeDeveloperTrace: opts.includeDeveloperTrace,
463
452
  queryDepth: opts.queryDepth,
453
+ debugScoutDeepMode: opts.debugScoutDeepMode,
464
454
  stream: true
465
455
  })
466
456
  });
@@ -498,10 +488,13 @@ var Query = class {
498
488
  event.result.developerTrace
499
489
  );
500
490
  if (!mergedTrace && opts.includeDeveloperTrace) {
501
- mergedTrace = this.buildSyntheticTraceFromStreamStatus({
491
+ mergedTrace = statusTimeline.length > 0 ? this.buildSyntheticTraceFromStreamStatus({
502
492
  statusTimeline,
503
493
  toolsUsed: event.result.toolsUsed,
504
494
  durationMs: event.result.durationMs
495
+ }) : this.buildSyntheticTraceFromRunResult({
496
+ toolsUsed: event.result.toolsUsed,
497
+ durationMs: event.result.durationMs
505
498
  });
506
499
  }
507
500
  if (mergedTrace) {
@@ -618,30 +611,34 @@ var ContextClient = class {
618
611
  *
619
612
  * @internal
620
613
  */
621
- async _fetch(endpoint, options = {}) {
614
+ async _fetch(endpoint, options = {}, fetchOptions) {
622
615
  if (this._closed) {
623
616
  throw new ContextError("Client has been closed");
624
617
  }
625
618
  const url = `${this.baseUrl}${endpoint}`;
626
619
  const maxRetries = 3;
627
620
  const timeoutMs = this.requestTimeoutMs;
621
+ const method = (options.method ?? "GET").toUpperCase();
622
+ const requestHeaders = new Headers(options.headers);
623
+ const canRetryRequest = fetchOptions?.retry === false ? false : method === "GET" || method === "HEAD" || method === "OPTIONS" || requestHeaders.has("Idempotency-Key");
628
624
  let lastError;
629
625
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
630
626
  const controller = new AbortController();
631
627
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
628
+ const mergedHeaders = new Headers(requestHeaders);
629
+ if (!mergedHeaders.has("Content-Type")) {
630
+ mergedHeaders.set("Content-Type", "application/json");
631
+ }
632
+ mergedHeaders.set("Authorization", `Bearer ${this.apiKey}`);
632
633
  try {
633
634
  const response = await fetch(url, {
634
635
  ...options,
635
636
  signal: controller.signal,
636
- headers: {
637
- "Content-Type": "application/json",
638
- Authorization: `Bearer ${this.apiKey}`,
639
- ...options.headers
640
- }
637
+ headers: mergedHeaders
641
638
  });
642
639
  clearTimeout(timeout);
643
640
  if (!response.ok) {
644
- if (response.status >= 500 && attempt < maxRetries) {
641
+ if (response.status >= 500 && canRetryRequest && attempt < maxRetries) {
645
642
  const delay = Math.min(1e3 * 2 ** attempt, 1e4);
646
643
  await new Promise((resolve) => setTimeout(resolve, delay));
647
644
  continue;
@@ -660,7 +657,16 @@ var ContextClient = class {
660
657
  }
661
658
  throw new ContextError(errorMessage, errorCode, response.status, helpUrl);
662
659
  }
663
- return response.json();
660
+ try {
661
+ return await response.json();
662
+ } catch (error) {
663
+ const parseError = error instanceof Error ? error : new Error(String(error));
664
+ throw new ContextError(
665
+ `Failed to parse JSON response: ${parseError.message}`,
666
+ void 0,
667
+ response.status
668
+ );
669
+ }
664
670
  } catch (error) {
665
671
  clearTimeout(timeout);
666
672
  if (error instanceof ContextError) {
@@ -668,7 +674,7 @@ var ContextClient = class {
668
674
  }
669
675
  lastError = error instanceof Error ? error : new Error(String(error));
670
676
  const isRetryable = lastError.name === "AbortError" || lastError.message.includes("fetch failed") || lastError.message.includes("ECONNRESET") || lastError.message.includes("ETIMEDOUT");
671
- if (isRetryable && attempt < maxRetries) {
677
+ if (isRetryable && canRetryRequest && attempt < maxRetries) {
672
678
  const delay = Math.min(1e3 * 2 ** attempt, 1e4);
673
679
  await new Promise((resolve) => setTimeout(resolve, delay));
674
680
  continue;