@niadra/sdk 0.9.0 → 0.10.3
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.
- package/CHANGELOG.md +71 -0
- package/README.md +40 -1
- package/dist/ai-sdk.d.cts +3 -3
- package/dist/ai-sdk.d.ts +3 -3
- package/dist/anthropic.d.cts +4 -4
- package/dist/anthropic.d.ts +4 -4
- package/dist/bedrock.d.cts +4 -4
- package/dist/bedrock.d.ts +4 -4
- package/dist/cli.js +368 -104
- package/dist/cli.js.map +1 -1
- package/dist/{client-B_ip8T2o.d.cts → client-rmWhZJOx.d.cts} +212 -21
- package/dist/{client-B_ip8T2o.d.ts → client-rmWhZJOx.d.ts} +212 -21
- package/dist/cloudflare-agents.d.cts +3 -3
- package/dist/cloudflare-agents.d.ts +3 -3
- package/dist/elevenlabs.cjs +7 -5
- package/dist/elevenlabs.cjs.map +1 -1
- package/dist/elevenlabs.d.cts +5 -5
- package/dist/elevenlabs.d.ts +5 -5
- package/dist/elevenlabs.js +7 -5
- package/dist/elevenlabs.js.map +1 -1
- package/dist/genkit.d.cts +3 -3
- package/dist/genkit.d.ts +3 -3
- package/dist/google-adk.cjs +7 -5
- package/dist/google-adk.cjs.map +1 -1
- package/dist/google-adk.d.cts +3 -3
- package/dist/google-adk.d.ts +3 -3
- package/dist/google-adk.js +7 -5
- package/dist/google-adk.js.map +1 -1
- package/dist/google-genai.d.cts +4 -4
- package/dist/google-genai.d.ts +4 -4
- package/dist/index.cjs +378 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +378 -106
- package/dist/index.js.map +1 -1
- package/dist/{intercept-CxmBLfoj.d.cts → intercept-C0XODsy9.d.cts} +1 -1
- package/dist/{intercept-CBaPK_-k.d.ts → intercept-veMDudKV.d.ts} +1 -1
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.d.cts +3 -3
- package/dist/langchain.d.ts +3 -3
- package/dist/langchain.js.map +1 -1
- package/dist/livekit.cjs +10 -2
- package/dist/livekit.cjs.map +1 -1
- package/dist/livekit.d.cts +3 -3
- package/dist/livekit.d.ts +3 -3
- package/dist/livekit.js +10 -2
- package/dist/livekit.js.map +1 -1
- package/dist/llamaindex.d.cts +3 -3
- package/dist/llamaindex.d.ts +3 -3
- package/dist/mastra.d.cts +3 -3
- package/dist/mastra.d.ts +3 -3
- package/dist/openai-agents.d.cts +3 -3
- package/dist/openai-agents.d.ts +3 -3
- package/dist/retell.cjs +7 -5
- package/dist/retell.cjs.map +1 -1
- package/dist/retell.d.cts +5 -5
- package/dist/retell.d.ts +5 -5
- package/dist/retell.js +7 -5
- package/dist/retell.js.map +1 -1
- package/dist/{shared-BLkAbvuf.d.ts → shared-Cs7h5C4Y.d.ts} +1 -1
- package/dist/{shared-DEkF2Y_z.d.cts → shared-zaTBum17.d.cts} +1 -1
- package/dist/strands.d.cts +3 -3
- package/dist/strands.d.ts +3 -3
- package/dist/twilio.cjs +10 -2
- package/dist/twilio.cjs.map +1 -1
- package/dist/twilio.d.cts +4 -4
- package/dist/twilio.d.ts +4 -4
- package/dist/twilio.js +10 -2
- package/dist/twilio.js.map +1 -1
- package/dist/vapi.cjs +7 -5
- package/dist/vapi.cjs.map +1 -1
- package/dist/vapi.d.cts +5 -5
- package/dist/vapi.d.ts +5 -5
- package/dist/vapi.js +7 -5
- package/dist/vapi.js.map +1 -1
- package/dist/voltagent.d.cts +3 -3
- package/dist/voltagent.d.ts +3 -3
- package/dist/{webhook-BaraTaA9.d.ts → webhook-2V5QgnKI.d.ts} +1 -1
- package/dist/{webhook-CuR0Wckd.d.cts → webhook-CX7nvQig.d.cts} +1 -1
- package/dist/whatsapp.cjs +10 -2
- package/dist/whatsapp.cjs.map +1 -1
- package/dist/whatsapp.d.cts +2 -2
- package/dist/whatsapp.d.ts +2 -2
- package/dist/whatsapp.js +10 -2
- package/dist/whatsapp.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,46 @@ import { resolve as resolvePath } from "path";
|
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
6
|
import { parseArgs as parseArgs4 } from "util";
|
|
7
7
|
|
|
8
|
+
// src/ids.ts
|
|
9
|
+
var lastMs = -1;
|
|
10
|
+
var counter = 0;
|
|
11
|
+
function randomBytes(length) {
|
|
12
|
+
const bytes = new Uint8Array(length);
|
|
13
|
+
const webCrypto = globalThis.crypto;
|
|
14
|
+
if (webCrypto?.getRandomValues) {
|
|
15
|
+
webCrypto.getRandomValues(bytes);
|
|
16
|
+
} else {
|
|
17
|
+
for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
18
|
+
}
|
|
19
|
+
return bytes;
|
|
20
|
+
}
|
|
21
|
+
function uuidv7(now = Date.now()) {
|
|
22
|
+
let ms = now;
|
|
23
|
+
if (ms <= lastMs) {
|
|
24
|
+
ms = lastMs;
|
|
25
|
+
counter = counter + 1 & 4095;
|
|
26
|
+
if (counter === 0) ms += 1;
|
|
27
|
+
} else {
|
|
28
|
+
counter = (randomBytes(2)[0] ?? 0) & 127;
|
|
29
|
+
}
|
|
30
|
+
lastMs = ms;
|
|
31
|
+
const bytes = randomBytes(16);
|
|
32
|
+
const high = Math.floor(ms / 2 ** 16);
|
|
33
|
+
const low = ms % 2 ** 16;
|
|
34
|
+
bytes[0] = high >>> 24 & 255;
|
|
35
|
+
bytes[1] = high >>> 16 & 255;
|
|
36
|
+
bytes[2] = high >>> 8 & 255;
|
|
37
|
+
bytes[3] = high & 255;
|
|
38
|
+
bytes[4] = low >>> 8 & 255;
|
|
39
|
+
bytes[5] = low & 255;
|
|
40
|
+
bytes[6] = 112 | counter >>> 8 & 15;
|
|
41
|
+
bytes[7] = counter & 255;
|
|
42
|
+
bytes[8] = 128 | (bytes[8] ?? 0) & 63;
|
|
43
|
+
let hex2 = "";
|
|
44
|
+
for (const byte of bytes) hex2 += byte.toString(16).padStart(2, "0");
|
|
45
|
+
return `${hex2.slice(0, 8)}-${hex2.slice(8, 12)}-${hex2.slice(12, 16)}-${hex2.slice(16, 20)}-${hex2.slice(20)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
8
48
|
// src/errors.ts
|
|
9
49
|
var NiadraError = class extends Error {
|
|
10
50
|
name = "NiadraError";
|
|
@@ -40,11 +80,12 @@ var NiadraAPIError = class extends NiadraError {
|
|
|
40
80
|
constructor(status, problem, requestId, retryAfterMs = null) {
|
|
41
81
|
const code = problem?.code ?? `http_${status}`;
|
|
42
82
|
const detail = problem?.detail ? `: ${problem.detail}` : "";
|
|
43
|
-
|
|
83
|
+
const id = requestId ?? problem?.request_id ?? null;
|
|
84
|
+
super(`${status} ${code}${detail}${id ? ` (request ${id})` : ""}`);
|
|
44
85
|
this.status = status;
|
|
45
86
|
this.code = code;
|
|
46
87
|
this.problem = problem;
|
|
47
|
-
this.requestId =
|
|
88
|
+
this.requestId = id;
|
|
48
89
|
this.retryAfterMs = retryAfterMs;
|
|
49
90
|
}
|
|
50
91
|
};
|
|
@@ -68,45 +109,11 @@ function toNiadraError(error) {
|
|
|
68
109
|
const message = error instanceof Error ? error.message : String(error);
|
|
69
110
|
return new NiadraError(message, { cause: error });
|
|
70
111
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const bytes = new Uint8Array(length);
|
|
77
|
-
const webCrypto = globalThis.crypto;
|
|
78
|
-
if (webCrypto?.getRandomValues) {
|
|
79
|
-
webCrypto.getRandomValues(bytes);
|
|
80
|
-
} else {
|
|
81
|
-
for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
82
|
-
}
|
|
83
|
-
return bytes;
|
|
84
|
-
}
|
|
85
|
-
function uuidv7(now = Date.now()) {
|
|
86
|
-
let ms = now;
|
|
87
|
-
if (ms <= lastMs) {
|
|
88
|
-
ms = lastMs;
|
|
89
|
-
counter = counter + 1 & 4095;
|
|
90
|
-
if (counter === 0) ms += 1;
|
|
91
|
-
} else {
|
|
92
|
-
counter = (randomBytes(2)[0] ?? 0) & 127;
|
|
93
|
-
}
|
|
94
|
-
lastMs = ms;
|
|
95
|
-
const bytes = randomBytes(16);
|
|
96
|
-
const high = Math.floor(ms / 2 ** 16);
|
|
97
|
-
const low = ms % 2 ** 16;
|
|
98
|
-
bytes[0] = high >>> 24 & 255;
|
|
99
|
-
bytes[1] = high >>> 16 & 255;
|
|
100
|
-
bytes[2] = high >>> 8 & 255;
|
|
101
|
-
bytes[3] = high & 255;
|
|
102
|
-
bytes[4] = low >>> 8 & 255;
|
|
103
|
-
bytes[5] = low & 255;
|
|
104
|
-
bytes[6] = 112 | counter >>> 8 & 15;
|
|
105
|
-
bytes[7] = counter & 255;
|
|
106
|
-
bytes[8] = 128 | (bytes[8] ?? 0) & 63;
|
|
107
|
-
let hex2 = "";
|
|
108
|
-
for (const byte of bytes) hex2 += byte.toString(16).padStart(2, "0");
|
|
109
|
-
return `${hex2.slice(0, 8)}-${hex2.slice(8, 12)}-${hex2.slice(12, 16)}-${hex2.slice(16, 20)}-${hex2.slice(20)}`;
|
|
112
|
+
function explain(error) {
|
|
113
|
+
if (!(error instanceof NiadraAPIError)) return error instanceof Error ? error.name : String(error);
|
|
114
|
+
const detail = error.problem?.detail?.trim();
|
|
115
|
+
const said = `${error.status} ${error.code}${detail ? `: ${detail.slice(0, 300)}` : ""}`;
|
|
116
|
+
return error.requestId ? `${said} (request ${error.requestId})` : said;
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
// src/admin.ts
|
|
@@ -503,7 +510,7 @@ function internalRecord(span, ref, act) {
|
|
|
503
510
|
}
|
|
504
511
|
|
|
505
512
|
// src/version.ts
|
|
506
|
-
var VERSION = "0.
|
|
513
|
+
var VERSION = "0.10.3";
|
|
507
514
|
|
|
508
515
|
// src/transport.ts
|
|
509
516
|
var RETRYABLE_WRITE_STATUS = /* @__PURE__ */ new Set([408, 421, 429, 500, 502, 503, 504]);
|
|
@@ -516,8 +523,32 @@ var Transport = class {
|
|
|
516
523
|
}
|
|
517
524
|
config;
|
|
518
525
|
baseURL;
|
|
526
|
+
answeredAt;
|
|
527
|
+
/** When the allowance was first given since the last answer; it holds for the calls of that moment only. */
|
|
528
|
+
grantedAt;
|
|
529
|
+
/** When the client last sent a request of its own (`RequestSpec.activity`), in epoch milliseconds. */
|
|
530
|
+
lastActivityAt;
|
|
519
531
|
async request(spec) {
|
|
520
|
-
|
|
532
|
+
if (spec.activity !== false) this.lastActivityAt = Date.now();
|
|
533
|
+
const budgeted = this.withAllowance(spec);
|
|
534
|
+
return budgeted.retry.kind === "read" ? this.read(budgeted, budgeted.retry) : this.write(budgeted, budgeted.retry);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* `spec` with the allowance for opening a connection, when it has a budget and none is likely open. The calls
|
|
538
|
+
* that start while the first one opens it get it too; after that, none does until an answer comes, so an
|
|
539
|
+
* outage costs the allowance once, not on every turn.
|
|
540
|
+
*/
|
|
541
|
+
withAllowance(spec) {
|
|
542
|
+
const allowance = this.config.coldAllowanceMs ?? 0;
|
|
543
|
+
const { retry } = spec;
|
|
544
|
+
if (allowance <= 0 || retry.kind === "write" && retry.totalMs === void 0) return spec;
|
|
545
|
+
const now = Date.now();
|
|
546
|
+
const keep = this.config.keepAliveMs ?? 0;
|
|
547
|
+
if (this.answeredAt !== void 0 && now - this.answeredAt <= keep) return spec;
|
|
548
|
+
this.grantedAt ??= now;
|
|
549
|
+
if (now - this.grantedAt > allowance) return spec;
|
|
550
|
+
const total = retry.kind === "write" && retry.totalMs !== void 0 ? { ...retry, totalMs: retry.totalMs + allowance } : retry;
|
|
551
|
+
return { ...spec, timeoutMs: spec.timeoutMs + allowance, retry: total };
|
|
521
552
|
}
|
|
522
553
|
async read(spec, policy) {
|
|
523
554
|
const deadline = new Deadline(spec.timeoutMs, spec.signal);
|
|
@@ -588,7 +619,18 @@ var Transport = class {
|
|
|
588
619
|
headers["content-type"] = "application/json";
|
|
589
620
|
init.body = JSON.stringify(spec.body);
|
|
590
621
|
}
|
|
591
|
-
|
|
622
|
+
try {
|
|
623
|
+
const answer = await this.exchange(this.url(spec.path, spec.query), init, deadline);
|
|
624
|
+
this.answered();
|
|
625
|
+
return answer;
|
|
626
|
+
} catch (error) {
|
|
627
|
+
if (error instanceof NiadraAPIError) this.answered();
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
answered() {
|
|
632
|
+
this.answeredAt = Date.now();
|
|
633
|
+
this.grantedAt = void 0;
|
|
592
634
|
}
|
|
593
635
|
async exchange(url, init, deadline) {
|
|
594
636
|
let response;
|
|
@@ -597,6 +639,7 @@ var Transport = class {
|
|
|
597
639
|
} catch (error) {
|
|
598
640
|
throw deadline.explain(error);
|
|
599
641
|
}
|
|
642
|
+
warnIfDeprecated(this.config.logger, init.method ?? "GET", url, response.headers);
|
|
600
643
|
const requestId = response.headers.get("x-request-id");
|
|
601
644
|
let payload;
|
|
602
645
|
try {
|
|
@@ -666,6 +709,29 @@ var Deadline = class {
|
|
|
666
709
|
return new NiadraConnectionError(`connection failed: ${message}`, { cause: error });
|
|
667
710
|
}
|
|
668
711
|
};
|
|
712
|
+
var VERSIONING_DOCS = "https://docs.niadra.com/en/security/api-versioning";
|
|
713
|
+
var DEPRECATION_LINK = /<([^>]*)>[^,]*;\s*rel="?deprecation"?/gi;
|
|
714
|
+
var deprecationsSeen = /* @__PURE__ */ new Set();
|
|
715
|
+
function warnIfDeprecated(logger, method, url, headers) {
|
|
716
|
+
const since = headers.get("deprecation");
|
|
717
|
+
if (since === null) return;
|
|
718
|
+
const links = [...(headers.get("link") ?? "").matchAll(DEPRECATION_LINK)].map((match) => match[1] ?? "");
|
|
719
|
+
const key2 = `${method} ${links.join(" ") || (url.split("?")[0] ?? url)}`;
|
|
720
|
+
if (deprecationsSeen.has(key2)) return;
|
|
721
|
+
deprecationsSeen.add(key2);
|
|
722
|
+
const sunset = headers.get("sunset");
|
|
723
|
+
logger.warn(
|
|
724
|
+
`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}`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
function deprecatedSince(value) {
|
|
728
|
+
const seconds = Number(value.trim().replace(/^@/, ""));
|
|
729
|
+
return Number.isInteger(seconds) ? new Date(seconds * 1e3).toISOString().slice(0, 10) : value;
|
|
730
|
+
}
|
|
731
|
+
function sunsetDay(value) {
|
|
732
|
+
const time = Date.parse(value);
|
|
733
|
+
return Number.isNaN(time) ? value : new Date(time).toISOString().slice(0, 10);
|
|
734
|
+
}
|
|
669
735
|
async function readBody(response) {
|
|
670
736
|
const text2 = await response.text();
|
|
671
737
|
if (text2.length === 0) return null;
|
|
@@ -4001,8 +4067,7 @@ var TurnSender = class {
|
|
|
4001
4067
|
return true;
|
|
4002
4068
|
}
|
|
4003
4069
|
}
|
|
4004
|
-
|
|
4005
|
-
this.recorder.rejected(batch.frames.length, [code]);
|
|
4070
|
+
this.recorder.rejected(batch.frames.length, [explain(error)]);
|
|
4006
4071
|
return true;
|
|
4007
4072
|
}
|
|
4008
4073
|
accepted(batch, answer) {
|
|
@@ -4010,8 +4075,8 @@ var TurnSender = class {
|
|
|
4010
4075
|
this.resumeAt = 0;
|
|
4011
4076
|
this.recorder.sent(answer.accepted, answer.duplicates);
|
|
4012
4077
|
const again = [];
|
|
4013
|
-
const
|
|
4014
|
-
let
|
|
4078
|
+
const reasons = /* @__PURE__ */ new Set();
|
|
4079
|
+
let refused3 = 0;
|
|
4015
4080
|
for (const error of answer.errors ?? []) {
|
|
4016
4081
|
const frame = batch.frames[error.index];
|
|
4017
4082
|
if (frame === void 0) continue;
|
|
@@ -4020,12 +4085,12 @@ var TurnSender = class {
|
|
|
4020
4085
|
this.recorder.modeRefused();
|
|
4021
4086
|
again.push(frame);
|
|
4022
4087
|
} else {
|
|
4023
|
-
|
|
4024
|
-
|
|
4088
|
+
refused3++;
|
|
4089
|
+
reasons.add(error.detail ? `${error.code}: ${error.detail.slice(0, 300)}` : error.code);
|
|
4025
4090
|
}
|
|
4026
4091
|
}
|
|
4027
4092
|
if (again.length > 0) this.queue.requeue(again);
|
|
4028
|
-
if (
|
|
4093
|
+
if (refused3 > 0) this.recorder.rejected(refused3, [...reasons]);
|
|
4029
4094
|
}
|
|
4030
4095
|
};
|
|
4031
4096
|
|
|
@@ -4147,9 +4212,12 @@ var TurnRecorder = class {
|
|
|
4147
4212
|
this.accepted += accepted;
|
|
4148
4213
|
this.duplicates += duplicates;
|
|
4149
4214
|
}
|
|
4150
|
-
|
|
4215
|
+
/** `reasons`: the API's code and detail of each refusal (field paths and rules, never a value). */
|
|
4216
|
+
rejected(count2, reasons) {
|
|
4151
4217
|
this.rejectedTurns += count2;
|
|
4152
|
-
|
|
4218
|
+
const shown = [...new Set(reasons)].sort();
|
|
4219
|
+
const more = shown.length > 3 ? ` (and ${shown.length - 3} more)` : "";
|
|
4220
|
+
this.logger.warn(`${count2} turn records were refused: ${shown.slice(0, 3).join("; ")}${more}`);
|
|
4153
4221
|
}
|
|
4154
4222
|
/** The space does not record turns: these are dropped, and with `off` (a 404) recording stops a while. */
|
|
4155
4223
|
notRecorded(count2, off) {
|
|
@@ -4219,6 +4287,10 @@ async function hex(text2) {
|
|
|
4219
4287
|
}
|
|
4220
4288
|
|
|
4221
4289
|
// src/coordination/client.ts
|
|
4290
|
+
var REFUSED = /* @__PURE__ */ new Set([400, 401, 403, 422]);
|
|
4291
|
+
function refused(error) {
|
|
4292
|
+
return error instanceof NiadraAPIError && REFUSED.has(error.status);
|
|
4293
|
+
}
|
|
4222
4294
|
var FAIL_CLOSED = /* @__PURE__ */ new Set(["marketing", "retention", "collection"]);
|
|
4223
4295
|
var CHECK_BUDGET_MS = 200;
|
|
4224
4296
|
function fallback(request, suppressed, failOpen) {
|
|
@@ -4240,15 +4312,22 @@ function claimed(data, error) {
|
|
|
4240
4312
|
return { held: false, claim: null, error: code };
|
|
4241
4313
|
}
|
|
4242
4314
|
var Coordinator = class {
|
|
4243
|
-
constructor(outbox, suppressions, declareNow) {
|
|
4315
|
+
constructor(outbox, suppressions, declareNow, logger) {
|
|
4244
4316
|
this.outbox = outbox;
|
|
4245
4317
|
this.suppressions = suppressions;
|
|
4246
4318
|
this.declareNow = declareNow;
|
|
4319
|
+
this.logger = logger;
|
|
4247
4320
|
}
|
|
4248
4321
|
outbox;
|
|
4249
4322
|
suppressions;
|
|
4250
4323
|
declareNow;
|
|
4324
|
+
logger;
|
|
4251
4325
|
async failed(request, failOpen, error) {
|
|
4326
|
+
if (refused(error)) {
|
|
4327
|
+
this.logger?.warn(`the coordination check was refused: ${explain(error)}`);
|
|
4328
|
+
const decision = request.direction === "inbound" ? "allow" : "defer";
|
|
4329
|
+
return recorded({ decision, decision_id: uuidv7(), reasons: ["invalid_request"], valid_for_s: 0 });
|
|
4330
|
+
}
|
|
4252
4331
|
const off = error instanceof NiadraAPIError && error.status === 404;
|
|
4253
4332
|
const suppressed = request.subject != null && request.direction === "outbound" ? !await this.suppressions.mayContact(request.subject, request.purpose, { channel: request.channel ?? null, failOpen: true }) : false;
|
|
4254
4333
|
const plain2 = off ? { ...request, effect_key: null } : request;
|
|
@@ -4269,7 +4348,7 @@ var Coordinator = class {
|
|
|
4269
4348
|
if (who.subject) body.subject = who.subject;
|
|
4270
4349
|
if (who.object) body.object = who.object;
|
|
4271
4350
|
const key2 = uuidv7();
|
|
4272
|
-
this.outbox.put({ send: () => this.declareNow(body, key2) });
|
|
4351
|
+
this.outbox.put({ send: () => this.declareNow(body, key2), route: "POST /v1/coordination/declare" });
|
|
4273
4352
|
return key2;
|
|
4274
4353
|
}
|
|
4275
4354
|
};
|
|
@@ -4376,26 +4455,42 @@ async function suppressionKey(salt, canonical) {
|
|
|
4376
4455
|
const key2 = await subtle.importKey("raw", saltBytes(salt), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
4377
4456
|
return toBase64url(new Uint8Array(await subtle.sign("HMAC", key2, new TextEncoder().encode(canonical))));
|
|
4378
4457
|
}
|
|
4458
|
+
var AREA_CODES = new Set(
|
|
4459
|
+
"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(" ")
|
|
4460
|
+
);
|
|
4461
|
+
var FORMAT_CHARACTERS = new RegExp("\\p{Cf}", "gu");
|
|
4379
4462
|
function phone(value) {
|
|
4380
|
-
|
|
4381
|
-
|
|
4463
|
+
let text2 = value.normalize("NFKC").replace(FORMAT_CHARACTERS, "").trim();
|
|
4464
|
+
if (text2.slice(0, 4).toLowerCase() === "tel:") text2 = text2.slice(4);
|
|
4465
|
+
const international = text2.startsWith("+") || text2.startsWith("00");
|
|
4466
|
+
if (international) text2 = text2.replaceAll("(0)", "");
|
|
4467
|
+
const raw = text2.replace(SEPARATORS2, "");
|
|
4382
4468
|
let digits = raw.startsWith("+") ? raw.slice(1) : international ? raw.slice(2) : raw;
|
|
4383
4469
|
if (!DIGITS2.test(digits)) throw new NiadraDestinationError("invalid_handle");
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4470
|
+
if (!international) {
|
|
4471
|
+
const national = digits.replace(/^0+/, "");
|
|
4472
|
+
if (isBrazilianNational(national)) {
|
|
4473
|
+
digits = `55${national}`;
|
|
4474
|
+
} else if (digits.startsWith("0") && (national.length === 12 || national.length === 13)) {
|
|
4475
|
+
if (isBrazilianNational(national.slice(2))) digits = `55${national.slice(2)}`;
|
|
4476
|
+
}
|
|
4387
4477
|
}
|
|
4388
4478
|
if (digits.length < 8 || digits.length > 15 || digits.startsWith("0")) {
|
|
4389
4479
|
throw new NiadraDestinationError("invalid_handle");
|
|
4390
4480
|
}
|
|
4391
4481
|
const rest = digits.slice(2);
|
|
4392
|
-
if (digits.startsWith("55") && rest.length === 10 &&
|
|
4482
|
+
if (digits.startsWith("55") && rest.length === 10 && AREA_CODES.has(rest.slice(0, 2)) && "6789".includes(rest.charAt(2))) {
|
|
4393
4483
|
digits = `55${rest.slice(0, 2)}9${rest.slice(2)}`;
|
|
4394
4484
|
}
|
|
4485
|
+
if (digits.length === 13 && (digits.startsWith("521") || digits.startsWith("549"))) {
|
|
4486
|
+
digits = digits.slice(0, 2) + digits.slice(3);
|
|
4487
|
+
}
|
|
4395
4488
|
return `phone:+${digits}`;
|
|
4396
4489
|
}
|
|
4397
|
-
function
|
|
4398
|
-
|
|
4490
|
+
function isBrazilianNational(national) {
|
|
4491
|
+
if (!AREA_CODES.has(national.slice(0, 2))) return false;
|
|
4492
|
+
if (national.length === 10) return "23456789".includes(national.charAt(2));
|
|
4493
|
+
return national.length === 11 && national.charAt(2) === "9";
|
|
4399
4494
|
}
|
|
4400
4495
|
function saltBytes(salt) {
|
|
4401
4496
|
const bytes = fromBase64url(salt);
|
|
@@ -4772,7 +4867,7 @@ var Outbox = class {
|
|
|
4772
4867
|
return false;
|
|
4773
4868
|
}
|
|
4774
4869
|
this.writes.shift();
|
|
4775
|
-
this.logger.warn(
|
|
4870
|
+
this.logger.warn(`${write.route ?? "a write"} was refused: ${explain(error)}`);
|
|
4776
4871
|
this.settle(write, void 0, error);
|
|
4777
4872
|
return true;
|
|
4778
4873
|
}
|
|
@@ -6309,6 +6404,7 @@ var AgentSession = class {
|
|
|
6309
6404
|
try {
|
|
6310
6405
|
return this.host.coordinator.decided(await this.host.check(request, options.timeoutMs ?? CHECK_BUDGET_MS), request, this.checked);
|
|
6311
6406
|
} catch (error) {
|
|
6407
|
+
if (this.host.strict && refused(error)) throw error;
|
|
6312
6408
|
return this.host.coordinator.failed(request, options.failOpen, error);
|
|
6313
6409
|
}
|
|
6314
6410
|
}
|
|
@@ -6920,6 +7016,7 @@ var SessionState = class {
|
|
|
6920
7016
|
/** The guards of the last read: they hold the answers to the turn they were written for. */
|
|
6921
7017
|
guards = /* @__PURE__ */ new Map();
|
|
6922
7018
|
lastReport = null;
|
|
7019
|
+
unlinkedSaid = false;
|
|
6923
7020
|
/** After the first pack, every read also asks what changed since. */
|
|
6924
7021
|
get wantsDelta() {
|
|
6925
7022
|
return this.etag !== null;
|
|
@@ -6948,6 +7045,19 @@ var SessionState = class {
|
|
|
6948
7045
|
this.last = absorbed;
|
|
6949
7046
|
return absorbed;
|
|
6950
7047
|
}
|
|
7048
|
+
/**
|
|
7049
|
+
* Once per session: the read named an organization (`about`) with no active link to the subject, so it went
|
|
7050
|
+
* on with the subject's own memory, never the organization's.
|
|
7051
|
+
*/
|
|
7052
|
+
sayUnlinked(result2, logger) {
|
|
7053
|
+
if (result2.response?.about_unlinked && !this.unlinkedSaid) {
|
|
7054
|
+
this.unlinkedSaid = true;
|
|
7055
|
+
logger.warn(
|
|
7056
|
+
"`about` names an organization with no active link to the subject; the context is the subject's own, without that organization's memory. Create the link to read it."
|
|
7057
|
+
);
|
|
7058
|
+
}
|
|
7059
|
+
return result2;
|
|
7060
|
+
}
|
|
6951
7061
|
/** The last backing check of an agent's answer. */
|
|
6952
7062
|
get lastBacking() {
|
|
6953
7063
|
return this.lastReport;
|
|
@@ -7195,7 +7305,7 @@ var Conversation = class {
|
|
|
7195
7305
|
* `query` picks this read's slots by other words than the turn; the pack is the pinned one.
|
|
7196
7306
|
*/
|
|
7197
7307
|
async context(options = {}) {
|
|
7198
|
-
const { query, turn: turn2, format, explain, include, ...requestOptions } = options;
|
|
7308
|
+
const { query, turn: turn2, format, explain: explain2, include, ...requestOptions } = options;
|
|
7199
7309
|
const params = {
|
|
7200
7310
|
subject: this.subject,
|
|
7201
7311
|
view: this.view,
|
|
@@ -7204,7 +7314,7 @@ var Conversation = class {
|
|
|
7204
7314
|
...this.params.about ? { about: this.params.about } : {},
|
|
7205
7315
|
...this.params.target ? { target: this.params.target } : {},
|
|
7206
7316
|
...format === "json" ? { format } : {},
|
|
7207
|
-
...
|
|
7317
|
+
...explain2 ? { explain: explain2 } : {}
|
|
7208
7318
|
};
|
|
7209
7319
|
if (query) params.query = query;
|
|
7210
7320
|
if (include?.length) params.include = include;
|
|
@@ -7212,7 +7322,7 @@ var Conversation = class {
|
|
|
7212
7322
|
params.turn = turn2 === void 0 ? this.turnText : turn2;
|
|
7213
7323
|
const result2 = await this.client.context(params, requestOptions);
|
|
7214
7324
|
this.features.observe(result2);
|
|
7215
|
-
return this.state.observe(this.state.absorb(result2));
|
|
7325
|
+
return this.state.observe(this.state.sayUnlinked(this.state.absorb(result2), this.logger));
|
|
7216
7326
|
}
|
|
7217
7327
|
/**
|
|
7218
7328
|
* Starts this conversation's first read now, in the background: call it when the call starts
|
|
@@ -7392,6 +7502,35 @@ function withHandles(base, extra) {
|
|
|
7392
7502
|
return all;
|
|
7393
7503
|
}
|
|
7394
7504
|
|
|
7505
|
+
// src/warm.ts
|
|
7506
|
+
var EVERY_MS = 1e5;
|
|
7507
|
+
var IDLE_MS = 9e4;
|
|
7508
|
+
var WARM_FOR_MS = 6e5;
|
|
7509
|
+
var KeepWarm = class {
|
|
7510
|
+
constructor(enabled) {
|
|
7511
|
+
this.enabled = enabled;
|
|
7512
|
+
}
|
|
7513
|
+
enabled;
|
|
7514
|
+
open = /* @__PURE__ */ new Map();
|
|
7515
|
+
openedAt = 0;
|
|
7516
|
+
/** Notes an open conversation or task. */
|
|
7517
|
+
add(scope, session, now) {
|
|
7518
|
+
if (!this.enabled || typeof WeakRef === "undefined") return;
|
|
7519
|
+
this.open.set(scope, new WeakRef(session));
|
|
7520
|
+
this.openedAt = now;
|
|
7521
|
+
}
|
|
7522
|
+
/** The conversation or task ended. */
|
|
7523
|
+
end(scope) {
|
|
7524
|
+
this.open.delete(scope);
|
|
7525
|
+
}
|
|
7526
|
+
step(now, lastActivityAt, idleMs = IDLE_MS, warmForMs = WARM_FOR_MS) {
|
|
7527
|
+
for (const [scope, ref] of this.open) if (ref.deref() === void 0) this.open.delete(scope);
|
|
7528
|
+
const used = Math.max(lastActivityAt ?? 0, this.openedAt);
|
|
7529
|
+
if (this.open.size === 0 || now - used > warmForMs) return "stop";
|
|
7530
|
+
return now - used >= idleMs ? "ping" : "wait";
|
|
7531
|
+
}
|
|
7532
|
+
};
|
|
7533
|
+
|
|
7395
7534
|
// src/exit.ts
|
|
7396
7535
|
var registered = /* @__PURE__ */ new Set();
|
|
7397
7536
|
var installedOn = null;
|
|
@@ -7521,8 +7660,10 @@ var DEFAULT_TIMEOUTS = {
|
|
|
7521
7660
|
write: 5e3,
|
|
7522
7661
|
token: 2e3,
|
|
7523
7662
|
upload: 6e4,
|
|
7524
|
-
prefetch: 1e3
|
|
7663
|
+
prefetch: 1e3,
|
|
7664
|
+
connect: 1e3
|
|
7525
7665
|
};
|
|
7666
|
+
var FETCH_KEEPALIVE_MS = 4e3;
|
|
7526
7667
|
var DEFAULT_CACHE = {
|
|
7527
7668
|
ttlMs: 1e4,
|
|
7528
7669
|
staleWhileRevalidateMs: 10 * 6e4,
|
|
@@ -7629,7 +7770,7 @@ var Task = class {
|
|
|
7629
7770
|
* Resolves with an empty result, never rejects, unless the client is strict.
|
|
7630
7771
|
*/
|
|
7631
7772
|
async context(options = {}) {
|
|
7632
|
-
const { query, format, explain, include, ...requestOptions } = options;
|
|
7773
|
+
const { query, format, explain: explain2, include, ...requestOptions } = options;
|
|
7633
7774
|
const target = this.object ? { object: this.object } : this.params.subject ? { subject: this.params.subject } : {};
|
|
7634
7775
|
const params = {
|
|
7635
7776
|
...target,
|
|
@@ -7639,18 +7780,18 @@ var Task = class {
|
|
|
7639
7780
|
...this.level ? { verification: this.level } : {},
|
|
7640
7781
|
...this.params.target ? { target: this.params.target } : {},
|
|
7641
7782
|
...format === "json" ? { format } : {},
|
|
7642
|
-
...
|
|
7783
|
+
...explain2 ? { explain: explain2 } : {},
|
|
7643
7784
|
...include?.length ? { include } : {}
|
|
7644
7785
|
};
|
|
7645
7786
|
if (query) {
|
|
7646
7787
|
const answered = await this.client.context({ ...params, query }, requestOptions);
|
|
7647
7788
|
this.features.observe(answered);
|
|
7648
|
-
return this.state.observe(answered);
|
|
7789
|
+
return this.state.observe(this.state.sayUnlinked(answered, this.logger));
|
|
7649
7790
|
}
|
|
7650
7791
|
if (this.state.wantsDelta) params.delta = true;
|
|
7651
7792
|
const result2 = await this.client.context(params, requestOptions);
|
|
7652
7793
|
this.features.observe(result2);
|
|
7653
|
-
return this.state.observe(this.state.absorb(result2));
|
|
7794
|
+
return this.state.observe(this.state.sayUnlinked(this.state.absorb(result2), this.logger));
|
|
7654
7795
|
}
|
|
7655
7796
|
/**
|
|
7656
7797
|
* The agent's own working notes for this task's prompt, as `niadra.agentMemory()` with
|
|
@@ -7780,14 +7921,14 @@ var AGENT_MEMORY_TOOL_NAMES = {
|
|
|
7780
7921
|
search: "search_agent_memory",
|
|
7781
7922
|
remember: "remember"
|
|
7782
7923
|
};
|
|
7783
|
-
var ITEM_KINDS = ["episode", "fact", "open_item", "action", "object", "trait"];
|
|
7924
|
+
var ITEM_KINDS = ["episode", "fact", "open_item", "action", "object", "trait", "system_event"];
|
|
7784
7925
|
var NOTE_KINDS = ["procedure", "tool_note", "process_note", "pitfall"];
|
|
7785
7926
|
var TOOL_DEFINITIONS = [
|
|
7786
7927
|
{
|
|
7787
7928
|
"type": "function",
|
|
7788
7929
|
"function": {
|
|
7789
7930
|
"name": "search_customer_history",
|
|
7790
|
-
"description": "Search everything that already happened with this customer: past conversations, promises, agent actions, orders and invoices. Use it when the customer refers to something earlier or asks whether a problem happened before. Do not call it when the answer is already in the context block, including its '
|
|
7931
|
+
"description": "Search everything that already happened with this customer: past conversations, promises, agent actions, orders and invoices. Use it when the customer refers to something earlier or asks whether a problem happened before. Do not call it when the answer is already in the context block, including its 'Hist\xF3rico' line, which already counts recurrences. Returns short items with date, channel and outcome, plus a recurrence count. Open a conversation (`episode:`) or an object (`object:`) with open_history_item; the other items are complete as listed.",
|
|
7791
7932
|
"parameters": {
|
|
7792
7933
|
"type": "object",
|
|
7793
7934
|
"properties": {
|
|
@@ -7831,7 +7972,8 @@ var TOOL_DEFINITIONS = [
|
|
|
7831
7972
|
"open_item",
|
|
7832
7973
|
"action",
|
|
7833
7974
|
"object",
|
|
7834
|
-
"trait"
|
|
7975
|
+
"trait",
|
|
7976
|
+
"system_event"
|
|
7835
7977
|
]
|
|
7836
7978
|
}
|
|
7837
7979
|
},
|
|
@@ -7858,7 +8000,7 @@ var TOOL_DEFINITIONS = [
|
|
|
7858
8000
|
"type": "function",
|
|
7859
8001
|
"function": {
|
|
7860
8002
|
"name": "get_customer_timeline",
|
|
7861
|
-
"description": "List this customer's conversations
|
|
8003
|
+
"description": "List this customer's conversations, agent actions and system events in order, newest first, one line each. Use it to leaf through the history when you do not know what to search for; filters.item_kinds also lists the open items, facts, patterns or objects it names. Pass next_cursor to continue. Prefer search_customer_history for a specific question.",
|
|
7862
8004
|
"parameters": {
|
|
7863
8005
|
"type": "object",
|
|
7864
8006
|
"properties": {
|
|
@@ -7906,7 +8048,8 @@ var TOOL_DEFINITIONS = [
|
|
|
7906
8048
|
"open_item",
|
|
7907
8049
|
"action",
|
|
7908
8050
|
"object",
|
|
7909
|
-
"trait"
|
|
8051
|
+
"trait",
|
|
8052
|
+
"system_event"
|
|
7910
8053
|
]
|
|
7911
8054
|
}
|
|
7912
8055
|
},
|
|
@@ -7925,7 +8068,7 @@ var TOOL_DEFINITIONS = [
|
|
|
7925
8068
|
"type": "function",
|
|
7926
8069
|
"function": {
|
|
7927
8070
|
"name": "open_history_item",
|
|
7928
|
-
"description": "Open one conversation or business object returned by search_customer_history or get_customer_timeline: what was asked, what was promised and by whom, the outcome and what memory came from it. Use it only after a search or timeline pointed to the item.",
|
|
8071
|
+
"description": "Open one conversation (`episode:`) or business object (`object:`) returned by search_customer_history or get_customer_timeline: what was asked, what was promised and by whom, the outcome and what memory came from it. Actions, events, facts and promises are complete as listed and do not open. Use it only after a search or timeline pointed to the item.",
|
|
7929
8072
|
"parameters": {
|
|
7930
8073
|
"type": "object",
|
|
7931
8074
|
"properties": {
|
|
@@ -8296,6 +8439,13 @@ function compose(body, read2) {
|
|
|
8296
8439
|
const pack = body.pack ? { pack: { ...body.pack, slots: fetched?.pack?.slots ?? [] } } : {};
|
|
8297
8440
|
return { ...body, slots: fetched?.slots ?? null, guards: fetched?.guards ?? [], ...pack };
|
|
8298
8441
|
}
|
|
8442
|
+
var RTT_MARGIN_MS = 50;
|
|
8443
|
+
function budgetWarnings(rttMs, timeouts, explicit) {
|
|
8444
|
+
const ms = Math.round(rttMs);
|
|
8445
|
+
return [...explicit].sort().filter((name) => timeouts[name] < rttMs + RTT_MARGIN_MS).map(
|
|
8446
|
+
(name) => `timeouts.${name} (${timeouts[name]} ms) is shorter than the round trip to the region (${ms} ms) plus ${RTT_MARGIN_MS} ms for the API: its reads will run out of time. Leave it at its default, which adds the measured round trip, or raise it`
|
|
8447
|
+
);
|
|
8448
|
+
}
|
|
8299
8449
|
function rttWarnings(rttMs, timeouts) {
|
|
8300
8450
|
const ms = Math.round(rttMs);
|
|
8301
8451
|
const found2 = [];
|
|
@@ -8333,6 +8483,12 @@ var Niadra = class _Niadra {
|
|
|
8333
8483
|
enabled;
|
|
8334
8484
|
core;
|
|
8335
8485
|
timeouts;
|
|
8486
|
+
keepWarm;
|
|
8487
|
+
warmTimer;
|
|
8488
|
+
/** The read budgets the caller left at their defaults: they take the measured round trip on top. */
|
|
8489
|
+
defaultReads;
|
|
8490
|
+
voiceStarted = false;
|
|
8491
|
+
voiceWarned = false;
|
|
8336
8492
|
strict;
|
|
8337
8493
|
/** Where the client reports what it swallows in fail-open mode. */
|
|
8338
8494
|
logger;
|
|
@@ -8377,6 +8533,8 @@ var Niadra = class _Niadra {
|
|
|
8377
8533
|
this.strict = options.strict ?? false;
|
|
8378
8534
|
this.logger = options.logger ?? consoleLogger;
|
|
8379
8535
|
this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
|
|
8536
|
+
this.keepWarm = new KeepWarm(options.keepWarm ?? true);
|
|
8537
|
+
this.defaultReads = new Set(["context", "navigation"].filter((name) => options.timeouts?.[name] === void 0));
|
|
8380
8538
|
this.voice = new VoiceLines(
|
|
8381
8539
|
options.voice === false ? { ...DEFAULT_VOICE, enabled: false } : { ...DEFAULT_VOICE, ...options.voice }
|
|
8382
8540
|
);
|
|
@@ -8402,13 +8560,14 @@ var Niadra = class _Niadra {
|
|
|
8402
8560
|
this.coordinator = new Coordinator(
|
|
8403
8561
|
this.outbox,
|
|
8404
8562
|
this.suppressions,
|
|
8405
|
-
(body, key2) => this.api.declare(body, { idempotency_key: key2 })
|
|
8563
|
+
(body, key2) => this.api.declare(body, { idempotency_key: key2 }),
|
|
8564
|
+
this.logger
|
|
8406
8565
|
);
|
|
8407
8566
|
this.states = new AgentStates(
|
|
8408
8567
|
this.outbox,
|
|
8409
8568
|
{
|
|
8410
|
-
read: (scope, agent) => this.api.readAgentState({ scope, agent }, { timeout: this.
|
|
8411
|
-
write: (write) => this.api.writeAgentState(write, { timeout: this.
|
|
8569
|
+
read: (scope, agent) => this.api.readAgentState({ scope, agent }, { timeout: this.readBudget("navigation") }),
|
|
8570
|
+
write: (write) => this.api.writeAgentState(write, { timeout: this.readBudget("navigation") })
|
|
8412
8571
|
},
|
|
8413
8572
|
this.logger
|
|
8414
8573
|
);
|
|
@@ -8435,6 +8594,7 @@ var Niadra = class _Niadra {
|
|
|
8435
8594
|
this.core = setup;
|
|
8436
8595
|
this.enabled = true;
|
|
8437
8596
|
this.disabledReason = null;
|
|
8597
|
+
this.probe(setup);
|
|
8438
8598
|
if (options.flushOnExit ?? true) this.unregisterExit = registerExitFlush(this);
|
|
8439
8599
|
}
|
|
8440
8600
|
setup(options) {
|
|
@@ -8456,7 +8616,10 @@ var Niadra = class _Niadra {
|
|
|
8456
8616
|
baseURL,
|
|
8457
8617
|
apiKey,
|
|
8458
8618
|
fetch: fetchImpl,
|
|
8459
|
-
defaultHeaders: options.defaultHeaders ?? {}
|
|
8619
|
+
defaultHeaders: options.defaultHeaders ?? {},
|
|
8620
|
+
logger: this.logger,
|
|
8621
|
+
coldAllowanceMs: this.timeouts.connect,
|
|
8622
|
+
keepAliveMs: options.keepAliveMs ?? FETCH_KEEPALIVE_MS
|
|
8460
8623
|
});
|
|
8461
8624
|
const queueOptions = { ...DEFAULT_QUEUE, ...options.queue };
|
|
8462
8625
|
queueOptions.maxBatchSize = Math.min(queueOptions.maxBatchSize, 499);
|
|
@@ -8512,7 +8675,7 @@ var Niadra = class _Niadra {
|
|
|
8512
8675
|
return result2;
|
|
8513
8676
|
}
|
|
8514
8677
|
readContext(core, params, request, options) {
|
|
8515
|
-
const timeout = options.timeout ?? (request.view === "voice" ? this.timeouts.contextVoice : this.
|
|
8678
|
+
const timeout = options.timeout ?? (request.view === "voice" ? this.timeouts.contextVoice : this.readBudget("context"));
|
|
8516
8679
|
const { query: own2, ...pinned2 } = request;
|
|
8517
8680
|
const query = turnText(own2 ?? params.turn);
|
|
8518
8681
|
const voiceCache = this.voiceCache(core, pinned2, options.cache);
|
|
@@ -8528,7 +8691,7 @@ var Niadra = class _Niadra {
|
|
|
8528
8691
|
*/
|
|
8529
8692
|
async profile() {
|
|
8530
8693
|
if (!this.core) return null;
|
|
8531
|
-
return this.profileCache.refresh(() => this.api.sdkProfile({ timeout: this.
|
|
8694
|
+
return this.profileCache.refresh(() => this.api.sdkProfile({ timeout: this.readBudget("navigation") }));
|
|
8532
8695
|
}
|
|
8533
8696
|
/**
|
|
8534
8697
|
* Checks outputs against this claim contract instead of the one the profile serves (a company's own copy,
|
|
@@ -8553,7 +8716,7 @@ var Niadra = class _Niadra {
|
|
|
8553
8716
|
*/
|
|
8554
8717
|
async mayContact(handle, purpose, options = {}) {
|
|
8555
8718
|
if (this.core && this.suppressions.due()) {
|
|
8556
|
-
const read2 = this.readSuppressions(this.suppressions.held ? this.timeouts.write : this.
|
|
8719
|
+
const read2 = this.readSuppressions(this.suppressions.held ? this.timeouts.write : this.readBudget("navigation"));
|
|
8557
8720
|
if (!this.suppressions.held) await read2;
|
|
8558
8721
|
}
|
|
8559
8722
|
const checkOptions = { channel: options.channel ?? null };
|
|
@@ -8563,8 +8726,8 @@ var Niadra = class _Niadra {
|
|
|
8563
8726
|
readSuppressions(budgetMs) {
|
|
8564
8727
|
return this.suppressions.read(
|
|
8565
8728
|
{
|
|
8566
|
-
salt: () => this.api.suppressionSalt({ timeout: this.
|
|
8567
|
-
page: (cursor, limit2) => this.api.suppressions({ cursor, limit: limit2 }, { timeout: this.
|
|
8729
|
+
salt: () => this.api.suppressionSalt({ timeout: this.readBudget("navigation") }),
|
|
8730
|
+
page: (cursor, limit2) => this.api.suppressions({ cursor, limit: limit2 }, { timeout: this.readBudget("navigation") })
|
|
8568
8731
|
},
|
|
8569
8732
|
budgetMs
|
|
8570
8733
|
);
|
|
@@ -8643,6 +8806,7 @@ var Niadra = class _Niadra {
|
|
|
8643
8806
|
}
|
|
8644
8807
|
/** What a conversation or a task needs of its client for the agent features. */
|
|
8645
8808
|
get agentHost() {
|
|
8809
|
+
const client = this;
|
|
8646
8810
|
return {
|
|
8647
8811
|
recorder: this.turns,
|
|
8648
8812
|
coordinator: this.coordinator,
|
|
@@ -8656,7 +8820,10 @@ var Niadra = class _Niadra {
|
|
|
8656
8820
|
claim: (request, timeoutMs) => this.api.claim(request, {}, { timeout: timeoutMs }),
|
|
8657
8821
|
verifyClaim: (ref, field, value, options) => this.verifyClaim(ref, field, value, options),
|
|
8658
8822
|
enabled: this.enabled,
|
|
8659
|
-
|
|
8823
|
+
strict: this.strict,
|
|
8824
|
+
get navigationMs() {
|
|
8825
|
+
return client.readBudget("navigation");
|
|
8826
|
+
}
|
|
8660
8827
|
};
|
|
8661
8828
|
}
|
|
8662
8829
|
/**
|
|
@@ -8684,7 +8851,7 @@ var Niadra = class _Niadra {
|
|
|
8684
8851
|
if (!cache) return false;
|
|
8685
8852
|
const key2 = cacheKey(request);
|
|
8686
8853
|
const line = this.voice.line(cacheScope(request) ?? "");
|
|
8687
|
-
this.
|
|
8854
|
+
this.startVoice(core);
|
|
8688
8855
|
line.request = request;
|
|
8689
8856
|
if (!cache.has(key2) && line.inFlight().length === 0) {
|
|
8690
8857
|
this.voiceRead(core, cache, line, key2, request, null, this.timeouts.contextVoiceStart);
|
|
@@ -8821,13 +8988,13 @@ var Niadra = class _Niadra {
|
|
|
8821
8988
|
if (!params.query || params.query.length > 2e3) {
|
|
8822
8989
|
throw new NiadraValidationError("query must be 1 to 2000 characters");
|
|
8823
8990
|
}
|
|
8824
|
-
return this.readSpec("POST", "/v1/history/search", params, this.
|
|
8991
|
+
return this.readSpec("POST", "/v1/history/search", params, this.readBudget("navigation"), options);
|
|
8825
8992
|
});
|
|
8826
8993
|
}
|
|
8827
8994
|
/** The customer's history, newest first, one line per item, paginated by cursor. */
|
|
8828
8995
|
async timeline(params, options = {}) {
|
|
8829
8996
|
return this.navigate(
|
|
8830
|
-
() => this.readSpec("POST", "/v1/history/timeline", params, this.
|
|
8997
|
+
() => this.readSpec("POST", "/v1/history/timeline", params, this.readBudget("navigation"), options)
|
|
8831
8998
|
);
|
|
8832
8999
|
}
|
|
8833
9000
|
/**
|
|
@@ -8840,9 +9007,10 @@ var Niadra = class _Niadra {
|
|
|
8840
9007
|
if (!id) throw new NiadraValidationError("open() needs an item id");
|
|
8841
9008
|
const body = { item_id: id };
|
|
8842
9009
|
if (params.subject) body.subject = params.subject;
|
|
9010
|
+
if (params.about) body.about = params.about;
|
|
8843
9011
|
if (params.verification) body.verification = params.verification;
|
|
8844
9012
|
if (params.conversation_id) body.conversation_id = params.conversation_id;
|
|
8845
|
-
return this.readSpec("POST", "/v1/history/open", body, this.
|
|
9013
|
+
return this.readSpec("POST", "/v1/history/open", body, this.readBudget("navigation"), options);
|
|
8846
9014
|
});
|
|
8847
9015
|
}
|
|
8848
9016
|
/**
|
|
@@ -8854,7 +9022,7 @@ var Niadra = class _Niadra {
|
|
|
8854
9022
|
* const { data: invoice } = await niadra.objectState("invoice:erp:0823");
|
|
8855
9023
|
*/
|
|
8856
9024
|
async objectState(object, options = {}) {
|
|
8857
|
-
return this.navigate(() => this.readSpec("GET", objectPath(object), void 0, this.
|
|
9025
|
+
return this.navigate(() => this.readSpec("GET", objectPath(object), void 0, this.readBudget("navigation"), options));
|
|
8858
9026
|
}
|
|
8859
9027
|
/**
|
|
8860
9028
|
* System events and agent actions about one object, newest first, one line each and never
|
|
@@ -8867,7 +9035,7 @@ var Niadra = class _Niadra {
|
|
|
8867
9035
|
throw new NiadraValidationError("limit must be between 1 and 100");
|
|
8868
9036
|
}
|
|
8869
9037
|
const path = `${objectPath(object)}/timeline`;
|
|
8870
|
-
const spec = this.readSpec("GET", path, void 0, this.
|
|
9038
|
+
const spec = this.readSpec("GET", path, void 0, this.readBudget("navigation"), options);
|
|
8871
9039
|
spec.query = { cursor: params.cursor, limit: String(limit2) };
|
|
8872
9040
|
return spec;
|
|
8873
9041
|
});
|
|
@@ -8886,6 +9054,7 @@ var Niadra = class _Niadra {
|
|
|
8886
9054
|
timeline: (params, voice) => this.timeline(params, this.voiceBudget(voice)),
|
|
8887
9055
|
open: (id, customer, bound, voice) => {
|
|
8888
9056
|
const scope = { subject: customer };
|
|
9057
|
+
if (bound.about) scope.about = bound.about;
|
|
8889
9058
|
if (bound.verification) scope.verification = bound.verification;
|
|
8890
9059
|
if (bound.conversation_id) scope.conversation_id = bound.conversation_id;
|
|
8891
9060
|
return this.open(id, scope, this.voiceBudget(voice));
|
|
@@ -8920,7 +9089,7 @@ var Niadra = class _Niadra {
|
|
|
8920
9089
|
const key2 = AgentMemoryCache.key(params);
|
|
8921
9090
|
const fresh = cache?.fresh(key2);
|
|
8922
9091
|
if (fresh) return blockResult(fresh, "cache");
|
|
8923
|
-
const timeout = options.timeout ?? (params.view === "voice" ? this.timeouts.contextVoice : this.
|
|
9092
|
+
const timeout = options.timeout ?? (params.view === "voice" ? this.timeouts.contextVoice : this.readBudget("context"));
|
|
8924
9093
|
const spec = this.readSpec("GET", "/v1/agent-memory/block", void 0, timeout, options);
|
|
8925
9094
|
spec.query = {
|
|
8926
9095
|
max_tokens: String(params.max_tokens ?? 300),
|
|
@@ -8957,7 +9126,7 @@ var Niadra = class _Niadra {
|
|
|
8957
9126
|
if (params.limit !== void 0) body.limit = params.limit;
|
|
8958
9127
|
if (params.conversation_id) body.conversation_id = params.conversation_id;
|
|
8959
9128
|
if (params.task_id) body.task_id = params.task_id;
|
|
8960
|
-
return this.readSpec("POST", "/v1/agent-memory/search", body, this.
|
|
9129
|
+
return this.readSpec("POST", "/v1/agent-memory/search", body, this.readBudget("navigation"), options);
|
|
8961
9130
|
});
|
|
8962
9131
|
return result2.error ? result2 : { data: result2.data.notes, error: null };
|
|
8963
9132
|
}
|
|
@@ -9051,6 +9220,41 @@ var Niadra = class _Niadra {
|
|
|
9051
9220
|
return { ok: false, idempotency_key: key2, error: this.swallow(error, "feedback") };
|
|
9052
9221
|
}
|
|
9053
9222
|
}
|
|
9223
|
+
/**
|
|
9224
|
+
* How the space's agents used the context they read (`GET /v1/context-use`): sessions, deliveries, use,
|
|
9225
|
+
* repetition, transfers and recontact, with intervals, grouped by `group_by`. A key of an `analyst` source
|
|
9226
|
+
* with the `analytics` scope reads every source of the space; a key with `admin` reads its own source.
|
|
9227
|
+
*/
|
|
9228
|
+
contextUse(params = {}, options = {}) {
|
|
9229
|
+
return this.navigate(() => {
|
|
9230
|
+
const { group_by: groups, ...filters } = params;
|
|
9231
|
+
const query = { ...filters };
|
|
9232
|
+
if (groups?.length) query.group_by = groups;
|
|
9233
|
+
return { ...this.readSpec("GET", "/v1/context-use", void 0, this.timeouts.write, options), query };
|
|
9234
|
+
});
|
|
9235
|
+
}
|
|
9236
|
+
/**
|
|
9237
|
+
* Links a person to the organization they act for (an account or a partner), as a system of record that
|
|
9238
|
+
* knows who works for whom: a CRM, an HR system. Needs a key with the `identity:link` scope (or `admin`);
|
|
9239
|
+
* `can_see_contacts` needs `admin`. Reads with `about` reach the organization through the link.
|
|
9240
|
+
*/
|
|
9241
|
+
link(params, options = {}) {
|
|
9242
|
+
const { idempotency_key: key2, ...rest } = params;
|
|
9243
|
+
const body = { can_see_contacts: false, method: "system_import", ...rest };
|
|
9244
|
+
return this.navigate(() => this.writeSpec("/v1/identity/links", body, key2 ?? uuidv7(), options));
|
|
9245
|
+
}
|
|
9246
|
+
/**
|
|
9247
|
+
* Ends a link, from `valid_to` (now when absent): the person no longer acts for the organization, and reads
|
|
9248
|
+
* with `about` for the pair go on with the person's own memory. Needs `identity:link` or `admin`.
|
|
9249
|
+
*/
|
|
9250
|
+
endLink(linkId, params = {}, options = {}) {
|
|
9251
|
+
return this.navigate(() => {
|
|
9252
|
+
if (!linkId) throw new NiadraValidationError("endLink() needs a link id");
|
|
9253
|
+
const body = params.valid_to ? { valid_to: params.valid_to } : {};
|
|
9254
|
+
const path = `/v1/identity/links/${encodeURIComponent(linkId)}/end`;
|
|
9255
|
+
return this.writeSpec(path, body, params.idempotency_key ?? uuidv7(), options);
|
|
9256
|
+
});
|
|
9257
|
+
}
|
|
9054
9258
|
/**
|
|
9055
9259
|
* Up to 500 corrections in one call, each with its own idempotency key (minted when missing).
|
|
9056
9260
|
* Resolves with `accepted`, `duplicates` for replayed keys and one error per refused item, by index.
|
|
@@ -9143,16 +9347,20 @@ var Niadra = class _Niadra {
|
|
|
9143
9347
|
* emits `conversation.ended` when you call `end()`.
|
|
9144
9348
|
*/
|
|
9145
9349
|
conversation(params) {
|
|
9146
|
-
|
|
9350
|
+
const conversation = new Conversation(this, params, {
|
|
9147
9351
|
endConversation: (id) => this.endScope(buildConversationEnded(id), `conversation:${id}`)
|
|
9148
9352
|
});
|
|
9353
|
+
this.warm(`conversation:${conversation.id}`, conversation);
|
|
9354
|
+
return conversation;
|
|
9149
9355
|
}
|
|
9150
9356
|
/** A helper for one internal-agent task: binds `task_id` to reads and writes and emits `task.ended`. */
|
|
9151
9357
|
task(params) {
|
|
9152
|
-
|
|
9358
|
+
const task = new Task(this, params, {
|
|
9153
9359
|
endTask: (id) => this.endScope(buildTaskEnded(id), `task:${id}`),
|
|
9154
9360
|
verifyTask: (verify) => this.verifyWith(verify)
|
|
9155
9361
|
});
|
|
9362
|
+
this.warm(`task:${task.id}`, task);
|
|
9363
|
+
return task;
|
|
9156
9364
|
}
|
|
9157
9365
|
/**
|
|
9158
9366
|
* Sends every queued event and resolves when done. Call it before a serverless function
|
|
@@ -9172,6 +9380,8 @@ var Niadra = class _Niadra {
|
|
|
9172
9380
|
*/
|
|
9173
9381
|
async shutdown() {
|
|
9174
9382
|
this.unregisterExit();
|
|
9383
|
+
clearInterval(this.warmTimer);
|
|
9384
|
+
this.warmTimer = void 0;
|
|
9175
9385
|
if (!this.core) return;
|
|
9176
9386
|
await this.turnSender?.stop(this.timeouts.write);
|
|
9177
9387
|
await this.outbox.stop(this.timeouts.write);
|
|
@@ -9275,9 +9485,9 @@ var Niadra = class _Niadra {
|
|
|
9275
9485
|
const response = await core.transport.request(this.readSpec("POST", "/v1/context", request, timeout, { signal, headers }));
|
|
9276
9486
|
return normalizeContext(response.data);
|
|
9277
9487
|
} catch (error) {
|
|
9278
|
-
const
|
|
9488
|
+
const refused3 = error instanceof NiadraAPIError && error.status === 404;
|
|
9279
9489
|
const left = timeout - (Date.now() - started);
|
|
9280
|
-
if (!request.include?.length || !
|
|
9490
|
+
if (!request.include?.length || !refused3 || left <= 0) throw error;
|
|
9281
9491
|
for (const name of request.include) this.refusedBlocks.set(name, Date.now() + BLOCK_RECHECK_AFTER_MS);
|
|
9282
9492
|
const { include: _dropped, ...plain2 } = request;
|
|
9283
9493
|
const response = await core.transport.request(this.readSpec("POST", "/v1/context", plain2, left, { signal, headers }));
|
|
@@ -9365,9 +9575,32 @@ var Niadra = class _Niadra {
|
|
|
9365
9575
|
const rtt = Math.min(...samples);
|
|
9366
9576
|
this.voice.rtt = rtt;
|
|
9367
9577
|
this.logger.debug(`round trip to the region ${Math.round(rtt)} ms`);
|
|
9368
|
-
|
|
9578
|
+
const explicit = ["context", "navigation"].filter((name) => !this.defaultReads.has(name));
|
|
9579
|
+
for (const warning of budgetWarnings(rtt, this.timeouts, explicit)) this.logger.warn(warning);
|
|
9580
|
+
if (this.voiceStarted) this.warnVoice();
|
|
9369
9581
|
})();
|
|
9370
9582
|
}
|
|
9583
|
+
/**
|
|
9584
|
+
* A read budget: one the caller left at its default is what the API may take, and the measured round trip
|
|
9585
|
+
* to the region goes on top, so an agent far from the region (Sao Paulo, 170 ms from us-east-2) is not
|
|
9586
|
+
* timed out by the network; one the caller set is a ceiling.
|
|
9587
|
+
*/
|
|
9588
|
+
readBudget(name) {
|
|
9589
|
+
const rtt = this.voice.rtt;
|
|
9590
|
+
return rtt !== null && this.defaultReads.has(name) ? this.timeouts[name] + rtt : this.timeouts[name];
|
|
9591
|
+
}
|
|
9592
|
+
/** The client reads in voice: the voice budgets' warnings matter from now on. */
|
|
9593
|
+
startVoice(core) {
|
|
9594
|
+
this.voiceStarted = true;
|
|
9595
|
+
this.probe(core);
|
|
9596
|
+
this.warnVoice();
|
|
9597
|
+
}
|
|
9598
|
+
warnVoice() {
|
|
9599
|
+
const rtt = this.voice.rtt;
|
|
9600
|
+
if (this.voiceWarned || rtt === null) return;
|
|
9601
|
+
this.voiceWarned = true;
|
|
9602
|
+
for (const warning of rttWarnings(rtt, this.timeouts)) this.logger.warn(warning);
|
|
9603
|
+
}
|
|
9371
9604
|
/**
|
|
9372
9605
|
* A voice turn: the pinned body from memory, and the slots of the read of its words when that
|
|
9373
9606
|
* read lands within `timeout`. See `voice.ts`.
|
|
@@ -9377,7 +9610,7 @@ var Niadra = class _Niadra {
|
|
|
9377
9610
|
const key2 = cacheKey(request);
|
|
9378
9611
|
const deadline = Date.now() + timeout;
|
|
9379
9612
|
const line = this.voice.line(scope);
|
|
9380
|
-
this.
|
|
9613
|
+
this.startVoice(core);
|
|
9381
9614
|
line.request = request;
|
|
9382
9615
|
const words2 = wordsOf(query);
|
|
9383
9616
|
const background = this.timeouts.prefetch;
|
|
@@ -9563,8 +9796,39 @@ var Niadra = class _Niadra {
|
|
|
9563
9796
|
core.queue.flushInBackground(true);
|
|
9564
9797
|
});
|
|
9565
9798
|
}
|
|
9799
|
+
/** Notes an open conversation or task; the first one starts the keep-warm timer (`warm.ts`). */
|
|
9800
|
+
warm(scope, session) {
|
|
9801
|
+
const core = this.core;
|
|
9802
|
+
if (!core || !this.keepWarm.enabled) return;
|
|
9803
|
+
this.keepWarm.add(scope, session, Date.now());
|
|
9804
|
+
if (this.warmTimer !== void 0) return;
|
|
9805
|
+
const timer = setInterval(() => {
|
|
9806
|
+
this.warmTick(core);
|
|
9807
|
+
}, EVERY_MS);
|
|
9808
|
+
timer.unref?.();
|
|
9809
|
+
this.warmTimer = timer;
|
|
9810
|
+
}
|
|
9811
|
+
warmTick(core) {
|
|
9812
|
+
const step = this.keepWarm.step(Date.now(), core.transport.lastActivityAt);
|
|
9813
|
+
if (step === "stop") {
|
|
9814
|
+
clearInterval(this.warmTimer);
|
|
9815
|
+
this.warmTimer = void 0;
|
|
9816
|
+
return;
|
|
9817
|
+
}
|
|
9818
|
+
if (step !== "ping") return;
|
|
9819
|
+
core.transport.request({
|
|
9820
|
+
method: "GET",
|
|
9821
|
+
path: "/healthz",
|
|
9822
|
+
timeoutMs: 2e3,
|
|
9823
|
+
retry: { kind: "read", maxAttempts: 1 },
|
|
9824
|
+
activity: false
|
|
9825
|
+
}).catch((error) => {
|
|
9826
|
+
this.logger.debug(`keep-warm ping failed: ${describe(toNiadraError(error))}`);
|
|
9827
|
+
});
|
|
9828
|
+
}
|
|
9566
9829
|
async endScope(item, scope) {
|
|
9567
9830
|
this.forgetScope(scope);
|
|
9831
|
+
this.keepWarm.end(scope);
|
|
9568
9832
|
return this.sendNow(() => item);
|
|
9569
9833
|
}
|
|
9570
9834
|
async sendBatch(transport, items, maxAttempts, backoff) {
|
|
@@ -9869,7 +10133,7 @@ var Replayer = class {
|
|
|
9869
10133
|
try {
|
|
9870
10134
|
answer = await this.niadra.callRoute({ method: "POST", path: "/v1/scenario-runs", body, idempotencyKey: uuidv7() });
|
|
9871
10135
|
} catch (error) {
|
|
9872
|
-
if (error instanceof NiadraAPIError && error.code === "pin_mismatch") return
|
|
10136
|
+
if (error instanceof NiadraAPIError && error.code === "pin_mismatch") return refused2(scenarioIds, results);
|
|
9873
10137
|
throw error;
|
|
9874
10138
|
}
|
|
9875
10139
|
let run = runOf(answer);
|
|
@@ -9972,7 +10236,7 @@ var Replayer = class {
|
|
|
9972
10236
|
return result(scenarioId, turnId, caseId, run, "completed", { paraphrase: rephrased, assertions, divergent_calls: played.divergent, latency_ms: latency });
|
|
9973
10237
|
}
|
|
9974
10238
|
};
|
|
9975
|
-
function
|
|
10239
|
+
function refused2(scenarioIds, results) {
|
|
9976
10240
|
const scenarios = scenarioIds.map((id) => {
|
|
9977
10241
|
const mine = results.filter((r) => r.scenario_id === id);
|
|
9978
10242
|
return {
|