@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
package/dist/index.js
CHANGED
|
@@ -25,30 +25,39 @@ function parseErrorBody(body) {
|
|
|
25
25
|
return { details: void 0, error: void 0, apiMessage: void 0 };
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
var MAX_PROMPT_DISPLAY_LEN, PromptBlockedException, SilmarilApiError;
|
|
28
|
+
var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, SilmarilApiError;
|
|
29
29
|
var init_exceptions = __esm({
|
|
30
30
|
"src/exceptions.ts"() {
|
|
31
31
|
"use strict";
|
|
32
32
|
MAX_PROMPT_DISPLAY_LEN = 100;
|
|
33
|
-
|
|
33
|
+
FirewallBlockedException = class _FirewallBlockedException extends Error {
|
|
34
34
|
score;
|
|
35
35
|
threshold;
|
|
36
36
|
promptText;
|
|
37
37
|
runId;
|
|
38
|
+
hook;
|
|
39
|
+
toolName;
|
|
40
|
+
toolCallId;
|
|
41
|
+
result;
|
|
38
42
|
constructor(params) {
|
|
39
|
-
super(
|
|
40
|
-
this.name = "
|
|
43
|
+
super(_FirewallBlockedException.formatMessage(params));
|
|
44
|
+
this.name = "FirewallBlockedException";
|
|
41
45
|
this.score = params.score;
|
|
42
46
|
this.threshold = params.threshold;
|
|
43
47
|
this.promptText = params.promptText;
|
|
44
48
|
this.runId = params.runId;
|
|
45
|
-
|
|
49
|
+
this.hook = params.hook;
|
|
50
|
+
this.toolName = params.toolName;
|
|
51
|
+
this.toolCallId = params.toolCallId;
|
|
52
|
+
this.result = params.result;
|
|
53
|
+
Object.setPrototypeOf(this, _FirewallBlockedException.prototype);
|
|
46
54
|
}
|
|
47
55
|
static formatMessage(params) {
|
|
48
56
|
const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
|
|
49
|
-
return `
|
|
57
|
+
return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
|
|
50
58
|
}
|
|
51
59
|
};
|
|
60
|
+
PromptBlockedException = FirewallBlockedException;
|
|
52
61
|
SilmarilApiError = class _SilmarilApiError extends Error {
|
|
53
62
|
status;
|
|
54
63
|
statusText;
|
|
@@ -239,19 +248,12 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
239
248
|
});
|
|
240
249
|
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
241
250
|
const onClassify = options.onClassify;
|
|
242
|
-
const fireOnClassify = (
|
|
251
|
+
const fireOnClassify = (event) => {
|
|
243
252
|
if (!onClassify) {
|
|
244
253
|
return;
|
|
245
254
|
}
|
|
246
255
|
try {
|
|
247
|
-
onClassify(
|
|
248
|
-
hook: hookLabel,
|
|
249
|
-
...toolName !== void 0 ? { toolName } : {},
|
|
250
|
-
text,
|
|
251
|
-
result,
|
|
252
|
-
blocked,
|
|
253
|
-
shadowMode
|
|
254
|
-
});
|
|
256
|
+
onClassify(event);
|
|
255
257
|
} catch (err) {
|
|
256
258
|
logger("onClassify callback threw", err);
|
|
257
259
|
}
|
|
@@ -272,15 +274,30 @@ async function createLangChainHandler(firewall, options = {}) {
|
|
|
272
274
|
}
|
|
273
275
|
const threshold = result.threshold;
|
|
274
276
|
const blocked = result.score >= threshold;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
277
|
+
const commonEventFields = {
|
|
278
|
+
hook: hookLabel,
|
|
279
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
280
|
+
runId,
|
|
281
|
+
text,
|
|
282
|
+
result
|
|
283
|
+
};
|
|
284
|
+
fireOnClassify({
|
|
285
|
+
...commonEventFields,
|
|
286
|
+
blocked,
|
|
287
|
+
shadowMode
|
|
288
|
+
});
|
|
289
|
+
if (!blocked || shadowMode) {
|
|
290
|
+
return;
|
|
283
291
|
}
|
|
292
|
+
throw new FirewallBlockedException({
|
|
293
|
+
score: result.score,
|
|
294
|
+
threshold,
|
|
295
|
+
promptText: text,
|
|
296
|
+
runId,
|
|
297
|
+
hook: hookLabel,
|
|
298
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
299
|
+
result
|
|
300
|
+
});
|
|
284
301
|
};
|
|
285
302
|
class SilmarilFirewallHandler extends BaseCallbackHandler {
|
|
286
303
|
name = "silmaril_firewall_handler";
|
|
@@ -383,6 +400,9 @@ var init_langchain = __esm({
|
|
|
383
400
|
}
|
|
384
401
|
});
|
|
385
402
|
|
|
403
|
+
// src/firewall.ts
|
|
404
|
+
import { randomUUID } from "crypto";
|
|
405
|
+
|
|
386
406
|
// src/adapters/vercel.ts
|
|
387
407
|
init_exceptions();
|
|
388
408
|
init_hooks();
|
|
@@ -399,6 +419,9 @@ function stringifyToolValue(value) {
|
|
|
399
419
|
return String(value);
|
|
400
420
|
}
|
|
401
421
|
}
|
|
422
|
+
function isRecord(value) {
|
|
423
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
424
|
+
}
|
|
402
425
|
function extractContentText(content) {
|
|
403
426
|
if (typeof content === "string") {
|
|
404
427
|
return content;
|
|
@@ -423,14 +446,29 @@ function iterateToolResultParts(message) {
|
|
|
423
446
|
continue;
|
|
424
447
|
}
|
|
425
448
|
if (part.type === "tool-result") {
|
|
426
|
-
const text =
|
|
449
|
+
const text = stringifyToolResult(part.result !== void 0 ? part.result : part.output);
|
|
427
450
|
if (text.trim()) {
|
|
428
|
-
out.push({ text, toolName: part.toolName });
|
|
451
|
+
out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });
|
|
429
452
|
}
|
|
430
453
|
}
|
|
431
454
|
}
|
|
432
455
|
return out;
|
|
433
456
|
}
|
|
457
|
+
function stringifyToolResult(value) {
|
|
458
|
+
if (!isRecord(value)) {
|
|
459
|
+
return stringifyToolValue(value);
|
|
460
|
+
}
|
|
461
|
+
if ((value.type === "text" || value.type === "error-text") && typeof value.value === "string") {
|
|
462
|
+
return value.value;
|
|
463
|
+
}
|
|
464
|
+
if ((value.type === "json" || value.type === "error-json") && value.value !== void 0) {
|
|
465
|
+
return stringifyToolValue(value.value);
|
|
466
|
+
}
|
|
467
|
+
if (value.type === "content" && Array.isArray(value.value)) {
|
|
468
|
+
return value.value.map((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(" ");
|
|
469
|
+
}
|
|
470
|
+
return stringifyToolValue(value);
|
|
471
|
+
}
|
|
434
472
|
function findLastUserMessage(messages) {
|
|
435
473
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
436
474
|
const msg = messages[i];
|
|
@@ -444,33 +482,43 @@ function createMiddleware(firewall, options = {}) {
|
|
|
444
482
|
const scanInput = options.scanInput ?? true;
|
|
445
483
|
const scanOutput = options.scanOutput ?? false;
|
|
446
484
|
const shadowMode = options.shadowMode ?? firewall.shadowMode;
|
|
447
|
-
const classifyOrBlock = async (text, hook, toolName) => {
|
|
485
|
+
const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
|
|
448
486
|
if (!text.trim()) {
|
|
449
487
|
return;
|
|
450
488
|
}
|
|
489
|
+
const { toolName, toolCallId } = context;
|
|
451
490
|
const result = await firewall.classify(
|
|
452
491
|
text,
|
|
453
492
|
toolName !== void 0 ? { hook, toolName } : { hook }
|
|
454
493
|
);
|
|
455
494
|
const threshold = result.threshold;
|
|
456
495
|
const blocked = result.score >= threshold;
|
|
457
|
-
|
|
496
|
+
const commonEventFields = {
|
|
458
497
|
hook,
|
|
459
498
|
...toolName !== void 0 ? { toolName } : {},
|
|
499
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
460
500
|
text,
|
|
461
|
-
result
|
|
501
|
+
result
|
|
502
|
+
};
|
|
503
|
+
options.onClassify?.({
|
|
504
|
+
...commonEventFields,
|
|
462
505
|
blocked,
|
|
463
506
|
shadowMode
|
|
464
507
|
});
|
|
465
|
-
if (blocked
|
|
466
|
-
|
|
467
|
-
score: result.score,
|
|
468
|
-
threshold,
|
|
469
|
-
promptText: text
|
|
470
|
-
});
|
|
471
|
-
options.onBlocked?.(err);
|
|
472
|
-
throw err;
|
|
508
|
+
if (!blocked || shadowMode) {
|
|
509
|
+
return;
|
|
473
510
|
}
|
|
511
|
+
const err = new FirewallBlockedException({
|
|
512
|
+
score: result.score,
|
|
513
|
+
threshold,
|
|
514
|
+
promptText: text,
|
|
515
|
+
hook,
|
|
516
|
+
...toolName !== void 0 ? { toolName } : {},
|
|
517
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
518
|
+
result
|
|
519
|
+
});
|
|
520
|
+
options.onBlocked?.(err);
|
|
521
|
+
throw err;
|
|
474
522
|
};
|
|
475
523
|
const scanPrompt = async (prompt) => {
|
|
476
524
|
if (prompt.length === 0) {
|
|
@@ -479,8 +527,8 @@ function createMiddleware(firewall, options = {}) {
|
|
|
479
527
|
const last = prompt[prompt.length - 1];
|
|
480
528
|
const role = last.role.toLowerCase();
|
|
481
529
|
if (role === "tool") {
|
|
482
|
-
for (const { text: text2, toolName } of iterateToolResultParts(last)) {
|
|
483
|
-
await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, toolName);
|
|
530
|
+
for (const { text: text2, toolName, toolCallId } of iterateToolResultParts(last)) {
|
|
531
|
+
await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });
|
|
484
532
|
}
|
|
485
533
|
return;
|
|
486
534
|
}
|
|
@@ -503,21 +551,25 @@ function createMiddleware(firewall, options = {}) {
|
|
|
503
551
|
if (!call || typeof call !== "object") {
|
|
504
552
|
continue;
|
|
505
553
|
}
|
|
506
|
-
const
|
|
554
|
+
const toolInput = call.args !== void 0 ? call.args : call.input;
|
|
555
|
+
const args = typeof toolInput === "string" ? toolInput : stringifyToolValue(toolInput);
|
|
507
556
|
const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
|
|
557
|
+
const toolCallId = typeof call.toolCallId === "string" ? call.toolCallId : void 0;
|
|
508
558
|
if (args.trim()) {
|
|
509
|
-
await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);
|
|
559
|
+
await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });
|
|
510
560
|
}
|
|
511
561
|
}
|
|
512
562
|
}
|
|
513
563
|
};
|
|
514
564
|
return {
|
|
565
|
+
specificationVersion: "v3",
|
|
566
|
+
middlewareVersion: "v2",
|
|
515
567
|
async wrapGenerate({
|
|
516
568
|
params,
|
|
517
569
|
doGenerate
|
|
518
570
|
}) {
|
|
519
571
|
if (scanInput) {
|
|
520
|
-
await scanPrompt(params.prompt);
|
|
572
|
+
await scanPrompt(params.prompt ?? []);
|
|
521
573
|
}
|
|
522
574
|
const result = await doGenerate();
|
|
523
575
|
await scanGenerateResult(result);
|
|
@@ -528,7 +580,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
528
580
|
doStream
|
|
529
581
|
}) {
|
|
530
582
|
if (scanInput) {
|
|
531
|
-
await scanPrompt(params.prompt);
|
|
583
|
+
await scanPrompt(params.prompt ?? []);
|
|
532
584
|
}
|
|
533
585
|
const { stream, ...rest } = await doStream();
|
|
534
586
|
if (!scanOutput) {
|
|
@@ -552,7 +604,7 @@ function createMiddleware(firewall, options = {}) {
|
|
|
552
604
|
try {
|
|
553
605
|
await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
|
|
554
606
|
} catch (err) {
|
|
555
|
-
if (err instanceof
|
|
607
|
+
if (err instanceof FirewallBlockedException) {
|
|
556
608
|
controller.enqueue({ type: "error", error: err });
|
|
557
609
|
return;
|
|
558
610
|
}
|
|
@@ -640,54 +692,133 @@ function chunkText(text) {
|
|
|
640
692
|
|
|
641
693
|
// src/firewall.ts
|
|
642
694
|
init_exceptions();
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
var
|
|
695
|
+
|
|
696
|
+
// src/outcomes.ts
|
|
697
|
+
var Outcome = {
|
|
698
|
+
Benign: "benign",
|
|
699
|
+
InformationDisclosure: "information_disclosure",
|
|
700
|
+
SecretExposure: "secret_exposure",
|
|
701
|
+
ControlAbuse: "control_abuse",
|
|
702
|
+
SystemCompromise: "system_compromise",
|
|
703
|
+
ServiceDisruption: "service_disruption"
|
|
704
|
+
};
|
|
705
|
+
var PRIMARY_OUTCOMES = [
|
|
706
|
+
Outcome.Benign,
|
|
707
|
+
Outcome.InformationDisclosure,
|
|
708
|
+
Outcome.SecretExposure,
|
|
709
|
+
Outcome.ControlAbuse,
|
|
710
|
+
Outcome.SystemCompromise,
|
|
711
|
+
Outcome.ServiceDisruption
|
|
712
|
+
];
|
|
713
|
+
var HARMFUL_OUTCOMES = [
|
|
714
|
+
Outcome.InformationDisclosure,
|
|
715
|
+
Outcome.SecretExposure,
|
|
716
|
+
Outcome.ControlAbuse,
|
|
717
|
+
Outcome.SystemCompromise,
|
|
718
|
+
Outcome.ServiceDisruption
|
|
719
|
+
];
|
|
720
|
+
var OUTCOME_DESCRIPTIONS = {
|
|
721
|
+
[Outcome.Benign]: "No harmful firewall outcome detected.",
|
|
722
|
+
[Outcome.InformationDisclosure]: "Exposes private data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.",
|
|
723
|
+
[Outcome.SecretExposure]: "Exposes credentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.",
|
|
724
|
+
[Outcome.ControlAbuse]: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.",
|
|
725
|
+
[Outcome.SystemCompromise]: "Enables privilege escalation, account takeover, hostile integration or plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.",
|
|
726
|
+
[Outcome.ServiceDisruption]: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence."
|
|
727
|
+
};
|
|
728
|
+
var PRIMARY_OUTCOME_SET = new Set(PRIMARY_OUTCOMES);
|
|
729
|
+
var HARMFUL_OUTCOME_SET = new Set(HARMFUL_OUTCOMES);
|
|
730
|
+
function isPrimaryOutcome(value) {
|
|
731
|
+
return typeof value === "string" && PRIMARY_OUTCOME_SET.has(value);
|
|
732
|
+
}
|
|
733
|
+
function isHarmfulOutcome(value) {
|
|
734
|
+
return typeof value === "string" && HARMFUL_OUTCOME_SET.has(value);
|
|
735
|
+
}
|
|
736
|
+
function normalizePrimaryOutcome(value, fieldName = "primary_outcome") {
|
|
737
|
+
if (typeof value !== "string") {
|
|
738
|
+
throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
|
|
739
|
+
}
|
|
740
|
+
return isPrimaryOutcome(value) ? value : value;
|
|
741
|
+
}
|
|
742
|
+
function normalizeHarmfulOutcome(value, fieldName = "outcome") {
|
|
743
|
+
if (typeof value !== "string" || value === Outcome.Benign) {
|
|
744
|
+
throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
|
|
745
|
+
}
|
|
746
|
+
return isHarmfulOutcome(value) ? value : value;
|
|
747
|
+
}
|
|
748
|
+
function normalizeHarmfulOutcomeMap(values, fieldName) {
|
|
749
|
+
if (values === void 0 || values === null) {
|
|
750
|
+
return void 0;
|
|
751
|
+
}
|
|
752
|
+
if (typeof values !== "object" || Array.isArray(values)) {
|
|
753
|
+
throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(values)}`);
|
|
754
|
+
}
|
|
755
|
+
const out = {};
|
|
756
|
+
for (const [key, value] of Object.entries(values)) {
|
|
757
|
+
const normalizedKey = normalizeHarmfulOutcome(key, `${fieldName} key`);
|
|
758
|
+
if (typeof value !== "number") {
|
|
759
|
+
throw new Error(`Firewall: invalid ${fieldName} value for ${JSON.stringify(key)}`);
|
|
760
|
+
}
|
|
761
|
+
out[normalizedKey] = value;
|
|
762
|
+
}
|
|
763
|
+
return Object.freeze(out);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/firewall.ts
|
|
767
|
+
var SDK_VERSION = "0.4.2";
|
|
646
768
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
647
769
|
var DEFAULT_CHUNK_CONCURRENCY = 8;
|
|
648
770
|
var DEFAULT_MAX_RETRIES = 5;
|
|
649
771
|
var MAX_BACKOFF_SECONDS = 30;
|
|
650
772
|
var MAX_ERROR_BODY_BYTES = 1 << 16;
|
|
651
|
-
function
|
|
652
|
-
if (!Number.isInteger(scoringOpportunityCount) || scoringOpportunityCount < 1) {
|
|
653
|
-
throw new Error(
|
|
654
|
-
`Firewall: scoringOpportunityCount must be an integer >= 1, got ${scoringOpportunityCount}`
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
if (scoringOpportunityCount === 1) {
|
|
658
|
-
return BASE_THRESHOLD;
|
|
659
|
-
}
|
|
660
|
-
const targetChunkFpr = 1 - Math.pow(1 - TARGET_SEQUENCE_FPR, 1 / scoringOpportunityCount);
|
|
661
|
-
const oddsRatio = TARGET_SEQUENCE_FPR / targetChunkFpr;
|
|
662
|
-
const rawThreshold = oddsRatio / (1 + oddsRatio);
|
|
663
|
-
return Math.min(rawThreshold, MAX_ADAPTIVE_THRESHOLD);
|
|
664
|
-
}
|
|
665
|
-
function blockResultFromResponse(data, threshold) {
|
|
773
|
+
function blockResultFromResponse(data) {
|
|
666
774
|
const result = {
|
|
667
775
|
prediction: data.prediction,
|
|
668
776
|
score: Number(data.score),
|
|
669
|
-
threshold
|
|
777
|
+
threshold: Number(data.threshold)
|
|
670
778
|
};
|
|
671
779
|
if (data.primary_outcome !== void 0) {
|
|
672
|
-
result.primaryOutcome = data.primary_outcome;
|
|
780
|
+
result.primaryOutcome = normalizePrimaryOutcome(data.primary_outcome);
|
|
673
781
|
}
|
|
674
782
|
if (data.outcome_scores !== void 0) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
783
|
+
const outcomeScores = normalizeHarmfulOutcomeMap(data.outcome_scores, "outcome_scores");
|
|
784
|
+
if (outcomeScores !== void 0) {
|
|
785
|
+
result.outcomeScores = outcomeScores;
|
|
786
|
+
}
|
|
678
787
|
}
|
|
679
788
|
if (data.detector_scores !== void 0) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
789
|
+
const detectorScores = normalizeHarmfulOutcomeMap(data.detector_scores, "detector_scores");
|
|
790
|
+
if (detectorScores !== void 0) {
|
|
791
|
+
result.detectorScores = detectorScores;
|
|
792
|
+
}
|
|
683
793
|
}
|
|
684
794
|
if (data.detector_counts !== void 0) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
795
|
+
const detectorCounts = normalizeHarmfulOutcomeMap(data.detector_counts, "detector_counts");
|
|
796
|
+
if (detectorCounts !== void 0) {
|
|
797
|
+
result.detectorCounts = detectorCounts;
|
|
798
|
+
}
|
|
688
799
|
}
|
|
689
800
|
return Object.freeze(result);
|
|
690
801
|
}
|
|
802
|
+
function isRecord2(value) {
|
|
803
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
804
|
+
}
|
|
805
|
+
function withSdkMetadata(metadata, info) {
|
|
806
|
+
const payload = { ...metadata ?? {} };
|
|
807
|
+
const existing = payload.silmaril;
|
|
808
|
+
if (existing !== void 0 && !isRecord2(existing)) {
|
|
809
|
+
throw new Error("Firewall: metadata.silmaril must be an object when provided");
|
|
810
|
+
}
|
|
811
|
+
payload.silmaril = {
|
|
812
|
+
...isRecord2(existing) ? existing : {},
|
|
813
|
+
sdk_language: "typescript",
|
|
814
|
+
sdk_version: SDK_VERSION,
|
|
815
|
+
request_id: info.requestId,
|
|
816
|
+
input_index: info.inputIndex,
|
|
817
|
+
chunk_index: info.chunkIndex,
|
|
818
|
+
chunk_count: info.chunkCount
|
|
819
|
+
};
|
|
820
|
+
return payload;
|
|
821
|
+
}
|
|
691
822
|
async function readCappedErrorBody(response) {
|
|
692
823
|
if (!response.body) {
|
|
693
824
|
return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
|
|
@@ -784,14 +915,24 @@ var Firewall = class {
|
|
|
784
915
|
}
|
|
785
916
|
async classify(text, options = {}) {
|
|
786
917
|
const chunks = chunkText(text);
|
|
787
|
-
const
|
|
918
|
+
const requestId = options.requestId ?? randomUUID();
|
|
788
919
|
if (chunks.length === 1) {
|
|
789
|
-
return this.classifySingleChunk(chunks[0], options,
|
|
920
|
+
return this.classifySingleChunk(chunks[0], options, {
|
|
921
|
+
requestId,
|
|
922
|
+
inputIndex: 0,
|
|
923
|
+
chunkIndex: 0,
|
|
924
|
+
chunkCount: 1
|
|
925
|
+
});
|
|
790
926
|
}
|
|
791
927
|
const results = await mapWithConcurrency(
|
|
792
928
|
chunks,
|
|
793
929
|
this.chunkConcurrency,
|
|
794
|
-
(chunk) => this.classifySingleChunk(chunk, options,
|
|
930
|
+
(chunk, index) => this.classifySingleChunk(chunk, options, {
|
|
931
|
+
requestId,
|
|
932
|
+
inputIndex: 0,
|
|
933
|
+
chunkIndex: index,
|
|
934
|
+
chunkCount: chunks.length
|
|
935
|
+
})
|
|
795
936
|
);
|
|
796
937
|
return results.reduce((best, r) => r.score > best.score ? r : best);
|
|
797
938
|
}
|
|
@@ -814,10 +955,9 @@ var Firewall = class {
|
|
|
814
955
|
`Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
|
|
815
956
|
);
|
|
816
957
|
}
|
|
817
|
-
const
|
|
958
|
+
const requestId = options.requestId ?? randomUUID();
|
|
818
959
|
const payload = {
|
|
819
|
-
texts: texts.map((text) => sanitizeText(text))
|
|
820
|
-
threshold
|
|
960
|
+
texts: texts.map((text) => sanitizeText(text))
|
|
821
961
|
};
|
|
822
962
|
if (options.hooks && options.hooks.length > 0) {
|
|
823
963
|
payload.hooks = options.hooks.map((h) => String(h));
|
|
@@ -825,14 +965,21 @@ var Firewall = class {
|
|
|
825
965
|
if (options.toolNames && options.toolNames.length > 0) {
|
|
826
966
|
payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
|
|
827
967
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
968
|
+
payload.metadata = texts.map(
|
|
969
|
+
(_, index) => withSdkMetadata(options.metadata?.[index], {
|
|
970
|
+
requestId,
|
|
971
|
+
inputIndex: index,
|
|
972
|
+
chunkIndex: 0,
|
|
973
|
+
chunkCount: 1
|
|
974
|
+
})
|
|
975
|
+
);
|
|
831
976
|
const data = await this.postWithRetry(payload);
|
|
832
|
-
return data.predictions.map((p) => blockResultFromResponse(p
|
|
977
|
+
return data.predictions.map((p) => blockResultFromResponse(p));
|
|
833
978
|
}
|
|
834
979
|
asLangChainHandler(options = {}) {
|
|
835
|
-
return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
|
|
980
|
+
return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
|
|
981
|
+
(m) => m.createLangChainHandler(this, options)
|
|
982
|
+
);
|
|
836
983
|
}
|
|
837
984
|
asMiddleware(options = {}) {
|
|
838
985
|
return createMiddleware(this, options);
|
|
@@ -862,19 +1009,17 @@ var Firewall = class {
|
|
|
862
1009
|
}
|
|
863
1010
|
throw new Error("Firewall: exhausted retries (unreachable)");
|
|
864
1011
|
}
|
|
865
|
-
async classifySingleChunk(text, options,
|
|
866
|
-
const payload = { text
|
|
1012
|
+
async classifySingleChunk(text, options, metadataInfo) {
|
|
1013
|
+
const payload = { text };
|
|
867
1014
|
if (options.hook !== void 0) {
|
|
868
1015
|
payload.hook = options.hook;
|
|
869
1016
|
}
|
|
870
1017
|
if (options.toolName !== void 0) {
|
|
871
1018
|
payload.tool_name = options.toolName;
|
|
872
1019
|
}
|
|
873
|
-
|
|
874
|
-
payload.metadata = options.metadata;
|
|
875
|
-
}
|
|
1020
|
+
payload.metadata = withSdkMetadata(options.metadata, metadataInfo);
|
|
876
1021
|
const data = await this.postWithRetry(payload);
|
|
877
|
-
return blockResultFromResponse(data
|
|
1022
|
+
return blockResultFromResponse(data);
|
|
878
1023
|
}
|
|
879
1024
|
};
|
|
880
1025
|
|
|
@@ -892,15 +1037,25 @@ export {
|
|
|
892
1037
|
DEFAULT_TIMEOUT_MS,
|
|
893
1038
|
FIREWALL_HOOK_TO_LABEL,
|
|
894
1039
|
Firewall,
|
|
1040
|
+
FirewallBlockedException,
|
|
895
1041
|
FirewallHook,
|
|
1042
|
+
HARMFUL_OUTCOMES,
|
|
896
1043
|
HookLabel,
|
|
897
1044
|
INPUT_HOOKS,
|
|
898
1045
|
MAX_INPUT_CHARS,
|
|
899
1046
|
MAX_INPUT_TOKENS,
|
|
1047
|
+
OUTCOME_DESCRIPTIONS,
|
|
900
1048
|
OUTPUT_HOOKS,
|
|
1049
|
+
Outcome,
|
|
1050
|
+
PRIMARY_OUTCOMES,
|
|
901
1051
|
PromptBlockedException,
|
|
902
1052
|
SilmarilApiError,
|
|
903
1053
|
chunkText,
|
|
1054
|
+
isHarmfulOutcome,
|
|
1055
|
+
isPrimaryOutcome,
|
|
1056
|
+
normalizeHarmfulOutcome,
|
|
1057
|
+
normalizeHarmfulOutcomeMap,
|
|
1058
|
+
normalizePrimaryOutcome,
|
|
904
1059
|
prependHook,
|
|
905
1060
|
prependToolName,
|
|
906
1061
|
resolveHooks,
|