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