@silmaril-security/sdk 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +271 -68
- package/dist/index.cjs +204 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -12
- package/dist/index.d.ts +21 -12
- package/dist/index.js +201 -78
- package/dist/index.js.map +1 -1
- package/package.json +5 -7
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,22 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
33
|
// src/exceptions.ts
|
|
34
|
+
function parseErrorBody(body) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(body);
|
|
37
|
+
if (!parsed || typeof parsed !== "object") {
|
|
38
|
+
return { details: void 0, error: void 0, apiMessage: void 0 };
|
|
39
|
+
}
|
|
40
|
+
const data = parsed;
|
|
41
|
+
return {
|
|
42
|
+
details: data.details && typeof data.details === "object" ? data.details : void 0,
|
|
43
|
+
error: typeof data.error === "string" ? data.error : void 0,
|
|
44
|
+
apiMessage: typeof data.message === "string" ? data.message : void 0
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return { details: void 0, error: void 0, apiMessage: void 0 };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
34
50
|
var MAX_PROMPT_DISPLAY_LEN, PromptBlockedException, SilmarilApiError;
|
|
35
51
|
var init_exceptions = __esm({
|
|
36
52
|
"src/exceptions.ts"() {
|
|
@@ -59,12 +75,20 @@ var init_exceptions = __esm({
|
|
|
59
75
|
status;
|
|
60
76
|
statusText;
|
|
61
77
|
body;
|
|
78
|
+
details;
|
|
79
|
+
error;
|
|
80
|
+
apiMessage;
|
|
62
81
|
constructor(params) {
|
|
63
|
-
|
|
82
|
+
const statusText = params.statusText ? ` ${params.statusText}` : "";
|
|
83
|
+
super(`Silmaril API error ${params.status}${statusText}`);
|
|
64
84
|
this.name = "SilmarilApiError";
|
|
65
85
|
this.status = params.status;
|
|
66
86
|
this.statusText = params.statusText;
|
|
67
87
|
this.body = params.body;
|
|
88
|
+
const parsed = parseErrorBody(params.body);
|
|
89
|
+
this.details = params.details ?? parsed.details;
|
|
90
|
+
this.error = params.error ?? parsed.error;
|
|
91
|
+
this.apiMessage = params.apiMessage ?? parsed.apiMessage;
|
|
68
92
|
Object.setPrototypeOf(this, _SilmarilApiError.prototype);
|
|
69
93
|
}
|
|
70
94
|
};
|
|
@@ -100,7 +124,7 @@ function prependToolName(text, toolName) {
|
|
|
100
124
|
}
|
|
101
125
|
return `[TOOL:${toolName}] ${text}`;
|
|
102
126
|
}
|
|
103
|
-
var HookLabel, FirewallHook, DEFAULT_HOOKS, INPUT_HOOKS, OUTPUT_HOOKS, ALL_HOOKS, FIREWALL_HOOK_TO_LABEL,
|
|
127
|
+
var HookLabel, FirewallHook, DEFAULT_HOOKS, INPUT_HOOKS, OUTPUT_HOOKS, ALL_HOOKS, FIREWALL_HOOK_TO_LABEL, FIREWALL_HOOK_VALUES;
|
|
104
128
|
var init_hooks = __esm({
|
|
105
129
|
"src/hooks.ts"() {
|
|
106
130
|
"use strict";
|
|
@@ -149,14 +173,6 @@ var init_hooks = __esm({
|
|
|
149
173
|
[FirewallHook.RETRIEVER_END]: HookLabel.TOOL_RESPONSE,
|
|
150
174
|
[FirewallHook.LLM_END]: HookLabel.LLM_OUTPUT
|
|
151
175
|
};
|
|
152
|
-
DEFAULT_HOOK_THRESHOLDS = {
|
|
153
|
-
[HookLabel.USER_INPUT]: 0.5,
|
|
154
|
-
[HookLabel.SYSTEM_PROMPT]: 0.5,
|
|
155
|
-
[HookLabel.TOOL_CALL]: 0.5,
|
|
156
|
-
[HookLabel.TOOL_RESPONSE]: 0.5,
|
|
157
|
-
[HookLabel.LLM_OUTPUT]: 0.5,
|
|
158
|
-
[HookLabel.UNKNOWN]: 0.5
|
|
159
|
-
};
|
|
160
176
|
FIREWALL_HOOK_VALUES = new Set(Object.values(FirewallHook));
|
|
161
177
|
}
|
|
162
178
|
});
|
|
@@ -243,19 +259,8 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
243
259
|
const logger = options.logger ?? ((message, error) => {
|
|
244
260
|
console.warn(`silmaril.firewall: ${message}`, error);
|
|
245
261
|
});
|
|
246
|
-
const overrideThreshold = options.threshold;
|
|
247
|
-
const overrideHookThresholds = options.hookThresholds ?? {};
|
|
248
262
|
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
249
263
|
const onClassify = options.onClassify;
|
|
250
|
-
const effectiveThreshold = (label) => {
|
|
251
|
-
if (overrideHookThresholds[label] !== void 0) {
|
|
252
|
-
return overrideHookThresholds[label];
|
|
253
|
-
}
|
|
254
|
-
if (firewall.hookThresholds[label] !== void 0) {
|
|
255
|
-
return firewall.hookThresholds[label];
|
|
256
|
-
}
|
|
257
|
-
return overrideThreshold ?? firewall.threshold;
|
|
258
|
-
};
|
|
259
264
|
const fireOnClassify = (hookLabel, text, result, blocked, toolName) => {
|
|
260
265
|
if (!onClassify) {
|
|
261
266
|
return;
|
|
@@ -287,7 +292,7 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
287
292
|
logger("classification failed, allowing prompt through", err);
|
|
288
293
|
return;
|
|
289
294
|
}
|
|
290
|
-
const threshold =
|
|
295
|
+
const threshold = result.threshold;
|
|
291
296
|
const blocked = result.score >= threshold;
|
|
292
297
|
fireOnClassify(hookLabel, text, result, blocked, toolName);
|
|
293
298
|
if (blocked && !shadowMode) {
|
|
@@ -408,9 +413,8 @@ __export(index_exports, {
|
|
|
408
413
|
CHUNK_OVERLAP_CHARS: () => CHUNK_OVERLAP_CHARS,
|
|
409
414
|
CHUNK_WINDOW: () => CHUNK_WINDOW,
|
|
410
415
|
CHUNK_WINDOW_CHARS: () => CHUNK_WINDOW_CHARS,
|
|
416
|
+
DEFAULT_CHUNK_CONCURRENCY: () => DEFAULT_CHUNK_CONCURRENCY,
|
|
411
417
|
DEFAULT_HOOKS: () => DEFAULT_HOOKS,
|
|
412
|
-
DEFAULT_HOOK_THRESHOLDS: () => DEFAULT_HOOK_THRESHOLDS,
|
|
413
|
-
DEFAULT_THRESHOLD: () => DEFAULT_THRESHOLD,
|
|
414
418
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
415
419
|
FIREWALL_HOOK_TO_LABEL: () => FIREWALL_HOOK_TO_LABEL,
|
|
416
420
|
Firewall: () => Firewall,
|
|
@@ -425,7 +429,8 @@ __export(index_exports, {
|
|
|
425
429
|
chunkText: () => chunkText,
|
|
426
430
|
prependHook: () => prependHook,
|
|
427
431
|
prependToolName: () => prependToolName,
|
|
428
|
-
resolveHooks: () => resolveHooks
|
|
432
|
+
resolveHooks: () => resolveHooks,
|
|
433
|
+
sanitizeText: () => sanitizeText
|
|
429
434
|
});
|
|
430
435
|
module.exports = __toCommonJS(index_exports);
|
|
431
436
|
|
|
@@ -489,18 +494,7 @@ function findLastUserMessage(messages) {
|
|
|
489
494
|
function createMiddleware(firewall, options = {}) {
|
|
490
495
|
const scanInput = options.scanInput ?? true;
|
|
491
496
|
const scanOutput = options.scanOutput ?? false;
|
|
492
|
-
const overrideThreshold = options.threshold;
|
|
493
|
-
const overrideHookThresholds = options.hookThresholds ?? {};
|
|
494
497
|
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
495
|
-
const effectiveThreshold = (label) => {
|
|
496
|
-
if (overrideHookThresholds[label] !== void 0) {
|
|
497
|
-
return overrideHookThresholds[label];
|
|
498
|
-
}
|
|
499
|
-
if (firewall.hookThresholds[label] !== void 0) {
|
|
500
|
-
return firewall.hookThresholds[label];
|
|
501
|
-
}
|
|
502
|
-
return overrideThreshold ?? firewall.threshold;
|
|
503
|
-
};
|
|
504
498
|
const classifyOrBlock = async (text, hook, toolName) => {
|
|
505
499
|
if (!text.trim()) {
|
|
506
500
|
return;
|
|
@@ -509,7 +503,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
509
503
|
text,
|
|
510
504
|
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
511
505
|
);
|
|
512
|
-
const threshold =
|
|
506
|
+
const threshold = result.threshold;
|
|
513
507
|
const blocked = result.score >= threshold;
|
|
514
508
|
options.onClassify?.({
|
|
515
509
|
hook,
|
|
@@ -632,7 +626,45 @@ var MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;
|
|
|
632
626
|
var CHUNK_WINDOW_CHARS = CHUNK_WINDOW * CHARS_PER_TOKEN;
|
|
633
627
|
var CHUNK_OVERLAP_CHARS = CHUNK_OVERLAP * CHARS_PER_TOKEN;
|
|
634
628
|
var CHUNK_STRIDE_CHARS = CHUNK_WINDOW_CHARS - CHUNK_OVERLAP_CHARS;
|
|
629
|
+
function isHighSurrogate(code) {
|
|
630
|
+
return code >= 55296 && code <= 56319;
|
|
631
|
+
}
|
|
632
|
+
function isLowSurrogate(code) {
|
|
633
|
+
return code >= 56320 && code <= 57343;
|
|
634
|
+
}
|
|
635
|
+
function sanitizeText(text) {
|
|
636
|
+
let out = "";
|
|
637
|
+
for (let i = 0; i < text.length; i++) {
|
|
638
|
+
const code = text.charCodeAt(i);
|
|
639
|
+
if (isHighSurrogate(code)) {
|
|
640
|
+
if (i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
641
|
+
out += text[i];
|
|
642
|
+
out += text[i + 1];
|
|
643
|
+
i++;
|
|
644
|
+
}
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (isLowSurrogate(code)) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
out += text[i];
|
|
651
|
+
}
|
|
652
|
+
return out;
|
|
653
|
+
}
|
|
654
|
+
function safeChunkStart(text, start) {
|
|
655
|
+
if (start > 0 && start < text.length && isLowSurrogate(text.charCodeAt(start)) && isHighSurrogate(text.charCodeAt(start - 1))) {
|
|
656
|
+
return start + 1;
|
|
657
|
+
}
|
|
658
|
+
return start;
|
|
659
|
+
}
|
|
660
|
+
function safeChunkEnd(text, end) {
|
|
661
|
+
if (end > 0 && end < text.length && isHighSurrogate(text.charCodeAt(end - 1)) && isLowSurrogate(text.charCodeAt(end))) {
|
|
662
|
+
return end - 1;
|
|
663
|
+
}
|
|
664
|
+
return end;
|
|
665
|
+
}
|
|
635
666
|
function chunkText(text) {
|
|
667
|
+
text = sanitizeText(text);
|
|
636
668
|
const n = text.length;
|
|
637
669
|
if (n > MAX_INPUT_CHARS) {
|
|
638
670
|
throw new Error(
|
|
@@ -644,8 +676,12 @@ function chunkText(text) {
|
|
|
644
676
|
}
|
|
645
677
|
const chunks = [];
|
|
646
678
|
for (let start = 0; start < n; start += CHUNK_STRIDE_CHARS) {
|
|
647
|
-
const
|
|
648
|
-
|
|
679
|
+
const safeStart = safeChunkStart(text, start);
|
|
680
|
+
if (safeStart >= n) {
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
const end = safeChunkEnd(text, safeStart + CHUNK_WINDOW_CHARS);
|
|
684
|
+
chunks.push(text.slice(safeStart, end));
|
|
649
685
|
if (end >= n) {
|
|
650
686
|
break;
|
|
651
687
|
}
|
|
@@ -655,14 +691,33 @@ function chunkText(text) {
|
|
|
655
691
|
|
|
656
692
|
// src/firewall.ts
|
|
657
693
|
init_exceptions();
|
|
658
|
-
var
|
|
694
|
+
var BASE_THRESHOLD = 0.5;
|
|
695
|
+
var TARGET_SEQUENCE_FPR = 0.01;
|
|
696
|
+
var MAX_ADAPTIVE_THRESHOLD = 0.9;
|
|
659
697
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
698
|
+
var DEFAULT_CHUNK_CONCURRENCY = 8;
|
|
660
699
|
var DEFAULT_MAX_RETRIES = 5;
|
|
661
700
|
var MAX_BACKOFF_SECONDS = 30;
|
|
662
|
-
|
|
701
|
+
var MAX_ERROR_BODY_BYTES = 1 << 16;
|
|
702
|
+
function adaptiveThreshold(scoringOpportunityCount) {
|
|
703
|
+
if (!Number.isInteger(scoringOpportunityCount) || scoringOpportunityCount < 1) {
|
|
704
|
+
throw new Error(
|
|
705
|
+
`Firewall: scoringOpportunityCount must be an integer >= 1, got ${scoringOpportunityCount}`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
if (scoringOpportunityCount === 1) {
|
|
709
|
+
return BASE_THRESHOLD;
|
|
710
|
+
}
|
|
711
|
+
const targetChunkFpr = 1 - Math.pow(1 - TARGET_SEQUENCE_FPR, 1 / scoringOpportunityCount);
|
|
712
|
+
const oddsRatio = TARGET_SEQUENCE_FPR / targetChunkFpr;
|
|
713
|
+
const rawThreshold = oddsRatio / (1 + oddsRatio);
|
|
714
|
+
return Math.min(rawThreshold, MAX_ADAPTIVE_THRESHOLD);
|
|
715
|
+
}
|
|
716
|
+
function blockResultFromResponse(data, threshold) {
|
|
663
717
|
const result = {
|
|
664
718
|
prediction: data.prediction,
|
|
665
|
-
score: Number(data.score)
|
|
719
|
+
score: Number(data.score),
|
|
720
|
+
threshold
|
|
666
721
|
};
|
|
667
722
|
if (data.primary_outcome !== void 0) {
|
|
668
723
|
result.primaryOutcome = data.primary_outcome;
|
|
@@ -684,12 +739,73 @@ function blockResultFromResponse(data) {
|
|
|
684
739
|
}
|
|
685
740
|
return Object.freeze(result);
|
|
686
741
|
}
|
|
742
|
+
async function readCappedErrorBody(response) {
|
|
743
|
+
if (!response.body) {
|
|
744
|
+
return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
|
|
745
|
+
}
|
|
746
|
+
const reader = response.body.getReader();
|
|
747
|
+
const chunks = [];
|
|
748
|
+
let remaining = MAX_ERROR_BODY_BYTES;
|
|
749
|
+
try {
|
|
750
|
+
while (remaining > 0) {
|
|
751
|
+
const { done, value } = await reader.read();
|
|
752
|
+
if (done) {
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
if (!value) {
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const chunk = value.byteLength > remaining ? value.slice(0, remaining) : value;
|
|
759
|
+
chunks.push(chunk);
|
|
760
|
+
remaining -= chunk.byteLength;
|
|
761
|
+
if (chunk.byteLength < value.byteLength) {
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
} catch {
|
|
766
|
+
return "";
|
|
767
|
+
} finally {
|
|
768
|
+
await reader.cancel().catch(() => void 0);
|
|
769
|
+
}
|
|
770
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
771
|
+
const body = new Uint8Array(total);
|
|
772
|
+
let offset = 0;
|
|
773
|
+
for (const chunk of chunks) {
|
|
774
|
+
body.set(chunk, offset);
|
|
775
|
+
offset += chunk.byteLength;
|
|
776
|
+
}
|
|
777
|
+
return new TextDecoder().decode(body);
|
|
778
|
+
}
|
|
779
|
+
async function mapWithConcurrency(items, concurrency, task) {
|
|
780
|
+
const results = new Array(items.length);
|
|
781
|
+
let nextIndex = 0;
|
|
782
|
+
let firstError;
|
|
783
|
+
const workerCount = Math.min(concurrency, items.length);
|
|
784
|
+
async function worker() {
|
|
785
|
+
for (; ; ) {
|
|
786
|
+
const index = nextIndex;
|
|
787
|
+
nextIndex += 1;
|
|
788
|
+
if (index >= items.length) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
results[index] = await task(items[index], index);
|
|
793
|
+
} catch (err) {
|
|
794
|
+
firstError ??= err;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
799
|
+
if (firstError !== void 0) {
|
|
800
|
+
throw firstError;
|
|
801
|
+
}
|
|
802
|
+
return results;
|
|
803
|
+
}
|
|
687
804
|
var Firewall = class {
|
|
688
805
|
apiKey;
|
|
689
806
|
apiUrl;
|
|
690
|
-
threshold;
|
|
691
807
|
timeoutMs;
|
|
692
|
-
|
|
808
|
+
chunkConcurrency;
|
|
693
809
|
shadowMode;
|
|
694
810
|
headers;
|
|
695
811
|
constructor(options) {
|
|
@@ -701,9 +817,16 @@ var Firewall = class {
|
|
|
701
817
|
}
|
|
702
818
|
this.apiKey = options.apiKey;
|
|
703
819
|
this.apiUrl = options.apiUrl;
|
|
704
|
-
this.threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
705
820
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
706
|
-
this.
|
|
821
|
+
if (typeof this.timeoutMs !== "number" || !Number.isFinite(this.timeoutMs) || this.timeoutMs < 0) {
|
|
822
|
+
throw new Error(`Firewall: timeoutMs must be a finite non-negative number, got ${this.timeoutMs}`);
|
|
823
|
+
}
|
|
824
|
+
this.chunkConcurrency = options.chunkConcurrency ?? DEFAULT_CHUNK_CONCURRENCY;
|
|
825
|
+
if (!Number.isInteger(this.chunkConcurrency) || this.chunkConcurrency < 1) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
`Firewall: chunkConcurrency must be an integer >= 1, got ${this.chunkConcurrency}`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
707
830
|
this.shadowMode = options.shadowMode ?? false;
|
|
708
831
|
this.headers = Object.freeze({
|
|
709
832
|
"x-api-key": this.apiKey,
|
|
@@ -712,34 +835,21 @@ var Firewall = class {
|
|
|
712
835
|
}
|
|
713
836
|
async classify(text, options = {}) {
|
|
714
837
|
const chunks = chunkText(text);
|
|
838
|
+
const threshold = adaptiveThreshold(chunks.length);
|
|
715
839
|
if (chunks.length === 1) {
|
|
716
|
-
|
|
717
|
-
if (options.hook !== void 0) {
|
|
718
|
-
payload.hook = options.hook;
|
|
719
|
-
}
|
|
720
|
-
if (options.toolName !== void 0) {
|
|
721
|
-
payload.tool_name = options.toolName;
|
|
722
|
-
}
|
|
723
|
-
if (options.metadata !== void 0) {
|
|
724
|
-
payload.metadata = options.metadata;
|
|
725
|
-
}
|
|
726
|
-
const data = await this.postWithRetry(payload);
|
|
727
|
-
return blockResultFromResponse(data);
|
|
840
|
+
return this.classifySingleChunk(chunks[0], options, threshold);
|
|
728
841
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
batchOpts.toolNames = chunks.map(() => options.toolName);
|
|
735
|
-
}
|
|
736
|
-
if (options.metadata !== void 0) {
|
|
737
|
-
batchOpts.metadata = chunks.map(() => options.metadata);
|
|
738
|
-
}
|
|
739
|
-
const results = await this.classifyBatch(chunks, batchOpts);
|
|
842
|
+
const results = await mapWithConcurrency(
|
|
843
|
+
chunks,
|
|
844
|
+
this.chunkConcurrency,
|
|
845
|
+
(chunk) => this.classifySingleChunk(chunk, options, threshold)
|
|
846
|
+
);
|
|
740
847
|
return results.reduce((best, r) => r.score > best.score ? r : best);
|
|
741
848
|
}
|
|
742
849
|
async classifyBatch(texts, options = {}) {
|
|
850
|
+
if (texts.length === 0) {
|
|
851
|
+
throw new Error("Firewall: texts must not be empty");
|
|
852
|
+
}
|
|
743
853
|
if (options.hooks !== void 0 && options.hooks.length !== texts.length) {
|
|
744
854
|
throw new Error(
|
|
745
855
|
`Firewall: hooks length ${options.hooks.length} does not match texts length ${texts.length}`
|
|
@@ -755,7 +865,11 @@ var Firewall = class {
|
|
|
755
865
|
`Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
|
|
756
866
|
);
|
|
757
867
|
}
|
|
758
|
-
const
|
|
868
|
+
const threshold = adaptiveThreshold(texts.length);
|
|
869
|
+
const payload = {
|
|
870
|
+
texts: texts.map((text) => sanitizeText(text)),
|
|
871
|
+
threshold
|
|
872
|
+
};
|
|
759
873
|
if (options.hooks && options.hooks.length > 0) {
|
|
760
874
|
payload.hooks = options.hooks.map((h) => String(h));
|
|
761
875
|
}
|
|
@@ -766,7 +880,7 @@ var Firewall = class {
|
|
|
766
880
|
payload.metadata = options.metadata.map((m) => m === void 0 ? null : m);
|
|
767
881
|
}
|
|
768
882
|
const data = await this.postWithRetry(payload);
|
|
769
|
-
return data.predictions.map((p) => blockResultFromResponse(p));
|
|
883
|
+
return data.predictions.map((p) => blockResultFromResponse(p, threshold));
|
|
770
884
|
}
|
|
771
885
|
asLangChainHandler(options = {}) {
|
|
772
886
|
return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
|
|
@@ -774,23 +888,18 @@ var Firewall = class {
|
|
|
774
888
|
asMiddleware(options = {}) {
|
|
775
889
|
return createMiddleware(this, options);
|
|
776
890
|
}
|
|
777
|
-
effectiveThreshold(hook) {
|
|
778
|
-
if (hook === void 0) {
|
|
779
|
-
return this.threshold;
|
|
780
|
-
}
|
|
781
|
-
return this.hookThresholds[hook] ?? this.threshold;
|
|
782
|
-
}
|
|
783
891
|
async postWithRetry(payload, maxRetries = DEFAULT_MAX_RETRIES) {
|
|
784
892
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
785
893
|
const response = await fetch(this.apiUrl, {
|
|
786
894
|
method: "POST",
|
|
787
895
|
headers: this.headers,
|
|
788
896
|
body: JSON.stringify(payload),
|
|
897
|
+
redirect: "error",
|
|
789
898
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
790
899
|
});
|
|
791
900
|
if (response.status !== 429 || attempt === maxRetries) {
|
|
792
901
|
if (!response.ok) {
|
|
793
|
-
const body = await response
|
|
902
|
+
const body = await readCappedErrorBody(response);
|
|
794
903
|
throw new SilmarilApiError({
|
|
795
904
|
status: response.status,
|
|
796
905
|
statusText: response.statusText,
|
|
@@ -804,6 +913,20 @@ var Firewall = class {
|
|
|
804
913
|
}
|
|
805
914
|
throw new Error("Firewall: exhausted retries (unreachable)");
|
|
806
915
|
}
|
|
916
|
+
async classifySingleChunk(text, options, threshold) {
|
|
917
|
+
const payload = { text, threshold };
|
|
918
|
+
if (options.hook !== void 0) {
|
|
919
|
+
payload.hook = options.hook;
|
|
920
|
+
}
|
|
921
|
+
if (options.toolName !== void 0) {
|
|
922
|
+
payload.tool_name = options.toolName;
|
|
923
|
+
}
|
|
924
|
+
if (options.metadata !== void 0) {
|
|
925
|
+
payload.metadata = options.metadata;
|
|
926
|
+
}
|
|
927
|
+
const data = await this.postWithRetry(payload);
|
|
928
|
+
return blockResultFromResponse(data, threshold);
|
|
929
|
+
}
|
|
807
930
|
};
|
|
808
931
|
|
|
809
932
|
// src/index.ts
|
|
@@ -816,9 +939,8 @@ init_hooks();
|
|
|
816
939
|
CHUNK_OVERLAP_CHARS,
|
|
817
940
|
CHUNK_WINDOW,
|
|
818
941
|
CHUNK_WINDOW_CHARS,
|
|
942
|
+
DEFAULT_CHUNK_CONCURRENCY,
|
|
819
943
|
DEFAULT_HOOKS,
|
|
820
|
-
DEFAULT_HOOK_THRESHOLDS,
|
|
821
|
-
DEFAULT_THRESHOLD,
|
|
822
944
|
DEFAULT_TIMEOUT_MS,
|
|
823
945
|
FIREWALL_HOOK_TO_LABEL,
|
|
824
946
|
Firewall,
|
|
@@ -833,6 +955,7 @@ init_hooks();
|
|
|
833
955
|
chunkText,
|
|
834
956
|
prependHook,
|
|
835
957
|
prependToolName,
|
|
836
|
-
resolveHooks
|
|
958
|
+
resolveHooks,
|
|
959
|
+
sanitizeText
|
|
837
960
|
});
|
|
838
961
|
//# sourceMappingURL=index.cjs.map
|