@dudousxd/nestjs-agent-core 0.15.5 → 0.17.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 +68 -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 +81 -1013
- package/dist/index.d.ts +81 -1013
- 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
|
@@ -35,8 +35,76 @@ import type { ModelProvider, AgentStore, ToolSpec, RolesPolicy } from '@dudousxd
|
|
|
35
35
|
- `RunCancelledError` / `AgentLoopHooks.cancelled()` — real cancellation. The loop asks `hooks.cancelled()` at the points where stopping is safe and cheap (between steps, before the next model call, before a turn's tools are dispatched), ALWAYS from inside a checkpoint, so the answer is journaled and a cancel arriving between two replays can never change a branch a replayed position already took; the positions themselves sit behind `hooks.patched('agent:cancellation')`, so a run already in flight keeps its recorded shape. A tool already executing is never interrupted — there is no un-executing a side effect, and abandoning a dispatched step leaves a journal holding a dispatch whose result never lands. Observing a cancel throws `RunCancelledError`, which unwinds through the path a suspend already uses; the RUNNER settles it, recording the run `cancelled` (`AgentStore.recordRunEnd`'s third terminal, never `failed`) and ending the stream on a `{ kind: 'cancelled' }` frame rather than failing it, so a user pressing Stop is not in anybody's error rate.
|
|
36
36
|
- `isControlFlowSignal(error)` / `isReplayIntegrityError(error)` — recognize a durable suspend and a checkpoint refusal without importing the durable packages (a `Symbol.for` marker and a class name, respectively). Rethrow both untouched from any `catch` in a workflow body.
|
|
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
|
+
- `AgentStreamEvent` / `encodeStreamEvent` / `decodeStreamEvent` — the live-stream vocabulary (text, reasoning, tool calls with optional `parentId` nesting, `elicitation`, `approval-requested` with `approver`/`expiresAt`, server-pushed `ui` components, `title`, `cancelled`). It is also the contract a runner that is not this loop writes to get the React client for free: see [docs/stream-protocol.md](../../docs/stream-protocol.md). `AgentUiComponent` and `AgentApprovalRequest` are the payload types.
|
|
38
39
|
- `runAgentLoop(deps, input, hooks)` — the loop; the NestJS package drives it from both runners
|
|
39
40
|
|
|
41
|
+
## Guardrails — `@dudousxd/nestjs-agent-core/guardrails`
|
|
42
|
+
|
|
43
|
+
PII, secret, prompt-injection and tool-poisoning detection, a rule engine with reversible redaction,
|
|
44
|
+
and the adapter onto the processor seams above. A separate entry point with **no imports at all** —
|
|
45
|
+
no NestJS, no agent loop, no Node-only API — so a process that never runs the loop (a gateway
|
|
46
|
+
proxying raw OpenAI / Anthropic / MCP traffic) uses the same detectors and engine.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { createGuardrails } from '@dudousxd/nestjs-agent-core/guardrails';
|
|
50
|
+
|
|
51
|
+
const guardrails = createGuardrails({
|
|
52
|
+
pii: 'redact', // reversible: the model sees [EMAIL_1], the reader the address
|
|
53
|
+
secrets: 'block',
|
|
54
|
+
injection: { threshold: 0.6 }, // default action: block
|
|
55
|
+
toolPoisoning: true, // for guardrails.screenTool (see the MCP package's `screen`)
|
|
56
|
+
rules: (ctx) => rulesForTenant(ctx.actor?.tenantRef), // more rules, resolved per scan
|
|
57
|
+
onEvent: (event) => audit.write(event), // hits carry fingerprints, never values
|
|
58
|
+
fingerprint: (value) => hmac(value),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
AgentModule.forRoot({
|
|
62
|
+
inputProcessors: [guardrails.input],
|
|
63
|
+
outputProcessors: [guardrails.output],
|
|
64
|
+
// …
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Where each stage runs, and what each action does there:
|
|
69
|
+
|
|
70
|
+
| Stage | Seen by | `redact` | `block` / `approve` |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `llm_request` — system, messages, earlier tool-call arguments | `guardrails.input`, every step | placeholders, restorable | the run fails: `ProcessorFailedError` whose `cause` is a `GuardrailBlockedError` |
|
|
73
|
+
| `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 |
|
|
74
|
+
| `llm_response` — each step's answer | `guardrails.output` | one-way placeholders; the thread's own placeholders are then restored | `reject` → `OutputRejectedError` |
|
|
75
|
+
| `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 |
|
|
76
|
+
| `tool_description` | `guardrails.screenTool(tool)` | — | `{ allowed: false, reason }` |
|
|
77
|
+
|
|
78
|
+
- **Streaming stays on.** The output processor declares `incremental` with a 256-character lookback
|
|
79
|
+
(`incremental: { lookbackChars }` widens it, `false` gates the whole answer). One-way placeholders
|
|
80
|
+
are numbered per pass, so a prefix and its extension agree, which is the promise `incremental`
|
|
81
|
+
makes.
|
|
82
|
+
- **Placeholders live per thread** in a `VaultStore` (default `InMemoryVaultStore`, bounded). A run
|
|
83
|
+
resumed in another process finds no vault and its placeholders reach the reader unrestored — the
|
|
84
|
+
values still never reach the model. `Vault.toJSON` / `Vault.fromJSON` back a shared store, which
|
|
85
|
+
then holds the raw values.
|
|
86
|
+
- **`wrapTool(name, handler)`** is the one place a tool's arguments can be rewritten: it restores the
|
|
87
|
+
thread's placeholders (the model only ever saw `[EMAIL_1]`, the tool needs the address) and runs
|
|
88
|
+
the `tool_args` rules on what the tool is about to receive. PII is deliberately not a `tool_args`
|
|
89
|
+
stage of the `pii` shorthand for that reason; secrets are.
|
|
90
|
+
- **Re-sent history is not re-refused.** Content only in earlier turns is redacted one-way instead
|
|
91
|
+
of blocked, and each hit is reported once per turn — every step re-sends the prompt and an
|
|
92
|
+
incremental gate re-reads a growing answer.
|
|
93
|
+
- **A rule** is `{ id, stages, detectors, action, match?: { sources?, tools? }, when?, options? }`.
|
|
94
|
+
`when(ctx)` carries anything scope-like (tenant, roles, model); a `{ kind: 'custom', name, detect }`
|
|
95
|
+
detector carries anything the built-ins do not (NER, a classifier, a moderation endpoint, an LLM
|
|
96
|
+
judge). A detector that throws follows the rule's `failMode` (`open` by default).
|
|
97
|
+
|
|
98
|
+
Standalone, without the loop: `detectPii` (Luhn + card brands, CPF/CNPJ incl. the 2026 alphanumeric
|
|
99
|
+
CNPJ, SSN, IBAN mod 97, phones, IPv4), `detectSecrets` (provider key formats, JWT, PEM, entropy-gated
|
|
100
|
+
generic assignments), `scoreInjection` (EN / PT-BR / ES, chat-template spoofing, hidden Unicode tag
|
|
101
|
+
characters, base64 payloads, markdown exfiltration), `scoreToolText` + `toolText`, `detectRegex`,
|
|
102
|
+
`detectKeywords`, then `scan(rules, ctx, segments, vault)` to combine them. For provider wire formats:
|
|
103
|
+
`requestSlots` / `responseSlots` / `toolResultSlots` (OpenAI chat, Anthropic Messages, MCP results —
|
|
104
|
+
each a text plus a setter that writes back in place), `refuseResponse`, and `StreamGuard`, which
|
|
105
|
+
redacts, restores and blocks an OpenAI or Anthropic SSE stream in windows that never cut through a
|
|
106
|
+
finding or a placeholder.
|
|
107
|
+
|
|
40
108
|
## License
|
|
41
109
|
|
|
42
110
|
MIT © Davide Carvalho
|