@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/dist/index.js CHANGED
@@ -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;