@silmaril-security/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,711 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/exceptions.ts
12
+ var MAX_PROMPT_DISPLAY_LEN, PromptBlockedException, SilmarilApiError;
13
+ var init_exceptions = __esm({
14
+ "src/exceptions.ts"() {
15
+ "use strict";
16
+ MAX_PROMPT_DISPLAY_LEN = 100;
17
+ PromptBlockedException = class _PromptBlockedException extends Error {
18
+ score;
19
+ threshold;
20
+ promptText;
21
+ runId;
22
+ constructor(params) {
23
+ super(_PromptBlockedException.formatMessage(params));
24
+ this.name = "PromptBlockedException";
25
+ this.score = params.score;
26
+ this.threshold = params.threshold;
27
+ this.promptText = params.promptText;
28
+ this.runId = params.runId;
29
+ Object.setPrototypeOf(this, _PromptBlockedException.prototype);
30
+ }
31
+ static formatMessage(params) {
32
+ const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
33
+ return `Prompt blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
34
+ }
35
+ };
36
+ SilmarilApiError = class _SilmarilApiError extends Error {
37
+ status;
38
+ statusText;
39
+ body;
40
+ constructor(params) {
41
+ super(`Silmaril API error ${params.status} ${params.statusText}: ${params.body}`);
42
+ this.name = "SilmarilApiError";
43
+ this.status = params.status;
44
+ this.statusText = params.statusText;
45
+ this.body = params.body;
46
+ Object.setPrototypeOf(this, _SilmarilApiError.prototype);
47
+ }
48
+ };
49
+ }
50
+ });
51
+
52
+ // src/hooks.ts
53
+ function resolveHooks(hooks) {
54
+ if (hooks === void 0) {
55
+ return DEFAULT_HOOKS;
56
+ }
57
+ const resolved = /* @__PURE__ */ new Set();
58
+ for (const h of hooks) {
59
+ if (!FIREWALL_HOOK_VALUES.has(h)) {
60
+ throw new Error(`Invalid FirewallHook value: ${String(h)}`);
61
+ }
62
+ resolved.add(h);
63
+ }
64
+ return resolved;
65
+ }
66
+ function prependHook(text, hook) {
67
+ if (hook === void 0 || hook === null) {
68
+ return text;
69
+ }
70
+ if (hook === HookLabel.UNKNOWN) {
71
+ return text;
72
+ }
73
+ return `[HOOK:${hook}] ${text}`;
74
+ }
75
+ function prependToolName(text, toolName) {
76
+ if (toolName === void 0 || toolName === null) {
77
+ return text;
78
+ }
79
+ return `[TOOL:${toolName}] ${text}`;
80
+ }
81
+ var HookLabel, FirewallHook, DEFAULT_HOOKS, INPUT_HOOKS, OUTPUT_HOOKS, ALL_HOOKS, FIREWALL_HOOK_TO_LABEL, DEFAULT_HOOK_THRESHOLDS, FIREWALL_HOOK_VALUES;
82
+ var init_hooks = __esm({
83
+ "src/hooks.ts"() {
84
+ "use strict";
85
+ HookLabel = {
86
+ USER_INPUT: "user_input",
87
+ SYSTEM_PROMPT: "system_prompt",
88
+ TOOL_CALL: "tool_call",
89
+ TOOL_RESPONSE: "tool_response",
90
+ LLM_OUTPUT: "llm_output",
91
+ UNKNOWN: "unknown"
92
+ };
93
+ FirewallHook = {
94
+ LLM_START: "on_llm_start",
95
+ CHAT_MODEL_START: "on_chat_model_start",
96
+ TOOL_START: "on_tool_start",
97
+ RETRIEVER_START: "on_retriever_start",
98
+ LLM_END: "on_llm_end",
99
+ TOOL_END: "on_tool_end",
100
+ RETRIEVER_END: "on_retriever_end"
101
+ };
102
+ DEFAULT_HOOKS = /* @__PURE__ */ new Set([
103
+ FirewallHook.LLM_START,
104
+ FirewallHook.CHAT_MODEL_START
105
+ ]);
106
+ INPUT_HOOKS = /* @__PURE__ */ new Set([
107
+ FirewallHook.LLM_START,
108
+ FirewallHook.CHAT_MODEL_START,
109
+ FirewallHook.TOOL_START,
110
+ FirewallHook.RETRIEVER_START
111
+ ]);
112
+ OUTPUT_HOOKS = /* @__PURE__ */ new Set([
113
+ FirewallHook.LLM_END,
114
+ FirewallHook.TOOL_END,
115
+ FirewallHook.RETRIEVER_END
116
+ ]);
117
+ ALL_HOOKS = /* @__PURE__ */ new Set([
118
+ ...INPUT_HOOKS,
119
+ ...OUTPUT_HOOKS
120
+ ]);
121
+ FIREWALL_HOOK_TO_LABEL = {
122
+ [FirewallHook.CHAT_MODEL_START]: HookLabel.USER_INPUT,
123
+ [FirewallHook.LLM_START]: HookLabel.USER_INPUT,
124
+ [FirewallHook.TOOL_START]: HookLabel.TOOL_CALL,
125
+ [FirewallHook.TOOL_END]: HookLabel.TOOL_RESPONSE,
126
+ [FirewallHook.RETRIEVER_START]: HookLabel.TOOL_CALL,
127
+ [FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,
128
+ [FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT
129
+ };
130
+ DEFAULT_HOOK_THRESHOLDS = {
131
+ [HookLabel.USER_INPUT]: 0.5,
132
+ [HookLabel.SYSTEM_PROMPT]: 0.3,
133
+ [HookLabel.TOOL_CALL]: 0.4,
134
+ [HookLabel.TOOL_RESPONSE]: 0.3,
135
+ [HookLabel.LLM_OUTPUT]: 0.4,
136
+ [HookLabel.UNKNOWN]: 0.5
137
+ };
138
+ FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
139
+ }
140
+ });
141
+
142
+ // src/utils/extract.ts
143
+ function extractTextFromPrompts(prompts) {
144
+ return prompts.map((p) => p.trim()).filter((p) => p.length > 0).join("\n");
145
+ }
146
+ function extractTextFromToolInput(inputStr) {
147
+ return inputStr.trim();
148
+ }
149
+ function extractTextFromLLMResult(response) {
150
+ const parts = [];
151
+ for (const genList of response.generations ?? []) {
152
+ for (const gen of genList) {
153
+ const text = (gen.text ?? "").trim();
154
+ if (text) {
155
+ parts.push(text);
156
+ }
157
+ }
158
+ }
159
+ return parts.join("\n");
160
+ }
161
+ function extractTextFromDocuments(documents) {
162
+ const parts = [];
163
+ for (const doc of documents) {
164
+ const text = (doc.pageContent ?? "").trim();
165
+ if (text) {
166
+ parts.push(text);
167
+ }
168
+ }
169
+ return parts.join("\n");
170
+ }
171
+ var init_extract = __esm({
172
+ "src/utils/extract.ts"() {
173
+ "use strict";
174
+ }
175
+ });
176
+
177
+ // src/adapters/langchain.ts
178
+ var langchain_exports = {};
179
+ __export(langchain_exports, {
180
+ createLangChainHandler: () => createLangChainHandler
181
+ });
182
+ function getMessageRole(message) {
183
+ if (typeof message.role === "string") {
184
+ return message.role.toLowerCase();
185
+ }
186
+ if (typeof message.type === "string") {
187
+ return message.type.toLowerCase();
188
+ }
189
+ return "";
190
+ }
191
+ function extractMessageText(content) {
192
+ if (content === void 0) {
193
+ return "";
194
+ }
195
+ if (typeof content === "string") {
196
+ return content;
197
+ }
198
+ const parts = [];
199
+ for (const block of content) {
200
+ if (typeof block === "string") {
201
+ parts.push(block);
202
+ } else if (block && block.type === "text") {
203
+ parts.push(block.text ?? "");
204
+ }
205
+ }
206
+ return parts.join(" ");
207
+ }
208
+ function findLastUserMessage2(messages) {
209
+ for (let i = messages.length - 1; i >= 0; i--) {
210
+ const msg = messages[i];
211
+ if (USER_ROLES.has(getMessageRole(msg))) {
212
+ return msg;
213
+ }
214
+ }
215
+ return void 0;
216
+ }
217
+ async function createLangChainHandler(firewall, options = {}) {
218
+ const { BaseCallbackHandler } = await import("@langchain/core/callbacks/base");
219
+ const enabledHooks = resolveHooks(options.hooks);
220
+ const failOpen = options.failOpen ?? true;
221
+ const logger = options.logger ?? ((message, error) => {
222
+ console.warn(`silmaril.firewall: ${message}`, error);
223
+ });
224
+ const overrideThreshold = options.threshold;
225
+ const overrideHookThresholds = options.hookThresholds ?? {};
226
+ const effectiveThreshold = (label) => {
227
+ if (overrideHookThresholds[label] !== void 0) {
228
+ return overrideHookThresholds[label];
229
+ }
230
+ if (firewall.hookThresholds[label] !== void 0) {
231
+ return firewall.hookThresholds[label];
232
+ }
233
+ return overrideThreshold ?? firewall.threshold;
234
+ };
235
+ const classify = async (text, hookLabel, runId, toolName) => {
236
+ try {
237
+ const result = await firewall.classify(text, {
238
+ hook: hookLabel,
239
+ ...toolName !== void 0 ? { toolName } : {}
240
+ });
241
+ const threshold = effectiveThreshold(hookLabel);
242
+ if (result.score >= threshold) {
243
+ throw new PromptBlockedException({
244
+ score: result.score,
245
+ threshold,
246
+ promptText: text,
247
+ runId
248
+ });
249
+ }
250
+ } catch (err) {
251
+ if (err instanceof PromptBlockedException) {
252
+ throw err;
253
+ }
254
+ if (!failOpen) {
255
+ throw err;
256
+ }
257
+ logger("classification failed, allowing prompt through", err);
258
+ }
259
+ };
260
+ class SilmarilFirewallHandler extends BaseCallbackHandler {
261
+ name = "silmaril_firewall_handler";
262
+ raiseError = true;
263
+ awaitHandlers = true;
264
+ async handleChatModelStart(_llm, messages, runId) {
265
+ if (!enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {
266
+ return;
267
+ }
268
+ const batches = messages;
269
+ const flat = [];
270
+ for (const batch of batches) {
271
+ for (const m of batch) {
272
+ flat.push(m);
273
+ }
274
+ }
275
+ const lastUser = findLastUserMessage2(flat);
276
+ if (!lastUser) {
277
+ return;
278
+ }
279
+ const text = extractMessageText(lastUser.content).trim();
280
+ if (!text) {
281
+ return;
282
+ }
283
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);
284
+ }
285
+ async handleLLMStart(_llm, prompts, runId) {
286
+ if (!enabledHooks.has(FirewallHook.LLM_START)) {
287
+ return;
288
+ }
289
+ const text = extractTextFromPrompts(prompts);
290
+ if (!text) {
291
+ return;
292
+ }
293
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);
294
+ }
295
+ async handleToolStart(tool, inputStr, runId) {
296
+ if (!enabledHooks.has(FirewallHook.TOOL_START)) {
297
+ return;
298
+ }
299
+ const text = extractTextFromToolInput(inputStr);
300
+ if (!text) {
301
+ return;
302
+ }
303
+ const toolName = tool?.name;
304
+ const prefixed = prependToolName(text, toolName);
305
+ await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);
306
+ }
307
+ async handleRetrieverStart(_retriever, query, runId) {
308
+ if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {
309
+ return;
310
+ }
311
+ const text = query.trim();
312
+ if (!text) {
313
+ return;
314
+ }
315
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);
316
+ }
317
+ async handleLLMEnd(output, runId) {
318
+ if (!enabledHooks.has(FirewallHook.LLM_END)) {
319
+ return;
320
+ }
321
+ const text = extractTextFromLLMResult(output);
322
+ if (!text) {
323
+ return;
324
+ }
325
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);
326
+ }
327
+ async handleToolEnd(output, runId, _parentRunId, _tags, _kwargs) {
328
+ if (!enabledHooks.has(FirewallHook.TOOL_END)) {
329
+ return;
330
+ }
331
+ const text = String(output).trim();
332
+ if (!text) {
333
+ return;
334
+ }
335
+ const toolName = _kwargs?.name;
336
+ const prefixed = prependToolName(text, toolName);
337
+ await classify(prefixed, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);
338
+ }
339
+ async handleRetrieverEnd(documents, runId) {
340
+ if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {
341
+ return;
342
+ }
343
+ const text = extractTextFromDocuments(documents);
344
+ if (!text) {
345
+ return;
346
+ }
347
+ await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);
348
+ }
349
+ }
350
+ return new SilmarilFirewallHandler();
351
+ }
352
+ var USER_ROLES;
353
+ var init_langchain = __esm({
354
+ "src/adapters/langchain.ts"() {
355
+ "use strict";
356
+ init_exceptions();
357
+ init_hooks();
358
+ init_extract();
359
+ USER_ROLES = /* @__PURE__ */ new Set(["human", "user"]);
360
+ }
361
+ });
362
+
363
+ // src/adapters/vercel.ts
364
+ init_exceptions();
365
+ init_hooks();
366
+ function stringifyToolValue(value) {
367
+ if (value === null || value === void 0) {
368
+ return "";
369
+ }
370
+ if (typeof value === "string") {
371
+ return value;
372
+ }
373
+ try {
374
+ return JSON.stringify(value);
375
+ } catch {
376
+ return String(value);
377
+ }
378
+ }
379
+ function extractContentText(content) {
380
+ if (typeof content === "string") {
381
+ return content;
382
+ }
383
+ const parts = [];
384
+ for (const block of content) {
385
+ if (typeof block === "string") {
386
+ parts.push(block);
387
+ } else if (block && block.type === "text") {
388
+ parts.push(block.text ?? "");
389
+ }
390
+ }
391
+ return parts.join(" ");
392
+ }
393
+ function iterateToolResultParts(message) {
394
+ if (typeof message.content === "string") {
395
+ return [];
396
+ }
397
+ const out = [];
398
+ for (const part of message.content) {
399
+ if (typeof part === "string") {
400
+ continue;
401
+ }
402
+ if (part.type === "tool-result") {
403
+ const text = stringifyToolValue(part.result);
404
+ if (text.trim()) {
405
+ out.push({ text, toolName: part.toolName });
406
+ }
407
+ }
408
+ }
409
+ return out;
410
+ }
411
+ function findLastUserMessage(messages) {
412
+ for (let i = messages.length - 1; i >= 0; i--) {
413
+ const msg = messages[i];
414
+ if (msg.role.toLowerCase() === "user") {
415
+ return msg;
416
+ }
417
+ }
418
+ return void 0;
419
+ }
420
+ function createMiddleware(firewall, options = {}) {
421
+ const scanInput = options.scanInput ?? true;
422
+ const scanOutput = options.scanOutput ?? false;
423
+ const overrideThreshold = options.threshold;
424
+ const overrideHookThresholds = options.hookThresholds ?? {};
425
+ const effectiveThreshold = (label) => {
426
+ if (overrideHookThresholds[label] !== void 0) {
427
+ return overrideHookThresholds[label];
428
+ }
429
+ if (firewall.hookThresholds[label] !== void 0) {
430
+ return firewall.hookThresholds[label];
431
+ }
432
+ return overrideThreshold ?? firewall.threshold;
433
+ };
434
+ const classifyOrBlock = async (text, hook, toolName) => {
435
+ if (!text.trim()) {
436
+ return;
437
+ }
438
+ const result = await firewall.classify(
439
+ text,
440
+ toolName !== void 0 ? { hook, toolName } : { hook }
441
+ );
442
+ const threshold = effectiveThreshold(hook);
443
+ const blocked = result.score >= threshold;
444
+ options.onClassify?.({
445
+ hook,
446
+ ...toolName !== void 0 ? { toolName } : {},
447
+ text,
448
+ result,
449
+ blocked
450
+ });
451
+ if (blocked) {
452
+ const err = new PromptBlockedException({
453
+ score: result.score,
454
+ threshold,
455
+ promptText: text
456
+ });
457
+ options.onBlocked?.(err);
458
+ throw err;
459
+ }
460
+ };
461
+ const scanPrompt = async (prompt) => {
462
+ if (prompt.length === 0) {
463
+ return;
464
+ }
465
+ const last = prompt[prompt.length - 1];
466
+ const role = last.role.toLowerCase();
467
+ if (role === "tool") {
468
+ for (const { text: text2, toolName } of iterateToolResultParts(last)) {
469
+ await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, toolName);
470
+ }
471
+ return;
472
+ }
473
+ const lastUser = findLastUserMessage(prompt);
474
+ if (!lastUser) {
475
+ return;
476
+ }
477
+ const text = extractContentText(lastUser.content).trim();
478
+ if (!text) {
479
+ return;
480
+ }
481
+ await classifyOrBlock(text, HookLabel.USER_INPUT);
482
+ };
483
+ const scanGenerateResult = async (result) => {
484
+ if (scanOutput && typeof result.text === "string" && result.text.length > 0) {
485
+ await classifyOrBlock(result.text, HookLabel.LLM_OUTPUT);
486
+ }
487
+ if (options.scanToolCalls && Array.isArray(result.toolCalls)) {
488
+ for (const call of result.toolCalls) {
489
+ if (!call || typeof call !== "object") {
490
+ continue;
491
+ }
492
+ const args = typeof call.args === "string" ? call.args : stringifyToolValue(call.args);
493
+ const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
494
+ if (args.trim()) {
495
+ await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);
496
+ }
497
+ }
498
+ }
499
+ };
500
+ return {
501
+ async wrapGenerate({ params, doGenerate }) {
502
+ if (scanInput) {
503
+ await scanPrompt(params.prompt);
504
+ }
505
+ const result = await doGenerate();
506
+ await scanGenerateResult(result);
507
+ return result;
508
+ },
509
+ async wrapStream({ params, doStream }) {
510
+ if (scanInput) {
511
+ await scanPrompt(params.prompt);
512
+ }
513
+ const { stream, ...rest } = await doStream();
514
+ if (!scanOutput) {
515
+ return { stream, ...rest };
516
+ }
517
+ let buffered = "";
518
+ const transformed = stream.pipeThrough(
519
+ new TransformStream({
520
+ transform(chunk, controller) {
521
+ const part = chunk;
522
+ if (part && part.type === "text-delta") {
523
+ const delta = part.textDelta ?? part.delta ?? "";
524
+ buffered += delta;
525
+ }
526
+ controller.enqueue(chunk);
527
+ },
528
+ async flush(controller) {
529
+ if (!buffered.trim()) {
530
+ return;
531
+ }
532
+ try {
533
+ await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
534
+ } catch (err) {
535
+ if (err instanceof PromptBlockedException) {
536
+ controller.enqueue({ type: "error", error: err });
537
+ return;
538
+ }
539
+ throw err;
540
+ }
541
+ }
542
+ })
543
+ );
544
+ return { stream: transformed, ...rest };
545
+ }
546
+ };
547
+ }
548
+
549
+ // src/chunking.ts
550
+ var CHARS_PER_TOKEN = 4;
551
+ var MAX_INPUT_TOKENS = 10240;
552
+ var CHUNK_WINDOW = 400;
553
+ var CHUNK_OVERLAP = 64;
554
+ var MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;
555
+ var CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;
556
+ var CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;
557
+ var CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;
558
+ function chunkText(text) {
559
+ const n = text.length;
560
+ if (n > MAX_INPUT_CHARS) {
561
+ throw new Error(
562
+ `Input has ~${Math.floor(n / CHARS_PER_TOKEN)} tokens (${n} chars); max is ${MAX_INPUT_TOKENS} tokens (${MAX_INPUT_CHARS} chars)`
563
+ );
564
+ }
565
+ if (n <= CHUNK_WINDOW_CHARS) {
566
+ return [text];
567
+ }
568
+ const chunks = [];
569
+ for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {
570
+ const end = start + CHUNK_WINDOW_CHARS;
571
+ chunks.push(text.slice(start, end));
572
+ if (end >= n) {
573
+ break;
574
+ }
575
+ }
576
+ return chunks;
577
+ }
578
+
579
+ // src/firewall.ts
580
+ init_exceptions();
581
+ var DEFAULT_THRESHOLD = 0.5;
582
+ var DEFAULT_TIMEOUT_MS = 1e4;
583
+ var DEFAULT_MAX_RETRIES = 5;
584
+ var MAX_BACKOFF_SECONDS = 30;
585
+ var Firewall = class {
586
+ apiKey;
587
+ apiUrl;
588
+ threshold;
589
+ timeoutMs;
590
+ hookThresholds;
591
+ headers;
592
+ constructor(options) {
593
+ if (!options.apiKey) {
594
+ throw new Error("Firewall: apiKey is required");
595
+ }
596
+ if (!options.apiUrl) {
597
+ throw new Error("Firewall: apiUrl is required");
598
+ }
599
+ this.apiKey = options.apiKey;
600
+ this.apiUrl = options.apiUrl;
601
+ this.threshold = options.threshold ?? DEFAULT_THRESHOLD;
602
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
603
+ this.hookThresholds = options.hookThresholds ?? {};
604
+ this.headers = Object.freeze({
605
+ "x-api-key": this.apiKey,
606
+ "content-type": "application/json"
607
+ });
608
+ }
609
+ async classify(text, options = {}) {
610
+ const chunks = chunkText(text);
611
+ if (chunks.length === 1) {
612
+ const payload = { text: chunks[0] };
613
+ if (options.hook !== void 0) {
614
+ payload.hook = options.hook;
615
+ }
616
+ if (options.toolName !== void 0) {
617
+ payload.tool_name = options.toolName;
618
+ }
619
+ const data = await this.postWithRetry(payload);
620
+ return Object.freeze({ prediction: data.prediction, score: Number(data.score) });
621
+ }
622
+ const batchOpts = {};
623
+ if (options.hook !== void 0) {
624
+ batchOpts.hooks = chunks.map(() => options.hook);
625
+ }
626
+ if (options.toolName !== void 0) {
627
+ batchOpts.toolNames = chunks.map(() => options.toolName);
628
+ }
629
+ const results = await this.classifyBatch(chunks, batchOpts);
630
+ return results.reduce((best, r) => r.score > best.score ? r : best);
631
+ }
632
+ async classifyBatch(texts, options = {}) {
633
+ const payload = { texts };
634
+ if (options.hooks && options.hooks.length > 0) {
635
+ payload.hooks = options.hooks.map((h) => String(h));
636
+ }
637
+ if (options.toolNames && options.toolNames.length > 0) {
638
+ payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
639
+ }
640
+ const data = await this.postWithRetry(payload);
641
+ return data.predictions.map(
642
+ (p) => Object.freeze({ prediction: p.prediction, score: Number(p.score) })
643
+ );
644
+ }
645
+ asLangChainHandler(options = {}) {
646
+ return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
647
+ }
648
+ asMiddleware(options = {}) {
649
+ return createMiddleware(this, options);
650
+ }
651
+ effectiveThreshold(hook) {
652
+ if (hook === void 0) {
653
+ return this.threshold;
654
+ }
655
+ return this.hookThresholds[hook] ?? this.threshold;
656
+ }
657
+ async postWithRetry(payload, maxRetries = DEFAULT_MAX_RETRIES) {
658
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
659
+ const response = await fetch(this.apiUrl, {
660
+ method: "POST",
661
+ headers: this.headers,
662
+ body: JSON.stringify(payload),
663
+ signal: AbortSignal.timeout(this.timeoutMs)
664
+ });
665
+ if (response.status !== 429 || attempt === maxRetries) {
666
+ if (!response.ok) {
667
+ const body = await response.text().catch(() => "");
668
+ throw new SilmarilApiError({
669
+ status: response.status,
670
+ statusText: response.statusText,
671
+ body
672
+ });
673
+ }
674
+ return await response.json();
675
+ }
676
+ const waitSeconds = Math.min(2 ** attempt, MAX_BACKOFF_SECONDS);
677
+ await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1e3));
678
+ }
679
+ throw new Error("Firewall: exhausted retries (unreachable)");
680
+ }
681
+ };
682
+
683
+ // src/index.ts
684
+ init_exceptions();
685
+ init_hooks();
686
+ export {
687
+ ALL_HOOKS,
688
+ CHUNK_OVERLAP,
689
+ CHUNK_OVERLAP_CHARS,
690
+ CHUNK_WINDOW,
691
+ CHUNK_WINDOW_CHARS,
692
+ DEFAULT_HOOKS,
693
+ DEFAULT_HOOK_THRESHOLDS,
694
+ DEFAULT_THRESHOLD,
695
+ DEFAULT_TIMEOUT_MS,
696
+ FIREWALL_HOOK_TO_LABEL,
697
+ Firewall,
698
+ FirewallHook,
699
+ HookLabel,
700
+ INPUT_HOOKS,
701
+ MAX_INPUT_CHARS,
702
+ MAX_INPUT_TOKENS,
703
+ OUTPUT_HOOKS,
704
+ PromptBlockedException,
705
+ SilmarilApiError,
706
+ chunkText,
707
+ prependHook,
708
+ prependToolName,
709
+ resolveHooks
710
+ };
711
+ //# sourceMappingURL=index.js.map