@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/cli.js CHANGED
@@ -503,7 +503,7 @@ function internalRecord(span, ref, act) {
503
503
  }
504
504
 
505
505
  // src/version.ts
506
- var VERSION = "0.7.0";
506
+ var VERSION = "0.9.0";
507
507
 
508
508
  // src/transport.ts
509
509
  var RETRYABLE_WRITE_STATUS = /* @__PURE__ */ new Set([408, 421, 429, 500, 502, 503, 504]);
@@ -2885,6 +2885,11 @@ ${change.field}`)) out.push({ ref, field: change.field, value: change.now, claim
2885
2885
  for (const s of constraints.soft ?? []) said.push([s.attr, s.value]);
2886
2886
  for (const a of constraints.attributes ?? []) said.push([a.name, a.value]);
2887
2887
  for (const [attr, value] of said) out.push({ ref: null, field: attr.split(".").pop() ?? attr, value, claimSafe: true });
2888
+ for (const shown of constraints.already_presented ?? []) {
2889
+ for (const [field, n] of Object.entries(shown.values ?? {})) {
2890
+ out.push({ ref: shown.ref, field, value: n.v, claimSafe: n.claim_safe, role: n.role ?? null });
2891
+ }
2892
+ }
2888
2893
  }
2889
2894
  return out;
2890
2895
  }
@@ -3251,7 +3256,7 @@ function thenable(value) {
3251
3256
  function tool(name, fn, options = {}) {
3252
3257
  const isAsync = isAsyncFunction(fn);
3253
3258
  function bindingOf(frame) {
3254
- return options.binding ?? (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3259
+ return (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3255
3260
  }
3256
3261
  function masks(frame) {
3257
3262
  return options.maskOutput ?? bindingOf(frame)?.capabilities?.mask_output ?? false;
@@ -3366,8 +3371,7 @@ function tool(name, fn, options = {}) {
3366
3371
  const marker = {
3367
3372
  name,
3368
3373
  dryRun: options.dryRun ?? false,
3369
- provenance: options.provenance ?? null,
3370
- binding: options.binding ?? null
3374
+ provenance: options.provenance ?? null
3371
3375
  };
3372
3376
  Object.defineProperty(wrapped, RECORDED, { value: marker });
3373
3377
  return wrapped;
@@ -3629,6 +3633,7 @@ function claimsOf(frame, claims, logger) {
3629
3633
 
3630
3634
  // src/queue.ts
3631
3635
  var HEARTBEAT_WINDOW_MS = 6e4;
3636
+ var MAX_PAUSE_MS = 6e4;
3632
3637
  var EventQueue = class {
3633
3638
  constructor(send, options, logger, now = Date.now) {
3634
3639
  this.send = send;
@@ -3649,6 +3654,8 @@ var EventQueue = class {
3649
3654
  droppedSinceWarning = 0;
3650
3655
  windowStart;
3651
3656
  sentInWindow = 0;
3657
+ pauseMs = 0;
3658
+ resumeAt = 0;
3652
3659
  /** Items waiting to be sent, not counting a batch already in flight. */
3653
3660
  get size() {
3654
3661
  return this.items.length;
@@ -3684,10 +3691,16 @@ var EventQueue = class {
3684
3691
  return run;
3685
3692
  }
3686
3693
  /**
3687
- * Starts a flush without waiting for it. Item failures are logged and settled inside the
3688
- * drain, so only an unexpected error reaches this catch.
3694
+ * Starts a flush without waiting for it: once the pause after a failed batch ends, or at once with
3695
+ * `now` (a caller waits for an item). Item failures are logged and settled inside the drain, so only
3696
+ * an unexpected error reaches this catch.
3689
3697
  */
3690
- flushInBackground() {
3698
+ flushInBackground(now = false) {
3699
+ const paused = this.resumeAt - this.now();
3700
+ if (!now && paused > 0) {
3701
+ this.schedule(paused);
3702
+ return;
3703
+ }
3691
3704
  this.flush().catch((error) => {
3692
3705
  this.logger.error(`event flush failed: ${toNiadraError(error).message}`);
3693
3706
  });
@@ -3704,24 +3717,36 @@ var EventQueue = class {
3704
3717
  while (this.items.length > 0) {
3705
3718
  const batch = this.items.splice(0, this.options.maxBatchSize);
3706
3719
  const heartbeat = this.heartbeat();
3707
- const wire = batch.map((pending) => pending.item);
3708
- if (heartbeat) wire.push(heartbeat);
3709
- await this.sendBatch(batch, wire, report);
3720
+ if (heartbeat) batch.push({ item: heartbeat });
3721
+ if (!await this.sendBatch(batch, report)) break;
3710
3722
  }
3711
3723
  return report;
3712
3724
  }
3713
- async sendBatch(batch, wire, report) {
3725
+ /** Sends one batch; `false` when it went back to the queue and the queue pauses. */
3726
+ async sendBatch(batch, report) {
3727
+ const wire = batch.map((pending) => pending.item);
3714
3728
  let response;
3715
3729
  try {
3716
3730
  response = await this.send(wire);
3717
3731
  } catch (error) {
3718
3732
  const failure = toNiadraError(error);
3719
- report.failed += batch.length;
3720
3733
  report.errors.push(failure);
3721
- this.logger.warn(`dropped ${batch.length} events after retries: ${failure.message}`);
3734
+ if (isTransient(failure)) {
3735
+ for (const pending of batch) pending.settle?.(failure);
3736
+ this.requeue(batch);
3737
+ this.pauseMs = Math.min(MAX_PAUSE_MS, Math.max(this.options.flushIntervalMs, this.pauseMs * 2));
3738
+ this.resumeAt = this.now() + this.pauseMs;
3739
+ this.logger.warn(`could not deliver ${batch.length} events, will retry: ${failure.message}`);
3740
+ this.schedule(this.pauseMs);
3741
+ return false;
3742
+ }
3743
+ report.failed += batch.length;
3744
+ this.logger.warn(`dropped ${batch.length} events refused by the API: ${failure.message}`);
3722
3745
  for (const pending of batch) pending.settle?.(failure);
3723
- return;
3746
+ return true;
3724
3747
  }
3748
+ this.pauseMs = 0;
3749
+ this.resumeAt = 0;
3725
3750
  const rejected = /* @__PURE__ */ new Map();
3726
3751
  for (const itemError of response.errors) {
3727
3752
  const detail = itemError.detail ? `: ${itemError.detail}` : "";
@@ -3732,6 +3757,7 @@ var EventQueue = class {
3732
3757
  this.logger.warn(`server rejected ${rejected.size} of ${wire.length} items (${codes2})`);
3733
3758
  }
3734
3759
  batch.forEach((pending, index) => {
3760
+ if (pending.item.type === "heartbeat") return;
3735
3761
  const error = rejected.get(index) ?? null;
3736
3762
  if (error) {
3737
3763
  report.failed++;
@@ -3742,6 +3768,17 @@ var EventQueue = class {
3742
3768
  }
3743
3769
  pending.settle?.(error);
3744
3770
  });
3771
+ return true;
3772
+ }
3773
+ /** Puts a batch back at the front, keeping only what still fits; what does not is dropped. */
3774
+ requeue(batch) {
3775
+ const room = Math.max(0, this.options.maxQueueSize - this.items.length);
3776
+ const lost = batch.slice(room);
3777
+ this.items.unshift(...batch.slice(0, room));
3778
+ if (lost.length > 0) {
3779
+ this.logger.warn(`event queue is full; dropped ${lost.length} events`);
3780
+ for (const pending of lost) pending.settle?.(new NiadraError("event queue is full"));
3781
+ }
3745
3782
  }
3746
3783
  /**
3747
3784
  * Once a minute, the next batch carries the number of items sent in the previous window.
@@ -3792,7 +3829,7 @@ function unref(timer) {
3792
3829
  var MAX_TURNS = 50;
3793
3830
  var COMPRESSED_ROOM = 4 * 1024 * 1024 - 64 * 1024;
3794
3831
  var DECODED_ROOM = 16 * 1024 * 1024 - 256 * 1024;
3795
- var MAX_PAUSE_MS = 6e4;
3832
+ var MAX_PAUSE_MS2 = 6e4;
3796
3833
  var encoder2 = new TextEncoder();
3797
3834
  async function encode(records) {
3798
3835
  const raw = encoder2.encode(JSON.stringify({ turns: records }));
@@ -3951,7 +3988,7 @@ var TurnSender = class {
3951
3988
  }
3952
3989
  if (isTransient(error)) {
3953
3990
  this.queue.requeue(batch.frames);
3954
- this.pause = Math.min(MAX_PAUSE_MS, Math.max(this.intervalMs, this.pause * 2));
3991
+ this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
3955
3992
  this.resumeAt = this.now() + this.pause;
3956
3993
  this.logger.warn(`could not deliver ${batch.frames.length} turns, will retry (${toNiadraError(error).name})`);
3957
3994
  return false;
@@ -4417,7 +4454,10 @@ var SuppressionCopy = class {
4417
4454
  }
4418
4455
  this.saltValue = salt;
4419
4456
  }
4420
- /** Applies one page, in order. `true` when more pages follow. */
4457
+ /**
4458
+ * Applies one page, in order. `true` when more pages follow: a full page. Every page carries the cursor to
4459
+ * read from next, the last one too, which the next read starts from.
4460
+ */
4421
4461
  apply(page) {
4422
4462
  if (page.salt_id !== this.saltValue?.salt_id) {
4423
4463
  this.saltValue = null;
@@ -4432,8 +4472,9 @@ var SuppressionCopy = class {
4432
4472
  }
4433
4473
  this.cursor = page.next_cursor ?? this.cursor;
4434
4474
  this.absent = false;
4435
- if (!page.next_cursor) this.readAt = this.now();
4436
- return Boolean(page.next_cursor);
4475
+ const more = Boolean(page.next_cursor) && page.items.length >= PAGE;
4476
+ if (!more) this.readAt = this.now();
4477
+ return more;
4437
4478
  }
4438
4479
  /** A 404: the space keeps no list. Anything else: the copy stays as it is. */
4439
4480
  failed(error) {
@@ -4658,7 +4699,7 @@ var ContactGateway = class {
4658
4699
 
4659
4700
  // src/outbox.ts
4660
4701
  var CAPACITY = 1e3;
4661
- var MAX_PAUSE_MS2 = 6e4;
4702
+ var MAX_PAUSE_MS3 = 6e4;
4662
4703
  var Outbox = class {
4663
4704
  constructor(logger, intervalMs = 1e3, now = Date.now) {
4664
4705
  this.logger = logger;
@@ -4726,7 +4767,7 @@ var Outbox = class {
4726
4767
  answer = await write.send();
4727
4768
  } catch (error) {
4728
4769
  if (isTransient(error)) {
4729
- this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
4770
+ this.pause = Math.min(MAX_PAUSE_MS3, Math.max(this.intervalMs, this.pause * 2));
4730
4771
  this.resumeAt = this.now() + this.pause;
4731
4772
  return false;
4732
4773
  }
@@ -5509,6 +5550,11 @@ var ContextCache = class {
5509
5550
  }
5510
5551
  return true;
5511
5552
  }
5553
+ /** Milliseconds since the server last sent or confirmed the pack held for `key`; `null` when none is held. */
5554
+ age(key2) {
5555
+ const entry = this.entries.get(key2);
5556
+ return entry ? this.now() - entry.confirmedAt : null;
5557
+ }
5512
5558
  /** The cached pack's ETag, without touching the order of eviction. */
5513
5559
  etag(key2) {
5514
5560
  return this.entries.get(key2)?.response.etag ?? null;
@@ -5702,7 +5748,7 @@ function mergeNotModified(cached, fresh) {
5702
5748
  coordination: fresh.coordination ?? cached.coordination ?? null
5703
5749
  };
5704
5750
  }
5705
- function resultFrom(response, source, error = null) {
5751
+ function resultFrom(response, source, error = null, ageMs = 0) {
5706
5752
  const holdout = response.path === "holdout";
5707
5753
  const text2 = holdout ? "" : response.text ?? "";
5708
5754
  const pack = holdout ? null : response.pack ?? null;
@@ -5711,10 +5757,10 @@ function resultFrom(response, source, error = null) {
5711
5757
  state: response.state ?? null,
5712
5758
  coordination: response.coordination ?? null
5713
5759
  };
5714
- return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, response, error, ...blocks2 };
5760
+ return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, ageMs, response, error, ...blocks2 };
5715
5761
  }
5716
5762
  function emptyResult(error) {
5717
- return { text: "", suffix: "", variables: {}, pack: null, source: "none", response: null, error, constraints: null, state: null, coordination: null };
5763
+ return { text: "", suffix: "", variables: {}, pack: null, source: "none", ageMs: null, response: null, error, constraints: null, state: null, coordination: null };
5718
5764
  }
5719
5765
  function renderSuffix(response) {
5720
5766
  if (response.path === "holdout") return "";
@@ -8507,19 +8553,29 @@ var Niadra = class _Niadra {
8507
8553
  */
8508
8554
  async mayContact(handle, purpose, options = {}) {
8509
8555
  if (this.core && this.suppressions.due()) {
8510
- const read2 = this.suppressions.read(
8511
- {
8512
- salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8513
- page: (cursor, limit2) => this.api.suppressions({ cursor, limit: limit2 }, { timeout: this.timeouts.navigation })
8514
- },
8515
- this.suppressions.held ? this.timeouts.write : this.timeouts.navigation
8516
- );
8556
+ const read2 = this.readSuppressions(this.suppressions.held ? this.timeouts.write : this.timeouts.navigation);
8517
8557
  if (!this.suppressions.held) await read2;
8518
8558
  }
8519
8559
  const checkOptions = { channel: options.channel ?? null };
8520
8560
  if (options.failOpen !== void 0) checkOptions.failOpen = options.failOpen;
8521
8561
  return this.suppressions.mayContact(handle, purpose, checkOptions);
8522
8562
  }
8563
+ readSuppressions(budgetMs) {
8564
+ return this.suppressions.read(
8565
+ {
8566
+ salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8567
+ page: (cursor, limit2) => this.api.suppressions({ cursor, limit: limit2 }, { timeout: this.timeouts.navigation })
8568
+ },
8569
+ budgetMs
8570
+ );
8571
+ }
8572
+ /**
8573
+ * Reads the local copy of the suppression list in the background when it is due: a check that Niadra does not
8574
+ * answer falls back on it, so an opt-out holds through an outage.
8575
+ */
8576
+ keepSuppressions() {
8577
+ if (this.core && this.suppressions.due()) void this.readSuppressions(this.timeouts.write);
8578
+ }
8523
8579
  /**
8524
8580
  * The build a turn runs on, for `conversation.turn({ build })`: each prompt's version, the digest of the
8525
8581
  * corpus the agent consults (computed by you, never the files), the model, your context assembler's version
@@ -8593,7 +8649,10 @@ var Niadra = class _Niadra {
8593
8649
  states: this.states,
8594
8650
  contract: () => this.currentContract(),
8595
8651
  internalText: this.internalText,
8596
- check: (request, timeoutMs) => this.api.check(request, { timeout: timeoutMs }),
8652
+ check: (request, timeoutMs) => {
8653
+ if (request.direction === "outbound") this.keepSuppressions();
8654
+ return this.api.check(request, { timeout: timeoutMs });
8655
+ },
8597
8656
  claim: (request, timeoutMs) => this.api.claim(request, {}, { timeout: timeoutMs }),
8598
8657
  verifyClaim: (ref, field, value, options) => this.verifyClaim(ref, field, value, options),
8599
8658
  enabled: this.enabled,
@@ -8707,21 +8766,21 @@ var Niadra = class _Niadra {
8707
8766
  }
8708
8767
  const key2 = cacheKey(request);
8709
8768
  const hit = cache.lookup(key2);
8710
- if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache");
8769
+ if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache", null, cache.age(key2));
8711
8770
  if (hit?.freshness === "stale") {
8712
8771
  this.revalidate(core, cache, key2, scope, request, timeout, options.headers).catch((error) => {
8713
8772
  this.logger.debug(`background context refresh failed: ${describe(toNiadraError(error))}`);
8714
8773
  });
8715
- return resultFrom(delivered(hit.response, cache.take(key2)), "stale");
8774
+ return resultFrom(delivered(hit.response, cache.take(key2)), "stale", null, cache.age(key2));
8716
8775
  }
8717
8776
  try {
8718
8777
  const pending = this.revalidate(core, cache, key2, scope, request, timeout, options.headers);
8719
8778
  const { response, source } = await abortable(pending, options.signal);
8720
- return resultFrom(delivered(response, cache.take(key2)), source);
8779
+ return resultFrom(delivered(response, cache.take(key2)), source, null, source === "network" ? 0 : cache.age(key2));
8721
8780
  } catch (error) {
8722
8781
  const failure = toNiadraError(error);
8723
8782
  const fallback2 = cache.lookup(key2);
8724
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8783
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
8725
8784
  }
8726
8785
  }
8727
8786
  /**
@@ -8743,11 +8802,11 @@ var Niadra = class _Niadra {
8743
8802
  const failure = toNiadraError(error);
8744
8803
  this.observeAuth(failure, key2);
8745
8804
  const fallback2 = cache && key2 ? cache.lookup(key2) : null;
8746
- return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8805
+ return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, key2 ? cache?.age(key2) ?? null : null);
8747
8806
  }
8748
8807
  if (!cache || !key2 || !scope) return resultFrom(response, "network");
8749
8808
  if (response.degraded && !response.not_modified && cached) {
8750
- return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback");
8809
+ return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback", null, cache.age(key2));
8751
8810
  }
8752
8811
  const merged = response.not_modified && cached ? mergeNotModified(cached.response, response) : response;
8753
8812
  cache.store(key2, scope, withoutSlots(merged), generation);
@@ -9249,11 +9308,11 @@ var Niadra = class _Niadra {
9249
9308
  }
9250
9309
  });
9251
9310
  }
9252
- contextFailure(error, fallback2) {
9311
+ contextFailure(error, fallback2, ageMs = null) {
9253
9312
  if (this.strict) throw error;
9254
9313
  if (fallback2) {
9255
9314
  this.logger.warn(`context request failed, serving the last good pack: ${describe(error)}`);
9256
- return resultFrom(fallback2, "fallback", error);
9315
+ return resultFrom(fallback2, "fallback", error, ageMs);
9257
9316
  }
9258
9317
  this.logger.warn(`context unavailable: ${describe(error)}`);
9259
9318
  return emptyResult(error);
@@ -9354,11 +9413,11 @@ var Niadra = class _Niadra {
9354
9413
  });
9355
9414
  }
9356
9415
  const response = compose(delivered(hit.response, cache.take(key2)), chosen2);
9357
- return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale");
9416
+ return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale", null, cache.age(key2));
9358
9417
  } catch (error) {
9359
9418
  const failure = toNiadraError(error);
9360
9419
  const fallback2 = cache.lookup(key2);
9361
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
9420
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
9362
9421
  }
9363
9422
  }
9364
9423
  /** Starts one read of a line in the background. */
@@ -9501,7 +9560,7 @@ var Niadra = class _Niadra {
9501
9560
  else resolve({ ok: false, idempotency_key: key2, error });
9502
9561
  };
9503
9562
  core.queue.push(item, settle2);
9504
- core.queue.flushInBackground();
9563
+ core.queue.flushInBackground(true);
9505
9564
  });
9506
9565
  }
9507
9566
  async endScope(item, scope) {
@@ -9971,7 +10030,8 @@ var Counterfactual = class {
9971
10030
  const fn = this.tools[tool2];
9972
10031
  if (fn === void 0) throw new NiadraError(`no function for the tool ${tool2}: pass it in tools`);
9973
10032
  if (!Number.isInteger(k) || k < 1 || k > MAX_K) throw new RangeError(`k must be 1 to ${String(MAX_K)}`);
9974
- await this.niadra.profile();
10033
+ const raw = await this.served(tool2);
10034
+ if (raw === null) throw new NiadraError(`the space binds no tool ${tool2} for this key's source: see its tool-bindings`);
9975
10035
  const ids = [...turnIds];
9976
10036
  if ((options.scenarioIds ?? []).length > 0) {
9977
10037
  const found2 = await this.niadra.callRoute({ method: "GET", path: "/v1/scenarios", query: { ids: (options.scenarioIds ?? []).join(","), limit: 50 } });
@@ -9989,7 +10049,7 @@ var Counterfactual = class {
9989
10049
  continue;
9990
10050
  }
9991
10051
  for (const call of calls(record, tool2)) {
9992
- const found2 = await this.case(record, call, fn, element, k);
10052
+ const found2 = await this.case(record, call, fn, raw, element, k);
9993
10053
  if (found2 === null) run.untouched++;
9994
10054
  else run.cases.push(found2);
9995
10055
  }
@@ -9999,17 +10059,15 @@ var Counterfactual = class {
9999
10059
  run.report = await this.niadra.callRoute({ method: "POST", path: "/v1/measure/counterfactual-runs", body: report });
10000
10060
  return run;
10001
10061
  }
10002
- async case(record, call, fn, element, k) {
10062
+ async case(record, call, fn, raw, element, k) {
10003
10063
  const base = { turn_id: record.turn_id, call_id: call.call_id };
10004
10064
  const tool2 = String(call.name);
10005
10065
  const wrapped = recordedTool(fn);
10006
- const raw = this.options.bindings?.[tool2] ?? wrapped?.binding ?? await this.served(tool2);
10007
10066
  let block;
10008
10067
  let args;
10009
10068
  try {
10010
10069
  block = await this.value(record, blockBlob(record, call));
10011
10070
  args = { ...await this.value(record, call.args) };
10012
- if (raw === null) throw new BlobError("the tool has no binding");
10013
10071
  } catch {
10014
10072
  return { ...base, status: "infrastructure_error", dry_run: false };
10015
10073
  }
@@ -11008,7 +11066,6 @@ async function counterfactual(argv, io) {
11008
11066
  element: { type: "string" },
11009
11067
  turn: { type: "string", multiple: true, default: [] },
11010
11068
  scenario: { type: "string", multiple: true, default: [] },
11011
- bindings: { type: "string" },
11012
11069
  safe: { type: "string", multiple: true, default: [] },
11013
11070
  k: { type: "string", default: "10" },
11014
11071
  label: { type: "string" }
@@ -11018,8 +11075,7 @@ async function counterfactual(argv, io) {
11018
11075
  if (!["constraints", "hard", "size", "exclude"].includes(values.element)) throw new Error(`--element ${values.element}: constraints, hard, size or exclude`);
11019
11076
  if (values.turn.length === 0 && values.scenario.length === 0) throw new Error("--turn or --scenario is needed");
11020
11077
  const tools = await load(values.tools);
11021
- const bindings = values.bindings === void 0 ? void 0 : await load(values.bindings);
11022
- const run = await new Counterfactual(io.client(), tools, { ...bindings ? { bindings } : {}, safe: values.safe }).run(values.turn, {
11078
+ const run = await new Counterfactual(io.client(), tools, { safe: values.safe }).run(values.turn, {
11023
11079
  tool: values.tool,
11024
11080
  element: values.element,
11025
11081
  scenarioIds: values.scenario,