@dudousxd/nestjs-agent-core 0.15.5 → 0.16.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/README.md +67 -0
- package/dist/guardrails/index.cjs +2519 -0
- package/dist/guardrails/index.cjs.map +1 -0
- package/dist/guardrails/index.d.cts +660 -0
- package/dist/guardrails/index.d.ts +660 -0
- package/dist/guardrails/index.js +2445 -0
- package/dist/guardrails/index.js.map +1 -0
- package/dist/index.cjs +27 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1012
- package/dist/index.d.ts +3 -1012
- package/dist/index.js +25 -25
- package/dist/index.js.map +1 -1
- package/dist/tool-CL9oEytW.d.cts +1014 -0
- package/dist/tool-CL9oEytW.d.ts +1014 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -37,6 +37,73 @@ import type { ModelProvider, AgentStore, ToolSpec, RolesPolicy } from '@dudousxd
|
|
|
37
37
|
- `settleAll(tasks)` / `SettledTask<T>` — the implementation behind the optional `AgentLoopHooks.parallel`, which is how a turn's `read` tool calls run concurrently. It invokes every task synchronously, in list order, before awaiting any of them (so a runner that takes checkpoint positions on the call keeps them in call order), and resolves only once all of them have settled (so a runner that unwinds a turn by throwing never abandons a sibling mid-dispatch). A runner that assigns positions anywhere else simply omits the hook and the loop stays sequential.
|
|
38
38
|
- `runAgentLoop(deps, input, hooks)` — the loop; the NestJS package drives it from both runners
|
|
39
39
|
|
|
40
|
+
## Guardrails — `@dudousxd/nestjs-agent-core/guardrails`
|
|
41
|
+
|
|
42
|
+
PII, secret, prompt-injection and tool-poisoning detection, a rule engine with reversible redaction,
|
|
43
|
+
and the adapter onto the processor seams above. A separate entry point with **no imports at all** —
|
|
44
|
+
no NestJS, no agent loop, no Node-only API — so a process that never runs the loop (a gateway
|
|
45
|
+
proxying raw OpenAI / Anthropic / MCP traffic) uses the same detectors and engine.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createGuardrails } from '@dudousxd/nestjs-agent-core/guardrails';
|
|
49
|
+
|
|
50
|
+
const guardrails = createGuardrails({
|
|
51
|
+
pii: 'redact', // reversible: the model sees [EMAIL_1], the reader the address
|
|
52
|
+
secrets: 'block',
|
|
53
|
+
injection: { threshold: 0.6 }, // default action: block
|
|
54
|
+
toolPoisoning: true, // for guardrails.screenTool (see the MCP package's `screen`)
|
|
55
|
+
rules: (ctx) => rulesForTenant(ctx.actor?.tenantRef), // more rules, resolved per scan
|
|
56
|
+
onEvent: (event) => audit.write(event), // hits carry fingerprints, never values
|
|
57
|
+
fingerprint: (value) => hmac(value),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
AgentModule.forRoot({
|
|
61
|
+
inputProcessors: [guardrails.input],
|
|
62
|
+
outputProcessors: [guardrails.output],
|
|
63
|
+
// …
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Where each stage runs, and what each action does there:
|
|
68
|
+
|
|
69
|
+
| Stage | Seen by | `redact` | `block` / `approve` |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `llm_request` — system, messages, earlier tool-call arguments | `guardrails.input`, every step | placeholders, restorable | the run fails: `ProcessorFailedError` whose `cause` is a `GuardrailBlockedError` |
|
|
72
|
+
| `tool_result` — every tool result riding in the prompt | `guardrails.input`, every step, one result at a time (`match.tools` applies) | placeholders, restorable | the result is **withheld**: the model is told why and the turn goes on |
|
|
73
|
+
| `llm_response` — each step's answer | `guardrails.output` | one-way placeholders; the thread's own placeholders are then restored | `reject` → `OutputRejectedError` |
|
|
74
|
+
| `tool_args` — the calls a step asks for | `guardrails.output` (read-only there) and `guardrails.wrapTool` | only through `wrapTool` | `reject` the step, or `wrapTool` throws and the model reads the tool's failure |
|
|
75
|
+
| `tool_description` | `guardrails.screenTool(tool)` | — | `{ allowed: false, reason }` |
|
|
76
|
+
|
|
77
|
+
- **Streaming stays on.** The output processor declares `incremental` with a 256-character lookback
|
|
78
|
+
(`incremental: { lookbackChars }` widens it, `false` gates the whole answer). One-way placeholders
|
|
79
|
+
are numbered per pass, so a prefix and its extension agree, which is the promise `incremental`
|
|
80
|
+
makes.
|
|
81
|
+
- **Placeholders live per thread** in a `VaultStore` (default `InMemoryVaultStore`, bounded). A run
|
|
82
|
+
resumed in another process finds no vault and its placeholders reach the reader unrestored — the
|
|
83
|
+
values still never reach the model. `Vault.toJSON` / `Vault.fromJSON` back a shared store, which
|
|
84
|
+
then holds the raw values.
|
|
85
|
+
- **`wrapTool(name, handler)`** is the one place a tool's arguments can be rewritten: it restores the
|
|
86
|
+
thread's placeholders (the model only ever saw `[EMAIL_1]`, the tool needs the address) and runs
|
|
87
|
+
the `tool_args` rules on what the tool is about to receive. PII is deliberately not a `tool_args`
|
|
88
|
+
stage of the `pii` shorthand for that reason; secrets are.
|
|
89
|
+
- **Re-sent history is not re-refused.** Content only in earlier turns is redacted one-way instead
|
|
90
|
+
of blocked, and each hit is reported once per turn — every step re-sends the prompt and an
|
|
91
|
+
incremental gate re-reads a growing answer.
|
|
92
|
+
- **A rule** is `{ id, stages, detectors, action, match?: { sources?, tools? }, when?, options? }`.
|
|
93
|
+
`when(ctx)` carries anything scope-like (tenant, roles, model); a `{ kind: 'custom', name, detect }`
|
|
94
|
+
detector carries anything the built-ins do not (NER, a classifier, a moderation endpoint, an LLM
|
|
95
|
+
judge). A detector that throws follows the rule's `failMode` (`open` by default).
|
|
96
|
+
|
|
97
|
+
Standalone, without the loop: `detectPii` (Luhn + card brands, CPF/CNPJ incl. the 2026 alphanumeric
|
|
98
|
+
CNPJ, SSN, IBAN mod 97, phones, IPv4), `detectSecrets` (provider key formats, JWT, PEM, entropy-gated
|
|
99
|
+
generic assignments), `scoreInjection` (EN / PT-BR / ES, chat-template spoofing, hidden Unicode tag
|
|
100
|
+
characters, base64 payloads, markdown exfiltration), `scoreToolText` + `toolText`, `detectRegex`,
|
|
101
|
+
`detectKeywords`, then `scan(rules, ctx, segments, vault)` to combine them. For provider wire formats:
|
|
102
|
+
`requestSlots` / `responseSlots` / `toolResultSlots` (OpenAI chat, Anthropic Messages, MCP results —
|
|
103
|
+
each a text plus a setter that writes back in place), `refuseResponse`, and `StreamGuard`, which
|
|
104
|
+
redacts, restores and blocks an OpenAI or Anthropic SSE stream in windows that never cut through a
|
|
105
|
+
finding or a placeholder.
|
|
106
|
+
|
|
40
107
|
## License
|
|
41
108
|
|
|
42
109
|
MIT © Davide Carvalho
|