@niadra/sdk 0.7.0 → 0.9.1

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 +58 -0
  2. package/README.md +55 -7
  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 +157 -60
  22. package/dist/cli.js.map +1 -1
  23. package/dist/{client-DsIxZxZk.d.cts → client-CWMxmJHs.d.cts} +91 -28
  24. package/dist/{client-DsIxZxZk.d.ts → client-CWMxmJHs.d.ts} +91 -28
  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 +156 -57
  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 +156 -57
  60. package/dist/index.js.map +1 -1
  61. package/dist/{intercept-D7qFCaRb.d.ts → intercept-Bv0nLpSw.d.ts} +1 -1
  62. package/dist/{intercept-0_lJE6k1.d.cts → intercept-CGngQI8B.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-B4Ef2Wpg.d.ts} +1 -1
  100. package/dist/{shared-DTPWVlWm.d.cts → shared-Cq8mz-7m.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-CG4om_DK.d.cts → webhook-jTx_SegV.d.cts} +1 -1
  122. package/dist/{webhook-1ozGyksG.d.ts → webhook-z2TjUzjv.d.ts} +1 -1
  123. package/dist/whatsapp.d.cts +2 -2
  124. package/dist/whatsapp.d.ts +2 -2
  125. package/package.json +3 -2
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.1";
507
507
 
508
508
  // src/transport.ts
509
509
  var RETRYABLE_WRITE_STATUS = /* @__PURE__ */ new Set([408, 421, 429, 500, 502, 503, 504]);
@@ -597,6 +597,7 @@ var Transport = class {
597
597
  } catch (error) {
598
598
  throw deadline.explain(error);
599
599
  }
600
+ warnIfDeprecated(this.config.logger, init.method ?? "GET", url, response.headers);
600
601
  const requestId = response.headers.get("x-request-id");
601
602
  let payload;
602
603
  try {
@@ -666,6 +667,29 @@ var Deadline = class {
666
667
  return new NiadraConnectionError(`connection failed: ${message}`, { cause: error });
667
668
  }
668
669
  };
670
+ var VERSIONING_DOCS = "https://docs.niadra.com/en/security/api-versioning";
671
+ var DEPRECATION_LINK = /<([^>]*)>[^,]*;\s*rel="?deprecation"?/gi;
672
+ var deprecationsSeen = /* @__PURE__ */ new Set();
673
+ function warnIfDeprecated(logger, method, url, headers) {
674
+ const since = headers.get("deprecation");
675
+ if (since === null) return;
676
+ const links = [...(headers.get("link") ?? "").matchAll(DEPRECATION_LINK)].map((match) => match[1] ?? "");
677
+ const key2 = `${method} ${links.join(" ") || (url.split("?")[0] ?? url)}`;
678
+ if (deprecationsSeen.has(key2)) return;
679
+ deprecationsSeen.add(key2);
680
+ const sunset = headers.get("sunset");
681
+ logger.warn(
682
+ `the API deprecated a ${method} route this client calls, since ${deprecatedSince(since)}; it stops answering on ${sunset === null ? "a date not announced yet" : sunsetDay(sunset)}. See ${links.join(", ") || VERSIONING_DOCS}`
683
+ );
684
+ }
685
+ function deprecatedSince(value) {
686
+ const seconds = Number(value.trim().replace(/^@/, ""));
687
+ return Number.isInteger(seconds) ? new Date(seconds * 1e3).toISOString().slice(0, 10) : value;
688
+ }
689
+ function sunsetDay(value) {
690
+ const time = Date.parse(value);
691
+ return Number.isNaN(time) ? value : new Date(time).toISOString().slice(0, 10);
692
+ }
669
693
  async function readBody(response) {
670
694
  const text2 = await response.text();
671
695
  if (text2.length === 0) return null;
@@ -2885,6 +2909,11 @@ ${change.field}`)) out.push({ ref, field: change.field, value: change.now, claim
2885
2909
  for (const s of constraints.soft ?? []) said.push([s.attr, s.value]);
2886
2910
  for (const a of constraints.attributes ?? []) said.push([a.name, a.value]);
2887
2911
  for (const [attr, value] of said) out.push({ ref: null, field: attr.split(".").pop() ?? attr, value, claimSafe: true });
2912
+ for (const shown of constraints.already_presented ?? []) {
2913
+ for (const [field, n] of Object.entries(shown.values ?? {})) {
2914
+ out.push({ ref: shown.ref, field, value: n.v, claimSafe: n.claim_safe, role: n.role ?? null });
2915
+ }
2916
+ }
2888
2917
  }
2889
2918
  return out;
2890
2919
  }
@@ -3251,7 +3280,7 @@ function thenable(value) {
3251
3280
  function tool(name, fn, options = {}) {
3252
3281
  const isAsync = isAsyncFunction(fn);
3253
3282
  function bindingOf(frame) {
3254
- return options.binding ?? (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3283
+ return (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3255
3284
  }
3256
3285
  function masks(frame) {
3257
3286
  return options.maskOutput ?? bindingOf(frame)?.capabilities?.mask_output ?? false;
@@ -3366,8 +3395,7 @@ function tool(name, fn, options = {}) {
3366
3395
  const marker = {
3367
3396
  name,
3368
3397
  dryRun: options.dryRun ?? false,
3369
- provenance: options.provenance ?? null,
3370
- binding: options.binding ?? null
3398
+ provenance: options.provenance ?? null
3371
3399
  };
3372
3400
  Object.defineProperty(wrapped, RECORDED, { value: marker });
3373
3401
  return wrapped;
@@ -3629,6 +3657,7 @@ function claimsOf(frame, claims, logger) {
3629
3657
 
3630
3658
  // src/queue.ts
3631
3659
  var HEARTBEAT_WINDOW_MS = 6e4;
3660
+ var MAX_PAUSE_MS = 6e4;
3632
3661
  var EventQueue = class {
3633
3662
  constructor(send, options, logger, now = Date.now) {
3634
3663
  this.send = send;
@@ -3649,6 +3678,8 @@ var EventQueue = class {
3649
3678
  droppedSinceWarning = 0;
3650
3679
  windowStart;
3651
3680
  sentInWindow = 0;
3681
+ pauseMs = 0;
3682
+ resumeAt = 0;
3652
3683
  /** Items waiting to be sent, not counting a batch already in flight. */
3653
3684
  get size() {
3654
3685
  return this.items.length;
@@ -3684,10 +3715,16 @@ var EventQueue = class {
3684
3715
  return run;
3685
3716
  }
3686
3717
  /**
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.
3718
+ * Starts a flush without waiting for it: once the pause after a failed batch ends, or at once with
3719
+ * `now` (a caller waits for an item). Item failures are logged and settled inside the drain, so only
3720
+ * an unexpected error reaches this catch.
3689
3721
  */
3690
- flushInBackground() {
3722
+ flushInBackground(now = false) {
3723
+ const paused = this.resumeAt - this.now();
3724
+ if (!now && paused > 0) {
3725
+ this.schedule(paused);
3726
+ return;
3727
+ }
3691
3728
  this.flush().catch((error) => {
3692
3729
  this.logger.error(`event flush failed: ${toNiadraError(error).message}`);
3693
3730
  });
@@ -3704,24 +3741,36 @@ var EventQueue = class {
3704
3741
  while (this.items.length > 0) {
3705
3742
  const batch = this.items.splice(0, this.options.maxBatchSize);
3706
3743
  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);
3744
+ if (heartbeat) batch.push({ item: heartbeat });
3745
+ if (!await this.sendBatch(batch, report)) break;
3710
3746
  }
3711
3747
  return report;
3712
3748
  }
3713
- async sendBatch(batch, wire, report) {
3749
+ /** Sends one batch; `false` when it went back to the queue and the queue pauses. */
3750
+ async sendBatch(batch, report) {
3751
+ const wire = batch.map((pending) => pending.item);
3714
3752
  let response;
3715
3753
  try {
3716
3754
  response = await this.send(wire);
3717
3755
  } catch (error) {
3718
3756
  const failure = toNiadraError(error);
3719
- report.failed += batch.length;
3720
3757
  report.errors.push(failure);
3721
- this.logger.warn(`dropped ${batch.length} events after retries: ${failure.message}`);
3758
+ if (isTransient(failure)) {
3759
+ for (const pending of batch) pending.settle?.(failure);
3760
+ this.requeue(batch);
3761
+ this.pauseMs = Math.min(MAX_PAUSE_MS, Math.max(this.options.flushIntervalMs, this.pauseMs * 2));
3762
+ this.resumeAt = this.now() + this.pauseMs;
3763
+ this.logger.warn(`could not deliver ${batch.length} events, will retry: ${failure.message}`);
3764
+ this.schedule(this.pauseMs);
3765
+ return false;
3766
+ }
3767
+ report.failed += batch.length;
3768
+ this.logger.warn(`dropped ${batch.length} events refused by the API: ${failure.message}`);
3722
3769
  for (const pending of batch) pending.settle?.(failure);
3723
- return;
3770
+ return true;
3724
3771
  }
3772
+ this.pauseMs = 0;
3773
+ this.resumeAt = 0;
3725
3774
  const rejected = /* @__PURE__ */ new Map();
3726
3775
  for (const itemError of response.errors) {
3727
3776
  const detail = itemError.detail ? `: ${itemError.detail}` : "";
@@ -3732,6 +3781,7 @@ var EventQueue = class {
3732
3781
  this.logger.warn(`server rejected ${rejected.size} of ${wire.length} items (${codes2})`);
3733
3782
  }
3734
3783
  batch.forEach((pending, index) => {
3784
+ if (pending.item.type === "heartbeat") return;
3735
3785
  const error = rejected.get(index) ?? null;
3736
3786
  if (error) {
3737
3787
  report.failed++;
@@ -3742,6 +3792,17 @@ var EventQueue = class {
3742
3792
  }
3743
3793
  pending.settle?.(error);
3744
3794
  });
3795
+ return true;
3796
+ }
3797
+ /** Puts a batch back at the front, keeping only what still fits; what does not is dropped. */
3798
+ requeue(batch) {
3799
+ const room = Math.max(0, this.options.maxQueueSize - this.items.length);
3800
+ const lost = batch.slice(room);
3801
+ this.items.unshift(...batch.slice(0, room));
3802
+ if (lost.length > 0) {
3803
+ this.logger.warn(`event queue is full; dropped ${lost.length} events`);
3804
+ for (const pending of lost) pending.settle?.(new NiadraError("event queue is full"));
3805
+ }
3745
3806
  }
3746
3807
  /**
3747
3808
  * Once a minute, the next batch carries the number of items sent in the previous window.
@@ -3792,7 +3853,7 @@ function unref(timer) {
3792
3853
  var MAX_TURNS = 50;
3793
3854
  var COMPRESSED_ROOM = 4 * 1024 * 1024 - 64 * 1024;
3794
3855
  var DECODED_ROOM = 16 * 1024 * 1024 - 256 * 1024;
3795
- var MAX_PAUSE_MS = 6e4;
3856
+ var MAX_PAUSE_MS2 = 6e4;
3796
3857
  var encoder2 = new TextEncoder();
3797
3858
  async function encode(records) {
3798
3859
  const raw = encoder2.encode(JSON.stringify({ turns: records }));
@@ -3951,7 +4012,7 @@ var TurnSender = class {
3951
4012
  }
3952
4013
  if (isTransient(error)) {
3953
4014
  this.queue.requeue(batch.frames);
3954
- this.pause = Math.min(MAX_PAUSE_MS, Math.max(this.intervalMs, this.pause * 2));
4015
+ this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
3955
4016
  this.resumeAt = this.now() + this.pause;
3956
4017
  this.logger.warn(`could not deliver ${batch.frames.length} turns, will retry (${toNiadraError(error).name})`);
3957
4018
  return false;
@@ -4339,26 +4400,42 @@ async function suppressionKey(salt, canonical) {
4339
4400
  const key2 = await subtle.importKey("raw", saltBytes(salt), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
4340
4401
  return toBase64url(new Uint8Array(await subtle.sign("HMAC", key2, new TextEncoder().encode(canonical))));
4341
4402
  }
4403
+ var AREA_CODES = new Set(
4404
+ "11 12 13 14 15 16 17 18 19 21 22 24 27 28 31 32 33 34 35 37 38 41 42 43 44 45 46 47 48 49 51 53 54 55 61 62 63 64 65 66 67 68 69 71 73 74 75 77 79 81 82 83 84 85 86 87 88 89 91 92 93 94 95 96 97 98 99".split(" ")
4405
+ );
4406
+ var FORMAT_CHARACTERS = new RegExp("\\p{Cf}", "gu");
4342
4407
  function phone(value) {
4343
- const raw = value.trim().replace(SEPARATORS2, "");
4344
- const international = raw.startsWith("+") || raw.startsWith("00");
4408
+ let text2 = value.normalize("NFKC").replace(FORMAT_CHARACTERS, "").trim();
4409
+ if (text2.slice(0, 4).toLowerCase() === "tel:") text2 = text2.slice(4);
4410
+ const international = text2.startsWith("+") || text2.startsWith("00");
4411
+ if (international) text2 = text2.replaceAll("(0)", "");
4412
+ const raw = text2.replace(SEPARATORS2, "");
4345
4413
  let digits = raw.startsWith("+") ? raw.slice(1) : international ? raw.slice(2) : raw;
4346
4414
  if (!DIGITS2.test(digits)) throw new NiadraDestinationError("invalid_handle");
4347
- const national = digits.replace(/^0+/, "");
4348
- if (!international && (national.length === 10 || national.length === 11) && isArea(national.slice(0, 2))) {
4349
- digits = `55${national}`;
4415
+ if (!international) {
4416
+ const national = digits.replace(/^0+/, "");
4417
+ if (isBrazilianNational(national)) {
4418
+ digits = `55${national}`;
4419
+ } else if (digits.startsWith("0") && (national.length === 12 || national.length === 13)) {
4420
+ if (isBrazilianNational(national.slice(2))) digits = `55${national.slice(2)}`;
4421
+ }
4350
4422
  }
4351
4423
  if (digits.length < 8 || digits.length > 15 || digits.startsWith("0")) {
4352
4424
  throw new NiadraDestinationError("invalid_handle");
4353
4425
  }
4354
4426
  const rest = digits.slice(2);
4355
- if (digits.startsWith("55") && rest.length === 10 && isArea(rest.slice(0, 2)) && "6789".includes(rest.charAt(2))) {
4427
+ if (digits.startsWith("55") && rest.length === 10 && AREA_CODES.has(rest.slice(0, 2)) && "6789".includes(rest.charAt(2))) {
4356
4428
  digits = `55${rest.slice(0, 2)}9${rest.slice(2)}`;
4357
4429
  }
4430
+ if (digits.length === 13 && (digits.startsWith("521") || digits.startsWith("549"))) {
4431
+ digits = digits.slice(0, 2) + digits.slice(3);
4432
+ }
4358
4433
  return `phone:+${digits}`;
4359
4434
  }
4360
- function isArea(code) {
4361
- return /^[1-9]{2}$/.test(code);
4435
+ function isBrazilianNational(national) {
4436
+ if (!AREA_CODES.has(national.slice(0, 2))) return false;
4437
+ if (national.length === 10) return "23456789".includes(national.charAt(2));
4438
+ return national.length === 11 && national.charAt(2) === "9";
4362
4439
  }
4363
4440
  function saltBytes(salt) {
4364
4441
  const bytes = fromBase64url(salt);
@@ -4417,7 +4494,10 @@ var SuppressionCopy = class {
4417
4494
  }
4418
4495
  this.saltValue = salt;
4419
4496
  }
4420
- /** Applies one page, in order. `true` when more pages follow. */
4497
+ /**
4498
+ * Applies one page, in order. `true` when more pages follow: a full page. Every page carries the cursor to
4499
+ * read from next, the last one too, which the next read starts from.
4500
+ */
4421
4501
  apply(page) {
4422
4502
  if (page.salt_id !== this.saltValue?.salt_id) {
4423
4503
  this.saltValue = null;
@@ -4432,8 +4512,9 @@ var SuppressionCopy = class {
4432
4512
  }
4433
4513
  this.cursor = page.next_cursor ?? this.cursor;
4434
4514
  this.absent = false;
4435
- if (!page.next_cursor) this.readAt = this.now();
4436
- return Boolean(page.next_cursor);
4515
+ const more = Boolean(page.next_cursor) && page.items.length >= PAGE;
4516
+ if (!more) this.readAt = this.now();
4517
+ return more;
4437
4518
  }
4438
4519
  /** A 404: the space keeps no list. Anything else: the copy stays as it is. */
4439
4520
  failed(error) {
@@ -4658,7 +4739,7 @@ var ContactGateway = class {
4658
4739
 
4659
4740
  // src/outbox.ts
4660
4741
  var CAPACITY = 1e3;
4661
- var MAX_PAUSE_MS2 = 6e4;
4742
+ var MAX_PAUSE_MS3 = 6e4;
4662
4743
  var Outbox = class {
4663
4744
  constructor(logger, intervalMs = 1e3, now = Date.now) {
4664
4745
  this.logger = logger;
@@ -4726,7 +4807,7 @@ var Outbox = class {
4726
4807
  answer = await write.send();
4727
4808
  } catch (error) {
4728
4809
  if (isTransient(error)) {
4729
- this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
4810
+ this.pause = Math.min(MAX_PAUSE_MS3, Math.max(this.intervalMs, this.pause * 2));
4730
4811
  this.resumeAt = this.now() + this.pause;
4731
4812
  return false;
4732
4813
  }
@@ -5509,6 +5590,11 @@ var ContextCache = class {
5509
5590
  }
5510
5591
  return true;
5511
5592
  }
5593
+ /** Milliseconds since the server last sent or confirmed the pack held for `key`; `null` when none is held. */
5594
+ age(key2) {
5595
+ const entry = this.entries.get(key2);
5596
+ return entry ? this.now() - entry.confirmedAt : null;
5597
+ }
5512
5598
  /** The cached pack's ETag, without touching the order of eviction. */
5513
5599
  etag(key2) {
5514
5600
  return this.entries.get(key2)?.response.etag ?? null;
@@ -5702,7 +5788,7 @@ function mergeNotModified(cached, fresh) {
5702
5788
  coordination: fresh.coordination ?? cached.coordination ?? null
5703
5789
  };
5704
5790
  }
5705
- function resultFrom(response, source, error = null) {
5791
+ function resultFrom(response, source, error = null, ageMs = 0) {
5706
5792
  const holdout = response.path === "holdout";
5707
5793
  const text2 = holdout ? "" : response.text ?? "";
5708
5794
  const pack = holdout ? null : response.pack ?? null;
@@ -5711,10 +5797,10 @@ function resultFrom(response, source, error = null) {
5711
5797
  state: response.state ?? null,
5712
5798
  coordination: response.coordination ?? null
5713
5799
  };
5714
- return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, response, error, ...blocks2 };
5800
+ return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, ageMs, response, error, ...blocks2 };
5715
5801
  }
5716
5802
  function emptyResult(error) {
5717
- return { text: "", suffix: "", variables: {}, pack: null, source: "none", response: null, error, constraints: null, state: null, coordination: null };
5803
+ return { text: "", suffix: "", variables: {}, pack: null, source: "none", ageMs: null, response: null, error, constraints: null, state: null, coordination: null };
5718
5804
  }
5719
5805
  function renderSuffix(response) {
5720
5806
  if (response.path === "holdout") return "";
@@ -8410,7 +8496,8 @@ var Niadra = class _Niadra {
8410
8496
  baseURL,
8411
8497
  apiKey,
8412
8498
  fetch: fetchImpl,
8413
- defaultHeaders: options.defaultHeaders ?? {}
8499
+ defaultHeaders: options.defaultHeaders ?? {},
8500
+ logger: this.logger
8414
8501
  });
8415
8502
  const queueOptions = { ...DEFAULT_QUEUE, ...options.queue };
8416
8503
  queueOptions.maxBatchSize = Math.min(queueOptions.maxBatchSize, 499);
@@ -8507,19 +8594,29 @@ var Niadra = class _Niadra {
8507
8594
  */
8508
8595
  async mayContact(handle, purpose, options = {}) {
8509
8596
  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
- );
8597
+ const read2 = this.readSuppressions(this.suppressions.held ? this.timeouts.write : this.timeouts.navigation);
8517
8598
  if (!this.suppressions.held) await read2;
8518
8599
  }
8519
8600
  const checkOptions = { channel: options.channel ?? null };
8520
8601
  if (options.failOpen !== void 0) checkOptions.failOpen = options.failOpen;
8521
8602
  return this.suppressions.mayContact(handle, purpose, checkOptions);
8522
8603
  }
8604
+ readSuppressions(budgetMs) {
8605
+ return this.suppressions.read(
8606
+ {
8607
+ salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8608
+ page: (cursor, limit2) => this.api.suppressions({ cursor, limit: limit2 }, { timeout: this.timeouts.navigation })
8609
+ },
8610
+ budgetMs
8611
+ );
8612
+ }
8613
+ /**
8614
+ * Reads the local copy of the suppression list in the background when it is due: a check that Niadra does not
8615
+ * answer falls back on it, so an opt-out holds through an outage.
8616
+ */
8617
+ keepSuppressions() {
8618
+ if (this.core && this.suppressions.due()) void this.readSuppressions(this.timeouts.write);
8619
+ }
8523
8620
  /**
8524
8621
  * The build a turn runs on, for `conversation.turn({ build })`: each prompt's version, the digest of the
8525
8622
  * corpus the agent consults (computed by you, never the files), the model, your context assembler's version
@@ -8593,7 +8690,10 @@ var Niadra = class _Niadra {
8593
8690
  states: this.states,
8594
8691
  contract: () => this.currentContract(),
8595
8692
  internalText: this.internalText,
8596
- check: (request, timeoutMs) => this.api.check(request, { timeout: timeoutMs }),
8693
+ check: (request, timeoutMs) => {
8694
+ if (request.direction === "outbound") this.keepSuppressions();
8695
+ return this.api.check(request, { timeout: timeoutMs });
8696
+ },
8597
8697
  claim: (request, timeoutMs) => this.api.claim(request, {}, { timeout: timeoutMs }),
8598
8698
  verifyClaim: (ref, field, value, options) => this.verifyClaim(ref, field, value, options),
8599
8699
  enabled: this.enabled,
@@ -8707,21 +8807,21 @@ var Niadra = class _Niadra {
8707
8807
  }
8708
8808
  const key2 = cacheKey(request);
8709
8809
  const hit = cache.lookup(key2);
8710
- if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache");
8810
+ if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache", null, cache.age(key2));
8711
8811
  if (hit?.freshness === "stale") {
8712
8812
  this.revalidate(core, cache, key2, scope, request, timeout, options.headers).catch((error) => {
8713
8813
  this.logger.debug(`background context refresh failed: ${describe(toNiadraError(error))}`);
8714
8814
  });
8715
- return resultFrom(delivered(hit.response, cache.take(key2)), "stale");
8815
+ return resultFrom(delivered(hit.response, cache.take(key2)), "stale", null, cache.age(key2));
8716
8816
  }
8717
8817
  try {
8718
8818
  const pending = this.revalidate(core, cache, key2, scope, request, timeout, options.headers);
8719
8819
  const { response, source } = await abortable(pending, options.signal);
8720
- return resultFrom(delivered(response, cache.take(key2)), source);
8820
+ return resultFrom(delivered(response, cache.take(key2)), source, null, source === "network" ? 0 : cache.age(key2));
8721
8821
  } catch (error) {
8722
8822
  const failure = toNiadraError(error);
8723
8823
  const fallback2 = cache.lookup(key2);
8724
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8824
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
8725
8825
  }
8726
8826
  }
8727
8827
  /**
@@ -8743,11 +8843,11 @@ var Niadra = class _Niadra {
8743
8843
  const failure = toNiadraError(error);
8744
8844
  this.observeAuth(failure, key2);
8745
8845
  const fallback2 = cache && key2 ? cache.lookup(key2) : null;
8746
- return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8846
+ return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, key2 ? cache?.age(key2) ?? null : null);
8747
8847
  }
8748
8848
  if (!cache || !key2 || !scope) return resultFrom(response, "network");
8749
8849
  if (response.degraded && !response.not_modified && cached) {
8750
- return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback");
8850
+ return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback", null, cache.age(key2));
8751
8851
  }
8752
8852
  const merged = response.not_modified && cached ? mergeNotModified(cached.response, response) : response;
8753
8853
  cache.store(key2, scope, withoutSlots(merged), generation);
@@ -9249,11 +9349,11 @@ var Niadra = class _Niadra {
9249
9349
  }
9250
9350
  });
9251
9351
  }
9252
- contextFailure(error, fallback2) {
9352
+ contextFailure(error, fallback2, ageMs = null) {
9253
9353
  if (this.strict) throw error;
9254
9354
  if (fallback2) {
9255
9355
  this.logger.warn(`context request failed, serving the last good pack: ${describe(error)}`);
9256
- return resultFrom(fallback2, "fallback", error);
9356
+ return resultFrom(fallback2, "fallback", error, ageMs);
9257
9357
  }
9258
9358
  this.logger.warn(`context unavailable: ${describe(error)}`);
9259
9359
  return emptyResult(error);
@@ -9354,11 +9454,11 @@ var Niadra = class _Niadra {
9354
9454
  });
9355
9455
  }
9356
9456
  const response = compose(delivered(hit.response, cache.take(key2)), chosen2);
9357
- return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale");
9457
+ return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale", null, cache.age(key2));
9358
9458
  } catch (error) {
9359
9459
  const failure = toNiadraError(error);
9360
9460
  const fallback2 = cache.lookup(key2);
9361
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
9461
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
9362
9462
  }
9363
9463
  }
9364
9464
  /** Starts one read of a line in the background. */
@@ -9501,7 +9601,7 @@ var Niadra = class _Niadra {
9501
9601
  else resolve({ ok: false, idempotency_key: key2, error });
9502
9602
  };
9503
9603
  core.queue.push(item, settle2);
9504
- core.queue.flushInBackground();
9604
+ core.queue.flushInBackground(true);
9505
9605
  });
9506
9606
  }
9507
9607
  async endScope(item, scope) {
@@ -9971,7 +10071,8 @@ var Counterfactual = class {
9971
10071
  const fn = this.tools[tool2];
9972
10072
  if (fn === void 0) throw new NiadraError(`no function for the tool ${tool2}: pass it in tools`);
9973
10073
  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();
10074
+ const raw = await this.served(tool2);
10075
+ if (raw === null) throw new NiadraError(`the space binds no tool ${tool2} for this key's source: see its tool-bindings`);
9975
10076
  const ids = [...turnIds];
9976
10077
  if ((options.scenarioIds ?? []).length > 0) {
9977
10078
  const found2 = await this.niadra.callRoute({ method: "GET", path: "/v1/scenarios", query: { ids: (options.scenarioIds ?? []).join(","), limit: 50 } });
@@ -9989,7 +10090,7 @@ var Counterfactual = class {
9989
10090
  continue;
9990
10091
  }
9991
10092
  for (const call of calls(record, tool2)) {
9992
- const found2 = await this.case(record, call, fn, element, k);
10093
+ const found2 = await this.case(record, call, fn, raw, element, k);
9993
10094
  if (found2 === null) run.untouched++;
9994
10095
  else run.cases.push(found2);
9995
10096
  }
@@ -9999,17 +10100,15 @@ var Counterfactual = class {
9999
10100
  run.report = await this.niadra.callRoute({ method: "POST", path: "/v1/measure/counterfactual-runs", body: report });
10000
10101
  return run;
10001
10102
  }
10002
- async case(record, call, fn, element, k) {
10103
+ async case(record, call, fn, raw, element, k) {
10003
10104
  const base = { turn_id: record.turn_id, call_id: call.call_id };
10004
10105
  const tool2 = String(call.name);
10005
10106
  const wrapped = recordedTool(fn);
10006
- const raw = this.options.bindings?.[tool2] ?? wrapped?.binding ?? await this.served(tool2);
10007
10107
  let block;
10008
10108
  let args;
10009
10109
  try {
10010
10110
  block = await this.value(record, blockBlob(record, call));
10011
10111
  args = { ...await this.value(record, call.args) };
10012
- if (raw === null) throw new BlobError("the tool has no binding");
10013
10112
  } catch {
10014
10113
  return { ...base, status: "infrastructure_error", dry_run: false };
10015
10114
  }
@@ -11008,7 +11107,6 @@ async function counterfactual(argv, io) {
11008
11107
  element: { type: "string" },
11009
11108
  turn: { type: "string", multiple: true, default: [] },
11010
11109
  scenario: { type: "string", multiple: true, default: [] },
11011
- bindings: { type: "string" },
11012
11110
  safe: { type: "string", multiple: true, default: [] },
11013
11111
  k: { type: "string", default: "10" },
11014
11112
  label: { type: "string" }
@@ -11018,8 +11116,7 @@ async function counterfactual(argv, io) {
11018
11116
  if (!["constraints", "hard", "size", "exclude"].includes(values.element)) throw new Error(`--element ${values.element}: constraints, hard, size or exclude`);
11019
11117
  if (values.turn.length === 0 && values.scenario.length === 0) throw new Error("--turn or --scenario is needed");
11020
11118
  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, {
11119
+ const run = await new Counterfactual(io.client(), tools, { safe: values.safe }).run(values.turn, {
11023
11120
  tool: values.tool,
11024
11121
  element: values.element,
11025
11122
  scenarioIds: values.scenario,