@niadra/sdk 0.7.0 → 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.
Files changed (125) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +15 -6
  3. package/dist/ai-sdk.cjs +3 -4
  4. package/dist/ai-sdk.cjs.map +1 -1
  5. package/dist/ai-sdk.d.cts +3 -3
  6. package/dist/ai-sdk.d.ts +3 -3
  7. package/dist/ai-sdk.js +3 -4
  8. package/dist/ai-sdk.js.map +1 -1
  9. package/dist/anthropic.cjs +1 -1
  10. package/dist/anthropic.cjs.map +1 -1
  11. package/dist/anthropic.d.cts +4 -4
  12. package/dist/anthropic.d.ts +4 -4
  13. package/dist/anthropic.js +1 -1
  14. package/dist/anthropic.js.map +1 -1
  15. package/dist/bedrock.cjs +1 -1
  16. package/dist/bedrock.cjs.map +1 -1
  17. package/dist/bedrock.d.cts +4 -4
  18. package/dist/bedrock.d.ts +4 -4
  19. package/dist/bedrock.js +1 -1
  20. package/dist/bedrock.js.map +1 -1
  21. package/dist/cli.js +107 -51
  22. package/dist/cli.js.map +1 -1
  23. package/dist/{client-DsIxZxZk.d.cts → client-B_ip8T2o.d.cts} +69 -14
  24. package/dist/{client-DsIxZxZk.d.ts → client-B_ip8T2o.d.ts} +69 -14
  25. package/dist/cloudflare-agents.cjs +1 -1
  26. package/dist/cloudflare-agents.cjs.map +1 -1
  27. package/dist/cloudflare-agents.d.cts +3 -3
  28. package/dist/cloudflare-agents.d.ts +3 -3
  29. package/dist/cloudflare-agents.js +1 -1
  30. package/dist/cloudflare-agents.js.map +1 -1
  31. package/dist/elevenlabs.cjs +1 -1
  32. package/dist/elevenlabs.cjs.map +1 -1
  33. package/dist/elevenlabs.d.cts +5 -5
  34. package/dist/elevenlabs.d.ts +5 -5
  35. package/dist/elevenlabs.js +1 -1
  36. package/dist/elevenlabs.js.map +1 -1
  37. package/dist/genkit.cjs +1 -1
  38. package/dist/genkit.cjs.map +1 -1
  39. package/dist/genkit.d.cts +3 -3
  40. package/dist/genkit.d.ts +3 -3
  41. package/dist/genkit.js +1 -1
  42. package/dist/genkit.js.map +1 -1
  43. package/dist/google-adk.cjs +1 -1
  44. package/dist/google-adk.cjs.map +1 -1
  45. package/dist/google-adk.d.cts +3 -3
  46. package/dist/google-adk.d.ts +3 -3
  47. package/dist/google-adk.js +1 -1
  48. package/dist/google-adk.js.map +1 -1
  49. package/dist/google-genai.cjs +1 -1
  50. package/dist/google-genai.cjs.map +1 -1
  51. package/dist/google-genai.d.cts +4 -4
  52. package/dist/google-genai.d.ts +4 -4
  53. package/dist/google-genai.js +1 -1
  54. package/dist/google-genai.js.map +1 -1
  55. package/dist/index.cjs +106 -48
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +6 -8
  58. package/dist/index.d.ts +6 -8
  59. package/dist/index.js +106 -48
  60. package/dist/index.js.map +1 -1
  61. package/dist/{intercept-D7qFCaRb.d.ts → intercept-CBaPK_-k.d.ts} +1 -1
  62. package/dist/{intercept-0_lJE6k1.d.cts → intercept-CxmBLfoj.d.cts} +1 -1
  63. package/dist/langchain.cjs +3 -4
  64. package/dist/langchain.cjs.map +1 -1
  65. package/dist/langchain.d.cts +3 -3
  66. package/dist/langchain.d.ts +3 -3
  67. package/dist/langchain.js +3 -4
  68. package/dist/langchain.js.map +1 -1
  69. package/dist/livekit.cjs +1 -1
  70. package/dist/livekit.cjs.map +1 -1
  71. package/dist/livekit.d.cts +3 -3
  72. package/dist/livekit.d.ts +3 -3
  73. package/dist/livekit.js +1 -1
  74. package/dist/livekit.js.map +1 -1
  75. package/dist/llamaindex.cjs +1 -1
  76. package/dist/llamaindex.cjs.map +1 -1
  77. package/dist/llamaindex.d.cts +3 -3
  78. package/dist/llamaindex.d.ts +3 -3
  79. package/dist/llamaindex.js +1 -1
  80. package/dist/llamaindex.js.map +1 -1
  81. package/dist/mastra.cjs +3 -4
  82. package/dist/mastra.cjs.map +1 -1
  83. package/dist/mastra.d.cts +3 -3
  84. package/dist/mastra.d.ts +3 -3
  85. package/dist/mastra.js +3 -4
  86. package/dist/mastra.js.map +1 -1
  87. package/dist/openai-agents.cjs +1 -1
  88. package/dist/openai-agents.cjs.map +1 -1
  89. package/dist/openai-agents.d.cts +3 -3
  90. package/dist/openai-agents.d.ts +3 -3
  91. package/dist/openai-agents.js +1 -1
  92. package/dist/openai-agents.js.map +1 -1
  93. package/dist/retell.cjs +1 -1
  94. package/dist/retell.cjs.map +1 -1
  95. package/dist/retell.d.cts +5 -5
  96. package/dist/retell.d.ts +5 -5
  97. package/dist/retell.js +1 -1
  98. package/dist/retell.js.map +1 -1
  99. package/dist/{shared-Bb5B59Bu.d.ts → shared-BLkAbvuf.d.ts} +1 -1
  100. package/dist/{shared-DTPWVlWm.d.cts → shared-DEkF2Y_z.d.cts} +1 -1
  101. package/dist/strands.cjs +1 -1
  102. package/dist/strands.cjs.map +1 -1
  103. package/dist/strands.d.cts +3 -3
  104. package/dist/strands.d.ts +3 -3
  105. package/dist/strands.js +1 -1
  106. package/dist/strands.js.map +1 -1
  107. package/dist/twilio.d.cts +4 -4
  108. package/dist/twilio.d.ts +4 -4
  109. package/dist/vapi.cjs +1 -1
  110. package/dist/vapi.cjs.map +1 -1
  111. package/dist/vapi.d.cts +5 -5
  112. package/dist/vapi.d.ts +5 -5
  113. package/dist/vapi.js +1 -1
  114. package/dist/vapi.js.map +1 -1
  115. package/dist/voltagent.cjs +1 -1
  116. package/dist/voltagent.cjs.map +1 -1
  117. package/dist/voltagent.d.cts +3 -3
  118. package/dist/voltagent.d.ts +3 -3
  119. package/dist/voltagent.js +1 -1
  120. package/dist/voltagent.js.map +1 -1
  121. package/dist/{webhook-1ozGyksG.d.ts → webhook-BaraTaA9.d.ts} +1 -1
  122. package/dist/{webhook-CG4om_DK.d.cts → webhook-CuR0Wckd.d.cts} +1 -1
  123. package/dist/whatsapp.d.cts +2 -2
  124. package/dist/whatsapp.d.ts +2 -2
  125. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -518,7 +518,7 @@ function internalRecord(span, ref, act) {
518
518
  }
519
519
 
520
520
  // src/version.ts
521
- var VERSION = "0.7.0";
521
+ var VERSION = "0.9.0";
522
522
 
523
523
  // src/transport.ts
524
524
  var RETRYABLE_WRITE_STATUS = /* @__PURE__ */ new Set([408, 421, 429, 500, 502, 503, 504]);
@@ -2904,6 +2904,11 @@ ${change.field}`)) out.push({ ref, field: change.field, value: change.now, claim
2904
2904
  for (const s of constraints.soft ?? []) said.push([s.attr, s.value]);
2905
2905
  for (const a of constraints.attributes ?? []) said.push([a.name, a.value]);
2906
2906
  for (const [attr, value] of said) out.push({ ref: null, field: attr.split(".").pop() ?? attr, value, claimSafe: true });
2907
+ for (const shown of constraints.already_presented ?? []) {
2908
+ for (const [field, n] of Object.entries(shown.values ?? {})) {
2909
+ out.push({ ref: shown.ref, field, value: n.v, claimSafe: n.claim_safe, role: n.role ?? null });
2910
+ }
2911
+ }
2907
2912
  }
2908
2913
  return out;
2909
2914
  }
@@ -3270,7 +3275,7 @@ function thenable(value) {
3270
3275
  function tool(name, fn, options = {}) {
3271
3276
  const isAsync = isAsyncFunction(fn);
3272
3277
  function bindingOf(frame) {
3273
- return options.binding ?? (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3278
+ return (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3274
3279
  }
3275
3280
  function masks(frame) {
3276
3281
  return options.maskOutput ?? bindingOf(frame)?.capabilities?.mask_output ?? false;
@@ -3385,8 +3390,7 @@ function tool(name, fn, options = {}) {
3385
3390
  const marker = {
3386
3391
  name,
3387
3392
  dryRun: options.dryRun ?? false,
3388
- provenance: options.provenance ?? null,
3389
- binding: options.binding ?? null
3393
+ provenance: options.provenance ?? null
3390
3394
  };
3391
3395
  Object.defineProperty(wrapped, RECORDED, { value: marker });
3392
3396
  return wrapped;
@@ -3648,6 +3652,7 @@ function claimsOf(frame, claims, logger) {
3648
3652
 
3649
3653
  // src/queue.ts
3650
3654
  var HEARTBEAT_WINDOW_MS = 6e4;
3655
+ var MAX_PAUSE_MS = 6e4;
3651
3656
  var EventQueue = class {
3652
3657
  constructor(send, options, logger, now = Date.now) {
3653
3658
  this.send = send;
@@ -3668,6 +3673,8 @@ var EventQueue = class {
3668
3673
  droppedSinceWarning = 0;
3669
3674
  windowStart;
3670
3675
  sentInWindow = 0;
3676
+ pauseMs = 0;
3677
+ resumeAt = 0;
3671
3678
  /** Items waiting to be sent, not counting a batch already in flight. */
3672
3679
  get size() {
3673
3680
  return this.items.length;
@@ -3703,10 +3710,16 @@ var EventQueue = class {
3703
3710
  return run;
3704
3711
  }
3705
3712
  /**
3706
- * Starts a flush without waiting for it. Item failures are logged and settled inside the
3707
- * drain, so only an unexpected error reaches this catch.
3713
+ * Starts a flush without waiting for it: once the pause after a failed batch ends, or at once with
3714
+ * `now` (a caller waits for an item). Item failures are logged and settled inside the drain, so only
3715
+ * an unexpected error reaches this catch.
3708
3716
  */
3709
- flushInBackground() {
3717
+ flushInBackground(now = false) {
3718
+ const paused = this.resumeAt - this.now();
3719
+ if (!now && paused > 0) {
3720
+ this.schedule(paused);
3721
+ return;
3722
+ }
3710
3723
  this.flush().catch((error) => {
3711
3724
  this.logger.error(`event flush failed: ${toNiadraError(error).message}`);
3712
3725
  });
@@ -3723,24 +3736,36 @@ var EventQueue = class {
3723
3736
  while (this.items.length > 0) {
3724
3737
  const batch = this.items.splice(0, this.options.maxBatchSize);
3725
3738
  const heartbeat = this.heartbeat();
3726
- const wire = batch.map((pending) => pending.item);
3727
- if (heartbeat) wire.push(heartbeat);
3728
- await this.sendBatch(batch, wire, report);
3739
+ if (heartbeat) batch.push({ item: heartbeat });
3740
+ if (!await this.sendBatch(batch, report)) break;
3729
3741
  }
3730
3742
  return report;
3731
3743
  }
3732
- async sendBatch(batch, wire, report) {
3744
+ /** Sends one batch; `false` when it went back to the queue and the queue pauses. */
3745
+ async sendBatch(batch, report) {
3746
+ const wire = batch.map((pending) => pending.item);
3733
3747
  let response;
3734
3748
  try {
3735
3749
  response = await this.send(wire);
3736
3750
  } catch (error) {
3737
3751
  const failure = toNiadraError(error);
3738
- report.failed += batch.length;
3739
3752
  report.errors.push(failure);
3740
- this.logger.warn(`dropped ${batch.length} events after retries: ${failure.message}`);
3753
+ if (isTransient(failure)) {
3754
+ for (const pending of batch) pending.settle?.(failure);
3755
+ this.requeue(batch);
3756
+ this.pauseMs = Math.min(MAX_PAUSE_MS, Math.max(this.options.flushIntervalMs, this.pauseMs * 2));
3757
+ this.resumeAt = this.now() + this.pauseMs;
3758
+ this.logger.warn(`could not deliver ${batch.length} events, will retry: ${failure.message}`);
3759
+ this.schedule(this.pauseMs);
3760
+ return false;
3761
+ }
3762
+ report.failed += batch.length;
3763
+ this.logger.warn(`dropped ${batch.length} events refused by the API: ${failure.message}`);
3741
3764
  for (const pending of batch) pending.settle?.(failure);
3742
- return;
3765
+ return true;
3743
3766
  }
3767
+ this.pauseMs = 0;
3768
+ this.resumeAt = 0;
3744
3769
  const rejected = /* @__PURE__ */ new Map();
3745
3770
  for (const itemError of response.errors) {
3746
3771
  const detail = itemError.detail ? `: ${itemError.detail}` : "";
@@ -3751,6 +3776,7 @@ var EventQueue = class {
3751
3776
  this.logger.warn(`server rejected ${rejected.size} of ${wire.length} items (${codes2})`);
3752
3777
  }
3753
3778
  batch.forEach((pending, index) => {
3779
+ if (pending.item.type === "heartbeat") return;
3754
3780
  const error = rejected.get(index) ?? null;
3755
3781
  if (error) {
3756
3782
  report.failed++;
@@ -3761,6 +3787,17 @@ var EventQueue = class {
3761
3787
  }
3762
3788
  pending.settle?.(error);
3763
3789
  });
3790
+ return true;
3791
+ }
3792
+ /** Puts a batch back at the front, keeping only what still fits; what does not is dropped. */
3793
+ requeue(batch) {
3794
+ const room = Math.max(0, this.options.maxQueueSize - this.items.length);
3795
+ const lost = batch.slice(room);
3796
+ this.items.unshift(...batch.slice(0, room));
3797
+ if (lost.length > 0) {
3798
+ this.logger.warn(`event queue is full; dropped ${lost.length} events`);
3799
+ for (const pending of lost) pending.settle?.(new NiadraError("event queue is full"));
3800
+ }
3764
3801
  }
3765
3802
  /**
3766
3803
  * Once a minute, the next batch carries the number of items sent in the previous window.
@@ -3811,7 +3848,7 @@ function unref(timer) {
3811
3848
  var MAX_TURNS = 50;
3812
3849
  var COMPRESSED_ROOM = 4 * 1024 * 1024 - 64 * 1024;
3813
3850
  var DECODED_ROOM = 16 * 1024 * 1024 - 256 * 1024;
3814
- var MAX_PAUSE_MS = 6e4;
3851
+ var MAX_PAUSE_MS2 = 6e4;
3815
3852
  var encoder2 = new TextEncoder();
3816
3853
  async function encode(records) {
3817
3854
  const raw = encoder2.encode(JSON.stringify({ turns: records }));
@@ -3970,7 +4007,7 @@ var TurnSender = class {
3970
4007
  }
3971
4008
  if (isTransient(error)) {
3972
4009
  this.queue.requeue(batch.frames);
3973
- this.pause = Math.min(MAX_PAUSE_MS, Math.max(this.intervalMs, this.pause * 2));
4010
+ this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
3974
4011
  this.resumeAt = this.now() + this.pause;
3975
4012
  this.logger.warn(`could not deliver ${batch.frames.length} turns, will retry (${toNiadraError(error).name})`);
3976
4013
  return false;
@@ -4436,7 +4473,10 @@ var SuppressionCopy = class {
4436
4473
  }
4437
4474
  this.saltValue = salt;
4438
4475
  }
4439
- /** Applies one page, in order. `true` when more pages follow. */
4476
+ /**
4477
+ * Applies one page, in order. `true` when more pages follow: a full page. Every page carries the cursor to
4478
+ * read from next, the last one too, which the next read starts from.
4479
+ */
4440
4480
  apply(page) {
4441
4481
  if (page.salt_id !== this.saltValue?.salt_id) {
4442
4482
  this.saltValue = null;
@@ -4451,8 +4491,9 @@ var SuppressionCopy = class {
4451
4491
  }
4452
4492
  this.cursor = page.next_cursor ?? this.cursor;
4453
4493
  this.absent = false;
4454
- if (!page.next_cursor) this.readAt = this.now();
4455
- return Boolean(page.next_cursor);
4494
+ const more = Boolean(page.next_cursor) && page.items.length >= PAGE;
4495
+ if (!more) this.readAt = this.now();
4496
+ return more;
4456
4497
  }
4457
4498
  /** A 404: the space keeps no list. Anything else: the copy stays as it is. */
4458
4499
  failed(error) {
@@ -4677,7 +4718,7 @@ var ContactGateway = class {
4677
4718
 
4678
4719
  // src/outbox.ts
4679
4720
  var CAPACITY = 1e3;
4680
- var MAX_PAUSE_MS2 = 6e4;
4721
+ var MAX_PAUSE_MS3 = 6e4;
4681
4722
  var Outbox = class {
4682
4723
  constructor(logger, intervalMs = 1e3, now = Date.now) {
4683
4724
  this.logger = logger;
@@ -4745,7 +4786,7 @@ var Outbox = class {
4745
4786
  answer = await write.send();
4746
4787
  } catch (error) {
4747
4788
  if (isTransient(error)) {
4748
- this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
4789
+ this.pause = Math.min(MAX_PAUSE_MS3, Math.max(this.intervalMs, this.pause * 2));
4749
4790
  this.resumeAt = this.now() + this.pause;
4750
4791
  return false;
4751
4792
  }
@@ -5528,6 +5569,11 @@ var ContextCache = class {
5528
5569
  }
5529
5570
  return true;
5530
5571
  }
5572
+ /** Milliseconds since the server last sent or confirmed the pack held for `key`; `null` when none is held. */
5573
+ age(key2) {
5574
+ const entry = this.entries.get(key2);
5575
+ return entry ? this.now() - entry.confirmedAt : null;
5576
+ }
5531
5577
  /** The cached pack's ETag, without touching the order of eviction. */
5532
5578
  etag(key2) {
5533
5579
  return this.entries.get(key2)?.response.etag ?? null;
@@ -5750,7 +5796,7 @@ function mergeNotModified(cached, fresh) {
5750
5796
  coordination: fresh.coordination ?? cached.coordination ?? null
5751
5797
  };
5752
5798
  }
5753
- function resultFrom(response, source, error = null) {
5799
+ function resultFrom(response, source, error = null, ageMs = 0) {
5754
5800
  const holdout = response.path === "holdout";
5755
5801
  const text2 = holdout ? "" : response.text ?? "";
5756
5802
  const pack = holdout ? null : response.pack ?? null;
@@ -5759,10 +5805,10 @@ function resultFrom(response, source, error = null) {
5759
5805
  state: response.state ?? null,
5760
5806
  coordination: response.coordination ?? null
5761
5807
  };
5762
- return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, response, error, ...blocks2 };
5808
+ return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, ageMs, response, error, ...blocks2 };
5763
5809
  }
5764
5810
  function emptyResult(error) {
5765
- return { text: "", suffix: "", variables: {}, pack: null, source: "none", response: null, error, constraints: null, state: null, coordination: null };
5811
+ return { text: "", suffix: "", variables: {}, pack: null, source: "none", ageMs: null, response: null, error, constraints: null, state: null, coordination: null };
5766
5812
  }
5767
5813
  function renderSuffix(response) {
5768
5814
  if (response.path === "holdout") return "";
@@ -8556,19 +8602,29 @@ var Niadra = class _Niadra {
8556
8602
  */
8557
8603
  async mayContact(handle, purpose, options = {}) {
8558
8604
  if (this.core && this.suppressions.due()) {
8559
- const read = this.suppressions.read(
8560
- {
8561
- salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8562
- page: (cursor, limit3) => this.api.suppressions({ cursor, limit: limit3 }, { timeout: this.timeouts.navigation })
8563
- },
8564
- this.suppressions.held ? this.timeouts.write : this.timeouts.navigation
8565
- );
8605
+ const read = this.readSuppressions(this.suppressions.held ? this.timeouts.write : this.timeouts.navigation);
8566
8606
  if (!this.suppressions.held) await read;
8567
8607
  }
8568
8608
  const checkOptions = { channel: options.channel ?? null };
8569
8609
  if (options.failOpen !== void 0) checkOptions.failOpen = options.failOpen;
8570
8610
  return this.suppressions.mayContact(handle, purpose, checkOptions);
8571
8611
  }
8612
+ readSuppressions(budgetMs) {
8613
+ return this.suppressions.read(
8614
+ {
8615
+ salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8616
+ page: (cursor, limit3) => this.api.suppressions({ cursor, limit: limit3 }, { timeout: this.timeouts.navigation })
8617
+ },
8618
+ budgetMs
8619
+ );
8620
+ }
8621
+ /**
8622
+ * Reads the local copy of the suppression list in the background when it is due: a check that Niadra does not
8623
+ * answer falls back on it, so an opt-out holds through an outage.
8624
+ */
8625
+ keepSuppressions() {
8626
+ if (this.core && this.suppressions.due()) void this.readSuppressions(this.timeouts.write);
8627
+ }
8572
8628
  /**
8573
8629
  * The build a turn runs on, for `conversation.turn({ build })`: each prompt's version, the digest of the
8574
8630
  * corpus the agent consults (computed by you, never the files), the model, your context assembler's version
@@ -8642,7 +8698,10 @@ var Niadra = class _Niadra {
8642
8698
  states: this.states,
8643
8699
  contract: () => this.currentContract(),
8644
8700
  internalText: this.internalText,
8645
- check: (request, timeoutMs) => this.api.check(request, { timeout: timeoutMs }),
8701
+ check: (request, timeoutMs) => {
8702
+ if (request.direction === "outbound") this.keepSuppressions();
8703
+ return this.api.check(request, { timeout: timeoutMs });
8704
+ },
8646
8705
  claim: (request, timeoutMs) => this.api.claim(request, {}, { timeout: timeoutMs }),
8647
8706
  verifyClaim: (ref, field, value, options) => this.verifyClaim(ref, field, value, options),
8648
8707
  enabled: this.enabled,
@@ -8756,21 +8815,21 @@ var Niadra = class _Niadra {
8756
8815
  }
8757
8816
  const key2 = cacheKey(request);
8758
8817
  const hit = cache.lookup(key2);
8759
- if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache");
8818
+ if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache", null, cache.age(key2));
8760
8819
  if (hit?.freshness === "stale") {
8761
8820
  this.revalidate(core, cache, key2, scope, request, timeout, options.headers).catch((error) => {
8762
8821
  this.logger.debug(`background context refresh failed: ${describe(toNiadraError(error))}`);
8763
8822
  });
8764
- return resultFrom(delivered(hit.response, cache.take(key2)), "stale");
8823
+ return resultFrom(delivered(hit.response, cache.take(key2)), "stale", null, cache.age(key2));
8765
8824
  }
8766
8825
  try {
8767
8826
  const pending = this.revalidate(core, cache, key2, scope, request, timeout, options.headers);
8768
8827
  const { response, source } = await abortable(pending, options.signal);
8769
- return resultFrom(delivered(response, cache.take(key2)), source);
8828
+ return resultFrom(delivered(response, cache.take(key2)), source, null, source === "network" ? 0 : cache.age(key2));
8770
8829
  } catch (error) {
8771
8830
  const failure = toNiadraError(error);
8772
8831
  const fallback2 = cache.lookup(key2);
8773
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8832
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
8774
8833
  }
8775
8834
  }
8776
8835
  /**
@@ -8792,11 +8851,11 @@ var Niadra = class _Niadra {
8792
8851
  const failure = toNiadraError(error);
8793
8852
  this.observeAuth(failure, key2);
8794
8853
  const fallback2 = cache && key2 ? cache.lookup(key2) : null;
8795
- return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8854
+ return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, key2 ? cache?.age(key2) ?? null : null);
8796
8855
  }
8797
8856
  if (!cache || !key2 || !scope) return resultFrom(response, "network");
8798
8857
  if (response.degraded && !response.not_modified && cached) {
8799
- return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback");
8858
+ return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback", null, cache.age(key2));
8800
8859
  }
8801
8860
  const merged = response.not_modified && cached ? mergeNotModified(cached.response, response) : response;
8802
8861
  cache.store(key2, scope, withoutSlots(merged), generation);
@@ -9298,11 +9357,11 @@ var Niadra = class _Niadra {
9298
9357
  }
9299
9358
  });
9300
9359
  }
9301
- contextFailure(error, fallback2) {
9360
+ contextFailure(error, fallback2, ageMs = null) {
9302
9361
  if (this.strict) throw error;
9303
9362
  if (fallback2) {
9304
9363
  this.logger.warn(`context request failed, serving the last good pack: ${describe(error)}`);
9305
- return resultFrom(fallback2, "fallback", error);
9364
+ return resultFrom(fallback2, "fallback", error, ageMs);
9306
9365
  }
9307
9366
  this.logger.warn(`context unavailable: ${describe(error)}`);
9308
9367
  return emptyResult(error);
@@ -9403,11 +9462,11 @@ var Niadra = class _Niadra {
9403
9462
  });
9404
9463
  }
9405
9464
  const response = compose(delivered(hit.response, cache.take(key2)), chosen2);
9406
- return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale");
9465
+ return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale", null, cache.age(key2));
9407
9466
  } catch (error) {
9408
9467
  const failure = toNiadraError(error);
9409
9468
  const fallback2 = cache.lookup(key2);
9410
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
9469
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
9411
9470
  }
9412
9471
  }
9413
9472
  /** Starts one read of a line in the background. */
@@ -9550,7 +9609,7 @@ var Niadra = class _Niadra {
9550
9609
  else resolve({ ok: false, idempotency_key: key2, error });
9551
9610
  };
9552
9611
  core.queue.push(item, settle2);
9553
- core.queue.flushInBackground();
9612
+ core.queue.flushInBackground(true);
9554
9613
  });
9555
9614
  }
9556
9615
  async endScope(item, scope) {
@@ -11570,7 +11629,8 @@ var Counterfactual = class {
11570
11629
  const fn = this.tools[tool2];
11571
11630
  if (fn === void 0) throw new NiadraError(`no function for the tool ${tool2}: pass it in tools`);
11572
11631
  if (!Number.isInteger(k) || k < 1 || k > MAX_K) throw new RangeError(`k must be 1 to ${String(MAX_K)}`);
11573
- await this.niadra.profile();
11632
+ const raw = await this.served(tool2);
11633
+ if (raw === null) throw new NiadraError(`the space binds no tool ${tool2} for this key's source: see its tool-bindings`);
11574
11634
  const ids = [...turnIds];
11575
11635
  if ((options.scenarioIds ?? []).length > 0) {
11576
11636
  const found2 = await this.niadra.callRoute({ method: "GET", path: "/v1/scenarios", query: { ids: (options.scenarioIds ?? []).join(","), limit: 50 } });
@@ -11588,7 +11648,7 @@ var Counterfactual = class {
11588
11648
  continue;
11589
11649
  }
11590
11650
  for (const call of calls(record, tool2)) {
11591
- const found2 = await this.case(record, call, fn, element, k);
11651
+ const found2 = await this.case(record, call, fn, raw, element, k);
11592
11652
  if (found2 === null) run.untouched++;
11593
11653
  else run.cases.push(found2);
11594
11654
  }
@@ -11598,17 +11658,15 @@ var Counterfactual = class {
11598
11658
  run.report = await this.niadra.callRoute({ method: "POST", path: "/v1/measure/counterfactual-runs", body: report });
11599
11659
  return run;
11600
11660
  }
11601
- async case(record, call, fn, element, k) {
11661
+ async case(record, call, fn, raw, element, k) {
11602
11662
  const base = { turn_id: record.turn_id, call_id: call.call_id };
11603
11663
  const tool2 = String(call.name);
11604
11664
  const wrapped = recordedTool(fn);
11605
- const raw = this.options.bindings?.[tool2] ?? wrapped?.binding ?? await this.served(tool2);
11606
11665
  let block;
11607
11666
  let args;
11608
11667
  try {
11609
11668
  block = await this.value(record, blockBlob(record, call));
11610
11669
  args = { ...await this.value(record, call.args) };
11611
- if (raw === null) throw new BlobError("the tool has no binding");
11612
11670
  } catch {
11613
11671
  return { ...base, status: "infrastructure_error", dry_run: false };
11614
11672
  }