@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 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.7.10 for reproducible installs:
37
+ After publication, pin version 0.8.1 for reproducible installs:
38
38
 
39
39
  ```sh
40
- npm install @silmaril-security/sdk@0.7.10
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
- Create a handler from the same client:
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 handler = await fw.asLangChainHandler();
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
- const model = new ChatOpenAI({ callbacks: [handler] });
607
- await model.invoke("Hello");
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`. Supply `conversationId` to the handler for
619
- sequence identity; it sends `metadata.conversationId`. Callback blocks throw
620
- and end that graph execution.
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.7.10. Its registry install command becomes
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
- ...options.conversationId === void 0 ? {} : { metadata: { conversationId: options.conversationId } }
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,sBAAqD;AACrD,uBAAwB;AACxB,wBAAmG;AACnG,uBAAsE;;;ACyEtE,IAAM,mCAAmC,uBAAO;AAAA,EAC9C;AACF;AAwBO,SAAS,2BAA2B,OAAmD;AAC5F,MAAK,OAAO,UAAU,YAAY,OAAO,UAAU,cAAe,UAAU,MAAM;AAChF,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,OAAO;AAAA,MACZ;AAAA,MACA;AAAA,IACF,GAAG,UAAU;AAAA,EACf,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AClHO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AACX;AAIO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AAAA,EACV,eAAe;AACjB;AAIO,IAAM,gBAA2C,oBAAI,IAAkB;AAAA,EAC5E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,cAAyC,oBAAI,IAAkB;AAAA,EAC1E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,eAA0C,oBAAI,IAAkB;AAAA,EAC3E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,YAAuC,oBAAI,IAAkB;AAAA,EACxE,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAEM,IAAM,yBAAoE;AAAA,EAC/E,CAAC,aAAa,gBAAgB,GAAG,UAAU;AAAA,EAC3C,CAAC,aAAa,SAAS,GAAG,UAAU;AAAA,EACpC,CAAC,aAAa,UAAU,GAAG,UAAU;AAAA,EACrC,CAAC,aAAa,QAAQ,GAAG,UAAU;AAAA,EACnC,CAAC,aAAa,eAAe,GAAG,UAAU;AAAA,EAC1C,CAAC,aAAa,aAAa,GAAG,UAAU;AAAA,EACxC,CAAC,aAAa,OAAO,GAAG,UAAU;AACpC;AAEA,IAAM,uBAA4C,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;;;AC1D9E,SAAS,aAAa,OAA0C;AACrE,MAAI,UAAU,UAAa,UAAU,YAAY,UAAU,UAAU,UAAU,SAAS;AACtF,WAAO;AAAA,EACT;AACA,QAAM,IAAI,MAAM,+CAA+C;AACjE;;;AHMO,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AACnC,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB,oBAAI,QAAqB;AAGvD,IAAM,0BAA0B,oBAAI,QAA0B;AAW9D,SAAS,OAAO,SAAuC;AACrD,SAAO,OAAO,QAAQ,YAAY,WAAW,QAAQ,UAAU,KAAK,UAAU,QAAQ,OAAO;AAC/F;AAEA,SAAS,sBAAsB,OAAgB,OAAO,oBAAI,QAAgB,GAAW;AACnF,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,MAAI,KAAK,IAAI,KAAK,EAAG,QAAO;AAC5B,OAAK,IAAI,KAAK;AACd,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,SAAS,sBAAsB,MAAM,IAAI,CAAC,EAAE,KAAK,IAAI;AACjG,SAAO,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,GAAG,GAAG,KAAK,sBAAsB,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC7G;AAEA,SAAS,mBAAmB,QAAsD;AAChF,QAAM,QAAQ,CAAC,YAAsC;AACnD,QAAI,QAAQ,mBAAmB,oBAAoB,oBAAqB,QAAO;AAC/E,QAAI,sBAAsB,IAAI,OAAO,EAAG,QAAO;AAC/C,UAAM,EAAE,iBAAiB,SAAS,GAAG,kBAAkB,IAAI,QAAQ;AAGnE,WAAO,IAAI,4BAAY,EAAE,GAAG,SAAS,kBAAkB,CAAC;AAAA,EAC1D;AACA,MAAI,4BAAY,WAAW,MAAM,EAAG,QAAO,MAAM,MAAM;AACvD,MAAI,kBAAkB,0BAAS;AAC7B,UAAM,SAAS,OAAO;AACtB,QAAI,UAAU,CAAC,MAAM,QAAQ,MAAM,KAAK,cAAc,UAAU,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAC9F,YAAM,mBAAmB,OAAO;AAChC,YAAM,WAAW,iBAAiB,IAAI,CAAC,YAAY,4BAAY,WAAW,OAAO,IAAI,MAAM,OAAO,IAAI,OAAO;AAC7G,UAAI,SAAS,KAAK,CAAC,SAAS,UAAU,YAAY,iBAAiB,KAAK,CAAC,GAAG;AAC1E,eAAO,IAAI,yBAAQ,EAAE,GAAG,QAAQ,QAAQ,EAAE,GAAG,QAAQ,SAAS,EAAE,CAAC;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,2BAA2B,UAAoB,UAAuC,CAAC,GAAG;AACxG,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,MAAI,CAAC,OAAO,UAAU,kBAAkB,KAAK,qBAAqB,GAAG;AACnE,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,QAAM,gBAAgB,aAAa,QAAQ,IAAI,KAAK,SAAS;AAC7D,QAAM,WAAW,OAAO,MAAc,MAAiB,aAAqF;AAC1I,QAAI,CAAC,KAAK,KAAK,EAAG,QAAO,EAAE,SAAS,OAAO,MAAM,cAAc;AAC/D,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,SAAS,MAAM;AAAA,QAC3C;AAAA,QACA,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,QAC7C,GAAI,kBAAkB,SAAY,CAAC,IAAI,EAAE,MAAM,cAAc;AAAA,QAC7D,GAAI,QAAQ,mBAAmB,SAAY,CAAC,IAAI,EAAE,UAAU,EAAE,gBAAgB,QAAQ,eAAe,EAAE;AAAA,MACzG,CAAC;AACD,YAAM,OAAO,iBAAiB,OAAO,QAAQ;AAC7C,YAAM,UAAU,OAAO,eAAe,eAAe,OAAO,YAAY,WAAW;AACnF,UAAI,QAAQ,YAAY;AACtB,YAAI;AACF,kBAAQ,WAAW,EAAE,MAAM,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS,GAAI,MAAM,QAAQ,SAAS,MAAM,YAAY,SAAS,SAAS,CAAC;AAAA,QAC1I,QAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO,EAAE,SAAS,WAAW,SAAS,SAAS,KAAK;AAAA,IACtD,SAAS,OAAO;AACd,UAAI,2BAA2B,KAAK,EAAG,OAAM;AAC7C,UAAI,QAAQ,aAAa,MAAO,OAAM;AACtC,aAAO,EAAE,SAAS,OAAO,MAAM,cAAc;AAAA,IAC/C;AAAA,EACF;AAEA,aAAO,mCAAiB;AAAA,IACtB,MAAM;AAAA,IACN,eAAe,OAAO,SAAS,YAAY;AACzC,YAAM,UAAU,QAAQ,MAAM;AAC9B,UAAI,gBAAgB;AACpB,eAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,YAAI,6BAAa,WAAW,QAAQ,CAAC,CAAC,GAAG;AACvC,0BAAgB;AAChB;AAAA,QACF;AAAA,MACF;AACA,YAAM,eAAe,QAAQ,MAAM,gBAAgB,CAAC,EAAE;AAAA,QACpD,CAAC,YAAY,4BAAY,WAAW,OAAO,KAAK,QAAQ,YAAY,qBAC/D,QAAQ,mBAAmB,oBAAoB;AAAA,MACtD,EAAE;AACF,YAAM,aAAa,CAAC,GAAG,QAAQ,QAAQ,EAAE,QAAQ,EAAE,KAAK,6BAAa,UAAU;AAC/E,YAAM,gBAAgB,aAClB,MAAM,SAAS,OAAO,UAAU,GAAG,UAAU,UAAU,IACvD;AACJ,UAAI,eAAe,QAAS,QAAO,IAAI,0BAAU,mBAAmB;AACpE,YAAM,UAAU,iBAAiB,eAAe,QAAQ;AAExD,UAAI,gBAAgB,sBAAsB,YAAY,SAAS;AAC7D,eAAO,IAAI,0BAAU,kBAAkB;AAAA,MACzC;AACA,YAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,UAAI,oBAAoB,6BAAY,MAAM,SAAS,sBAAsB,SAAS,MAAM,GAAG,UAAU,UAAU,GAAG,SAAS;AACzH,eAAO,IAAI,0BAAU,mBAAmB;AAAA,MAC1C;AACA,UAAI,0BAAU,WAAW,QAAQ,MAAM,MAAM,SAAS,OAAO,QAAQ,GAAG,UAAU,UAAU,GAAG,SAAS;AACtG,eAAO,IAAI,0BAAU,mBAAmB;AAAA,MAC1C;AACA,aAAO;AAAA,IACT;AAAA,IACA,cAAc,OAAO,SAAS,YAAY;AACxC,YAAM,OAAO,MAAM;AACjB,cAAM,UAAU,IAAI,4BAAY;AAAA,UAC9B,SAAS;AAAA,UACT,cAAc,QAAQ,SAAS,MAAM;AAAA,UACrC,MAAM,QAAQ,SAAS;AAAA,UACvB,mBAAmB,EAAE,iBAAiB,oBAAoB;AAAA,QAC5D,CAAC;AACD,8BAAsB,IAAI,OAAO;AACjC,eAAO;AAAA,MACT;AACA,WAAK,MAAM,SAAS,KAAK,UAAU,QAAQ,SAAS,QAAQ,CAAC,CAAC,GAAG,UAAU,WAAW,QAAQ,SAAS,IAAI,GAAG,SAAS;AACrH,eAAO,KAAK;AAAA,MACd;AACA,YAAM,SAAS,MAAM,QAAQ,OAAO;AACpC,UAAI;AACJ,UAAI;AACF,qBAAa,4BAAY,WAAW,MAAM,IAAI,OAAO,MAAM,IACvD,kBAAkB,2BAAU,sBAAsB,OAAO,MAAM,IAAI,OAAO,MAAM;AAAA,MACtF,QAAQ;AAGN,cAAM,WAAW,MAAM,SAAS,+BAA+B,UAAU,eAAe,QAAQ,SAAS,IAAI;AAC7G,eAAO,SAAS,SAAS,UAAU,KAAK,IAAI,mBAAmB,MAAM;AAAA,MACvE;AACA,WAAK,MAAM,SAAS,YAAY,UAAU,eAAe,QAAQ,SAAS,IAAI,GAAG,SAAS;AACxF,eAAO,KAAK;AAAA,MACd;AACA,aAAO,mBAAmB,MAAM;AAAA,IAClC;AAAA,EACF,CAAC;AACH;AAUO,SAAS,gCAAgC,UAAoB,SAA6D;AAC/H,QAAM,EAAE,MAAM,aAAa,UAAU,aAAa,CAAC,GAAG,GAAG,aAAa,IAAI;AAC1E,QAAM,eAAW,8BAAY;AAAA,IAC3B,GAAG;AAAA,IACH,YAAY,CAAC,GAAG,YAAY,2BAA2B,UAAU,QAAQ,CAAC;AAAA,EAC5E,CAAC;AACD,0BAAwB,IAAI,UAAU,QAAQ;AAC9C,SAAO,EAAE,MAAM,aAAa,SAAS;AACvC;AAWO,SAAS,yBAAyB,UAAoB,UAAqC,CAAC,GAAG;AACpG,QAAM,EAAE,UAAU,YAAY,CAAC,GAAG,6BAA6B,CAAC,GAAG,aAAa,CAAC,GAAG,GAAG,aAAa,IAAI;AACxG,QAAM,qBAAqB,UAAU,IAAI,CAAC,aAAa;AACrD,QAAI,cAAc,YAAY,aAAa,UAAU;AACnD,YAAM,IAAI,MAAM,sFAAsF;AAAA,IACxG;AACA,WAAO,EAAE,GAAG,UAAU,YAAY,CAAC,GAAI,SAAS,cAAc,CAAC,GAAI,2BAA2B,UAAU,QAAQ,CAAC,EAAE;AAAA,EACrH,CAAC;AACD,MAAI,CAAC,mBAAmB,KAAK,CAAC,aAAa,SAAS,SAAS,2CAAyB,IAAI,GAAG;AAC3F,uBAAmB,QAAQ,EAAE,GAAG,4CAA0B,YAAY,CAAC,2BAA2B,UAAU,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC1H;AACA,aAAW,QAAQ,4BAA4B;AAC7C,QAAI,CAAC,KAAK,YAAY,wBAAwB,IAAI,KAAK,QAAQ,MAAM,UAAU;AAC7E,YAAM,IAAI,MAAM,gGAAgG;AAAA,IAClH;AAAA,EACF;AACA,aAAO,mCAAgB;AAAA,IACrB,GAAG;AAAA,IACH,YAAY,CAAC,GAAG,YAAY,2BAA2B,UAAU,QAAQ,CAAC;AAAA,IAC1E,WAAW,CAAC,GAAG,oBAAoB,GAAG,0BAA0B;AAAA,EAClE,CAAC;AACH;","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-DqjD6aXD.cjs';
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-DqjD6aXD.js';
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
- ...options.conversationId === void 0 ? {} : { metadata: { conversationId: options.conversationId } }
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) {