@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/index.js CHANGED
@@ -516,7 +516,7 @@ function internalRecord(span, ref, act) {
516
516
  }
517
517
 
518
518
  // src/version.ts
519
- var VERSION = "0.7.0";
519
+ var VERSION = "0.9.1";
520
520
 
521
521
  // src/transport.ts
522
522
  var RETRYABLE_WRITE_STATUS = /* @__PURE__ */ new Set([408, 421, 429, 500, 502, 503, 504]);
@@ -610,6 +610,7 @@ var Transport = class {
610
610
  } catch (error) {
611
611
  throw deadline.explain(error);
612
612
  }
613
+ warnIfDeprecated(this.config.logger, init.method ?? "GET", url, response.headers);
613
614
  const requestId = response.headers.get("x-request-id");
614
615
  let payload;
615
616
  try {
@@ -679,6 +680,29 @@ var Deadline = class {
679
680
  return new NiadraConnectionError(`connection failed: ${message}`, { cause: error });
680
681
  }
681
682
  };
683
+ var VERSIONING_DOCS = "https://docs.niadra.com/en/security/api-versioning";
684
+ var DEPRECATION_LINK = /<([^>]*)>[^,]*;\s*rel="?deprecation"?/gi;
685
+ var deprecationsSeen = /* @__PURE__ */ new Set();
686
+ function warnIfDeprecated(logger, method, url, headers) {
687
+ const since = headers.get("deprecation");
688
+ if (since === null) return;
689
+ const links = [...(headers.get("link") ?? "").matchAll(DEPRECATION_LINK)].map((match) => match[1] ?? "");
690
+ const key2 = `${method} ${links.join(" ") || (url.split("?")[0] ?? url)}`;
691
+ if (deprecationsSeen.has(key2)) return;
692
+ deprecationsSeen.add(key2);
693
+ const sunset = headers.get("sunset");
694
+ logger.warn(
695
+ `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}`
696
+ );
697
+ }
698
+ function deprecatedSince(value) {
699
+ const seconds = Number(value.trim().replace(/^@/, ""));
700
+ return Number.isInteger(seconds) ? new Date(seconds * 1e3).toISOString().slice(0, 10) : value;
701
+ }
702
+ function sunsetDay(value) {
703
+ const time = Date.parse(value);
704
+ return Number.isNaN(time) ? value : new Date(time).toISOString().slice(0, 10);
705
+ }
682
706
  async function readBody(response) {
683
707
  const text2 = await response.text();
684
708
  if (text2.length === 0) return null;
@@ -2902,6 +2926,11 @@ ${change.field}`)) out.push({ ref, field: change.field, value: change.now, claim
2902
2926
  for (const s of constraints.soft ?? []) said.push([s.attr, s.value]);
2903
2927
  for (const a of constraints.attributes ?? []) said.push([a.name, a.value]);
2904
2928
  for (const [attr, value] of said) out.push({ ref: null, field: attr.split(".").pop() ?? attr, value, claimSafe: true });
2929
+ for (const shown of constraints.already_presented ?? []) {
2930
+ for (const [field, n] of Object.entries(shown.values ?? {})) {
2931
+ out.push({ ref: shown.ref, field, value: n.v, claimSafe: n.claim_safe, role: n.role ?? null });
2932
+ }
2933
+ }
2905
2934
  }
2906
2935
  return out;
2907
2936
  }
@@ -3268,7 +3297,7 @@ function thenable(value) {
3268
3297
  function tool(name, fn, options = {}) {
3269
3298
  const isAsync = isAsyncFunction(fn);
3270
3299
  function bindingOf(frame) {
3271
- return options.binding ?? (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3300
+ return (options.served ?? frame?.profile.bindings)?.(name) ?? null;
3272
3301
  }
3273
3302
  function masks(frame) {
3274
3303
  return options.maskOutput ?? bindingOf(frame)?.capabilities?.mask_output ?? false;
@@ -3383,8 +3412,7 @@ function tool(name, fn, options = {}) {
3383
3412
  const marker = {
3384
3413
  name,
3385
3414
  dryRun: options.dryRun ?? false,
3386
- provenance: options.provenance ?? null,
3387
- binding: options.binding ?? null
3415
+ provenance: options.provenance ?? null
3388
3416
  };
3389
3417
  Object.defineProperty(wrapped, RECORDED, { value: marker });
3390
3418
  return wrapped;
@@ -3646,6 +3674,7 @@ function claimsOf(frame, claims, logger) {
3646
3674
 
3647
3675
  // src/queue.ts
3648
3676
  var HEARTBEAT_WINDOW_MS = 6e4;
3677
+ var MAX_PAUSE_MS = 6e4;
3649
3678
  var EventQueue = class {
3650
3679
  constructor(send, options, logger, now = Date.now) {
3651
3680
  this.send = send;
@@ -3666,6 +3695,8 @@ var EventQueue = class {
3666
3695
  droppedSinceWarning = 0;
3667
3696
  windowStart;
3668
3697
  sentInWindow = 0;
3698
+ pauseMs = 0;
3699
+ resumeAt = 0;
3669
3700
  /** Items waiting to be sent, not counting a batch already in flight. */
3670
3701
  get size() {
3671
3702
  return this.items.length;
@@ -3701,10 +3732,16 @@ var EventQueue = class {
3701
3732
  return run;
3702
3733
  }
3703
3734
  /**
3704
- * Starts a flush without waiting for it. Item failures are logged and settled inside the
3705
- * drain, so only an unexpected error reaches this catch.
3735
+ * Starts a flush without waiting for it: once the pause after a failed batch ends, or at once with
3736
+ * `now` (a caller waits for an item). Item failures are logged and settled inside the drain, so only
3737
+ * an unexpected error reaches this catch.
3706
3738
  */
3707
- flushInBackground() {
3739
+ flushInBackground(now = false) {
3740
+ const paused = this.resumeAt - this.now();
3741
+ if (!now && paused > 0) {
3742
+ this.schedule(paused);
3743
+ return;
3744
+ }
3708
3745
  this.flush().catch((error) => {
3709
3746
  this.logger.error(`event flush failed: ${toNiadraError(error).message}`);
3710
3747
  });
@@ -3721,24 +3758,36 @@ var EventQueue = class {
3721
3758
  while (this.items.length > 0) {
3722
3759
  const batch = this.items.splice(0, this.options.maxBatchSize);
3723
3760
  const heartbeat = this.heartbeat();
3724
- const wire = batch.map((pending) => pending.item);
3725
- if (heartbeat) wire.push(heartbeat);
3726
- await this.sendBatch(batch, wire, report);
3761
+ if (heartbeat) batch.push({ item: heartbeat });
3762
+ if (!await this.sendBatch(batch, report)) break;
3727
3763
  }
3728
3764
  return report;
3729
3765
  }
3730
- async sendBatch(batch, wire, report) {
3766
+ /** Sends one batch; `false` when it went back to the queue and the queue pauses. */
3767
+ async sendBatch(batch, report) {
3768
+ const wire = batch.map((pending) => pending.item);
3731
3769
  let response;
3732
3770
  try {
3733
3771
  response = await this.send(wire);
3734
3772
  } catch (error) {
3735
3773
  const failure = toNiadraError(error);
3736
- report.failed += batch.length;
3737
3774
  report.errors.push(failure);
3738
- this.logger.warn(`dropped ${batch.length} events after retries: ${failure.message}`);
3775
+ if (isTransient(failure)) {
3776
+ for (const pending of batch) pending.settle?.(failure);
3777
+ this.requeue(batch);
3778
+ this.pauseMs = Math.min(MAX_PAUSE_MS, Math.max(this.options.flushIntervalMs, this.pauseMs * 2));
3779
+ this.resumeAt = this.now() + this.pauseMs;
3780
+ this.logger.warn(`could not deliver ${batch.length} events, will retry: ${failure.message}`);
3781
+ this.schedule(this.pauseMs);
3782
+ return false;
3783
+ }
3784
+ report.failed += batch.length;
3785
+ this.logger.warn(`dropped ${batch.length} events refused by the API: ${failure.message}`);
3739
3786
  for (const pending of batch) pending.settle?.(failure);
3740
- return;
3787
+ return true;
3741
3788
  }
3789
+ this.pauseMs = 0;
3790
+ this.resumeAt = 0;
3742
3791
  const rejected = /* @__PURE__ */ new Map();
3743
3792
  for (const itemError of response.errors) {
3744
3793
  const detail = itemError.detail ? `: ${itemError.detail}` : "";
@@ -3749,6 +3798,7 @@ var EventQueue = class {
3749
3798
  this.logger.warn(`server rejected ${rejected.size} of ${wire.length} items (${codes2})`);
3750
3799
  }
3751
3800
  batch.forEach((pending, index) => {
3801
+ if (pending.item.type === "heartbeat") return;
3752
3802
  const error = rejected.get(index) ?? null;
3753
3803
  if (error) {
3754
3804
  report.failed++;
@@ -3759,6 +3809,17 @@ var EventQueue = class {
3759
3809
  }
3760
3810
  pending.settle?.(error);
3761
3811
  });
3812
+ return true;
3813
+ }
3814
+ /** Puts a batch back at the front, keeping only what still fits; what does not is dropped. */
3815
+ requeue(batch) {
3816
+ const room = Math.max(0, this.options.maxQueueSize - this.items.length);
3817
+ const lost = batch.slice(room);
3818
+ this.items.unshift(...batch.slice(0, room));
3819
+ if (lost.length > 0) {
3820
+ this.logger.warn(`event queue is full; dropped ${lost.length} events`);
3821
+ for (const pending of lost) pending.settle?.(new NiadraError("event queue is full"));
3822
+ }
3762
3823
  }
3763
3824
  /**
3764
3825
  * Once a minute, the next batch carries the number of items sent in the previous window.
@@ -3809,7 +3870,7 @@ function unref(timer) {
3809
3870
  var MAX_TURNS = 50;
3810
3871
  var COMPRESSED_ROOM = 4 * 1024 * 1024 - 64 * 1024;
3811
3872
  var DECODED_ROOM = 16 * 1024 * 1024 - 256 * 1024;
3812
- var MAX_PAUSE_MS = 6e4;
3873
+ var MAX_PAUSE_MS2 = 6e4;
3813
3874
  var encoder2 = new TextEncoder();
3814
3875
  async function encode(records) {
3815
3876
  const raw = encoder2.encode(JSON.stringify({ turns: records }));
@@ -3968,7 +4029,7 @@ var TurnSender = class {
3968
4029
  }
3969
4030
  if (isTransient(error)) {
3970
4031
  this.queue.requeue(batch.frames);
3971
- this.pause = Math.min(MAX_PAUSE_MS, Math.max(this.intervalMs, this.pause * 2));
4032
+ this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
3972
4033
  this.resumeAt = this.now() + this.pause;
3973
4034
  this.logger.warn(`could not deliver ${batch.frames.length} turns, will retry (${toNiadraError(error).name})`);
3974
4035
  return false;
@@ -4356,26 +4417,42 @@ async function suppressionKey(salt, canonical2) {
4356
4417
  const key2 = await subtle.importKey("raw", saltBytes(salt), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
4357
4418
  return toBase64url(new Uint8Array(await subtle.sign("HMAC", key2, new TextEncoder().encode(canonical2))));
4358
4419
  }
4420
+ var AREA_CODES = new Set(
4421
+ "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(" ")
4422
+ );
4423
+ var FORMAT_CHARACTERS = /\p{Cf}/gu;
4359
4424
  function phone(value) {
4360
- const raw = value.trim().replace(SEPARATORS2, "");
4361
- const international = raw.startsWith("+") || raw.startsWith("00");
4425
+ let text2 = value.normalize("NFKC").replace(FORMAT_CHARACTERS, "").trim();
4426
+ if (text2.slice(0, 4).toLowerCase() === "tel:") text2 = text2.slice(4);
4427
+ const international = text2.startsWith("+") || text2.startsWith("00");
4428
+ if (international) text2 = text2.replaceAll("(0)", "");
4429
+ const raw = text2.replace(SEPARATORS2, "");
4362
4430
  let digits = raw.startsWith("+") ? raw.slice(1) : international ? raw.slice(2) : raw;
4363
4431
  if (!DIGITS2.test(digits)) throw new NiadraDestinationError("invalid_handle");
4364
- const national = digits.replace(/^0+/, "");
4365
- if (!international && (national.length === 10 || national.length === 11) && isArea(national.slice(0, 2))) {
4366
- digits = `55${national}`;
4432
+ if (!international) {
4433
+ const national = digits.replace(/^0+/, "");
4434
+ if (isBrazilianNational(national)) {
4435
+ digits = `55${national}`;
4436
+ } else if (digits.startsWith("0") && (national.length === 12 || national.length === 13)) {
4437
+ if (isBrazilianNational(national.slice(2))) digits = `55${national.slice(2)}`;
4438
+ }
4367
4439
  }
4368
4440
  if (digits.length < 8 || digits.length > 15 || digits.startsWith("0")) {
4369
4441
  throw new NiadraDestinationError("invalid_handle");
4370
4442
  }
4371
4443
  const rest = digits.slice(2);
4372
- if (digits.startsWith("55") && rest.length === 10 && isArea(rest.slice(0, 2)) && "6789".includes(rest.charAt(2))) {
4444
+ if (digits.startsWith("55") && rest.length === 10 && AREA_CODES.has(rest.slice(0, 2)) && "6789".includes(rest.charAt(2))) {
4373
4445
  digits = `55${rest.slice(0, 2)}9${rest.slice(2)}`;
4374
4446
  }
4447
+ if (digits.length === 13 && (digits.startsWith("521") || digits.startsWith("549"))) {
4448
+ digits = digits.slice(0, 2) + digits.slice(3);
4449
+ }
4375
4450
  return `phone:+${digits}`;
4376
4451
  }
4377
- function isArea(code) {
4378
- return /^[1-9]{2}$/.test(code);
4452
+ function isBrazilianNational(national) {
4453
+ if (!AREA_CODES.has(national.slice(0, 2))) return false;
4454
+ if (national.length === 10) return "23456789".includes(national.charAt(2));
4455
+ return national.length === 11 && national.charAt(2) === "9";
4379
4456
  }
4380
4457
  function saltBytes(salt) {
4381
4458
  const bytes = fromBase64url(salt);
@@ -4434,7 +4511,10 @@ var SuppressionCopy = class {
4434
4511
  }
4435
4512
  this.saltValue = salt;
4436
4513
  }
4437
- /** Applies one page, in order. `true` when more pages follow. */
4514
+ /**
4515
+ * Applies one page, in order. `true` when more pages follow: a full page. Every page carries the cursor to
4516
+ * read from next, the last one too, which the next read starts from.
4517
+ */
4438
4518
  apply(page) {
4439
4519
  if (page.salt_id !== this.saltValue?.salt_id) {
4440
4520
  this.saltValue = null;
@@ -4449,8 +4529,9 @@ var SuppressionCopy = class {
4449
4529
  }
4450
4530
  this.cursor = page.next_cursor ?? this.cursor;
4451
4531
  this.absent = false;
4452
- if (!page.next_cursor) this.readAt = this.now();
4453
- return Boolean(page.next_cursor);
4532
+ const more = Boolean(page.next_cursor) && page.items.length >= PAGE;
4533
+ if (!more) this.readAt = this.now();
4534
+ return more;
4454
4535
  }
4455
4536
  /** A 404: the space keeps no list. Anything else: the copy stays as it is. */
4456
4537
  failed(error) {
@@ -4675,7 +4756,7 @@ var ContactGateway = class {
4675
4756
 
4676
4757
  // src/outbox.ts
4677
4758
  var CAPACITY = 1e3;
4678
- var MAX_PAUSE_MS2 = 6e4;
4759
+ var MAX_PAUSE_MS3 = 6e4;
4679
4760
  var Outbox = class {
4680
4761
  constructor(logger, intervalMs = 1e3, now = Date.now) {
4681
4762
  this.logger = logger;
@@ -4743,7 +4824,7 @@ var Outbox = class {
4743
4824
  answer = await write.send();
4744
4825
  } catch (error) {
4745
4826
  if (isTransient(error)) {
4746
- this.pause = Math.min(MAX_PAUSE_MS2, Math.max(this.intervalMs, this.pause * 2));
4827
+ this.pause = Math.min(MAX_PAUSE_MS3, Math.max(this.intervalMs, this.pause * 2));
4747
4828
  this.resumeAt = this.now() + this.pause;
4748
4829
  return false;
4749
4830
  }
@@ -5526,6 +5607,11 @@ var ContextCache = class {
5526
5607
  }
5527
5608
  return true;
5528
5609
  }
5610
+ /** Milliseconds since the server last sent or confirmed the pack held for `key`; `null` when none is held. */
5611
+ age(key2) {
5612
+ const entry = this.entries.get(key2);
5613
+ return entry ? this.now() - entry.confirmedAt : null;
5614
+ }
5529
5615
  /** The cached pack's ETag, without touching the order of eviction. */
5530
5616
  etag(key2) {
5531
5617
  return this.entries.get(key2)?.response.etag ?? null;
@@ -5748,7 +5834,7 @@ function mergeNotModified(cached, fresh) {
5748
5834
  coordination: fresh.coordination ?? cached.coordination ?? null
5749
5835
  };
5750
5836
  }
5751
- function resultFrom(response, source, error = null) {
5837
+ function resultFrom(response, source, error = null, ageMs = 0) {
5752
5838
  const holdout = response.path === "holdout";
5753
5839
  const text2 = holdout ? "" : response.text ?? "";
5754
5840
  const pack = holdout ? null : response.pack ?? null;
@@ -5757,10 +5843,10 @@ function resultFrom(response, source, error = null) {
5757
5843
  state: response.state ?? null,
5758
5844
  coordination: response.coordination ?? null
5759
5845
  };
5760
- return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, response, error, ...blocks2 };
5846
+ return { text: text2, suffix: renderSuffix(response), variables: response.variables, pack, source, ageMs, response, error, ...blocks2 };
5761
5847
  }
5762
5848
  function emptyResult(error) {
5763
- return { text: "", suffix: "", variables: {}, pack: null, source: "none", response: null, error, constraints: null, state: null, coordination: null };
5849
+ return { text: "", suffix: "", variables: {}, pack: null, source: "none", ageMs: null, response: null, error, constraints: null, state: null, coordination: null };
5764
5850
  }
5765
5851
  function renderSuffix(response) {
5766
5852
  if (response.path === "holdout") return "";
@@ -8457,7 +8543,8 @@ var Niadra = class _Niadra {
8457
8543
  baseURL,
8458
8544
  apiKey,
8459
8545
  fetch: fetchImpl,
8460
- defaultHeaders: options.defaultHeaders ?? {}
8546
+ defaultHeaders: options.defaultHeaders ?? {},
8547
+ logger: this.logger
8461
8548
  });
8462
8549
  const queueOptions = { ...DEFAULT_QUEUE, ...options.queue };
8463
8550
  queueOptions.maxBatchSize = Math.min(queueOptions.maxBatchSize, 499);
@@ -8554,19 +8641,29 @@ var Niadra = class _Niadra {
8554
8641
  */
8555
8642
  async mayContact(handle, purpose, options = {}) {
8556
8643
  if (this.core && this.suppressions.due()) {
8557
- const read = this.suppressions.read(
8558
- {
8559
- salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8560
- page: (cursor, limit3) => this.api.suppressions({ cursor, limit: limit3 }, { timeout: this.timeouts.navigation })
8561
- },
8562
- this.suppressions.held ? this.timeouts.write : this.timeouts.navigation
8563
- );
8644
+ const read = this.readSuppressions(this.suppressions.held ? this.timeouts.write : this.timeouts.navigation);
8564
8645
  if (!this.suppressions.held) await read;
8565
8646
  }
8566
8647
  const checkOptions = { channel: options.channel ?? null };
8567
8648
  if (options.failOpen !== void 0) checkOptions.failOpen = options.failOpen;
8568
8649
  return this.suppressions.mayContact(handle, purpose, checkOptions);
8569
8650
  }
8651
+ readSuppressions(budgetMs) {
8652
+ return this.suppressions.read(
8653
+ {
8654
+ salt: () => this.api.suppressionSalt({ timeout: this.timeouts.navigation }),
8655
+ page: (cursor, limit3) => this.api.suppressions({ cursor, limit: limit3 }, { timeout: this.timeouts.navigation })
8656
+ },
8657
+ budgetMs
8658
+ );
8659
+ }
8660
+ /**
8661
+ * Reads the local copy of the suppression list in the background when it is due: a check that Niadra does not
8662
+ * answer falls back on it, so an opt-out holds through an outage.
8663
+ */
8664
+ keepSuppressions() {
8665
+ if (this.core && this.suppressions.due()) void this.readSuppressions(this.timeouts.write);
8666
+ }
8570
8667
  /**
8571
8668
  * The build a turn runs on, for `conversation.turn({ build })`: each prompt's version, the digest of the
8572
8669
  * corpus the agent consults (computed by you, never the files), the model, your context assembler's version
@@ -8640,7 +8737,10 @@ var Niadra = class _Niadra {
8640
8737
  states: this.states,
8641
8738
  contract: () => this.currentContract(),
8642
8739
  internalText: this.internalText,
8643
- check: (request, timeoutMs) => this.api.check(request, { timeout: timeoutMs }),
8740
+ check: (request, timeoutMs) => {
8741
+ if (request.direction === "outbound") this.keepSuppressions();
8742
+ return this.api.check(request, { timeout: timeoutMs });
8743
+ },
8644
8744
  claim: (request, timeoutMs) => this.api.claim(request, {}, { timeout: timeoutMs }),
8645
8745
  verifyClaim: (ref, field, value, options) => this.verifyClaim(ref, field, value, options),
8646
8746
  enabled: this.enabled,
@@ -8754,21 +8854,21 @@ var Niadra = class _Niadra {
8754
8854
  }
8755
8855
  const key2 = cacheKey(request);
8756
8856
  const hit = cache.lookup(key2);
8757
- if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache");
8857
+ if (hit?.freshness === "fresh") return resultFrom(delivered(hit.response, cache.take(key2)), "cache", null, cache.age(key2));
8758
8858
  if (hit?.freshness === "stale") {
8759
8859
  this.revalidate(core, cache, key2, scope, request, timeout, options.headers).catch((error) => {
8760
8860
  this.logger.debug(`background context refresh failed: ${describe(toNiadraError(error))}`);
8761
8861
  });
8762
- return resultFrom(delivered(hit.response, cache.take(key2)), "stale");
8862
+ return resultFrom(delivered(hit.response, cache.take(key2)), "stale", null, cache.age(key2));
8763
8863
  }
8764
8864
  try {
8765
8865
  const pending = this.revalidate(core, cache, key2, scope, request, timeout, options.headers);
8766
8866
  const { response, source } = await abortable(pending, options.signal);
8767
- return resultFrom(delivered(response, cache.take(key2)), source);
8867
+ return resultFrom(delivered(response, cache.take(key2)), source, null, source === "network" ? 0 : cache.age(key2));
8768
8868
  } catch (error) {
8769
8869
  const failure = toNiadraError(error);
8770
8870
  const fallback2 = cache.lookup(key2);
8771
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8871
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
8772
8872
  }
8773
8873
  }
8774
8874
  /**
@@ -8790,11 +8890,11 @@ var Niadra = class _Niadra {
8790
8890
  const failure = toNiadraError(error);
8791
8891
  this.observeAuth(failure, key2);
8792
8892
  const fallback2 = cache && key2 ? cache.lookup(key2) : null;
8793
- return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
8893
+ return this.contextFailure(failure, cache && key2 && fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, key2 ? cache?.age(key2) ?? null : null);
8794
8894
  }
8795
8895
  if (!cache || !key2 || !scope) return resultFrom(response, "network");
8796
8896
  if (response.degraded && !response.not_modified && cached) {
8797
- return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback");
8897
+ return resultFrom(delivered({ ...cached.response, slots: response.slots ?? null }, cache.take(key2)), "fallback", null, cache.age(key2));
8798
8898
  }
8799
8899
  const merged = response.not_modified && cached ? mergeNotModified(cached.response, response) : response;
8800
8900
  cache.store(key2, scope, withoutSlots(merged), generation);
@@ -9296,11 +9396,11 @@ var Niadra = class _Niadra {
9296
9396
  }
9297
9397
  });
9298
9398
  }
9299
- contextFailure(error, fallback2) {
9399
+ contextFailure(error, fallback2, ageMs = null) {
9300
9400
  if (this.strict) throw error;
9301
9401
  if (fallback2) {
9302
9402
  this.logger.warn(`context request failed, serving the last good pack: ${describe(error)}`);
9303
- return resultFrom(fallback2, "fallback", error);
9403
+ return resultFrom(fallback2, "fallback", error, ageMs);
9304
9404
  }
9305
9405
  this.logger.warn(`context unavailable: ${describe(error)}`);
9306
9406
  return emptyResult(error);
@@ -9401,11 +9501,11 @@ var Niadra = class _Niadra {
9401
9501
  });
9402
9502
  }
9403
9503
  const response = compose(delivered(hit.response, cache.take(key2)), chosen2);
9404
- return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale");
9504
+ return resultFrom(response, opening.length > 0 ? "network" : hit.freshness === "fresh" ? "cache" : "stale", null, cache.age(key2));
9405
9505
  } catch (error) {
9406
9506
  const failure = toNiadraError(error);
9407
9507
  const fallback2 = cache.lookup(key2);
9408
- return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null);
9508
+ return this.contextFailure(failure, fallback2 ? delivered(fallback2.response, cache.take(key2)) : null, cache.age(key2));
9409
9509
  }
9410
9510
  }
9411
9511
  /** Starts one read of a line in the background. */
@@ -9548,7 +9648,7 @@ var Niadra = class _Niadra {
9548
9648
  else resolve({ ok: false, idempotency_key: key2, error });
9549
9649
  };
9550
9650
  core.queue.push(item, settle2);
9551
- core.queue.flushInBackground();
9651
+ core.queue.flushInBackground(true);
9552
9652
  });
9553
9653
  }
9554
9654
  async endScope(item, scope) {
@@ -11568,7 +11668,8 @@ var Counterfactual = class {
11568
11668
  const fn = this.tools[tool2];
11569
11669
  if (fn === void 0) throw new NiadraError(`no function for the tool ${tool2}: pass it in tools`);
11570
11670
  if (!Number.isInteger(k) || k < 1 || k > MAX_K) throw new RangeError(`k must be 1 to ${String(MAX_K)}`);
11571
- await this.niadra.profile();
11671
+ const raw = await this.served(tool2);
11672
+ if (raw === null) throw new NiadraError(`the space binds no tool ${tool2} for this key's source: see its tool-bindings`);
11572
11673
  const ids = [...turnIds];
11573
11674
  if ((options.scenarioIds ?? []).length > 0) {
11574
11675
  const found2 = await this.niadra.callRoute({ method: "GET", path: "/v1/scenarios", query: { ids: (options.scenarioIds ?? []).join(","), limit: 50 } });
@@ -11586,7 +11687,7 @@ var Counterfactual = class {
11586
11687
  continue;
11587
11688
  }
11588
11689
  for (const call of calls(record, tool2)) {
11589
- const found2 = await this.case(record, call, fn, element, k);
11690
+ const found2 = await this.case(record, call, fn, raw, element, k);
11590
11691
  if (found2 === null) run.untouched++;
11591
11692
  else run.cases.push(found2);
11592
11693
  }
@@ -11596,17 +11697,15 @@ var Counterfactual = class {
11596
11697
  run.report = await this.niadra.callRoute({ method: "POST", path: "/v1/measure/counterfactual-runs", body: report });
11597
11698
  return run;
11598
11699
  }
11599
- async case(record, call, fn, element, k) {
11700
+ async case(record, call, fn, raw, element, k) {
11600
11701
  const base = { turn_id: record.turn_id, call_id: call.call_id };
11601
11702
  const tool2 = String(call.name);
11602
11703
  const wrapped = recordedTool(fn);
11603
- const raw = this.options.bindings?.[tool2] ?? wrapped?.binding ?? await this.served(tool2);
11604
11704
  let block;
11605
11705
  let args;
11606
11706
  try {
11607
11707
  block = await this.value(record, blockBlob(record, call));
11608
11708
  args = { ...await this.value(record, call.args) };
11609
- if (raw === null) throw new BlobError("the tool has no binding");
11610
11709
  } catch {
11611
11710
  return { ...base, status: "infrastructure_error", dry_run: false };
11612
11711
  }