@silmaril-security/sdk 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +32 -27
- package/dist/adapters/langchain.cjs +1 -1
- package/dist/adapters/langchain.cjs.map +1 -1
- package/dist/adapters/langchain.d.cts +1 -1
- package/dist/adapters/langchain.d.ts +1 -1
- package/dist/adapters/langchain.js +1 -1
- package/dist/adapters/langchain.js.map +1 -1
- package/dist/adapters/vercel.cjs +1 -1
- package/dist/adapters/vercel.cjs.map +1 -1
- package/dist/adapters/vercel.d.cts +1 -1
- package/dist/adapters/vercel.d.ts +1 -1
- package/dist/adapters/vercel.js +1 -1
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/{firewall-DfZCCDU6.d.cts → firewall-D2doJsD5.d.cts} +16 -7
- package/dist/{firewall-DfZCCDU6.d.ts → firewall-D2doJsD5.d.ts} +16 -7
- package/dist/index.cjs +73 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -12
- package/dist/index.d.ts +3 -12
- package/dist/index.js +72 -151
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Silmaril Firewall TypeScript SDK are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.5.1 - 2026-07-29
|
|
6
|
+
|
|
7
|
+
- Add typed support for `code_generation`, `story_script_generation`,
|
|
8
|
+
`game_generation`, `website_generation`, `clickup_terms_violation`, and
|
|
9
|
+
`traditional_ai_abuse`.
|
|
10
|
+
- Extend ordered outcome collections, descriptions, guards, normalization
|
|
11
|
+
fixtures, exports, and documentation without changing the `/classify` wire
|
|
12
|
+
shape.
|
|
13
|
+
|
|
14
|
+
## 0.5.0 - 2026-07-15
|
|
15
|
+
|
|
16
|
+
- Send every `classify()` input as one complete event without client chunking.
|
|
17
|
+
- Preserve exact `metadata.conversationId` and emit one
|
|
18
|
+
`metadata.silmaril.request_id` per event.
|
|
19
|
+
- Require the backend `prediction` field for enforcement while preserving
|
|
20
|
+
optional outcome scores.
|
|
21
|
+
- Remove the public `sanitizeText`, `chunkText`, `MAX_INPUT_TOKENS`,
|
|
22
|
+
`CHUNK_WINDOW`, `CHUNK_OVERLAP`, `MAX_INPUT_CHARS`, `CHUNK_WINDOW_CHARS`,
|
|
23
|
+
`CHUNK_OVERLAP_CHARS`, and `DEFAULT_CHUNK_CONCURRENCY` exports, plus the
|
|
24
|
+
`FirewallOptions.chunkConcurrency` option.
|
|
25
|
+
|
|
5
26
|
## 0.4.2 - 2026-06-02
|
|
6
27
|
|
|
7
28
|
- Add typed firewall outcome constants, ordered outcome lists, descriptions,
|
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ This SDK provides the low-level TypeScript interface for that workflow:
|
|
|
20
20
|
- Preserve hook and tool-name context for more accurate decisions.
|
|
21
21
|
- Enforce backend-owned adaptive thresholds in adapters, with shadow mode for
|
|
22
22
|
observation-only rollout.
|
|
23
|
-
-
|
|
24
|
-
-
|
|
23
|
+
- Send each complete sanitized event in one request.
|
|
24
|
+
- Preserve exact `metadata.conversationId` sequence identity and add one event ID.
|
|
25
25
|
- Retry API rate-limit responses.
|
|
26
26
|
- Optionally attach the firewall to Vercel AI SDK middleware and LangChain.js
|
|
27
27
|
callback flows.
|
|
@@ -37,7 +37,7 @@ npm install @silmaril-security/sdk
|
|
|
37
37
|
For reproducible installs, pin a tagged release:
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
|
-
npm install @silmaril-security/sdk@0.
|
|
40
|
+
npm install @silmaril-security/sdk@0.5.1
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Requires Node 18 or later.
|
|
@@ -129,7 +129,7 @@ const result = await fw.classify(userInput, {
|
|
|
129
129
|
hook: HookLabel.USER_INPUT,
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
if (result.
|
|
132
|
+
if (result.prediction === "BENIGN") {
|
|
133
133
|
continueNormally();
|
|
134
134
|
} else {
|
|
135
135
|
switch (result.primaryOutcome) {
|
|
@@ -148,6 +148,14 @@ if (result.score < result.threshold) {
|
|
|
148
148
|
case Outcome.ServiceDisruption:
|
|
149
149
|
blockDisruptiveAction(result);
|
|
150
150
|
break;
|
|
151
|
+
case Outcome.CodeGeneration:
|
|
152
|
+
case Outcome.StoryScriptGeneration:
|
|
153
|
+
case Outcome.GameGeneration:
|
|
154
|
+
case Outcome.WebsiteGeneration:
|
|
155
|
+
case Outcome.ClickUpTermsViolation:
|
|
156
|
+
case Outcome.TraditionalAiAbuse:
|
|
157
|
+
applyTenantPolicy(result);
|
|
158
|
+
break;
|
|
151
159
|
default:
|
|
152
160
|
blockByDefault(result);
|
|
153
161
|
}
|
|
@@ -162,6 +170,12 @@ Outcome taxonomy:
|
|
|
162
170
|
- `control_abuse`: misuse of authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.
|
|
163
171
|
- `system_compromise`: privilege escalation, account takeover, hostile integration/plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.
|
|
164
172
|
- `service_disruption`: downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.
|
|
173
|
+
- `code_generation`: generation or material modification of executable code, scripts, workflows, or configuration.
|
|
174
|
+
- `story_script_generation`: generation of narrative prose, dialogue, scripts, or story artifacts.
|
|
175
|
+
- `game_generation`: generation of a game, quest, level, mechanic, or playable experience.
|
|
176
|
+
- `website_generation`: generation of a website, landing page, storefront, or web experience.
|
|
177
|
+
- `clickup_terms_violation`: content or actions that violate the configured ClickUp tenant policy.
|
|
178
|
+
- `traditional_ai_abuse`: unsafe AI assistance outside the concrete security outcome classes.
|
|
165
179
|
|
|
166
180
|
## Options
|
|
167
181
|
|
|
@@ -170,7 +184,6 @@ interface FirewallOptions {
|
|
|
170
184
|
apiKey: string; // required
|
|
171
185
|
apiUrl: string; // required
|
|
172
186
|
timeoutMs?: number; // default: 10000 ms
|
|
173
|
-
chunkConcurrency?: number; // default: 8
|
|
174
187
|
shadowMode?: boolean; // adapter observation mode
|
|
175
188
|
}
|
|
176
189
|
```
|
|
@@ -187,16 +200,15 @@ the adaptive threshold schedule. The default backend config is
|
|
|
187
200
|
opportunity uses `0.5`, 2 use about `0.6661`, 5 use about `0.8328`, and 10 or
|
|
188
201
|
more are capped at `0.9`.
|
|
189
202
|
|
|
190
|
-
The SDK
|
|
191
|
-
|
|
192
|
-
and chunk count to decide the threshold. The applied value remains available on
|
|
203
|
+
The SDK does not send `threshold` in request payloads. The backend owns the
|
|
204
|
+
applied threshold, which remains available on
|
|
193
205
|
`BlockResult.threshold` and `FirewallBlockedException.threshold` as diagnostic
|
|
194
206
|
metadata.
|
|
195
207
|
|
|
196
208
|
## Shadow Mode
|
|
197
209
|
|
|
198
|
-
The Vercel AI SDK and LangChain.js adapters enforce
|
|
199
|
-
Shadow mode keeps the same classification
|
|
210
|
+
The Vercel AI SDK and LangChain.js adapters enforce backend predictions by default.
|
|
211
|
+
Shadow mode keeps the same classification result but suppresses
|
|
200
212
|
`FirewallBlockedException`, so live traffic can continue while telemetry records
|
|
201
213
|
what would have blocked:
|
|
202
214
|
|
|
@@ -243,8 +255,8 @@ await fw.asLangChainHandler({
|
|
|
243
255
|
```
|
|
244
256
|
|
|
245
257
|
`ClassifyEvent` includes `hook`, `toolName`, `toolCallId`, `runId`, `text`,
|
|
246
|
-
`result`, `blocked`, and `shadowMode`. `blocked` is
|
|
247
|
-
`
|
|
258
|
+
`result`, `blocked`, and `shadowMode`. `blocked` is true only when the backend
|
|
259
|
+
returns `prediction: "MALICIOUS"`. Direct `classify()` and `classifyBatch()`
|
|
248
260
|
calls never throw on verdicts and are unaffected by shadow mode.
|
|
249
261
|
|
|
250
262
|
## Hook Labels
|
|
@@ -283,10 +295,9 @@ await fw.classify(text, {
|
|
|
283
295
|
|
|
284
296
|
The SDK preserves caller metadata and adds a reserved `metadata.silmaril`
|
|
285
297
|
namespace to every request. SDK-controlled fields are `sdk_language`,
|
|
286
|
-
`sdk_version`, `request_id
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
reuse a single request id across all chunks. If callers provide
|
|
298
|
+
`sdk_version`, and `request_id`; batches additionally carry `input_index` for
|
|
299
|
+
diagnostics and remain stateless. Exact `metadata.conversationId` is preserved
|
|
300
|
+
as the backend sequence identity. No aliases are inspected. If callers provide
|
|
290
301
|
`metadata.silmaril`, it must be an object and SDK-reserved keys are overwritten
|
|
291
302
|
by the SDK.
|
|
292
303
|
|
|
@@ -299,18 +310,12 @@ by the SDK.
|
|
|
299
310
|
|
|
300
311
|
All SDK exception types extend `Error` and work with `instanceof`.
|
|
301
312
|
|
|
302
|
-
##
|
|
303
|
-
|
|
304
|
-
Long inputs are chunked client-side into 400-token overlapping windows
|
|
305
|
-
(64-token overlap). The maximum input is 81,920 tokens. For `classify()`, chunks
|
|
306
|
-
are sent as bounded parallel single-text requests using `chunkConcurrency`
|
|
307
|
-
(default: 8), letting API Gateway and SageMaker distribute work across serving
|
|
308
|
-
instances. The highest score is returned.
|
|
309
|
-
|
|
310
|
-
Set `chunkConcurrency: 1` to send chunk requests sequentially. `classifyBatch()`
|
|
311
|
-
continues to send independent texts as one batch request.
|
|
313
|
+
## Complete events
|
|
312
314
|
|
|
313
|
-
`
|
|
315
|
+
`classify()` sanitizes invalid Unicode surrogate fragments and sends the full
|
|
316
|
+
logical event once. The backend owns token-window processing and sequence
|
|
317
|
+
ordering. `classifyBatch()` continues to send independent stateless texts as one
|
|
318
|
+
batch request.
|
|
314
319
|
|
|
315
320
|
## Batch Classification
|
|
316
321
|
|
|
@@ -226,7 +226,7 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
228
228
|
const threshold = result.threshold;
|
|
229
|
-
const blocked = result.
|
|
229
|
+
const blocked = result.prediction === "MALICIOUS";
|
|
230
230
|
const commonEventFields = {
|
|
231
231
|
hook: hookLabel,
|
|
232
232
|
...toolName !== void 0 ? { toolName } : {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/langchain.ts","../../src/exceptions.ts","../../src/hooks.ts","../../src/utils/extract.ts"],"sourcesContent":["// 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 { FirewallBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { BlockResult, ClassifyEvent, 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 = (event: ClassifyEvent): void => {\n if (!onClassify) {\n return;\n }\n try {\n onClassify(event);\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 const commonEventFields = {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n runId,\n text,\n result,\n };\n fireOnClassify({\n ...commonEventFields,\n blocked,\n shadowMode,\n });\n if (!blocked || shadowMode) {\n return;\n }\n\n throw new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n result,\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 { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): 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 `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/langchain.ts","../../src/exceptions.ts","../../src/hooks.ts","../../src/utils/extract.ts"],"sourcesContent":["// 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 { FirewallBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { BlockResult, ClassifyEvent, 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 = (event: ClassifyEvent): void => {\n if (!onClassify) {\n return;\n }\n try {\n onClassify(event);\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.prediction === \"MALICIOUS\";\n const commonEventFields = {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n runId,\n text,\n result,\n };\n fireOnClassify({\n ...commonEventFields,\n blocked,\n shadowMode,\n });\n if (!blocked || shadowMode) {\n return;\n }\n\n throw new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n result,\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 { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): 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 `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\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"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
2
|
-
import {
|
|
2
|
+
import { e as Firewall, L as LangChainAdapterOptions } from '../firewall-D2doJsD5.cjs';
|
|
3
3
|
|
|
4
4
|
declare function createLangChainHandler(firewall: Firewall, options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
2
|
-
import {
|
|
2
|
+
import { e as Firewall, L as LangChainAdapterOptions } from '../firewall-D2doJsD5.js';
|
|
3
3
|
|
|
4
4
|
declare function createLangChainHandler(firewall: Firewall, options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
|
|
5
5
|
|
|
@@ -190,7 +190,7 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
192
|
const threshold = result.threshold;
|
|
193
|
-
const blocked = result.
|
|
193
|
+
const blocked = result.prediction === "MALICIOUS";
|
|
194
194
|
const commonEventFields = {
|
|
195
195
|
hook: hookLabel,
|
|
196
196
|
...toolName !== void 0 ? { toolName } : {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exceptions.ts","../../src/hooks.ts","../../src/utils/extract.ts","../../src/adapters/langchain.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): 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 `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\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 { FirewallBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { BlockResult, ClassifyEvent, 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 = (event: ClassifyEvent): void => {\n if (!onClassify) {\n return;\n }\n try {\n onClassify(event);\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 const commonEventFields = {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n runId,\n text,\n result,\n };\n fireOnClassify({\n ...commonEventFields,\n blocked,\n shadowMode,\n });\n if (!blocked || shadowMode) {\n return;\n }\n\n throw new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n result,\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"],"mappings":";AAMA,IAAM,yBAAyB;AAWxB,IAAM,2BAAN,MAAM,kCAAiC,MAAM;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAST;AACD,UAAM,0BAAyB,cAAc,MAAM,CAAC;AACpD,SAAK,OAAO;AACZ,SAAK,QAAQ,OAAO;AACpB,SAAK,YAAY,OAAO;AACxB,SAAK,aAAa,OAAO;AACzB,SAAK,QAAQ,OAAO;AACpB,SAAK,OAAO,OAAO;AACnB,SAAK,WAAW,OAAO;AACvB,SAAK,aAAa,OAAO;AACzB,SAAK,SAAS,OAAO;AACrB,WAAO,eAAe,MAAM,0BAAyB,SAAS;AAAA,EAChE;AAAA,EAEA,OAAe,cAAc,QAIlB;AACT,UAAM,YACJ,OAAO,WAAW,SAAS,yBACvB,GAAG,OAAO,WAAW,MAAM,GAAG,sBAAsB,CAAC,QACrD,OAAO;AACb,WACE,+CACU,OAAO,MAAM,QAAQ,CAAC,CAAC,eAAe,OAAO,UAAU,QAAQ,CAAC,CAAC,OACvE,SAAS;AAAA,EAEjB;AACF;;;AC9DO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AACX;AAIO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AAAA,EACV,eAAe;AACjB;AAIO,IAAM,gBAA2C,oBAAI,IAAkB;AAAA,EAC5E,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,cAAyC,oBAAI,IAAkB;AAAA,EAC1E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,eAA0C,oBAAI,IAAkB;AAAA,EAC3E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,YAAuC,oBAAI,IAAkB;AAAA,EACxE,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAEM,IAAM,yBAAoE;AAAA,EAC/E,CAAC,aAAa,gBAAgB,GAAG,UAAU;AAAA,EAC3C,CAAC,aAAa,SAAS,GAAG,UAAU;AAAA,EACpC,CAAC,aAAa,UAAU,GAAG,UAAU;AAAA,EACrC,CAAC,aAAa,QAAQ,GAAG,UAAU;AAAA,EACnC,CAAC,aAAa,eAAe,GAAG,UAAU;AAAA,EAC1C,CAAC,aAAa,aAAa,GAAG,UAAU;AAAA,EACxC,CAAC,aAAa,OAAO,GAAG,UAAU;AACpC;AAEA,IAAM,uBAA4C,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;AAE9E,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;;;ACOO,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;;;AC9FA,IAAM,aAAkC,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAQjE,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,SAAS,oBACP,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,GACN;AAC9B,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,aAAa,QAAQ,cAAc,SAAS;AAClD,QAAM,aAAa,QAAQ;AAE3B,QAAM,iBAAiB,CAAC,UAA+B;AACrD,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AACA,QAAI;AACF,iBAAW,KAAK;AAAA,IAClB,SAAS,KAAK;AACZ,aAAO,6BAA6B,GAAG;AAAA,IACzC;AAAA,EACF;AAEA,QAAM,WAAW,OACf,MACA,WACA,OACA,aACkB;AAClB,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,SAAS,SAAS,MAAM;AAAA,QACrC,MAAM;AAAA,QACN,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/C,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,CAAC,UAAU;AACb,cAAM;AAAA,MACR;AACA,aAAO,kDAAkD,GAAG;AAC5D;AAAA,IACF;AACA,UAAM,YAAY,OAAO;AACzB,UAAM,UAAU,OAAO,SAAS;AAChC,UAAM,oBAAoB;AAAA,MACxB,MAAM;AAAA,MACN,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,mBAAe;AAAA,MACb,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,CAAC,WAAW,YAAY;AAC1B;AAAA,IACF;AAEA,UAAM,IAAI,yBAAyB;AAAA,MACjC,OAAO,OAAO;AAAA,MACd;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA,MAAM;AAAA,MACN,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C;AAAA,IACF,CAAC;AAAA,EACH;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,WAAW,oBAAoB,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,SAAS,MAAM,uBAAuB,aAAa,UAAU,GAAG,OAAO,QAAQ;AAAA,IACvF;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,SAAS,MAAM,uBAAuB,aAAa,QAAQ,GAAG,OAAO,QAAQ;AAAA,IACrF;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;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/exceptions.ts","../../src/hooks.ts","../../src/utils/extract.ts","../../src/adapters/langchain.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): 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 `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\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 { FirewallBlockedException } from \"../exceptions.js\";\nimport {\n FIREWALL_HOOK_TO_LABEL,\n FirewallHook,\n HookLabel,\n resolveHooks,\n} from \"../hooks.js\";\nimport type { BlockResult, ClassifyEvent, 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 = (event: ClassifyEvent): void => {\n if (!onClassify) {\n return;\n }\n try {\n onClassify(event);\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.prediction === \"MALICIOUS\";\n const commonEventFields = {\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n runId,\n text,\n result,\n };\n fireOnClassify({\n ...commonEventFields,\n blocked,\n shadowMode,\n });\n if (!blocked || shadowMode) {\n return;\n }\n\n throw new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n runId,\n hook: hookLabel,\n ...(toolName !== undefined ? { toolName } : {}),\n result,\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"],"mappings":"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","names":[]}
|
package/dist/adapters/vercel.cjs
CHANGED
|
@@ -189,7 +189,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
189
189
|
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
190
190
|
);
|
|
191
191
|
const threshold = result.threshold;
|
|
192
|
-
const blocked = result.
|
|
192
|
+
const blocked = result.prediction === "MALICIOUS";
|
|
193
193
|
const commonEventFields = {
|
|
194
194
|
hook,
|
|
195
195
|
...toolName !== void 0 ? { toolName } : {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/vercel.ts","../../src/exceptions.ts","../../src/hooks.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { FirewallBlockedException } 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 output?: 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 input?: 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\ninterface VercelStepContext {\n toolName: string | undefined;\n toolCallId: string | undefined;\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\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\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; toolCallId: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined; toolCallId: 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 = stringifyToolResult(part.result !== undefined ? part.result : part.output);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });\n }\n }\n }\n return out;\n}\n\nfunction stringifyToolResult(value: unknown): string {\n if (!isRecord(value)) {\n return stringifyToolValue(value);\n }\n if ((value.type === \"text\" || value.type === \"error-text\") && typeof value.value === \"string\") {\n return value.value;\n }\n if ((value.type === \"json\" || value.type === \"error-json\") && value.value !== undefined) {\n return stringifyToolValue(value.value);\n }\n if (value.type === \"content\" && Array.isArray(value.value)) {\n return value.value\n .map((part) => (isRecord(part) && part.type === \"text\" && typeof part.text === \"string\" ? part.text : \"\"))\n .filter((text) => text.length > 0)\n .join(\" \");\n }\n return stringifyToolValue(value);\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 readonly specificationVersion: \"v3\";\n readonly middlewareVersion: \"v2\";\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 context: VercelStepContext = { toolName: undefined, toolCallId: undefined },\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const { toolName, toolCallId } = context;\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 const commonEventFields = {\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n text,\n result,\n };\n options.onClassify?.({\n ...commonEventFields,\n blocked,\n shadowMode,\n });\n if (!blocked || shadowMode) {\n return;\n }\n\n const err = new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n result,\n });\n options.onBlocked?.(err);\n throw err;\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, toolCallId } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });\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 toolInput = call.args !== undefined ? call.args : call.input;\n const args =\n typeof toolInput === \"string\" ? toolInput : stringifyToolValue(toolInput);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n const toolCallId = typeof call.toolCallId === \"string\" ? call.toolCallId : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });\n }\n }\n }\n };\n\n return {\n specificationVersion: \"v3\",\n middlewareVersion: \"v2\",\n\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 FirewallBlockedException) {\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\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): 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 `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMA,IAAM,yBAAyB;AAWxB,IAAM,2BAAN,MAAM,kCAAiC,MAAM;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAST;AACD,UAAM,0BAAyB,cAAc,MAAM,CAAC;AACpD,SAAK,OAAO;AACZ,SAAK,QAAQ,OAAO;AACpB,SAAK,YAAY,OAAO;AACxB,SAAK,aAAa,OAAO;AACzB,SAAK,QAAQ,OAAO;AACpB,SAAK,OAAO,OAAO;AACnB,SAAK,WAAW,OAAO;AACvB,SAAK,aAAa,OAAO;AACzB,SAAK,SAAS,OAAO;AACrB,WAAO,eAAe,MAAM,0BAAyB,SAAS;AAAA,EAChE;AAAA,EAEA,OAAe,cAAc,QAIlB;AACT,UAAM,YACJ,OAAO,WAAW,SAAS,yBACvB,GAAG,OAAO,WAAW,MAAM,GAAG,sBAAsB,CAAC,QACrD,OAAO;AACb,WACE,+CACU,OAAO,MAAM,QAAQ,CAAC,CAAC,eAAe,OAAO,UAAU,QAAQ,CAAC,CAAC,OACvE,SAAS;AAAA,EAEjB;AACF;;;AC9DO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AACX;AAIO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AAAA,EACV,eAAe;AACjB;AAIO,IAAM,gBAA2C,oBAAI,IAAkB;AAAA,EAC5E,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,cAAyC,oBAAI,IAAkB;AAAA,EAC1E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,eAA0C,oBAAI,IAAkB;AAAA,EAC3E,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf,CAAC;AAEM,IAAM,YAAuC,oBAAI,IAAkB;AAAA,EACxE,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAEM,IAAM,yBAAoE;AAAA,EAC/E,CAAC,aAAa,gBAAgB,GAAG,UAAU;AAAA,EAC3C,CAAC,aAAa,SAAS,GAAG,UAAU;AAAA,EACpC,CAAC,aAAa,UAAU,GAAG,UAAU;AAAA,EACrC,CAAC,aAAa,QAAQ,GAAG,UAAU;AAAA,EACnC,CAAC,aAAa,eAAe,GAAG,UAAU;AAAA,EAC1C,CAAC,aAAa,aAAa,GAAG,UAAU;AAAA,EACxC,CAAC,aAAa,OAAO,GAAG,UAAU;AACpC;AAEA,IAAM,uBAA4C,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;;;AFArF,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;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,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,SACuF;AACvF,MAAI,OAAO,QAAQ,YAAY,UAAU;AACvC,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAA6F,CAAC;AACpG,aAAW,QAAQ,QAAQ,SAAS;AAClC,QAAI,OAAO,SAAS,UAAU;AAC5B;AAAA,IACF;AACA,QAAI,KAAK,SAAS,eAAe;AAC/B,YAAM,OAAO,oBAAoB,KAAK,WAAW,SAAY,KAAK,SAAS,KAAK,MAAM;AACtF,UAAI,KAAK,KAAK,GAAG;AACf,YAAI,KAAK,EAAE,MAAM,UAAU,KAAK,UAAU,YAAY,KAAK,WAAW,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAwB;AACnD,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO,mBAAmB,KAAK;AAAA,EACjC;AACA,OAAK,MAAM,SAAS,UAAU,MAAM,SAAS,iBAAiB,OAAO,MAAM,UAAU,UAAU;AAC7F,WAAO,MAAM;AAAA,EACf;AACA,OAAK,MAAM,SAAS,UAAU,MAAM,SAAS,iBAAiB,MAAM,UAAU,QAAW;AACvF,WAAO,mBAAmB,MAAM,KAAK;AAAA,EACvC;AACA,MAAI,MAAM,SAAS,aAAa,MAAM,QAAQ,MAAM,KAAK,GAAG;AAC1D,WAAO,MAAM,MACV,IAAI,CAAC,SAAU,SAAS,IAAI,KAAK,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,EAAG,EACxG,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,KAAK,GAAG;AAAA,EACb;AACA,SAAO,mBAAmB,KAAK;AACjC;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;AAaO,SAAS,iBACd,UACA,UAA6B,CAAC,GACV;AACpB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,aAAa,QAAQ,cAAc,SAAS;AAElD,QAAM,kBAAkB,OACtB,MACA,MACA,UAA6B,EAAE,UAAU,QAAW,YAAY,OAAU,MACxD;AAClB,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB;AAAA,IACF;AACA,UAAM,EAAE,UAAU,WAAW,IAAI;AACjC,UAAM,SAAsB,MAAM,SAAS;AAAA,MACzC;AAAA,MACA,aAAa,SAAY,EAAE,MAAM,SAAS,IAAI,EAAE,KAAK;AAAA,IACvD;AACA,UAAM,YAAY,OAAO;AACzB,UAAM,UAAU,OAAO,SAAS;AAChC,UAAM,oBAAoB;AAAA,MACxB;AAAA,MACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD;AAAA,MACA;AAAA,IACF;AACA,YAAQ,aAAa;AAAA,MACnB,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,CAAC,WAAW,YAAY;AAC1B;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,yBAAyB;AAAA,MACvC,OAAO,OAAO;AAAA,MACd;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD;AAAA,IACF,CAAC;AACD,YAAQ,YAAY,GAAG;AACvB,UAAM;AAAA,EACR;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,MAAAA,OAAM,UAAU,WAAW,KAAK,uBAAuB,IAAI,GAAG;AACzE,cAAM,gBAAgBA,OAAM,UAAU,eAAe,EAAE,UAAU,WAAW,CAAC;AAAA,MAC/E;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,YAAY,KAAK,SAAS,SAAY,KAAK,OAAO,KAAK;AAC7D,cAAM,OACJ,OAAO,cAAc,WAAW,YAAY,mBAAmB,SAAS;AAC1E,cAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,cAAM,aAAa,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAC3E,YAAI,KAAK,KAAK,GAAG;AACf,gBAAM,gBAAgB,MAAM,UAAU,WAAW,EAAE,UAAU,WAAW,CAAC;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IAEnB,MAAM,aAA6E;AAAA,MACjF;AAAA,MACA;AAAA,IACF,GAAgD;AAC9C,UAAI,WAAW;AACb,cAAM,WAAW,OAAO,UAAU,CAAC,CAAC;AAAA,MACtC;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,UAAU,CAAC,CAAC;AAAA,MACtC;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,0BAA0B;AAC3C,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;","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/vercel.ts","../../src/exceptions.ts","../../src/hooks.ts"],"sourcesContent":["// Copyright (c) 2024-2025 Silmaril Security Inc. All rights reserved.\n// PROPRIETARY AND CONFIDENTIAL\n\nimport type { Firewall } from \"../firewall.js\";\nimport { FirewallBlockedException } 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 output?: 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 input?: 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\ninterface VercelStepContext {\n toolName: string | undefined;\n toolCallId: string | undefined;\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\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\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; toolCallId: string | undefined }> {\n if (typeof message.content === \"string\") {\n return [];\n }\n const out: Array<{ text: string; toolName: string | undefined; toolCallId: 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 = stringifyToolResult(part.result !== undefined ? part.result : part.output);\n if (text.trim()) {\n out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });\n }\n }\n }\n return out;\n}\n\nfunction stringifyToolResult(value: unknown): string {\n if (!isRecord(value)) {\n return stringifyToolValue(value);\n }\n if ((value.type === \"text\" || value.type === \"error-text\") && typeof value.value === \"string\") {\n return value.value;\n }\n if ((value.type === \"json\" || value.type === \"error-json\") && value.value !== undefined) {\n return stringifyToolValue(value.value);\n }\n if (value.type === \"content\" && Array.isArray(value.value)) {\n return value.value\n .map((part) => (isRecord(part) && part.type === \"text\" && typeof part.text === \"string\" ? part.text : \"\"))\n .filter((text) => text.length > 0)\n .join(\" \");\n }\n return stringifyToolValue(value);\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 readonly specificationVersion: \"v3\";\n readonly middlewareVersion: \"v2\";\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 context: VercelStepContext = { toolName: undefined, toolCallId: undefined },\n ): Promise<void> => {\n if (!text.trim()) {\n return;\n }\n const { toolName, toolCallId } = context;\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.prediction === \"MALICIOUS\";\n const commonEventFields = {\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n text,\n result,\n };\n options.onClassify?.({\n ...commonEventFields,\n blocked,\n shadowMode,\n });\n if (!blocked || shadowMode) {\n return;\n }\n\n const err = new FirewallBlockedException({\n score: result.score,\n threshold,\n promptText: text,\n hook,\n ...(toolName !== undefined ? { toolName } : {}),\n ...(toolCallId !== undefined ? { toolCallId } : {}),\n result,\n });\n options.onBlocked?.(err);\n throw err;\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, toolCallId } of iterateToolResultParts(last)) {\n await classifyOrBlock(text, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });\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 toolInput = call.args !== undefined ? call.args : call.input;\n const args =\n typeof toolInput === \"string\" ? toolInput : stringifyToolValue(toolInput);\n const toolName = typeof call.toolName === \"string\" ? call.toolName : undefined;\n const toolCallId = typeof call.toolCallId === \"string\" ? call.toolCallId : undefined;\n if (args.trim()) {\n await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });\n }\n }\n }\n };\n\n return {\n specificationVersion: \"v3\",\n middlewareVersion: \"v2\",\n\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 FirewallBlockedException) {\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\nimport type { HookLabel } from \"./hooks.js\";\nimport type { BlockResult } from \"./types.js\";\n\nconst MAX_PROMPT_DISPLAY_LEN = 100;\n\nexport interface MalformedInputDetails {\n field?: string;\n inputIndex?: number;\n charOffset?: number;\n malformedToken?: string;\n codePoint?: string;\n reason?: string;\n}\n\nexport class FirewallBlockedException extends Error {\n readonly score: number;\n readonly threshold: number;\n readonly promptText: string;\n readonly runId: string | undefined;\n readonly hook: HookLabel | undefined;\n readonly toolName: string | undefined;\n readonly toolCallId: string | undefined;\n readonly result: BlockResult | undefined;\n\n constructor(params: {\n score: number;\n threshold: number;\n promptText: string;\n runId?: string;\n hook?: HookLabel;\n toolName?: string;\n toolCallId?: string;\n result?: BlockResult;\n }) {\n super(FirewallBlockedException.formatMessage(params));\n this.name = \"FirewallBlockedException\";\n this.score = params.score;\n this.threshold = params.threshold;\n this.promptText = params.promptText;\n this.runId = params.runId;\n this.hook = params.hook;\n this.toolName = params.toolName;\n this.toolCallId = params.toolCallId;\n this.result = params.result;\n Object.setPrototypeOf(this, FirewallBlockedException.prototype);\n }\n\n private static formatMessage(params: {\n score: number;\n threshold: number;\n promptText: string;\n }): 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 `Request blocked by Silmaril Firewall ` +\n `(score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): ` +\n `'${truncated}'`\n );\n }\n}\n\n/** @deprecated Use FirewallBlockedException instead. */\nexport const PromptBlockedException = FirewallBlockedException;\n/** @deprecated Use FirewallBlockedException instead. */\nexport type PromptBlockedException = FirewallBlockedException;\n\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"],"mappings":"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","names":["text"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { g as FirewallMiddleware, y as createMiddleware } from '../firewall-D2doJsD5.cjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { g as FirewallMiddleware, y as createMiddleware } from '../firewall-D2doJsD5.js';
|
package/dist/adapters/vercel.js
CHANGED
|
@@ -163,7 +163,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
163
163
|
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
164
164
|
);
|
|
165
165
|
const threshold = result.threshold;
|
|
166
|
-
const blocked = result.
|
|
166
|
+
const blocked = result.prediction === "MALICIOUS";
|
|
167
167
|
const commonEventFields = {
|
|
168
168
|
hook,
|
|
169
169
|
...toolName !== void 0 ? { toolName } : {},
|