@silmaril-security/sdk 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -86,6 +86,8 @@ const { text } = await generateText({ model, prompt: "Hello" });
86
86
 
87
87
  Middleware is **fail-closed** by default: classification errors bubble up to the caller, matching Vercel AI SDK convention.
88
88
 
89
+ When `scanOutput: true` is combined with streaming, output is classified in the stream's `flush` after all deltas have been emitted, so blocking is advisory — the consumer has already seen the text by the time an error part is enqueued. Use non-streaming generation if you need to block before the caller observes output.
90
+
89
91
  ## LangChain.js callback handler
90
92
 
91
93
  ```ts
@@ -119,3 +121,31 @@ Pipeline-stage-aware classification. The model uses `[HOOK:<label>]` and `[TOOL:
119
121
  | `LLM_OUTPUT` | LLM end |
120
122
 
121
123
  Per-hook threshold overrides via `hookThresholds: { tool_response: 0.3 }`.
124
+
125
+ ## Shadow mode
126
+
127
+ Set `shadowMode: true` on the `Firewall` client to turn every downstream adapter into observation-only. The firewall still classifies every hook and still computes the would-block decision against its thresholds, but the adapters suppress `PromptBlockedException` so live traffic is unaffected. Use it to roll the firewall out against real traffic and tune thresholds before committing to enforcement.
128
+
129
+ ```ts
130
+ const firewall = new Firewall({
131
+ apiKey: process.env.SILMARIL_API_KEY!,
132
+ apiUrl: process.env.SILMARIL_API_URL!,
133
+ shadowMode: true,
134
+ });
135
+
136
+ const model = wrapLanguageModel({
137
+ model: openai("gpt-4o-mini"),
138
+ middleware: firewall.asMiddleware({
139
+ scanOutput: true,
140
+ onClassify: ({ hook, result, blocked, shadowMode }) => {
141
+ if (blocked) {
142
+ metrics.increment("firewall.would_block", { hook, shadow: String(shadowMode) });
143
+ }
144
+ },
145
+ }),
146
+ });
147
+ ```
148
+
149
+ Adapter-level `shadowMode` overrides the firewall-level value, so you can enforce on some adapters and shadow on others (for example shadow on `asMiddleware` while enforcing on `asLangChainHandler`). The direct `classify()` / `classifyBatch()` calls never throw on verdicts and are unaffected by the flag.
150
+
151
+ The exported `prependHook` / `prependToolName` helpers mirror the server-side tokenization for offline use (evaluation harnesses, parity checks). You do **not** need to call them before `classify()` — the server prepends `[HOOK:<label>]` and `[TOOL:<name>]` from the `hook` and `tool_name` payload fields.
package/dist/index.cjs CHANGED
@@ -151,10 +151,10 @@ var init_hooks = __esm({
151
151
  };
152
152
  DEFAULT_HOOK_THRESHOLDS = {
153
153
  [HookLabel.USER_INPUT]: 0.5,
154
- [HookLabel.SYSTEM_PROMPT]: 0.3,
155
- [HookLabel.TOOL_CALL]: 0.4,
156
- [HookLabel.TOOL_RESPONSE]: 0.3,
157
- [HookLabel.LLM_OUTPUT]: 0.4,
154
+ [HookLabel.SYSTEM_PROMPT]: 0.5,
155
+ [HookLabel.TOOL_CALL]: 0.5,
156
+ [HookLabel.TOOL_RESPONSE]: 0.5,
157
+ [HookLabel.LLM_OUTPUT]: 0.5,
158
158
  [HookLabel.UNKNOWN]: 0.5
159
159
  };
160
160
  FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
@@ -245,6 +245,8 @@ async function createLangChainHandler(firewall, options = {}) {
245
245
  });
246
246
  const overrideThreshold = options.threshold;
247
247
  const overrideHookThresholds = options.hookThresholds ?? {};
248
+ const shadowMode = options.shadowMode ?? firewall.shadowMode;
249
+ const onClassify = options.onClassify;
248
250
  const effectiveThreshold = (label) => {
249
251
  if (overrideHookThresholds[label] !== void 0) {
250
252
  return overrideHookThresholds[label];
@@ -254,29 +256,47 @@ async function createLangChainHandler(firewall, options = {}) {
254
256
  }
255
257
  return overrideThreshold ?? firewall.threshold;
256
258
  };
259
+ const fireOnClassify = (hookLabel, text, result, blocked, toolName) => {
260
+ if (!onClassify) {
261
+ return;
262
+ }
263
+ try {
264
+ onClassify({
265
+ hook: hookLabel,
266
+ ...toolName !== void 0 ? { toolName } : {},
267
+ text,
268
+ result,
269
+ blocked,
270
+ shadowMode
271
+ });
272
+ } catch (err) {
273
+ logger("onClassify callback threw", err);
274
+ }
275
+ };
257
276
  const classify = async (text, hookLabel, runId, toolName) => {
277
+ let result;
258
278
  try {
259
- const result = await firewall.classify(text, {
279
+ result = await firewall.classify(text, {
260
280
  hook: hookLabel,
261
281
  ...toolName !== void 0 ? { toolName } : {}
262
282
  });
263
- const threshold = effectiveThreshold(hookLabel);
264
- if (result.score >= threshold) {
265
- throw new PromptBlockedException({
266
- score: result.score,
267
- threshold,
268
- promptText: text,
269
- runId
270
- });
271
- }
272
283
  } catch (err) {
273
- if (err instanceof PromptBlockedException) {
274
- throw err;
275
- }
276
284
  if (!failOpen) {
277
285
  throw err;
278
286
  }
279
287
  logger("classification failed, allowing prompt through", err);
288
+ return;
289
+ }
290
+ const threshold = effectiveThreshold(hookLabel);
291
+ const blocked = result.score >= threshold;
292
+ fireOnClassify(hookLabel, text, result, blocked, toolName);
293
+ if (blocked && !shadowMode) {
294
+ throw new PromptBlockedException({
295
+ score: result.score,
296
+ threshold,
297
+ promptText: text,
298
+ runId
299
+ });
280
300
  }
281
301
  };
282
302
  class SilmarilFirewallHandler extends BaseCallbackHandler {
@@ -323,8 +343,7 @@ async function createLangChainHandler(firewall, options = {}) {
323
343
  return;
324
344
  }
325
345
  const toolName = tool?.name;
326
- const prefixed = prependToolName(text, toolName);
327
- await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);
346
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);
328
347
  }
329
348
  async handleRetrieverStart(_retriever, query, runId) {
330
349
  if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {
@@ -355,8 +374,7 @@ async function createLangChainHandler(firewall, options = {}) {
355
374
  return;
356
375
  }
357
376
  const toolName = _kwargs?.name;
358
- const prefixed = prependToolName(text, toolName);
359
- await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);
377
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);
360
378
  }
361
379
  async handleRetrieverEnd(documents, runId) {
362
380
  if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {
@@ -473,6 +491,7 @@ function createMiddleware(firewall, options = {}) {
473
491
  const scanOutput = options.scanOutput ?? false;
474
492
  const overrideThreshold = options.threshold;
475
493
  const overrideHookThresholds = options.hookThresholds ?? {};
494
+ const shadowMode = options.shadowMode ?? firewall.shadowMode;
476
495
  const effectiveThreshold = (label) => {
477
496
  if (overrideHookThresholds[label] !== void 0) {
478
497
  return overrideHookThresholds[label];
@@ -497,9 +516,10 @@ function createMiddleware(firewall, options = {}) {
497
516
  ...toolName !== void 0 ? { toolName } : {},
498
517
  text,
499
518
  result,
500
- blocked
519
+ blocked,
520
+ shadowMode
501
521
  });
502
- if (blocked) {
522
+ if (blocked && !shadowMode) {
503
523
  const err = new PromptBlockedException({
504
524
  score: result.score,
505
525
  threshold,
@@ -639,12 +659,38 @@ var DEFAULT_THRESHOLD = 0.5;
639
659
  var DEFAULT_TIMEOUT_MS = 1e4;
640
660
  var DEFAULT_MAX_RETRIES = 5;
641
661
  var MAX_BACKOFF_SECONDS = 30;
662
+ function blockResultFromResponse(data) {
663
+ const result = {
664
+ prediction: data.prediction,
665
+ score: Number(data.score)
666
+ };
667
+ if (data.primary_outcome !== void 0) {
668
+ result.primaryOutcome = data.primary_outcome;
669
+ }
670
+ if (data.outcome_scores !== void 0) {
671
+ result.outcomeScores = Object.freeze(
672
+ Object.fromEntries(Object.entries(data.outcome_scores).map(([k, v]) => [k, Number(v)]))
673
+ );
674
+ }
675
+ if (data.detector_scores !== void 0) {
676
+ result.detectorScores = Object.freeze(
677
+ Object.fromEntries(Object.entries(data.detector_scores).map(([k, v]) => [k, Number(v)]))
678
+ );
679
+ }
680
+ if (data.detector_counts !== void 0) {
681
+ result.detectorCounts = Object.freeze(
682
+ Object.fromEntries(Object.entries(data.detector_counts).map(([k, v]) => [k, Number(v)]))
683
+ );
684
+ }
685
+ return Object.freeze(result);
686
+ }
642
687
  var Firewall = class {
643
688
  apiKey;
644
689
  apiUrl;
645
690
  threshold;
646
691
  timeoutMs;
647
692
  hookThresholds;
693
+ shadowMode;
648
694
  headers;
649
695
  constructor(options) {
650
696
  if (!options.apiKey) {
@@ -657,7 +703,8 @@ var Firewall = class {
657
703
  this.apiUrl = options.apiUrl;
658
704
  this.threshold = options.threshold ?? DEFAULT_THRESHOLD;
659
705
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
660
- this.hookThresholds = options.hookThresholds ?? {};
706
+ this.hookThresholds = Object.freeze({ ...options.hookThresholds ?? {} });
707
+ this.shadowMode = options.shadowMode ?? false;
661
708
  this.headers = Object.freeze({
662
709
  "x-api-key": this.apiKey,
663
710
  "content-type": "application/json"
@@ -673,8 +720,11 @@ var Firewall = class {
673
720
  if (options.toolName !== void 0) {
674
721
  payload.tool_name = options.toolName;
675
722
  }
723
+ if (options.metadata !== void 0) {
724
+ payload.metadata = options.metadata;
725
+ }
676
726
  const data = await this.postWithRetry(payload);
677
- return Object.freeze({ prediction: data.prediction, score: Number(data.score) });
727
+ return blockResultFromResponse(data);
678
728
  }
679
729
  const batchOpts = {};
680
730
  if (options.hook !== void 0) {
@@ -683,10 +733,28 @@ var Firewall = class {
683
733
  if (options.toolName !== void 0) {
684
734
  batchOpts.toolNames = chunks.map(() => options.toolName);
685
735
  }
736
+ if (options.metadata !== void 0) {
737
+ batchOpts.metadata = chunks.map(() => options.metadata);
738
+ }
686
739
  const results = await this.classifyBatch(chunks, batchOpts);
687
740
  return results.reduce((best, r) => r.score > best.score ? r : best);
688
741
  }
689
742
  async classifyBatch(texts, options = {}) {
743
+ if (options.hooks !== void 0 && options.hooks.length !== texts.length) {
744
+ throw new Error(
745
+ `Firewall: hooks length ${options.hooks.length} does not match texts length ${texts.length}`
746
+ );
747
+ }
748
+ if (options.toolNames !== void 0 && options.toolNames.length !== texts.length) {
749
+ throw new Error(
750
+ `Firewall: toolNames length ${options.toolNames.length} does not match texts length ${texts.length}`
751
+ );
752
+ }
753
+ if (options.metadata !== void 0 && options.metadata.length !== texts.length) {
754
+ throw new Error(
755
+ `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
756
+ );
757
+ }
690
758
  const payload = { texts };
691
759
  if (options.hooks && options.hooks.length > 0) {
692
760
  payload.hooks = options.hooks.map((h) => String(h));
@@ -694,10 +762,11 @@ var Firewall = class {
694
762
  if (options.toolNames && options.toolNames.length > 0) {
695
763
  payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
696
764
  }
765
+ if (options.metadata && options.metadata.length > 0) {
766
+ payload.metadata = options.metadata.map((m) => m === void 0 ? null : m);
767
+ }
697
768
  const data = await this.postWithRetry(payload);
698
- return data.predictions.map(
699
- (p) => Object.freeze({ prediction: p.prediction, score: Number(p.score) })
700
- );
769
+ return data.predictions.map((p) => blockResultFromResponse(p));
701
770
  }
702
771
  asLangChainHandler(options = {}) {
703
772
  return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/exceptions.ts","../src/hooks.ts","../src/utils/extract.ts","../src/adapters/langchain.ts","../src/index.ts","../src/adapters/vercel.ts","../src/chunking.ts","../src/firewall.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport class PromptBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n }) {\n super(PromptBlockedException.formatMessage(params));\n this.name = \"PromptBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n Object.setPrototypeOf(this, PromptBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\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 return (\n `Prompt blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n\n constructor(params: { status: number; statusText: string; body: string }) {\n super(`Silmaril API error ${params.status} ${params.statusText}: ${params.body}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\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]);\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\nexport const DEFAULT_HOOK_THRESHOLDS: Readonly<Record<HookLabel, number>> = {\n [HookLabel.USER_INPUT]: 0.5,\n [HookLabel.SYSTEM_PROMPT]: 0.3,\n [HookLabel.TOOL_CALL]: 0.4,\n [HookLabel.TOOL_RESPONSE]: 0.3,\n [HookLabel.LLM_OUTPUT]: 0.4,\n [HookLabel.UNKNOWN]: 0.5,\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","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst SKIP_ROLES: ReadonlySet<string> = new Set([\"ai\", \"assistant\"]);\nconst SYSTEM_ROLES: ReadonlySet<string> = new Set([\"system\"]);\nconst TOOL_ROLES: ReadonlySet<string> = new Set([\"tool\", \"function\"]);\n\ntype MessageContent = string | ReadonlyArray<string | { type?: string; text?: string }>;\n\ninterface DucktypedMessage {\n role?: string;\n type?: string;\n content?: MessageContent;\n}\n\ninterface DucktypedGeneration {\n text?: string;\n}\n\ninterface DucktypedLLMResult {\n generations?: ReadonlyArray<ReadonlyArray<DucktypedGeneration>>;\n}\n\ninterface DucktypedDocument {\n pageContent?: string;\n}\n\nfunction extractContentText(content: MessageContent | undefined): string {\n if (typeof content === \"string\") {\n return content;\n }\n if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && typeof block === \"object\" && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n }\n return \"\";\n}\n\nfunction getRole(message: DucktypedMessage): string {\n if (message.role !== undefined) {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nexport function extractTextFromMessages(\n messages: ReadonlyArray<DucktypedMessage>,\n options: { includeSystem?: boolean; includeTool?: boolean } = {},\n): string {\n const includeSystem = options.includeSystem ?? true;\n const includeTool = options.includeTool ?? true;\n const parts: string[] = [];\n for (const message of messages) {\n const role = getRole(message);\n if (SKIP_ROLES.has(role)) {\n continue;\n }\n if (!includeSystem && SYSTEM_ROLES.has(role)) {\n continue;\n }\n if (!includeTool && TOOL_ROLES.has(role)) {\n continue;\n }\n const text = extractContentText(message.content);\n const stripped = text.trim();\n if (stripped) {\n parts.push(stripped);\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromPrompts(prompts: readonly string[]): string {\n return prompts\n .map((p) => p.trim())\n .filter((p) => p.length > 0)\n .join(\"\\n\");\n}\n\nexport function extractTextFromToolInput(inputStr: string): string {\n return inputStr.trim();\n}\n\nexport function extractTextFromLLMResult(response: DucktypedLLMResult): string {\n const parts: string[] = [];\n for (const genList of response.generations ?? []) {\n for (const gen of genList) {\n const text = (gen.text ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromDocuments(documents: ReadonlyArray<DucktypedDocument>): string {\n const parts: string[] = [];\n for (const doc of documents) {\n const text = (doc.pageContent ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n return parts.join(\"\\n\");\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n prependToolName,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { LangChainAdapterOptions } from \"../types.js\";\nimport {\n extractTextFromDocuments,\n extractTextFromLLMResult,\n extractTextFromPrompts,\n extractTextFromToolInput,\n} from \"../utils/extract.js\";\n\nconst USER_ROLES: ReadonlySet<string> = new Set([\"human\", \"user\"]);\n\ninterface LangChainDucktypedMessage {\n role?: string;\n type?: string;\n content?: string | ReadonlyArray<string | { type?: string; text?: string }>;\n}\n\nfunction getMessageRole(message: LangChainDucktypedMessage): string {\n if (typeof message.role === \"string\") {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nfunction extractMessageText(content: LangChainDucktypedMessage[\"content\"]): string {\n if (content === undefined) {\n return \"\";\n }\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<LangChainDucktypedMessage>,\n): LangChainDucktypedMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (USER_ROLES.has(getMessageRole(msg))) {\n return msg;\n }\n }\n return undefined;\n}\n\nexport async function createLangChainHandler(\n firewall: Firewall,\n options: LangChainAdapterOptions = {},\n): Promise<unknown> {\n const { BaseCallbackHandler } = await import(\"@langchain/core/callbacks/base\");\n\n const enabledHooks = resolveHooks(options.hooks);\n // includeSystem / includeTool are retained in LangChainAdapterOptions for\n // backwards compat but are inert under the per-message hook-routing algorithm:\n // handleChatModelStart only scans the last user-role message; system and tool\n // messages are either trusted or already classified by their own hooks.\n const failOpen = options.failOpen ?? true;\n const logger =\n options.logger ??\n ((message: string, error: unknown): void => {\n // eslint-disable-next-line no-console\n console.warn(`silmaril.firewall: ${message}`, error);\n });\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const classify = async (\n text: string,\n hookLabel: HookLabel,\n runId: string,\n toolName?: string,\n ): Promise<void> => {\n try {\n const result = await firewall.classify(text, {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n });\n const threshold = effectiveThreshold(hookLabel);\n if (result.score >= threshold) {\n throw new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n });\n }\n } catch (err) {\n if (err instanceof PromptBlockedException) {\n throw err;\n }\n if (!failOpen) {\n throw err;\n }\n logger(\"classification failed, allowing prompt through\", err);\n }\n };\n\n class SilmarilFirewallHandler extends BaseCallbackHandler {\n override name = \"silmaril_firewall_handler\";\n override raiseError = true;\n override awaitHandlers = true;\n\n override async handleChatModelStart(\n _llm: unknown,\n messages: unknown,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {\n return;\n }\n const batches = messages as ReadonlyArray<ReadonlyArray<LangChainDucktypedMessage>>;\n const flat: LangChainDucktypedMessage[] = [];\n for (const batch of batches) {\n for (const m of batch) {\n flat.push(m);\n }\n }\n const lastUser = findLastUserMessage(flat);\n if (!lastUser) {\n return;\n }\n const text = extractMessageText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);\n }\n\n override async handleLLMStart(\n _llm: unknown,\n prompts: string[],\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_START)) {\n return;\n }\n const text = extractTextFromPrompts(prompts);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);\n }\n\n override async handleToolStart(\n tool: { name?: string } | undefined,\n inputStr: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_START)) {\n return;\n }\n const text = extractTextFromToolInput(inputStr);\n if (!text) {\n return;\n }\n const toolName = tool?.name;\n const prefixed = prependToolName(text, toolName);\n await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);\n }\n\n override async handleRetrieverStart(\n _retriever: unknown,\n query: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {\n return;\n }\n const text = query.trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);\n }\n\n override async handleLLMEnd(output: unknown, runId: string): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_END)) {\n return;\n }\n const text = extractTextFromLLMResult(output as { generations?: ReadonlyArray<ReadonlyArray<{ text?: string }>> });\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);\n }\n\n override async handleToolEnd(\n output: unknown,\n runId: string,\n _parentRunId?: string,\n _tags?: string[],\n _kwargs?: { name?: string },\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_END)) {\n return;\n }\n const text = String(output).trim();\n if (!text) {\n return;\n }\n const toolName = _kwargs?.name;\n const prefixed = prependToolName(text, toolName);\n await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);\n }\n\n override async handleRetrieverEnd(\n documents: ReadonlyArray<{ pageContent?: string }>,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {\n return;\n }\n const text = extractTextFromDocuments(documents);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);\n }\n }\n\n return new SilmarilFirewallHandler();\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport {\n Firewall,\n DEFAULT_THRESHOLD,\n DEFAULT_TIMEOUT_MS,\n} from \"./firewall.js\";\n\nexport { PromptBlockedException, SilmarilApiError } from \"./exceptions.js\";\n\nexport type { FirewallMiddleware } from \"./adapters/vercel.js\";\n\nexport {\n chunkText,\n MAX_INPUT_TOKENS,\n CHUNK_WINDOW,\n CHUNK_OVERLAP,\n MAX_INPUT_CHARS,\n CHUNK_WINDOW_CHARS,\n CHUNK_OVERLAP_CHARS,\n} from \"./chunking.js\";\n\nexport {\n HookLabel,\n FirewallHook,\n DEFAULT_HOOKS,\n INPUT_HOOKS,\n OUTPUT_HOOKS,\n ALL_HOOKS,\n DEFAULT_HOOK_THRESHOLDS,\n FIREWALL_HOOK_TO_LABEL,\n resolveHooks,\n prependHook,\n prependToolName,\n} from \"./hooks.js\";\n\nexport type {\n BlockResult,\n ClassifyEvent,\n Prediction,\n FirewallOptions,\n ClassifyOptions,\n ClassifyBatchOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n} from \"./types.js\";\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { BlockResult, MiddlewareOptions } from \"../types.js\";\n\ninterface VercelContentPart {\n type?: string;\n text?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n}\n\ninterface VercelPromptMessage {\n role: string;\n content: string | ReadonlyArray<VercelContentPart | string>;\n}\n\ninterface VercelToolCall {\n toolCallType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n}\n\ninterface VercelGenerateResult {\n text?: string;\n toolCalls?: ReadonlyArray<VercelToolCall>;\n}\n\ninterface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doGenerate: () => PromiseLike<TResult>;\n}\n\ninterface WrapStreamArgs<\n TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>,\n> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doStream: () => PromiseLike<TResult>;\n}\n\ninterface StreamPart {\n type?: string;\n textDelta?: string;\n delta?: string;\n}\n\nfunction stringifyToolValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n\nfunction extractContentText(content: VercelPromptMessage[\"content\"]): string {\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction iterateToolResultParts(\n message: VercelPromptMessage,\n): Array<{ text: string; toolName: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined }> = [];\n for (const part of message.content) {\n if (typeof part === \"string\") {\n continue;\n }\n if (part.type === \"tool-result\") {\n const text = stringifyToolValue(part.result);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName });\n }\n }\n }\n return out;\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<VercelPromptMessage>,\n): VercelPromptMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role.toLowerCase() === \"user\") {\n return msg;\n }\n }\n return undefined;\n}\n\nexport interface FirewallMiddleware {\n wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(\n args: WrapGenerateArgs<TResult>,\n ) => Promise<TResult>;\n wrapStream: <TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>(\n args: WrapStreamArgs<TResult>,\n ) => Promise<TResult>;\n}\n\nexport function createMiddleware(\n firewall: Firewall,\n options: MiddlewareOptions = {},\n): FirewallMiddleware {\n const scanInput = options.scanInput ?? true;\n const scanOutput = options.scanOutput ?? false;\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const classifyOrBlock = async (\n text: string,\n hook: HookLabel,\n toolName?: string,\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const result: BlockResult = await firewall.classify(\n text,\n toolName !== undefined ? { hook, toolName } : { hook },\n );\n const threshold = effectiveThreshold(hook);\n const blocked = result.score >= threshold;\n options.onClassify?.({\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n text,\n result,\n blocked,\n });\n if (blocked) {\n const err = new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n });\n options.onBlocked?.(err);\n throw err;\n }\n };\n\n const scanPrompt = async (prompt: ReadonlyArray<VercelPromptMessage>): Promise<void> => {\n if (prompt.length === 0) {\n return;\n }\n const last = prompt[prompt.length - 1]!;\n const role = last.role.toLowerCase();\n\n // If the newest message is a tool result (typical mid-flow in a\n // tool-calling loop), classify each tool-result part individually with\n // tool_response + toolName. No manual wiring from the caller — the\n // toolName is read directly from the Vercel content part.\n if (role === \"tool\") {\n for (const { text, toolName } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, toolName);\n }\n return;\n }\n\n // Otherwise, fall back to scanning the most recent user message.\n // Covers: [user], [system, user], [user, assistant(text), user], etc.\n const lastUser = findLastUserMessage(prompt);\n if (!lastUser) {\n return;\n }\n const text = extractContentText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classifyOrBlock(text, HookLabel.USER_INPUT);\n };\n\n const scanGenerateResult = async (result: VercelGenerateResult): Promise<void> => {\n if (scanOutput && typeof result.text === \"string\" && result.text.length > 0) {\n await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);\n }\n if (options.scanToolCalls && Array.isArray(result.toolCalls)) {\n for (const call of result.toolCalls) {\n if (!call || typeof call !== \"object\") {\n continue;\n }\n const args =\n typeof call.args === \"string\" ? call.args : stringifyToolValue(call.args);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);\n }\n }\n }\n };\n\n return {\n async wrapGenerate<TResult extends VercelGenerateResult & Record<string, unknown>>({\n params,\n doGenerate,\n }: WrapGenerateArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const result = await doGenerate();\n await scanGenerateResult(result);\n return result;\n },\n\n async wrapStream<TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>({\n params,\n doStream,\n }: WrapStreamArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const { stream, ...rest } = await doStream();\n if (!scanOutput) {\n return { stream, ...rest } as unknown as TResult;\n }\n\n let buffered = \"\";\n const transformed = stream.pipeThrough(\n new TransformStream<unknown, unknown>({\n transform(chunk, controller): void {\n const part = chunk as StreamPart;\n if (part && part.type === \"text-delta\") {\n const delta = part.textDelta ?? part.delta ?? \"\";\n buffered += delta;\n }\n controller.enqueue(chunk);\n },\n async flush(controller): Promise<void> {\n if (!buffered.trim()) {\n return;\n }\n try {\n await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);\n } catch (err) {\n if (err instanceof PromptBlockedException) {\n controller.enqueue({ type: \"error\", error: err });\n return;\n }\n throw err;\n }\n },\n }),\n );\n return { stream: transformed, ...rest } as unknown as TResult;\n },\n };\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst CHARS_PER_TOKEN = 4;\n\nexport const MAX_INPUT_TOKENS = 10_240;\nexport const CHUNK_WINDOW = 400;\nexport const CHUNK_OVERLAP = 64;\n\nexport const MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;\nexport const CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;\nexport const CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;\nconst CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;\n\nexport function chunkText(text: string): string[] {\n const n = text.length;\n if (n > MAX_INPUT_CHARS) {\n throw new Error(\n `Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); ` +\n `max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`,\n );\n }\n if (n <= CHUNK_WINDOW_CHARS) {\n return [text];\n }\n const chunks: string[] = [];\n for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {\n const end = start + CHUNK_WINDOW_CHARS;\n chunks.push(text.slice(start, end));\n if (end >= n) {\n break;\n }\n }\n return chunks;\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport { createMiddleware, type FirewallMiddleware } from \"./adapters/vercel.js\";\nimport { chunkText } from \"./chunking.js\";\nimport { SilmarilApiError } from \"./exceptions.js\";\nimport type { HookLabel } from \"./hooks.js\";\nimport type {\n BlockResult,\n ClassifyBatchOptions,\n ClassifyOptions,\n FirewallOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n Prediction,\n} from \"./types.js\";\n\nexport const DEFAULT_THRESHOLD = 0.5;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_RETRIES = 5;\nconst MAX_BACKOFF_SECONDS = 30;\n\ninterface SingleClassifyPayload {\n text: string;\n hook?: string;\n tool_name?: string;\n}\n\ninterface BatchClassifyPayload {\n texts: readonly string[];\n hooks?: readonly string[];\n tool_names?: readonly (string | null)[];\n}\n\ninterface SingleClassifyResponse {\n prediction: Prediction;\n score: number;\n}\n\ninterface BatchClassifyResponse {\n predictions: readonly SingleClassifyResponse[];\n}\n\nexport class Firewall {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly threshold: number;\n readonly timeoutMs: number;\n readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;\n\n private readonly headers: Readonly<Record<string, string>>;\n\n constructor(options: FirewallOptions) {\n if (!options.apiKey) {\n throw new Error(\"Firewall: apiKey is required\");\n }\n if (!options.apiUrl) {\n throw new Error(\"Firewall: apiUrl is required\");\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl;\n this.threshold = options.threshold ?? DEFAULT_THRESHOLD;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.hookThresholds = options.hookThresholds ?? {};\n this.headers = Object.freeze({\n \"x-api-key\": this.apiKey,\n \"content-type\": \"application/json\",\n });\n }\n\n async classify(text: string, options: ClassifyOptions = {}): Promise<BlockResult> {\n const chunks = chunkText(text);\n if (chunks.length === 1) {\n const payload: SingleClassifyPayload = { text: chunks[0]! };\n if (options.hook !== undefined) {\n payload.hook = options.hook;\n }\n if (options.toolName !== undefined) {\n payload.tool_name = options.toolName;\n }\n const data = await this.postWithRetry<SingleClassifyResponse>(payload);\n return Object.freeze({ prediction: data.prediction, score: Number(data.score) });\n }\n const batchOpts: ClassifyBatchOptions = {};\n if (options.hook !== undefined) {\n batchOpts.hooks = chunks.map(() => options.hook!);\n }\n if (options.toolName !== undefined) {\n batchOpts.toolNames = chunks.map(() => options.toolName);\n }\n const results = await this.classifyBatch(chunks, batchOpts);\n return results.reduce((best, r) => (r.score > best.score ? r : best));\n }\n\n async classifyBatch(\n texts: readonly string[],\n options: ClassifyBatchOptions = {},\n ): Promise<BlockResult[]> {\n const payload: BatchClassifyPayload = { texts };\n if (options.hooks && options.hooks.length > 0) {\n payload.hooks = options.hooks.map((h) => String(h));\n }\n if (options.toolNames && options.toolNames.length > 0) {\n payload.tool_names = options.toolNames.map((t) => (t === undefined ? null : t));\n }\n const data = await this.postWithRetry<BatchClassifyResponse>(payload);\n return data.predictions.map((p) =>\n Object.freeze({ prediction: p.prediction, score: Number(p.score) }),\n );\n }\n\n asLangChainHandler(options: LangChainAdapterOptions = {}): Promise<unknown> {\n return import(\"./adapters/langchain.js\").then((m) => m.createLangChainHandler(this, options));\n }\n\n asMiddleware(options: MiddlewareOptions = {}): FirewallMiddleware {\n return createMiddleware(this, options);\n }\n\n effectiveThreshold(hook: HookLabel | undefined): number {\n if (hook === undefined) {\n return this.threshold;\n }\n return this.hookThresholds[hook] ?? this.threshold;\n }\n\n private async postWithRetry<T>(\n payload: SingleClassifyPayload | BatchClassifyPayload,\n maxRetries: number = DEFAULT_MAX_RETRIES,\n ): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const response = await fetch(this.apiUrl, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n if (response.status !== 429 || attempt === maxRetries) {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new SilmarilApiError({\n status: response.status,\n statusText: response.statusText,\n body,\n });\n }\n return (await response.json()) as T;\n }\n const waitSeconds = Math.min(2 ** attempt, MAX_BACKOFF_SECONDS);\n await new Promise<void>((resolve) => setTimeout(resolve, waitSeconds * 1000));\n }\n throw new Error(\"Firewall: exhausted retries (unreachable)\");\n }\n}\n"],"mappings":"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","names":["findLastUserMessage","text"]}
1
+ {"version":3,"sources":["../src/exceptions.ts","../src/hooks.ts","../src/utils/extract.ts","../src/adapters/langchain.ts","../src/index.ts","../src/adapters/vercel.ts","../src/chunking.ts","../src/firewall.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport class PromptBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n }) {\n super(PromptBlockedException.formatMessage(params));\n this.name = \"PromptBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n Object.setPrototypeOf(this, PromptBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\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 return (\n `Prompt blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n\n constructor(params: { status: number; statusText: string; body: string }) {\n super(`Silmaril API error ${params.status} ${params.statusText}: ${params.body}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\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]);\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\nexport const DEFAULT_HOOK_THRESHOLDS: Readonly<Record<HookLabel, number>> = {\n [HookLabel.USER_INPUT]: 0.5,\n [HookLabel.SYSTEM_PROMPT]: 0.5,\n [HookLabel.TOOL_CALL]: 0.5,\n [HookLabel.TOOL_RESPONSE]: 0.5,\n [HookLabel.LLM_OUTPUT]: 0.5,\n [HookLabel.UNKNOWN]: 0.5,\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","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst SKIP_ROLES: ReadonlySet<string> = new Set([\"ai\", \"assistant\"]);\nconst SYSTEM_ROLES: ReadonlySet<string> = new Set([\"system\"]);\nconst TOOL_ROLES: ReadonlySet<string> = new Set([\"tool\", \"function\"]);\n\ntype MessageContent = string | ReadonlyArray<string | { type?: string; text?: string }>;\n\ninterface DucktypedMessage {\n role?: string;\n type?: string;\n content?: MessageContent;\n}\n\ninterface DucktypedGeneration {\n text?: string;\n}\n\ninterface DucktypedLLMResult {\n generations?: ReadonlyArray<ReadonlyArray<DucktypedGeneration>>;\n}\n\ninterface DucktypedDocument {\n pageContent?: string;\n}\n\nfunction extractContentText(content: MessageContent | undefined): string {\n if (typeof content === \"string\") {\n return content;\n }\n if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && typeof block === \"object\" && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n }\n return \"\";\n}\n\nfunction getRole(message: DucktypedMessage): string {\n if (message.role !== undefined) {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nexport function extractTextFromMessages(\n messages: ReadonlyArray<DucktypedMessage>,\n options: { includeSystem?: boolean; includeTool?: boolean } = {},\n): string {\n const includeSystem = options.includeSystem ?? true;\n const includeTool = options.includeTool ?? true;\n const parts: string[] = [];\n for (const message of messages) {\n const role = getRole(message);\n if (SKIP_ROLES.has(role)) {\n continue;\n }\n if (!includeSystem && SYSTEM_ROLES.has(role)) {\n continue;\n }\n if (!includeTool && TOOL_ROLES.has(role)) {\n continue;\n }\n const text = extractContentText(message.content);\n const stripped = text.trim();\n if (stripped) {\n parts.push(stripped);\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromPrompts(prompts: readonly string[]): string {\n return prompts\n .map((p) => p.trim())\n .filter((p) => p.length > 0)\n .join(\"\\n\");\n}\n\nexport function extractTextFromToolInput(inputStr: string): string {\n return inputStr.trim();\n}\n\nexport function extractTextFromLLMResult(response: DucktypedLLMResult): string {\n const parts: string[] = [];\n for (const genList of response.generations ?? []) {\n for (const gen of genList) {\n const text = (gen.text ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromDocuments(documents: ReadonlyArray<DucktypedDocument>): string {\n const parts: string[] = [];\n for (const doc of documents) {\n const text = (doc.pageContent ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n return parts.join(\"\\n\");\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { BlockResult, LangChainAdapterOptions } from \"../types.js\";\nimport {\n extractTextFromDocuments,\n extractTextFromLLMResult,\n extractTextFromPrompts,\n extractTextFromToolInput,\n} from \"../utils/extract.js\";\n\nconst USER_ROLES: ReadonlySet<string> = new Set([\"human\", \"user\"]);\n\ninterface LangChainDucktypedMessage {\n role?: string;\n type?: string;\n content?: string | ReadonlyArray<string | { type?: string; text?: string }>;\n}\n\nfunction getMessageRole(message: LangChainDucktypedMessage): string {\n if (typeof message.role === \"string\") {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nfunction extractMessageText(content: LangChainDucktypedMessage[\"content\"]): string {\n if (content === undefined) {\n return \"\";\n }\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<LangChainDucktypedMessage>,\n): LangChainDucktypedMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (USER_ROLES.has(getMessageRole(msg))) {\n return msg;\n }\n }\n return undefined;\n}\n\nexport async function createLangChainHandler(\n firewall: Firewall,\n options: LangChainAdapterOptions = {},\n): Promise<BaseCallbackHandler> {\n const { BaseCallbackHandler } = await import(\"@langchain/core/callbacks/base\");\n\n const enabledHooks = resolveHooks(options.hooks);\n // includeSystem / includeTool are retained in LangChainAdapterOptions for\n // backwards compat but are inert under the per-message hook-routing algorithm:\n // handleChatModelStart only scans the last user-role message; system and tool\n // messages are either trusted or already classified by their own hooks.\n const failOpen = options.failOpen ?? true;\n const logger =\n options.logger ??\n ((message: string, error: unknown): void => {\n // eslint-disable-next-line no-console\n console.warn(`silmaril.firewall: ${message}`, error);\n });\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n const shadowMode = options.shadowMode ?? firewall.shadowMode;\n const onClassify = options.onClassify;\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const fireOnClassify = (\n hookLabel: HookLabel,\n text: string,\n result: BlockResult,\n blocked: boolean,\n toolName: string | undefined,\n ): void => {\n if (!onClassify) {\n return;\n }\n try {\n onClassify({\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n text,\n result,\n blocked,\n shadowMode,\n });\n } catch (err) {\n logger(\"onClassify callback threw\", err);\n }\n };\n\n const classify = async (\n text: string,\n hookLabel: HookLabel,\n runId: string,\n toolName?: string,\n ): Promise<void> => {\n let result: BlockResult;\n try {\n result = await firewall.classify(text, {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n });\n } catch (err) {\n if (!failOpen) {\n throw err;\n }\n logger(\"classification failed, allowing prompt through\", err);\n return;\n }\n const threshold = effectiveThreshold(hookLabel);\n const blocked = result.score >= threshold;\n fireOnClassify(hookLabel, text, result, blocked, toolName);\n if (blocked && !shadowMode) {\n throw new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n });\n }\n };\n\n class SilmarilFirewallHandler extends BaseCallbackHandler {\n override name = \"silmaril_firewall_handler\";\n override raiseError = true;\n override awaitHandlers = true;\n\n override async handleChatModelStart(\n _llm: unknown,\n messages: unknown,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {\n return;\n }\n const batches = messages as ReadonlyArray<ReadonlyArray<LangChainDucktypedMessage>>;\n const flat: LangChainDucktypedMessage[] = [];\n for (const batch of batches) {\n for (const m of batch) {\n flat.push(m);\n }\n }\n const lastUser = findLastUserMessage(flat);\n if (!lastUser) {\n return;\n }\n const text = extractMessageText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);\n }\n\n override async handleLLMStart(\n _llm: unknown,\n prompts: string[],\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_START)) {\n return;\n }\n const text = extractTextFromPrompts(prompts);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);\n }\n\n override async handleToolStart(\n tool: { name?: string } | undefined,\n inputStr: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_START)) {\n return;\n }\n const text = extractTextFromToolInput(inputStr);\n if (!text) {\n return;\n }\n const toolName = tool?.name;\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);\n }\n\n override async handleRetrieverStart(\n _retriever: unknown,\n query: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {\n return;\n }\n const text = query.trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);\n }\n\n override async handleLLMEnd(output: unknown, runId: string): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_END)) {\n return;\n }\n const text = extractTextFromLLMResult(output as { generations?: ReadonlyArray<ReadonlyArray<{ text?: string }>> });\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);\n }\n\n override async handleToolEnd(\n output: unknown,\n runId: string,\n _parentRunId?: string,\n _tags?: string[],\n _kwargs?: { name?: string },\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_END)) {\n return;\n }\n const text = String(output).trim();\n if (!text) {\n return;\n }\n const toolName = _kwargs?.name;\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);\n }\n\n override async handleRetrieverEnd(\n documents: ReadonlyArray<{ pageContent?: string }>,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {\n return;\n }\n const text = extractTextFromDocuments(documents);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);\n }\n }\n\n return new SilmarilFirewallHandler();\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport {\n Firewall,\n DEFAULT_THRESHOLD,\n DEFAULT_TIMEOUT_MS,\n} from \"./firewall.js\";\n\nexport { PromptBlockedException, SilmarilApiError } from \"./exceptions.js\";\n\nexport type { FirewallMiddleware } from \"./adapters/vercel.js\";\n\nexport {\n chunkText,\n MAX_INPUT_TOKENS,\n CHUNK_WINDOW,\n CHUNK_OVERLAP,\n MAX_INPUT_CHARS,\n CHUNK_WINDOW_CHARS,\n CHUNK_OVERLAP_CHARS,\n} from \"./chunking.js\";\n\nexport {\n HookLabel,\n FirewallHook,\n DEFAULT_HOOKS,\n INPUT_HOOKS,\n OUTPUT_HOOKS,\n ALL_HOOKS,\n DEFAULT_HOOK_THRESHOLDS,\n FIREWALL_HOOK_TO_LABEL,\n resolveHooks,\n prependHook,\n prependToolName,\n} from \"./hooks.js\";\n\nexport type {\n BlockResult,\n ClassifyEvent,\n Prediction,\n ClassificationMetadata,\n FirewallOptions,\n ClassifyOptions,\n ClassifyBatchOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n} from \"./types.js\";\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { BlockResult, MiddlewareOptions } from \"../types.js\";\n\ninterface VercelContentPart {\n type?: string;\n text?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n}\n\ninterface VercelPromptMessage {\n role: string;\n content: string | ReadonlyArray<VercelContentPart | string>;\n}\n\ninterface VercelToolCall {\n toolCallType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n}\n\ninterface VercelGenerateResult {\n text?: string;\n toolCalls?: ReadonlyArray<VercelToolCall>;\n}\n\ninterface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doGenerate: () => PromiseLike<TResult>;\n}\n\ninterface WrapStreamArgs<\n TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>,\n> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doStream: () => PromiseLike<TResult>;\n}\n\ninterface StreamPart {\n type?: string;\n textDelta?: string;\n delta?: string;\n}\n\nfunction stringifyToolValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n\nfunction extractContentText(content: VercelPromptMessage[\"content\"]): string {\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction iterateToolResultParts(\n message: VercelPromptMessage,\n): Array<{ text: string; toolName: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined }> = [];\n for (const part of message.content) {\n if (typeof part === \"string\") {\n continue;\n }\n if (part.type === \"tool-result\") {\n const text = stringifyToolValue(part.result);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName });\n }\n }\n }\n return out;\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<VercelPromptMessage>,\n): VercelPromptMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role.toLowerCase() === \"user\") {\n return msg;\n }\n }\n return undefined;\n}\n\nexport interface FirewallMiddleware {\n wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(\n args: WrapGenerateArgs<TResult>,\n ) => Promise<TResult>;\n wrapStream: <TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>(\n args: WrapStreamArgs<TResult>,\n ) => Promise<TResult>;\n}\n\nexport function createMiddleware(\n firewall: Firewall,\n options: MiddlewareOptions = {},\n): FirewallMiddleware {\n const scanInput = options.scanInput ?? true;\n const scanOutput = options.scanOutput ?? false;\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n const shadowMode = options.shadowMode ?? firewall.shadowMode;\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const classifyOrBlock = async (\n text: string,\n hook: HookLabel,\n toolName?: string,\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const result: BlockResult = await firewall.classify(\n text,\n toolName !== undefined ? { hook, toolName } : { hook },\n );\n const threshold = effectiveThreshold(hook);\n const blocked = result.score >= threshold;\n options.onClassify?.({\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n text,\n result,\n blocked,\n shadowMode,\n });\n if (blocked && !shadowMode) {\n const err = new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n });\n options.onBlocked?.(err);\n throw err;\n }\n };\n\n const scanPrompt = async (prompt: ReadonlyArray<VercelPromptMessage>): Promise<void> => {\n if (prompt.length === 0) {\n return;\n }\n const last = prompt[prompt.length - 1]!;\n const role = last.role.toLowerCase();\n\n // If the newest message is a tool result (typical mid-flow in a\n // tool-calling loop), classify each tool-result part individually with\n // tool_response + toolName. No manual wiring from the caller — the\n // toolName is read directly from the Vercel content part.\n if (role === \"tool\") {\n for (const { text, toolName } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, toolName);\n }\n return;\n }\n\n // Otherwise, fall back to scanning the most recent user message.\n // Covers: [user], [system, user], [user, assistant(text), user], etc.\n const lastUser = findLastUserMessage(prompt);\n if (!lastUser) {\n return;\n }\n const text = extractContentText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classifyOrBlock(text, HookLabel.USER_INPUT);\n };\n\n const scanGenerateResult = async (result: VercelGenerateResult): Promise<void> => {\n if (scanOutput && typeof result.text === \"string\" && result.text.length > 0) {\n await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);\n }\n if (options.scanToolCalls && Array.isArray(result.toolCalls)) {\n for (const call of result.toolCalls) {\n if (!call || typeof call !== \"object\") {\n continue;\n }\n const args =\n typeof call.args === \"string\" ? call.args : stringifyToolValue(call.args);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);\n }\n }\n }\n };\n\n return {\n async wrapGenerate<TResult extends VercelGenerateResult & Record<string, unknown>>({\n params,\n doGenerate,\n }: WrapGenerateArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const result = await doGenerate();\n await scanGenerateResult(result);\n return result;\n },\n\n async wrapStream<TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>({\n params,\n doStream,\n }: WrapStreamArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const { stream, ...rest } = await doStream();\n if (!scanOutput) {\n return { stream, ...rest } as unknown as TResult;\n }\n\n let buffered = \"\";\n const transformed = stream.pipeThrough(\n new TransformStream<unknown, unknown>({\n transform(chunk, controller): void {\n const part = chunk as StreamPart;\n if (part && part.type === \"text-delta\") {\n const delta = part.textDelta ?? part.delta ?? \"\";\n buffered += delta;\n }\n controller.enqueue(chunk);\n },\n async flush(controller): Promise<void> {\n if (!buffered.trim()) {\n return;\n }\n try {\n await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);\n } catch (err) {\n if (err instanceof PromptBlockedException) {\n controller.enqueue({ type: \"error\", error: err });\n return;\n }\n throw err;\n }\n },\n }),\n );\n return { stream: transformed, ...rest } as unknown as TResult;\n },\n };\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst CHARS_PER_TOKEN = 4;\n\nexport const MAX_INPUT_TOKENS = 10_240;\nexport const CHUNK_WINDOW = 400;\nexport const CHUNK_OVERLAP = 64;\n\nexport const MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;\nexport const CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;\nexport const CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;\nconst CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;\n\nexport function chunkText(text: string): string[] {\n const n = text.length;\n if (n > MAX_INPUT_CHARS) {\n throw new Error(\n `Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); ` +\n `max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`,\n );\n }\n if (n <= CHUNK_WINDOW_CHARS) {\n return [text];\n }\n const chunks: string[] = [];\n for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {\n const end = start + CHUNK_WINDOW_CHARS;\n chunks.push(text.slice(start, end));\n if (end >= n) {\n break;\n }\n }\n return chunks;\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nimport { createMiddleware, type FirewallMiddleware } from \"./adapters/vercel.js\";\nimport { chunkText } from \"./chunking.js\";\nimport { SilmarilApiError } from \"./exceptions.js\";\nimport type { HookLabel } from \"./hooks.js\";\nimport type {\n BlockResult,\n ClassifyBatchOptions,\n ClassifyOptions,\n ClassificationMetadata,\n FirewallOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n Prediction,\n} from \"./types.js\";\n\nexport const DEFAULT_THRESHOLD = 0.5;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_RETRIES = 5;\nconst MAX_BACKOFF_SECONDS = 30;\n\ninterface SingleClassifyPayload {\n text: string;\n hook?: string;\n tool_name?: string;\n metadata?: ClassificationMetadata;\n}\n\ninterface BatchClassifyPayload {\n texts: readonly string[];\n hooks?: readonly string[];\n tool_names?: readonly (string | null)[];\n metadata?: readonly (ClassificationMetadata | null)[];\n}\n\ninterface SingleClassifyResponse {\n prediction: Prediction;\n score: number;\n primary_outcome?: string;\n outcome_scores?: Record<string, number>;\n detector_scores?: Record<string, number>;\n detector_counts?: Record<string, number>;\n}\n\ninterface BatchClassifyResponse {\n predictions: readonly SingleClassifyResponse[];\n}\n\nfunction blockResultFromResponse(data: SingleClassifyResponse): BlockResult {\n const result: {\n prediction: Prediction;\n score: number;\n primaryOutcome?: string;\n outcomeScores?: Readonly<Record<string, number>>;\n detectorScores?: Readonly<Record<string, number>>;\n detectorCounts?: Readonly<Record<string, number>>;\n } = {\n prediction: data.prediction,\n score: Number(data.score),\n };\n if (data.primary_outcome !== undefined) {\n result.primaryOutcome = data.primary_outcome;\n }\n if (data.outcome_scores !== undefined) {\n result.outcomeScores = Object.freeze(\n Object.fromEntries(Object.entries(data.outcome_scores).map(([k, v]) => [k, Number(v)])),\n );\n }\n if (data.detector_scores !== undefined) {\n result.detectorScores = Object.freeze(\n Object.fromEntries(Object.entries(data.detector_scores).map(([k, v]) => [k, Number(v)])),\n );\n }\n if (data.detector_counts !== undefined) {\n result.detectorCounts = Object.freeze(\n Object.fromEntries(Object.entries(data.detector_counts).map(([k, v]) => [k, Number(v)])),\n );\n }\n return Object.freeze(result);\n}\n\nexport class Firewall {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly threshold: number;\n readonly timeoutMs: number;\n readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;\n readonly shadowMode: boolean;\n\n private readonly headers: Readonly<Record<string, string>>;\n\n constructor(options: FirewallOptions) {\n if (!options.apiKey) {\n throw new Error(\"Firewall: apiKey is required\");\n }\n if (!options.apiUrl) {\n throw new Error(\"Firewall: apiUrl is required\");\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl;\n this.threshold = options.threshold ?? DEFAULT_THRESHOLD;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.hookThresholds = Object.freeze({ ...(options.hookThresholds ?? {}) });\n this.shadowMode = options.shadowMode ?? false;\n this.headers = Object.freeze({\n \"x-api-key\": this.apiKey,\n \"content-type\": \"application/json\",\n });\n }\n\n async classify(text: string, options: ClassifyOptions = {}): Promise<BlockResult> {\n const chunks = chunkText(text);\n if (chunks.length === 1) {\n const payload: SingleClassifyPayload = { text: chunks[0]! };\n if (options.hook !== undefined) {\n payload.hook = options.hook;\n }\n if (options.toolName !== undefined) {\n payload.tool_name = options.toolName;\n }\n if (options.metadata !== undefined) {\n payload.metadata = options.metadata;\n }\n const data = await this.postWithRetry<SingleClassifyResponse>(payload);\n return blockResultFromResponse(data);\n }\n const batchOpts: ClassifyBatchOptions = {};\n if (options.hook !== undefined) {\n batchOpts.hooks = chunks.map(() => options.hook!);\n }\n if (options.toolName !== undefined) {\n batchOpts.toolNames = chunks.map(() => options.toolName);\n }\n if (options.metadata !== undefined) {\n batchOpts.metadata = chunks.map(() => options.metadata);\n }\n const results = await this.classifyBatch(chunks, batchOpts);\n return results.reduce((best, r) => (r.score > best.score ? r : best));\n }\n\n async classifyBatch(\n texts: readonly string[],\n options: ClassifyBatchOptions = {},\n ): Promise<BlockResult[]> {\n if (options.hooks !== undefined && options.hooks.length !== texts.length) {\n throw new Error(\n `Firewall: hooks length ${options.hooks.length} does not match texts length ${texts.length}`,\n );\n }\n if (options.toolNames !== undefined && options.toolNames.length !== texts.length) {\n throw new Error(\n `Firewall: toolNames length ${options.toolNames.length} does not match texts length ${texts.length}`,\n );\n }\n if (options.metadata !== undefined && options.metadata.length !== texts.length) {\n throw new Error(\n `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`,\n );\n }\n\n const payload: BatchClassifyPayload = { texts };\n if (options.hooks && options.hooks.length > 0) {\n payload.hooks = options.hooks.map((h) => String(h));\n }\n if (options.toolNames && options.toolNames.length > 0) {\n payload.tool_names = options.toolNames.map((t) => (t === undefined ? null : t));\n }\n if (options.metadata && options.metadata.length > 0) {\n payload.metadata = options.metadata.map((m) => (m === undefined ? null : m));\n }\n const data = await this.postWithRetry<BatchClassifyResponse>(payload);\n return data.predictions.map((p) => blockResultFromResponse(p));\n }\n\n asLangChainHandler(options: LangChainAdapterOptions = {}): Promise<BaseCallbackHandler> {\n return import(\"./adapters/langchain.js\").then((m) => m.createLangChainHandler(this, options));\n }\n\n asMiddleware(options: MiddlewareOptions = {}): FirewallMiddleware {\n return createMiddleware(this, options);\n }\n\n effectiveThreshold(hook: HookLabel | undefined): number {\n if (hook === undefined) {\n return this.threshold;\n }\n return this.hookThresholds[hook] ?? this.threshold;\n }\n\n private async postWithRetry<T>(\n payload: SingleClassifyPayload | BatchClassifyPayload,\n maxRetries: number = DEFAULT_MAX_RETRIES,\n ): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const response = await fetch(this.apiUrl, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n if (response.status !== 429 || attempt === maxRetries) {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new SilmarilApiError({\n status: response.status,\n statusText: response.statusText,\n body,\n });\n }\n return (await response.json()) as T;\n }\n const waitSeconds = Math.min(2 ** attempt, MAX_BACKOFF_SECONDS);\n await new Promise<void>((resolve) => setTimeout(resolve, waitSeconds * 1000));\n }\n throw new Error(\"Firewall: exhausted retries (unreachable)\");\n }\n}\n"],"mappings":"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","names":["findLastUserMessage","text"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
2
+
1
3
  declare const HookLabel: {
2
4
  readonly USER_INPUT: "user_input";
3
5
  readonly SYSTEM_PROMPT: "system_prompt";
@@ -31,6 +33,10 @@ type Prediction = "BENIGN" | "MALICIOUS";
31
33
  interface BlockResult {
32
34
  readonly prediction: Prediction;
33
35
  readonly score: number;
36
+ readonly primaryOutcome?: string;
37
+ readonly outcomeScores?: Readonly<Record<string, number>>;
38
+ readonly detectorScores?: Readonly<Record<string, number>>;
39
+ readonly detectorCounts?: Readonly<Record<string, number>>;
34
40
  }
35
41
  interface FirewallOptions {
36
42
  apiKey: string;
@@ -38,14 +44,18 @@ interface FirewallOptions {
38
44
  threshold?: number;
39
45
  timeoutMs?: number;
40
46
  hookThresholds?: Partial<Record<HookLabel, number>>;
47
+ shadowMode?: boolean;
41
48
  }
49
+ type ClassificationMetadata = Readonly<Record<string, unknown>>;
42
50
  interface ClassifyOptions {
43
51
  hook?: HookLabel;
44
52
  toolName?: string;
53
+ metadata?: ClassificationMetadata;
45
54
  }
46
55
  interface ClassifyBatchOptions {
47
56
  hooks?: readonly HookLabel[];
48
57
  toolNames?: readonly (string | undefined)[];
58
+ metadata?: readonly (ClassificationMetadata | undefined)[];
49
59
  }
50
60
  interface LangChainAdapterOptions {
51
61
  threshold?: number;
@@ -55,6 +65,8 @@ interface LangChainAdapterOptions {
55
65
  includeTool?: boolean;
56
66
  failOpen?: boolean;
57
67
  logger?: (message: string, error: unknown) => void;
68
+ shadowMode?: boolean;
69
+ onClassify?: (event: ClassifyEvent) => void;
58
70
  }
59
71
  interface ClassifyEvent {
60
72
  readonly hook: HookLabel;
@@ -62,6 +74,7 @@ interface ClassifyEvent {
62
74
  readonly text: string;
63
75
  readonly result: BlockResult;
64
76
  readonly blocked: boolean;
77
+ readonly shadowMode: boolean;
65
78
  }
66
79
  interface MiddlewareOptions {
67
80
  scanInput?: boolean;
@@ -69,6 +82,7 @@ interface MiddlewareOptions {
69
82
  scanToolCalls?: boolean;
70
83
  threshold?: number;
71
84
  hookThresholds?: Partial<Record<HookLabel, number>>;
85
+ shadowMode?: boolean;
72
86
  onBlocked?: (err: Error) => void;
73
87
  onClassify?: (event: ClassifyEvent) => void;
74
88
  }
@@ -124,11 +138,12 @@ declare class Firewall {
124
138
  readonly threshold: number;
125
139
  readonly timeoutMs: number;
126
140
  readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;
141
+ readonly shadowMode: boolean;
127
142
  private readonly headers;
128
143
  constructor(options: FirewallOptions);
129
144
  classify(text: string, options?: ClassifyOptions): Promise<BlockResult>;
130
145
  classifyBatch(texts: readonly string[], options?: ClassifyBatchOptions): Promise<BlockResult[]>;
131
- asLangChainHandler(options?: LangChainAdapterOptions): Promise<unknown>;
146
+ asLangChainHandler(options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
132
147
  asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
133
148
  effectiveThreshold(hook: HookLabel | undefined): number;
134
149
  private postWithRetry;
@@ -166,4 +181,4 @@ declare const CHUNK_WINDOW_CHARS: number;
166
181
  declare const CHUNK_OVERLAP_CHARS: number;
167
182
  declare function chunkText(text: string): string[];
168
183
 
169
- export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_HOOKS, DEFAULT_HOOK_THRESHOLDS, DEFAULT_THRESHOLD, DEFAULT_TIMEOUT_MS, FIREWALL_HOOK_TO_LABEL, Firewall, FirewallHook, type FirewallMiddleware, type FirewallOptions, HookLabel, INPUT_HOOKS, type LangChainAdapterOptions, MAX_INPUT_CHARS, MAX_INPUT_TOKENS, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks };
184
+ export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassificationMetadata, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_HOOKS, DEFAULT_HOOK_THRESHOLDS, DEFAULT_THRESHOLD, DEFAULT_TIMEOUT_MS, FIREWALL_HOOK_TO_LABEL, Firewall, FirewallHook, type FirewallMiddleware, type FirewallOptions, HookLabel, INPUT_HOOKS, type LangChainAdapterOptions, MAX_INPUT_CHARS, MAX_INPUT_TOKENS, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
2
+
1
3
  declare const HookLabel: {
2
4
  readonly USER_INPUT: "user_input";
3
5
  readonly SYSTEM_PROMPT: "system_prompt";
@@ -31,6 +33,10 @@ type Prediction = "BENIGN" | "MALICIOUS";
31
33
  interface BlockResult {
32
34
  readonly prediction: Prediction;
33
35
  readonly score: number;
36
+ readonly primaryOutcome?: string;
37
+ readonly outcomeScores?: Readonly<Record<string, number>>;
38
+ readonly detectorScores?: Readonly<Record<string, number>>;
39
+ readonly detectorCounts?: Readonly<Record<string, number>>;
34
40
  }
35
41
  interface FirewallOptions {
36
42
  apiKey: string;
@@ -38,14 +44,18 @@ interface FirewallOptions {
38
44
  threshold?: number;
39
45
  timeoutMs?: number;
40
46
  hookThresholds?: Partial<Record<HookLabel, number>>;
47
+ shadowMode?: boolean;
41
48
  }
49
+ type ClassificationMetadata = Readonly<Record<string, unknown>>;
42
50
  interface ClassifyOptions {
43
51
  hook?: HookLabel;
44
52
  toolName?: string;
53
+ metadata?: ClassificationMetadata;
45
54
  }
46
55
  interface ClassifyBatchOptions {
47
56
  hooks?: readonly HookLabel[];
48
57
  toolNames?: readonly (string | undefined)[];
58
+ metadata?: readonly (ClassificationMetadata | undefined)[];
49
59
  }
50
60
  interface LangChainAdapterOptions {
51
61
  threshold?: number;
@@ -55,6 +65,8 @@ interface LangChainAdapterOptions {
55
65
  includeTool?: boolean;
56
66
  failOpen?: boolean;
57
67
  logger?: (message: string, error: unknown) => void;
68
+ shadowMode?: boolean;
69
+ onClassify?: (event: ClassifyEvent) => void;
58
70
  }
59
71
  interface ClassifyEvent {
60
72
  readonly hook: HookLabel;
@@ -62,6 +74,7 @@ interface ClassifyEvent {
62
74
  readonly text: string;
63
75
  readonly result: BlockResult;
64
76
  readonly blocked: boolean;
77
+ readonly shadowMode: boolean;
65
78
  }
66
79
  interface MiddlewareOptions {
67
80
  scanInput?: boolean;
@@ -69,6 +82,7 @@ interface MiddlewareOptions {
69
82
  scanToolCalls?: boolean;
70
83
  threshold?: number;
71
84
  hookThresholds?: Partial<Record<HookLabel, number>>;
85
+ shadowMode?: boolean;
72
86
  onBlocked?: (err: Error) => void;
73
87
  onClassify?: (event: ClassifyEvent) => void;
74
88
  }
@@ -124,11 +138,12 @@ declare class Firewall {
124
138
  readonly threshold: number;
125
139
  readonly timeoutMs: number;
126
140
  readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;
141
+ readonly shadowMode: boolean;
127
142
  private readonly headers;
128
143
  constructor(options: FirewallOptions);
129
144
  classify(text: string, options?: ClassifyOptions): Promise<BlockResult>;
130
145
  classifyBatch(texts: readonly string[], options?: ClassifyBatchOptions): Promise<BlockResult[]>;
131
- asLangChainHandler(options?: LangChainAdapterOptions): Promise<unknown>;
146
+ asLangChainHandler(options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
132
147
  asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
133
148
  effectiveThreshold(hook: HookLabel | undefined): number;
134
149
  private postWithRetry;
@@ -166,4 +181,4 @@ declare const CHUNK_WINDOW_CHARS: number;
166
181
  declare const CHUNK_OVERLAP_CHARS: number;
167
182
  declare function chunkText(text: string): string[];
168
183
 
169
- export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_HOOKS, DEFAULT_HOOK_THRESHOLDS, DEFAULT_THRESHOLD, DEFAULT_TIMEOUT_MS, FIREWALL_HOOK_TO_LABEL, Firewall, FirewallHook, type FirewallMiddleware, type FirewallOptions, HookLabel, INPUT_HOOKS, type LangChainAdapterOptions, MAX_INPUT_CHARS, MAX_INPUT_TOKENS, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks };
184
+ export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassificationMetadata, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_HOOKS, DEFAULT_HOOK_THRESHOLDS, DEFAULT_THRESHOLD, DEFAULT_TIMEOUT_MS, FIREWALL_HOOK_TO_LABEL, Firewall, FirewallHook, type FirewallMiddleware, type FirewallOptions, HookLabel, INPUT_HOOKS, type LangChainAdapterOptions, MAX_INPUT_CHARS, MAX_INPUT_TOKENS, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks };
package/dist/index.js CHANGED
@@ -129,10 +129,10 @@ var init_hooks = __esm({
129
129
  };
130
130
  DEFAULT_HOOK_THRESHOLDS = {
131
131
  [HookLabel.USER_INPUT]: 0.5,
132
- [HookLabel.SYSTEM_PROMPT]: 0.3,
133
- [HookLabel.TOOL_CALL]: 0.4,
134
- [HookLabel.TOOL_RESPONSE]: 0.3,
135
- [HookLabel.LLM_OUTPUT]: 0.4,
132
+ [HookLabel.SYSTEM_PROMPT]: 0.5,
133
+ [HookLabel.TOOL_CALL]: 0.5,
134
+ [HookLabel.TOOL_RESPONSE]: 0.5,
135
+ [HookLabel.LLM_OUTPUT]: 0.5,
136
136
  [HookLabel.UNKNOWN]: 0.5
137
137
  };
138
138
  FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
@@ -223,6 +223,8 @@ async function createLangChainHandler(firewall, options = {}) {
223
223
  });
224
224
  const overrideThreshold = options.threshold;
225
225
  const overrideHookThresholds = options.hookThresholds ?? {};
226
+ const shadowMode = options.shadowMode ?? firewall.shadowMode;
227
+ const onClassify = options.onClassify;
226
228
  const effectiveThreshold = (label) => {
227
229
  if (overrideHookThresholds[label] !== void 0) {
228
230
  return overrideHookThresholds[label];
@@ -232,29 +234,47 @@ async function createLangChainHandler(firewall, options = {}) {
232
234
  }
233
235
  return overrideThreshold ?? firewall.threshold;
234
236
  };
237
+ const fireOnClassify = (hookLabel, text, result, blocked, toolName) => {
238
+ if (!onClassify) {
239
+ return;
240
+ }
241
+ try {
242
+ onClassify({
243
+ hook: hookLabel,
244
+ ...toolName !== void 0 ? { toolName } : {},
245
+ text,
246
+ result,
247
+ blocked,
248
+ shadowMode
249
+ });
250
+ } catch (err) {
251
+ logger("onClassify callback threw", err);
252
+ }
253
+ };
235
254
  const classify = async (text, hookLabel, runId, toolName) => {
255
+ let result;
236
256
  try {
237
- const result = await firewall.classify(text, {
257
+ result = await firewall.classify(text, {
238
258
  hook: hookLabel,
239
259
  ...toolName !== void 0 ? { toolName } : {}
240
260
  });
241
- const threshold = effectiveThreshold(hookLabel);
242
- if (result.score >= threshold) {
243
- throw new PromptBlockedException({
244
- score: result.score,
245
- threshold,
246
- promptText: text,
247
- runId
248
- });
249
- }
250
261
  } catch (err) {
251
- if (err instanceof PromptBlockedException) {
252
- throw err;
253
- }
254
262
  if (!failOpen) {
255
263
  throw err;
256
264
  }
257
265
  logger("classification failed, allowing prompt through", err);
266
+ return;
267
+ }
268
+ const threshold = effectiveThreshold(hookLabel);
269
+ const blocked = result.score >= threshold;
270
+ fireOnClassify(hookLabel, text, result, blocked, toolName);
271
+ if (blocked && !shadowMode) {
272
+ throw new PromptBlockedException({
273
+ score: result.score,
274
+ threshold,
275
+ promptText: text,
276
+ runId
277
+ });
258
278
  }
259
279
  };
260
280
  class SilmarilFirewallHandler extends BaseCallbackHandler {
@@ -301,8 +321,7 @@ async function createLangChainHandler(firewall, options = {}) {
301
321
  return;
302
322
  }
303
323
  const toolName = tool?.name;
304
- const prefixed = prependToolName(text, toolName);
305
- await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);
324
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);
306
325
  }
307
326
  async handleRetrieverStart(_retriever, query, runId) {
308
327
  if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {
@@ -333,8 +352,7 @@ async function createLangChainHandler(firewall, options = {}) {
333
352
  return;
334
353
  }
335
354
  const toolName = _kwargs?.name;
336
- const prefixed = prependToolName(text, toolName);
337
- await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);
355
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);
338
356
  }
339
357
  async handleRetrieverEnd(documents, runId) {
340
358
  if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {
@@ -422,6 +440,7 @@ function createMiddleware(firewall, options = {}) {
422
440
  const scanOutput = options.scanOutput ?? false;
423
441
  const overrideThreshold = options.threshold;
424
442
  const overrideHookThresholds = options.hookThresholds ?? {};
443
+ const shadowMode = options.shadowMode ?? firewall.shadowMode;
425
444
  const effectiveThreshold = (label) => {
426
445
  if (overrideHookThresholds[label] !== void 0) {
427
446
  return overrideHookThresholds[label];
@@ -446,9 +465,10 @@ function createMiddleware(firewall, options = {}) {
446
465
  ...toolName !== void 0 ? { toolName } : {},
447
466
  text,
448
467
  result,
449
- blocked
468
+ blocked,
469
+ shadowMode
450
470
  });
451
- if (blocked) {
471
+ if (blocked && !shadowMode) {
452
472
  const err = new PromptBlockedException({
453
473
  score: result.score,
454
474
  threshold,
@@ -588,12 +608,38 @@ var DEFAULT_THRESHOLD = 0.5;
588
608
  var DEFAULT_TIMEOUT_MS = 1e4;
589
609
  var DEFAULT_MAX_RETRIES = 5;
590
610
  var MAX_BACKOFF_SECONDS = 30;
611
+ function blockResultFromResponse(data) {
612
+ const result = {
613
+ prediction: data.prediction,
614
+ score: Number(data.score)
615
+ };
616
+ if (data.primary_outcome !== void 0) {
617
+ result.primaryOutcome = data.primary_outcome;
618
+ }
619
+ if (data.outcome_scores !== void 0) {
620
+ result.outcomeScores = Object.freeze(
621
+ Object.fromEntries(Object.entries(data.outcome_scores).map(([k, v]) => [k, Number(v)]))
622
+ );
623
+ }
624
+ if (data.detector_scores !== void 0) {
625
+ result.detectorScores = Object.freeze(
626
+ Object.fromEntries(Object.entries(data.detector_scores).map(([k, v]) => [k, Number(v)]))
627
+ );
628
+ }
629
+ if (data.detector_counts !== void 0) {
630
+ result.detectorCounts = Object.freeze(
631
+ Object.fromEntries(Object.entries(data.detector_counts).map(([k, v]) => [k, Number(v)]))
632
+ );
633
+ }
634
+ return Object.freeze(result);
635
+ }
591
636
  var Firewall = class {
592
637
  apiKey;
593
638
  apiUrl;
594
639
  threshold;
595
640
  timeoutMs;
596
641
  hookThresholds;
642
+ shadowMode;
597
643
  headers;
598
644
  constructor(options) {
599
645
  if (!options.apiKey) {
@@ -606,7 +652,8 @@ var Firewall = class {
606
652
  this.apiUrl = options.apiUrl;
607
653
  this.threshold = options.threshold ?? DEFAULT_THRESHOLD;
608
654
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
609
- this.hookThresholds = options.hookThresholds ?? {};
655
+ this.hookThresholds = Object.freeze({ ...options.hookThresholds ?? {} });
656
+ this.shadowMode = options.shadowMode ?? false;
610
657
  this.headers = Object.freeze({
611
658
  "x-api-key": this.apiKey,
612
659
  "content-type": "application/json"
@@ -622,8 +669,11 @@ var Firewall = class {
622
669
  if (options.toolName !== void 0) {
623
670
  payload.tool_name = options.toolName;
624
671
  }
672
+ if (options.metadata !== void 0) {
673
+ payload.metadata = options.metadata;
674
+ }
625
675
  const data = await this.postWithRetry(payload);
626
- return Object.freeze({ prediction: data.prediction, score: Number(data.score) });
676
+ return blockResultFromResponse(data);
627
677
  }
628
678
  const batchOpts = {};
629
679
  if (options.hook !== void 0) {
@@ -632,10 +682,28 @@ var Firewall = class {
632
682
  if (options.toolName !== void 0) {
633
683
  batchOpts.toolNames = chunks.map(() => options.toolName);
634
684
  }
685
+ if (options.metadata !== void 0) {
686
+ batchOpts.metadata = chunks.map(() => options.metadata);
687
+ }
635
688
  const results = await this.classifyBatch(chunks, batchOpts);
636
689
  return results.reduce((best, r) => r.score > best.score ? r : best);
637
690
  }
638
691
  async classifyBatch(texts, options = {}) {
692
+ if (options.hooks !== void 0 && options.hooks.length !== texts.length) {
693
+ throw new Error(
694
+ `Firewall: hooks length ${options.hooks.length} does not match texts length ${texts.length}`
695
+ );
696
+ }
697
+ if (options.toolNames !== void 0 && options.toolNames.length !== texts.length) {
698
+ throw new Error(
699
+ `Firewall: toolNames length ${options.toolNames.length} does not match texts length ${texts.length}`
700
+ );
701
+ }
702
+ if (options.metadata !== void 0 && options.metadata.length !== texts.length) {
703
+ throw new Error(
704
+ `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
705
+ );
706
+ }
639
707
  const payload = { texts };
640
708
  if (options.hooks && options.hooks.length > 0) {
641
709
  payload.hooks = options.hooks.map((h) => String(h));
@@ -643,10 +711,11 @@ var Firewall = class {
643
711
  if (options.toolNames && options.toolNames.length > 0) {
644
712
  payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
645
713
  }
714
+ if (options.metadata && options.metadata.length > 0) {
715
+ payload.metadata = options.metadata.map((m) => m === void 0 ? null : m);
716
+ }
646
717
  const data = await this.postWithRetry(payload);
647
- return data.predictions.map(
648
- (p) => Object.freeze({ prediction: p.prediction, score: Number(p.score) })
649
- );
718
+ return data.predictions.map((p) => blockResultFromResponse(p));
650
719
  }
651
720
  asLangChainHandler(options = {}) {
652
721
  return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/exceptions.ts","../src/hooks.ts","../src/utils/extract.ts","../src/adapters/langchain.ts","../src/adapters/vercel.ts","../src/chunking.ts","../src/firewall.ts","../src/index.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport class PromptBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n }) {\n super(PromptBlockedException.formatMessage(params));\n this.name = \"PromptBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n Object.setPrototypeOf(this, PromptBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\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 return (\n `Prompt blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n\n constructor(params: { status: number; statusText: string; body: string }) {\n super(`Silmaril API error ${params.status} ${params.statusText}: ${params.body}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\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]);\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\nexport const DEFAULT_HOOK_THRESHOLDS: Readonly<Record<HookLabel, number>> = {\n [HookLabel.USER_INPUT]: 0.5,\n [HookLabel.SYSTEM_PROMPT]: 0.3,\n [HookLabel.TOOL_CALL]: 0.4,\n [HookLabel.TOOL_RESPONSE]: 0.3,\n [HookLabel.LLM_OUTPUT]: 0.4,\n [HookLabel.UNKNOWN]: 0.5,\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","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst SKIP_ROLES: ReadonlySet<string> = new Set([\"ai\", \"assistant\"]);\nconst SYSTEM_ROLES: ReadonlySet<string> = new Set([\"system\"]);\nconst TOOL_ROLES: ReadonlySet<string> = new Set([\"tool\", \"function\"]);\n\ntype MessageContent = string | ReadonlyArray<string | { type?: string; text?: string }>;\n\ninterface DucktypedMessage {\n role?: string;\n type?: string;\n content?: MessageContent;\n}\n\ninterface DucktypedGeneration {\n text?: string;\n}\n\ninterface DucktypedLLMResult {\n generations?: ReadonlyArray<ReadonlyArray<DucktypedGeneration>>;\n}\n\ninterface DucktypedDocument {\n pageContent?: string;\n}\n\nfunction extractContentText(content: MessageContent | undefined): string {\n if (typeof content === \"string\") {\n return content;\n }\n if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && typeof block === \"object\" && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n }\n return \"\";\n}\n\nfunction getRole(message: DucktypedMessage): string {\n if (message.role !== undefined) {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nexport function extractTextFromMessages(\n messages: ReadonlyArray<DucktypedMessage>,\n options: { includeSystem?: boolean; includeTool?: boolean } = {},\n): string {\n const includeSystem = options.includeSystem ?? true;\n const includeTool = options.includeTool ?? true;\n const parts: string[] = [];\n for (const message of messages) {\n const role = getRole(message);\n if (SKIP_ROLES.has(role)) {\n continue;\n }\n if (!includeSystem && SYSTEM_ROLES.has(role)) {\n continue;\n }\n if (!includeTool && TOOL_ROLES.has(role)) {\n continue;\n }\n const text = extractContentText(message.content);\n const stripped = text.trim();\n if (stripped) {\n parts.push(stripped);\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromPrompts(prompts: readonly string[]): string {\n return prompts\n .map((p) => p.trim())\n .filter((p) => p.length > 0)\n .join(\"\\n\");\n}\n\nexport function extractTextFromToolInput(inputStr: string): string {\n return inputStr.trim();\n}\n\nexport function extractTextFromLLMResult(response: DucktypedLLMResult): string {\n const parts: string[] = [];\n for (const genList of response.generations ?? []) {\n for (const gen of genList) {\n const text = (gen.text ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromDocuments(documents: ReadonlyArray<DucktypedDocument>): string {\n const parts: string[] = [];\n for (const doc of documents) {\n const text = (doc.pageContent ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n return parts.join(\"\\n\");\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n prependToolName,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { LangChainAdapterOptions } from \"../types.js\";\nimport {\n extractTextFromDocuments,\n extractTextFromLLMResult,\n extractTextFromPrompts,\n extractTextFromToolInput,\n} from \"../utils/extract.js\";\n\nconst USER_ROLES: ReadonlySet<string> = new Set([\"human\", \"user\"]);\n\ninterface LangChainDucktypedMessage {\n role?: string;\n type?: string;\n content?: string | ReadonlyArray<string | { type?: string; text?: string }>;\n}\n\nfunction getMessageRole(message: LangChainDucktypedMessage): string {\n if (typeof message.role === \"string\") {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nfunction extractMessageText(content: LangChainDucktypedMessage[\"content\"]): string {\n if (content === undefined) {\n return \"\";\n }\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<LangChainDucktypedMessage>,\n): LangChainDucktypedMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (USER_ROLES.has(getMessageRole(msg))) {\n return msg;\n }\n }\n return undefined;\n}\n\nexport async function createLangChainHandler(\n firewall: Firewall,\n options: LangChainAdapterOptions = {},\n): Promise<unknown> {\n const { BaseCallbackHandler } = await import(\"@langchain/core/callbacks/base\");\n\n const enabledHooks = resolveHooks(options.hooks);\n // includeSystem / includeTool are retained in LangChainAdapterOptions for\n // backwards compat but are inert under the per-message hook-routing algorithm:\n // handleChatModelStart only scans the last user-role message; system and tool\n // messages are either trusted or already classified by their own hooks.\n const failOpen = options.failOpen ?? true;\n const logger =\n options.logger ??\n ((message: string, error: unknown): void => {\n // eslint-disable-next-line no-console\n console.warn(`silmaril.firewall: ${message}`, error);\n });\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const classify = async (\n text: string,\n hookLabel: HookLabel,\n runId: string,\n toolName?: string,\n ): Promise<void> => {\n try {\n const result = await firewall.classify(text, {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n });\n const threshold = effectiveThreshold(hookLabel);\n if (result.score >= threshold) {\n throw new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n });\n }\n } catch (err) {\n if (err instanceof PromptBlockedException) {\n throw err;\n }\n if (!failOpen) {\n throw err;\n }\n logger(\"classification failed, allowing prompt through\", err);\n }\n };\n\n class SilmarilFirewallHandler extends BaseCallbackHandler {\n override name = \"silmaril_firewall_handler\";\n override raiseError = true;\n override awaitHandlers = true;\n\n override async handleChatModelStart(\n _llm: unknown,\n messages: unknown,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {\n return;\n }\n const batches = messages as ReadonlyArray<ReadonlyArray<LangChainDucktypedMessage>>;\n const flat: LangChainDucktypedMessage[] = [];\n for (const batch of batches) {\n for (const m of batch) {\n flat.push(m);\n }\n }\n const lastUser = findLastUserMessage(flat);\n if (!lastUser) {\n return;\n }\n const text = extractMessageText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);\n }\n\n override async handleLLMStart(\n _llm: unknown,\n prompts: string[],\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_START)) {\n return;\n }\n const text = extractTextFromPrompts(prompts);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);\n }\n\n override async handleToolStart(\n tool: { name?: string } | undefined,\n inputStr: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_START)) {\n return;\n }\n const text = extractTextFromToolInput(inputStr);\n if (!text) {\n return;\n }\n const toolName = tool?.name;\n const prefixed = prependToolName(text, toolName);\n await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);\n }\n\n override async handleRetrieverStart(\n _retriever: unknown,\n query: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {\n return;\n }\n const text = query.trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);\n }\n\n override async handleLLMEnd(output: unknown, runId: string): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_END)) {\n return;\n }\n const text = extractTextFromLLMResult(output as { generations?: ReadonlyArray<ReadonlyArray<{ text?: string }>> });\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);\n }\n\n override async handleToolEnd(\n output: unknown,\n runId: string,\n _parentRunId?: string,\n _tags?: string[],\n _kwargs?: { name?: string },\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_END)) {\n return;\n }\n const text = String(output).trim();\n if (!text) {\n return;\n }\n const toolName = _kwargs?.name;\n const prefixed = prependToolName(text, toolName);\n await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);\n }\n\n override async handleRetrieverEnd(\n documents: ReadonlyArray<{ pageContent?: string }>,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {\n return;\n }\n const text = extractTextFromDocuments(documents);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);\n }\n }\n\n return new SilmarilFirewallHandler();\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { BlockResult, MiddlewareOptions } from \"../types.js\";\n\ninterface VercelContentPart {\n type?: string;\n text?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n}\n\ninterface VercelPromptMessage {\n role: string;\n content: string | ReadonlyArray<VercelContentPart | string>;\n}\n\ninterface VercelToolCall {\n toolCallType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n}\n\ninterface VercelGenerateResult {\n text?: string;\n toolCalls?: ReadonlyArray<VercelToolCall>;\n}\n\ninterface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doGenerate: () => PromiseLike<TResult>;\n}\n\ninterface WrapStreamArgs<\n TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>,\n> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doStream: () => PromiseLike<TResult>;\n}\n\ninterface StreamPart {\n type?: string;\n textDelta?: string;\n delta?: string;\n}\n\nfunction stringifyToolValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n\nfunction extractContentText(content: VercelPromptMessage[\"content\"]): string {\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction iterateToolResultParts(\n message: VercelPromptMessage,\n): Array<{ text: string; toolName: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined }> = [];\n for (const part of message.content) {\n if (typeof part === \"string\") {\n continue;\n }\n if (part.type === \"tool-result\") {\n const text = stringifyToolValue(part.result);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName });\n }\n }\n }\n return out;\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<VercelPromptMessage>,\n): VercelPromptMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role.toLowerCase() === \"user\") {\n return msg;\n }\n }\n return undefined;\n}\n\nexport interface FirewallMiddleware {\n wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(\n args: WrapGenerateArgs<TResult>,\n ) => Promise<TResult>;\n wrapStream: <TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>(\n args: WrapStreamArgs<TResult>,\n ) => Promise<TResult>;\n}\n\nexport function createMiddleware(\n firewall: Firewall,\n options: MiddlewareOptions = {},\n): FirewallMiddleware {\n const scanInput = options.scanInput ?? true;\n const scanOutput = options.scanOutput ?? false;\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const classifyOrBlock = async (\n text: string,\n hook: HookLabel,\n toolName?: string,\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const result: BlockResult = await firewall.classify(\n text,\n toolName !== undefined ? { hook, toolName } : { hook },\n );\n const threshold = effectiveThreshold(hook);\n const blocked = result.score >= threshold;\n options.onClassify?.({\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n text,\n result,\n blocked,\n });\n if (blocked) {\n const err = new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n });\n options.onBlocked?.(err);\n throw err;\n }\n };\n\n const scanPrompt = async (prompt: ReadonlyArray<VercelPromptMessage>): Promise<void> => {\n if (prompt.length === 0) {\n return;\n }\n const last = prompt[prompt.length - 1]!;\n const role = last.role.toLowerCase();\n\n // If the newest message is a tool result (typical mid-flow in a\n // tool-calling loop), classify each tool-result part individually with\n // tool_response + toolName. No manual wiring from the caller — the\n // toolName is read directly from the Vercel content part.\n if (role === \"tool\") {\n for (const { text, toolName } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, toolName);\n }\n return;\n }\n\n // Otherwise, fall back to scanning the most recent user message.\n // Covers: [user], [system, user], [user, assistant(text), user], etc.\n const lastUser = findLastUserMessage(prompt);\n if (!lastUser) {\n return;\n }\n const text = extractContentText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classifyOrBlock(text, HookLabel.USER_INPUT);\n };\n\n const scanGenerateResult = async (result: VercelGenerateResult): Promise<void> => {\n if (scanOutput && typeof result.text === \"string\" && result.text.length > 0) {\n await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);\n }\n if (options.scanToolCalls && Array.isArray(result.toolCalls)) {\n for (const call of result.toolCalls) {\n if (!call || typeof call !== \"object\") {\n continue;\n }\n const args =\n typeof call.args === \"string\" ? call.args : stringifyToolValue(call.args);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);\n }\n }\n }\n };\n\n return {\n async wrapGenerate<TResult extends VercelGenerateResult & Record<string, unknown>>({\n params,\n doGenerate,\n }: WrapGenerateArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const result = await doGenerate();\n await scanGenerateResult(result);\n return result;\n },\n\n async wrapStream<TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>({\n params,\n doStream,\n }: WrapStreamArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const { stream, ...rest } = await doStream();\n if (!scanOutput) {\n return { stream, ...rest } as unknown as TResult;\n }\n\n let buffered = \"\";\n const transformed = stream.pipeThrough(\n new TransformStream<unknown, unknown>({\n transform(chunk, controller): void {\n const part = chunk as StreamPart;\n if (part && part.type === \"text-delta\") {\n const delta = part.textDelta ?? part.delta ?? \"\";\n buffered += delta;\n }\n controller.enqueue(chunk);\n },\n async flush(controller): Promise<void> {\n if (!buffered.trim()) {\n return;\n }\n try {\n await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);\n } catch (err) {\n if (err instanceof PromptBlockedException) {\n controller.enqueue({ type: \"error\", error: err });\n return;\n }\n throw err;\n }\n },\n }),\n );\n return { stream: transformed, ...rest } as unknown as TResult;\n },\n };\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst CHARS_PER_TOKEN = 4;\n\nexport const MAX_INPUT_TOKENS = 10_240;\nexport const CHUNK_WINDOW = 400;\nexport const CHUNK_OVERLAP = 64;\n\nexport const MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;\nexport const CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;\nexport const CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;\nconst CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;\n\nexport function chunkText(text: string): string[] {\n const n = text.length;\n if (n > MAX_INPUT_CHARS) {\n throw new Error(\n `Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); ` +\n `max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`,\n );\n }\n if (n <= CHUNK_WINDOW_CHARS) {\n return [text];\n }\n const chunks: string[] = [];\n for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {\n const end = start + CHUNK_WINDOW_CHARS;\n chunks.push(text.slice(start, end));\n if (end >= n) {\n break;\n }\n }\n return chunks;\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport { createMiddleware, type FirewallMiddleware } from \"./adapters/vercel.js\";\nimport { chunkText } from \"./chunking.js\";\nimport { SilmarilApiError } from \"./exceptions.js\";\nimport type { HookLabel } from \"./hooks.js\";\nimport type {\n BlockResult,\n ClassifyBatchOptions,\n ClassifyOptions,\n FirewallOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n Prediction,\n} from \"./types.js\";\n\nexport const DEFAULT_THRESHOLD = 0.5;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_RETRIES = 5;\nconst MAX_BACKOFF_SECONDS = 30;\n\ninterface SingleClassifyPayload {\n text: string;\n hook?: string;\n tool_name?: string;\n}\n\ninterface BatchClassifyPayload {\n texts: readonly string[];\n hooks?: readonly string[];\n tool_names?: readonly (string | null)[];\n}\n\ninterface SingleClassifyResponse {\n prediction: Prediction;\n score: number;\n}\n\ninterface BatchClassifyResponse {\n predictions: readonly SingleClassifyResponse[];\n}\n\nexport class Firewall {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly threshold: number;\n readonly timeoutMs: number;\n readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;\n\n private readonly headers: Readonly<Record<string, string>>;\n\n constructor(options: FirewallOptions) {\n if (!options.apiKey) {\n throw new Error(\"Firewall: apiKey is required\");\n }\n if (!options.apiUrl) {\n throw new Error(\"Firewall: apiUrl is required\");\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl;\n this.threshold = options.threshold ?? DEFAULT_THRESHOLD;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.hookThresholds = options.hookThresholds ?? {};\n this.headers = Object.freeze({\n \"x-api-key\": this.apiKey,\n \"content-type\": \"application/json\",\n });\n }\n\n async classify(text: string, options: ClassifyOptions = {}): Promise<BlockResult> {\n const chunks = chunkText(text);\n if (chunks.length === 1) {\n const payload: SingleClassifyPayload = { text: chunks[0]! };\n if (options.hook !== undefined) {\n payload.hook = options.hook;\n }\n if (options.toolName !== undefined) {\n payload.tool_name = options.toolName;\n }\n const data = await this.postWithRetry<SingleClassifyResponse>(payload);\n return Object.freeze({ prediction: data.prediction, score: Number(data.score) });\n }\n const batchOpts: ClassifyBatchOptions = {};\n if (options.hook !== undefined) {\n batchOpts.hooks = chunks.map(() => options.hook!);\n }\n if (options.toolName !== undefined) {\n batchOpts.toolNames = chunks.map(() => options.toolName);\n }\n const results = await this.classifyBatch(chunks, batchOpts);\n return results.reduce((best, r) => (r.score > best.score ? r : best));\n }\n\n async classifyBatch(\n texts: readonly string[],\n options: ClassifyBatchOptions = {},\n ): Promise<BlockResult[]> {\n const payload: BatchClassifyPayload = { texts };\n if (options.hooks && options.hooks.length > 0) {\n payload.hooks = options.hooks.map((h) => String(h));\n }\n if (options.toolNames && options.toolNames.length > 0) {\n payload.tool_names = options.toolNames.map((t) => (t === undefined ? null : t));\n }\n const data = await this.postWithRetry<BatchClassifyResponse>(payload);\n return data.predictions.map((p) =>\n Object.freeze({ prediction: p.prediction, score: Number(p.score) }),\n );\n }\n\n asLangChainHandler(options: LangChainAdapterOptions = {}): Promise<unknown> {\n return import(\"./adapters/langchain.js\").then((m) => m.createLangChainHandler(this, options));\n }\n\n asMiddleware(options: MiddlewareOptions = {}): FirewallMiddleware {\n return createMiddleware(this, options);\n }\n\n effectiveThreshold(hook: HookLabel | undefined): number {\n if (hook === undefined) {\n return this.threshold;\n }\n return this.hookThresholds[hook] ?? this.threshold;\n }\n\n private async postWithRetry<T>(\n payload: SingleClassifyPayload | BatchClassifyPayload,\n maxRetries: number = DEFAULT_MAX_RETRIES,\n ): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const response = await fetch(this.apiUrl, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n if (response.status !== 429 || attempt === maxRetries) {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new SilmarilApiError({\n status: response.status,\n statusText: response.statusText,\n body,\n });\n }\n return (await response.json()) as T;\n }\n const waitSeconds = Math.min(2 ** attempt, MAX_BACKOFF_SECONDS);\n await new Promise<void>((resolve) => setTimeout(resolve, waitSeconds * 1000));\n }\n throw new Error(\"Firewall: exhausted retries (unreachable)\");\n }\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport {\n Firewall,\n DEFAULT_THRESHOLD,\n DEFAULT_TIMEOUT_MS,\n} from \"./firewall.js\";\n\nexport { PromptBlockedException, SilmarilApiError } from \"./exceptions.js\";\n\nexport type { FirewallMiddleware } from \"./adapters/vercel.js\";\n\nexport {\n chunkText,\n MAX_INPUT_TOKENS,\n CHUNK_WINDOW,\n CHUNK_OVERLAP,\n MAX_INPUT_CHARS,\n CHUNK_WINDOW_CHARS,\n CHUNK_OVERLAP_CHARS,\n} from \"./chunking.js\";\n\nexport {\n HookLabel,\n FirewallHook,\n DEFAULT_HOOKS,\n INPUT_HOOKS,\n OUTPUT_HOOKS,\n ALL_HOOKS,\n DEFAULT_HOOK_THRESHOLDS,\n FIREWALL_HOOK_TO_LABEL,\n resolveHooks,\n prependHook,\n prependToolName,\n} from \"./hooks.js\";\n\nexport type {\n BlockResult,\n ClassifyEvent,\n Prediction,\n FirewallOptions,\n ClassifyOptions,\n ClassifyBatchOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n} from \"./types.js\";\n"],"mappings":";;;;;;;;;;;AAAA,IAGM,wBAEO,wBAsCA;AA3Cb;AAAA;AAAA;AAGA,IAAM,yBAAyB;AAExB,IAAM,yBAAN,MAAM,gCAA+B,MAAM;AAAA,MACvC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAET,YAAY,QAKT;AACD,cAAM,wBAAuB,cAAc,MAAM,CAAC;AAClD,aAAK,OAAO;AACZ,aAAK,QAAQ,OAAO;AACpB,aAAK,YAAY,OAAO;AACxB,aAAK,aAAa,OAAO;AACzB,aAAK,QAAQ,OAAO;AACpB,eAAO,eAAe,MAAM,wBAAuB,SAAS;AAAA,MAC9D;AAAA,MAEA,OAAe,cAAc,QAIlB;AACT,cAAM,YACJ,OAAO,WAAW,SAAS,yBACvB,GAAG,OAAO,WAAW,MAAM,GAAG,sBAAsB,CAAC,QACrD,OAAO;AACb,eACE,8CACU,OAAO,MAAM,QAAQ,CAAC,CAAC,eAAe,OAAO,UAAU,QAAQ,CAAC,CAAC,OACvE,SAAS;AAAA,MAEjB;AAAA,IACF;AAEO,IAAM,mBAAN,MAAM,0BAAyB,MAAM;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MAET,YAAY,QAA8D;AACxE,cAAM,sBAAsB,OAAO,MAAM,IAAI,OAAO,UAAU,KAAK,OAAO,IAAI,EAAE;AAChF,aAAK,OAAO;AACZ,aAAK,SAAS,OAAO;AACrB,aAAK,aAAa,OAAO;AACzB,aAAK,OAAO,OAAO;AACnB,eAAO,eAAe,MAAM,kBAAiB,SAAS;AAAA,MACxD;AAAA,IACF;AAAA;AAAA;;;ACcO,SAAS,aACd,OAC2B;AAC3B,MAAI,UAAU,QAAW;AACvB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,oBAAI,IAAkB;AACvC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,qBAAqB,IAAI,CAAC,GAAG;AAChC,YAAM,IAAI,MAAM,+BAA+B,OAAO,CAAC,CAAC,EAAE;AAAA,IAC5D;AACA,aAAS,IAAI,CAAiB;AAAA,EAChC;AACA,SAAO;AACT;AAEO,SAAS,YAAY,MAAc,MAAqD;AAC7F,MAAI,SAAS,UAAa,SAAS,MAAM;AACvC,WAAO;AAAA,EACT;AACA,MAAI,SAAS,UAAU,SAAS;AAC9B,WAAO;AAAA,EACT;AACA,SAAO,SAAS,IAAI,KAAK,IAAI;AAC/B;AAEO,SAAS,gBAAgB,MAAc,UAA6C;AACzF,MAAI,aAAa,UAAa,aAAa,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,SAAO,SAAS,QAAQ,KAAK,IAAI;AACnC;AArGA,IAGa,WAWA,cAYA,eAKA,aAOA,cAMA,WAKA,wBAUA,yBASP;AApEN;AAAA;AAAA;AAGO,IAAM,YAAY;AAAA,MACvB,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,WAAW;AAAA,MACX,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,SAAS;AAAA,IACX;AAIO,IAAM,eAAe;AAAA,MAC1B,WAAW;AAAA,MACX,kBAAkB;AAAA,MAClB,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,UAAU;AAAA,MACV,eAAe;AAAA,IACjB;AAIO,IAAM,gBAA2C,oBAAI,IAAkB;AAAA,MAC5E,aAAa;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAEM,IAAM,cAAyC,oBAAI,IAAkB;AAAA,MAC1E,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAEM,IAAM,eAA0C,oBAAI,IAAkB;AAAA,MAC3E,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAEM,IAAM,YAAuC,oBAAI,IAAkB;AAAA,MACxE,GAAG;AAAA,MACH,GAAG;AAAA,IACL,CAAC;AAEM,IAAM,yBAAoE;AAAA,MAC/E,CAAC,aAAa,gBAAgB,GAAG,UAAU;AAAA,MAC3C,CAAC,aAAa,SAAS,GAAG,UAAU;AAAA,MACpC,CAAC,aAAa,UAAU,GAAG,UAAU;AAAA,MACrC,CAAC,aAAa,QAAQ,GAAG,UAAU;AAAA,MACnC,CAAC,aAAa,eAAe,GAAG,UAAU;AAAA,MAC1C,CAAC,aAAa,aAAa,GAAG,UAAU;AAAA,MACxC,CAAC,aAAa,OAAO,GAAG,UAAU;AAAA,IACpC;AAEO,IAAM,0BAA+D;AAAA,MAC1E,CAAC,UAAU,UAAU,GAAG;AAAA,MACxB,CAAC,UAAU,aAAa,GAAG;AAAA,MAC3B,CAAC,UAAU,SAAS,GAAG;AAAA,MACvB,CAAC,UAAU,aAAa,GAAG;AAAA,MAC3B,CAAC,UAAU,UAAU,GAAG;AAAA,MACxB,CAAC,UAAU,OAAO,GAAG;AAAA,IACvB;AAEA,IAAM,uBAA4C,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;AAAA;AAAA;;;ACc9E,SAAS,uBAAuB,SAAoC;AACzE,SAAO,QACJ,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,KAAK,IAAI;AACd;AAEO,SAAS,yBAAyB,UAA0B;AACjE,SAAO,SAAS,KAAK;AACvB;AAEO,SAAS,yBAAyB,UAAsC;AAC7E,QAAM,QAAkB,CAAC;AACzB,aAAW,WAAW,SAAS,eAAe,CAAC,GAAG;AAChD,eAAW,OAAO,SAAS;AACzB,YAAM,QAAQ,IAAI,QAAQ,IAAI,KAAK;AACnC,UAAI,MAAM;AACR,cAAM,KAAK,IAAI;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,yBAAyB,WAAqD;AAC5F,QAAM,QAAkB,CAAC;AACzB,aAAW,OAAO,WAAW;AAC3B,UAAM,QAAQ,IAAI,eAAe,IAAI,KAAK;AAC1C,QAAI,MAAM;AACR,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAnHA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AA4BA,SAAS,eAAe,SAA4C;AAClE,MAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,WAAO,QAAQ,KAAK,YAAY;AAAA,EAClC;AACA,MAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,WAAO,QAAQ,KAAK,YAAY;AAAA,EAClC;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,SAAuD;AACjF,MAAI,YAAY,QAAW;AACzB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,KAAK,KAAK;AAAA,IAClB,WAAW,SAAS,MAAM,SAAS,QAAQ;AACzC,YAAM,KAAK,MAAM,QAAQ,EAAE;AAAA,IAC7B;AAAA,EACF;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAASA,qBACP,UACuC;AACvC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,WAAW,IAAI,eAAe,GAAG,CAAC,GAAG;AACvC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,uBACpB,UACA,UAAmC,CAAC,GAClB;AAClB,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,gCAAgC;AAE7E,QAAM,eAAe,aAAa,QAAQ,KAAK;AAK/C,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,SACJ,QAAQ,WACP,CAAC,SAAiB,UAAyB;AAE1C,YAAQ,KAAK,sBAAsB,OAAO,IAAI,KAAK;AAAA,EACrD;AACF,QAAM,oBAAoB,QAAQ;AAClC,QAAM,yBAAyB,QAAQ,kBAAkB,CAAC;AAE1D,QAAM,qBAAqB,CAAC,UAA6B;AACvD,QAAI,uBAAuB,KAAK,MAAM,QAAW;AAC/C,aAAO,uBAAuB,KAAK;AAAA,IACrC;AACA,QAAI,SAAS,eAAe,KAAK,MAAM,QAAW;AAChD,aAAO,SAAS,eAAe,KAAK;AAAA,IACtC;AACA,WAAO,qBAAqB,SAAS;AAAA,EACvC;AAEA,QAAM,WAAW,OACf,MACA,WACA,OACA,aACkB;AAClB,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,SAAS,MAAM;AAAA,QAC3C,MAAM;AAAA,QACN,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/C,CAAC;AACD,YAAM,YAAY,mBAAmB,SAAS;AAC9C,UAAI,OAAO,SAAS,WAAW;AAC7B,cAAM,IAAI,uBAAuB;AAAA,UAC/B,OAAO,OAAO;AAAA,UACd;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,wBAAwB;AACzC,cAAM;AAAA,MACR;AACA,UAAI,CAAC,UAAU;AACb,cAAM;AAAA,MACR;AACA,aAAO,kDAAkD,GAAG;AAAA,IAC9D;AAAA,EACF;AAAA,EAEA,MAAM,gCAAgC,oBAAoB;AAAA,IAC/C,OAAO;AAAA,IACP,aAAa;AAAA,IACb,gBAAgB;AAAA,IAEzB,MAAe,qBACb,MACA,UACA,OACe;AACf,UAAI,CAAC,aAAa,IAAI,aAAa,gBAAgB,GAAG;AACpD;AAAA,MACF;AACA,YAAM,UAAU;AAChB,YAAM,OAAoC,CAAC;AAC3C,iBAAW,SAAS,SAAS;AAC3B,mBAAW,KAAK,OAAO;AACrB,eAAK,KAAK,CAAC;AAAA,QACb;AAAA,MACF;AACA,YAAM,WAAWA,qBAAoB,IAAI;AACzC,UAAI,CAAC,UAAU;AACb;AAAA,MACF;AACA,YAAM,OAAO,mBAAmB,SAAS,OAAO,EAAE,KAAK;AACvD,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,SAAS,MAAM,uBAAuB,aAAa,gBAAgB,GAAG,KAAK;AAAA,IACnF;AAAA,IAEA,MAAe,eACb,MACA,SACA,OACe;AACf,UAAI,CAAC,aAAa,IAAI,aAAa,SAAS,GAAG;AAC7C;AAAA,MACF;AACA,YAAM,OAAO,uBAAuB,OAAO;AAC3C,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,SAAS,MAAM,uBAAuB,aAAa,SAAS,GAAG,KAAK;AAAA,IAC5E;AAAA,IAEA,MAAe,gBACb,MACA,UACA,OACe;AACf,UAAI,CAAC,aAAa,IAAI,aAAa,UAAU,GAAG;AAC9C;AAAA,MACF;AACA,YAAM,OAAO,yBAAyB,QAAQ;AAC9C,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,WAAW,MAAM;AACvB,YAAM,WAAW,gBAAgB,MAAM,QAAQ;AAC/C,YAAM,SAAS,UAAU,uBAAuB,aAAa,UAAU,GAAG,OAAO,QAAQ;AAAA,IAC3F;AAAA,IAEA,MAAe,qBACb,YACA,OACA,OACe;AACf,UAAI,CAAC,aAAa,IAAI,aAAa,eAAe,GAAG;AACnD;AAAA,MACF;AACA,YAAM,OAAO,MAAM,KAAK;AACxB,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,SAAS,MAAM,uBAAuB,aAAa,eAAe,GAAG,KAAK;AAAA,IAClF;AAAA,IAEA,MAAe,aAAa,QAAiB,OAA8B;AACzE,UAAI,CAAC,aAAa,IAAI,aAAa,OAAO,GAAG;AAC3C;AAAA,MACF;AACA,YAAM,OAAO,yBAAyB,MAA2E;AACjH,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,SAAS,MAAM,uBAAuB,aAAa,OAAO,GAAG,KAAK;AAAA,IAC1E;AAAA,IAEA,MAAe,cACb,QACA,OACA,cACA,OACA,SACe;AACf,UAAI,CAAC,aAAa,IAAI,aAAa,QAAQ,GAAG;AAC5C;AAAA,MACF;AACA,YAAM,OAAO,OAAO,MAAM,EAAE,KAAK;AACjC,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,WAAW,SAAS;AAC1B,YAAM,WAAW,gBAAgB,MAAM,QAAQ;AAC/C,YAAM,SAAS,UAAU,uBAAuB,aAAa,QAAQ,GAAG,OAAO,QAAQ;AAAA,IACzF;AAAA,IAEA,MAAe,mBACb,WACA,OACe;AACf,UAAI,CAAC,aAAa,IAAI,aAAa,aAAa,GAAG;AACjD;AAAA,MACF;AACA,YAAM,OAAO,yBAAyB,SAAS;AAC/C,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,SAAS,MAAM,uBAAuB,aAAa,aAAa,GAAG,KAAK;AAAA,IAChF;AAAA,EACF;AAEA,SAAO,IAAI,wBAAwB;AACrC;AA9PA,IAoBM;AApBN;AAAA;AAAA;AAIA;AACA;AAQA;AAOA,IAAM,aAAkC,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAAA;AAAA;;;AChBjE;AACA;AA+CA,SAAS,mBAAmB,OAAwB;AAClD,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AAGA,SAAS,mBAAmB,SAAiD;AAC3E,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,KAAK,KAAK;AAAA,IAClB,WAAW,SAAS,MAAM,SAAS,QAAQ;AACzC,YAAM,KAAK,MAAM,QAAQ,EAAE;AAAA,IAC7B;AAAA,EACF;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAAS,uBACP,SACuD;AACvD,MAAI,OAAO,QAAQ,YAAY,UAAU;AACvC,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAA6D,CAAC;AACpE,aAAW,QAAQ,QAAQ,SAAS;AAClC,QAAI,OAAO,SAAS,UAAU;AAC5B;AAAA,IACF;AACA,QAAI,KAAK,SAAS,eAAe;AAC/B,YAAM,OAAO,mBAAmB,KAAK,MAAM;AAC3C,UAAI,KAAK,KAAK,GAAG;AACf,YAAI,KAAK,EAAE,MAAM,UAAU,KAAK,SAAS,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBACP,UACiC;AACjC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,IAAI,KAAK,YAAY,MAAM,QAAQ;AACrC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAWO,SAAS,iBACd,UACA,UAA6B,CAAC,GACV;AACpB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,oBAAoB,QAAQ;AAClC,QAAM,yBAAyB,QAAQ,kBAAkB,CAAC;AAE1D,QAAM,qBAAqB,CAAC,UAA6B;AACvD,QAAI,uBAAuB,KAAK,MAAM,QAAW;AAC/C,aAAO,uBAAuB,KAAK;AAAA,IACrC;AACA,QAAI,SAAS,eAAe,KAAK,MAAM,QAAW;AAChD,aAAO,SAAS,eAAe,KAAK;AAAA,IACtC;AACA,WAAO,qBAAqB,SAAS;AAAA,EACvC;AAEA,QAAM,kBAAkB,OACtB,MACA,MACA,aACkB;AAClB,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB;AAAA,IACF;AACA,UAAM,SAAsB,MAAM,SAAS;AAAA,MACzC;AAAA,MACA,aAAa,SAAY,EAAE,MAAM,SAAS,IAAI,EAAE,KAAK;AAAA,IACvD;AACA,UAAM,YAAY,mBAAmB,IAAI;AACzC,UAAM,UAAU,OAAO,SAAS;AAChC,YAAQ,aAAa;AAAA,MACnB;AAAA,MACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,SAAS;AACX,YAAM,MAAM,IAAI,uBAAuB;AAAA,QACrC,OAAO,OAAO;AAAA,QACd;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AACD,cAAQ,YAAY,GAAG;AACvB,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,aAAa,OAAO,WAA8D;AACtF,QAAI,OAAO,WAAW,GAAG;AACvB;AAAA,IACF;AACA,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,KAAK,KAAK,YAAY;AAMnC,QAAI,SAAS,QAAQ;AACnB,iBAAW,EAAE,MAAAC,OAAM,SAAS,KAAK,uBAAuB,IAAI,GAAG;AAC7D,cAAM,gBAAgBA,OAAM,UAAU,eAAe,QAAQ;AAAA,MAC/D;AACA;AAAA,IACF;AAIA,UAAM,WAAW,oBAAoB,MAAM;AAC3C,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,UAAM,OAAO,mBAAmB,SAAS,OAAO,EAAE,KAAK;AACvD,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,UAAM,gBAAgB,MAAM,UAAU,UAAU;AAAA,EAClD;AAEA,QAAM,qBAAqB,OAAO,WAAgD;AAChF,QAAI,cAAc,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,GAAG;AAC3E,YAAM,gBAAgB,OAAO,MAAM,UAAU,UAAU;AAAA,IACzD;AACA,QAAI,QAAQ,iBAAiB,MAAM,QAAQ,OAAO,SAAS,GAAG;AAC5D,iBAAW,QAAQ,OAAO,WAAW;AACnC,YAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC;AAAA,QACF;AACA,cAAM,OACJ,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,mBAAmB,KAAK,IAAI;AAC1E,cAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,YAAI,KAAK,KAAK,GAAG;AACf,gBAAM,gBAAgB,MAAM,UAAU,WAAW,QAAQ;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,aAA6E;AAAA,MACjF;AAAA,MACA;AAAA,IACF,GAAgD;AAC9C,UAAI,WAAW;AACb,cAAM,WAAW,OAAO,MAAM;AAAA,MAChC;AACA,YAAM,SAAS,MAAM,WAAW;AAChC,YAAM,mBAAmB,MAAM;AAC/B,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,WAA0F;AAAA,MAC9F;AAAA,MACA;AAAA,IACF,GAA8C;AAC5C,UAAI,WAAW;AACb,cAAM,WAAW,OAAO,MAAM;AAAA,MAChC;AACA,YAAM,EAAE,QAAQ,GAAG,KAAK,IAAI,MAAM,SAAS;AAC3C,UAAI,CAAC,YAAY;AACf,eAAO,EAAE,QAAQ,GAAG,KAAK;AAAA,MAC3B;AAEA,UAAI,WAAW;AACf,YAAM,cAAc,OAAO;AAAA,QACzB,IAAI,gBAAkC;AAAA,UACpC,UAAU,OAAO,YAAkB;AACjC,kBAAM,OAAO;AACb,gBAAI,QAAQ,KAAK,SAAS,cAAc;AACtC,oBAAM,QAAQ,KAAK,aAAa,KAAK,SAAS;AAC9C,0BAAY;AAAA,YACd;AACA,uBAAW,QAAQ,KAAK;AAAA,UAC1B;AAAA,UACA,MAAM,MAAM,YAA2B;AACrC,gBAAI,CAAC,SAAS,KAAK,GAAG;AACpB;AAAA,YACF;AACA,gBAAI;AACF,oBAAM,gBAAgB,UAAU,UAAU,UAAU;AAAA,YACtD,SAAS,KAAK;AACZ,kBAAI,eAAe,wBAAwB;AACzC,2BAAW,QAAQ,EAAE,MAAM,SAAS,OAAO,IAAI,CAAC;AAChD;AAAA,cACF;AACA,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO,EAAE,QAAQ,aAAa,GAAG,KAAK;AAAA,IACxC;AAAA,EACF;AACF;;;ACrRA,IAAM,kBAAkB;AAEjB,IAAM,mBAAmB;AACzB,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAEtB,IAAM,kBAAkB,mBAAmB;AAC3C,IAAM,qBAAqB,eAAe;AAC1C,IAAM,sBAAsB,gBAAgB;AACnD,IAAM,qBAAqB,qBAAqB;AAEzC,SAAS,UAAU,MAAwB;AAChD,QAAM,IAAI,KAAK;AACf,MAAI,IAAI,iBAAiB;AACvB,UAAM,IAAI;AAAA,MACR,cAAc,KAAK,MAAM,IAAI,eAAe,CAAC,YAAY,CAAC,mBAC9C,gBAAgB,YAAY,eAAe;AAAA,IACzD;AAAA,EACF;AACA,MAAI,KAAK,oBAAoB;AAC3B,WAAO,CAAC,IAAI;AAAA,EACd;AACA,QAAM,SAAmB,CAAC;AAC1B,WAAS,QAAQ,GAAG,QAAQ,GAAG,SAAS,oBAAoB;AAC1D,UAAM,MAAM,QAAQ;AACpB,WAAO,KAAK,KAAK,MAAM,OAAO,GAAG,CAAC;AAClC,QAAI,OAAO,GAAG;AACZ;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AC7BA;AAYO,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAClC,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAuBrB,IAAM,WAAN,MAAe;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEQ;AAAA,EAEjB,YAAY,SAA0B;AACpC,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AACA,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AACA,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ;AACtB,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,iBAAiB,QAAQ,kBAAkB,CAAC;AACjD,SAAK,UAAU,OAAO,OAAO;AAAA,MAC3B,aAAa,KAAK;AAAA,MAClB,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,MAAc,UAA2B,CAAC,GAAyB;AAChF,UAAM,SAAS,UAAU,IAAI;AAC7B,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,UAAiC,EAAE,MAAM,OAAO,CAAC,EAAG;AAC1D,UAAI,QAAQ,SAAS,QAAW;AAC9B,gBAAQ,OAAO,QAAQ;AAAA,MACzB;AACA,UAAI,QAAQ,aAAa,QAAW;AAClC,gBAAQ,YAAY,QAAQ;AAAA,MAC9B;AACA,YAAM,OAAO,MAAM,KAAK,cAAsC,OAAO;AACrE,aAAO,OAAO,OAAO,EAAE,YAAY,KAAK,YAAY,OAAO,OAAO,KAAK,KAAK,EAAE,CAAC;AAAA,IACjF;AACA,UAAM,YAAkC,CAAC;AACzC,QAAI,QAAQ,SAAS,QAAW;AAC9B,gBAAU,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAK;AAAA,IAClD;AACA,QAAI,QAAQ,aAAa,QAAW;AAClC,gBAAU,YAAY,OAAO,IAAI,MAAM,QAAQ,QAAQ;AAAA,IACzD;AACA,UAAM,UAAU,MAAM,KAAK,cAAc,QAAQ,SAAS;AAC1D,WAAO,QAAQ,OAAO,CAAC,MAAM,MAAO,EAAE,QAAQ,KAAK,QAAQ,IAAI,IAAK;AAAA,EACtE;AAAA,EAEA,MAAM,cACJ,OACA,UAAgC,CAAC,GACT;AACxB,UAAM,UAAgC,EAAE,MAAM;AAC9C,QAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,GAAG;AAC7C,cAAQ,QAAQ,QAAQ,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;AAAA,IACpD;AACA,QAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,cAAQ,aAAa,QAAQ,UAAU,IAAI,CAAC,MAAO,MAAM,SAAY,OAAO,CAAE;AAAA,IAChF;AACA,UAAM,OAAO,MAAM,KAAK,cAAqC,OAAO;AACpE,WAAO,KAAK,YAAY;AAAA,MAAI,CAAC,MAC3B,OAAO,OAAO,EAAE,YAAY,EAAE,YAAY,OAAO,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,IACpE;AAAA,EACF;AAAA,EAEA,mBAAmB,UAAmC,CAAC,GAAqB;AAC1E,WAAO,oEAAkC,KAAK,CAAC,MAAM,EAAE,uBAAuB,MAAM,OAAO,CAAC;AAAA,EAC9F;AAAA,EAEA,aAAa,UAA6B,CAAC,GAAuB;AAChE,WAAO,iBAAiB,MAAM,OAAO;AAAA,EACvC;AAAA,EAEA,mBAAmB,MAAqC;AACtD,QAAI,SAAS,QAAW;AACtB,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK,eAAe,IAAI,KAAK,KAAK;AAAA,EAC3C;AAAA,EAEA,MAAc,cACZ,SACA,aAAqB,qBACT;AACZ,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,YAAM,WAAW,MAAM,MAAM,KAAK,QAAQ;AAAA,QACxC,QAAQ;AAAA,QACR,SAAS,KAAK;AAAA,QACd,MAAM,KAAK,UAAU,OAAO;AAAA,QAC5B,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,MAC5C,CAAC;AACD,UAAI,SAAS,WAAW,OAAO,YAAY,YAAY;AACrD,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,gBAAM,IAAI,iBAAiB;AAAA,YACzB,QAAQ,SAAS;AAAA,YACjB,YAAY,SAAS;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QACH;AACA,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B;AACA,YAAM,cAAc,KAAK,IAAI,KAAK,SAAS,mBAAmB;AAC9D,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,cAAc,GAAI,CAAC;AAAA,IAC9E;AACA,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACF;;;AChJA;AAcA;","names":["findLastUserMessage","text"]}
1
+ {"version":3,"sources":["../src/exceptions.ts","../src/hooks.ts","../src/utils/extract.ts","../src/adapters/langchain.ts","../src/adapters/vercel.ts","../src/chunking.ts","../src/firewall.ts","../src/index.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport class PromptBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n }) {\n super(PromptBlockedException.formatMessage(params));\n this.name = \"PromptBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n Object.setPrototypeOf(this, PromptBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\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 return (\n `Prompt blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\nexport class SilmarilApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly body: string;\n\n constructor(params: { status: number; statusText: string; body: string }) {\n super(`Silmaril API error ${params.status} ${params.statusText}: ${params.body}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\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]);\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\nexport const DEFAULT_HOOK_THRESHOLDS: Readonly<Record<HookLabel, number>> = {\n [HookLabel.USER_INPUT]: 0.5,\n [HookLabel.SYSTEM_PROMPT]: 0.5,\n [HookLabel.TOOL_CALL]: 0.5,\n [HookLabel.TOOL_RESPONSE]: 0.5,\n [HookLabel.LLM_OUTPUT]: 0.5,\n [HookLabel.UNKNOWN]: 0.5,\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","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst SKIP_ROLES: ReadonlySet<string> = new Set([\"ai\", \"assistant\"]);\nconst SYSTEM_ROLES: ReadonlySet<string> = new Set([\"system\"]);\nconst TOOL_ROLES: ReadonlySet<string> = new Set([\"tool\", \"function\"]);\n\ntype MessageContent = string | ReadonlyArray<string | { type?: string; text?: string }>;\n\ninterface DucktypedMessage {\n role?: string;\n type?: string;\n content?: MessageContent;\n}\n\ninterface DucktypedGeneration {\n text?: string;\n}\n\ninterface DucktypedLLMResult {\n generations?: ReadonlyArray<ReadonlyArray<DucktypedGeneration>>;\n}\n\ninterface DucktypedDocument {\n pageContent?: string;\n}\n\nfunction extractContentText(content: MessageContent | undefined): string {\n if (typeof content === \"string\") {\n return content;\n }\n if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && typeof block === \"object\" && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n }\n return \"\";\n}\n\nfunction getRole(message: DucktypedMessage): string {\n if (message.role !== undefined) {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nexport function extractTextFromMessages(\n messages: ReadonlyArray<DucktypedMessage>,\n options: { includeSystem?: boolean; includeTool?: boolean } = {},\n): string {\n const includeSystem = options.includeSystem ?? true;\n const includeTool = options.includeTool ?? true;\n const parts: string[] = [];\n for (const message of messages) {\n const role = getRole(message);\n if (SKIP_ROLES.has(role)) {\n continue;\n }\n if (!includeSystem && SYSTEM_ROLES.has(role)) {\n continue;\n }\n if (!includeTool && TOOL_ROLES.has(role)) {\n continue;\n }\n const text = extractContentText(message.content);\n const stripped = text.trim();\n if (stripped) {\n parts.push(stripped);\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromPrompts(prompts: readonly string[]): string {\n return prompts\n .map((p) => p.trim())\n .filter((p) => p.length > 0)\n .join(\"\\n\");\n}\n\nexport function extractTextFromToolInput(inputStr: string): string {\n return inputStr.trim();\n}\n\nexport function extractTextFromLLMResult(response: DucktypedLLMResult): string {\n const parts: string[] = [];\n for (const genList of response.generations ?? []) {\n for (const gen of genList) {\n const text = (gen.text ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n }\n return parts.join(\"\\n\");\n}\n\nexport function extractTextFromDocuments(documents: ReadonlyArray<DucktypedDocument>): string {\n const parts: string[] = [];\n for (const doc of documents) {\n const text = (doc.pageContent ?? \"\").trim();\n if (text) {\n parts.push(text);\n }\n }\n return parts.join(\"\\n\");\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { BlockResult, LangChainAdapterOptions } from \"../types.js\";\nimport {\n extractTextFromDocuments,\n extractTextFromLLMResult,\n extractTextFromPrompts,\n extractTextFromToolInput,\n} from \"../utils/extract.js\";\n\nconst USER_ROLES: ReadonlySet<string> = new Set([\"human\", \"user\"]);\n\ninterface LangChainDucktypedMessage {\n role?: string;\n type?: string;\n content?: string | ReadonlyArray<string | { type?: string; text?: string }>;\n}\n\nfunction getMessageRole(message: LangChainDucktypedMessage): string {\n if (typeof message.role === \"string\") {\n return message.role.toLowerCase();\n }\n if (typeof message.type === \"string\") {\n return message.type.toLowerCase();\n }\n return \"\";\n}\n\nfunction extractMessageText(content: LangChainDucktypedMessage[\"content\"]): string {\n if (content === undefined) {\n return \"\";\n }\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<LangChainDucktypedMessage>,\n): LangChainDucktypedMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (USER_ROLES.has(getMessageRole(msg))) {\n return msg;\n }\n }\n return undefined;\n}\n\nexport async function createLangChainHandler(\n firewall: Firewall,\n options: LangChainAdapterOptions = {},\n): Promise<BaseCallbackHandler> {\n const { BaseCallbackHandler } = await import(\"@langchain/core/callbacks/base\");\n\n const enabledHooks = resolveHooks(options.hooks);\n // includeSystem / includeTool are retained in LangChainAdapterOptions for\n // backwards compat but are inert under the per-message hook-routing algorithm:\n // handleChatModelStart only scans the last user-role message; system and tool\n // messages are either trusted or already classified by their own hooks.\n const failOpen = options.failOpen ?? true;\n const logger =\n options.logger ??\n ((message: string, error: unknown): void => {\n // eslint-disable-next-line no-console\n console.warn(`silmaril.firewall: ${message}`, error);\n });\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n const shadowMode = options.shadowMode ?? firewall.shadowMode;\n const onClassify = options.onClassify;\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const fireOnClassify = (\n hookLabel: HookLabel,\n text: string,\n result: BlockResult,\n blocked: boolean,\n toolName: string | undefined,\n ): void => {\n if (!onClassify) {\n return;\n }\n try {\n onClassify({\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n text,\n result,\n blocked,\n shadowMode,\n });\n } catch (err) {\n logger(\"onClassify callback threw\", err);\n }\n };\n\n const classify = async (\n text: string,\n hookLabel: HookLabel,\n runId: string,\n toolName?: string,\n ): Promise<void> => {\n let result: BlockResult;\n try {\n result = await firewall.classify(text, {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n });\n } catch (err) {\n if (!failOpen) {\n throw err;\n }\n logger(\"classification failed, allowing prompt through\", err);\n return;\n }\n const threshold = effectiveThreshold(hookLabel);\n const blocked = result.score >= threshold;\n fireOnClassify(hookLabel, text, result, blocked, toolName);\n if (blocked && !shadowMode) {\n throw new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n });\n }\n };\n\n class SilmarilFirewallHandler extends BaseCallbackHandler {\n override name = \"silmaril_firewall_handler\";\n override raiseError = true;\n override awaitHandlers = true;\n\n override async handleChatModelStart(\n _llm: unknown,\n messages: unknown,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {\n return;\n }\n const batches = messages as ReadonlyArray<ReadonlyArray<LangChainDucktypedMessage>>;\n const flat: LangChainDucktypedMessage[] = [];\n for (const batch of batches) {\n for (const m of batch) {\n flat.push(m);\n }\n }\n const lastUser = findLastUserMessage(flat);\n if (!lastUser) {\n return;\n }\n const text = extractMessageText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);\n }\n\n override async handleLLMStart(\n _llm: unknown,\n prompts: string[],\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_START)) {\n return;\n }\n const text = extractTextFromPrompts(prompts);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);\n }\n\n override async handleToolStart(\n tool: { name?: string } | undefined,\n inputStr: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_START)) {\n return;\n }\n const text = extractTextFromToolInput(inputStr);\n if (!text) {\n return;\n }\n const toolName = tool?.name;\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);\n }\n\n override async handleRetrieverStart(\n _retriever: unknown,\n query: string,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {\n return;\n }\n const text = query.trim();\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);\n }\n\n override async handleLLMEnd(output: unknown, runId: string): Promise<void> {\n if (!enabledHooks.has(FirewallHook.LLM_END)) {\n return;\n }\n const text = extractTextFromLLMResult(output as { generations?: ReadonlyArray<ReadonlyArray<{ text?: string }>> });\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);\n }\n\n override async handleToolEnd(\n output: unknown,\n runId: string,\n _parentRunId?: string,\n _tags?: string[],\n _kwargs?: { name?: string },\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.TOOL_END)) {\n return;\n }\n const text = String(output).trim();\n if (!text) {\n return;\n }\n const toolName = _kwargs?.name;\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);\n }\n\n override async handleRetrieverEnd(\n documents: ReadonlyArray<{ pageContent?: string }>,\n runId: string,\n ): Promise<void> {\n if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {\n return;\n }\n const text = extractTextFromDocuments(documents);\n if (!text) {\n return;\n }\n await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);\n }\n }\n\n return new SilmarilFirewallHandler();\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { PromptBlockedException } from \"../exceptions.js\";\nimport { HookLabel } from \"../hooks.js\";\nimport type { BlockResult, MiddlewareOptions } from \"../types.js\";\n\ninterface VercelContentPart {\n type?: string;\n text?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n}\n\ninterface VercelPromptMessage {\n role: string;\n content: string | ReadonlyArray<VercelContentPart | string>;\n}\n\ninterface VercelToolCall {\n toolCallType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n}\n\ninterface VercelGenerateResult {\n text?: string;\n toolCalls?: ReadonlyArray<VercelToolCall>;\n}\n\ninterface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doGenerate: () => PromiseLike<TResult>;\n}\n\ninterface WrapStreamArgs<\n TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>,\n> {\n params: { prompt: ReadonlyArray<VercelPromptMessage> } & Record<string, unknown>;\n doStream: () => PromiseLike<TResult>;\n}\n\ninterface StreamPart {\n type?: string;\n textDelta?: string;\n delta?: string;\n}\n\nfunction stringifyToolValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n\nfunction extractContentText(content: VercelPromptMessage[\"content\"]): string {\n if (typeof content === \"string\") {\n return content;\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === \"string\") {\n parts.push(block);\n } else if (block && block.type === \"text\") {\n parts.push(block.text ?? \"\");\n }\n }\n return parts.join(\" \");\n}\n\nfunction iterateToolResultParts(\n message: VercelPromptMessage,\n): Array<{ text: string; toolName: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined }> = [];\n for (const part of message.content) {\n if (typeof part === \"string\") {\n continue;\n }\n if (part.type === \"tool-result\") {\n const text = stringifyToolValue(part.result);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName });\n }\n }\n }\n return out;\n}\n\nfunction findLastUserMessage(\n messages: ReadonlyArray<VercelPromptMessage>,\n): VercelPromptMessage | undefined {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role.toLowerCase() === \"user\") {\n return msg;\n }\n }\n return undefined;\n}\n\nexport interface FirewallMiddleware {\n wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(\n args: WrapGenerateArgs<TResult>,\n ) => Promise<TResult>;\n wrapStream: <TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>(\n args: WrapStreamArgs<TResult>,\n ) => Promise<TResult>;\n}\n\nexport function createMiddleware(\n firewall: Firewall,\n options: MiddlewareOptions = {},\n): FirewallMiddleware {\n const scanInput = options.scanInput ?? true;\n const scanOutput = options.scanOutput ?? false;\n const overrideThreshold = options.threshold;\n const overrideHookThresholds = options.hookThresholds ?? {};\n const shadowMode = options.shadowMode ?? firewall.shadowMode;\n\n const effectiveThreshold = (label: HookLabel): number => {\n if (overrideHookThresholds[label] !== undefined) {\n return overrideHookThresholds[label] as number;\n }\n if (firewall.hookThresholds[label] !== undefined) {\n return firewall.hookThresholds[label] as number;\n }\n return overrideThreshold ?? firewall.threshold;\n };\n\n const classifyOrBlock = async (\n text: string,\n hook: HookLabel,\n toolName?: string,\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const result: BlockResult = await firewall.classify(\n text,\n toolName !== undefined ? { hook, toolName } : { hook },\n );\n const threshold = effectiveThreshold(hook);\n const blocked = result.score >= threshold;\n options.onClassify?.({\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n text,\n result,\n blocked,\n shadowMode,\n });\n if (blocked && !shadowMode) {\n const err = new PromptBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n });\n options.onBlocked?.(err);\n throw err;\n }\n };\n\n const scanPrompt = async (prompt: ReadonlyArray<VercelPromptMessage>): Promise<void> => {\n if (prompt.length === 0) {\n return;\n }\n const last = prompt[prompt.length - 1]!;\n const role = last.role.toLowerCase();\n\n // If the newest message is a tool result (typical mid-flow in a\n // tool-calling loop), classify each tool-result part individually with\n // tool_response + toolName. No manual wiring from the caller — the\n // toolName is read directly from the Vercel content part.\n if (role === \"tool\") {\n for (const { text, toolName } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, toolName);\n }\n return;\n }\n\n // Otherwise, fall back to scanning the most recent user message.\n // Covers: [user], [system, user], [user, assistant(text), user], etc.\n const lastUser = findLastUserMessage(prompt);\n if (!lastUser) {\n return;\n }\n const text = extractContentText(lastUser.content).trim();\n if (!text) {\n return;\n }\n await classifyOrBlock(text, HookLabel.USER_INPUT);\n };\n\n const scanGenerateResult = async (result: VercelGenerateResult): Promise<void> => {\n if (scanOutput && typeof result.text === \"string\" && result.text.length > 0) {\n await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);\n }\n if (options.scanToolCalls && Array.isArray(result.toolCalls)) {\n for (const call of result.toolCalls) {\n if (!call || typeof call !== \"object\") {\n continue;\n }\n const args =\n typeof call.args === \"string\" ? call.args : stringifyToolValue(call.args);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);\n }\n }\n }\n };\n\n return {\n async wrapGenerate<TResult extends VercelGenerateResult & Record<string, unknown>>({\n params,\n doGenerate,\n }: WrapGenerateArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const result = await doGenerate();\n await scanGenerateResult(result);\n return result;\n },\n\n async wrapStream<TResult extends { stream: ReadableStream<unknown> } & Record<string, unknown>>({\n params,\n doStream,\n }: WrapStreamArgs<TResult>): Promise<TResult> {\n if (scanInput) {\n await scanPrompt(params.prompt);\n }\n const { stream, ...rest } = await doStream();\n if (!scanOutput) {\n return { stream, ...rest } as unknown as TResult;\n }\n\n let buffered = \"\";\n const transformed = stream.pipeThrough(\n new TransformStream<unknown, unknown>({\n transform(chunk, controller): void {\n const part = chunk as StreamPart;\n if (part && part.type === \"text-delta\") {\n const delta = part.textDelta ?? part.delta ?? \"\";\n buffered += delta;\n }\n controller.enqueue(chunk);\n },\n async flush(controller): Promise<void> {\n if (!buffered.trim()) {\n return;\n }\n try {\n await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);\n } catch (err) {\n if (err instanceof PromptBlockedException) {\n controller.enqueue({ type: \"error\", error: err });\n return;\n }\n throw err;\n }\n },\n }),\n );\n return { stream: transformed, ...rest } as unknown as TResult;\n },\n };\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nconst CHARS_PER_TOKEN = 4;\n\nexport const MAX_INPUT_TOKENS = 10_240;\nexport const CHUNK_WINDOW = 400;\nexport const CHUNK_OVERLAP = 64;\n\nexport const MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;\nexport const CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;\nexport const CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;\nconst CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;\n\nexport function chunkText(text: string): string[] {\n const n = text.length;\n if (n > MAX_INPUT_CHARS) {\n throw new Error(\n `Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); ` +\n `max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`,\n );\n }\n if (n <= CHUNK_WINDOW_CHARS) {\n return [text];\n }\n const chunks: string[] = [];\n for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {\n const end = start + CHUNK_WINDOW_CHARS;\n chunks.push(text.slice(start, end));\n if (end >= n) {\n break;\n }\n }\n return chunks;\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nimport { createMiddleware, type FirewallMiddleware } from \"./adapters/vercel.js\";\nimport { chunkText } from \"./chunking.js\";\nimport { SilmarilApiError } from \"./exceptions.js\";\nimport type { HookLabel } from \"./hooks.js\";\nimport type {\n BlockResult,\n ClassifyBatchOptions,\n ClassifyOptions,\n ClassificationMetadata,\n FirewallOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n Prediction,\n} from \"./types.js\";\n\nexport const DEFAULT_THRESHOLD = 0.5;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_RETRIES = 5;\nconst MAX_BACKOFF_SECONDS = 30;\n\ninterface SingleClassifyPayload {\n text: string;\n hook?: string;\n tool_name?: string;\n metadata?: ClassificationMetadata;\n}\n\ninterface BatchClassifyPayload {\n texts: readonly string[];\n hooks?: readonly string[];\n tool_names?: readonly (string | null)[];\n metadata?: readonly (ClassificationMetadata | null)[];\n}\n\ninterface SingleClassifyResponse {\n prediction: Prediction;\n score: number;\n primary_outcome?: string;\n outcome_scores?: Record<string, number>;\n detector_scores?: Record<string, number>;\n detector_counts?: Record<string, number>;\n}\n\ninterface BatchClassifyResponse {\n predictions: readonly SingleClassifyResponse[];\n}\n\nfunction blockResultFromResponse(data: SingleClassifyResponse): BlockResult {\n const result: {\n prediction: Prediction;\n score: number;\n primaryOutcome?: string;\n outcomeScores?: Readonly<Record<string, number>>;\n detectorScores?: Readonly<Record<string, number>>;\n detectorCounts?: Readonly<Record<string, number>>;\n } = {\n prediction: data.prediction,\n score: Number(data.score),\n };\n if (data.primary_outcome !== undefined) {\n result.primaryOutcome = data.primary_outcome;\n }\n if (data.outcome_scores !== undefined) {\n result.outcomeScores = Object.freeze(\n Object.fromEntries(Object.entries(data.outcome_scores).map(([k, v]) => [k, Number(v)])),\n );\n }\n if (data.detector_scores !== undefined) {\n result.detectorScores = Object.freeze(\n Object.fromEntries(Object.entries(data.detector_scores).map(([k, v]) => [k, Number(v)])),\n );\n }\n if (data.detector_counts !== undefined) {\n result.detectorCounts = Object.freeze(\n Object.fromEntries(Object.entries(data.detector_counts).map(([k, v]) => [k, Number(v)])),\n );\n }\n return Object.freeze(result);\n}\n\nexport class Firewall {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly threshold: number;\n readonly timeoutMs: number;\n readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;\n readonly shadowMode: boolean;\n\n private readonly headers: Readonly<Record<string, string>>;\n\n constructor(options: FirewallOptions) {\n if (!options.apiKey) {\n throw new Error(\"Firewall: apiKey is required\");\n }\n if (!options.apiUrl) {\n throw new Error(\"Firewall: apiUrl is required\");\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl;\n this.threshold = options.threshold ?? DEFAULT_THRESHOLD;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.hookThresholds = Object.freeze({ ...(options.hookThresholds ?? {}) });\n this.shadowMode = options.shadowMode ?? false;\n this.headers = Object.freeze({\n \"x-api-key\": this.apiKey,\n \"content-type\": \"application/json\",\n });\n }\n\n async classify(text: string, options: ClassifyOptions = {}): Promise<BlockResult> {\n const chunks = chunkText(text);\n if (chunks.length === 1) {\n const payload: SingleClassifyPayload = { text: chunks[0]! };\n if (options.hook !== undefined) {\n payload.hook = options.hook;\n }\n if (options.toolName !== undefined) {\n payload.tool_name = options.toolName;\n }\n if (options.metadata !== undefined) {\n payload.metadata = options.metadata;\n }\n const data = await this.postWithRetry<SingleClassifyResponse>(payload);\n return blockResultFromResponse(data);\n }\n const batchOpts: ClassifyBatchOptions = {};\n if (options.hook !== undefined) {\n batchOpts.hooks = chunks.map(() => options.hook!);\n }\n if (options.toolName !== undefined) {\n batchOpts.toolNames = chunks.map(() => options.toolName);\n }\n if (options.metadata !== undefined) {\n batchOpts.metadata = chunks.map(() => options.metadata);\n }\n const results = await this.classifyBatch(chunks, batchOpts);\n return results.reduce((best, r) => (r.score > best.score ? r : best));\n }\n\n async classifyBatch(\n texts: readonly string[],\n options: ClassifyBatchOptions = {},\n ): Promise<BlockResult[]> {\n if (options.hooks !== undefined && options.hooks.length !== texts.length) {\n throw new Error(\n `Firewall: hooks length ${options.hooks.length} does not match texts length ${texts.length}`,\n );\n }\n if (options.toolNames !== undefined && options.toolNames.length !== texts.length) {\n throw new Error(\n `Firewall: toolNames length ${options.toolNames.length} does not match texts length ${texts.length}`,\n );\n }\n if (options.metadata !== undefined && options.metadata.length !== texts.length) {\n throw new Error(\n `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`,\n );\n }\n\n const payload: BatchClassifyPayload = { texts };\n if (options.hooks && options.hooks.length > 0) {\n payload.hooks = options.hooks.map((h) => String(h));\n }\n if (options.toolNames && options.toolNames.length > 0) {\n payload.tool_names = options.toolNames.map((t) => (t === undefined ? null : t));\n }\n if (options.metadata && options.metadata.length > 0) {\n payload.metadata = options.metadata.map((m) => (m === undefined ? null : m));\n }\n const data = await this.postWithRetry<BatchClassifyResponse>(payload);\n return data.predictions.map((p) => blockResultFromResponse(p));\n }\n\n asLangChainHandler(options: LangChainAdapterOptions = {}): Promise<BaseCallbackHandler> {\n return import(\"./adapters/langchain.js\").then((m) => m.createLangChainHandler(this, options));\n }\n\n asMiddleware(options: MiddlewareOptions = {}): FirewallMiddleware {\n return createMiddleware(this, options);\n }\n\n effectiveThreshold(hook: HookLabel | undefined): number {\n if (hook === undefined) {\n return this.threshold;\n }\n return this.hookThresholds[hook] ?? this.threshold;\n }\n\n private async postWithRetry<T>(\n payload: SingleClassifyPayload | BatchClassifyPayload,\n maxRetries: number = DEFAULT_MAX_RETRIES,\n ): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const response = await fetch(this.apiUrl, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n if (response.status !== 429 || attempt === maxRetries) {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new SilmarilApiError({\n status: response.status,\n statusText: response.statusText,\n body,\n });\n }\n return (await response.json()) as T;\n }\n const waitSeconds = Math.min(2 ** attempt, MAX_BACKOFF_SECONDS);\n await new Promise<void>((resolve) => setTimeout(resolve, waitSeconds * 1000));\n }\n throw new Error(\"Firewall: exhausted retries (unreachable)\");\n }\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport {\n Firewall,\n DEFAULT_THRESHOLD,\n DEFAULT_TIMEOUT_MS,\n} from \"./firewall.js\";\n\nexport { PromptBlockedException, SilmarilApiError } from \"./exceptions.js\";\n\nexport type { FirewallMiddleware } from \"./adapters/vercel.js\";\n\nexport {\n chunkText,\n MAX_INPUT_TOKENS,\n CHUNK_WINDOW,\n CHUNK_OVERLAP,\n MAX_INPUT_CHARS,\n CHUNK_WINDOW_CHARS,\n CHUNK_OVERLAP_CHARS,\n} from \"./chunking.js\";\n\nexport {\n HookLabel,\n FirewallHook,\n DEFAULT_HOOKS,\n INPUT_HOOKS,\n OUTPUT_HOOKS,\n ALL_HOOKS,\n DEFAULT_HOOK_THRESHOLDS,\n FIREWALL_HOOK_TO_LABEL,\n resolveHooks,\n prependHook,\n prependToolName,\n} from \"./hooks.js\";\n\nexport type {\n BlockResult,\n ClassifyEvent,\n Prediction,\n ClassificationMetadata,\n FirewallOptions,\n ClassifyOptions,\n ClassifyBatchOptions,\n LangChainAdapterOptions,\n MiddlewareOptions,\n} from \"./types.js\";\n"],"mappings":"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","names":["findLastUserMessage","text"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silmaril-security/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "TypeScript SDK for Silmaril Firewall — prompt injection and jailbreak detection",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Silmaril Security Inc.",
@@ -42,14 +42,16 @@
42
42
  "LICENSE"
43
43
  ],
44
44
  "publishConfig": {
45
- "access": "restricted"
45
+ "access": "public"
46
46
  },
47
47
  "engines": {
48
48
  "node": ">=18"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "tsup",
52
- "typecheck": "tsc --noEmit",
52
+ "prepare": "tsup",
53
+ "typecheck": "tsc --noEmit && npm run typecheck:notebooks",
54
+ "typecheck:notebooks": "node scripts/typecheck-notebooks.mjs",
53
55
  "lint": "eslint src test",
54
56
  "test": "vitest run",
55
57
  "test:watch": "vitest"
@@ -67,7 +69,8 @@
67
69
  }
68
70
  },
69
71
  "devDependencies": {
70
- "@langchain/core": "^0.3.0",
72
+ "@ai-sdk/provider": "^2.0.0",
73
+ "@langchain/core": "^1.1.39",
71
74
  "@types/node": "^20.12.0",
72
75
  "@typescript-eslint/eslint-plugin": "^7.8.0",
73
76
  "@typescript-eslint/parser": "^7.8.0",
@@ -76,6 +79,6 @@
76
79
  "tsup": "^8.0.0",
77
80
  "typescript": "^5.4.0",
78
81
  "undici": "^6.15.0",
79
- "vitest": "^1.6.0"
82
+ "vitest": "^4.1.4"
80
83
  }
81
84
  }