@silmaril-security/sdk 0.3.1 → 0.4.2
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 +30 -0
- package/README.md +131 -32
- package/dist/adapters/langchain.cjs +349 -0
- package/dist/adapters/langchain.cjs.map +1 -0
- package/dist/adapters/langchain.d.cts +6 -0
- package/dist/adapters/langchain.d.ts +6 -0
- package/dist/adapters/langchain.js +312 -0
- package/dist/adapters/langchain.js.map +1 -0
- package/dist/adapters/vercel.cjs +321 -0
- package/dist/adapters/vercel.cjs.map +1 -0
- package/dist/adapters/vercel.d.cts +1 -0
- package/dist/adapters/vercel.d.ts +1 -0
- package/dist/adapters/vercel.js +294 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/firewall-DfZCCDU6.d.cts +197 -0
- package/dist/firewall-DfZCCDU6.d.ts +197 -0
- package/dist/index.cjs +257 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -146
- package/dist/index.d.ts +16 -146
- package/dist/index.js +244 -89
- package/dist/index.js.map +1 -1
- package/package.json +38 -10
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/vercel.ts
|
|
21
|
+
var vercel_exports = {};
|
|
22
|
+
__export(vercel_exports, {
|
|
23
|
+
createMiddleware: () => createMiddleware
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(vercel_exports);
|
|
26
|
+
|
|
27
|
+
// src/exceptions.ts
|
|
28
|
+
var MAX_PROMPT_DISPLAY_LEN = 100;
|
|
29
|
+
var FirewallBlockedException = class _FirewallBlockedException extends Error {
|
|
30
|
+
score;
|
|
31
|
+
threshold;
|
|
32
|
+
promptText;
|
|
33
|
+
runId;
|
|
34
|
+
hook;
|
|
35
|
+
toolName;
|
|
36
|
+
toolCallId;
|
|
37
|
+
result;
|
|
38
|
+
constructor(params) {
|
|
39
|
+
super(_FirewallBlockedException.formatMessage(params));
|
|
40
|
+
this.name = "FirewallBlockedException";
|
|
41
|
+
this.score = params.score;
|
|
42
|
+
this.threshold = params.threshold;
|
|
43
|
+
this.promptText = params.promptText;
|
|
44
|
+
this.runId = params.runId;
|
|
45
|
+
this.hook = params.hook;
|
|
46
|
+
this.toolName = params.toolName;
|
|
47
|
+
this.toolCallId = params.toolCallId;
|
|
48
|
+
this.result = params.result;
|
|
49
|
+
Object.setPrototypeOf(this, _FirewallBlockedException.prototype);
|
|
50
|
+
}
|
|
51
|
+
static formatMessage(params) {
|
|
52
|
+
const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
|
|
53
|
+
return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/hooks.ts
|
|
58
|
+
var HookLabel = {
|
|
59
|
+
USER_INPUT: "user_input",
|
|
60
|
+
SYSTEM_PROMPT: "system_prompt",
|
|
61
|
+
TOOL_CALL: "tool_call",
|
|
62
|
+
TOOL_RESPONSE: "tool_response",
|
|
63
|
+
LLM_OUTPUT: "llm_output",
|
|
64
|
+
UNKNOWN: "unknown"
|
|
65
|
+
};
|
|
66
|
+
var FirewallHook = {
|
|
67
|
+
LLM_START: "on_llm_start",
|
|
68
|
+
CHAT_MODEL_START: "on_chat_model_start",
|
|
69
|
+
TOOL_START: "on_tool_start",
|
|
70
|
+
RETRIEVER_START: "on_retriever_start",
|
|
71
|
+
LLM_END: "on_llm_end",
|
|
72
|
+
TOOL_END: "on_tool_end",
|
|
73
|
+
RETRIEVER_END: "on_retriever_end"
|
|
74
|
+
};
|
|
75
|
+
var DEFAULT_HOOKS = /* @__PURE__ */ new Set([
|
|
76
|
+
FirewallHook.LLM_START,
|
|
77
|
+
FirewallHook.CHAT_MODEL_START
|
|
78
|
+
]);
|
|
79
|
+
var INPUT_HOOKS = /* @__PURE__ */ new Set([
|
|
80
|
+
FirewallHook.LLM_START,
|
|
81
|
+
FirewallHook.CHAT_MODEL_START,
|
|
82
|
+
FirewallHook.TOOL_START,
|
|
83
|
+
FirewallHook.RETRIEVER_START
|
|
84
|
+
]);
|
|
85
|
+
var OUTPUT_HOOKS = /* @__PURE__ */ new Set([
|
|
86
|
+
FirewallHook.LLM_END,
|
|
87
|
+
FirewallHook.TOOL_END,
|
|
88
|
+
FirewallHook.RETRIEVER_END
|
|
89
|
+
]);
|
|
90
|
+
var ALL_HOOKS = /* @__PURE__ */ new Set([
|
|
91
|
+
...INPUT_HOOKS,
|
|
92
|
+
...OUTPUT_HOOKS
|
|
93
|
+
]);
|
|
94
|
+
var FIREWALL_HOOK_TO_LABEL = {
|
|
95
|
+
[FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,
|
|
96
|
+
[FirewallHook.LLM_START]: HookLabel.USER_INPUT,
|
|
97
|
+
[FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,
|
|
98
|
+
[FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,
|
|
99
|
+
[FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,
|
|
100
|
+
[FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,
|
|
101
|
+
[FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT
|
|
102
|
+
};
|
|
103
|
+
var FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
|
|
104
|
+
|
|
105
|
+
// src/adapters/vercel.ts
|
|
106
|
+
function stringifyToolValue(value) {
|
|
107
|
+
if (value === null || value === void 0) {
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === "string") {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
return JSON.stringify(value);
|
|
115
|
+
} catch {
|
|
116
|
+
return String(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function isRecord(value) {
|
|
120
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
121
|
+
}
|
|
122
|
+
function extractContentText(content) {
|
|
123
|
+
if (typeof content === "string") {
|
|
124
|
+
return content;
|
|
125
|
+
}
|
|
126
|
+
const parts = [];
|
|
127
|
+
for (const block of content) {
|
|
128
|
+
if (typeof block === "string") {
|
|
129
|
+
parts.push(block);
|
|
130
|
+
} else if (block && block.type === "text") {
|
|
131
|
+
parts.push(block.text ?? "");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return parts.join(" ");
|
|
135
|
+
}
|
|
136
|
+
function iterateToolResultParts(message) {
|
|
137
|
+
if (typeof message.content === "string") {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const part of message.content) {
|
|
142
|
+
if (typeof part === "string") {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (part.type === "tool-result") {
|
|
146
|
+
const text = stringifyToolResult(part.result !== void 0 ? part.result : part.output);
|
|
147
|
+
if (text.trim()) {
|
|
148
|
+
out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function stringifyToolResult(value) {
|
|
155
|
+
if (!isRecord(value)) {
|
|
156
|
+
return stringifyToolValue(value);
|
|
157
|
+
}
|
|
158
|
+
if ((value.type === "text" || value.type === "error-text") && typeof value.value === "string") {
|
|
159
|
+
return value.value;
|
|
160
|
+
}
|
|
161
|
+
if ((value.type === "json" || value.type === "error-json") && value.value !== void 0) {
|
|
162
|
+
return stringifyToolValue(value.value);
|
|
163
|
+
}
|
|
164
|
+
if (value.type === "content" && Array.isArray(value.value)) {
|
|
165
|
+
return value.value.map((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(" ");
|
|
166
|
+
}
|
|
167
|
+
return stringifyToolValue(value);
|
|
168
|
+
}
|
|
169
|
+
function findLastUserMessage(messages) {
|
|
170
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
171
|
+
const msg = messages[i];
|
|
172
|
+
if (msg.role.toLowerCase() === "user") {
|
|
173
|
+
return msg;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
function createMiddleware(firewall, options = {}) {
|
|
179
|
+
const scanInput = options.scanInput ?? true;
|
|
180
|
+
const scanOutput = options.scanOutput ?? false;
|
|
181
|
+
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
182
|
+
const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
|
|
183
|
+
if (!text.trim()) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const { toolName, toolCallId } = context;
|
|
187
|
+
const result = await firewall.classify(
|
|
188
|
+
text,
|
|
189
|
+
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
190
|
+
);
|
|
191
|
+
const threshold = result.threshold;
|
|
192
|
+
const blocked = result.score >= threshold;
|
|
193
|
+
const commonEventFields = {
|
|
194
|
+
hook,
|
|
195
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
196
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
197
|
+
text,
|
|
198
|
+
result
|
|
199
|
+
};
|
|
200
|
+
options.onClassify?.({
|
|
201
|
+
...commonEventFields,
|
|
202
|
+
blocked,
|
|
203
|
+
shadowMode
|
|
204
|
+
});
|
|
205
|
+
if (!blocked || shadowMode) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const err = new FirewallBlockedException({
|
|
209
|
+
score: result.score,
|
|
210
|
+
threshold,
|
|
211
|
+
promptText: text,
|
|
212
|
+
hook,
|
|
213
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
214
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
215
|
+
result
|
|
216
|
+
});
|
|
217
|
+
options.onBlocked?.(err);
|
|
218
|
+
throw err;
|
|
219
|
+
};
|
|
220
|
+
const scanPrompt = async (prompt) => {
|
|
221
|
+
if (prompt.length === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const last = prompt[prompt.length - 1];
|
|
225
|
+
const role = last.role.toLowerCase();
|
|
226
|
+
if (role === "tool") {
|
|
227
|
+
for (const { text: text2, toolName, toolCallId } of iterateToolResultParts(last)) {
|
|
228
|
+
await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const lastUser = findLastUserMessage(prompt);
|
|
233
|
+
if (!lastUser) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const text = extractContentText(lastUser.content).trim();
|
|
237
|
+
if (!text) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
await classifyOrBlock(text, HookLabel.USER_INPUT);
|
|
241
|
+
};
|
|
242
|
+
const scanGenerateResult = async (result) => {
|
|
243
|
+
if (scanOutput && typeof result.text === "string" && result.text.length > 0) {
|
|
244
|
+
await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);
|
|
245
|
+
}
|
|
246
|
+
if (options.scanToolCalls && Array.isArray(result.toolCalls)) {
|
|
247
|
+
for (const call of result.toolCalls) {
|
|
248
|
+
if (!call || typeof call !== "object") {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const toolInput = call.args !== void 0 ? call.args : call.input;
|
|
252
|
+
const args = typeof toolInput === "string" ? toolInput : stringifyToolValue(toolInput);
|
|
253
|
+
const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
|
|
254
|
+
const toolCallId = typeof call.toolCallId === "string" ? call.toolCallId : void 0;
|
|
255
|
+
if (args.trim()) {
|
|
256
|
+
await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
specificationVersion: "v3",
|
|
263
|
+
middlewareVersion: "v2",
|
|
264
|
+
async wrapGenerate({
|
|
265
|
+
params,
|
|
266
|
+
doGenerate
|
|
267
|
+
}) {
|
|
268
|
+
if (scanInput) {
|
|
269
|
+
await scanPrompt(params.prompt ?? []);
|
|
270
|
+
}
|
|
271
|
+
const result = await doGenerate();
|
|
272
|
+
await scanGenerateResult(result);
|
|
273
|
+
return result;
|
|
274
|
+
},
|
|
275
|
+
async wrapStream({
|
|
276
|
+
params,
|
|
277
|
+
doStream
|
|
278
|
+
}) {
|
|
279
|
+
if (scanInput) {
|
|
280
|
+
await scanPrompt(params.prompt ?? []);
|
|
281
|
+
}
|
|
282
|
+
const { stream, ...rest } = await doStream();
|
|
283
|
+
if (!scanOutput) {
|
|
284
|
+
return { stream, ...rest };
|
|
285
|
+
}
|
|
286
|
+
let buffered = "";
|
|
287
|
+
const transformed = stream.pipeThrough(
|
|
288
|
+
new TransformStream({
|
|
289
|
+
transform(chunk, controller) {
|
|
290
|
+
const part = chunk;
|
|
291
|
+
if (part && part.type === "text-delta") {
|
|
292
|
+
const delta = part.textDelta ?? part.delta ?? "";
|
|
293
|
+
buffered += delta;
|
|
294
|
+
}
|
|
295
|
+
controller.enqueue(chunk);
|
|
296
|
+
},
|
|
297
|
+
async flush(controller) {
|
|
298
|
+
if (!buffered.trim()) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
if (err instanceof FirewallBlockedException) {
|
|
305
|
+
controller.enqueue({ type: "error", error: err });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
return { stream: transformed, ...rest };
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
318
|
+
0 && (module.exports = {
|
|
319
|
+
createMiddleware
|
|
320
|
+
});
|
|
321
|
+
//# sourceMappingURL=vercel.cjs.map
|
|
@@ -0,0 +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":"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","names":["text"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { h as FirewallMiddleware, z as createMiddleware } from '../firewall-DfZCCDU6.cjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { h as FirewallMiddleware, z as createMiddleware } from '../firewall-DfZCCDU6.js';
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// src/exceptions.ts
|
|
2
|
+
var MAX_PROMPT_DISPLAY_LEN = 100;
|
|
3
|
+
var FirewallBlockedException = class _FirewallBlockedException extends Error {
|
|
4
|
+
score;
|
|
5
|
+
threshold;
|
|
6
|
+
promptText;
|
|
7
|
+
runId;
|
|
8
|
+
hook;
|
|
9
|
+
toolName;
|
|
10
|
+
toolCallId;
|
|
11
|
+
result;
|
|
12
|
+
constructor(params) {
|
|
13
|
+
super(_FirewallBlockedException.formatMessage(params));
|
|
14
|
+
this.name = "FirewallBlockedException";
|
|
15
|
+
this.score = params.score;
|
|
16
|
+
this.threshold = params.threshold;
|
|
17
|
+
this.promptText = params.promptText;
|
|
18
|
+
this.runId = params.runId;
|
|
19
|
+
this.hook = params.hook;
|
|
20
|
+
this.toolName = params.toolName;
|
|
21
|
+
this.toolCallId = params.toolCallId;
|
|
22
|
+
this.result = params.result;
|
|
23
|
+
Object.setPrototypeOf(this, _FirewallBlockedException.prototype);
|
|
24
|
+
}
|
|
25
|
+
static formatMessage(params) {
|
|
26
|
+
const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
|
|
27
|
+
return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/hooks.ts
|
|
32
|
+
var HookLabel = {
|
|
33
|
+
USER_INPUT: "user_input",
|
|
34
|
+
SYSTEM_PROMPT: "system_prompt",
|
|
35
|
+
TOOL_CALL: "tool_call",
|
|
36
|
+
TOOL_RESPONSE: "tool_response",
|
|
37
|
+
LLM_OUTPUT: "llm_output",
|
|
38
|
+
UNKNOWN: "unknown"
|
|
39
|
+
};
|
|
40
|
+
var FirewallHook = {
|
|
41
|
+
LLM_START: "on_llm_start",
|
|
42
|
+
CHAT_MODEL_START: "on_chat_model_start",
|
|
43
|
+
TOOL_START: "on_tool_start",
|
|
44
|
+
RETRIEVER_START: "on_retriever_start",
|
|
45
|
+
LLM_END: "on_llm_end",
|
|
46
|
+
TOOL_END: "on_tool_end",
|
|
47
|
+
RETRIEVER_END: "on_retriever_end"
|
|
48
|
+
};
|
|
49
|
+
var DEFAULT_HOOKS = /* @__PURE__ */ new Set([
|
|
50
|
+
FirewallHook.LLM_START,
|
|
51
|
+
FirewallHook.CHAT_MODEL_START
|
|
52
|
+
]);
|
|
53
|
+
var INPUT_HOOKS = /* @__PURE__ */ new Set([
|
|
54
|
+
FirewallHook.LLM_START,
|
|
55
|
+
FirewallHook.CHAT_MODEL_START,
|
|
56
|
+
FirewallHook.TOOL_START,
|
|
57
|
+
FirewallHook.RETRIEVER_START
|
|
58
|
+
]);
|
|
59
|
+
var OUTPUT_HOOKS = /* @__PURE__ */ new Set([
|
|
60
|
+
FirewallHook.LLM_END,
|
|
61
|
+
FirewallHook.TOOL_END,
|
|
62
|
+
FirewallHook.RETRIEVER_END
|
|
63
|
+
]);
|
|
64
|
+
var ALL_HOOKS = /* @__PURE__ */ new Set([
|
|
65
|
+
...INPUT_HOOKS,
|
|
66
|
+
...OUTPUT_HOOKS
|
|
67
|
+
]);
|
|
68
|
+
var FIREWALL_HOOK_TO_LABEL = {
|
|
69
|
+
[FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,
|
|
70
|
+
[FirewallHook.LLM_START]: HookLabel.USER_INPUT,
|
|
71
|
+
[FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,
|
|
72
|
+
[FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,
|
|
73
|
+
[FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,
|
|
74
|
+
[FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,
|
|
75
|
+
[FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT
|
|
76
|
+
};
|
|
77
|
+
var FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
|
|
78
|
+
|
|
79
|
+
// src/adapters/vercel.ts
|
|
80
|
+
function stringifyToolValue(value) {
|
|
81
|
+
if (value === null || value === void 0) {
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
if (typeof value === "string") {
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
} catch {
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function isRecord(value) {
|
|
94
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
95
|
+
}
|
|
96
|
+
function extractContentText(content) {
|
|
97
|
+
if (typeof content === "string") {
|
|
98
|
+
return content;
|
|
99
|
+
}
|
|
100
|
+
const parts = [];
|
|
101
|
+
for (const block of content) {
|
|
102
|
+
if (typeof block === "string") {
|
|
103
|
+
parts.push(block);
|
|
104
|
+
} else if (block && block.type === "text") {
|
|
105
|
+
parts.push(block.text ?? "");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return parts.join(" ");
|
|
109
|
+
}
|
|
110
|
+
function iterateToolResultParts(message) {
|
|
111
|
+
if (typeof message.content === "string") {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const part of message.content) {
|
|
116
|
+
if (typeof part === "string") {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (part.type === "tool-result") {
|
|
120
|
+
const text = stringifyToolResult(part.result !== void 0 ? part.result : part.output);
|
|
121
|
+
if (text.trim()) {
|
|
122
|
+
out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
function stringifyToolResult(value) {
|
|
129
|
+
if (!isRecord(value)) {
|
|
130
|
+
return stringifyToolValue(value);
|
|
131
|
+
}
|
|
132
|
+
if ((value.type === "text" || value.type === "error-text") && typeof value.value === "string") {
|
|
133
|
+
return value.value;
|
|
134
|
+
}
|
|
135
|
+
if ((value.type === "json" || value.type === "error-json") && value.value !== void 0) {
|
|
136
|
+
return stringifyToolValue(value.value);
|
|
137
|
+
}
|
|
138
|
+
if (value.type === "content" && Array.isArray(value.value)) {
|
|
139
|
+
return value.value.map((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(" ");
|
|
140
|
+
}
|
|
141
|
+
return stringifyToolValue(value);
|
|
142
|
+
}
|
|
143
|
+
function findLastUserMessage(messages) {
|
|
144
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
145
|
+
const msg = messages[i];
|
|
146
|
+
if (msg.role.toLowerCase() === "user") {
|
|
147
|
+
return msg;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
function createMiddleware(firewall, options = {}) {
|
|
153
|
+
const scanInput = options.scanInput ?? true;
|
|
154
|
+
const scanOutput = options.scanOutput ?? false;
|
|
155
|
+
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
156
|
+
const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
|
|
157
|
+
if (!text.trim()) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const { toolName, toolCallId } = context;
|
|
161
|
+
const result = await firewall.classify(
|
|
162
|
+
text,
|
|
163
|
+
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
164
|
+
);
|
|
165
|
+
const threshold = result.threshold;
|
|
166
|
+
const blocked = result.score >= threshold;
|
|
167
|
+
const commonEventFields = {
|
|
168
|
+
hook,
|
|
169
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
170
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
171
|
+
text,
|
|
172
|
+
result
|
|
173
|
+
};
|
|
174
|
+
options.onClassify?.({
|
|
175
|
+
...commonEventFields,
|
|
176
|
+
blocked,
|
|
177
|
+
shadowMode
|
|
178
|
+
});
|
|
179
|
+
if (!blocked || shadowMode) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const err = new FirewallBlockedException({
|
|
183
|
+
score: result.score,
|
|
184
|
+
threshold,
|
|
185
|
+
promptText: text,
|
|
186
|
+
hook,
|
|
187
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
188
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
189
|
+
result
|
|
190
|
+
});
|
|
191
|
+
options.onBlocked?.(err);
|
|
192
|
+
throw err;
|
|
193
|
+
};
|
|
194
|
+
const scanPrompt = async (prompt) => {
|
|
195
|
+
if (prompt.length === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const last = prompt[prompt.length - 1];
|
|
199
|
+
const role = last.role.toLowerCase();
|
|
200
|
+
if (role === "tool") {
|
|
201
|
+
for (const { text: text2, toolName, toolCallId } of iterateToolResultParts(last)) {
|
|
202
|
+
await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const lastUser = findLastUserMessage(prompt);
|
|
207
|
+
if (!lastUser) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const text = extractContentText(lastUser.content).trim();
|
|
211
|
+
if (!text) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await classifyOrBlock(text, HookLabel.USER_INPUT);
|
|
215
|
+
};
|
|
216
|
+
const scanGenerateResult = async (result) => {
|
|
217
|
+
if (scanOutput && typeof result.text === "string" && result.text.length > 0) {
|
|
218
|
+
await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);
|
|
219
|
+
}
|
|
220
|
+
if (options.scanToolCalls && Array.isArray(result.toolCalls)) {
|
|
221
|
+
for (const call of result.toolCalls) {
|
|
222
|
+
if (!call || typeof call !== "object") {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const toolInput = call.args !== void 0 ? call.args : call.input;
|
|
226
|
+
const args = typeof toolInput === "string" ? toolInput : stringifyToolValue(toolInput);
|
|
227
|
+
const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
|
|
228
|
+
const toolCallId = typeof call.toolCallId === "string" ? call.toolCallId : void 0;
|
|
229
|
+
if (args.trim()) {
|
|
230
|
+
await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
specificationVersion: "v3",
|
|
237
|
+
middlewareVersion: "v2",
|
|
238
|
+
async wrapGenerate({
|
|
239
|
+
params,
|
|
240
|
+
doGenerate
|
|
241
|
+
}) {
|
|
242
|
+
if (scanInput) {
|
|
243
|
+
await scanPrompt(params.prompt ?? []);
|
|
244
|
+
}
|
|
245
|
+
const result = await doGenerate();
|
|
246
|
+
await scanGenerateResult(result);
|
|
247
|
+
return result;
|
|
248
|
+
},
|
|
249
|
+
async wrapStream({
|
|
250
|
+
params,
|
|
251
|
+
doStream
|
|
252
|
+
}) {
|
|
253
|
+
if (scanInput) {
|
|
254
|
+
await scanPrompt(params.prompt ?? []);
|
|
255
|
+
}
|
|
256
|
+
const { stream, ...rest } = await doStream();
|
|
257
|
+
if (!scanOutput) {
|
|
258
|
+
return { stream, ...rest };
|
|
259
|
+
}
|
|
260
|
+
let buffered = "";
|
|
261
|
+
const transformed = stream.pipeThrough(
|
|
262
|
+
new TransformStream({
|
|
263
|
+
transform(chunk, controller) {
|
|
264
|
+
const part = chunk;
|
|
265
|
+
if (part && part.type === "text-delta") {
|
|
266
|
+
const delta = part.textDelta ?? part.delta ?? "";
|
|
267
|
+
buffered += delta;
|
|
268
|
+
}
|
|
269
|
+
controller.enqueue(chunk);
|
|
270
|
+
},
|
|
271
|
+
async flush(controller) {
|
|
272
|
+
if (!buffered.trim()) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
if (err instanceof FirewallBlockedException) {
|
|
279
|
+
controller.enqueue({ type: "error", error: err });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
return { stream: transformed, ...rest };
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
export {
|
|
292
|
+
createMiddleware
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=vercel.js.map
|