@intentface/latch-core 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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/dist/agent.d.ts +200 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +9 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/compaction.d.ts +33 -0
  8. package/dist/compaction.d.ts.map +1 -0
  9. package/dist/compaction.js +104 -0
  10. package/dist/compaction.js.map +1 -0
  11. package/dist/connections.d.ts +16 -0
  12. package/dist/connections.d.ts.map +1 -0
  13. package/dist/connections.js +41 -0
  14. package/dist/connections.js.map +1 -0
  15. package/dist/context.d.ts +41 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/context.js +25 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/current-date.d.ts +12 -0
  20. package/dist/current-date.d.ts.map +1 -0
  21. package/dist/current-date.js +25 -0
  22. package/dist/current-date.js.map +1 -0
  23. package/dist/extensions.d.ts +333 -0
  24. package/dist/extensions.d.ts.map +1 -0
  25. package/dist/extensions.js +569 -0
  26. package/dist/extensions.js.map +1 -0
  27. package/dist/harness/index.d.ts +17 -0
  28. package/dist/harness/index.d.ts.map +1 -0
  29. package/dist/harness/index.js +15 -0
  30. package/dist/harness/index.js.map +1 -0
  31. package/dist/harness/tools.d.ts +88 -0
  32. package/dist/harness/tools.d.ts.map +1 -0
  33. package/dist/harness/tools.js +296 -0
  34. package/dist/harness/tools.js.map +1 -0
  35. package/dist/harness/web-fetch.d.ts +47 -0
  36. package/dist/harness/web-fetch.d.ts.map +1 -0
  37. package/dist/harness/web-fetch.js +247 -0
  38. package/dist/harness/web-fetch.js.map +1 -0
  39. package/dist/index.d.ts +25 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +25 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/limits.d.ts +152 -0
  44. package/dist/limits.d.ts.map +1 -0
  45. package/dist/limits.js +97 -0
  46. package/dist/limits.js.map +1 -0
  47. package/dist/memory.d.ts +93 -0
  48. package/dist/memory.d.ts.map +1 -0
  49. package/dist/memory.js +13 -0
  50. package/dist/memory.js.map +1 -0
  51. package/dist/message.d.ts +46 -0
  52. package/dist/message.d.ts.map +1 -0
  53. package/dist/message.js +2 -0
  54. package/dist/message.js.map +1 -0
  55. package/dist/models/catalog.d.ts +56 -0
  56. package/dist/models/catalog.d.ts.map +1 -0
  57. package/dist/models/catalog.js +211 -0
  58. package/dist/models/catalog.js.map +1 -0
  59. package/dist/models/defaults.d.ts +23 -0
  60. package/dist/models/defaults.d.ts.map +1 -0
  61. package/dist/models/defaults.js +19 -0
  62. package/dist/models/defaults.js.map +1 -0
  63. package/dist/models/index.d.ts +23 -0
  64. package/dist/models/index.d.ts.map +1 -0
  65. package/dist/models/index.js +19 -0
  66. package/dist/models/index.js.map +1 -0
  67. package/dist/models/prompt-caching.d.ts +19 -0
  68. package/dist/models/prompt-caching.d.ts.map +1 -0
  69. package/dist/models/prompt-caching.js +18 -0
  70. package/dist/models/prompt-caching.js.map +1 -0
  71. package/dist/models/provider.d.ts +19 -0
  72. package/dist/models/provider.d.ts.map +1 -0
  73. package/dist/models/provider.js +22 -0
  74. package/dist/models/provider.js.map +1 -0
  75. package/dist/models/reasoning.d.ts +21 -0
  76. package/dist/models/reasoning.d.ts.map +1 -0
  77. package/dist/models/reasoning.js +59 -0
  78. package/dist/models/reasoning.js.map +1 -0
  79. package/dist/pricing.d.ts +52 -0
  80. package/dist/pricing.d.ts.map +1 -0
  81. package/dist/pricing.js +37 -0
  82. package/dist/pricing.js.map +1 -0
  83. package/dist/principal.d.ts +37 -0
  84. package/dist/principal.d.ts.map +1 -0
  85. package/dist/principal.js +30 -0
  86. package/dist/principal.js.map +1 -0
  87. package/dist/projections.d.ts +37 -0
  88. package/dist/projections.d.ts.map +1 -0
  89. package/dist/projections.js +128 -0
  90. package/dist/projections.js.map +1 -0
  91. package/dist/prompt-caching.d.ts +106 -0
  92. package/dist/prompt-caching.d.ts.map +1 -0
  93. package/dist/prompt-caching.js +165 -0
  94. package/dist/prompt-caching.js.map +1 -0
  95. package/dist/runtime.d.ts +670 -0
  96. package/dist/runtime.d.ts.map +1 -0
  97. package/dist/runtime.js +2425 -0
  98. package/dist/runtime.js.map +1 -0
  99. package/dist/scheduler.d.ts +31 -0
  100. package/dist/scheduler.d.ts.map +1 -0
  101. package/dist/scheduler.js +43 -0
  102. package/dist/scheduler.js.map +1 -0
  103. package/dist/storage.d.ts +389 -0
  104. package/dist/storage.d.ts.map +1 -0
  105. package/dist/storage.js +38 -0
  106. package/dist/storage.js.map +1 -0
  107. package/dist/telemetry.d.ts +155 -0
  108. package/dist/telemetry.d.ts.map +1 -0
  109. package/dist/telemetry.js +2 -0
  110. package/dist/telemetry.js.map +1 -0
  111. package/dist/vault-node.d.ts +20 -0
  112. package/dist/vault-node.d.ts.map +1 -0
  113. package/dist/vault-node.js +30 -0
  114. package/dist/vault-node.js.map +1 -0
  115. package/dist/vault.d.ts +62 -0
  116. package/dist/vault.d.ts.map +1 -0
  117. package/dist/vault.js +88 -0
  118. package/dist/vault.js.map +1 -0
  119. package/package.json +95 -0
@@ -0,0 +1,247 @@
1
+ import TurndownService from "turndown";
2
+ import { isBlockedHost, isBlockedResolved } from "@intentface/latch-net";
3
+ import { pinnedFetch } from "@intentface/latch-net/pinned";
4
+ /**
5
+ * Robust URL fetching for the `web_fetch` tool — mirrors eve's implementation
6
+ * so pages come back as clean markdown/text instead of raw HTML, with a
7
+ * browser User-Agent, content negotiation, a timeout, a Cloudflare-challenge
8
+ * retry, a size cap, and head-truncation. Runs in the app process (not the
9
+ * sandbox); private/loopback hosts are blocked (SSRF).
10
+ */
11
+ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5 MB
12
+ const DEFAULT_TIMEOUT_MS = 30_000;
13
+ const MAX_TIMEOUT_MS = 120_000;
14
+ const MAX_OUTPUT_LINES = 2000;
15
+ const MAX_OUTPUT_BYTES = 50 * 1024;
16
+ const MAX_REDIRECTS = 4;
17
+ // Browser-like UA so servers return full content, not bot-degraded pages.
18
+ const BROWSER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
19
+ const HONEST_UA = "intentface-latch-agent";
20
+ /**
21
+ * Anthropic caps images at 5 MB of source data; staying under it here beats a
22
+ * provider error after the fetch already succeeded.
23
+ */
24
+ const MAX_MEDIA_SIZE = 4.5 * 1024 * 1024;
25
+ /**
26
+ * What media the body is, if it's a type worth showing the model. The
27
+ * Content-Type header decides when it's one of ours; magic bytes rescue the
28
+ * common case of a server that lies (application/octet-stream, text/plain) —
29
+ * sniffing is deliberately limited to unambiguous signatures.
30
+ */
31
+ export function detectMedia(contentType, bytes) {
32
+ const declared = contentType.split(";")[0].trim().toLowerCase();
33
+ if (declared === "image/png" ||
34
+ declared === "image/jpeg" ||
35
+ declared === "image/gif" ||
36
+ declared === "image/webp" ||
37
+ declared === "application/pdf") {
38
+ return declared;
39
+ }
40
+ if (declared === "image/jpg")
41
+ return "image/jpeg";
42
+ // Magic bytes, only when the declared type doesn't commit to something else
43
+ // textual (an HTML page that happens to start with %PDF is not a PDF).
44
+ if (declared.startsWith("text/") || declared.includes("html"))
45
+ return null;
46
+ const ascii = (start, s) => bytes.length >= start + s.length &&
47
+ s.split("").every((ch, i) => bytes[start + i] === ch.charCodeAt(0));
48
+ if (ascii(0, "%PDF-"))
49
+ return "application/pdf";
50
+ if (bytes.length >= 8 && bytes[0] === 0x89 && ascii(1, "PNG"))
51
+ return "image/png";
52
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
53
+ return "image/jpeg";
54
+ if (ascii(0, "GIF87a") || ascii(0, "GIF89a"))
55
+ return "image/gif";
56
+ if (ascii(0, "RIFF") && ascii(8, "WEBP"))
57
+ return "image/webp";
58
+ return null;
59
+ }
60
+ /**
61
+ * Full SSRF gate for one hop: protocol, the string-level blocklist, and the DNS
62
+ * backstop (a public NAME that resolves to a private address — the string check
63
+ * alone cannot see that). Applied to the initial URL AND every redirect target.
64
+ */
65
+ async function blockedReason(t) {
66
+ if (t.protocol !== "http:" && t.protocol !== "https:") {
67
+ return "Only http(s) URLs are allowed.";
68
+ }
69
+ if (isBlockedHost(t.hostname) || (await isBlockedResolved(t.hostname))) {
70
+ return "Host is not allowed.";
71
+ }
72
+ return null;
73
+ }
74
+ function buildHeaders(format) {
75
+ const accept = format === "markdown"
76
+ ? "text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1"
77
+ : format === "text"
78
+ ? "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1"
79
+ : "text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, */*;q=0.1";
80
+ return { Accept: accept, "Accept-Language": "en-US,en;q=0.9", "User-Agent": BROWSER_USER_AGENT };
81
+ }
82
+ /** HTML → Markdown (Turndown), dropping script/style/meta/link noise. */
83
+ export function convertHtmlToMarkdown(html) {
84
+ const service = new TurndownService({
85
+ bulletListMarker: "-",
86
+ codeBlockStyle: "fenced",
87
+ emDelimiter: "*",
88
+ headingStyle: "atx",
89
+ hr: "---",
90
+ });
91
+ service.remove(["script", "style", "meta", "link"]);
92
+ return service.turndown(html);
93
+ }
94
+ const ENTITY_MAP = {
95
+ "&": "&",
96
+ ">": ">",
97
+ "&lt;": "<",
98
+ "&nbsp;": " ",
99
+ "&quot;": '"',
100
+ "&#39;": "'",
101
+ "&#x27;": "'",
102
+ "&#x2f;": "/",
103
+ };
104
+ /** Strip HTML to readable text (no Turndown) — for `format: "text"`. */
105
+ export function extractTextFromHtml(html) {
106
+ let text = html.replace(/<(script|style|noscript)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
107
+ text = text.replace(/<\/(p|div|br|h[1-6]|li|tr|blockquote|pre|section|article|header|footer|nav|aside|main|figure|figcaption|details|summary)>/gi, "\n");
108
+ text = text.replace(/<br\s*\/?>/gi, "\n");
109
+ text = text.replace(/<[^>]+>/g, "");
110
+ text = text.replace(/&[a-z#0-9]+;/gi, (m) => ENTITY_MAP[m.toLowerCase()] ?? m);
111
+ return text
112
+ .split("\n")
113
+ .map((line) => line.replace(/[ \t]+/g, " ").trim())
114
+ .join("\n")
115
+ .replace(/\n{3,}/g, "\n\n")
116
+ .trim();
117
+ }
118
+ /** Keep the HEAD of long content (the start = the article, not the footer). */
119
+ function headTruncate(text) {
120
+ let out = text;
121
+ let truncated = false;
122
+ const lines = out.split("\n");
123
+ if (lines.length > MAX_OUTPUT_LINES) {
124
+ out = lines.slice(0, MAX_OUTPUT_LINES).join("\n");
125
+ truncated = true;
126
+ }
127
+ if (Buffer.byteLength(out) > MAX_OUTPUT_BYTES) {
128
+ out = out.slice(0, MAX_OUTPUT_BYTES);
129
+ truncated = true;
130
+ }
131
+ return { text: out, truncated };
132
+ }
133
+ export async function webFetch(args) {
134
+ const format = args.format ?? "markdown";
135
+ let u;
136
+ try {
137
+ u = new URL(args.url);
138
+ }
139
+ catch {
140
+ return { ok: false, error: "Invalid URL." };
141
+ }
142
+ // Non-positive / NaN timeouts fall back to the default rather than reaching
143
+ // AbortSignal.timeout(), which throws a RangeError outside our error result.
144
+ const requested = args.timeout != null && Number.isFinite(args.timeout) && args.timeout > 0 ? args.timeout * 1000 : DEFAULT_TIMEOUT_MS;
145
+ const timeoutMs = Math.min(requested, MAX_TIMEOUT_MS);
146
+ const signal = AbortSignal.timeout(timeoutMs);
147
+ const headers = buildHeaders(format);
148
+ /**
149
+ * Follow redirects MANUALLY so each hop is re-gated: with `redirect: "follow"`
150
+ * a public URL that 302s to an internal address (e.g. cloud metadata) would be
151
+ * fetched transparently, and only the first host would ever be checked.
152
+ */
153
+ const fetchGuarded = async (start, reqHeaders) => {
154
+ let current = start;
155
+ for (let hop = 0;; hop++) {
156
+ const bad = await blockedReason(current);
157
+ if (bad)
158
+ return { error: bad };
159
+ // Pinned fetch: the SSRF vetting also runs inside the connection's own
160
+ // DNS lookup, so `blockedReason` above is the well-messaged pre-check,
161
+ // not the last line of defense against rebinding.
162
+ const res = await pinnedFetch(current, { headers: reqHeaders, signal, redirect: "manual" });
163
+ const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
164
+ if (!location)
165
+ return res;
166
+ if (hop >= MAX_REDIRECTS)
167
+ return { error: "Too many redirects." };
168
+ try {
169
+ current = new URL(location, current);
170
+ }
171
+ catch {
172
+ return { error: "Invalid redirect location." };
173
+ }
174
+ }
175
+ };
176
+ let response;
177
+ try {
178
+ const initial = await fetchGuarded(u, headers);
179
+ if ("error" in initial)
180
+ return { ok: false, error: initial.error };
181
+ // Cloudflare may block browser UAs from non-browser TLS; retry honestly.
182
+ if (initial.status === 403 && initial.headers.get("cf-mitigated") === "challenge") {
183
+ const retry = await fetchGuarded(u, { ...headers, "User-Agent": HONEST_UA });
184
+ if ("error" in retry)
185
+ return { ok: false, error: retry.error };
186
+ response = retry;
187
+ }
188
+ else {
189
+ response = initial;
190
+ }
191
+ }
192
+ catch (e) {
193
+ const msg = e instanceof Error
194
+ ? e.name === "TimeoutError" || e.name === "AbortError"
195
+ ? `Request timed out after ${timeoutMs / 1000}s`
196
+ : // undici wraps connect-time failures (incl. the pinned fetch's
197
+ // SSRF block) as `TypeError: fetch failed` with the reason in
198
+ // `cause` — surface it or the block looks like a network blip.
199
+ e.cause instanceof Error && e.cause.message
200
+ ? `${e.message}: ${e.cause.message}`
201
+ : e.message
202
+ : "Fetch failed";
203
+ return { ok: false, error: msg };
204
+ }
205
+ if (!response.ok)
206
+ return { ok: false, error: `Request failed with status ${response.status}` };
207
+ const declared = response.headers.get("content-length");
208
+ if (declared && Number.parseInt(declared, 10) > MAX_RESPONSE_SIZE) {
209
+ return { ok: false, error: "Response too large (exceeds 5 MB)." };
210
+ }
211
+ const buffer = await response.arrayBuffer();
212
+ if (buffer.byteLength > MAX_RESPONSE_SIZE) {
213
+ return { ok: false, error: "Response too large (exceeds 5 MB)." };
214
+ }
215
+ const contentType = response.headers.get("content-type") ?? "";
216
+ // Images and PDFs come back as bytes so the model can SEE them (the tool's
217
+ // toModelOutput turns them into provider image/document blocks). Text-decoding
218
+ // them — the previous behavior — produced mojibake the model couldn't use.
219
+ const mediaType = detectMedia(contentType, new Uint8Array(buffer));
220
+ if (mediaType) {
221
+ if (buffer.byteLength > MAX_MEDIA_SIZE) {
222
+ return {
223
+ ok: false,
224
+ error: `Media too large for model input (${(buffer.byteLength / (1024 * 1024)).toFixed(1)} MB, max ${MAX_MEDIA_SIZE / (1024 * 1024)} MB).`,
225
+ };
226
+ }
227
+ return {
228
+ ok: true,
229
+ kind: "media",
230
+ status: response.status,
231
+ contentType,
232
+ mediaType,
233
+ data: Buffer.from(buffer).toString("base64"),
234
+ bytes: buffer.byteLength,
235
+ };
236
+ }
237
+ const isHtml = contentType.includes("text/html");
238
+ const body = new TextDecoder().decode(buffer);
239
+ const raw = format === "markdown" && isHtml
240
+ ? convertHtmlToMarkdown(body)
241
+ : format === "text" && isHtml
242
+ ? extractTextFromHtml(body)
243
+ : body;
244
+ const { text: content, truncated } = headTruncate(raw);
245
+ return { ok: true, kind: "text", status: response.status, contentType, content, truncated };
246
+ }
247
+ //# sourceMappingURL=web-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-fetch.js","sourceRoot":"","sources":["../../src/harness/web-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @intentface/latch-core
3
+ *
4
+ * Foundational types & seams. The Runtime (composition root + operations facade)
5
+ * and the executor land in later pieces; this is the vocabulary everything else
6
+ * is built on.
7
+ */
8
+ export * from "./principal.js";
9
+ export * from "./message.js";
10
+ export * from "./storage.js";
11
+ export * from "./context.js";
12
+ export * from "./current-date.js";
13
+ export * from "./agent.js";
14
+ export * from "./memory.js";
15
+ export * from "./runtime.js";
16
+ export * from "./connections.js";
17
+ export * from "./scheduler.js";
18
+ export * from "./vault.js";
19
+ export * from "./projections.js";
20
+ export * from "./compaction.js";
21
+ export * from "./pricing.js";
22
+ export * from "./prompt-caching.js";
23
+ export * from "./telemetry.js";
24
+ export * from "./limits.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @intentface/latch-core
3
+ *
4
+ * Foundational types & seams. The Runtime (composition root + operations facade)
5
+ * and the executor land in later pieces; this is the vocabulary everything else
6
+ * is built on.
7
+ */
8
+ export * from "./principal.js";
9
+ export * from "./message.js";
10
+ export * from "./storage.js";
11
+ export * from "./context.js";
12
+ export * from "./current-date.js";
13
+ export * from "./agent.js";
14
+ export * from "./memory.js";
15
+ export * from "./runtime.js";
16
+ export * from "./connections.js";
17
+ export * from "./scheduler.js";
18
+ export * from "./vault.js";
19
+ export * from "./projections.js";
20
+ export * from "./compaction.js";
21
+ export * from "./pricing.js";
22
+ export * from "./prompt-caching.js";
23
+ export * from "./telemetry.js";
24
+ export * from "./limits.js";
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,152 @@
1
+ import type { TurnTrigger } from "./telemetry.js";
2
+ /**
3
+ * Limits — per org / user / agent budgets on turns, tokens and cost.
4
+ *
5
+ * The split is the same as every other seam: the HOST decides which counters a
6
+ * turn counts against and what the caps are (`limitsFor` — derived from the
7
+ * principal, the agent and how the turn started), and where the counters live
8
+ * (`LimitStore`, a table the drizzle package implements). The RUNTIME counts
9
+ * and enforces:
10
+ *
11
+ * • admission — before a turn opens, the turn is RESERVED: one atomic
12
+ * increment of every policy's turn counter that returns the new count. The
13
+ * first policy whose count is now over its cap refuses the turn with
14
+ * {@link LimitExceededError} (the HTTP handler maps it to 429 +
15
+ * Retry-After) and the reservation is released. Increment-then-check means
16
+ * N concurrent requests against a one-turn cap get counts 1..N and exactly
17
+ * one is admitted — there is no read-then-write window to race through.
18
+ * Token and cost caps are checked against usage already settled; an
19
+ * in-flight turn's spend lands at settlement, so those caps can be
20
+ * overshot by at most the concurrent in-flight turns (bounded further by
21
+ * the mid-turn stop).
22
+ * • mid-turn — the tool loop stops once this turn's tokens reach the
23
+ * tightest remaining token budget (the model's last text is kept; no
24
+ * further steps run). Without this a single long agentic turn could blow
25
+ * through a whole day's budget after being admitted at 99%.
26
+ * • settlement — tokens and cost are added to every policy's counter when
27
+ * the turn finishes (resumes and subagent turns included).
28
+ *
29
+ * Windows are fixed, aligned to the epoch: `windowStart = floor(now / windowMs)
30
+ * * windowMs`. Fixed windows are what people expect from "per day" / "per
31
+ * hour", cost one row per (key, window) and need no sliding-log storage. A
32
+ * refusal says when the window turns over.
33
+ */
34
+ /** One counter a turn counts against, plus its caps in the window. */
35
+ export interface LimitPolicy {
36
+ /** Stable counter key — e.g. `org:o1`, `user:o1:u1`, `agent:o1:support`. Name the scope in it. */
37
+ key: string;
38
+ /** Fixed window length in ms (see `MINUTE_MS` … `DAY_MS`). */
39
+ windowMs: number;
40
+ /** Caps within one window. An omitted cap is unlimited. */
41
+ max: {
42
+ /** Turns admitted (a resume doesn't count; a subagent turn does). */
43
+ turns?: number;
44
+ /** Input + output tokens. */
45
+ tokens?: number;
46
+ /** Cost in the pricing seam's currency (0 when no pricing is configured). */
47
+ cost?: number;
48
+ };
49
+ /** How a refusal names this budget to the user, e.g. "your organization's daily token budget". */
50
+ label?: string;
51
+ }
52
+ /** What has been counted against one policy in the current window. */
53
+ export interface LimitUsage {
54
+ turns: number;
55
+ tokens: number;
56
+ cost: number;
57
+ }
58
+ export interface LimitWindowRef {
59
+ key: string;
60
+ windowStart: number;
61
+ windowMs: number;
62
+ }
63
+ /**
64
+ * Counter storage — one row per (key, window). Both methods must be atomic per
65
+ * row (the drizzle implementation upserts with `SET n = n + excluded.n`).
66
+ */
67
+ export interface LimitStore {
68
+ /** Current usage for each window ref; absent rows read as zero. */
69
+ read(refs: LimitWindowRef[]): Promise<Record<string, LimitUsage>>;
70
+ /**
71
+ * Add usage to each window (creating the row) and return each window's usage
72
+ * AFTER the add, keyed by `usageKey`. Atomic per row and the returned value
73
+ * must be the row's post-increment state (`INSERT … ON CONFLICT DO UPDATE …
74
+ * RETURNING`): admission reserves a turn with it. Partial usage: omitted
75
+ * fields add 0; negative values release a reservation.
76
+ */
77
+ add(entries: (LimitWindowRef & {
78
+ usage: Partial<LimitUsage>;
79
+ })[]): Promise<Record<string, LimitUsage>>;
80
+ /** Drop windows that ended before `before` (retention; called by `runtime.cron()`). */
81
+ prune?(before: number): Promise<void>;
82
+ }
83
+ /** The limits seam as `createRuntime({ limits })` takes it. */
84
+ export interface LatchLimits<P> {
85
+ store: LimitStore;
86
+ /**
87
+ * The counters this turn counts against. Empty → unlimited. Called per turn
88
+ * (admission), so it may read a policy table; keep it cheap.
89
+ */
90
+ limitsFor(input: {
91
+ principal: P;
92
+ /** The agent ref as resolved for the turn. */
93
+ agent: string;
94
+ trigger: TurnTrigger;
95
+ }): LimitPolicy[] | Promise<LimitPolicy[]>;
96
+ /** A turn was refused. For metrics/alerts; must not throw. */
97
+ onRefused?(input: {
98
+ principal: P;
99
+ agent: string;
100
+ trigger: TurnTrigger;
101
+ policy: LimitPolicy;
102
+ usage: LimitUsage;
103
+ retryAfterMs: number;
104
+ }): void | Promise<void>;
105
+ }
106
+ export declare const MINUTE_MS = 60000;
107
+ export declare const HOUR_MS: number;
108
+ export declare const DAY_MS: number;
109
+ /** The start of the fixed window containing `now`. */
110
+ export declare const windowStartOf: (now: number, windowMs: number) => number;
111
+ /** `LimitWindowRef` for a policy at `now`. */
112
+ export declare const windowRef: (policy: LimitPolicy, now: number) => LimitWindowRef;
113
+ /**
114
+ * Map key for one window in `LimitStore.read`'s result. The window LENGTH is
115
+ * part of it: the same counter key may carry a per-hour and a per-day policy,
116
+ * and their aligned windows share a start on the hour.
117
+ */
118
+ export declare const usageKey: (ref: LimitWindowRef) => string;
119
+ /**
120
+ * Thrown (and mapped to HTTP 429) when a turn is refused at admission.
121
+ * `code` is stable so hosts can match it structurally.
122
+ */
123
+ export declare class LimitExceededError extends Error {
124
+ readonly policy: LimitPolicy;
125
+ readonly usage: LimitUsage;
126
+ /** Milliseconds until the window turns over and the budget resets. */
127
+ readonly retryAfterMs: number;
128
+ readonly code: "limit_exceeded";
129
+ constructor(policy: LimitPolicy, usage: LimitUsage,
130
+ /** Milliseconds until the window turns over and the budget resets. */
131
+ retryAfterMs: number);
132
+ }
133
+ export declare function isLimitExceededError(e: unknown): e is LimitExceededError;
134
+ /**
135
+ * Admission decision over usage that ALREADY includes this turn's reservation
136
+ * (`nextTurn: false`), or over settled usage when the turn is to be counted on
137
+ * top (`nextTurn: true`). Returns the first exhausted policy, or null when every
138
+ * budget has room.
139
+ */
140
+ export declare function exceededPolicy(policies: LimitPolicy[], usage: Record<string, LimitUsage>, now: number, opts: {
141
+ nextTurn: boolean;
142
+ }): {
143
+ policy: LimitPolicy;
144
+ usage: LimitUsage;
145
+ retryAfterMs: number;
146
+ } | null;
147
+ /**
148
+ * The tightest remaining token budget across policies (for the mid-turn stop),
149
+ * or undefined when no policy caps tokens.
150
+ */
151
+ export declare function remainingTokens(policies: LimitPolicy[], usage: Record<string, LimitUsage>, now: number): number | undefined;
152
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"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"}
package/dist/limits.js ADDED
@@ -0,0 +1,97 @@
1
+ export const MINUTE_MS = 60_000;
2
+ export const HOUR_MS = 60 * MINUTE_MS;
3
+ export const DAY_MS = 24 * HOUR_MS;
4
+ /** The start of the fixed window containing `now`. */
5
+ export const windowStartOf = (now, windowMs) => Math.floor(now / windowMs) * windowMs;
6
+ /** `LimitWindowRef` for a policy at `now`. */
7
+ export const windowRef = (policy, now) => ({
8
+ key: policy.key,
9
+ windowStart: windowStartOf(now, policy.windowMs),
10
+ windowMs: policy.windowMs,
11
+ });
12
+ /**
13
+ * Map key for one window in `LimitStore.read`'s result. The window LENGTH is
14
+ * part of it: the same counter key may carry a per-hour and a per-day policy,
15
+ * and their aligned windows share a start on the hour.
16
+ */
17
+ export const usageKey = (ref) => `${ref.key}@${ref.windowStart}/${ref.windowMs}`;
18
+ const ZERO = { turns: 0, tokens: 0, cost: 0 };
19
+ /**
20
+ * Thrown (and mapped to HTTP 429) when a turn is refused at admission.
21
+ * `code` is stable so hosts can match it structurally.
22
+ */
23
+ export class LimitExceededError extends Error {
24
+ policy;
25
+ usage;
26
+ retryAfterMs;
27
+ code = "limit_exceeded";
28
+ constructor(policy, usage,
29
+ /** Milliseconds until the window turns over and the budget resets. */
30
+ retryAfterMs) {
31
+ const what = policy.label ?? `the ${describeWindow(policy.windowMs)} limit for ${policy.key}`;
32
+ super(`Limit reached: ${what}. Try again in ${describeDuration(retryAfterMs)}.`);
33
+ this.policy = policy;
34
+ this.usage = usage;
35
+ this.retryAfterMs = retryAfterMs;
36
+ this.name = "LimitExceededError";
37
+ }
38
+ }
39
+ export function isLimitExceededError(e) {
40
+ return e?.code === "limit_exceeded";
41
+ }
42
+ /**
43
+ * Admission decision over usage that ALREADY includes this turn's reservation
44
+ * (`nextTurn: false`), or over settled usage when the turn is to be counted on
45
+ * top (`nextTurn: true`). Returns the first exhausted policy, or null when every
46
+ * budget has room.
47
+ */
48
+ export function exceededPolicy(policies, usage, now, opts) {
49
+ for (const policy of policies) {
50
+ const ref = windowRef(policy, now);
51
+ const u = usage[usageKey(ref)] ?? ZERO;
52
+ const turns = u.turns + (opts.nextTurn ? 1 : 0);
53
+ const over = (policy.max.turns !== undefined && turns > policy.max.turns) ||
54
+ (policy.max.tokens !== undefined && u.tokens >= policy.max.tokens) ||
55
+ (policy.max.cost !== undefined && u.cost >= policy.max.cost);
56
+ if (over)
57
+ return { policy, usage: u, retryAfterMs: ref.windowStart + ref.windowMs - now };
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * The tightest remaining token budget across policies (for the mid-turn stop),
63
+ * or undefined when no policy caps tokens.
64
+ */
65
+ export function remainingTokens(policies, usage, now) {
66
+ let min;
67
+ for (const policy of policies) {
68
+ if (policy.max.tokens === undefined)
69
+ continue;
70
+ const u = usage[usageKey(windowRef(policy, now))] ?? ZERO;
71
+ const left = Math.max(0, policy.max.tokens - u.tokens);
72
+ min = min === undefined ? left : Math.min(min, left);
73
+ }
74
+ return min;
75
+ }
76
+ function describeWindow(ms) {
77
+ if (ms % DAY_MS === 0)
78
+ return ms === DAY_MS ? "daily" : `${ms / DAY_MS}-day`;
79
+ if (ms % HOUR_MS === 0)
80
+ return ms === HOUR_MS ? "hourly" : `${ms / HOUR_MS}-hour`;
81
+ if (ms % MINUTE_MS === 0)
82
+ return ms === MINUTE_MS ? "per-minute" : `${ms / MINUTE_MS}-minute`;
83
+ return `${Math.round(ms / 1000)}s`;
84
+ }
85
+ function describeDuration(ms) {
86
+ const s = Math.ceil(ms / 1000);
87
+ if (s < 90)
88
+ return `${s}s`;
89
+ const m = Math.ceil(s / 60);
90
+ if (m < 90)
91
+ return `${m} min`;
92
+ const h = Math.ceil(m / 60);
93
+ if (h < 36)
94
+ return `${h} h`;
95
+ return `${Math.ceil(h / 24)} d`;
96
+ }
97
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"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"}