@silmaril-security/sdk 0.7.10 → 0.8.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 +23 -0
- package/README.md +60 -11
- package/dist/adapters/deepagents.cjs +37 -8
- package/dist/adapters/deepagents.cjs.map +1 -1
- package/dist/adapters/deepagents.d.cts +1 -3
- package/dist/adapters/deepagents.d.ts +1 -3
- package/dist/adapters/deepagents.js +37 -8
- package/dist/adapters/deepagents.js.map +1 -1
- package/dist/adapters/langchain.cjs +409 -68
- package/dist/adapters/langchain.cjs.map +1 -1
- package/dist/adapters/langchain.d.cts +5 -2
- package/dist/adapters/langchain.d.ts +5 -2
- package/dist/adapters/langchain.js +407 -67
- package/dist/adapters/langchain.js.map +1 -1
- package/dist/adapters/vercel.d.cts +1 -1
- package/dist/adapters/vercel.d.ts +1 -1
- package/dist/{firewall-DqjD6aXD.d.cts → firewall-Bv5y03P0.d.cts} +0 -2
- package/dist/{firewall-DqjD6aXD.d.ts → firewall-Bv5y03P0.d.ts} +0 -2
- package/dist/index.cjs +417 -70
- 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 +417 -70
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
- Preserve native thread defaults and config factories on runnables configured
|
|
6
|
+
before `protectLangChainRunnable()`. Install the protection boundary inside
|
|
7
|
+
existing native bindings so defaults, invocation overrides, callbacks and
|
|
8
|
+
config factories resolve once before conversation identity is captured.
|
|
9
|
+
- Verify pre-bound invoke, batch and streaming behavior and native override
|
|
10
|
+
precedence in source tests and the built ESM/CJS package release gate.
|
|
11
|
+
|
|
12
|
+
## 0.8.0
|
|
13
|
+
|
|
14
|
+
- Use each invocation’s native framework thread ID for LangChain, LangGraph,
|
|
15
|
+
and Deep Agents conversation correlation, including reusable and concurrent
|
|
16
|
+
agents, tool completion, and protected subagents. Threadless events remain
|
|
17
|
+
independent; run and request IDs are never used as conversation identity.
|
|
18
|
+
- Add `protectLangChainRunnable()` for direct model/tool calls and native
|
|
19
|
+
invocation overrides that upstream callbacks do not expose. Standard invoke,
|
|
20
|
+
batch, stream, config, retry, and listener composition retain protection.
|
|
21
|
+
- **Breaking:** remove adapter `conversationId` / `conversation_id` options.
|
|
22
|
+
Pass only the framework’s native `configurable.thread_id` at invocation time.
|
|
23
|
+
Removed options raise an actionable migration error for JavaScript callers.
|
|
24
|
+
Direct `classify()` metadata APIs are unchanged.
|
|
25
|
+
|
|
3
26
|
## 0.7.10
|
|
4
27
|
|
|
5
28
|
- Require detector counts in single and batch classification responses to be
|
package/README.md
CHANGED
|
@@ -34,10 +34,10 @@ This SDK is distributed as an npm package.
|
|
|
34
34
|
npm install @silmaril-security/sdk
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
After publication, pin version 0.
|
|
37
|
+
After publication, pin version 0.8.1 for reproducible installs:
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
|
-
npm install @silmaril-security/sdk@0.
|
|
40
|
+
npm install @silmaril-security/sdk@0.8.1
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Requires Node 20 or later.
|
|
@@ -594,17 +594,37 @@ Install the optional peer dependencies:
|
|
|
594
594
|
npm install @langchain/core @langchain/openai
|
|
595
595
|
```
|
|
596
596
|
|
|
597
|
-
|
|
597
|
+
Wrap a model or tool once, then reuse it across native threads:
|
|
598
598
|
|
|
599
599
|
```ts
|
|
600
600
|
import { ChatOpenAI } from "@langchain/openai";
|
|
601
601
|
import { Firewall } from "@silmaril-security/sdk";
|
|
602
|
+
import { protectLangChainRunnable } from "@silmaril-security/sdk/adapters/langchain";
|
|
602
603
|
|
|
603
604
|
const fw = new Firewall({ apiKey, apiUrl });
|
|
604
|
-
const
|
|
605
|
+
const model = await protectLangChainRunnable(fw, new ChatOpenAI());
|
|
606
|
+
|
|
607
|
+
await model.invoke("Hello", { configurable: { thread_id: "conversation-a" } });
|
|
608
|
+
await model.invoke("Continue", { configurable: { thread_id: "conversation-a" } });
|
|
609
|
+
await model.invoke("A different conversation", { configurable: { thread_id: "conversation-b" } });
|
|
610
|
+
```
|
|
605
611
|
|
|
606
|
-
|
|
607
|
-
|
|
612
|
+
The wrapper captures the actual native config for each invocation, including
|
|
613
|
+
preconfigured runnable defaults, per-item batch configs, streams, and child calls
|
|
614
|
+
with their own thread override. Existing native config factories run once before
|
|
615
|
+
identity is captured. Caller callbacks are preserved. Bind model tools before wrapping; this generic
|
|
616
|
+
runnable wrapper does not expose model-specific methods such as `bindTools()`.
|
|
617
|
+
Use the protected constructor below for Deep Agents.
|
|
618
|
+
|
|
619
|
+
For a graph already exposing native thread metadata, callback-only integration
|
|
620
|
+
remains available:
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
const handler = await fw.asLangChainHandler();
|
|
624
|
+
await graph.invoke(input, {
|
|
625
|
+
configurable: { thread_id: "conversation-a" },
|
|
626
|
+
callbacks: [handler],
|
|
627
|
+
});
|
|
608
628
|
```
|
|
609
629
|
|
|
610
630
|
The LangChain handler is fail-open by default: infrastructure errors are logged
|
|
@@ -615,9 +635,18 @@ is Block; Shadow and Warn preserve the host flow.
|
|
|
615
635
|
Model-start, tool-start, and tool-end hooks are enabled by default; retriever
|
|
616
636
|
and model-end (`LLM_END`) hooks remain opt-in. The LangChain run ID is sent as
|
|
617
637
|
`metadata.langgraph.run_id`; every classification gets a distinct
|
|
618
|
-
`metadata.silmaril.request_id`.
|
|
619
|
-
|
|
620
|
-
and
|
|
638
|
+
`metadata.silmaril.request_id`. The current native thread ID is sent as
|
|
639
|
+
`metadata.conversationId` without a separate Silmaril conversation option.
|
|
640
|
+
Root and nested graph callbacks retain their captured identity through end/error
|
|
641
|
+
events. Callback-only hooks cannot recover a direct-root or child invocation’s
|
|
642
|
+
native config when the framework hides it from callbacks; those events omit
|
|
643
|
+
conversation identity rather than guessing from a parent’s ambient context.
|
|
644
|
+
Use `protectLangChainRunnable()` when those boundaries need correlation.
|
|
645
|
+
Callback blocks throw and end that graph execution. Some upstream streaming
|
|
646
|
+
cancellations emit no terminal callback. Callback-only bookkeeping is bounded
|
|
647
|
+
at 4,096 outstanding runs and retains no prompt/output/token payloads; evicted
|
|
648
|
+
unresolved completions omit identity. Prefer the runnable wrapper, which cleans
|
|
649
|
+
its invocation handler when a stream is closed early.
|
|
621
650
|
|
|
622
651
|
`asLangChainHandler()` is async because it lazy-loads `@langchain/core` so core
|
|
623
652
|
users do not pay for it. The root package intentionally exposes a structural
|
|
@@ -640,8 +669,11 @@ import { createProtectedDeepAgent } from "@silmaril-security/sdk/adapters/deepag
|
|
|
640
669
|
const agent = createProtectedDeepAgent(fw, {
|
|
641
670
|
model, tools,
|
|
642
671
|
subagents: [{ name: "research", description: "Research safely" }],
|
|
643
|
-
silmaril: { conversationId },
|
|
644
672
|
});
|
|
673
|
+
await agent.invoke(
|
|
674
|
+
{ messages: [{ role: "user", content: "Research Redis Streams versus Kafka." }] },
|
|
675
|
+
{ configurable: { thread_id: "conversation-a" } },
|
|
676
|
+
);
|
|
645
677
|
```
|
|
646
678
|
|
|
647
679
|
The constructor installs checks on the root, general-purpose, and declarative
|
|
@@ -665,6 +697,23 @@ Classification errors allow model and tool execution by default; set
|
|
|
665
697
|
`silmaril: { failOpen: false }` to require a successful classification. Already
|
|
666
698
|
emitted streaming text cannot be recalled.
|
|
667
699
|
|
|
700
|
+
## Migrating to 0.8.1
|
|
701
|
+
|
|
702
|
+
Remove `conversationId` / `conversation_id` from LangChain handler options and
|
|
703
|
+
Deep Agents `silmaril` middleware options. These removed options raise a
|
|
704
|
+
`TypeError` with migration guidance, including when supplied by JavaScript.
|
|
705
|
+
Use the framework’s normal `configurable.thread_id` on `invoke()`, `batch()`,
|
|
706
|
+
or `stream()`; keep it stable for turns in the same conversation and use a new
|
|
707
|
+
native thread for a different conversation. A single protected agent or runnable
|
|
708
|
+
can serve many threads, including overlapping executions.
|
|
709
|
+
|
|
710
|
+
Without a valid native thread ID, events are classified independently and
|
|
711
|
+
`metadata.conversationId` is omitted. No run ID, request ID, or random UUID is
|
|
712
|
+
substituted. Subagents inheriting a parent native thread share its conversation;
|
|
713
|
+
separately executed graphs use their own native thread. Agent names and
|
|
714
|
+
checkpoint namespaces do not change the conversation ID. The direct
|
|
715
|
+
`classify()` API still accepts explicit `metadata.conversationId`.
|
|
716
|
+
|
|
668
717
|
## Retries
|
|
669
718
|
|
|
670
719
|
HTTP 429 responses are retried with exponential backoff capped at 30s, up to 5
|
|
@@ -707,6 +756,6 @@ IDs must be non-empty; MCP tools also require their canonical server parent ID.
|
|
|
707
756
|
The compatibility metadata copy remains attribution only. Caller agent metadata
|
|
708
757
|
does not supply an authenticated principal.
|
|
709
758
|
|
|
710
|
-
This source prepares version 0.
|
|
759
|
+
This source prepares version 0.8.1. Its registry install command becomes
|
|
711
760
|
available after the authorized main-branch release workflow publishes the package.
|
|
712
761
|
Confirm registry publication before installing this version.
|
|
@@ -29,6 +29,7 @@ __export(deepagents_exports, {
|
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(deepagents_exports);
|
|
31
31
|
var import_messages = require("@langchain/core/messages");
|
|
32
|
+
var import_runnables = require("@langchain/core/runnables");
|
|
32
33
|
var import_langgraph = require("@langchain/langgraph");
|
|
33
34
|
var import_deepagents = require("deepagents");
|
|
34
35
|
var import_langchain = require("langchain");
|
|
@@ -109,6 +110,29 @@ function validateMode(value) {
|
|
|
109
110
|
throw new Error("Firewall: mode must be shadow, warn, or block");
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
// src/adapters/thread-identity.ts
|
|
114
|
+
function record(value) {
|
|
115
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
116
|
+
}
|
|
117
|
+
function validThreadId(value) {
|
|
118
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
119
|
+
}
|
|
120
|
+
function nativeThreadId(runtime, metadata, inherited, currentConfig) {
|
|
121
|
+
const explicit = record(record(runtime)?.configurable);
|
|
122
|
+
const current = record(record(currentConfig)?.configurable);
|
|
123
|
+
for (const candidate of [explicit, current, record(metadata)]) {
|
|
124
|
+
if (candidate && Object.prototype.hasOwnProperty.call(candidate, "thread_id")) {
|
|
125
|
+
return validThreadId(candidate.thread_id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return inherited;
|
|
129
|
+
}
|
|
130
|
+
function rejectConversationOptions(options) {
|
|
131
|
+
if ("conversationId" in options || "conversation_id" in options) {
|
|
132
|
+
throw new TypeError("conversationId/conversation_id was removed. Pass the native configurable.thread_id when invoking the agent or runnable instead.");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
// src/adapters/deepagents.ts
|
|
113
137
|
var SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
|
|
114
138
|
var SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
|
|
@@ -148,19 +172,20 @@ function stripAllowedMarker(result) {
|
|
|
148
172
|
return result;
|
|
149
173
|
}
|
|
150
174
|
function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
175
|
+
rejectConversationOptions(options);
|
|
151
176
|
const maxBlockedAttempts = options.maxBlockedAttempts ?? 3;
|
|
152
177
|
if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {
|
|
153
178
|
throw new Error("maxBlockedAttempts must be a positive integer");
|
|
154
179
|
}
|
|
155
180
|
const effectiveMode = validateMode(options.mode) ?? firewall.mode;
|
|
156
|
-
const classify = async (text, hook, toolName) => {
|
|
181
|
+
const classify = async (text, hook, conversationId, toolName) => {
|
|
157
182
|
if (!text.trim()) return { enforce: false, mode: effectiveMode };
|
|
158
183
|
try {
|
|
159
184
|
const result = await firewall.classify(text, {
|
|
160
185
|
hook,
|
|
161
186
|
...toolName === void 0 ? {} : { toolName },
|
|
162
187
|
...effectiveMode === void 0 ? {} : { mode: effectiveMode },
|
|
163
|
-
...
|
|
188
|
+
...conversationId === void 0 ? {} : { metadata: { conversationId } }
|
|
164
189
|
});
|
|
165
190
|
const mode = effectiveMode ?? result.mode ?? "block";
|
|
166
191
|
const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block";
|
|
@@ -180,6 +205,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
180
205
|
return (0, import_langchain.createMiddleware)({
|
|
181
206
|
name: "SilmarilDeepAgentsMiddleware",
|
|
182
207
|
wrapModelCall: async (request, handler) => {
|
|
208
|
+
const conversationId = nativeThreadId(request.runtime, void 0, void 0, (0, import_runnables.ensureConfig)());
|
|
183
209
|
const history = request.state.messages;
|
|
184
210
|
let lastUserIndex = -1;
|
|
185
211
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
@@ -192,22 +218,23 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
192
218
|
(message) => import_messages.ToolMessage.isInstance(message) && message.content === SAFE_TOOL_MESSAGE && message.additional_kwargs?.silmarilBlocked === BLOCKED_TOOL_MARKER
|
|
193
219
|
).length;
|
|
194
220
|
const latestUser = [...request.messages].reverse().find(import_messages.HumanMessage.isInstance);
|
|
195
|
-
const inputDecision = latestUser ? await classify(textOf(latestUser), HookLabel.USER_INPUT) : void 0;
|
|
221
|
+
const inputDecision = latestUser ? await classify(textOf(latestUser), HookLabel.USER_INPUT, conversationId) : void 0;
|
|
196
222
|
if (inputDecision?.enforce) return new import_messages.AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
197
223
|
const capMode = effectiveMode ?? inputDecision?.mode ?? "block";
|
|
198
224
|
if (blockedCount >= maxBlockedAttempts && capMode === "block") {
|
|
199
225
|
return new import_messages.AIMessage(SAFE_FINAL_MESSAGE);
|
|
200
226
|
}
|
|
201
227
|
const response = await handler(request);
|
|
202
|
-
if (response instanceof import_langgraph.Command && (await classify(textOfStructuredValue(response.update), HookLabel.LLM_OUTPUT)).enforce) {
|
|
228
|
+
if (response instanceof import_langgraph.Command && (await classify(textOfStructuredValue(response.update), HookLabel.LLM_OUTPUT, conversationId)).enforce) {
|
|
203
229
|
return new import_messages.AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
204
230
|
}
|
|
205
|
-
if (import_messages.AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT)).enforce) {
|
|
231
|
+
if (import_messages.AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT, conversationId)).enforce) {
|
|
206
232
|
return new import_messages.AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
207
233
|
}
|
|
208
234
|
return response;
|
|
209
235
|
},
|
|
210
236
|
wrapToolCall: async (request, handler) => {
|
|
237
|
+
const conversationId = nativeThreadId(request.runtime, void 0, void 0, (0, import_runnables.ensureConfig)());
|
|
211
238
|
const safe = () => {
|
|
212
239
|
const message = new import_messages.ToolMessage({
|
|
213
240
|
content: SAFE_TOOL_MESSAGE,
|
|
@@ -218,7 +245,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
218
245
|
issuedBlockedMessages.add(message);
|
|
219
246
|
return message;
|
|
220
247
|
};
|
|
221
|
-
if ((await classify(JSON.stringify(request.toolCall.args ?? {}), HookLabel.TOOL_CALL, request.toolCall.name)).enforce) {
|
|
248
|
+
if ((await classify(JSON.stringify(request.toolCall.args ?? {}), HookLabel.TOOL_CALL, conversationId, request.toolCall.name)).enforce) {
|
|
222
249
|
return safe();
|
|
223
250
|
}
|
|
224
251
|
const result = await handler(request);
|
|
@@ -226,10 +253,10 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
226
253
|
try {
|
|
227
254
|
resultText = import_messages.ToolMessage.isInstance(result) ? textOf(result) : result instanceof import_langgraph.Command ? textOfStructuredValue(result.update) : String(result);
|
|
228
255
|
} catch {
|
|
229
|
-
const decision = await classify("[uninspectable tool result]", HookLabel.TOOL_RESPONSE, request.toolCall.name);
|
|
256
|
+
const decision = await classify("[uninspectable tool result]", HookLabel.TOOL_RESPONSE, conversationId, request.toolCall.name);
|
|
230
257
|
return decision.mode === "block" ? safe() : stripAllowedMarker(result);
|
|
231
258
|
}
|
|
232
|
-
if ((await classify(resultText, HookLabel.TOOL_RESPONSE, request.toolCall.name)).enforce) {
|
|
259
|
+
if ((await classify(resultText, HookLabel.TOOL_RESPONSE, conversationId, request.toolCall.name)).enforce) {
|
|
233
260
|
return safe();
|
|
234
261
|
}
|
|
235
262
|
return stripAllowedMarker(result);
|
|
@@ -237,6 +264,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
237
264
|
});
|
|
238
265
|
}
|
|
239
266
|
function createProtectedCompiledSubagent(firewall, options) {
|
|
267
|
+
rejectConversationOptions(options);
|
|
240
268
|
const { name, description, silmaril, middleware = [], ...agentOptions } = options;
|
|
241
269
|
const runnable = (0, import_langchain.createAgent)({
|
|
242
270
|
...agentOptions,
|
|
@@ -246,6 +274,7 @@ function createProtectedCompiledSubagent(firewall, options) {
|
|
|
246
274
|
return { name, description, runnable };
|
|
247
275
|
}
|
|
248
276
|
function createProtectedDeepAgent(firewall, options = {}) {
|
|
277
|
+
rejectConversationOptions(options);
|
|
249
278
|
const { silmaril, subagents = [], protectedCompiledSubagents = [], middleware = [], ...agentOptions } = options;
|
|
250
279
|
const protectedSubagents = subagents.map((subagent) => {
|
|
251
280
|
if ("runnable" in subagent || "graphId" in subagent) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/deepagents.ts","../../src/exceptions.ts","../../src/hooks.ts","../../src/modes.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 { isReviewCompatibilityError } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport { validateMode } from \"../modes.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 = validateMode(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 (isReviewCompatibilityError(error)) throw 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 const capMode = effectiveMode ?? inputDecision?.mode ?? \"block\";\n // Only genuine prior Block markers count, and the terminal cap requires current effective Block mode.\n if (blockedCount >= maxBlockedAttempts && capMode === \"block\") {\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\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult, ReviewLevel } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n result?: BlockResult;\n }): string {\n const truncated =\n params.promptText.length > MAX_PROMPT_DISPLAY_LEN\n ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...`\n : params.promptText;\n if (\n params.result?.prediction !== \"MALICIOUS\" &&\n params.result?.governance?.action === \"block\"\n ) {\n return `Request blocked by Silmaril Firewall governance policy: '${truncated}'`;\n }\n return (\n `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\nconst REVIEW_COMPATIBILITY_ERROR_BRAND = Symbol.for(\n \"@silmaril-security/sdk/ReviewCompatibilityError\",\n);\n\nexport class ReviewCompatibilityError extends Error {\n readonly requestedReview: ReviewLevel;\n readonly acknowledgedReview: unknown;\n\n constructor(requestedReview: ReviewLevel, acknowledgedReview: unknown) {\n const detail = acknowledgedReview === undefined\n ? \"the response omitted its acknowledgement\"\n : \"the response acknowledgement did not match\";\n super(`Silmaril review compatibility error: ${detail} for requested ${requestedReview} review`);\n this.name = \"ReviewCompatibilityError\";\n this.requestedReview = requestedReview;\n this.acknowledgedReview = acknowledgedReview;\n Object.defineProperty(this, REVIEW_COMPATIBILITY_ERROR_BRAND, {\n value: true,\n enumerable: false,\n configurable: false,\n writable: false,\n });\n Object.setPrototypeOf(this, ReviewCompatibilityError.prototype);\n }\n}\n\nexport function isReviewCompatibilityError(error: unknown): error is ReviewCompatibilityError {\n if ((typeof error !== \"object\" && typeof error !== \"function\") || error === null) {\n return false;\n }\n try {\n return Object.getOwnPropertyDescriptor(\n error,\n REVIEW_COMPATIBILITY_ERROR_BRAND,\n )?.value === true;\n } catch {\n return false;\n }\n}\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n readonly details: MalformedInputDetails | undefined;\n readonly error: string | undefined;\n readonly apiMessage: string | undefined;\n\n constructor(params: {\n status: number;\n statusText: string;\n body: string;\n details?: MalformedInputDetails;\n error?: string;\n apiMessage?: string;\n }) {\n const statusText = params.statusText ? ` ${params.statusText}` : \"\";\n super(`Silmaril API error ${params.status}${statusText}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n const parsed = parseErrorBody(params.body);\n this.details = params.details ?? parsed.details;\n this.error = params.error ?? parsed.error;\n this.apiMessage = params.apiMessage ?? parsed.apiMessage;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\n }\n}\n\nfunction parseErrorBody(body: string): {\n details: MalformedInputDetails | undefined;\n error: string | undefined;\n apiMessage: string | undefined;\n} {\n try {\n const parsed = JSON.parse(body) as unknown;\n if (!parsed || typeof parsed !== \"object\") {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n const data = parsed as Record<string, unknown>;\n return {\n details:\n data.details && typeof data.details === \"object\"\n ? (data.details as MalformedInputDetails)\n : undefined,\n error: typeof data.error === \"string\" ? data.error : undefined,\n apiMessage: typeof data.message === \"string\" ? data.message : undefined,\n };\n } catch {\n return { details: undefined, error: undefined, apiMessage: undefined };\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","import type { FirewallMode } from \"./types.js\";\n\n/** Validate JavaScript/configuration inputs before any fail-open adapter path. */\nexport function validateMode(value: unknown): FirewallMode | undefined {\n if (value === undefined || value === \"shadow\" || value === \"warn\" || value === \"block\") {\n return value;\n }\n throw new Error(\"Firewall: mode must be shadow, warn, or block\");\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/deepagents.ts","../../src/exceptions.ts","../../src/hooks.ts","../../src/modes.ts","../../src/adapters/thread-identity.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 { ensureConfig } from \"@langchain/core/runnables\";\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 { isReviewCompatibilityError } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport { validateMode } from \"../modes.js\";\nimport type { ClassifyEvent, FirewallMode } from \"../types.js\";\n\nimport { nativeThreadId, rejectConversationOptions } from \"./thread-identity.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 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 rejectConversationOptions(options);\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 = validateMode(options.mode) ?? firewall.mode;\n const classify = async (text: string, hook: HookLabel, conversationId: string | undefined, 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 ...(conversationId === undefined ? {} : { metadata: { 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 (isReviewCompatibilityError(error)) throw 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 conversationId = nativeThreadId(request.runtime, undefined, undefined, ensureConfig());\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, conversationId)\n : undefined;\n if (inputDecision?.enforce) return new AIMessage(SAFE_OUTPUT_MESSAGE);\n const capMode = effectiveMode ?? inputDecision?.mode ?? \"block\";\n // Only genuine prior Block markers count, and the terminal cap requires current effective Block mode.\n if (blockedCount >= maxBlockedAttempts && capMode === \"block\") {\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, conversationId)).enforce) {\n return new AIMessage(SAFE_OUTPUT_MESSAGE);\n }\n if (AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT, conversationId)).enforce) {\n return new AIMessage(SAFE_OUTPUT_MESSAGE);\n }\n return response;\n },\n wrapToolCall: async (request, handler) => {\n const conversationId = nativeThreadId(request.runtime, undefined, undefined, ensureConfig());\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, conversationId, 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, conversationId, request.toolCall.name);\n return decision.mode === \"block\" ? safe() : stripAllowedMarker(result);\n }\n if ((await classify(resultText, HookLabel.TOOL_RESPONSE, conversationId, 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 rejectConversationOptions(options);\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 rejectConversationOptions(options);\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\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult, ReviewLevel } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n result?: BlockResult;\n }): string {\n const truncated =\n params.promptText.length > MAX_PROMPT_DISPLAY_LEN\n ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...`\n : params.promptText;\n if (\n params.result?.prediction !== \"MALICIOUS\" &&\n params.result?.governance?.action === \"block\"\n ) {\n return `Request blocked by Silmaril Firewall governance policy: '${truncated}'`;\n }\n return (\n `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\nconst REVIEW_COMPATIBILITY_ERROR_BRAND = Symbol.for(\n \"@silmaril-security/sdk/ReviewCompatibilityError\",\n);\n\nexport class ReviewCompatibilityError extends Error {\n readonly requestedReview: ReviewLevel;\n readonly acknowledgedReview: unknown;\n\n constructor(requestedReview: ReviewLevel, acknowledgedReview: unknown) {\n const detail = acknowledgedReview === undefined\n ? \"the response omitted its acknowledgement\"\n : \"the response acknowledgement did not match\";\n super(`Silmaril review compatibility error: ${detail} for requested ${requestedReview} review`);\n this.name = \"ReviewCompatibilityError\";\n this.requestedReview = requestedReview;\n this.acknowledgedReview = acknowledgedReview;\n Object.defineProperty(this, REVIEW_COMPATIBILITY_ERROR_BRAND, {\n value: true,\n enumerable: false,\n configurable: false,\n writable: false,\n });\n Object.setPrototypeOf(this, ReviewCompatibilityError.prototype);\n }\n}\n\nexport function isReviewCompatibilityError(error: unknown): error is ReviewCompatibilityError {\n if ((typeof error !== \"object\" && typeof error !== \"function\") || error === null) {\n return false;\n }\n try {\n return Object.getOwnPropertyDescriptor(\n error,\n REVIEW_COMPATIBILITY_ERROR_BRAND,\n )?.value === true;\n } catch {\n return false;\n }\n}\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n readonly details: MalformedInputDetails | undefined;\n readonly error: string | undefined;\n readonly apiMessage: string | undefined;\n\n constructor(params: {\n status: number;\n statusText: string;\n body: string;\n details?: MalformedInputDetails;\n error?: string;\n apiMessage?: string;\n }) {\n const statusText = params.statusText ? ` ${params.statusText}` : \"\";\n super(`Silmaril API error ${params.status}${statusText}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n const parsed = parseErrorBody(params.body);\n this.details = params.details ?? parsed.details;\n this.error = params.error ?? parsed.error;\n this.apiMessage = params.apiMessage ?? parsed.apiMessage;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\n }\n}\n\nfunction parseErrorBody(body: string): {\n details: MalformedInputDetails | undefined;\n error: string | undefined;\n apiMessage: string | undefined;\n} {\n try {\n const parsed = JSON.parse(body) as unknown;\n if (!parsed || typeof parsed !== \"object\") {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n const data = parsed as Record<string, unknown>;\n return {\n details:\n data.details && typeof data.details === \"object\"\n ? (data.details as MalformedInputDetails)\n : undefined,\n error: typeof data.error === \"string\" ? data.error : undefined,\n apiMessage: typeof data.message === \"string\" ? data.message : undefined,\n };\n } catch {\n return { details: undefined, error: undefined, apiMessage: undefined };\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","import type { FirewallMode } from \"./types.js\";\n\n/** Validate JavaScript/configuration inputs before any fail-open adapter path. */\nexport function validateMode(value: unknown): FirewallMode | undefined {\n if (value === undefined || value === \"shadow\" || value === \"warn\" || value === \"block\") {\n return value;\n }\n throw new Error(\"Firewall: mode must be shadow, warn, or block\");\n}\n","// Copyright (c) 2026 Silmaril Security Inc. All rights reserved.\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown> : undefined;\n}\n\nexport function validThreadId(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value : undefined;\n}\n\n/** Native config is authoritative, including an explicitly invalid/empty ID. */\nexport function nativeThreadId(runtime?: unknown, metadata?: unknown, inherited?: string, currentConfig?: unknown): string | undefined {\n const explicit = record(record(runtime)?.configurable);\n const current = record(record(currentConfig)?.configurable);\n for (const candidate of [explicit, current, record(metadata)]) {\n if (candidate && Object.prototype.hasOwnProperty.call(candidate, \"thread_id\")) {\n return validThreadId(candidate.thread_id);\n }\n }\n return inherited;\n}\n\nexport function rejectConversationOptions(options: object): void {\n if (\"conversationId\" in options || \"conversation_id\" in options) {\n throw new TypeError(\"conversationId/conversation_id was removed. Pass the native configurable.thread_id when invoking the agent or runnable instead.\");\n }\n}\n\ninterface RunIdentity {\n threadId: string | undefined;\n parentRunId: string | undefined;\n}\n\n/** State belongs to callback runs, never to a reusable agent or current user. */\nexport class RunThreadIdentity {\n private readonly runs = new Map<string, RunIdentity>();\n private readonly children = new Map<string, Set<string>>();\n\n constructor(\n private readonly boundIdentity?: { threadId: string | undefined },\n private readonly currentConfig: () => unknown = () => undefined,\n ) {}\n\n start(runId: string, parentRunId?: string, metadata?: unknown): string[] {\n const discarded = this.finish(runId);\n if (this.runs.size >= 4096) {\n const oldestRoot = [...this.runs].find(([, run]) => run.parentRunId === undefined || !this.runs.has(run.parentRunId))?.[0] ?? this.runs.keys().next().value;\n if (oldestRoot !== undefined) discarded.push(...this.finish(oldestRoot));\n }\n const inherited = parentRunId === undefined ? undefined : this.runs.get(parentRunId)?.threadId;\n const exposed = record(metadata);\n // Ordinary callbacks may see a parent's ambient config while a child has a\n // hidden per-call override. Only graph-tagged metadata proves that context.\n const threadId = this.boundIdentity && (parentRunId === undefined || !this.runs.has(parentRunId)) ? this.boundIdentity.threadId\n : exposed && Object.prototype.hasOwnProperty.call(exposed, \"thread_id\")\n ? typeof exposed.langgraph_node === \"string\"\n ? nativeThreadId(undefined, exposed, inherited, this.currentConfig())\n : validThreadId(exposed.thread_id)\n : inherited;\n this.runs.set(runId, { threadId, parentRunId });\n if (parentRunId !== undefined && parentRunId !== runId) {\n const children = this.children.get(parentRunId) ?? new Set<string>();\n children.add(runId);\n this.children.set(parentRunId, children);\n }\n return discarded;\n }\n\n get(runId: string): string | undefined {\n const run = this.runs.get(runId);\n return run ? run.threadId : this.boundIdentity?.threadId;\n }\n\n finish(runId: string): string[] {\n const pending = [runId];\n const seen = new Set<string>();\n while (pending.length) {\n const id = pending.pop()!;\n if (seen.has(id)) continue;\n seen.add(id);\n pending.push(...this.children.get(id) ?? []);\n this.children.delete(id);\n const parent = this.runs.get(id)?.parentRunId;\n this.runs.delete(id);\n if (parent !== undefined) {\n const siblings = this.children.get(parent);\n siblings?.delete(id);\n if (siblings?.size === 0) this.children.delete(parent);\n }\n }\n return [...seen];\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -12,7 +12,7 @@ import * as _langchain_core_utils_types from '@langchain/core/utils/types';
|
|
|
12
12
|
import * as langchain from 'langchain';
|
|
13
13
|
import { CreateAgentParams } from 'langchain';
|
|
14
14
|
import * as _langchain_core_tools from '@langchain/core/tools';
|
|
15
|
-
import { h as FirewallMode, b as ClassifyEvent, e as Firewall } from '../firewall-
|
|
15
|
+
import { h as FirewallMode, b as ClassifyEvent, e as Firewall } from '../firewall-Bv5y03P0.cjs';
|
|
16
16
|
|
|
17
17
|
declare const SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
|
|
18
18
|
declare const SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
|
|
@@ -21,8 +21,6 @@ interface DeepAgentsMiddlewareOptions {
|
|
|
21
21
|
mode?: FirewallMode;
|
|
22
22
|
failOpen?: boolean;
|
|
23
23
|
maxBlockedAttempts?: number;
|
|
24
|
-
/** A caller-supplied sequence identity; never derived from a LangChain run ID. */
|
|
25
|
-
conversationId?: string;
|
|
26
24
|
onClassify?: (event: ClassifyEvent) => void;
|
|
27
25
|
}
|
|
28
26
|
declare function createDeepAgentsMiddleware(firewall: Firewall, options?: DeepAgentsMiddlewareOptions): langchain.AgentMiddleware<undefined, undefined, unknown, readonly (_langchain_core_tools.ClientTool | _langchain_core_tools.ServerTool)[], readonly []>;
|
|
@@ -12,7 +12,7 @@ import * as _langchain_core_utils_types from '@langchain/core/utils/types';
|
|
|
12
12
|
import * as langchain from 'langchain';
|
|
13
13
|
import { CreateAgentParams } from 'langchain';
|
|
14
14
|
import * as _langchain_core_tools from '@langchain/core/tools';
|
|
15
|
-
import { h as FirewallMode, b as ClassifyEvent, e as Firewall } from '../firewall-
|
|
15
|
+
import { h as FirewallMode, b as ClassifyEvent, e as Firewall } from '../firewall-Bv5y03P0.js';
|
|
16
16
|
|
|
17
17
|
declare const SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
|
|
18
18
|
declare const SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
|
|
@@ -21,8 +21,6 @@ interface DeepAgentsMiddlewareOptions {
|
|
|
21
21
|
mode?: FirewallMode;
|
|
22
22
|
failOpen?: boolean;
|
|
23
23
|
maxBlockedAttempts?: number;
|
|
24
|
-
/** A caller-supplied sequence identity; never derived from a LangChain run ID. */
|
|
25
|
-
conversationId?: string;
|
|
26
24
|
onClassify?: (event: ClassifyEvent) => void;
|
|
27
25
|
}
|
|
28
26
|
declare function createDeepAgentsMiddleware(firewall: Firewall, options?: DeepAgentsMiddlewareOptions): langchain.AgentMiddleware<undefined, undefined, unknown, readonly (_langchain_core_tools.ClientTool | _langchain_core_tools.ServerTool)[], readonly []>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/adapters/deepagents.ts
|
|
2
2
|
import { AIMessage, HumanMessage, ToolMessage } from "@langchain/core/messages";
|
|
3
|
+
import { ensureConfig } from "@langchain/core/runnables";
|
|
3
4
|
import { Command } from "@langchain/langgraph";
|
|
4
5
|
import { createDeepAgent, GENERAL_PURPOSE_SUBAGENT } from "deepagents";
|
|
5
6
|
import { createAgent, createMiddleware } from "langchain";
|
|
@@ -80,6 +81,29 @@ function validateMode(value) {
|
|
|
80
81
|
throw new Error("Firewall: mode must be shadow, warn, or block");
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// src/adapters/thread-identity.ts
|
|
85
|
+
function record(value) {
|
|
86
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
87
|
+
}
|
|
88
|
+
function validThreadId(value) {
|
|
89
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
90
|
+
}
|
|
91
|
+
function nativeThreadId(runtime, metadata, inherited, currentConfig) {
|
|
92
|
+
const explicit = record(record(runtime)?.configurable);
|
|
93
|
+
const current = record(record(currentConfig)?.configurable);
|
|
94
|
+
for (const candidate of [explicit, current, record(metadata)]) {
|
|
95
|
+
if (candidate && Object.prototype.hasOwnProperty.call(candidate, "thread_id")) {
|
|
96
|
+
return validThreadId(candidate.thread_id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return inherited;
|
|
100
|
+
}
|
|
101
|
+
function rejectConversationOptions(options) {
|
|
102
|
+
if ("conversationId" in options || "conversation_id" in options) {
|
|
103
|
+
throw new TypeError("conversationId/conversation_id was removed. Pass the native configurable.thread_id when invoking the agent or runnable instead.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
83
107
|
// src/adapters/deepagents.ts
|
|
84
108
|
var SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
|
|
85
109
|
var SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
|
|
@@ -119,19 +143,20 @@ function stripAllowedMarker(result) {
|
|
|
119
143
|
return result;
|
|
120
144
|
}
|
|
121
145
|
function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
146
|
+
rejectConversationOptions(options);
|
|
122
147
|
const maxBlockedAttempts = options.maxBlockedAttempts ?? 3;
|
|
123
148
|
if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {
|
|
124
149
|
throw new Error("maxBlockedAttempts must be a positive integer");
|
|
125
150
|
}
|
|
126
151
|
const effectiveMode = validateMode(options.mode) ?? firewall.mode;
|
|
127
|
-
const classify = async (text, hook, toolName) => {
|
|
152
|
+
const classify = async (text, hook, conversationId, toolName) => {
|
|
128
153
|
if (!text.trim()) return { enforce: false, mode: effectiveMode };
|
|
129
154
|
try {
|
|
130
155
|
const result = await firewall.classify(text, {
|
|
131
156
|
hook,
|
|
132
157
|
...toolName === void 0 ? {} : { toolName },
|
|
133
158
|
...effectiveMode === void 0 ? {} : { mode: effectiveMode },
|
|
134
|
-
...
|
|
159
|
+
...conversationId === void 0 ? {} : { metadata: { conversationId } }
|
|
135
160
|
});
|
|
136
161
|
const mode = effectiveMode ?? result.mode ?? "block";
|
|
137
162
|
const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block";
|
|
@@ -151,6 +176,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
151
176
|
return createMiddleware({
|
|
152
177
|
name: "SilmarilDeepAgentsMiddleware",
|
|
153
178
|
wrapModelCall: async (request, handler) => {
|
|
179
|
+
const conversationId = nativeThreadId(request.runtime, void 0, void 0, ensureConfig());
|
|
154
180
|
const history = request.state.messages;
|
|
155
181
|
let lastUserIndex = -1;
|
|
156
182
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
@@ -163,22 +189,23 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
163
189
|
(message) => ToolMessage.isInstance(message) && message.content === SAFE_TOOL_MESSAGE && message.additional_kwargs?.silmarilBlocked === BLOCKED_TOOL_MARKER
|
|
164
190
|
).length;
|
|
165
191
|
const latestUser = [...request.messages].reverse().find(HumanMessage.isInstance);
|
|
166
|
-
const inputDecision = latestUser ? await classify(textOf(latestUser), HookLabel.USER_INPUT) : void 0;
|
|
192
|
+
const inputDecision = latestUser ? await classify(textOf(latestUser), HookLabel.USER_INPUT, conversationId) : void 0;
|
|
167
193
|
if (inputDecision?.enforce) return new AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
168
194
|
const capMode = effectiveMode ?? inputDecision?.mode ?? "block";
|
|
169
195
|
if (blockedCount >= maxBlockedAttempts && capMode === "block") {
|
|
170
196
|
return new AIMessage(SAFE_FINAL_MESSAGE);
|
|
171
197
|
}
|
|
172
198
|
const response = await handler(request);
|
|
173
|
-
if (response instanceof Command && (await classify(textOfStructuredValue(response.update), HookLabel.LLM_OUTPUT)).enforce) {
|
|
199
|
+
if (response instanceof Command && (await classify(textOfStructuredValue(response.update), HookLabel.LLM_OUTPUT, conversationId)).enforce) {
|
|
174
200
|
return new AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
175
201
|
}
|
|
176
|
-
if (AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT)).enforce) {
|
|
202
|
+
if (AIMessage.isInstance(response) && (await classify(textOf(response), HookLabel.LLM_OUTPUT, conversationId)).enforce) {
|
|
177
203
|
return new AIMessage(SAFE_OUTPUT_MESSAGE);
|
|
178
204
|
}
|
|
179
205
|
return response;
|
|
180
206
|
},
|
|
181
207
|
wrapToolCall: async (request, handler) => {
|
|
208
|
+
const conversationId = nativeThreadId(request.runtime, void 0, void 0, ensureConfig());
|
|
182
209
|
const safe = () => {
|
|
183
210
|
const message = new ToolMessage({
|
|
184
211
|
content: SAFE_TOOL_MESSAGE,
|
|
@@ -189,7 +216,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
189
216
|
issuedBlockedMessages.add(message);
|
|
190
217
|
return message;
|
|
191
218
|
};
|
|
192
|
-
if ((await classify(JSON.stringify(request.toolCall.args ?? {}), HookLabel.TOOL_CALL, request.toolCall.name)).enforce) {
|
|
219
|
+
if ((await classify(JSON.stringify(request.toolCall.args ?? {}), HookLabel.TOOL_CALL, conversationId, request.toolCall.name)).enforce) {
|
|
193
220
|
return safe();
|
|
194
221
|
}
|
|
195
222
|
const result = await handler(request);
|
|
@@ -197,10 +224,10 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
197
224
|
try {
|
|
198
225
|
resultText = ToolMessage.isInstance(result) ? textOf(result) : result instanceof Command ? textOfStructuredValue(result.update) : String(result);
|
|
199
226
|
} catch {
|
|
200
|
-
const decision = await classify("[uninspectable tool result]", HookLabel.TOOL_RESPONSE, request.toolCall.name);
|
|
227
|
+
const decision = await classify("[uninspectable tool result]", HookLabel.TOOL_RESPONSE, conversationId, request.toolCall.name);
|
|
201
228
|
return decision.mode === "block" ? safe() : stripAllowedMarker(result);
|
|
202
229
|
}
|
|
203
|
-
if ((await classify(resultText, HookLabel.TOOL_RESPONSE, request.toolCall.name)).enforce) {
|
|
230
|
+
if ((await classify(resultText, HookLabel.TOOL_RESPONSE, conversationId, request.toolCall.name)).enforce) {
|
|
204
231
|
return safe();
|
|
205
232
|
}
|
|
206
233
|
return stripAllowedMarker(result);
|
|
@@ -208,6 +235,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
|
|
|
208
235
|
});
|
|
209
236
|
}
|
|
210
237
|
function createProtectedCompiledSubagent(firewall, options) {
|
|
238
|
+
rejectConversationOptions(options);
|
|
211
239
|
const { name, description, silmaril, middleware = [], ...agentOptions } = options;
|
|
212
240
|
const runnable = createAgent({
|
|
213
241
|
...agentOptions,
|
|
@@ -217,6 +245,7 @@ function createProtectedCompiledSubagent(firewall, options) {
|
|
|
217
245
|
return { name, description, runnable };
|
|
218
246
|
}
|
|
219
247
|
function createProtectedDeepAgent(firewall, options = {}) {
|
|
248
|
+
rejectConversationOptions(options);
|
|
220
249
|
const { silmaril, subagents = [], protectedCompiledSubagents = [], middleware = [], ...agentOptions } = options;
|
|
221
250
|
const protectedSubagents = subagents.map((subagent) => {
|
|
222
251
|
if ("runnable" in subagent || "graphId" in subagent) {
|