@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 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
- - Enforce backend-owned adaptive thresholds and effective Shadow, Warn, or
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
- - Preserve exact `metadata.conversationId` sequence identity and add one event ID.
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 and LangChain.js
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.6.2
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
- internally applied threshold. Direct calls do not throw on malicious verdicts.
117
- The Vercel AI SDK and LangChain.js adapters use `result.threshold` and throw
118
- `FirewallBlockedException` when enforcement is enabled.
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. Responses are matched to their own
124
- promise regardless of completion order:
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. Tenant Firewall config owns
240
- the adaptive threshold schedule. The default backend config is
241
- `base_threshold=0.5`, `target_sequence_fpr=0.01`, and
242
- `max_adaptive_threshold=0.9`, which keeps the current schedule: 1 scoring
243
- opportunity uses `0.5`, 2 use about `0.6661`, 5 use about `0.8328`, and 10 or
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. The backend owns the
247
- applied threshold, which remains available on
248
- `BlockResult.threshold` and `FirewallBlockedException.threshold` as diagnostic
249
- metadata.
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 are `sdk_language`,
348
- `sdk_version`, and `request_id`; batches additionally carry `input_index` for
349
- diagnostics and remain stateless. `governance` is also SDK-controlled whenever
350
- the typed governance option is supplied; it replaces any caller-provided
351
- `metadata.silmaril.governance` value. Exact `metadata.conversationId` is
352
- preserved as the backend sequence identity. No aliases are inspected. If
353
- callers provide `metadata.silmaril`, it must be an object and SDK-reserved keys
354
- are overwritten by the SDK.
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 for one release.
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. The backend owns token-window processing and sequence
369
- ordering. `classifyBatch()` continues to send independent stateless texts as one
370
- batch request.
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 metadata. Hook, tool-name, and metadata arrays must match the number of
395
- texts. Each batch carries SDK metadata per item so the backend can apply
396
- tenant-owned thresholding.
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 for one release.
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. Set `scanOutput: true` to classify model
431
- text, and `scanToolCalls: true` to classify tool-call arguments. Infrastructure
432
- errors and blocking decisions are fail-closed by default and bubble up to the
433
- caller.
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 still throw `FirewallBlockedException` unless shadow mode is
465
- enabled. `PromptBlockedException` continues to work as a deprecated alias for
466
- one release.
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":[]}