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