@ctxprotocol/sdk 0.8.2 → 0.8.4

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 CHANGED
@@ -101,12 +101,15 @@ console.log(result.session); // methodPrice, spent, remaining, maxSpend, ...
101
101
  const answer = await client.query.run({
102
102
  query: "What are the top whale movements on Base?",
103
103
  modelId: "glm-model", // optional: choose a supported model
104
+ queryDepth: "deep", // optional: fast | auto | deep
104
105
  includeDataUrl: true, // optional: persist full execution data to blob
106
+ includeDeveloperTrace: true, // optional: include machine-readable runtime trace
105
107
  });
106
108
  console.log(answer.response); // AI-synthesized answer
107
109
  console.log(answer.toolsUsed); // Which tools were used
108
110
  console.log(answer.cost); // Cost breakdown
109
111
  console.log(answer.dataUrl); // Optional blob URL with full data
112
+ console.log(answer.developerTrace?.summary); // retries/toolCalls/loops summary
110
113
  ```
111
114
 
112
115
  > Mixed listings are first-class: one listing can expose methods to both surfaces. Methods without `_meta.pricing.executeUsd` remain query-only until priced.
@@ -298,10 +301,12 @@ If you can answer without a tool, just respond normally.`;
298
301
 
299
302
  ### Client Options
300
303
 
301
- | Option | Type | Required | Default | Description |
302
- | --------- | -------- | -------- | ------------------------ | ------------------------------ |
303
- | `apiKey` | `string` | Yes | — | Your Context Protocol API key |
304
- | `baseUrl` | `string` | No | `https://ctxprotocol.com`| API base URL (for development) |
304
+ | Option | Type | Required | Default | Description |
305
+ | ------------------ | -------- | -------- | ------------------------------ | --------------------------------------------- |
306
+ | `apiKey` | `string` | Yes | — | Your Context Protocol API key |
307
+ | `baseUrl` | `string` | No | `https://www.ctxprotocol.com` | API base URL (for development) |
308
+ | `requestTimeoutMs` | `number` | No | `300000` | Timeout for non-streaming API calls |
309
+ | `streamTimeoutMs` | `number` | No | `600000` | Timeout for establishing streaming API calls |
305
310
 
306
311
  ```typescript
307
312
  // Production
@@ -313,6 +318,8 @@ const client = new ContextClient({
313
318
  const client = new ContextClient({
314
319
  apiKey: "sk_test_...",
315
320
  baseUrl: "http://localhost:3000",
321
+ requestTimeoutMs: 420_000,
322
+ streamTimeoutMs: 840_000,
316
323
  });
317
324
  ```
318
325
 
@@ -395,6 +402,11 @@ const closed = await client.tools.closeSession("sess_123");
395
402
 
396
403
  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.
397
404
 
405
+ `queryDepth` controls orchestration depth:
406
+ - `fast`: lower-latency path for simple lookups.
407
+ - `auto`: server routes to either `fast` or `deep` from query intent + selected tool complexity.
408
+ - `deep`: completeness-oriented path (default when omitted).
409
+
398
410
  ```typescript
399
411
  // Simple string
400
412
  const answer = await client.query.run("What are the top whale movements on Base?");
@@ -404,8 +416,10 @@ const answer = await client.query.run({
404
416
  query: "Analyze whale activity on Base",
405
417
  tools: ["tool-uuid-1", "tool-uuid-2"], // optional — auto-discover if omitted
406
418
  modelId: "kimi-model-thinking", // optional
419
+ queryDepth: "auto", // optional: fast | auto | deep
407
420
  includeData: true, // optional: include execution data inline
408
421
  includeDataUrl: true, // optional: include blob URL for full data
422
+ includeDeveloperTrace: true, // optional: include Developer Mode trace
409
423
  });
410
424
 
411
425
  console.log(answer.response); // AI-synthesized text
@@ -414,14 +428,26 @@ console.log(answer.cost); // { modelCostUsd, toolCostUsd, totalCostUsd }
414
428
  console.log(answer.durationMs); // Total time
415
429
  console.log(answer.data); // Optional execution data (when includeData=true)
416
430
  console.log(answer.dataUrl); // Optional blob URL (when includeDataUrl=true)
431
+ console.log(answer.developerTrace?.summary); // Optional trace summary (retries/toolCalls/loops)
417
432
  ```
418
433
 
434
+ 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.
435
+
419
436
  #### `client.query.stream(options)`
420
437
 
421
438
  Same as `run()` but streams events in real-time via SSE.
422
439
 
440
+ Event types:
441
+ - `tool-status`
442
+ - `text-delta`
443
+ - `developer-trace` (when `includeDeveloperTrace=true`)
444
+ - `done`
445
+
423
446
  ```typescript
424
- for await (const event of client.query.stream("What are the top whale movements?")) {
447
+ for await (const event of client.query.stream({
448
+ query: "What are the top whale movements?",
449
+ queryDepth: "fast",
450
+ })) {
425
451
  switch (event.type) {
426
452
  case "tool-status":
427
453
  console.log(`Tool ${event.tool.name}: ${event.status}`);
@@ -456,6 +482,7 @@ import type {
456
482
  // Query types (pay-per-response)
457
483
  QueryOptions,
458
484
  QueryResult,
485
+ QueryDeveloperTrace,
459
486
  QueryCost,
460
487
  QueryStreamEvent,
461
488
  ContextErrorCode,
@@ -533,6 +560,7 @@ interface QueryResult {
533
560
  durationMs: number;
534
561
  data?: unknown; // Optional execution data (includeData=true)
535
562
  dataUrl?: string; // Optional blob URL (includeDataUrl=true)
563
+ developerTrace?: QueryDeveloperTrace; // Optional machine-readable runtime trace
536
564
  }
537
565
  ```
538
566
 
@@ -228,6 +228,114 @@ var Query = class {
228
228
  constructor(client) {
229
229
  this.client = client;
230
230
  }
231
+ buildSyntheticTraceFromRunResult(params) {
232
+ const timeline = params.toolsUsed.map((tool, index) => ({
233
+ stepType: "tool-call",
234
+ event: "tool-call",
235
+ status: "success",
236
+ timestampMs: index,
237
+ tool: {
238
+ id: tool.id,
239
+ name: tool.name
240
+ },
241
+ metadata: {
242
+ skillCalls: tool.skillCalls,
243
+ synthetic: true
244
+ }
245
+ }));
246
+ const toolCalls = params.toolsUsed.reduce(
247
+ (sum, tool) => sum + Math.max(tool.skillCalls, 0),
248
+ 0
249
+ );
250
+ return {
251
+ summary: {
252
+ toolCalls,
253
+ retryCount: 0,
254
+ selfHealCount: 0,
255
+ fallbackCount: 0,
256
+ failureCount: 0,
257
+ recoveryCount: 0,
258
+ completionChecks: 0,
259
+ loopCount: 0
260
+ },
261
+ timeline,
262
+ source: "sdk-fallback",
263
+ synthetic: true,
264
+ reason: "backend_trace_missing",
265
+ durationMs: params.durationMs
266
+ };
267
+ }
268
+ buildSyntheticTraceFromStreamStatus(params) {
269
+ const timeline = params.statusTimeline.map((entry, index) => ({
270
+ stepType: "tool-status",
271
+ event: "tool-status",
272
+ status: entry.status,
273
+ timestampMs: index,
274
+ tool: entry.tool.name || entry.tool.id ? {
275
+ id: entry.tool.id || void 0,
276
+ name: entry.tool.name || void 0
277
+ } : void 0,
278
+ metadata: { synthetic: true }
279
+ }));
280
+ const toolCallsFromUsage = params.toolsUsed.reduce(
281
+ (sum, tool) => sum + Math.max(tool.skillCalls, 0),
282
+ 0
283
+ );
284
+ const toolCallsFromStatus = params.statusTimeline.filter(
285
+ (entry) => entry.status === "tool-complete"
286
+ ).length;
287
+ const toolCalls = toolCallsFromUsage > 0 ? toolCallsFromUsage : toolCallsFromStatus;
288
+ const retryCount = params.statusTimeline.filter(
289
+ (entry) => /(retry|fix|reflect|recover)/i.test(entry.status)
290
+ ).length;
291
+ const completionChecks = params.statusTimeline.filter(
292
+ (entry) => /complet/i.test(entry.status)
293
+ ).length;
294
+ return {
295
+ summary: {
296
+ toolCalls,
297
+ retryCount,
298
+ selfHealCount: retryCount,
299
+ fallbackCount: 0,
300
+ failureCount: 0,
301
+ recoveryCount: 0,
302
+ completionChecks,
303
+ loopCount: retryCount
304
+ },
305
+ timeline,
306
+ source: "sdk-fallback",
307
+ synthetic: true,
308
+ reason: "backend_trace_missing",
309
+ durationMs: params.durationMs
310
+ };
311
+ }
312
+ mergeDeveloperTrace(first, second) {
313
+ if (!first) return second;
314
+ if (!second) return first;
315
+ const firstTimeline = Array.isArray(first.timeline) ? first.timeline : [];
316
+ const secondTimeline = Array.isArray(second.timeline) ? second.timeline : [];
317
+ const mergedTimeline = [...firstTimeline, ...secondTimeline];
318
+ return {
319
+ ...first,
320
+ ...second,
321
+ summary: {
322
+ ...typeof first.summary === "object" && first.summary ? first.summary : {},
323
+ ...typeof second.summary === "object" && second.summary ? second.summary : {}
324
+ },
325
+ ...mergedTimeline.length > 0 ? { timeline: mergedTimeline } : {}
326
+ };
327
+ }
328
+ parseStreamEvent(rawData) {
329
+ const parsed = JSON.parse(rawData);
330
+ if (!parsed || typeof parsed !== "object") {
331
+ return void 0;
332
+ }
333
+ const event = parsed;
334
+ if (typeof event.type !== "string") {
335
+ return void 0;
336
+ }
337
+ return event;
338
+ }
231
339
  /**
232
340
  * Run an agentic query and wait for the full response.
233
341
  *
@@ -273,6 +381,8 @@ var Query = class {
273
381
  modelId: opts.modelId,
274
382
  includeData: opts.includeData,
275
383
  includeDataUrl: opts.includeDataUrl,
384
+ includeDeveloperTrace: opts.includeDeveloperTrace,
385
+ queryDepth: opts.queryDepth,
276
386
  stream: false
277
387
  })
278
388
  }
@@ -286,13 +396,18 @@ var Query = class {
286
396
  );
287
397
  }
288
398
  if (response.success) {
399
+ const developerTrace = response.developerTrace ?? (opts.includeDeveloperTrace ? this.buildSyntheticTraceFromRunResult({
400
+ toolsUsed: response.toolsUsed,
401
+ durationMs: response.durationMs
402
+ }) : void 0);
289
403
  return {
290
404
  response: response.response,
291
405
  toolsUsed: response.toolsUsed,
292
406
  cost: response.cost,
293
407
  durationMs: response.durationMs,
294
408
  data: response.data,
295
- dataUrl: response.dataUrl
409
+ dataUrl: response.dataUrl,
410
+ developerTrace
296
411
  };
297
412
  }
298
413
  throw new ContextError("Unexpected response format from query API");
@@ -304,6 +419,7 @@ var Query = class {
304
419
  * Event types:
305
420
  * - `tool-status` — A tool started executing or changed status
306
421
  * - `text-delta` — A chunk of the AI response text
422
+ * - `developer-trace` — Runtime trace metadata (when includeDeveloperTrace=true)
307
423
  * - `done` — The full response is complete (includes final `QueryResult`)
308
424
  *
309
425
  * @param options - Query options or a plain string question
@@ -319,6 +435,9 @@ var Query = class {
319
435
  * case "text-delta":
320
436
  * process.stdout.write(event.delta);
321
437
  * break;
438
+ * case "developer-trace":
439
+ * console.log("Trace summary:", event.trace.summary);
440
+ * break;
322
441
  * case "done":
323
442
  * console.log("\nCost:", event.result.cost.totalCostUsd);
324
443
  * break;
@@ -338,6 +457,8 @@ var Query = class {
338
457
  modelId: opts.modelId,
339
458
  includeData: opts.includeData,
340
459
  includeDataUrl: opts.includeDataUrl,
460
+ includeDeveloperTrace: opts.includeDeveloperTrace,
461
+ queryDepth: opts.queryDepth,
341
462
  stream: true
342
463
  })
343
464
  });
@@ -348,6 +469,45 @@ var Query = class {
348
469
  const reader = body.getReader();
349
470
  const decoder = new TextDecoder();
350
471
  let buffer = "";
472
+ let aggregatedTrace;
473
+ const statusTimeline = [];
474
+ const parseAndHydrateEvent = (rawData) => {
475
+ const event = this.parseStreamEvent(rawData);
476
+ if (!event) {
477
+ return void 0;
478
+ }
479
+ if (event.type === "developer-trace") {
480
+ aggregatedTrace = this.mergeDeveloperTrace(aggregatedTrace, event.trace);
481
+ return event;
482
+ }
483
+ if (event.type === "tool-status") {
484
+ statusTimeline.push({
485
+ status: event.status,
486
+ tool: {
487
+ id: event.tool.id,
488
+ name: event.tool.name
489
+ }
490
+ });
491
+ return event;
492
+ }
493
+ if (event.type === "done") {
494
+ let mergedTrace = this.mergeDeveloperTrace(
495
+ aggregatedTrace,
496
+ event.result.developerTrace
497
+ );
498
+ if (!mergedTrace && opts.includeDeveloperTrace) {
499
+ mergedTrace = this.buildSyntheticTraceFromStreamStatus({
500
+ statusTimeline,
501
+ toolsUsed: event.result.toolsUsed,
502
+ durationMs: event.result.durationMs
503
+ });
504
+ }
505
+ if (mergedTrace) {
506
+ event.result.developerTrace = mergedTrace;
507
+ }
508
+ }
509
+ return event;
510
+ };
351
511
  try {
352
512
  while (true) {
353
513
  const { done, value } = await reader.read();
@@ -361,7 +521,10 @@ var Query = class {
361
521
  const data = trimmed.slice(6);
362
522
  if (data === "[DONE]") return;
363
523
  try {
364
- yield JSON.parse(data);
524
+ const event = parseAndHydrateEvent(data);
525
+ if (event) {
526
+ yield event;
527
+ }
365
528
  } catch {
366
529
  }
367
530
  }
@@ -371,7 +534,10 @@ var Query = class {
371
534
  const data = buffer.trim().slice(6);
372
535
  if (data !== "[DONE]") {
373
536
  try {
374
- yield JSON.parse(data);
537
+ const event = parseAndHydrateEvent(data);
538
+ if (event) {
539
+ yield event;
540
+ }
375
541
  } catch {
376
542
  }
377
543
  }
@@ -383,9 +549,14 @@ var Query = class {
383
549
  };
384
550
 
385
551
  // src/client/client.ts
552
+ var DEFAULT_BASE_URL = "https://www.ctxprotocol.com";
553
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e5;
554
+ var DEFAULT_STREAM_TIMEOUT_MS = 6e5;
386
555
  var ContextClient = class {
387
556
  apiKey;
388
557
  baseUrl;
558
+ requestTimeoutMs;
559
+ streamTimeoutMs;
389
560
  _closed = false;
390
561
  /**
391
562
  * Discovery resource for searching tools
@@ -408,14 +579,26 @@ var ContextClient = class {
408
579
  *
409
580
  * @param options - Client configuration options
410
581
  * @param options.apiKey - Your Context Protocol API key (format: sk_live_...)
411
- * @param options.baseUrl - Optional base URL override (defaults to https://ctxprotocol.com)
582
+ * @param options.baseUrl - Optional base URL override (defaults to https://www.ctxprotocol.com)
583
+ * @param options.requestTimeoutMs - Optional timeout for non-streaming requests (default 300000ms)
584
+ * @param options.streamTimeoutMs - Optional timeout for establishing stream requests (default 600000ms)
412
585
  */
413
586
  constructor(options) {
414
587
  if (!options.apiKey) {
415
588
  throw new ContextError("API key is required");
416
589
  }
590
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
591
+ const streamTimeoutMs = options.streamTimeoutMs ?? DEFAULT_STREAM_TIMEOUT_MS;
592
+ if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) {
593
+ throw new ContextError("requestTimeoutMs must be a positive number");
594
+ }
595
+ if (!Number.isFinite(streamTimeoutMs) || streamTimeoutMs <= 0) {
596
+ throw new ContextError("streamTimeoutMs must be a positive number");
597
+ }
417
598
  this.apiKey = options.apiKey;
418
- this.baseUrl = (options.baseUrl ?? "https://ctxprotocol.com").replace(/\/$/, "");
599
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
600
+ this.requestTimeoutMs = requestTimeoutMs;
601
+ this.streamTimeoutMs = streamTimeoutMs;
419
602
  this.discovery = new Discovery(this);
420
603
  this.tools = new Tools(this);
421
604
  this.query = new Query(this);
@@ -429,7 +612,7 @@ var ContextClient = class {
429
612
  }
430
613
  /**
431
614
  * Internal method for making authenticated HTTP requests
432
- * Includes timeout (30s) and retry with exponential backoff for transient errors
615
+ * Includes timeout and retry with exponential backoff for transient errors
433
616
  *
434
617
  * @internal
435
618
  */
@@ -439,7 +622,7 @@ var ContextClient = class {
439
622
  }
440
623
  const url = `${this.baseUrl}${endpoint}`;
441
624
  const maxRetries = 3;
442
- const timeoutMs = 3e4;
625
+ const timeoutMs = this.requestTimeoutMs;
443
626
  let lastError;
444
627
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
445
628
  const controller = new AbortController();
@@ -507,6 +690,7 @@ var ContextClient = class {
507
690
  /**
508
691
  * Internal method for making authenticated HTTP requests that returns
509
692
  * the raw Response object. Used for streaming endpoints (SSE).
693
+ * Includes a configurable timeout for stream setup.
510
694
  *
511
695
  * @internal
512
696
  */
@@ -515,14 +699,32 @@ var ContextClient = class {
515
699
  throw new ContextError("Client has been closed");
516
700
  }
517
701
  const url = `${this.baseUrl}${endpoint}`;
518
- const response = await fetch(url, {
519
- ...options,
520
- headers: {
521
- "Content-Type": "application/json",
522
- Authorization: `Bearer ${this.apiKey}`,
523
- ...options.headers
702
+ const controller = new AbortController();
703
+ const timeout = setTimeout(() => controller.abort(), this.streamTimeoutMs);
704
+ let response;
705
+ try {
706
+ response = await fetch(url, {
707
+ ...options,
708
+ signal: controller.signal,
709
+ headers: {
710
+ "Content-Type": "application/json",
711
+ Authorization: `Bearer ${this.apiKey}`,
712
+ ...options.headers
713
+ }
714
+ });
715
+ } catch (error) {
716
+ clearTimeout(timeout);
717
+ const lastError = error instanceof Error ? error : new Error(String(error));
718
+ if (lastError.name === "AbortError") {
719
+ throw new ContextError(
720
+ `Streaming request timed out after ${this.streamTimeoutMs / 1e3}s`,
721
+ void 0,
722
+ 408
723
+ );
524
724
  }
525
- });
725
+ throw new ContextError(lastError.message);
726
+ }
727
+ clearTimeout(timeout);
526
728
  if (!response.ok) {
527
729
  let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
528
730
  let errorCode;