@silmaril-security/sdk 0.6.3 → 0.7.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.
- package/CHANGELOG.md +19 -0
- package/README.md +146 -43
- package/dist/adapters/deepagents.cjs +251 -0
- package/dist/adapters/deepagents.cjs.map +1 -0
- package/dist/adapters/deepagents.d.cts +184 -0
- package/dist/adapters/deepagents.d.ts +184 -0
- package/dist/adapters/deepagents.js +221 -0
- package/dist/adapters/deepagents.js.map +1 -0
- package/dist/adapters/langchain.cjs +10 -3
- package/dist/adapters/langchain.cjs.map +1 -1
- package/dist/adapters/langchain.d.cts +1 -1
- package/dist/adapters/langchain.d.ts +1 -1
- package/dist/adapters/langchain.js +10 -3
- package/dist/adapters/langchain.js.map +1 -1
- package/dist/adapters/vercel.cjs +3 -1
- package/dist/adapters/vercel.cjs.map +1 -1
- package/dist/adapters/vercel.d.cts +1 -1
- package/dist/adapters/vercel.d.ts +1 -1
- package/dist/adapters/vercel.js +3 -1
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/{firewall-DkArls03.d.cts → firewall-CRNFqgMa.d.cts} +2 -0
- package/dist/{firewall-DkArls03.d.ts → firewall-CRNFqgMa.d.ts} +2 -0
- package/dist/index.cjs +19 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/package.json +31 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Silmaril Firewall TypeScript SDK are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.7.1 - 2026-10-04
|
|
6
|
+
|
|
7
|
+
- Reject malformed batch responses and result counts that do not match the
|
|
8
|
+
serialized request, including retried requests whose caller inputs change.
|
|
9
|
+
|
|
10
|
+
## 0.7.0 - 2026-09-30
|
|
11
|
+
|
|
12
|
+
- Add optional Deep Agents middleware and protected constructors for root, general-purpose,
|
|
13
|
+
declarative, and explicitly protected compiled subagents. The compiled graph factory
|
|
14
|
+
records middleware installation and constructors reject unverified graphs.
|
|
15
|
+
- Declare optional peer dependencies for Deep Agents:
|
|
16
|
+
`deepagents@^1.14.1`, `langchain@^1.5.10`, `@langchain/langgraph@^1.4.10`, and
|
|
17
|
+
existing `@langchain/core@>=0.3.0`.
|
|
18
|
+
- Enforce input, tool-call, tool-result, and non-streamed model-output decisions in
|
|
19
|
+
Block mode with safe continuation and a repeated-denial cap (`maxBlockedAttempts`,
|
|
20
|
+
default `3`). Shadow and Warn report decisions without changing content.
|
|
21
|
+
- Enable LangChain tool start/end callbacks by default and send the LangChain run ID
|
|
22
|
+
as correlation metadata alongside a distinct classification request ID per event.
|
|
23
|
+
|
|
5
24
|
## 0.6.3 - 2026-09-24
|
|
6
25
|
|
|
7
26
|
- Add an optional `signal` option to `classify()` and `classifyBatch()`.
|
package/README.md
CHANGED
|
@@ -18,13 +18,13 @@ This SDK provides the low-level TypeScript interface for that workflow:
|
|
|
18
18
|
- Classify user input, tool calls, tool responses, model output, or system
|
|
19
19
|
prompt content.
|
|
20
20
|
- Preserve hook and tool-name context for more accurate decisions.
|
|
21
|
-
-
|
|
21
|
+
- Honor backend threat and governance decisions and effective Shadow, Warn, or
|
|
22
22
|
Block behavior in adapters.
|
|
23
23
|
- Send each complete sanitized event in one request.
|
|
24
|
-
-
|
|
24
|
+
- On individual `classify()` calls, preserve exact `metadata.conversationId` as sequence identity and add one event ID.
|
|
25
25
|
- Retry API rate-limit responses.
|
|
26
|
-
- Optionally attach the firewall to Vercel AI SDK middleware
|
|
27
|
-
callback flows.
|
|
26
|
+
- Optionally attach the firewall to Vercel AI SDK middleware, LangChain.js
|
|
27
|
+
callback flows, and Deep Agents middleware/constructors.
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
@@ -37,7 +37,7 @@ npm install @silmaril-security/sdk
|
|
|
37
37
|
For reproducible installs, pin a tagged release:
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
|
-
npm install @silmaril-security/sdk@0.
|
|
40
|
+
npm install @silmaril-security/sdk@0.7.1
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Requires Node 20 or later.
|
|
@@ -63,6 +63,12 @@ Optional LangChain.js support:
|
|
|
63
63
|
npm install @langchain/core @langchain/openai
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
Optional Deep Agents support (peer ranges shipped with `0.7.0`):
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
npm install "deepagents@^1.14.1" "langchain@^1.5.10" "@langchain/core@>=0.3.0" "@langchain/langgraph@^1.4.10"
|
|
70
|
+
```
|
|
71
|
+
|
|
66
72
|
## Configuration
|
|
67
73
|
|
|
68
74
|
Every `Firewall` client needs two required options:
|
|
@@ -113,15 +119,21 @@ console.log(`tool output: ${toolResult.prediction} ${toolResult.score.toFixed(4)
|
|
|
113
119
|
```
|
|
114
120
|
|
|
115
121
|
`classify()` and `classifyBatch()` return the server's prediction, score, and
|
|
116
|
-
|
|
117
|
-
The Vercel AI SDK and LangChain.js adapters
|
|
118
|
-
`FirewallBlockedException` when
|
|
122
|
+
diagnostic threshold. Direct calls do not throw on a `MALICIOUS` prediction or
|
|
123
|
+
a governance block. The Vercel AI SDK and LangChain.js adapters throw
|
|
124
|
+
`FirewallBlockedException` only when `prediction` is `MALICIOUS` or
|
|
125
|
+
`governance.action` is `block`, and the effective mode is `block`.
|
|
126
|
+
`result.threshold` is backend-returned diagnostic metadata carried on the
|
|
127
|
+
result and the exception. Disabled and observe policy paths can retain the
|
|
128
|
+
compatibility threshold. The adapters do not compare `score` to a local
|
|
129
|
+
threshold.
|
|
119
130
|
|
|
120
131
|
## Concurrency and Cancellation
|
|
121
132
|
|
|
122
133
|
A `Firewall` instance holds no per-request state, so one client can serve any
|
|
123
|
-
number of concurrent calls in a runtime.
|
|
124
|
-
|
|
134
|
+
number of concurrent calls in a runtime. Concurrent calls are appropriate for
|
|
135
|
+
independent events or different conversations. Responses are matched to their
|
|
136
|
+
own promise regardless of completion order:
|
|
125
137
|
|
|
126
138
|
```ts
|
|
127
139
|
const [userResult, toolResult] = await Promise.all([
|
|
@@ -130,6 +142,10 @@ const [userResult, toolResult] = await Promise.all([
|
|
|
130
142
|
]);
|
|
131
143
|
```
|
|
132
144
|
|
|
145
|
+
Events that belong to one conversation are not independent. Send them with
|
|
146
|
+
ordered individual `classify()` calls that share `metadata.conversationId`, and
|
|
147
|
+
wait for each call before sending the next event for that conversation.
|
|
148
|
+
|
|
133
149
|
Pass a `signal` to cancel one call without touching its siblings:
|
|
134
150
|
|
|
135
151
|
```ts
|
|
@@ -236,17 +252,16 @@ to each attempt and is combined with any caller-supplied `signal`.
|
|
|
236
252
|
|
|
237
253
|
## Backend Thresholding
|
|
238
254
|
|
|
239
|
-
Customers do not tune score thresholds in the SDK.
|
|
240
|
-
the
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
more are capped at `0.9`.
|
|
255
|
+
Customers do not tune score thresholds in the SDK. The Firewall backend owns
|
|
256
|
+
the threat decision and threshold policy. The current Cascade backend resolves
|
|
257
|
+
decision thresholds from a tenant default or a hook-specific override; it does
|
|
258
|
+
not raise them as text length, token-window count, batch size, or conversation
|
|
259
|
+
length grows.
|
|
245
260
|
|
|
246
|
-
The SDK does not send `threshold` in request payloads.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
261
|
+
The SDK does not send `threshold` in request payloads. `BlockResult.threshold`
|
|
262
|
+
and `FirewallBlockedException.threshold` are backend-returned diagnostic
|
|
263
|
+
metadata. Disabled and observe policy paths can retain the compatibility
|
|
264
|
+
threshold.
|
|
250
265
|
|
|
251
266
|
## Modes
|
|
252
267
|
|
|
@@ -344,30 +359,49 @@ await fw.classify(text, {
|
|
|
344
359
|
```
|
|
345
360
|
|
|
346
361
|
The SDK preserves caller metadata and adds a reserved `metadata.silmaril`
|
|
347
|
-
namespace to every request. SDK-controlled fields
|
|
348
|
-
`sdk_version`, and `request_id`; batches additionally
|
|
349
|
-
|
|
350
|
-
the typed governance option is supplied; it replaces any
|
|
351
|
-
`metadata.silmaril.governance` value.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
362
|
+
namespace to every request, including each batch item. SDK-controlled fields
|
|
363
|
+
are `sdk_language`, `sdk_version`, and `request_id`; batches additionally
|
|
364
|
+
carry `input_index` for diagnostics. `governance` is also SDK-controlled
|
|
365
|
+
whenever the typed governance option is supplied; it replaces any
|
|
366
|
+
caller-provided `metadata.silmaril.governance` value. On individual
|
|
367
|
+
`classify()` calls, exact `metadata.conversationId` is preserved as the
|
|
368
|
+
backend sequence identity. Batch items also preserve per-item metadata,
|
|
369
|
+
including `metadata.conversationId`, but current Cascade treats each input
|
|
370
|
+
independently and neither reads nor updates conversation history. Giving items
|
|
371
|
+
the same `metadata.conversationId` does not connect them into a sequence. For
|
|
372
|
+
conversation-aware checks, send complete events through ordered individual
|
|
373
|
+
`classify()` calls with the same `metadata.conversationId`, and wait for each
|
|
374
|
+
call before sending the next event for that conversation. No aliases are
|
|
375
|
+
inspected. If callers provide `metadata.silmaril`, it must be an object and
|
|
376
|
+
SDK-reserved keys are overwritten by the SDK.
|
|
377
|
+
|
|
378
|
+
The `governance` option on `classify()` and per-item `governance` array on
|
|
379
|
+
`classifyBatch()` send typed agent/resource context. `BlockResult.governance`
|
|
380
|
+
returns the server action, policy version, and optional rule ID. A malicious
|
|
381
|
+
prediction or explicit governance block is blocked in Block mode; legacy
|
|
382
|
+
responses without governance remain valid.
|
|
355
383
|
|
|
356
384
|
## Errors
|
|
357
385
|
|
|
358
386
|
- `SilmarilApiError`: thrown when the firewall API responds with a non-2xx or redirect status. Carries `status`, `statusText`, a 64 KiB-capped `body`, and any parsed malformed-input diagnostics. The default error message omits the body to keep logs clean.
|
|
359
387
|
- `FirewallBlockedException`: thrown by the Vercel AI SDK and LangChain.js adapters when a malicious or governance-block decision has effective Block mode. The message distinguishes governance-policy denials from threat-score denials. Carries `score`, `threshold`, `promptText`, and optional `runId`, `hook`, `toolName`, `toolCallId`, and `result`.
|
|
360
388
|
|
|
361
|
-
`PromptBlockedException` remains as a deprecated alias
|
|
389
|
+
`PromptBlockedException` remains as a deprecated alias of
|
|
390
|
+
`FirewallBlockedException`.
|
|
362
391
|
|
|
363
392
|
All SDK exception types extend `Error` and work with `instanceof`.
|
|
364
393
|
|
|
365
394
|
## Complete events
|
|
366
395
|
|
|
367
396
|
`classify()` sanitizes invalid Unicode surrogate fragments and sends the full
|
|
368
|
-
logical event once.
|
|
369
|
-
|
|
370
|
-
|
|
397
|
+
logical event once. For conversation-aware checks, send those complete events
|
|
398
|
+
through ordered individual `classify()` calls with the same
|
|
399
|
+
`metadata.conversationId`, and wait for each call before sending the next
|
|
400
|
+
event for that conversation. The backend owns token-window processing and
|
|
401
|
+
sequence ordering for those individual calls. `classifyBatch()` sends
|
|
402
|
+
independent texts in one request: each item keeps its metadata, including
|
|
403
|
+
`metadata.conversationId`, but Cascade neither reads nor updates conversation
|
|
404
|
+
history for the batch.
|
|
371
405
|
|
|
372
406
|
## Batch Classification
|
|
373
407
|
|
|
@@ -391,9 +425,36 @@ console.log(`classified ${results.length} items`);
|
|
|
391
425
|
```
|
|
392
426
|
|
|
393
427
|
Batch requests preserve result order and can carry per-item hooks, tool names,
|
|
394
|
-
and
|
|
395
|
-
texts. Each batch
|
|
396
|
-
|
|
428
|
+
metadata, and governance. Hook, tool-name, metadata, and governance arrays must
|
|
429
|
+
match the number of texts. Each batch item preserves caller metadata, including
|
|
430
|
+
`metadata.conversationId`, and carries SDK metadata so the backend can apply
|
|
431
|
+
tenant-owned thresholding. Current Cascade treats each input independently and
|
|
432
|
+
neither reads nor updates conversation history. Giving items the same
|
|
433
|
+
`metadata.conversationId` does not connect them into a sequence.
|
|
434
|
+
|
|
435
|
+
For conversation-aware checks, send complete events through ordered individual
|
|
436
|
+
`classify()` calls with the same `metadata.conversationId`. Wait for each call
|
|
437
|
+
before sending the next event for that conversation:
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
const conversationId = "customer-conversation-123";
|
|
441
|
+
|
|
442
|
+
const first = await fw.classify(firstEvent, {
|
|
443
|
+
hook: HookLabel.USER_INPUT,
|
|
444
|
+
metadata: { conversationId },
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const second = await fw.classify(secondEvent, {
|
|
448
|
+
hook: HookLabel.TOOL_RESPONSE,
|
|
449
|
+
toolName: "read_file",
|
|
450
|
+
metadata: { conversationId },
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Batch responses must contain a `predictions` array with exactly one result per
|
|
455
|
+
sent input. A missing array or mismatched count raises an `Error` before
|
|
456
|
+
results are returned or blocking callbacks run. Retries use the original
|
|
457
|
+
payload snapshot, even if caller-owned inputs change while the request is in flight.
|
|
397
458
|
|
|
398
459
|
## Migration Notes
|
|
399
460
|
|
|
@@ -404,7 +465,7 @@ tenant/backend config, adds SDK reconstruction metadata, renames blocking
|
|
|
404
465
|
exceptions to `FirewallBlockedException`, keeps optional LangChain.js types out
|
|
405
466
|
of the root package declarations, adds typed CJS/ESM export conditions, supports
|
|
406
467
|
Vercel AI SDK v5 and v6. The deprecated
|
|
407
|
-
`PromptBlockedException` alias remains available
|
|
468
|
+
`PromptBlockedException` alias remains available.
|
|
408
469
|
|
|
409
470
|
## Vercel AI SDK Middleware
|
|
410
471
|
|
|
@@ -427,10 +488,13 @@ const { text } = await generateText({ model, prompt: "Hello" });
|
|
|
427
488
|
console.log(text);
|
|
428
489
|
```
|
|
429
490
|
|
|
430
|
-
Middleware scans input by default
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
491
|
+
Middleware scans input by default (`scanInput` defaults to true). It classifies
|
|
492
|
+
that message's tool-result parts as `tool_response` only when the newest prompt
|
|
493
|
+
message is a tool message. Otherwise it classifies the latest user message as
|
|
494
|
+
`user_input` and does not scan earlier tool results. Set `scanOutput: true` to
|
|
495
|
+
classify model text, and `scanToolCalls: true` to classify tool-call arguments
|
|
496
|
+
on generate results. Infrastructure errors and blocking decisions are
|
|
497
|
+
fail-closed by default and bubble up to the caller.
|
|
434
498
|
|
|
435
499
|
When `scanOutput: true` is combined with streaming, output is classified in the
|
|
436
500
|
stream's `flush` after all deltas have been emitted, so blocking is advisory:
|
|
@@ -461,9 +525,15 @@ await model.invoke("Hello");
|
|
|
461
525
|
|
|
462
526
|
The LangChain handler is fail-open by default: infrastructure errors are logged
|
|
463
527
|
and the LLM call proceeds. Set `failOpen: false` to make API errors bubble up.
|
|
464
|
-
Blocking decisions
|
|
465
|
-
|
|
466
|
-
|
|
528
|
+
Blocking decisions throw `FirewallBlockedException` only when the effective mode
|
|
529
|
+
is Block; Shadow and Warn preserve the host flow.
|
|
530
|
+
`PromptBlockedException` continues to work as a deprecated alias.
|
|
531
|
+
Model-start, tool-start, and tool-end hooks are enabled by default; retriever
|
|
532
|
+
and model-end (`LLM_END`) hooks remain opt-in. The LangChain run ID is sent as
|
|
533
|
+
`metadata.langgraph.run_id`; every classification gets a distinct
|
|
534
|
+
`metadata.silmaril.request_id`. Supply `conversationId` to the handler for
|
|
535
|
+
sequence identity; it sends `metadata.conversationId`. Callback blocks throw
|
|
536
|
+
and end that graph execution.
|
|
467
537
|
|
|
468
538
|
`asLangChainHandler()` is async because it lazy-loads `@langchain/core` so core
|
|
469
539
|
users do not pay for it. The root package intentionally exposes a structural
|
|
@@ -478,6 +548,39 @@ import { createLangChainHandler } from "@silmaril-security/sdk/adapters/langchai
|
|
|
478
548
|
const handler = await createLangChainHandler(fw);
|
|
479
549
|
```
|
|
480
550
|
|
|
551
|
+
## Deep Agents
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
import { createProtectedDeepAgent } from "@silmaril-security/sdk/adapters/deepagents";
|
|
555
|
+
|
|
556
|
+
const agent = createProtectedDeepAgent(fw, {
|
|
557
|
+
model, tools,
|
|
558
|
+
subagents: [{ name: "research", description: "Research safely" }],
|
|
559
|
+
silmaril: { conversationId },
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
The constructor installs checks on the root, general-purpose, and declarative
|
|
564
|
+
subagents. For a compiled subagent, call
|
|
565
|
+
`createProtectedCompiledSubagent(fw, { name: "review", description: "Review",
|
|
566
|
+
model, tools })` and pass its returned spec through
|
|
567
|
+
`protectedCompiledSubagents`. That factory installs middleware before
|
|
568
|
+
compilation. The parent constructor verifies the exact graph and Firewall
|
|
569
|
+
client; it rejects arbitrary compiled runnables. Root custom middleware does
|
|
570
|
+
not automatically reach every subagent. For custom agent construction, import
|
|
571
|
+
`createDeepAgentsMiddleware` from the same adapter subpath.
|
|
572
|
+
|
|
573
|
+
The middleware checks input before model use, tool calls before execution,
|
|
574
|
+
tool results before the next model call, and non-streamed model output before
|
|
575
|
+
the graph consumes it. In Block mode, denied tool interactions become a fixed
|
|
576
|
+
safe `ToolMessage` with the original call ID. The agent can choose an allowed
|
|
577
|
+
alternative; after `maxBlockedAttempts` denials (default `3`), the middleware
|
|
578
|
+
returns a fixed safe final response. Denied model output is replaced. Shadow
|
|
579
|
+
and Warn report decisions through `onClassify` without replacing content.
|
|
580
|
+
Classification errors allow model and tool execution by default; set
|
|
581
|
+
`silmaril: { failOpen: false }` to require a successful classification. Already
|
|
582
|
+
emitted streaming text cannot be recalled.
|
|
583
|
+
|
|
481
584
|
## Retries
|
|
482
585
|
|
|
483
586
|
HTTP 429 responses are retried with exponential backoff capped at 30s, up to 5
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/deepagents.ts
|
|
21
|
+
var deepagents_exports = {};
|
|
22
|
+
__export(deepagents_exports, {
|
|
23
|
+
SAFE_FINAL_MESSAGE: () => SAFE_FINAL_MESSAGE,
|
|
24
|
+
SAFE_OUTPUT_MESSAGE: () => SAFE_OUTPUT_MESSAGE,
|
|
25
|
+
SAFE_TOOL_MESSAGE: () => SAFE_TOOL_MESSAGE,
|
|
26
|
+
createDeepAgentsMiddleware: () => createDeepAgentsMiddleware,
|
|
27
|
+
createProtectedCompiledSubagent: () => createProtectedCompiledSubagent,
|
|
28
|
+
createProtectedDeepAgent: () => createProtectedDeepAgent
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(deepagents_exports);
|
|
31
|
+
var import_messages = require("@langchain/core/messages");
|
|
32
|
+
var import_langgraph = require("@langchain/langgraph");
|
|
33
|
+
var import_deepagents = require("deepagents");
|
|
34
|
+
var import_langchain = require("langchain");
|
|
35
|
+
|
|
36
|
+
// src/hooks.ts
|
|
37
|
+
var HookLabel = {
|
|
38
|
+
USER_INPUT: "user_input",
|
|
39
|
+
SYSTEM_PROMPT: "system_prompt",
|
|
40
|
+
TOOL_CALL: "tool_call",
|
|
41
|
+
TOOL_RESPONSE: "tool_response",
|
|
42
|
+
LLM_OUTPUT: "llm_output",
|
|
43
|
+
UNKNOWN: "unknown"
|
|
44
|
+
};
|
|
45
|
+
var FirewallHook = {
|
|
46
|
+
LLM_START: "on_llm_start",
|
|
47
|
+
CHAT_MODEL_START: "on_chat_model_start",
|
|
48
|
+
TOOL_START: "on_tool_start",
|
|
49
|
+
RETRIEVER_START: "on_retriever_start",
|
|
50
|
+
LLM_END: "on_llm_end",
|
|
51
|
+
TOOL_END: "on_tool_end",
|
|
52
|
+
RETRIEVER_END: "on_retriever_end"
|
|
53
|
+
};
|
|
54
|
+
var DEFAULT_HOOKS = /* @__PURE__ */ new Set([
|
|
55
|
+
FirewallHook.LLM_START,
|
|
56
|
+
FirewallHook.CHAT_MODEL_START,
|
|
57
|
+
FirewallHook.TOOL_START,
|
|
58
|
+
FirewallHook.TOOL_END
|
|
59
|
+
]);
|
|
60
|
+
var INPUT_HOOKS = /* @__PURE__ */ new Set([
|
|
61
|
+
FirewallHook.LLM_START,
|
|
62
|
+
FirewallHook.CHAT_MODEL_START,
|
|
63
|
+
FirewallHook.TOOL_START,
|
|
64
|
+
FirewallHook.RETRIEVER_START
|
|
65
|
+
]);
|
|
66
|
+
var OUTPUT_HOOKS = /* @__PURE__ */ new Set([
|
|
67
|
+
FirewallHook.LLM_END,
|
|
68
|
+
FirewallHook.TOOL_END,
|
|
69
|
+
FirewallHook.RETRIEVER_END
|
|
70
|
+
]);
|
|
71
|
+
var ALL_HOOKS = /* @__PURE__ */ new Set([
|
|
72
|
+
...INPUT_HOOKS,
|
|
73
|
+
...OUTPUT_HOOKS
|
|
74
|
+
]);
|
|
75
|
+
var FIREWALL_HOOK_TO_LABEL = {
|
|
76
|
+
[FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,
|
|
77
|
+
[FirewallHook.LLM_START]: HookLabel.USER_INPUT,
|
|
78
|
+
[FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,
|
|
79
|
+
[FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,
|
|
80
|
+
[FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,
|
|
81
|
+
[FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,
|
|
82
|
+
[FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT
|
|
83
|
+
};
|
|
84
|
+
var FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
|
|
85
|
+
|
|
86
|
+
// src/adapters/deepagents.ts
|
|
87
|
+
var SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
|
|
88
|
+
var SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
|
|
89
|
+
var SAFE_OUTPUT_MESSAGE = "Silmaril Firewall blocked this response.";
|
|
90
|
+
var BLOCKED_TOOL_MARKER = "silmaril-firewall:v1:blocked-tool";
|
|
91
|
+
var issuedBlockedMessages = /* @__PURE__ */ new WeakSet();
|
|
92
|
+
var protectedCompiledGraphs = /* @__PURE__ */ new WeakMap();
|
|
93
|
+
function textOf(message) {
|
|
94
|
+
return typeof message.content === "string" ? message.content : JSON.stringify(message.content);
|
|
95
|
+
}
|
|
96
|
+
function textOfStructuredValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
97
|
+
if (value == null) return "";
|
|
98
|
+
if (typeof value !== "object") return String(value);
|
|
99
|
+
if (seen.has(value)) return "[circular]";
|
|
100
|
+
seen.add(value);
|
|
101
|
+
if (Array.isArray(value)) return value.map((item) => textOfStructuredValue(item, seen)).join("\n");
|
|
102
|
+
return Object.entries(value).map(([key, item]) => `${key}: ${textOfStructuredValue(item, seen)}`).join("\n");
|
|
103
|
+
}
|
|
104
|
+
function stripAllowedMarker(result) {
|
|
105
|
+
const strip = (message) => {
|
|
106
|
+
if (message.additional_kwargs?.silmarilBlocked !== BLOCKED_TOOL_MARKER) return message;
|
|
107
|
+
if (issuedBlockedMessages.has(message)) return message;
|
|
108
|
+
const { silmarilBlocked: _marker, ...additional_kwargs } = message.additional_kwargs;
|
|
109
|
+
return new import_messages.ToolMessage({ ...message, additional_kwargs });
|
|
110
|
+
};
|
|
111
|
+
if (import_messages.ToolMessage.isInstance(result)) return strip(result);
|
|
112
|
+
if (result instanceof import_langgraph.Command) {
|
|
113
|
+
const update = result.update;
|
|
114
|
+
if (update && !Array.isArray(update) && "messages" in update && Array.isArray(update.messages)) {
|
|
115
|
+
const originalMessages = update.messages;
|
|
116
|
+
const messages = originalMessages.map((message) => import_messages.ToolMessage.isInstance(message) ? strip(message) : message);
|
|
117
|
+
if (messages.some((message, index) => message !== originalMessages[index])) {
|
|
118
|
+
return new import_langgraph.Command({ ...result, update: { ...update, messages } });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
125
|
+
const maxBlockedAttempts = options.maxBlockedAttempts ?? 3;
|
|
126
|
+
if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {
|
|
127
|
+
throw new Error("maxBlockedAttempts must be a positive integer");
|
|
128
|
+
}
|
|
129
|
+
const effectiveMode = options.mode ?? firewall.mode;
|
|
130
|
+
const classify = async (text, hook, toolName) => {
|
|
131
|
+
if (!text.trim()) return { enforce: false, mode: effectiveMode };
|
|
132
|
+
try {
|
|
133
|
+
const result = await firewall.classify(text, {
|
|
134
|
+
hook,
|
|
135
|
+
...toolName === void 0 ? {} : { toolName },
|
|
136
|
+
...effectiveMode === void 0 ? {} : { mode: effectiveMode },
|
|
137
|
+
...options.conversationId === void 0 ? {} : { metadata: { conversationId: options.conversationId } }
|
|
138
|
+
});
|
|
139
|
+
const mode = effectiveMode ?? result.mode ?? "block";
|
|
140
|
+
const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block";
|
|
141
|
+
if (options.onClassify) {
|
|
142
|
+
try {
|
|
143
|
+
options.onClassify({ hook, ...toolName === void 0 ? {} : { toolName }, text, result, blocked, mode, shadowMode: mode === "shadow" });
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { enforce: blocked && mode === "block", mode };
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (options.failOpen === false) throw error;
|
|
150
|
+
return { enforce: false, mode: effectiveMode };
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
return (0, import_langchain.createMiddleware)({
|
|
154
|
+
name: "SilmarilDeepAgentsMiddleware",
|
|
155
|
+
wrapModelCall: async (request, handler) => {
|
|
156
|
+
const history = request.state.messages;
|
|
157
|
+
let lastUserIndex = -1;
|
|
158
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
159
|
+
if (import_messages.HumanMessage.isInstance(history[i])) {
|
|
160
|
+
lastUserIndex = i;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const blockedCount = history.slice(lastUserIndex + 1).filter(
|
|
165
|
+
(message) => import_messages.ToolMessage.isInstance(message) && message.content === SAFE_TOOL_MESSAGE && message.additional_kwargs?.silmarilBlocked === BLOCKED_TOOL_MARKER
|
|
166
|
+
).length;
|
|
167
|
+
const latestUser = [...request.messages].reverse().find(import_messages.HumanMessage.isInstance);
|
|
168
|
+
const inputDecision = latestUser ? await classify(textOf(latestUser), HookLabel.USER_INPUT) : void 0;
|
|
169
|
+
if (inputDecision?.enforce) return new import_messages.AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
170
|
+
if (blockedCount >= maxBlockedAttempts) {
|
|
171
|
+
return new import_messages.AIMessage(SAFE_FINAL_MESSAGE);
|
|
172
|
+
}
|
|
173
|
+
const response = await handler(request);
|
|
174
|
+
if (response instanceof import_langgraph.Command && (await classify(textOfStructuredValue(response.update), HookLabel.LLM_OUTPUT)).enforce) {
|
|
175
|
+
return new import_messages.AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
176
|
+
}
|
|
177
|
+
if (import_messages.AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT)).enforce) {
|
|
178
|
+
return new import_messages.AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
179
|
+
}
|
|
180
|
+
return response;
|
|
181
|
+
},
|
|
182
|
+
wrapToolCall: async (request, handler) => {
|
|
183
|
+
const safe = () => {
|
|
184
|
+
const message = new import_messages.ToolMessage({
|
|
185
|
+
content: SAFE_TOOL_MESSAGE,
|
|
186
|
+
tool_call_id: request.toolCall.id ?? "",
|
|
187
|
+
name: request.toolCall.name,
|
|
188
|
+
additional_kwargs: { silmarilBlocked: BLOCKED_TOOL_MARKER }
|
|
189
|
+
});
|
|
190
|
+
issuedBlockedMessages.add(message);
|
|
191
|
+
return message;
|
|
192
|
+
};
|
|
193
|
+
if ((await classify(JSON.stringify(request.toolCall.args ?? {}), HookLabel.TOOL_CALL, request.toolCall.name)).enforce) {
|
|
194
|
+
return safe();
|
|
195
|
+
}
|
|
196
|
+
const result = await handler(request);
|
|
197
|
+
let resultText;
|
|
198
|
+
try {
|
|
199
|
+
resultText = import_messages.ToolMessage.isInstance(result) ? textOf(result) : result instanceof import_langgraph.Command ? textOfStructuredValue(result.update) : String(result);
|
|
200
|
+
} catch {
|
|
201
|
+
const decision = await classify("[uninspectable tool result]", HookLabel.TOOL_RESPONSE, request.toolCall.name);
|
|
202
|
+
return decision.mode === "block" ? safe() : stripAllowedMarker(result);
|
|
203
|
+
}
|
|
204
|
+
if ((await classify(resultText, HookLabel.TOOL_RESPONSE, request.toolCall.name)).enforce) {
|
|
205
|
+
return safe();
|
|
206
|
+
}
|
|
207
|
+
return stripAllowedMarker(result);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function createProtectedCompiledSubagent(firewall, options) {
|
|
212
|
+
const { name, description, silmaril, middleware = [], ...agentOptions } = options;
|
|
213
|
+
const runnable = (0, import_langchain.createAgent)({
|
|
214
|
+
...agentOptions,
|
|
215
|
+
middleware: [...middleware, createDeepAgentsMiddleware(firewall, silmaril)]
|
|
216
|
+
});
|
|
217
|
+
protectedCompiledGraphs.set(runnable, firewall);
|
|
218
|
+
return { name, description, runnable };
|
|
219
|
+
}
|
|
220
|
+
function createProtectedDeepAgent(firewall, options = {}) {
|
|
221
|
+
const { silmaril, subagents = [], protectedCompiledSubagents = [], middleware = [], ...agentOptions } = options;
|
|
222
|
+
const protectedSubagents = subagents.map((subagent) => {
|
|
223
|
+
if ("runnable" in subagent || "graphId" in subagent) {
|
|
224
|
+
throw new Error("Compiled or remote subagents must install Silmaril middleware inside their own graph");
|
|
225
|
+
}
|
|
226
|
+
return { ...subagent, middleware: [...subagent.middleware ?? [], createDeepAgentsMiddleware(firewall, silmaril)] };
|
|
227
|
+
});
|
|
228
|
+
if (!protectedSubagents.some((subagent) => subagent.name === import_deepagents.GENERAL_PURPOSE_SUBAGENT.name)) {
|
|
229
|
+
protectedSubagents.unshift({ ...import_deepagents.GENERAL_PURPOSE_SUBAGENT, middleware: [createDeepAgentsMiddleware(firewall, silmaril)] });
|
|
230
|
+
}
|
|
231
|
+
for (const spec of protectedCompiledSubagents) {
|
|
232
|
+
if (!spec.runnable || protectedCompiledGraphs.get(spec.runnable) !== firewall) {
|
|
233
|
+
throw new Error("Compiled subagents must be built with createProtectedCompiledSubagent for this Firewall client");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return (0, import_deepagents.createDeepAgent)({
|
|
237
|
+
...agentOptions,
|
|
238
|
+
middleware: [...middleware, createDeepAgentsMiddleware(firewall, silmaril)],
|
|
239
|
+
subagents: [...protectedSubagents, ...protectedCompiledSubagents]
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
243
|
+
0 && (module.exports = {
|
|
244
|
+
SAFE_FINAL_MESSAGE,
|
|
245
|
+
SAFE_OUTPUT_MESSAGE,
|
|
246
|
+
SAFE_TOOL_MESSAGE,
|
|
247
|
+
createDeepAgentsMiddleware,
|
|
248
|
+
createProtectedCompiledSubagent,
|
|
249
|
+
createProtectedDeepAgent
|
|
250
|
+
});
|
|
251
|
+
//# sourceMappingURL=deepagents.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/deepagents.ts","../../src/hooks.ts"],"sourcesContent":["// Copyright (c) 2024-2026 Silmaril Security Inc. All rights reserved.\n\n/** Optional Deep Agents integration. Import from @silmaril-security/sdk/adapters/deepagents. */\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport { Command } from \"@langchain/langgraph\";\nimport { createDeepAgent, GENERAL_PURPOSE_SUBAGENT, type AnySubAgent, type CompiledSubAgent } from \"deepagents\";\nimport { createAgent, createMiddleware, type CreateAgentParams } from \"langchain\";\n\nimport type { Firewall } from \"../firewall.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { ClassifyEvent, FirewallMode } from \"../types.js\";\n\nexport const SAFE_TOOL_MESSAGE = \"Silmaril Firewall blocked this tool interaction. Choose a different safe action.\";\nexport const SAFE_FINAL_MESSAGE = \"Silmaril Firewall stopped this request after repeated unsafe actions.\";\nexport const SAFE_OUTPUT_MESSAGE = \"Silmaril Firewall blocked this response.\";\nconst BLOCKED_TOOL_MARKER = \"silmaril-firewall:v1:blocked-tool\";\nconst issuedBlockedMessages = new WeakSet<ToolMessage>();\n\n// A protected graph is identified by the exact runnable returned by our factory.\nconst protectedCompiledGraphs = new WeakMap<object, Firewall>();\n\nexport interface DeepAgentsMiddlewareOptions {\n mode?: FirewallMode;\n failOpen?: boolean;\n maxBlockedAttempts?: number;\n /** A caller-supplied sequence identity; never derived from a LangChain run ID. */\n conversationId?: string;\n onClassify?: (event: ClassifyEvent) => void;\n}\n\nfunction textOf(message: { content: unknown }): string {\n return typeof message.content === \"string\" ? message.content : JSON.stringify(message.content);\n}\n\nfunction textOfStructuredValue(value: unknown, seen = new WeakSet<object>()): string {\n if (value == null) return \"\";\n if (typeof value !== \"object\") return String(value);\n if (seen.has(value)) return \"[circular]\";\n seen.add(value);\n if (Array.isArray(value)) return value.map((item) => textOfStructuredValue(item, seen)).join(\"\\n\");\n return Object.entries(value).map(([key, item]) => `${key}: ${textOfStructuredValue(item, seen)}`).join(\"\\n\");\n}\n\nfunction stripAllowedMarker(result: ToolMessage | Command): ToolMessage | Command {\n const strip = (message: ToolMessage): ToolMessage => {\n if (message.additional_kwargs?.silmarilBlocked !== BLOCKED_TOOL_MARKER) return message;\n if (issuedBlockedMessages.has(message)) return message;\n const { silmarilBlocked: _marker, ...additional_kwargs } = message.additional_kwargs;\n // Tool providers may return frozen messages or metadata. Copy rather\n // than mutating a result owned by the caller.\n return new ToolMessage({ ...message, additional_kwargs });\n };\n if (ToolMessage.isInstance(result)) return strip(result);\n if (result instanceof Command) {\n const update = result.update;\n if (update && !Array.isArray(update) && \"messages\" in update && Array.isArray(update.messages)) {\n const originalMessages = update.messages;\n const messages = originalMessages.map((message) => ToolMessage.isInstance(message) ? strip(message) : message);\n if (messages.some((message, index) => message !== originalMessages[index])) {\n return new Command({ ...result, update: { ...update, messages } });\n }\n }\n }\n return result;\n}\n\nexport function createDeepAgentsMiddleware(firewall: Firewall, options: DeepAgentsMiddlewareOptions = {}) {\n const maxBlockedAttempts = options.maxBlockedAttempts ?? 3;\n if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {\n throw new Error(\"maxBlockedAttempts must be a positive integer\");\n }\n const effectiveMode = options.mode ?? firewall.mode;\n const classify = async (text: string, hook: HookLabel, toolName?: string): Promise<{ enforce: boolean; mode: FirewallMode | undefined }> => {\n if (!text.trim()) return { enforce: false, mode: effectiveMode };\n try {\n const result = await firewall.classify(text, {\n hook,\n ...(toolName === undefined ? {} : { toolName }),\n ...(effectiveMode === undefined ? {} : { mode: effectiveMode }),\n ...(options.conversationId === undefined ? {} : { metadata: { conversationId: options.conversationId } }),\n });\n const mode = effectiveMode ?? result.mode ?? \"block\";\n const blocked = result.prediction === \"MALICIOUS\" || result.governance?.action === \"block\";\n if (options.onClassify) {\n try {\n options.onClassify({ hook, ...(toolName === undefined ? {} : { toolName }), text, result, blocked, mode, shadowMode: mode === \"shadow\" });\n } catch {\n // Observation callbacks do not change enforcement.\n }\n }\n return { enforce: blocked && mode === \"block\", mode };\n } catch (error) {\n if (options.failOpen === false) throw error;\n return { enforce: false, mode: effectiveMode };\n }\n };\n\n return createMiddleware({\n name: \"SilmarilDeepAgentsMiddleware\",\n wrapModelCall: async (request, handler) => {\n const history = request.state.messages;\n let lastUserIndex = -1;\n for (let i = history.length - 1; i >= 0; i--) {\n if (HumanMessage.isInstance(history[i])) {\n lastUserIndex = i;\n break;\n }\n }\n const blockedCount = history.slice(lastUserIndex + 1).filter(\n (message) => ToolMessage.isInstance(message) && message.content === SAFE_TOOL_MESSAGE\n && message.additional_kwargs?.silmarilBlocked === BLOCKED_TOOL_MARKER,\n ).length;\n const latestUser = [...request.messages].reverse().find(HumanMessage.isInstance);\n const inputDecision = latestUser\n ? await classify(textOf(latestUser), HookLabel.USER_INPUT)\n : undefined;\n if (inputDecision?.enforce) return new AIMessage(SAFE_OUTPUT_MESSAGE);\n // The marker is added only after a Block decision, so neither an\n // unrelated input mode nor tool output that quotes the safe text affects the cap.\n if (blockedCount >= maxBlockedAttempts) {\n return new AIMessage(SAFE_FINAL_MESSAGE);\n }\n const response = await handler(request);\n if (response instanceof Command && (await classify(textOfStructuredValue(response.update), HookLabel.LLM_OUTPUT)).enforce) {\n return new AIMessage(SAFE_OUTPUT_MESSAGE);\n }\n if (AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT)).enforce) {\n return new AIMessage(SAFE_OUTPUT_MESSAGE);\n }\n return response;\n },\n wrapToolCall: async (request, handler) => {\n const safe = () => {\n const message = new ToolMessage({\n content: SAFE_TOOL_MESSAGE,\n tool_call_id: request.toolCall.id ?? \"\",\n name: request.toolCall.name,\n additional_kwargs: { silmarilBlocked: BLOCKED_TOOL_MARKER },\n });\n issuedBlockedMessages.add(message);\n return message;\n };\n if ((await classify(JSON.stringify(request.toolCall.args ?? {}), HookLabel.TOOL_CALL, request.toolCall.name)).enforce) {\n return safe();\n }\n const result = await handler(request);\n let resultText: string;\n try {\n resultText = ToolMessage.isInstance(result) ? textOf(result)\n : result instanceof Command ? textOfStructuredValue(result.update) : String(result);\n } catch {\n // Content cannot be inspected, but the backend still resolves the\n // enforcement mode. Observation modes retain the original result.\n const decision = await classify(\"[uninspectable tool result]\", HookLabel.TOOL_RESPONSE, request.toolCall.name);\n return decision.mode === \"block\" ? safe() : stripAllowedMarker(result);\n }\n if ((await classify(resultText, HookLabel.TOOL_RESPONSE, request.toolCall.name)).enforce) {\n return safe();\n }\n return stripAllowedMarker(result);\n },\n });\n}\n\nexport interface ProtectedCompiledSubagentOptions extends Omit<CreateAgentParams, \"middleware\"> {\n name: string;\n description: string;\n middleware?: CreateAgentParams[\"middleware\"];\n silmaril?: DeepAgentsMiddlewareOptions;\n}\n\n/** Compile a subagent with Silmaril middleware and register that exact graph. */\nexport function createProtectedCompiledSubagent(firewall: Firewall, options: ProtectedCompiledSubagentOptions): CompiledSubAgent {\n const { name, description, silmaril, middleware = [], ...agentOptions } = options;\n const runnable = createAgent({\n ...agentOptions,\n middleware: [...middleware, createDeepAgentsMiddleware(firewall, silmaril)] as const,\n });\n protectedCompiledGraphs.set(runnable, firewall);\n return { name, description, runnable };\n}\n\nexport interface ProtectedDeepAgentOptions extends Omit<NonNullable<Parameters<typeof createDeepAgent>[0]>, \"subagents\" | \"middleware\"> {\n subagents?: readonly AnySubAgent[];\n /** Only pass specs returned by createProtectedCompiledSubagent for this client. */\n protectedCompiledSubagents?: readonly CompiledSubAgent[];\n middleware?: NonNullable<Parameters<typeof createDeepAgent>[0]>[\"middleware\"];\n silmaril?: DeepAgentsMiddlewareOptions;\n}\n\n/** Protect root, default general-purpose, and declarative subagents. */\nexport function createProtectedDeepAgent(firewall: Firewall, options: ProtectedDeepAgentOptions = {}) {\n const { silmaril, subagents = [], protectedCompiledSubagents = [], middleware = [], ...agentOptions } = options;\n const protectedSubagents = subagents.map((subagent) => {\n if (\"runnable\" in subagent || \"graphId\" in subagent) {\n throw new Error(\"Compiled or remote subagents must install Silmaril middleware inside their own graph\");\n }\n return { ...subagent, middleware: [...(subagent.middleware ?? []), createDeepAgentsMiddleware(firewall, silmaril)] };\n });\n if (!protectedSubagents.some((subagent) => subagent.name === GENERAL_PURPOSE_SUBAGENT.name)) {\n protectedSubagents.unshift({ ...GENERAL_PURPOSE_SUBAGENT, middleware: [createDeepAgentsMiddleware(firewall, silmaril)] });\n }\n for (const spec of protectedCompiledSubagents) {\n if (!spec.runnable || protectedCompiledGraphs.get(spec.runnable) !== firewall) {\n throw new Error(\"Compiled subagents must be built with createProtectedCompiledSubagent for this Firewall client\");\n }\n }\n return createDeepAgent({\n ...agentOptions,\n middleware: [...middleware, createDeepAgentsMiddleware(firewall, silmaril)],\n subagents: [...protectedSubagents, ...protectedCompiledSubagents],\n });\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport const HookLabel = {\n USER_INPUT: \"user_input\",\n SYSTEM_PROMPT: \"system_prompt\",\n TOOL_CALL: \"tool_call\",\n TOOL_RESPONSE: \"tool_response\",\n LLM_OUTPUT: \"llm_output\",\n UNKNOWN: \"unknown\",\n} as const;\n\nexport type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];\n\nexport const FirewallHook = {\n LLM_START: \"on_llm_start\",\n CHAT_MODEL_START: \"on_chat_model_start\",\n TOOL_START: \"on_tool_start\",\n RETRIEVER_START: \"on_retriever_start\",\n LLM_END: \"on_llm_end\",\n TOOL_END: \"on_tool_end\",\n RETRIEVER_END: \"on_retriever_end\",\n} as const;\n\nexport type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];\n\nexport const DEFAULT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.TOOL_END,\n]);\n\nexport const INPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.RETRIEVER_START,\n]);\n\nexport const OUTPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_END,\n FirewallHook.TOOL_END,\n FirewallHook.RETRIEVER_END,\n]);\n\nexport const ALL_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n ...INPUT_HOOKS,\n ...OUTPUT_HOOKS,\n]);\n\nexport const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>> = {\n [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,\n [FirewallHook.LLM_START]: HookLabel.USER_INPUT,\n [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,\n [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,\n [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT,\n};\n\nconst FIREWALL_HOOK_VALUES: ReadonlySet<string> = new Set(Object.values(FirewallHook));\n\nexport function resolveHooks(\n hooks: Iterable<FirewallHook | string> | undefined,\n): ReadonlySet<FirewallHook> {\n if (hooks === undefined) {\n return DEFAULT_HOOKS;\n }\n const resolved = new Set<FirewallHook>();\n for (const h of hooks) {\n if (!FIREWALL_HOOK_VALUES.has(h)) {\n throw new Error(`Invalid FirewallHook value: ${String(h)}`);\n }\n resolved.add(h as FirewallHook);\n }\n return resolved;\n}\n\nexport function prependHook(text: string, hook: HookLabel | string | undefined | null): string {\n if (hook === undefined || hook === null) {\n return text;\n }\n if (hook === HookLabel.UNKNOWN) {\n return text;\n }\n return `[HOOK:${hook}] ${text}`;\n}\n\nexport function prependToolName(text: string, toolName: string | undefined | null): string {\n if (toolName === undefined || toolName === null) {\n return text;\n }\n return `[TOOL:${toolName}] ${text}`;\n}\n"],"mappings":"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","names":[]}
|