@adonis-agora/agent 0.34.0 → 0.35.0
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 +24 -0
- package/README.md +34 -0
- package/dist/assets/spa/assets/index-7XqfoeQH.js +9 -0
- package/dist/assets/spa/index.html +1 -1
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +4 -0
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/src/agent-deps-factory.d.ts +5 -0
- package/dist/src/agent-deps-factory.d.ts.map +1 -1
- package/dist/src/agent-deps-factory.js +6 -0
- package/dist/src/agent-deps-factory.js.map +1 -1
- package/dist/src/agent-deps.d.ts +5 -0
- package/dist/src/agent-deps.d.ts.map +1 -1
- package/dist/src/agent-deps.js.map +1 -1
- package/dist/src/define_config.d.ts +12 -0
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/guardrails/agent.d.ts +211 -0
- package/dist/src/guardrails/agent.d.ts.map +1 -0
- package/dist/src/guardrails/agent.js +433 -0
- package/dist/src/guardrails/agent.js.map +1 -0
- package/dist/src/guardrails/detectors/custom.d.ts +9 -0
- package/dist/src/guardrails/detectors/custom.d.ts.map +1 -0
- package/dist/src/guardrails/detectors/custom.js +81 -0
- package/dist/src/guardrails/detectors/custom.js.map +1 -0
- package/dist/src/guardrails/detectors/injection.d.ts +23 -0
- package/dist/src/guardrails/detectors/injection.d.ts.map +1 -0
- package/dist/src/guardrails/detectors/injection.js +208 -0
- package/dist/src/guardrails/detectors/injection.js.map +1 -0
- package/dist/src/guardrails/detectors/pii.d.ts +23 -0
- package/dist/src/guardrails/detectors/pii.d.ts.map +1 -0
- package/dist/src/guardrails/detectors/pii.js +295 -0
- package/dist/src/guardrails/detectors/pii.js.map +1 -0
- package/dist/src/guardrails/detectors/secrets.d.ts +5 -0
- package/dist/src/guardrails/detectors/secrets.d.ts.map +1 -0
- package/dist/src/guardrails/detectors/secrets.js +110 -0
- package/dist/src/guardrails/detectors/secrets.js.map +1 -0
- package/dist/src/guardrails/detectors/tool-poisoning.d.ts +16 -0
- package/dist/src/guardrails/detectors/tool-poisoning.d.ts.map +1 -0
- package/dist/src/guardrails/detectors/tool-poisoning.js +116 -0
- package/dist/src/guardrails/detectors/tool-poisoning.js.map +1 -0
- package/dist/src/guardrails/engine.d.ts +90 -0
- package/dist/src/guardrails/engine.d.ts.map +1 -0
- package/dist/src/guardrails/engine.js +324 -0
- package/dist/src/guardrails/engine.js.map +1 -0
- package/dist/src/guardrails/glob.d.ts +4 -0
- package/dist/src/guardrails/glob.d.ts.map +1 -0
- package/dist/src/guardrails/glob.js +20 -0
- package/dist/src/guardrails/glob.js.map +1 -0
- package/dist/src/guardrails/index.d.ts +20 -0
- package/dist/src/guardrails/index.d.ts.map +1 -0
- package/dist/src/guardrails/index.js +20 -0
- package/dist/src/guardrails/index.js.map +1 -0
- package/dist/src/guardrails/segments.d.ts +14 -0
- package/dist/src/guardrails/segments.d.ts.map +1 -0
- package/dist/src/guardrails/segments.js +32 -0
- package/dist/src/guardrails/segments.js.map +1 -0
- package/dist/src/guardrails/types.d.ts +161 -0
- package/dist/src/guardrails/types.d.ts.map +1 -0
- package/dist/src/guardrails/types.js +49 -0
- package/dist/src/guardrails/types.js.map +1 -0
- package/dist/src/guardrails/vault.d.ts +44 -0
- package/dist/src/guardrails/vault.d.ts.map +1 -0
- package/dist/src/guardrails/vault.js +103 -0
- package/dist/src/guardrails/vault.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/mcp-client/index.d.ts +1 -1
- package/dist/src/mcp-client/index.d.ts.map +1 -1
- package/dist/src/mcp-client/index.js.map +1 -1
- package/dist/src/mcp-client/options.d.ts +32 -0
- package/dist/src/mcp-client/options.d.ts.map +1 -1
- package/dist/src/mcp-client/tool-source.d.ts +2 -0
- package/dist/src/mcp-client/tool-source.d.ts.map +1 -1
- package/dist/src/mcp-client/tool-source.js +27 -1
- package/dist/src/mcp-client/tool-source.js.map +1 -1
- package/dist/src/rag/http-reranker.d.ts +46 -0
- package/dist/src/rag/http-reranker.d.ts.map +1 -0
- package/dist/src/rag/http-reranker.js +83 -0
- package/dist/src/rag/http-reranker.js.map +1 -0
- package/dist/src/rag/hybrid-retriever.d.ts +6 -1
- package/dist/src/rag/hybrid-retriever.d.ts.map +1 -1
- package/dist/src/rag/hybrid-retriever.js +27 -3
- package/dist/src/rag/hybrid-retriever.js.map +1 -1
- package/dist/src/rag/index.d.ts +5 -2
- package/dist/src/rag/index.d.ts.map +1 -1
- package/dist/src/rag/index.js +5 -2
- package/dist/src/rag/index.js.map +1 -1
- package/dist/src/rag/lexical-retriever.d.ts +23 -0
- package/dist/src/rag/lexical-retriever.d.ts.map +1 -0
- package/dist/src/rag/lexical-retriever.js +28 -0
- package/dist/src/rag/lexical-retriever.js.map +1 -0
- package/dist/src/rag/openai-embeddings.d.ts +62 -0
- package/dist/src/rag/openai-embeddings.d.ts.map +1 -0
- package/dist/src/rag/openai-embeddings.js +106 -0
- package/dist/src/rag/openai-embeddings.js.map +1 -0
- package/dist/src/rag/pg-vector-store.d.ts +111 -6
- package/dist/src/rag/pg-vector-store.d.ts.map +1 -1
- package/dist/src/rag/pg-vector-store.js +224 -31
- package/dist/src/rag/pg-vector-store.js.map +1 -1
- package/dist/src/rag/vector-store.d.ts +15 -0
- package/dist/src/rag/vector-store.d.ts.map +1 -1
- package/dist/src/rag/vector-store.js +4 -0
- package/dist/src/rag/vector-store.js.map +1 -1
- package/dist/src/stores/factory.d.ts +25 -3
- package/dist/src/stores/factory.d.ts.map +1 -1
- package/dist/src/stores/factory.js +21 -3
- package/dist/src/stores/factory.js.map +1 -1
- package/dist/src/testing/fake-embedding-provider.d.ts +12 -0
- package/dist/src/testing/fake-embedding-provider.d.ts.map +1 -1
- package/dist/src/testing/fake-embedding-provider.js +35 -0
- package/dist/src/testing/fake-embedding-provider.js.map +1 -1
- package/dist/src/testing/index.d.ts +1 -1
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -1
- package/dist/src/testing/index.js.map +1 -1
- package/package.json +14 -8
- package/dist/assets/spa/assets/index-DjIMQOxi.js +0 -9
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrails on the agent loop's own seams: an {@link InputProcessor} that scans (and redacts) every
|
|
3
|
+
* prompt — including the tool results riding in it — an {@link OutputProcessor} that scans every
|
|
4
|
+
* answer and the tool calls it asks for, a tool-handler wrapper, and a tool-definition screen.
|
|
5
|
+
*
|
|
6
|
+
* Built only on the processor SPI, so it is a consumer of the loop rather than a part of it: the
|
|
7
|
+
* same rules and detectors run standalone through `scan` for traffic that never meets the loop.
|
|
8
|
+
*/
|
|
9
|
+
import { scoreToolText, toolText } from './detectors/tool-poisoning.js';
|
|
10
|
+
import { scan } from './engine.js';
|
|
11
|
+
import { jsonSlots } from './segments.js';
|
|
12
|
+
import { Vault } from './vault.js';
|
|
13
|
+
/**
|
|
14
|
+
* The default {@link VaultStore}: this process's memory, bounded to the most recent threads. A run
|
|
15
|
+
* resumed in ANOTHER process finds no vault, so its placeholders reach the reader unrestored — the
|
|
16
|
+
* values still never reach the model. Supply a shared store (with {@link Vault.toJSON}) to restore
|
|
17
|
+
* across processes; it then holds the raw values.
|
|
18
|
+
*/
|
|
19
|
+
export class InMemoryVaultStore {
|
|
20
|
+
maxThreads;
|
|
21
|
+
vaults = new Map();
|
|
22
|
+
constructor(maxThreads = 1_000) {
|
|
23
|
+
this.maxThreads = maxThreads;
|
|
24
|
+
}
|
|
25
|
+
load(threadId) {
|
|
26
|
+
const vault = this.vaults.get(threadId);
|
|
27
|
+
if (vault !== undefined) {
|
|
28
|
+
// Re-inserted so the map's order is recency.
|
|
29
|
+
this.vaults.delete(threadId);
|
|
30
|
+
this.vaults.set(threadId, vault);
|
|
31
|
+
}
|
|
32
|
+
return vault;
|
|
33
|
+
}
|
|
34
|
+
save(threadId, vault) {
|
|
35
|
+
this.vaults.delete(threadId);
|
|
36
|
+
this.vaults.set(threadId, vault);
|
|
37
|
+
while (this.vaults.size > this.maxThreads) {
|
|
38
|
+
const oldest = this.vaults.keys().next().value;
|
|
39
|
+
if (oldest === undefined)
|
|
40
|
+
break;
|
|
41
|
+
this.vaults.delete(oldest);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** A guardrail refused content the loop cannot rewrite around: a prompt, or a tool call. */
|
|
46
|
+
export class GuardrailBlockedError extends Error {
|
|
47
|
+
stage;
|
|
48
|
+
userMessage;
|
|
49
|
+
rule;
|
|
50
|
+
constructor(stage,
|
|
51
|
+
/** The message for the person — the rule's own, or the stage default. */
|
|
52
|
+
userMessage, rule) {
|
|
53
|
+
super(userMessage);
|
|
54
|
+
this.stage = stage;
|
|
55
|
+
this.userMessage = userMessage;
|
|
56
|
+
this.rule = rule;
|
|
57
|
+
this.name = 'GuardrailBlockedError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const ALL_CONTENT = ['llm_request', 'tool_args', 'tool_result', 'llm_response'];
|
|
61
|
+
/**
|
|
62
|
+
* PII is redacted where it would reach the MODEL (and in what the model writes), not in the
|
|
63
|
+
* arguments of a tool: those are restored for the tool on purpose — see {@link Guardrails.wrapTool}.
|
|
64
|
+
*/
|
|
65
|
+
const RESTORABLE_STAGES = ['llm_request', 'tool_result'];
|
|
66
|
+
const PII_STAGES = ['llm_request', 'tool_result', 'llm_response'];
|
|
67
|
+
/** The rules the shorthand options stand for. Exported so a caller can inspect or extend them. */
|
|
68
|
+
export function shorthandRules(options) {
|
|
69
|
+
const rules = [];
|
|
70
|
+
if (options.pii !== undefined) {
|
|
71
|
+
const pii = typeof options.pii === 'string' ? { action: options.pii } : options.pii;
|
|
72
|
+
rules.push({
|
|
73
|
+
id: 'pii',
|
|
74
|
+
name: 'PII',
|
|
75
|
+
stages: pii.stages ?? PII_STAGES,
|
|
76
|
+
detectors: [{ kind: 'pii', ...(pii.types !== undefined ? { types: pii.types } : {}) }],
|
|
77
|
+
action: pii.action,
|
|
78
|
+
options: { restore: pii.restore ?? true },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (options.secrets !== undefined) {
|
|
82
|
+
const secrets = typeof options.secrets === 'string' ? { action: options.secrets } : options.secrets;
|
|
83
|
+
rules.push({
|
|
84
|
+
id: 'secrets',
|
|
85
|
+
name: 'Secrets',
|
|
86
|
+
stages: secrets.stages ?? ALL_CONTENT,
|
|
87
|
+
detectors: [
|
|
88
|
+
{ kind: 'secrets', ...(secrets.types !== undefined ? { types: secrets.types } : {}) },
|
|
89
|
+
],
|
|
90
|
+
action: secrets.action,
|
|
91
|
+
// A secret sent to a model is not handed back: the reader should not see it echoed either.
|
|
92
|
+
options: { restore: false },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (options.injection !== undefined) {
|
|
96
|
+
const injection = typeof options.injection === 'string' ? { action: options.injection } : options.injection;
|
|
97
|
+
rules.push({
|
|
98
|
+
id: 'injection',
|
|
99
|
+
name: 'Prompt injection',
|
|
100
|
+
// The prompt (a jailbreak typed by the user) and tool results (indirect injection).
|
|
101
|
+
stages: injection.stages ?? ['llm_request', 'tool_result'],
|
|
102
|
+
detectors: [
|
|
103
|
+
{
|
|
104
|
+
kind: 'injection',
|
|
105
|
+
...(injection.threshold !== undefined ? { threshold: injection.threshold } : {}),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
action: injection.action ?? 'block',
|
|
109
|
+
// What the user typed and what tools returned. Not the system prompt — the app's own text,
|
|
110
|
+
// which legitimately carries role tags and imperatives — nor the model's earlier answers.
|
|
111
|
+
match: { sources: ['user', 'tool'] },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (options.toolPoisoning !== undefined && options.toolPoisoning !== false) {
|
|
115
|
+
const poisoning = options.toolPoisoning === true ? {} : options.toolPoisoning;
|
|
116
|
+
rules.push({
|
|
117
|
+
id: 'tool_poisoning',
|
|
118
|
+
name: 'Tool poisoning',
|
|
119
|
+
stages: ['tool_description'],
|
|
120
|
+
detectors: [
|
|
121
|
+
{
|
|
122
|
+
kind: 'tool_poisoning',
|
|
123
|
+
...(poisoning.threshold !== undefined ? { threshold: poisoning.threshold } : {}),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
action: poisoning.action ?? 'block',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return rules;
|
|
130
|
+
}
|
|
131
|
+
const isRefusal = (action) => action === 'block' || action === 'approve';
|
|
132
|
+
function sourceOf(role) {
|
|
133
|
+
return role === 'system' ? 'system' : role === 'assistant' ? 'assistant' : 'user';
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Index of the newest user message: it and everything after it are this turn. One with no text is
|
|
137
|
+
* skipped — a loop may carry tool results back on an empty `user` message, which is not a new turn.
|
|
138
|
+
*/
|
|
139
|
+
function turnStart(messages) {
|
|
140
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
141
|
+
const message = messages[i];
|
|
142
|
+
if (message?.role === 'user' && message.content)
|
|
143
|
+
return i;
|
|
144
|
+
}
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
function cloneJson(value) {
|
|
148
|
+
return value === undefined ? value : structuredClone(value);
|
|
149
|
+
}
|
|
150
|
+
/** Scans segments, and writes the (redacted) texts back through their slots. */
|
|
151
|
+
function writeBack(slots, result) {
|
|
152
|
+
if (!result.changed)
|
|
153
|
+
return;
|
|
154
|
+
slots.forEach((slot, i) => {
|
|
155
|
+
const text = result.segments[i];
|
|
156
|
+
if (text !== undefined && text !== slot.segment.text)
|
|
157
|
+
slot.set(text);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Guardrails for the agent loop. Register the two processors, optionally wrap tool handlers, and
|
|
162
|
+
* hand {@link screenTool} to whatever imports remote tool definitions:
|
|
163
|
+
*
|
|
164
|
+
* ```ts
|
|
165
|
+
* const guardrails = createGuardrails({ pii: 'redact', secrets: 'block', injection: { threshold: 0.6 }, toolPoisoning: true });
|
|
166
|
+
* // config/agent.ts
|
|
167
|
+
* defineConfig({ inputProcessors: [guardrails.input], outputProcessors: [guardrails.output], … });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export class Guardrails {
|
|
171
|
+
options;
|
|
172
|
+
/** Scans every prompt; redacts in place, withholds tool results, throws on a blocked prompt. */
|
|
173
|
+
input;
|
|
174
|
+
/** Scans every answer and the tool calls it asks for; redacts, restores placeholders, or rejects. */
|
|
175
|
+
output;
|
|
176
|
+
shorthand;
|
|
177
|
+
vaults;
|
|
178
|
+
/** Per thread: hits already reported this turn, so re-scans of the same content stay quiet. */
|
|
179
|
+
reported = new Map();
|
|
180
|
+
constructor(options = {}) {
|
|
181
|
+
this.options = options;
|
|
182
|
+
this.shorthand = shorthandRules(options);
|
|
183
|
+
this.vaults = options.vaults ?? new InMemoryVaultStore();
|
|
184
|
+
const name = options.name ?? 'guardrails';
|
|
185
|
+
this.input = { name, process: (prompt, ctx) => this.processInput(prompt, ctx) };
|
|
186
|
+
const incremental = options.incremental === false
|
|
187
|
+
? undefined
|
|
188
|
+
: { lookbackChars: options.incremental?.lookbackChars ?? 256 };
|
|
189
|
+
this.output = {
|
|
190
|
+
name,
|
|
191
|
+
...(incremental !== undefined ? { incremental } : {}),
|
|
192
|
+
process: (answer, ctx) => this.processOutput(answer, ctx),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Every rule in force for `ctx`: the shorthands, then {@link GuardrailsOptions.rules}. */
|
|
196
|
+
async rulesFor(ctx) {
|
|
197
|
+
const extra = this.options.rules;
|
|
198
|
+
const resolved = typeof extra === 'function' ? await extra(ctx) : (extra ?? []);
|
|
199
|
+
return [...this.shorthand, ...resolved];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Scans arbitrary segments at one stage with the configured rules, reporting through `onEvent`.
|
|
203
|
+
* What the processors use; also the entry point for traffic that never meets the loop.
|
|
204
|
+
*/
|
|
205
|
+
async scan(ctx, segments, vault = new Vault()) {
|
|
206
|
+
const rules = await this.rulesFor(ctx);
|
|
207
|
+
const result = await scan(rules, ctx, segments, vault, {
|
|
208
|
+
...(this.options.messages !== undefined ? { messages: this.options.messages } : {}),
|
|
209
|
+
// A tool result is the app's own data: a value redacted out of it is handed back to the
|
|
210
|
+
// reader and to the next tool call like one the user typed. `restore: false` opts a rule out.
|
|
211
|
+
restorableStages: RESTORABLE_STAGES,
|
|
212
|
+
});
|
|
213
|
+
await this.report(ctx, result);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Screens one tool definition (name, title, description, and every description in its input
|
|
218
|
+
* schema) for hidden instructions. `allowed: false` means a `block`/`approve` rule fired — leave
|
|
219
|
+
* the tool out of the catalog. Shaped to be handed straight to an MCP importer's screen hook.
|
|
220
|
+
*/
|
|
221
|
+
async screenTool(tool) {
|
|
222
|
+
const ctx = {
|
|
223
|
+
stage: 'tool_description',
|
|
224
|
+
tool: tool.name,
|
|
225
|
+
...(tool.server !== undefined ? { server: tool.server } : {}),
|
|
226
|
+
};
|
|
227
|
+
const result = await this.scan(ctx, [{ text: toolText(tool) }]);
|
|
228
|
+
if (isRefusal(result.action)) {
|
|
229
|
+
const categories = [...new Set(result.hits.map((h) => h.category))].join(', ');
|
|
230
|
+
return {
|
|
231
|
+
allowed: false,
|
|
232
|
+
reason: `${result.decisive?.message ?? 'quarantined by guardrails'} (${categories})`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return { allowed: true };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Wraps a tool handler so its arguments are guarded where the loop cannot rewrite them: the
|
|
239
|
+
* thread's placeholders are restored (the model only ever saw `[EMAIL_1]`, the tool needs the
|
|
240
|
+
* address), then the `tool_args` rules run — `redact` rewrites what the tool receives, `block`
|
|
241
|
+
* throws a {@link GuardrailBlockedError}, which the model reads as the tool's failure.
|
|
242
|
+
*/
|
|
243
|
+
wrapTool(toolName, handler) {
|
|
244
|
+
return {
|
|
245
|
+
execute: async (input, ctx) => {
|
|
246
|
+
const vault = (await this.vaults.load(ctx.threadId)) ?? new Vault();
|
|
247
|
+
const box = { value: cloneJson(input) };
|
|
248
|
+
const slots = jsonSlots(box.value, 'assistant', true, (text) => {
|
|
249
|
+
box.value = text;
|
|
250
|
+
});
|
|
251
|
+
for (const slot of slots)
|
|
252
|
+
slot.set(vault.restore(slot.segment.text));
|
|
253
|
+
const guardCtx = {
|
|
254
|
+
stage: 'tool_args',
|
|
255
|
+
tool: toolName,
|
|
256
|
+
threadId: ctx.threadId,
|
|
257
|
+
actor: ctx.actor,
|
|
258
|
+
};
|
|
259
|
+
const restoredSlots = jsonSlots(box.value, 'assistant', true, (text) => {
|
|
260
|
+
box.value = text;
|
|
261
|
+
});
|
|
262
|
+
const result = await this.scan(guardCtx, restoredSlots.map((s) => s.segment), vault);
|
|
263
|
+
if (isRefusal(result.action)) {
|
|
264
|
+
throw new GuardrailBlockedError('tool_args', result.decisive?.message ?? 'blocked', result.decisive);
|
|
265
|
+
}
|
|
266
|
+
writeBack(restoredSlots, result);
|
|
267
|
+
return handler.execute(box.value, ctx);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
contextOf(stage, ctx, tool) {
|
|
272
|
+
return {
|
|
273
|
+
stage,
|
|
274
|
+
threadId: ctx.threadId,
|
|
275
|
+
actor: ctx.actor,
|
|
276
|
+
step: ctx.step,
|
|
277
|
+
...(ctx.agentName !== undefined ? { agentName: ctx.agentName } : {}),
|
|
278
|
+
...(tool !== undefined ? { tool } : {}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
async processInput(prompt, pctx) {
|
|
282
|
+
if (pctx.step === 0) {
|
|
283
|
+
// A new turn: whatever it contains is new, even if an earlier turn reported the same value.
|
|
284
|
+
this.reported.delete(pctx.threadId);
|
|
285
|
+
}
|
|
286
|
+
const vault = (await this.vaults.load(pctx.threadId)) ?? new Vault();
|
|
287
|
+
const out = { system: prompt.system, messages: cloneJson(prompt.messages) };
|
|
288
|
+
const from = turnStart(out.messages);
|
|
289
|
+
// The prompt proper: system, every message's text, and the arguments of earlier tool calls.
|
|
290
|
+
const slots = [];
|
|
291
|
+
if (out.system) {
|
|
292
|
+
slots.push({
|
|
293
|
+
segment: { text: out.system, source: 'system', fresh: true },
|
|
294
|
+
set: (text) => {
|
|
295
|
+
out.system = text;
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
out.messages.forEach((message, i) => {
|
|
300
|
+
const fresh = i >= from;
|
|
301
|
+
if (message.content) {
|
|
302
|
+
slots.push({
|
|
303
|
+
segment: { text: message.content, source: sourceOf(message.role), fresh },
|
|
304
|
+
set: (text) => {
|
|
305
|
+
message.content = text;
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
for (const call of message.toolCalls ?? []) {
|
|
310
|
+
slots.push(...jsonSlots(call.input, 'assistant', fresh, (text) => {
|
|
311
|
+
call.input = text;
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
const request = await this.scan(this.contextOf('llm_request', pctx), slots.map((s) => s.segment), vault);
|
|
316
|
+
if (isRefusal(request.action)) {
|
|
317
|
+
await this.vaults.save(pctx.threadId, vault);
|
|
318
|
+
throw new GuardrailBlockedError('llm_request', request.decisive?.message ?? 'blocked', request.decisive);
|
|
319
|
+
}
|
|
320
|
+
writeBack(slots, request);
|
|
321
|
+
// Tool results, one at a time so `match.tools` can tell them apart.
|
|
322
|
+
for (const [i, message] of out.messages.entries()) {
|
|
323
|
+
for (const result of message.toolResults ?? []) {
|
|
324
|
+
await this.guardToolResult(result, i >= from, pctx, vault);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
await this.vaults.save(pctx.threadId, vault);
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
async guardToolResult(result, fresh, pctx, vault) {
|
|
331
|
+
const slots = jsonSlots(result.output, 'tool', fresh, (text) => {
|
|
332
|
+
result.output = text;
|
|
333
|
+
});
|
|
334
|
+
if (result.error) {
|
|
335
|
+
slots.push({
|
|
336
|
+
segment: { text: result.error, source: 'tool', fresh },
|
|
337
|
+
set: (text) => {
|
|
338
|
+
result.error = text;
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (slots.length === 0)
|
|
343
|
+
return;
|
|
344
|
+
const scanned = await this.scan(this.contextOf('tool_result', pctx, result.name), slots.map((s) => s.segment), vault);
|
|
345
|
+
if (isRefusal(scanned.action)) {
|
|
346
|
+
// Withheld rather than failing the turn: the model is told why and can go on without it.
|
|
347
|
+
result.output = scanned.decisive?.message ?? 'withheld';
|
|
348
|
+
if (result.error !== undefined)
|
|
349
|
+
result.error = result.output;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
writeBack(slots, scanned);
|
|
353
|
+
}
|
|
354
|
+
async processOutput(answer, pctx) {
|
|
355
|
+
// Tool calls first: the loop cannot rewrite them, so all it can do is refuse the step.
|
|
356
|
+
for (const call of answer.toolCalls) {
|
|
357
|
+
const slots = jsonSlots(cloneJson(call.input), 'assistant', true);
|
|
358
|
+
if (slots.length === 0)
|
|
359
|
+
continue;
|
|
360
|
+
const args = await this.scan(this.contextOf('tool_args', pctx, call.name), slots.map((s) => s.segment));
|
|
361
|
+
if (isRefusal(args.action)) {
|
|
362
|
+
return { action: 'reject', reason: args.decisive?.message ?? 'blocked' };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!answer.text)
|
|
366
|
+
return { action: 'pass' };
|
|
367
|
+
// A fresh vault per pass: one-way placeholders are then numbered by first appearance in THIS
|
|
368
|
+
// text, so a prefix and its extension agree — what an incremental gate needs.
|
|
369
|
+
const response = await this.scan(this.contextOf('llm_response', pctx), [{ text: answer.text, source: 'assistant' }], new Vault());
|
|
370
|
+
if (isRefusal(response.action)) {
|
|
371
|
+
return { action: 'reject', reason: response.decisive?.message ?? 'blocked' };
|
|
372
|
+
}
|
|
373
|
+
const vault = await this.vaults.load(pctx.threadId);
|
|
374
|
+
const redacted = response.segments[0] ?? answer.text;
|
|
375
|
+
const text = vault !== undefined ? vault.restore(redacted) : redacted;
|
|
376
|
+
return text === answer.text ? { action: 'pass' } : { action: 'replace', text };
|
|
377
|
+
}
|
|
378
|
+
async report(ctx, result) {
|
|
379
|
+
const onEvent = this.options.onEvent;
|
|
380
|
+
if (onEvent === undefined || result.hits.length === 0)
|
|
381
|
+
return;
|
|
382
|
+
const seen = ctx.threadId !== undefined
|
|
383
|
+
? (this.reported.get(ctx.threadId) ??
|
|
384
|
+
this.reported.set(ctx.threadId, new Set()).get(ctx.threadId))
|
|
385
|
+
: undefined;
|
|
386
|
+
const fingerprint = this.options.fingerprint;
|
|
387
|
+
const hits = [];
|
|
388
|
+
for (const { values, ...hit } of result.hits) {
|
|
389
|
+
if (!hit.fresh)
|
|
390
|
+
continue;
|
|
391
|
+
// Scans repeat within a turn (every step re-sends the prompt; an incremental gate re-reads
|
|
392
|
+
// the growing answer), so a hit is reported once per turn per stage, rule, category and value.
|
|
393
|
+
const key = `${ctx.stage}\u0000${ctx.tool ?? ''}\u0000${hit.ruleId}\u0000${hit.category}\u0000${values.join('\u0001')}`;
|
|
394
|
+
if (seen?.has(key))
|
|
395
|
+
continue;
|
|
396
|
+
seen?.add(key);
|
|
397
|
+
hits.push({ ...hit, fingerprints: fingerprint !== undefined ? values.map(fingerprint) : [] });
|
|
398
|
+
}
|
|
399
|
+
if (hits.length === 0)
|
|
400
|
+
return;
|
|
401
|
+
if (this.reported.size > 1_000) {
|
|
402
|
+
const oldest = this.reported.keys().next().value;
|
|
403
|
+
if (oldest !== undefined)
|
|
404
|
+
this.reported.delete(oldest);
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
await onEvent({
|
|
408
|
+
stage: result.stage,
|
|
409
|
+
action: result.action,
|
|
410
|
+
...(result.decisive !== undefined ? { decisive: result.decisive } : {}),
|
|
411
|
+
hits,
|
|
412
|
+
redactions: result.redactions,
|
|
413
|
+
latencyMs: result.latencyMs,
|
|
414
|
+
context: ctx,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// An audit sink must never change a decision.
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/** Shorthand for `new Guardrails(options)`. */
|
|
423
|
+
export function createGuardrails(options = {}) {
|
|
424
|
+
return new Guardrails(options);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* The tool-poisoning screen alone — no rules, no events. `score` is the noisy-OR of the signals
|
|
428
|
+
* that fired; `findings` is empty below `threshold` (default 0.5).
|
|
429
|
+
*/
|
|
430
|
+
export function screenToolDefinition(tool, threshold = 0.5) {
|
|
431
|
+
return scoreToolText(toolText(tool), threshold);
|
|
432
|
+
}
|
|
433
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/guardrails/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
/** Compiled once per pattern (global flag added), with a bounded cache. */
|
|
3
|
+
export declare function compileGuardPattern(pattern: string): RegExp;
|
|
4
|
+
export declare function detectRegex(text: string, patterns: readonly string[], label?: string): Finding[];
|
|
5
|
+
/** Lower case without accents, keeping string length stable (one output char per input char). */
|
|
6
|
+
export declare function fold(s: string): string;
|
|
7
|
+
/** Whole-word, case- and accent-insensitive keyword/phrase matching. */
|
|
8
|
+
export declare function detectKeywords(text: string, words: readonly string[], label?: string): Finding[];
|
|
9
|
+
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../../../src/guardrails/detectors/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAS3C,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAS3D;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,SAAW,GACf,OAAO,EAAE,CA4BX;AAID,iGAAiG;AACjG,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQtC;AAID,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,SAAa,GACjB,OAAO,EAAE,CAqBX"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** `(?i)` prefix = case-insensitive, the one inline flag JavaScript regexes lack. */
|
|
2
|
+
function compileFlagged(pattern) {
|
|
3
|
+
return pattern.startsWith('(?i)') ? new RegExp(pattern.slice(4), 'i') : new RegExp(pattern);
|
|
4
|
+
}
|
|
5
|
+
const compiled = new Map();
|
|
6
|
+
/** Compiled once per pattern (global flag added), with a bounded cache. */
|
|
7
|
+
export function compileGuardPattern(pattern) {
|
|
8
|
+
let re = compiled.get(pattern);
|
|
9
|
+
if (!re) {
|
|
10
|
+
const base = compileFlagged(pattern);
|
|
11
|
+
re = new RegExp(base.source, `${base.flags.replace('g', '')}g`);
|
|
12
|
+
if (compiled.size > 5_000)
|
|
13
|
+
compiled.clear();
|
|
14
|
+
compiled.set(pattern, re);
|
|
15
|
+
}
|
|
16
|
+
return re;
|
|
17
|
+
}
|
|
18
|
+
export function detectRegex(text, patterns, label = 'custom') {
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const p of patterns) {
|
|
21
|
+
let re;
|
|
22
|
+
try {
|
|
23
|
+
re = compileGuardPattern(p);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
re.lastIndex = 0;
|
|
29
|
+
let guard = 0;
|
|
30
|
+
for (let m = re.exec(text); m && guard < 1000; m = re.exec(text), guard++) {
|
|
31
|
+
if (m[0].length === 0) {
|
|
32
|
+
re.lastIndex++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
out.push({
|
|
36
|
+
detector: 'regex',
|
|
37
|
+
category: `regex.${label}`,
|
|
38
|
+
start: m.index,
|
|
39
|
+
end: m.index + m[0].length,
|
|
40
|
+
score: 1,
|
|
41
|
+
value: m[0],
|
|
42
|
+
spanned: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const COMBINING = /[\u0300-\u036f]/g;
|
|
49
|
+
/** Lower case without accents, keeping string length stable (one output char per input char). */
|
|
50
|
+
export function fold(s) {
|
|
51
|
+
let out = '';
|
|
52
|
+
for (const ch of s) {
|
|
53
|
+
const base = ch.normalize('NFD').replace(COMBINING, '');
|
|
54
|
+
const c = (base.length === ch.length ? base : ch).toLowerCase();
|
|
55
|
+
out += c.length === ch.length ? c : ch;
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
60
|
+
/** Whole-word, case- and accent-insensitive keyword/phrase matching. */
|
|
61
|
+
export function detectKeywords(text, words, label = 'keywords') {
|
|
62
|
+
const clean = words.map((w) => fold(w.trim())).filter(Boolean);
|
|
63
|
+
if (clean.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
const re = new RegExp(`(?<![\\p{L}\\p{N}_])(?:${clean.map(escapeRe).join('|')})(?![\\p{L}\\p{N}_])`, 'gu');
|
|
66
|
+
const folded = fold(text);
|
|
67
|
+
const out = [];
|
|
68
|
+
for (let m = re.exec(folded); m; m = re.exec(folded)) {
|
|
69
|
+
out.push({
|
|
70
|
+
detector: 'keywords',
|
|
71
|
+
category: `keywords.${label}`,
|
|
72
|
+
start: m.index,
|
|
73
|
+
end: m.index + m[0].length,
|
|
74
|
+
score: 1,
|
|
75
|
+
value: text.slice(m.index, m.index + m[0].length),
|
|
76
|
+
spanned: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=custom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.js","sourceRoot":"","sources":["../../../../src/guardrails/detectors/custom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Prompt-injection / jailbreak heuristics. Each signal has a weight; the text's score is the
|
|
4
|
+
* noisy-OR of the distinct signals that fired (1 - Π(1 - w)), so one strong signal or several weak
|
|
5
|
+
* ones cross the threshold. English, Portuguese (PT-BR) and Spanish phrasings, role/delimiter
|
|
6
|
+
* spoofing, hidden Unicode (tag characters, bidi overrides, zero-width runs) and exfiltration
|
|
7
|
+
* through rendered links/images.
|
|
8
|
+
*/
|
|
9
|
+
export interface Signal {
|
|
10
|
+
id: string;
|
|
11
|
+
re: RegExp;
|
|
12
|
+
weight: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const INJECTION_SIGNALS: Signal[];
|
|
15
|
+
/** Decodes Unicode tag characters ("ASCII smuggling") back to the ASCII they encode. */
|
|
16
|
+
export declare function decodeTagChars(s: string): string;
|
|
17
|
+
export interface InjectionResult {
|
|
18
|
+
score: number;
|
|
19
|
+
findings: Finding[];
|
|
20
|
+
}
|
|
21
|
+
/** Scores a text; findings are returned only when the score reaches `threshold`. */
|
|
22
|
+
export declare function scoreInjection(text: string, threshold?: number): InjectionResult;
|
|
23
|
+
//# sourceMappingURL=injection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection.d.ts","sourceRoot":"","sources":["../../../../src/guardrails/detectors/injection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,eAAO,MAAM,iBAAiB,EAAE,MAAM,EAuFrC,CAAC;AAmBF,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOhD;AA+BD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,eAAe,CA0E7E"}
|