@theoremjs/agents 0.3.4-next.484.g20d8b9d → 0.3.4-next.522.g9e684cd
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/README.md +61 -51
- package/esm/mod.d.ts +2 -2
- package/esm/mod.js +1 -1
- package/esm/src/guardrails/credential-scan.d.ts +1 -1
- package/esm/src/guardrails/credential-scan.js +41 -18
- package/esm/src/guardrails/destinations.js +6 -2
- package/esm/src/guardrails/detect-at.d.ts +5 -5
- package/esm/src/guardrails/detect-at.js +51 -41
- package/esm/src/guardrails/detect-reply.d.ts +1 -1
- package/esm/src/guardrails/detect-reply.js +4 -2
- package/esm/src/guardrails/egress-automata.js +203 -202
- package/esm/src/guardrails/egress-compiler.js +1 -4
- package/esm/src/guardrails/egress-stream.js +10 -3
- package/esm/src/guardrails/egress-urls.js +4 -2
- package/esm/src/guardrails/error.d.ts +1 -1
- package/esm/src/guardrails/error.js +19 -2
- package/esm/src/guardrails/host-patterns.d.ts +1 -4
- package/esm/src/guardrails/host-patterns.js +1 -10
- package/esm/src/guardrails/injection-patterns.d.ts +3 -1
- package/esm/src/guardrails/injection-patterns.js +87 -4
- package/esm/src/guardrails/labels.d.ts +2 -2
- package/esm/src/guardrails/labels.js +7 -5
- package/esm/src/guardrails/mod.d.ts +2 -2
- package/esm/src/guardrails/mod.js +1 -1
- package/esm/src/guardrails/normalize.js +4 -2
- package/esm/src/guardrails/prompt-echo.js +4 -2
- package/esm/src/guardrails/sanitize.d.ts +1 -1
- package/esm/src/guardrails/sanitize.js +4 -3
- package/esm/src/guardrails/sensitive.d.ts +2 -2
- package/esm/src/guardrails/sensitive.js +1 -1
- package/esm/src/host/client-turn.js +8 -1
- package/esm/src/interface/profile-interface.js +13 -0
- package/esm/src/kernel/answer-tool.d.ts +14 -0
- package/esm/src/kernel/answer-tool.js +26 -0
- package/esm/src/kernel/engine/decision-references.d.ts +14 -0
- package/esm/src/kernel/engine/decision-references.js +55 -0
- package/esm/src/kernel/engine/decision.d.ts +3 -3
- package/esm/src/kernel/engine/decision.js +38 -22
- package/esm/src/kernel/engine/embedding.js +0 -2
- package/esm/src/kernel/engine/runner/gates.js +40 -5
- package/esm/src/kernel/engine/runner/mod.js +13 -5
- package/esm/src/kernel/engine/runner/state.d.ts +9 -1
- package/esm/src/kernel/engine/runner/state.js +10 -1
- package/esm/src/kernel/engine/runner/steps.js +50 -13
- package/esm/src/kernel/engine/runner/stream.d.ts +14 -8
- package/esm/src/kernel/engine/runner/stream.js +25 -13
- package/esm/src/kernel/engine/turn-trace.js +2 -2
- package/esm/src/kernel/mod.d.ts +1 -1
- package/esm/src/kernel/mod.js +1 -1
- package/esm/src/kernel/profile-presence.js +12 -0
- package/esm/src/kernel/profile-scope.js +14 -14
- package/esm/src/kernel/protocols/binding.d.ts +3 -1
- package/esm/src/kernel/protocols/binding.js +6 -0
- package/esm/src/kernel/protocols/limits.d.ts +10 -11
- package/esm/src/kernel/protocols/limits.js +9 -23
- package/esm/src/kernel/protocols/runtime.d.ts +3 -2
- package/esm/src/kernel/protocols/runtime.js +3 -4
- package/esm/src/kernel/provider-budget.d.ts +2 -2
- package/esm/src/kernel/provider-budget.js +22 -12
- package/esm/src/kernel/provider-runtime.js +8 -5
- package/esm/src/kernel/registry/profiles.js +3 -3
- package/esm/src/kernel/registry/resolve.js +4 -0
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/schema.js +28 -2
- package/esm/src/kernel/tools/execute.js +3 -1
- package/esm/src/kernel/tools/image-fetch.d.ts +2 -4
- package/esm/src/kernel/tools/image-fetch.js +19 -20
- package/esm/src/kernel/tools/mcp-sessions.d.ts +7 -2
- package/esm/src/kernel/tools/mcp-sessions.js +21 -12
- package/esm/src/kernel/tools/registry.js +21 -0
- package/esm/src/kernel/tools/remote.d.ts +0 -2
- package/esm/src/kernel/tools/remote.js +28 -23
- package/esm/src/kernel/tools/types.d.ts +30 -4
- package/esm/src/kernel/turn-events.d.ts +2 -0
- package/esm/src/kernel/turn-events.js +1 -0
- package/esm/src/kernel/types.d.ts +4 -2
- package/esm/src/observability/spans.js +7 -8
- package/esm/src/observability/trace-catalog.js +1 -0
- package/esm/src/providers/embedding-limits.js +4 -4
- package/esm/src/providers/gemini/{embedding-protocol.d.ts → embeddings-protocol.d.ts} +3 -3
- package/esm/src/providers/gemini/{embedding-protocol.js → embeddings-protocol.js} +3 -3
- package/esm/src/providers/gemini/live-protocol.js +4 -1
- package/esm/src/providers/gemini/provider.js +2 -2
- package/esm/src/providers/local/embeddings-protocol.d.ts +7 -0
- package/esm/src/providers/local/{embedding-protocol.js → embeddings-protocol.js} +2 -2
- package/esm/src/providers/local/provider.js +2 -2
- package/esm/src/providers/mod.d.ts +3 -3
- package/esm/src/providers/mod.js +3 -3
- package/esm/src/providers/openrouter/decisions-protocol.js +11 -8
- package/esm/src/providers/openrouter/embeddings-protocol.d.ts +6 -0
- package/esm/src/providers/openrouter/{embedding-protocol.js → embeddings-protocol.js} +2 -2
- package/esm/src/providers/openrouter/provider.js +2 -2
- package/package.json +1 -1
- package/esm/src/providers/local/embedding-protocol.d.ts +0 -7
- package/esm/src/providers/openrouter/embedding-protocol.d.ts +0 -6
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// invariant: The one place a match becomes an action. Every boundary calls `detectAt`; nothing
|
|
2
2
|
// else reads the matrix to decide what a match does.
|
|
3
3
|
import { applySpans, spansFromPatterns } from '../observability/spans.js';
|
|
4
|
-
import {
|
|
4
|
+
import { recordOf, TOOL_BOUNDARIES } from './boundaries.js';
|
|
5
5
|
import { canaryLeakRanges } from './canary.js';
|
|
6
6
|
import { actionAt, DETECTORS, detects, } from './detectors.js';
|
|
7
7
|
import { boundaryNote } from './egress.js';
|
|
8
8
|
import { notePattern, SYSTEM_BOUNDARY } from './egress-patterns.js';
|
|
9
9
|
import { imageLeakSpans, linkLeakSpans } from './egress-urls.js';
|
|
10
|
+
import { describeError } from './error.js';
|
|
10
11
|
import { CANARY_HIT, hitFromSpan, promptEchoHit } from './hits.js';
|
|
11
12
|
import { injectionSpans } from './injection.js';
|
|
12
13
|
import { promptEchoRanges } from './prompt-echo.js';
|
|
13
14
|
import { DETECT_RULES, detectRule } from './rules.js';
|
|
14
15
|
import { sensitiveSpans } from './sensitive.js';
|
|
15
16
|
import { SENSITIVE_GROUPS } from './sensitive-groups.js';
|
|
16
|
-
import { TheoremError } from './theorem-error.js';
|
|
17
17
|
import { directives, mergedRanges } from './tool-directives.js';
|
|
18
18
|
import { ownToolsWithout, toolLeakSpans } from './tool-leak.js';
|
|
19
19
|
const STRENGTH = ['allow', 'flag', 'redact', 'block'];
|
|
@@ -143,18 +143,23 @@ function leavesImages(detector, boundary, detect) {
|
|
|
143
143
|
function foundBy(find, text, boundary) {
|
|
144
144
|
try {
|
|
145
145
|
const spans = find(text, boundary ? { boundary } : {});
|
|
146
|
+
const outside = {
|
|
147
|
+
signal: 'find_result',
|
|
148
|
+
// labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
149
|
+
reason: 'find returned no list of stretches inside the text',
|
|
150
|
+
};
|
|
146
151
|
if (!Array.isArray(spans))
|
|
147
|
-
return
|
|
152
|
+
return outside;
|
|
148
153
|
const inside = spans.every(({ start, end }) => Number.isInteger(start) &&
|
|
149
154
|
Number.isInteger(end) &&
|
|
150
155
|
start >= 0 &&
|
|
151
156
|
start < end &&
|
|
152
157
|
end <= text.length);
|
|
153
|
-
return inside ? spans.map(({ start, end }) => ({ start, end, kind: 'host' })) :
|
|
158
|
+
return inside ? spans.map(({ start, end }) => ({ start, end, kind: 'host' })) : outside;
|
|
154
159
|
}
|
|
155
|
-
catch {
|
|
160
|
+
catch (err) {
|
|
156
161
|
// why: A reading that failed found nothing it can vouch for, so the text does not cross.
|
|
157
|
-
return 'find_threw';
|
|
162
|
+
return { signal: 'find_threw', reason: `find threw: ${describeError(err)}` };
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
function hostReader(host, boundary, chosen) {
|
|
@@ -169,7 +174,9 @@ function hostReader(host, boundary, chosen) {
|
|
|
169
174
|
if (!host.find)
|
|
170
175
|
return matched;
|
|
171
176
|
const found = foundBy(host.find, text, boundary);
|
|
172
|
-
|
|
177
|
+
if (!Array.isArray(found))
|
|
178
|
+
return { ...found, reason: `Host detector '${host.id}' ${found.reason}` };
|
|
179
|
+
return [...matched, ...found];
|
|
173
180
|
},
|
|
174
181
|
hit: (text, span) => ({ ...hitFromSpan(text, span, rule, 'high'), label: host.label }),
|
|
175
182
|
};
|
|
@@ -208,32 +215,17 @@ function failedHit({ rule, label }, signal) {
|
|
|
208
215
|
...(signal === undefined ? {} : { signal }),
|
|
209
216
|
};
|
|
210
217
|
}
|
|
211
|
-
/**
|
|
212
|
-
* The longest single text the person or host may send, in characters: far over any model's
|
|
213
|
-
* context, and under what the detectors read safely.
|
|
214
|
-
*/
|
|
215
|
-
const MAX_INBOUND_CHARS = 1_000_000;
|
|
216
|
-
const INBOUND = new Set(INBOUND_BOUNDARIES);
|
|
217
|
-
/** Refuses a text the person or host sent that is over `MAX_INBOUND_CHARS`; `where` names it for the log. */
|
|
218
|
-
function refuseOverlong(text, where) {
|
|
219
|
-
if (text.length <= MAX_INBOUND_CHARS)
|
|
220
|
-
return;
|
|
221
|
-
// labels-exempt: internal diagnostic; the person reads `input.too_long`
|
|
222
|
-
throw new TheoremError('input', `${where} text of ${text.length} characters is over ${MAX_INBOUND_CHARS}`, {
|
|
223
|
-
copy: { key: 'input.too_long', params: { maxChars: MAX_INBOUND_CHARS } },
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
218
|
/** Reads `text` as it crosses `boundary` under the profile's resolved matrix. */
|
|
227
219
|
function detectAt(text, boundary, detect, scope = NO_SCOPE) {
|
|
228
|
-
if (INBOUND.has(boundary))
|
|
229
|
-
refuseOverlong(text, boundary);
|
|
230
220
|
const hits = [];
|
|
231
221
|
const redact = [];
|
|
222
|
+
const reasons = [];
|
|
232
223
|
let action = 'allow';
|
|
233
224
|
for (const reader of readersAt(boundary, detect, scope)) {
|
|
234
225
|
const spans = reader.spans(text);
|
|
235
|
-
if (
|
|
236
|
-
hits.push(failedHit(reader, spans));
|
|
226
|
+
if (!Array.isArray(spans)) {
|
|
227
|
+
hits.push(failedHit(reader, spans.signal));
|
|
228
|
+
reasons.push(spans.reason);
|
|
237
229
|
action = 'block';
|
|
238
230
|
continue;
|
|
239
231
|
}
|
|
@@ -242,11 +234,13 @@ function detectAt(text, boundary, detect, scope = NO_SCOPE) {
|
|
|
242
234
|
for (const span of spans)
|
|
243
235
|
hits.push(reader.hit(text, span));
|
|
244
236
|
if (reader.chosen === 'redact')
|
|
245
|
-
|
|
237
|
+
for (const span of spans)
|
|
238
|
+
redact.push(span);
|
|
246
239
|
action = stronger(action, reader.chosen);
|
|
247
240
|
}
|
|
248
|
-
if (action === 'block')
|
|
249
|
-
return { action, hits };
|
|
241
|
+
if (action === 'block') {
|
|
242
|
+
return { action, hits, ...(reasons.length ? { errorInternal: reasons.join('\n') } : {}) };
|
|
243
|
+
}
|
|
250
244
|
if (redact.length === 0)
|
|
251
245
|
return { action, text, hits };
|
|
252
246
|
const replaced = applySpans(text, redact);
|
|
@@ -264,7 +258,7 @@ function leftAfterRedact(replaced, boundary, detect, scope, detectors) {
|
|
|
264
258
|
if (chosen === 'flag')
|
|
265
259
|
return false;
|
|
266
260
|
const left = spans(replaced);
|
|
267
|
-
return !left || left.length > 0;
|
|
261
|
+
return !Array.isArray(left) || left.length > 0;
|
|
268
262
|
});
|
|
269
263
|
}
|
|
270
264
|
/** The detectors that read `boundary`: every one not set to `ignore`. */
|
|
@@ -298,16 +292,21 @@ function detectReads(boundaries, detect, scope) {
|
|
|
298
292
|
*/
|
|
299
293
|
function detectRelease(window, { from, to, settled }, boundary, detect, { detectors, scope }) {
|
|
300
294
|
const failed = [];
|
|
295
|
+
const reasons = [];
|
|
301
296
|
const found = readersAt(boundary, detect, scope, detectors).flatMap((reader) => {
|
|
302
297
|
const spans = reader.spans(window);
|
|
303
|
-
if (
|
|
304
|
-
failed.push(failedHit(reader, spans));
|
|
305
|
-
|
|
298
|
+
if (!Array.isArray(spans)) {
|
|
299
|
+
failed.push(failedHit(reader, spans.signal));
|
|
300
|
+
reasons.push(spans.reason);
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return spans
|
|
306
304
|
.filter((span) => span.end > from && span.start < to)
|
|
307
305
|
.map((span) => ({ span, rule: reader.rule, label: reader.label, chosen: reader.chosen }));
|
|
308
306
|
});
|
|
309
|
-
if (failed.length > 0)
|
|
310
|
-
return { action: 'block', hits: failed, taken: 0 };
|
|
307
|
+
if (failed.length > 0) {
|
|
308
|
+
return { action: 'block', hits: failed, taken: 0, errorInternal: reasons.join('\n') };
|
|
309
|
+
}
|
|
311
310
|
const hitOf = ({ span, rule, label }) => ({
|
|
312
311
|
...hitFromSpan(window, span, rule, 'high'),
|
|
313
312
|
...(label === undefined ? {} : { label }),
|
|
@@ -370,23 +369,31 @@ function storedHostSpans(text, detect) {
|
|
|
370
369
|
if (!host.find || !text)
|
|
371
370
|
return matched;
|
|
372
371
|
const found = foundBy(host.find, text);
|
|
373
|
-
return
|
|
374
|
-
? [
|
|
375
|
-
: [
|
|
372
|
+
return Array.isArray(found)
|
|
373
|
+
? [...matched, ...found]
|
|
374
|
+
: [{ start: 0, end: text.length, kind: 'host' }];
|
|
376
375
|
});
|
|
377
376
|
}
|
|
378
377
|
/** A reader for everything crossing `boundary` in one request. */
|
|
379
378
|
function boundaryReader(boundary, detect) {
|
|
380
379
|
const hits = [];
|
|
380
|
+
const reasons = [];
|
|
381
381
|
let action = 'allow';
|
|
382
382
|
return {
|
|
383
383
|
read(text) {
|
|
384
384
|
const detected = detectAt(text, boundary, detect);
|
|
385
|
-
|
|
385
|
+
for (const hit of detected.hits)
|
|
386
|
+
hits.push(hit);
|
|
387
|
+
if (detected.errorInternal)
|
|
388
|
+
reasons.push(detected.errorInternal);
|
|
386
389
|
action = stronger(action, detected.action);
|
|
387
390
|
return detected.text ?? '';
|
|
388
391
|
},
|
|
389
|
-
found: () => ({
|
|
392
|
+
found: () => ({
|
|
393
|
+
action,
|
|
394
|
+
hits,
|
|
395
|
+
...(reasons.length ? { errorInternal: reasons.join('\n') } : {}),
|
|
396
|
+
}),
|
|
390
397
|
};
|
|
391
398
|
}
|
|
392
399
|
const TOOL_STAGE = Object.fromEntries(TOOL_BOUNDARIES.map((boundary) => [
|
|
@@ -429,6 +436,9 @@ function detectEvent(boundary, found, provenance, stage = BOUNDARY_STAGE[boundar
|
|
|
429
436
|
action: found.action,
|
|
430
437
|
hits: found.hits,
|
|
431
438
|
...(provenance ? { provenance } : {}),
|
|
439
|
+
...(found.action === 'block' && found.errorInternal
|
|
440
|
+
? { errorInternal: found.errorInternal }
|
|
441
|
+
: {}),
|
|
432
442
|
};
|
|
433
443
|
}
|
|
434
|
-
export { boundaryReader, detectAt, detectEvent, detectorsAt, detectReads, detectRelease, hostAt, isScoped, leakScopeOf, redactDetectors,
|
|
444
|
+
export { boundaryReader, detectAt, detectEvent, detectorsAt, detectReads, detectRelease, hostAt, isScoped, leakScopeOf, redactDetectors, scopeOf, storedHostSpans, storedSpans, stronger, };
|
|
@@ -26,7 +26,7 @@ interface ReplyRead {
|
|
|
26
26
|
blocked?: GuardrailHit[];
|
|
27
27
|
}
|
|
28
28
|
/** A JSON value read at `boundary`, with each match in a string replaced, or what stops it crossing. */
|
|
29
|
-
declare function readValue(value: unknown, boundary: Boundary, detect: ResolvedDetect, scope?: DetectScope): Pick<Detection, 'action' | 'hits'> & {
|
|
29
|
+
declare function readValue(value: unknown, boundary: Boundary, detect: ResolvedDetect, scope?: DetectScope): Pick<Detection, 'action' | 'hits' | 'errorInternal'> & {
|
|
30
30
|
value?: unknown;
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
@@ -23,8 +23,10 @@ function readValue(value, boundary, detect, scope = {}) {
|
|
|
23
23
|
return { action: 'block', hits: [{ rule: EGRESS_RULES.unscannable, severity: 'high' }] };
|
|
24
24
|
}
|
|
25
25
|
const found = detectAt(scan.text, boundary, detect, scope);
|
|
26
|
-
if (found.action !== 'redact')
|
|
27
|
-
|
|
26
|
+
if (found.action !== 'redact') {
|
|
27
|
+
const why = found.errorInternal ? { errorInternal: found.errorInternal } : {};
|
|
28
|
+
return { action: found.action, hits: found.hits, value, ...why };
|
|
29
|
+
}
|
|
28
30
|
const redacted = mapStrings(value, (leaf) => detectAt(leaf, boundary, detect, scope).text ?? '');
|
|
29
31
|
const left = detectAt(textForScan(redacted).text, boundary, detect, scope).action;
|
|
30
32
|
// why: A match in a key, or one that runs across values, has no string to replace: it does not cross.
|