@silmaril-security/sdk 0.1.2 → 0.3.0

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.
@@ -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 interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\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 readonly details: MalformedInputDetails | undefined;\n readonly error: string | undefined;\n readonly apiMessage: string | undefined;\n\n constructor(params: {\n status: number;\n statusText: string;\n body: string;\n details?: MalformedInputDetails;\n error?: string;\n apiMessage?: string;\n }) {\n const statusText = params.statusText ? ` ${params.statusText}` : \"\";\n super(`Silmaril API error ${params.status}${statusText}`);\n this.name = \"SilmarilApiError\";\n this.status = params.status;\n this.statusText = params.statusText;\n this.body = params.body;\n const parsed = parseErrorBody(params.body);\n this.details = params.details ?? parsed.details;\n this.error = params.error ?? parsed.error;\n this.apiMessage = params.apiMessage ?? parsed.apiMessage;\n Object.setPrototypeOf(this, SilmarilApiError.prototype);\n }\n}\n\nfunction parseErrorBody(body: string): {\n details: MalformedInputDetails | undefined;\n error: string | undefined;\n apiMessage: string | undefined;\n} {\n try {\n const parsed = JSON.parse(body) as unknown;\n if (!parsed || typeof parsed !== \"object\") {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n const data = parsed as Record<string, unknown>;\n return {\n details:\n data.details && typeof data.details === \"object\"\n ? (data.details as MalformedInputDetails)\n : undefined,\n error: typeof data.error === \"string\" ? data.error : undefined,\n apiMessage: typeof data.message === \"string\" ? data.message : undefined,\n };\n } catch {\n return { details: undefined, error: undefined, apiMessage: undefined };\n }\n}\n","// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nexport const HookLabel = {\n USER_INPUT: \"user_input\",\n SYSTEM_PROMPT: \"system_prompt\",\n TOOL_CALL: \"tool_call\",\n TOOL_RESPONSE: \"tool_response\",\n LLM_OUTPUT: \"llm_output\",\n UNKNOWN: \"unknown\",\n} as const;\n\nexport type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];\n\nexport const FirewallHook = {\n LLM_START: \"on_llm_start\",\n CHAT_MODEL_START: \"on_chat_model_start\",\n TOOL_START: \"on_tool_start\",\n RETRIEVER_START: \"on_retriever_start\",\n LLM_END: \"on_llm_end\",\n TOOL_END: \"on_tool_end\",\n RETRIEVER_END: \"on_retriever_end\",\n} as const;\n\nexport type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];\n\nexport const DEFAULT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n]);\n\nexport const INPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_START,\n FirewallHook.CHAT_MODEL_START,\n FirewallHook.TOOL_START,\n FirewallHook.RETRIEVER_START,\n]);\n\nexport const OUTPUT_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n FirewallHook.LLM_END,\n FirewallHook.TOOL_END,\n FirewallHook.RETRIEVER_END,\n]);\n\nexport const ALL_HOOKS: ReadonlySet<FirewallHook> = new Set<FirewallHook>([\n ...INPUT_HOOKS,\n ...OUTPUT_HOOKS,\n]);\n\nexport const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>> = {\n [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,\n [FirewallHook.LLM_START]: HookLabel.USER_INPUT,\n [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,\n [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,\n [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,\n [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT,\n};\n\nconst FIREWALL_HOOK_VALUES: ReadonlySet<string> = new Set(Object.values(FirewallHook));\n\nexport function resolveHooks(\n hooks: Iterable<FirewallHook | string> | undefined,\n): ReadonlySet<FirewallHook> {\n if (hooks === undefined) {\n return DEFAULT_HOOKS;\n }\n const resolved = new Set<FirewallHook>();\n for (const h of hooks) {\n if (!FIREWALL_HOOK_VALUES.has(h)) {\n throw new Error(`Invalid FirewallHook value: ${String(h)}`);\n }\n resolved.add(h as FirewallHook);\n }\n return resolved;\n}\n\nexport function prependHook(text: string, hook: HookLabel | string | undefined | null): string {\n if (hook === undefined || hook === null) {\n return text;\n }\n if (hook === HookLabel.UNKNOWN) {\n return text;\n }\n return `[HOOK:${hook}] ${text}`;\n}\n\nexport function prependToolName(text: string, toolName: string | undefined | null): string {\n if (toolName === undefined || toolName === null) {\n return text;\n }\n return `[TOOL:${toolName}] ${text}`;\n}\n","// 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 shadowMode = options.shadowMode ?? firewall.shadowMode;\n const onClassify = options.onClassify;\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 = result.threshold;\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_CHUNK_CONCURRENCY,\n DEFAULT_TIMEOUT_MS,\n} from \"./firewall.js\";\n\nexport { PromptBlockedException, SilmarilApiError } from \"./exceptions.js\";\nexport type { MalformedInputDetails } from \"./exceptions.js\";\n\nexport type { FirewallMiddleware } from \"./adapters/vercel.js\";\n\nexport {\n chunkText,\n sanitizeText,\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 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 shadowMode = options.shadowMode ?? firewall.shadowMode;\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 = result.threshold;\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\nfunction isHighSurrogate(code: number): boolean {\n return code >= 0xd800 && code <= 0xdbff;\n}\n\nfunction isLowSurrogate(code: number): boolean {\n return code >= 0xdc00 && code <= 0xdfff;\n}\n\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (isHighSurrogate(code)) {\n if (i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {\n out += text[i]!;\n out += text[i + 1]!;\n i++;\n }\n continue;\n }\n if (isLowSurrogate(code)) {\n continue;\n }\n out += text[i]!;\n }\n return out;\n}\n\nfunction safeChunkStart(text: string, start: number): number {\n if (\n start > 0 &&\n start < text.length &&\n isLowSurrogate(text.charCodeAt(start)) &&\n isHighSurrogate(text.charCodeAt(start - 1))\n ) {\n return start + 1;\n }\n return start;\n}\n\nfunction safeChunkEnd(text: string, end: number): number {\n if (\n end > 0 &&\n end < text.length &&\n isHighSurrogate(text.charCodeAt(end - 1)) &&\n isLowSurrogate(text.charCodeAt(end))\n ) {\n return end - 1;\n }\n return end;\n}\n\nexport function chunkText(text: string): string[] {\n text = sanitizeText(text);\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 safeStart = safeChunkStart(text, start);\n if (safeStart >= n) {\n break;\n }\n const end = safeChunkEnd(text, safeStart + CHUNK_WINDOW_CHARS);\n chunks.push(text.slice(safeStart, 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, sanitizeText } from \"./chunking.js\";\nimport { SilmarilApiError } from \"./exceptions.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 BASE_THRESHOLD = 0.5;\nexport const TARGET_SEQUENCE_FPR = 0.01;\nexport const MAX_ADAPTIVE_THRESHOLD = 0.9;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_CHUNK_CONCURRENCY = 8;\nconst DEFAULT_MAX_RETRIES = 5;\nconst MAX_BACKOFF_SECONDS = 30;\nconst MAX_ERROR_BODY_BYTES = 1 << 16;\n\ninterface SingleClassifyPayload {\n text: string;\n threshold: number;\n hook?: string;\n tool_name?: string;\n metadata?: ClassificationMetadata;\n}\n\ninterface BatchClassifyPayload {\n texts: readonly string[];\n threshold: number;\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\nexport function adaptiveThreshold(scoringOpportunityCount: number): number {\n if (!Number.isInteger(scoringOpportunityCount) || scoringOpportunityCount < 1) {\n throw new Error(\n `Firewall: scoringOpportunityCount must be an integer >= 1, got ${scoringOpportunityCount}`,\n );\n }\n if (scoringOpportunityCount === 1) {\n return BASE_THRESHOLD;\n }\n const targetChunkFpr = 1 - Math.pow(1 - TARGET_SEQUENCE_FPR, 1 / scoringOpportunityCount);\n const oddsRatio = TARGET_SEQUENCE_FPR / targetChunkFpr;\n const rawThreshold = oddsRatio / (1 + oddsRatio);\n return Math.min(rawThreshold, MAX_ADAPTIVE_THRESHOLD);\n}\n\nfunction blockResultFromResponse(data: SingleClassifyResponse, threshold: number): BlockResult {\n const result: {\n prediction: Prediction;\n score: number;\n threshold: 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 threshold,\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\nasync function readCappedErrorBody(response: Response): Promise<string> {\n if (!response.body) {\n return response.text().then((body) => body.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => \"\");\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let remaining = MAX_ERROR_BODY_BYTES;\n try {\n while (remaining > 0) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n if (!value) {\n continue;\n }\n const chunk = value.byteLength > remaining ? value.slice(0, remaining) : value;\n chunks.push(chunk);\n remaining -= chunk.byteLength;\n if (chunk.byteLength < value.byteLength) {\n break;\n }\n }\n } catch {\n return \"\";\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n\n const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n const body = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(body);\n}\n\nasync function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n task: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let nextIndex = 0;\n let firstError: unknown;\n const workerCount = Math.min(concurrency, items.length);\n\n async function worker(): Promise<void> {\n for (;;) {\n const index = nextIndex;\n nextIndex += 1;\n if (index >= items.length) {\n return;\n }\n try {\n results[index] = await task(items[index]!, index);\n } catch (err) {\n firstError ??= err;\n }\n }\n }\n\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n if (firstError !== undefined) {\n throw firstError;\n }\n return results;\n}\n\nexport class Firewall {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly timeoutMs: number;\n readonly chunkConcurrency: 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.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (typeof this.timeoutMs !== \"number\" || !Number.isFinite(this.timeoutMs) || this.timeoutMs < 0) {\n throw new Error(`Firewall: timeoutMs must be a finite non-negative number, got ${this.timeoutMs}`);\n }\n this.chunkConcurrency = options.chunkConcurrency ?? DEFAULT_CHUNK_CONCURRENCY;\n if (!Number.isInteger(this.chunkConcurrency) || this.chunkConcurrency < 1) {\n throw new Error(\n `Firewall: chunkConcurrency must be an integer >= 1, got ${this.chunkConcurrency}`,\n );\n }\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 const threshold = adaptiveThreshold(chunks.length);\n if (chunks.length === 1) {\n return this.classifySingleChunk(chunks[0]!, options, threshold);\n }\n const results = await mapWithConcurrency(chunks, this.chunkConcurrency, (chunk) =>\n this.classifySingleChunk(chunk, options, threshold),\n );\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 (texts.length === 0) {\n throw new Error(\"Firewall: texts must not be empty\");\n }\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 threshold = adaptiveThreshold(texts.length);\n const payload: BatchClassifyPayload = {\n texts: texts.map((text) => sanitizeText(text)),\n threshold,\n };\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, threshold));\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 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 redirect: \"error\",\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n if (response.status !== 429 || attempt === maxRetries) {\n if (!response.ok) {\n const body = await readCappedErrorBody(response);\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 private async classifySingleChunk(\n text: string,\n options: ClassifyOptions,\n threshold: number,\n ): Promise<BlockResult> {\n const payload: SingleClassifyPayload = { text, threshold };\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, threshold);\n }\n}\n"],"mappings":"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","names":["findLastUserMessage","text","body"]}
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";
@@ -22,7 +24,6 @@ declare const INPUT_HOOKS: ReadonlySet<FirewallHook>;
22
24
  declare const OUTPUT_HOOKS: ReadonlySet<FirewallHook>;
23
25
  declare const ALL_HOOKS: ReadonlySet<FirewallHook>;
24
26
  declare const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>>;
25
- declare const DEFAULT_HOOK_THRESHOLDS: Readonly<Record<HookLabel, number>>;
26
27
  declare function resolveHooks(hooks: Iterable<FirewallHook | string> | undefined): ReadonlySet<FirewallHook>;
27
28
  declare function prependHook(text: string, hook: HookLabel | string | undefined | null): string;
28
29
  declare function prependToolName(text: string, toolName: string | undefined | null): string;
@@ -31,30 +32,38 @@ type Prediction = "BENIGN" | "MALICIOUS";
31
32
  interface BlockResult {
32
33
  readonly prediction: Prediction;
33
34
  readonly score: number;
35
+ readonly threshold: 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;
37
43
  apiUrl: string;
38
- threshold?: number;
39
44
  timeoutMs?: number;
40
- hookThresholds?: Partial<Record<HookLabel, number>>;
45
+ chunkConcurrency?: number;
46
+ shadowMode?: boolean;
41
47
  }
48
+ type ClassificationMetadata = Readonly<Record<string, unknown>>;
42
49
  interface ClassifyOptions {
43
50
  hook?: HookLabel;
44
51
  toolName?: string;
52
+ metadata?: ClassificationMetadata;
45
53
  }
46
54
  interface ClassifyBatchOptions {
47
55
  hooks?: readonly HookLabel[];
48
56
  toolNames?: readonly (string | undefined)[];
57
+ metadata?: readonly (ClassificationMetadata | undefined)[];
49
58
  }
50
59
  interface LangChainAdapterOptions {
51
- threshold?: number;
52
- hookThresholds?: Partial<Record<HookLabel, number>>;
53
60
  hooks?: ReadonlySet<FirewallHook>;
54
61
  includeSystem?: boolean;
55
62
  includeTool?: boolean;
56
63
  failOpen?: boolean;
57
64
  logger?: (message: string, error: unknown) => void;
65
+ shadowMode?: boolean;
66
+ onClassify?: (event: ClassifyEvent) => void;
58
67
  }
59
68
  interface ClassifyEvent {
60
69
  readonly hook: HookLabel;
@@ -62,13 +71,13 @@ interface ClassifyEvent {
62
71
  readonly text: string;
63
72
  readonly result: BlockResult;
64
73
  readonly blocked: boolean;
74
+ readonly shadowMode: boolean;
65
75
  }
66
76
  interface MiddlewareOptions {
67
77
  scanInput?: boolean;
68
78
  scanOutput?: boolean;
69
79
  scanToolCalls?: boolean;
70
- threshold?: number;
71
- hookThresholds?: Partial<Record<HookLabel, number>>;
80
+ shadowMode?: boolean;
72
81
  onBlocked?: (err: Error) => void;
73
82
  onClassify?: (event: ClassifyEvent) => void;
74
83
  }
@@ -116,24 +125,32 @@ interface FirewallMiddleware {
116
125
  } & Record<string, unknown>>(args: WrapStreamArgs<TResult>) => Promise<TResult>;
117
126
  }
118
127
 
119
- declare const DEFAULT_THRESHOLD = 0.5;
120
128
  declare const DEFAULT_TIMEOUT_MS = 10000;
129
+ declare const DEFAULT_CHUNK_CONCURRENCY = 8;
121
130
  declare class Firewall {
122
131
  readonly apiKey: string;
123
132
  readonly apiUrl: string;
124
- readonly threshold: number;
125
133
  readonly timeoutMs: number;
126
- readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;
134
+ readonly chunkConcurrency: number;
135
+ readonly shadowMode: boolean;
127
136
  private readonly headers;
128
137
  constructor(options: FirewallOptions);
129
138
  classify(text: string, options?: ClassifyOptions): Promise<BlockResult>;
130
139
  classifyBatch(texts: readonly string[], options?: ClassifyBatchOptions): Promise<BlockResult[]>;
131
- asLangChainHandler(options?: LangChainAdapterOptions): Promise<unknown>;
140
+ asLangChainHandler(options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
132
141
  asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
133
- effectiveThreshold(hook: HookLabel | undefined): number;
134
142
  private postWithRetry;
143
+ private classifySingleChunk;
135
144
  }
136
145
 
146
+ interface MalformedInputDetails {
147
+ field?: string;
148
+ inputIndex?: number;
149
+ charOffset?: number;
150
+ malformedToken?: string;
151
+ codePoint?: string;
152
+ reason?: string;
153
+ }
137
154
  declare class PromptBlockedException extends Error {
138
155
  readonly score: number;
139
156
  readonly threshold: number;
@@ -151,10 +168,16 @@ declare class SilmarilApiError extends Error {
151
168
  readonly status: number;
152
169
  readonly statusText: string;
153
170
  readonly body: string;
171
+ readonly details: MalformedInputDetails | undefined;
172
+ readonly error: string | undefined;
173
+ readonly apiMessage: string | undefined;
154
174
  constructor(params: {
155
175
  status: number;
156
176
  statusText: string;
157
177
  body: string;
178
+ details?: MalformedInputDetails;
179
+ error?: string;
180
+ apiMessage?: string;
158
181
  });
159
182
  }
160
183
 
@@ -164,6 +187,7 @@ declare const CHUNK_OVERLAP = 64;
164
187
  declare const MAX_INPUT_CHARS: number;
165
188
  declare const CHUNK_WINDOW_CHARS: number;
166
189
  declare const CHUNK_OVERLAP_CHARS: number;
190
+ declare function sanitizeText(text: string): string;
167
191
  declare function chunkText(text: string): string[];
168
192
 
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 };
193
+ export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassificationMetadata, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_CHUNK_CONCURRENCY, DEFAULT_HOOKS, 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 MalformedInputDetails, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks, sanitizeText };
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";
@@ -22,7 +24,6 @@ declare const INPUT_HOOKS: ReadonlySet<FirewallHook>;
22
24
  declare const OUTPUT_HOOKS: ReadonlySet<FirewallHook>;
23
25
  declare const ALL_HOOKS: ReadonlySet<FirewallHook>;
24
26
  declare const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>>;
25
- declare const DEFAULT_HOOK_THRESHOLDS: Readonly<Record<HookLabel, number>>;
26
27
  declare function resolveHooks(hooks: Iterable<FirewallHook | string> | undefined): ReadonlySet<FirewallHook>;
27
28
  declare function prependHook(text: string, hook: HookLabel | string | undefined | null): string;
28
29
  declare function prependToolName(text: string, toolName: string | undefined | null): string;
@@ -31,30 +32,38 @@ type Prediction = "BENIGN" | "MALICIOUS";
31
32
  interface BlockResult {
32
33
  readonly prediction: Prediction;
33
34
  readonly score: number;
35
+ readonly threshold: 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;
37
43
  apiUrl: string;
38
- threshold?: number;
39
44
  timeoutMs?: number;
40
- hookThresholds?: Partial<Record<HookLabel, number>>;
45
+ chunkConcurrency?: number;
46
+ shadowMode?: boolean;
41
47
  }
48
+ type ClassificationMetadata = Readonly<Record<string, unknown>>;
42
49
  interface ClassifyOptions {
43
50
  hook?: HookLabel;
44
51
  toolName?: string;
52
+ metadata?: ClassificationMetadata;
45
53
  }
46
54
  interface ClassifyBatchOptions {
47
55
  hooks?: readonly HookLabel[];
48
56
  toolNames?: readonly (string | undefined)[];
57
+ metadata?: readonly (ClassificationMetadata | undefined)[];
49
58
  }
50
59
  interface LangChainAdapterOptions {
51
- threshold?: number;
52
- hookThresholds?: Partial<Record<HookLabel, number>>;
53
60
  hooks?: ReadonlySet<FirewallHook>;
54
61
  includeSystem?: boolean;
55
62
  includeTool?: boolean;
56
63
  failOpen?: boolean;
57
64
  logger?: (message: string, error: unknown) => void;
65
+ shadowMode?: boolean;
66
+ onClassify?: (event: ClassifyEvent) => void;
58
67
  }
59
68
  interface ClassifyEvent {
60
69
  readonly hook: HookLabel;
@@ -62,13 +71,13 @@ interface ClassifyEvent {
62
71
  readonly text: string;
63
72
  readonly result: BlockResult;
64
73
  readonly blocked: boolean;
74
+ readonly shadowMode: boolean;
65
75
  }
66
76
  interface MiddlewareOptions {
67
77
  scanInput?: boolean;
68
78
  scanOutput?: boolean;
69
79
  scanToolCalls?: boolean;
70
- threshold?: number;
71
- hookThresholds?: Partial<Record<HookLabel, number>>;
80
+ shadowMode?: boolean;
72
81
  onBlocked?: (err: Error) => void;
73
82
  onClassify?: (event: ClassifyEvent) => void;
74
83
  }
@@ -116,24 +125,32 @@ interface FirewallMiddleware {
116
125
  } & Record<string, unknown>>(args: WrapStreamArgs<TResult>) => Promise<TResult>;
117
126
  }
118
127
 
119
- declare const DEFAULT_THRESHOLD = 0.5;
120
128
  declare const DEFAULT_TIMEOUT_MS = 10000;
129
+ declare const DEFAULT_CHUNK_CONCURRENCY = 8;
121
130
  declare class Firewall {
122
131
  readonly apiKey: string;
123
132
  readonly apiUrl: string;
124
- readonly threshold: number;
125
133
  readonly timeoutMs: number;
126
- readonly hookThresholds: Readonly<Partial<Record<HookLabel, number>>>;
134
+ readonly chunkConcurrency: number;
135
+ readonly shadowMode: boolean;
127
136
  private readonly headers;
128
137
  constructor(options: FirewallOptions);
129
138
  classify(text: string, options?: ClassifyOptions): Promise<BlockResult>;
130
139
  classifyBatch(texts: readonly string[], options?: ClassifyBatchOptions): Promise<BlockResult[]>;
131
- asLangChainHandler(options?: LangChainAdapterOptions): Promise<unknown>;
140
+ asLangChainHandler(options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
132
141
  asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
133
- effectiveThreshold(hook: HookLabel | undefined): number;
134
142
  private postWithRetry;
143
+ private classifySingleChunk;
135
144
  }
136
145
 
146
+ interface MalformedInputDetails {
147
+ field?: string;
148
+ inputIndex?: number;
149
+ charOffset?: number;
150
+ malformedToken?: string;
151
+ codePoint?: string;
152
+ reason?: string;
153
+ }
137
154
  declare class PromptBlockedException extends Error {
138
155
  readonly score: number;
139
156
  readonly threshold: number;
@@ -151,10 +168,16 @@ declare class SilmarilApiError extends Error {
151
168
  readonly status: number;
152
169
  readonly statusText: string;
153
170
  readonly body: string;
171
+ readonly details: MalformedInputDetails | undefined;
172
+ readonly error: string | undefined;
173
+ readonly apiMessage: string | undefined;
154
174
  constructor(params: {
155
175
  status: number;
156
176
  statusText: string;
157
177
  body: string;
178
+ details?: MalformedInputDetails;
179
+ error?: string;
180
+ apiMessage?: string;
158
181
  });
159
182
  }
160
183
 
@@ -164,6 +187,7 @@ declare const CHUNK_OVERLAP = 64;
164
187
  declare const MAX_INPUT_CHARS: number;
165
188
  declare const CHUNK_WINDOW_CHARS: number;
166
189
  declare const CHUNK_OVERLAP_CHARS: number;
190
+ declare function sanitizeText(text: string): string;
167
191
  declare function chunkText(text: string): string[];
168
192
 
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 };
193
+ export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassificationMetadata, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_CHUNK_CONCURRENCY, DEFAULT_HOOKS, 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 MalformedInputDetails, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks, sanitizeText };