@silmaril-security/sdk 0.7.7 → 0.7.9

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,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.7.9
4
+
5
+ - Reject invalid runtime mode values in clients, per-call overrides, and all
6
+ framework adapters before requests or fail-open handling. Valid modes and
7
+ legacy defaults retain their existing behavior.
8
+
9
+ ## 0.7.8
10
+
11
+ - Add explicit `standard` and `deep` review selection for clients and individual
12
+ classify calls, with per-call precedence and legacy omission behavior.
13
+ - Verify top-level review acknowledgements before returning results or invoking
14
+ adapter callbacks, and expose non-retryable `ReviewCompatibilityError` for
15
+ missing or mismatched acknowledgements.
16
+ - Preserve review defaults through Vercel, LangChain, and Deep Agents adapters
17
+ without adding adapter-specific review options.
4
18
 
5
19
  ## 0.7.7
6
20
 
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.7 for reproducible installs:
37
+ After publication, pin version 0.7.9 for reproducible installs:
38
38
 
39
39
  ```sh
40
- npm install @silmaril-security/sdk@0.7.7
40
+ npm install @silmaril-security/sdk@0.7.9
41
41
  ```
42
42
 
43
43
  Requires Node 20 or later.
@@ -128,6 +128,50 @@ result and the exception. Disabled and observe policy paths can retain the
128
128
  compatibility threshold. The adapters do not compare `score` to a local
129
129
  threshold.
130
130
 
131
+ ## Review Levels
132
+
133
+ Review selection is available starting with TypeScript SDK 0.7.8. Deploy
134
+ compatible Firewall support before using an explicit selection.
135
+
136
+ Omit `review` to inherit the selected tenant/surface AppConfig
137
+ `threat_judge.enabled` value without
138
+ adding a review field to the request. Set `review: "standard"` to disable the
139
+ optional large-judge stage for that request, or `review: "deep"` to enable it
140
+ with the server-owned judge configuration. A per-call selection takes
141
+ precedence over a client default.
142
+
143
+ Deep review uses the existing second opinion on eligible Qwen threat candidates.
144
+ It requires an active Cascade runtime with a supported judge configured, even
145
+ when its AppConfig default is `enabled: false`; unsupported selections return
146
+ HTTP 400. Removing the judge configuration prevents deep requests. Standard
147
+ review is valid without a configured judge, and review is independent of `mode`.
148
+
149
+ TypeScript:
150
+
151
+ ```ts
152
+ const configured = new Firewall({
153
+ apiKey: process.env.SILMARIL_API_KEY!,
154
+ apiUrl: process.env.SILMARIL_API_URL!,
155
+ }); // review omitted
156
+
157
+ await configured.classify(userInput, { review: "standard" });
158
+
159
+ const deepByDefault = new Firewall({
160
+ apiKey: process.env.SILMARIL_API_KEY!,
161
+ apiUrl: process.env.SILMARIL_API_URL!,
162
+ review: "deep",
163
+ });
164
+ await deepByDefault.classify(userInput);
165
+ ```
166
+
167
+ Explicit review selection requires a backend that supports the review wire
168
+ contract. The SDK verifies the successful response's top-level acknowledgement
169
+ before returning results or invoking adapter callbacks. A missing or mismatched
170
+ acknowledgement throws non-retryable `ReviewCompatibilityError`. The
171
+ acknowledged `result.review` reports the applied setting, not whether the
172
+ optional judge actually executed. Adapters inherit the client default and do
173
+ not add a separate review override.
174
+
131
175
  ## Concurrency and Cancellation
132
176
 
133
177
  A `Firewall` instance holds no per-request state, so one client can serve any
@@ -242,6 +286,7 @@ interface FirewallOptions {
242
286
  apiUrl: string; // required
243
287
  timeoutMs?: number; // default: 10000 ms, max 2147483647
244
288
  mode?: "shadow" | "warn" | "block"; // explicit local client default
289
+ review?: "standard" | "deep"; // explicit client review default
245
290
  shadowMode?: boolean; // deprecated legacy mapping
246
291
  }
247
292
  ```
@@ -265,6 +310,11 @@ threshold.
265
310
 
266
311
  ## Modes
267
312
 
313
+ Modes are case-sensitive: use `shadow`, `warn`, or `block`. Invalid client or
314
+ adapter settings throw during construction; invalid per-call modes reject
315
+ before an HTTP request. Adapter fail-open handling does not ignore invalid
316
+ configuration.
317
+
268
318
  Mode controls local SDK and adapter handling; the backend remains authoritative
269
319
  for threat decisions and thresholds. Explicitly set `mode: "shadow"` or
270
320
  `mode: "warn"` when you want observation without adapter enforcement. Set
@@ -415,6 +465,10 @@ identity and does not change enforcement behavior.
415
465
  refused redirect does not expose a reliable redirect status to JavaScript,
416
466
  particularly in browsers, so it is not reported as `SilmarilApiError`.
417
467
  - `FirewallBlockedException`: thrown by the Vercel AI SDK and LangChain.js adapters when a malicious or governance-block decision has effective Block mode. The message distinguishes governance-policy denials from threat-score denials. Carries `score`, `threshold`, `promptText`, and optional `runId`, `hook`, `toolName`, `toolCallId`, and `result`.
468
+ - `ReviewCompatibilityError`: thrown when an explicit review selection is not
469
+ acknowledged exactly by a successful backend response. That successful
470
+ response is not retried; prior transient rate-limit responses may already
471
+ have been retried. Fail-open adapters propagate the compatibility error.
418
472
 
419
473
  `PromptBlockedException` remains as a deprecated alias of
420
474
  `FirewallBlockedException`.
@@ -653,6 +707,6 @@ IDs must be non-empty; MCP tools also require their canonical server parent ID.
653
707
  The compatibility metadata copy remains attribution only. Caller agent metadata
654
708
  does not supply an authenticated principal.
655
709
 
656
- This source prepares version 0.7.7. Its registry install command becomes
710
+ This source prepares version 0.7.9. Its registry install command becomes
657
711
  available after the authorized main-branch release workflow publishes the package.
658
712
  Confirm registry publication before installing this version.
@@ -33,6 +33,24 @@ var import_langgraph = require("@langchain/langgraph");
33
33
  var import_deepagents = require("deepagents");
34
34
  var import_langchain = require("langchain");
35
35
 
36
+ // src/exceptions.ts
37
+ var REVIEW_COMPATIBILITY_ERROR_BRAND = /* @__PURE__ */ Symbol.for(
38
+ "@silmaril-security/sdk/ReviewCompatibilityError"
39
+ );
40
+ function isReviewCompatibilityError(error) {
41
+ if (typeof error !== "object" && typeof error !== "function" || error === null) {
42
+ return false;
43
+ }
44
+ try {
45
+ return Object.getOwnPropertyDescriptor(
46
+ error,
47
+ REVIEW_COMPATIBILITY_ERROR_BRAND
48
+ )?.value === true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
36
54
  // src/hooks.ts
37
55
  var HookLabel = {
38
56
  USER_INPUT: "user_input",
@@ -83,6 +101,14 @@ var FIREWALL_HOOK_TO_LABEL = {
83
101
  };
84
102
  var FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
85
103
 
104
+ // src/modes.ts
105
+ function validateMode(value) {
106
+ if (value === void 0 || value === "shadow" || value === "warn" || value === "block") {
107
+ return value;
108
+ }
109
+ throw new Error("Firewall: mode must be shadow, warn, or block");
110
+ }
111
+
86
112
  // src/adapters/deepagents.ts
87
113
  var SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
88
114
  var SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
@@ -126,7 +152,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
126
152
  if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {
127
153
  throw new Error("maxBlockedAttempts must be a positive integer");
128
154
  }
129
- const effectiveMode = options.mode ?? firewall.mode;
155
+ const effectiveMode = validateMode(options.mode) ?? firewall.mode;
130
156
  const classify = async (text, hook, toolName) => {
131
157
  if (!text.trim()) return { enforce: false, mode: effectiveMode };
132
158
  try {
@@ -146,6 +172,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
146
172
  }
147
173
  return { enforce: blocked && mode === "block", mode };
148
174
  } catch (error) {
175
+ if (isReviewCompatibilityError(error)) throw error;
149
176
  if (options.failOpen === false) throw error;
150
177
  return { enforce: false, mode: effectiveMode };
151
178
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/deepagents.ts","../../src/hooks.ts"],"sourcesContent":["// Copyright (c) 2024-2026 Silmaril Security Inc. All rights reserved.\n\n/** Optional Deep Agents integration. Import from @silmaril-security/sdk/adapters/deepagents. */\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport { Command } from \"@langchain/langgraph\";\nimport { createDeepAgent, GENERAL_PURPOSE_SUBAGENT, type AnySubAgent, type CompiledSubAgent } from \"deepagents\";\nimport { createAgent, createMiddleware, type CreateAgentParams } from \"langchain\";\n\nimport type { Firewall } from \"../firewall.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { ClassifyEvent, FirewallMode } from \"../types.js\";\n\nexport const SAFE_TOOL_MESSAGE = \"Silmaril Firewall blocked this tool interaction. Choose a different safe action.\";\nexport const SAFE_FINAL_MESSAGE = \"Silmaril Firewall stopped this request after repeated unsafe actions.\";\nexport const SAFE_OUTPUT_MESSAGE = \"Silmaril Firewall blocked this response.\";\nconst BLOCKED_TOOL_MARKER = \"silmaril-firewall:v1:blocked-tool\";\nconst issuedBlockedMessages = new WeakSet<ToolMessage>();\n\n// A protected graph is identified by the exact runnable returned by our factory.\nconst protectedCompiledGraphs = new WeakMap<object, Firewall>();\n\nexport interface DeepAgentsMiddlewareOptions {\n mode?: FirewallMode;\n failOpen?: boolean;\n maxBlockedAttempts?: number;\n /** A caller-supplied sequence identity; never derived from a LangChain run ID. */\n conversationId?: string;\n onClassify?: (event: ClassifyEvent) => void;\n}\n\nfunction textOf(message: { content: unknown }): string {\n return typeof message.content === \"string\" ? message.content : JSON.stringify(message.content);\n}\n\nfunction textOfStructuredValue(value: unknown, seen = new WeakSet<object>()): string {\n if (value == null) return \"\";\n if (typeof value !== \"object\") return String(value);\n if (seen.has(value)) return \"[circular]\";\n seen.add(value);\n if (Array.isArray(value)) return value.map((item) => textOfStructuredValue(item, seen)).join(\"\\n\");\n return Object.entries(value).map(([key, item]) => `${key}: ${textOfStructuredValue(item, seen)}`).join(\"\\n\");\n}\n\nfunction stripAllowedMarker(result: ToolMessage | Command): ToolMessage | Command {\n const strip = (message: ToolMessage): ToolMessage => {\n if (message.additional_kwargs?.silmarilBlocked !== BLOCKED_TOOL_MARKER) return message;\n if (issuedBlockedMessages.has(message)) return message;\n const { silmarilBlocked: _marker, ...additional_kwargs } = message.additional_kwargs;\n // Tool providers may return frozen messages or metadata. Copy rather\n // than mutating a result owned by the caller.\n return new ToolMessage({ ...message, additional_kwargs });\n };\n if (ToolMessage.isInstance(result)) return strip(result);\n if (result instanceof Command) {\n const update = result.update;\n if (update && !Array.isArray(update) && \"messages\" in update && Array.isArray(update.messages)) {\n const originalMessages = update.messages;\n const messages = originalMessages.map((message) => ToolMessage.isInstance(message) ? strip(message) : message);\n if (messages.some((message, index) => message !== originalMessages[index])) {\n return new Command({ ...result, update: { ...update, messages } });\n }\n }\n }\n return result;\n}\n\nexport function createDeepAgentsMiddleware(firewall: Firewall, options: DeepAgentsMiddlewareOptions = {}) {\n const maxBlockedAttempts = options.maxBlockedAttempts ?? 3;\n if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {\n throw new Error(\"maxBlockedAttempts must be a positive integer\");\n }\n const effectiveMode = options.mode ?? firewall.mode;\n const classify = async (text: string, hook: HookLabel, toolName?: string): Promise<{ enforce: boolean; mode: FirewallMode | undefined }> => {\n if (!text.trim()) return { enforce: false, mode: effectiveMode };\n try {\n const result = await firewall.classify(text, {\n hook,\n ...(toolName === undefined ? {} : { toolName }),\n ...(effectiveMode === undefined ? {} : { mode: effectiveMode }),\n ...(options.conversationId === undefined ? {} : { metadata: { conversationId: options.conversationId } }),\n });\n const mode = effectiveMode ?? result.mode ?? \"block\";\n const blocked = result.prediction === \"MALICIOUS\" || result.governance?.action === \"block\";\n if (options.onClassify) {\n try {\n options.onClassify({ hook, ...(toolName === undefined ? {} : { toolName }), text, result, blocked, mode, shadowMode: mode === \"shadow\" });\n } catch {\n // Observation callbacks do not change enforcement.\n }\n }\n return { enforce: blocked && mode === \"block\", mode };\n } catch (error) {\n if (options.failOpen === false) throw error;\n return { enforce: false, mode: effectiveMode };\n }\n };\n\n return createMiddleware({\n name: \"SilmarilDeepAgentsMiddleware\",\n wrapModelCall: async (request, handler) => {\n const history = request.state.messages;\n let lastUserIndex = -1;\n for (let i = history.length - 1; i >= 0; i--) {\n if (HumanMessage.isInstance(history[i])) {\n lastUserIndex = i;\n break;\n }\n }\n const blockedCount = history.slice(lastUserIndex + 1).filter(\n (message) => ToolMessage.isInstance(message) && message.content === SAFE_TOOL_MESSAGE\n && message.additional_kwargs?.silmarilBlocked === BLOCKED_TOOL_MARKER,\n ).length;\n const latestUser = [...request.messages].reverse().find(HumanMessage.isInstance);\n const inputDecision = latestUser\n ? await classify(textOf(latestUser), HookLabel.USER_INPUT)\n : undefined;\n if (inputDecision?.enforce) return new AIMessage(SAFE_OUTPUT_MESSAGE);\n 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\nexport const HookLabel = {\n USER_INPUT: \"user_input\",\n SYSTEM_PROMPT: \"system_prompt\",\n TOOL_CALL: \"tool_call\",\n TOOL_RESPONSE: \"tool_response\",\n LLM_OUTPUT: \"llm_output\",\n UNKNOWN: \"unknown\",\n} as const;\n\nexport type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];\n\nexport const FirewallHook = {\n LLM_START: \"on_llm_start\",\n CHAT_MODEL_START: \"on_chat_model_start\",\n TOOL_START: \"on_tool_start\",\n RETRIEVER_START: \"on_retriever_start\",\n LLM_END: \"on_llm_end\",\n TOOL_END: \"on_tool_end\",\n RETRIEVER_END: \"on_retriever_end\",\n} as const;\n\nexport type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];\n\nexport const DEFAULT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.TOOL_END,\n]);\n\nexport const INPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.RETRIEVER_START,\n]);\n\nexport const OUTPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_END,\n FirewallHook.TOOL_END,\n FirewallHook.RETRIEVER_END,\n]);\n\nexport const ALL_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n ...INPUT_HOOKS,\n ...OUTPUT_HOOKS,\n]);\n\nexport const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>> = {\n [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,\n [FirewallHook.LLM_START]: HookLabel.USER_INPUT,\n [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,\n [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,\n [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT,\n};\n\nconst FIREWALL_HOOK_VALUES: ReadonlySet<string> = new Set(Object.values(FirewallHook));\n\nexport function resolveHooks(\n hooks: Iterable<FirewallHook | string> | undefined,\n): ReadonlySet<FirewallHook> {\n if (hooks === undefined) {\n return DEFAULT_HOOKS;\n }\n const resolved = new Set<FirewallHook>();\n for (const h of hooks) {\n if (!FIREWALL_HOOK_VALUES.has(h)) {\n throw new Error(`Invalid FirewallHook value: ${String(h)}`);\n }\n resolved.add(h as FirewallHook);\n }\n return resolved;\n}\n\nexport function prependHook(text: string, hook: HookLabel | string | undefined | null): string {\n if (hook === undefined || hook === null) {\n return text;\n }\n if (hook === HookLabel.UNKNOWN) {\n return text;\n }\n return `[HOOK:${hook}] ${text}`;\n}\n\nexport function prependToolName(text: string, toolName: string | undefined | null): string {\n if (toolName === undefined || toolName === null) {\n return text;\n }\n return `[TOOL:${toolName}] ${text}`;\n}\n"],"mappings":"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","names":[]}
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":[]}
@@ -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-CueYday8.cjs';
15
+ import { h as FirewallMode, b as ClassifyEvent, e as Firewall } from '../firewall-DqjD6aXD.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.";
@@ -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-CueYday8.js';
15
+ import { h as FirewallMode, b as ClassifyEvent, e as Firewall } from '../firewall-DqjD6aXD.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.";
@@ -4,6 +4,24 @@ import { Command } from "@langchain/langgraph";
4
4
  import { createDeepAgent, GENERAL_PURPOSE_SUBAGENT } from "deepagents";
5
5
  import { createAgent, createMiddleware } from "langchain";
6
6
 
7
+ // src/exceptions.ts
8
+ var REVIEW_COMPATIBILITY_ERROR_BRAND = /* @__PURE__ */ Symbol.for(
9
+ "@silmaril-security/sdk/ReviewCompatibilityError"
10
+ );
11
+ function isReviewCompatibilityError(error) {
12
+ if (typeof error !== "object" && typeof error !== "function" || error === null) {
13
+ return false;
14
+ }
15
+ try {
16
+ return Object.getOwnPropertyDescriptor(
17
+ error,
18
+ REVIEW_COMPATIBILITY_ERROR_BRAND
19
+ )?.value === true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
7
25
  // src/hooks.ts
8
26
  var HookLabel = {
9
27
  USER_INPUT: "user_input",
@@ -54,6 +72,14 @@ var FIREWALL_HOOK_TO_LABEL = {
54
72
  };
55
73
  var FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
56
74
 
75
+ // src/modes.ts
76
+ function validateMode(value) {
77
+ if (value === void 0 || value === "shadow" || value === "warn" || value === "block") {
78
+ return value;
79
+ }
80
+ throw new Error("Firewall: mode must be shadow, warn, or block");
81
+ }
82
+
57
83
  // src/adapters/deepagents.ts
58
84
  var SAFE_TOOL_MESSAGE = "Silmaril Firewall blocked this tool interaction. Choose a different safe action.";
59
85
  var SAFE_FINAL_MESSAGE = "Silmaril Firewall stopped this request after repeated unsafe actions.";
@@ -97,7 +123,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
97
123
  if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {
98
124
  throw new Error("maxBlockedAttempts must be a positive integer");
99
125
  }
100
- const effectiveMode = options.mode ?? firewall.mode;
126
+ const effectiveMode = validateMode(options.mode) ?? firewall.mode;
101
127
  const classify = async (text, hook, toolName) => {
102
128
  if (!text.trim()) return { enforce: false, mode: effectiveMode };
103
129
  try {
@@ -117,6 +143,7 @@ function createDeepAgentsMiddleware(firewall, options = {}) {
117
143
  }
118
144
  return { enforce: blocked && mode === "block", mode };
119
145
  } catch (error) {
146
+ if (isReviewCompatibilityError(error)) throw error;
120
147
  if (options.failOpen === false) throw error;
121
148
  return { enforce: false, mode: effectiveMode };
122
149
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/deepagents.ts","../../src/hooks.ts"],"sourcesContent":["// Copyright (c) 2024-2026 Silmaril Security Inc. All rights reserved.\n\n/** Optional Deep Agents integration. Import from @silmaril-security/sdk/adapters/deepagents. */\nimport { AIMessage, HumanMessage, ToolMessage } from \"@langchain/core/messages\";\nimport { Command } from \"@langchain/langgraph\";\nimport { createDeepAgent, GENERAL_PURPOSE_SUBAGENT, type AnySubAgent, type CompiledSubAgent } from \"deepagents\";\nimport { createAgent, createMiddleware, type CreateAgentParams } from \"langchain\";\n\nimport type { Firewall } from \"../firewall.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { ClassifyEvent, FirewallMode } from \"../types.js\";\n\nexport const SAFE_TOOL_MESSAGE = \"Silmaril Firewall blocked this tool interaction. Choose a different safe action.\";\nexport const SAFE_FINAL_MESSAGE = \"Silmaril Firewall stopped this request after repeated unsafe actions.\";\nexport const SAFE_OUTPUT_MESSAGE = \"Silmaril Firewall blocked this response.\";\nconst BLOCKED_TOOL_MARKER = \"silmaril-firewall:v1:blocked-tool\";\nconst issuedBlockedMessages = new WeakSet<ToolMessage>();\n\n// A protected graph is identified by the exact runnable returned by our factory.\nconst protectedCompiledGraphs = new WeakMap<object, Firewall>();\n\nexport interface DeepAgentsMiddlewareOptions {\n mode?: FirewallMode;\n failOpen?: boolean;\n maxBlockedAttempts?: number;\n /** A caller-supplied sequence identity; never derived from a LangChain run ID. */\n conversationId?: string;\n onClassify?: (event: ClassifyEvent) => void;\n}\n\nfunction textOf(message: { content: unknown }): string {\n return typeof message.content === \"string\" ? message.content : JSON.stringify(message.content);\n}\n\nfunction textOfStructuredValue(value: unknown, seen = new WeakSet<object>()): string {\n if (value == null) return \"\";\n if (typeof value !== \"object\") return String(value);\n if (seen.has(value)) return \"[circular]\";\n seen.add(value);\n if (Array.isArray(value)) return value.map((item) => textOfStructuredValue(item, seen)).join(\"\\n\");\n return Object.entries(value).map(([key, item]) => `${key}: ${textOfStructuredValue(item, seen)}`).join(\"\\n\");\n}\n\nfunction stripAllowedMarker(result: ToolMessage | Command): ToolMessage | Command {\n const strip = (message: ToolMessage): ToolMessage => {\n if (message.additional_kwargs?.silmarilBlocked !== BLOCKED_TOOL_MARKER) return message;\n if (issuedBlockedMessages.has(message)) return message;\n const { silmarilBlocked: _marker, ...additional_kwargs } = message.additional_kwargs;\n // Tool providers may return frozen messages or metadata. Copy rather\n // than mutating a result owned by the caller.\n return new ToolMessage({ ...message, additional_kwargs });\n };\n if (ToolMessage.isInstance(result)) return strip(result);\n if (result instanceof Command) {\n const update = result.update;\n if (update && !Array.isArray(update) && \"messages\" in update && Array.isArray(update.messages)) {\n const originalMessages = update.messages;\n const messages = originalMessages.map((message) => ToolMessage.isInstance(message) ? strip(message) : message);\n if (messages.some((message, index) => message !== originalMessages[index])) {\n return new Command({ ...result, update: { ...update, messages } });\n }\n }\n }\n return result;\n}\n\nexport function createDeepAgentsMiddleware(firewall: Firewall, options: DeepAgentsMiddlewareOptions = {}) {\n const maxBlockedAttempts = options.maxBlockedAttempts ?? 3;\n if (!Number.isInteger(maxBlockedAttempts) || maxBlockedAttempts < 1) {\n throw new Error(\"maxBlockedAttempts must be a positive integer\");\n }\n const effectiveMode = options.mode ?? firewall.mode;\n const classify = async (text: string, hook: HookLabel, toolName?: string): Promise<{ enforce: boolean; mode: FirewallMode | undefined }> => {\n if (!text.trim()) return { enforce: false, mode: effectiveMode };\n try {\n const result = await firewall.classify(text, {\n hook,\n ...(toolName === undefined ? {} : { toolName }),\n ...(effectiveMode === undefined ? {} : { mode: effectiveMode }),\n ...(options.conversationId === undefined ? {} : { metadata: { conversationId: options.conversationId } }),\n });\n const mode = effectiveMode ?? result.mode ?? \"block\";\n const blocked = result.prediction === \"MALICIOUS\" || result.governance?.action === \"block\";\n if (options.onClassify) {\n try {\n options.onClassify({ hook, ...(toolName === undefined ? {} : { toolName }), text, result, blocked, mode, shadowMode: mode === \"shadow\" });\n } catch {\n // Observation callbacks do not change enforcement.\n }\n }\n return { enforce: blocked && mode === \"block\", mode };\n } catch (error) {\n if (options.failOpen === false) throw error;\n return { enforce: false, mode: effectiveMode };\n }\n };\n\n return createMiddleware({\n name: \"SilmarilDeepAgentsMiddleware\",\n wrapModelCall: async (request, handler) => {\n const history = request.state.messages;\n let lastUserIndex = -1;\n for (let i = history.length - 1; i >= 0; i--) {\n if (HumanMessage.isInstance(history[i])) {\n lastUserIndex = i;\n break;\n }\n }\n const blockedCount = history.slice(lastUserIndex + 1).filter(\n (message) => ToolMessage.isInstance(message) && message.content === SAFE_TOOL_MESSAGE\n && message.additional_kwargs?.silmarilBlocked === BLOCKED_TOOL_MARKER,\n ).length;\n const latestUser = [...request.messages].reverse().find(HumanMessage.isInstance);\n const inputDecision = latestUser\n ? await classify(textOf(latestUser), HookLabel.USER_INPUT)\n : undefined;\n if (inputDecision?.enforce) return new AIMessage(SAFE_OUTPUT_MESSAGE);\n 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\nexport const HookLabel = {\n USER_INPUT: \"user_input\",\n SYSTEM_PROMPT: \"system_prompt\",\n TOOL_CALL: \"tool_call\",\n TOOL_RESPONSE: \"tool_response\",\n LLM_OUTPUT: \"llm_output\",\n UNKNOWN: \"unknown\",\n} as const;\n\nexport type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];\n\nexport const FirewallHook = {\n LLM_START: \"on_llm_start\",\n CHAT_MODEL_START: \"on_chat_model_start\",\n TOOL_START: \"on_tool_start\",\n RETRIEVER_START: \"on_retriever_start\",\n LLM_END: \"on_llm_end\",\n TOOL_END: \"on_tool_end\",\n RETRIEVER_END: \"on_retriever_end\",\n} as const;\n\nexport type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];\n\nexport const DEFAULT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.TOOL_END,\n]);\n\nexport const INPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.RETRIEVER_START,\n]);\n\nexport const OUTPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_END,\n FirewallHook.TOOL_END,\n FirewallHook.RETRIEVER_END,\n]);\n\nexport const ALL_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n ...INPUT_HOOKS,\n ...OUTPUT_HOOKS,\n]);\n\nexport const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>> = {\n [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,\n [FirewallHook.LLM_START]: HookLabel.USER_INPUT,\n [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,\n [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,\n [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT,\n};\n\nconst FIREWALL_HOOK_VALUES: ReadonlySet<string> = new Set(Object.values(FirewallHook));\n\nexport function resolveHooks(\n hooks: Iterable<FirewallHook | string> | undefined,\n): ReadonlySet<FirewallHook> {\n if (hooks === undefined) {\n return DEFAULT_HOOKS;\n }\n const resolved = new Set<FirewallHook>();\n for (const h of hooks) {\n if (!FIREWALL_HOOK_VALUES.has(h)) {\n throw new Error(`Invalid FirewallHook value: ${String(h)}`);\n }\n resolved.add(h as FirewallHook);\n }\n return resolved;\n}\n\nexport function prependHook(text: string, hook: HookLabel | string | undefined | null): string {\n if (hook === undefined || hook === null) {\n return text;\n }\n if (hook === HookLabel.UNKNOWN) {\n return text;\n }\n return `[HOOK:${hook}] ${text}`;\n}\n\nexport function prependToolName(text: string, toolName: string | undefined | null): string {\n if (toolName === undefined || toolName === null) {\n return text;\n }\n return `[TOOL:${toolName}] ${text}`;\n}\n"],"mappings":"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","names":[]}
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":[]}
@@ -66,6 +66,22 @@ var FirewallBlockedException = class _FirewallBlockedException extends Error {
66
66
  return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
67
67
  }
68
68
  };
69
+ var REVIEW_COMPATIBILITY_ERROR_BRAND = /* @__PURE__ */ Symbol.for(
70
+ "@silmaril-security/sdk/ReviewCompatibilityError"
71
+ );
72
+ function isReviewCompatibilityError(error) {
73
+ if (typeof error !== "object" && typeof error !== "function" || error === null) {
74
+ return false;
75
+ }
76
+ try {
77
+ return Object.getOwnPropertyDescriptor(
78
+ error,
79
+ REVIEW_COMPATIBILITY_ERROR_BRAND
80
+ )?.value === true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
69
85
 
70
86
  // src/hooks.ts
71
87
  var HookLabel = {
@@ -130,6 +146,14 @@ function resolveHooks(hooks) {
130
146
  return resolved;
131
147
  }
132
148
 
149
+ // src/modes.ts
150
+ function validateMode(value) {
151
+ if (value === void 0 || value === "shadow" || value === "warn" || value === "block") {
152
+ return value;
153
+ }
154
+ throw new Error("Firewall: mode must be shadow, warn, or block");
155
+ }
156
+
133
157
  // src/utils/extract.ts
134
158
  function extractTextFromPrompts(prompts) {
135
159
  return prompts.map((p) => p.trim()).filter((p) => p.length > 0).join("\n");
@@ -205,7 +229,7 @@ async function createLangChainHandler(firewall, options = {}) {
205
229
  const logger = options.logger ?? ((message, error) => {
206
230
  console.warn(`silmaril.firewall: ${message}`, error);
207
231
  });
208
- const requestedMode = options.mode ?? (options.shadowMode === void 0 ? firewall.mode : options.shadowMode ? "shadow" : "block");
232
+ const requestedMode = validateMode(options.mode) ?? (options.shadowMode === void 0 ? firewall.mode : options.shadowMode ? "shadow" : "block");
209
233
  const onClassify = options.onClassify;
210
234
  const fireOnClassify = (event) => {
211
235
  if (!onClassify) {
@@ -230,6 +254,9 @@ async function createLangChainHandler(firewall, options = {}) {
230
254
  ...toolName !== void 0 ? { toolName } : {}
231
255
  });
232
256
  } catch (err) {
257
+ if (isReviewCompatibilityError(err)) {
258
+ throw err;
259
+ }
233
260
  if (!failOpen) {
234
261
  throw err;
235
262
  }