@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/dist/index.cjs CHANGED
@@ -47,30 +47,39 @@ function parseErrorBody(body) {
47
47
  return { details: void 0, error: void 0, apiMessage: void 0 };
48
48
  }
49
49
  }
50
- var MAX_PROMPT_DISPLAY_LEN, PromptBlockedException, SilmarilApiError;
50
+ var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, SilmarilApiError;
51
51
  var init_exceptions = __esm({
52
52
  "src/exceptions.ts"() {
53
53
  "use strict";
54
54
  MAX_PROMPT_DISPLAY_LEN = 100;
55
- PromptBlockedException = class _PromptBlockedException extends Error {
55
+ FirewallBlockedException = class _FirewallBlockedException extends Error {
56
56
  score;
57
57
  threshold;
58
58
  promptText;
59
59
  runId;
60
+ hook;
61
+ toolName;
62
+ toolCallId;
63
+ result;
60
64
  constructor(params) {
61
- super(_PromptBlockedException.formatMessage(params));
62
- this.name = "PromptBlockedException";
65
+ super(_FirewallBlockedException.formatMessage(params));
66
+ this.name = "FirewallBlockedException";
63
67
  this.score = params.score;
64
68
  this.threshold = params.threshold;
65
69
  this.promptText = params.promptText;
66
70
  this.runId = params.runId;
67
- Object.setPrototypeOf(this, _PromptBlockedException.prototype);
71
+ this.hook = params.hook;
72
+ this.toolName = params.toolName;
73
+ this.toolCallId = params.toolCallId;
74
+ this.result = params.result;
75
+ Object.setPrototypeOf(this, _FirewallBlockedException.prototype);
68
76
  }
69
77
  static formatMessage(params) {
70
78
  const truncated = params.promptText.length > MAX_PROMPT_DISPLAY_LEN ? `${params.promptText.slice(0, MAX_PROMPT_DISPLAY_LEN)}...` : params.promptText;
71
- return `Prompt blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
79
+ return `Request blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
72
80
  }
73
81
  };
82
+ PromptBlockedException = FirewallBlockedException;
74
83
  SilmarilApiError = class _SilmarilApiError extends Error {
75
84
  status;
76
85
  statusText;
@@ -261,19 +270,12 @@ async function createLangChainHandler(firewall, options = {}) {
261
270
  });
262
271
  const shadowMode = options.shadowMode ?? firewall.shadowMode;
263
272
  const onClassify = options.onClassify;
264
- const fireOnClassify = (hookLabel, text, result, blocked, toolName) => {
273
+ const fireOnClassify = (event) => {
265
274
  if (!onClassify) {
266
275
  return;
267
276
  }
268
277
  try {
269
- onClassify({
270
- hook: hookLabel,
271
- ...toolName !== void 0 ? { toolName } : {},
272
- text,
273
- result,
274
- blocked,
275
- shadowMode
276
- });
278
+ onClassify(event);
277
279
  } catch (err) {
278
280
  logger("onClassify callback threw", err);
279
281
  }
@@ -294,15 +296,30 @@ async function createLangChainHandler(firewall, options = {}) {
294
296
  }
295
297
  const threshold = result.threshold;
296
298
  const blocked = result.score >= threshold;
297
- fireOnClassify(hookLabel, text, result, blocked, toolName);
298
- if (blocked && !shadowMode) {
299
- throw new PromptBlockedException({
300
- score: result.score,
301
- threshold,
302
- promptText: text,
303
- runId
304
- });
299
+ const commonEventFields = {
300
+ hook: hookLabel,
301
+ ...toolName !== void 0 ? { toolName } : {},
302
+ runId,
303
+ text,
304
+ result
305
+ };
306
+ fireOnClassify({
307
+ ...commonEventFields,
308
+ blocked,
309
+ shadowMode
310
+ });
311
+ if (!blocked || shadowMode) {
312
+ return;
305
313
  }
314
+ throw new FirewallBlockedException({
315
+ score: result.score,
316
+ threshold,
317
+ promptText: text,
318
+ runId,
319
+ hook: hookLabel,
320
+ ...toolName !== void 0 ? { toolName } : {},
321
+ result
322
+ });
306
323
  };
307
324
  class SilmarilFirewallHandler extends BaseCallbackHandler {
308
325
  name = "silmaril_firewall_handler";
@@ -406,8 +423,8 @@ var init_langchain = __esm({
406
423
  });
407
424
 
408
425
  // src/index.ts
409
- var index_exports = {};
410
- __export(index_exports, {
426
+ var src_exports = {};
427
+ __export(src_exports, {
411
428
  ALL_HOOKS: () => ALL_HOOKS,
412
429
  CHUNK_OVERLAP: () => CHUNK_OVERLAP,
413
430
  CHUNK_OVERLAP_CHARS: () => CHUNK_OVERLAP_CHARS,
@@ -418,21 +435,34 @@ __export(index_exports, {
418
435
  DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
419
436
  FIREWALL_HOOK_TO_LABEL: () => FIREWALL_HOOK_TO_LABEL,
420
437
  Firewall: () => Firewall,
438
+ FirewallBlockedException: () => FirewallBlockedException,
421
439
  FirewallHook: () => FirewallHook,
440
+ HARMFUL_OUTCOMES: () => HARMFUL_OUTCOMES,
422
441
  HookLabel: () => HookLabel,
423
442
  INPUT_HOOKS: () => INPUT_HOOKS,
424
443
  MAX_INPUT_CHARS: () => MAX_INPUT_CHARS,
425
444
  MAX_INPUT_TOKENS: () => MAX_INPUT_TOKENS,
445
+ OUTCOME_DESCRIPTIONS: () => OUTCOME_DESCRIPTIONS,
426
446
  OUTPUT_HOOKS: () => OUTPUT_HOOKS,
447
+ Outcome: () => Outcome,
448
+ PRIMARY_OUTCOMES: () => PRIMARY_OUTCOMES,
427
449
  PromptBlockedException: () => PromptBlockedException,
428
450
  SilmarilApiError: () => SilmarilApiError,
429
451
  chunkText: () => chunkText,
452
+ isHarmfulOutcome: () => isHarmfulOutcome,
453
+ isPrimaryOutcome: () => isPrimaryOutcome,
454
+ normalizeHarmfulOutcome: () => normalizeHarmfulOutcome,
455
+ normalizeHarmfulOutcomeMap: () => normalizeHarmfulOutcomeMap,
456
+ normalizePrimaryOutcome: () => normalizePrimaryOutcome,
430
457
  prependHook: () => prependHook,
431
458
  prependToolName: () => prependToolName,
432
459
  resolveHooks: () => resolveHooks,
433
460
  sanitizeText: () => sanitizeText
434
461
  });
435
- module.exports = __toCommonJS(index_exports);
462
+ module.exports = __toCommonJS(src_exports);
463
+
464
+ // src/firewall.ts
465
+ var import_node_crypto = require("crypto");
436
466
 
437
467
  // src/adapters/vercel.ts
438
468
  init_exceptions();
@@ -450,6 +480,9 @@ function stringifyToolValue(value) {
450
480
  return String(value);
451
481
  }
452
482
  }
483
+ function isRecord(value) {
484
+ return typeof value === "object" && value !== null && !Array.isArray(value);
485
+ }
453
486
  function extractContentText(content) {
454
487
  if (typeof content === "string") {
455
488
  return content;
@@ -474,14 +507,29 @@ function iterateToolResultParts(message) {
474
507
  continue;
475
508
  }
476
509
  if (part.type === "tool-result") {
477
- const text = stringifyToolValue(part.result);
510
+ const text = stringifyToolResult(part.result !== void 0 ? part.result : part.output);
478
511
  if (text.trim()) {
479
- out.push({ text, toolName: part.toolName });
512
+ out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });
480
513
  }
481
514
  }
482
515
  }
483
516
  return out;
484
517
  }
518
+ function stringifyToolResult(value) {
519
+ if (!isRecord(value)) {
520
+ return stringifyToolValue(value);
521
+ }
522
+ if ((value.type === "text" || value.type === "error-text") && typeof value.value === "string") {
523
+ return value.value;
524
+ }
525
+ if ((value.type === "json" || value.type === "error-json") && value.value !== void 0) {
526
+ return stringifyToolValue(value.value);
527
+ }
528
+ if (value.type === "content" && Array.isArray(value.value)) {
529
+ return value.value.map((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(" ");
530
+ }
531
+ return stringifyToolValue(value);
532
+ }
485
533
  function findLastUserMessage(messages) {
486
534
  for (let i = messages.length - 1; i >= 0; i--) {
487
535
  const msg = messages[i];
@@ -495,33 +543,43 @@ function createMiddleware(firewall, options = {}) {
495
543
  const scanInput = options.scanInput ?? true;
496
544
  const scanOutput = options.scanOutput ?? false;
497
545
  const shadowMode = options.shadowMode ?? firewall.shadowMode;
498
- const classifyOrBlock = async (text, hook, toolName) => {
546
+ const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
499
547
  if (!text.trim()) {
500
548
  return;
501
549
  }
550
+ const { toolName, toolCallId } = context;
502
551
  const result = await firewall.classify(
503
552
  text,
504
553
  toolName !== void 0 ? { hook, toolName } : { hook }
505
554
  );
506
555
  const threshold = result.threshold;
507
556
  const blocked = result.score >= threshold;
508
- options.onClassify?.({
557
+ const commonEventFields = {
509
558
  hook,
510
559
  ...toolName !== void 0 ? { toolName } : {},
560
+ ...toolCallId !== void 0 ? { toolCallId } : {},
511
561
  text,
512
- result,
562
+ result
563
+ };
564
+ options.onClassify?.({
565
+ ...commonEventFields,
513
566
  blocked,
514
567
  shadowMode
515
568
  });
516
- if (blocked && !shadowMode) {
517
- const err = new PromptBlockedException({
518
- score: result.score,
519
- threshold,
520
- promptText: text
521
- });
522
- options.onBlocked?.(err);
523
- throw err;
569
+ if (!blocked || shadowMode) {
570
+ return;
524
571
  }
572
+ const err = new FirewallBlockedException({
573
+ score: result.score,
574
+ threshold,
575
+ promptText: text,
576
+ hook,
577
+ ...toolName !== void 0 ? { toolName } : {},
578
+ ...toolCallId !== void 0 ? { toolCallId } : {},
579
+ result
580
+ });
581
+ options.onBlocked?.(err);
582
+ throw err;
525
583
  };
526
584
  const scanPrompt = async (prompt) => {
527
585
  if (prompt.length === 0) {
@@ -530,8 +588,8 @@ function createMiddleware(firewall, options = {}) {
530
588
  const last = prompt[prompt.length - 1];
531
589
  const role = last.role.toLowerCase();
532
590
  if (role === "tool") {
533
- for (const { text: text2, toolName } of iterateToolResultParts(last)) {
534
- await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, toolName);
591
+ for (const { text: text2, toolName, toolCallId } of iterateToolResultParts(last)) {
592
+ await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });
535
593
  }
536
594
  return;
537
595
  }
@@ -554,21 +612,25 @@ function createMiddleware(firewall, options = {}) {
554
612
  if (!call || typeof call !== "object") {
555
613
  continue;
556
614
  }
557
- const args = typeof call.args === "string" ? call.args : stringifyToolValue(call.args);
615
+ const toolInput = call.args !== void 0 ? call.args : call.input;
616
+ const args = typeof toolInput === "string" ? toolInput : stringifyToolValue(toolInput);
558
617
  const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
618
+ const toolCallId = typeof call.toolCallId === "string" ? call.toolCallId : void 0;
559
619
  if (args.trim()) {
560
- await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);
620
+ await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });
561
621
  }
562
622
  }
563
623
  }
564
624
  };
565
625
  return {
626
+ specificationVersion: "v3",
627
+ middlewareVersion: "v2",
566
628
  async wrapGenerate({
567
629
  params,
568
630
  doGenerate
569
631
  }) {
570
632
  if (scanInput) {
571
- await scanPrompt(params.prompt);
633
+ await scanPrompt(params.prompt ?? []);
572
634
  }
573
635
  const result = await doGenerate();
574
636
  await scanGenerateResult(result);
@@ -579,7 +641,7 @@ function createMiddleware(firewall, options = {}) {
579
641
  doStream
580
642
  }) {
581
643
  if (scanInput) {
582
- await scanPrompt(params.prompt);
644
+ await scanPrompt(params.prompt ?? []);
583
645
  }
584
646
  const { stream, ...rest } = await doStream();
585
647
  if (!scanOutput) {
@@ -603,7 +665,7 @@ function createMiddleware(firewall, options = {}) {
603
665
  try {
604
666
  await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
605
667
  } catch (err) {
606
- if (err instanceof PromptBlockedException) {
668
+ if (err instanceof FirewallBlockedException) {
607
669
  controller.enqueue({ type: "error", error: err });
608
670
  return;
609
671
  }
@@ -691,54 +753,133 @@ function chunkText(text) {
691
753
 
692
754
  // src/firewall.ts
693
755
  init_exceptions();
694
- var BASE_THRESHOLD = 0.5;
695
- var TARGET_SEQUENCE_FPR = 0.01;
696
- var MAX_ADAPTIVE_THRESHOLD = 0.9;
756
+
757
+ // src/outcomes.ts
758
+ var Outcome = {
759
+ Benign: "benign",
760
+ InformationDisclosure: "information_disclosure",
761
+ SecretExposure: "secret_exposure",
762
+ ControlAbuse: "control_abuse",
763
+ SystemCompromise: "system_compromise",
764
+ ServiceDisruption: "service_disruption"
765
+ };
766
+ var PRIMARY_OUTCOMES = [
767
+ Outcome.Benign,
768
+ Outcome.InformationDisclosure,
769
+ Outcome.SecretExposure,
770
+ Outcome.ControlAbuse,
771
+ Outcome.SystemCompromise,
772
+ Outcome.ServiceDisruption
773
+ ];
774
+ var HARMFUL_OUTCOMES = [
775
+ Outcome.InformationDisclosure,
776
+ Outcome.SecretExposure,
777
+ Outcome.ControlAbuse,
778
+ Outcome.SystemCompromise,
779
+ Outcome.ServiceDisruption
780
+ ];
781
+ var OUTCOME_DESCRIPTIONS = {
782
+ [Outcome.Benign]: "No harmful firewall outcome detected.",
783
+ [Outcome.InformationDisclosure]: "Exposes private data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.",
784
+ [Outcome.SecretExposure]: "Exposes credentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.",
785
+ [Outcome.ControlAbuse]: "Misuses authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.",
786
+ [Outcome.SystemCompromise]: "Enables privilege escalation, account takeover, hostile integration or plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.",
787
+ [Outcome.ServiceDisruption]: "Causes downtime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence."
788
+ };
789
+ var PRIMARY_OUTCOME_SET = new Set(PRIMARY_OUTCOMES);
790
+ var HARMFUL_OUTCOME_SET = new Set(HARMFUL_OUTCOMES);
791
+ function isPrimaryOutcome(value) {
792
+ return typeof value === "string" && PRIMARY_OUTCOME_SET.has(value);
793
+ }
794
+ function isHarmfulOutcome(value) {
795
+ return typeof value === "string" && HARMFUL_OUTCOME_SET.has(value);
796
+ }
797
+ function normalizePrimaryOutcome(value, fieldName = "primary_outcome") {
798
+ if (typeof value !== "string") {
799
+ throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
800
+ }
801
+ return isPrimaryOutcome(value) ? value : value;
802
+ }
803
+ function normalizeHarmfulOutcome(value, fieldName = "outcome") {
804
+ if (typeof value !== "string" || value === Outcome.Benign) {
805
+ throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(value)}`);
806
+ }
807
+ return isHarmfulOutcome(value) ? value : value;
808
+ }
809
+ function normalizeHarmfulOutcomeMap(values, fieldName) {
810
+ if (values === void 0 || values === null) {
811
+ return void 0;
812
+ }
813
+ if (typeof values !== "object" || Array.isArray(values)) {
814
+ throw new Error(`Firewall: invalid ${fieldName} ${JSON.stringify(values)}`);
815
+ }
816
+ const out = {};
817
+ for (const [key, value] of Object.entries(values)) {
818
+ const normalizedKey = normalizeHarmfulOutcome(key, `${fieldName} key`);
819
+ if (typeof value !== "number") {
820
+ throw new Error(`Firewall: invalid ${fieldName} value for ${JSON.stringify(key)}`);
821
+ }
822
+ out[normalizedKey] = value;
823
+ }
824
+ return Object.freeze(out);
825
+ }
826
+
827
+ // src/firewall.ts
828
+ var SDK_VERSION = "0.4.2";
697
829
  var DEFAULT_TIMEOUT_MS = 1e4;
698
830
  var DEFAULT_CHUNK_CONCURRENCY = 8;
699
831
  var DEFAULT_MAX_RETRIES = 5;
700
832
  var MAX_BACKOFF_SECONDS = 30;
701
833
  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) {
834
+ function blockResultFromResponse(data) {
717
835
  const result = {
718
836
  prediction: data.prediction,
719
837
  score: Number(data.score),
720
- threshold
838
+ threshold: Number(data.threshold)
721
839
  };
722
840
  if (data.primary_outcome !== void 0) {
723
- result.primaryOutcome = data.primary_outcome;
841
+ result.primaryOutcome = normalizePrimaryOutcome(data.primary_outcome);
724
842
  }
725
843
  if (data.outcome_scores !== void 0) {
726
- result.outcomeScores = Object.freeze(
727
- Object.fromEntries(Object.entries(data.outcome_scores).map(([k, v]) => [k, Number(v)]))
728
- );
844
+ const outcomeScores = normalizeHarmfulOutcomeMap(data.outcome_scores, "outcome_scores");
845
+ if (outcomeScores !== void 0) {
846
+ result.outcomeScores = outcomeScores;
847
+ }
729
848
  }
730
849
  if (data.detector_scores !== void 0) {
731
- result.detectorScores = Object.freeze(
732
- Object.fromEntries(Object.entries(data.detector_scores).map(([k, v]) => [k, Number(v)]))
733
- );
850
+ const detectorScores = normalizeHarmfulOutcomeMap(data.detector_scores, "detector_scores");
851
+ if (detectorScores !== void 0) {
852
+ result.detectorScores = detectorScores;
853
+ }
734
854
  }
735
855
  if (data.detector_counts !== void 0) {
736
- result.detectorCounts = Object.freeze(
737
- Object.fromEntries(Object.entries(data.detector_counts).map(([k, v]) => [k, Number(v)]))
738
- );
856
+ const detectorCounts = normalizeHarmfulOutcomeMap(data.detector_counts, "detector_counts");
857
+ if (detectorCounts !== void 0) {
858
+ result.detectorCounts = detectorCounts;
859
+ }
739
860
  }
740
861
  return Object.freeze(result);
741
862
  }
863
+ function isRecord2(value) {
864
+ return typeof value === "object" && value !== null && !Array.isArray(value);
865
+ }
866
+ function withSdkMetadata(metadata, info) {
867
+ const payload = { ...metadata ?? {} };
868
+ const existing = payload.silmaril;
869
+ if (existing !== void 0 && !isRecord2(existing)) {
870
+ throw new Error("Firewall: metadata.silmaril must be an object when provided");
871
+ }
872
+ payload.silmaril = {
873
+ ...isRecord2(existing) ? existing : {},
874
+ sdk_language: "typescript",
875
+ sdk_version: SDK_VERSION,
876
+ request_id: info.requestId,
877
+ input_index: info.inputIndex,
878
+ chunk_index: info.chunkIndex,
879
+ chunk_count: info.chunkCount
880
+ };
881
+ return payload;
882
+ }
742
883
  async function readCappedErrorBody(response) {
743
884
  if (!response.body) {
744
885
  return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
@@ -835,14 +976,24 @@ var Firewall = class {
835
976
  }
836
977
  async classify(text, options = {}) {
837
978
  const chunks = chunkText(text);
838
- const threshold = adaptiveThreshold(chunks.length);
979
+ const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
839
980
  if (chunks.length === 1) {
840
- return this.classifySingleChunk(chunks[0], options, threshold);
981
+ return this.classifySingleChunk(chunks[0], options, {
982
+ requestId,
983
+ inputIndex: 0,
984
+ chunkIndex: 0,
985
+ chunkCount: 1
986
+ });
841
987
  }
842
988
  const results = await mapWithConcurrency(
843
989
  chunks,
844
990
  this.chunkConcurrency,
845
- (chunk) => this.classifySingleChunk(chunk, options, threshold)
991
+ (chunk, index) => this.classifySingleChunk(chunk, options, {
992
+ requestId,
993
+ inputIndex: 0,
994
+ chunkIndex: index,
995
+ chunkCount: chunks.length
996
+ })
846
997
  );
847
998
  return results.reduce((best, r) => r.score > best.score ? r : best);
848
999
  }
@@ -865,10 +1016,9 @@ var Firewall = class {
865
1016
  `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
866
1017
  );
867
1018
  }
868
- const threshold = adaptiveThreshold(texts.length);
1019
+ const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
869
1020
  const payload = {
870
- texts: texts.map((text) => sanitizeText(text)),
871
- threshold
1021
+ texts: texts.map((text) => sanitizeText(text))
872
1022
  };
873
1023
  if (options.hooks && options.hooks.length > 0) {
874
1024
  payload.hooks = options.hooks.map((h) => String(h));
@@ -876,14 +1026,21 @@ var Firewall = class {
876
1026
  if (options.toolNames && options.toolNames.length > 0) {
877
1027
  payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
878
1028
  }
879
- if (options.metadata && options.metadata.length > 0) {
880
- payload.metadata = options.metadata.map((m) => m === void 0 ? null : m);
881
- }
1029
+ payload.metadata = texts.map(
1030
+ (_, index) => withSdkMetadata(options.metadata?.[index], {
1031
+ requestId,
1032
+ inputIndex: index,
1033
+ chunkIndex: 0,
1034
+ chunkCount: 1
1035
+ })
1036
+ );
882
1037
  const data = await this.postWithRetry(payload);
883
- return data.predictions.map((p) => blockResultFromResponse(p, threshold));
1038
+ return data.predictions.map((p) => blockResultFromResponse(p));
884
1039
  }
885
1040
  asLangChainHandler(options = {}) {
886
- return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
1041
+ return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
1042
+ (m) => m.createLangChainHandler(this, options)
1043
+ );
887
1044
  }
888
1045
  asMiddleware(options = {}) {
889
1046
  return createMiddleware(this, options);
@@ -913,19 +1070,17 @@ var Firewall = class {
913
1070
  }
914
1071
  throw new Error("Firewall: exhausted retries (unreachable)");
915
1072
  }
916
- async classifySingleChunk(text, options, threshold) {
917
- const payload = { text, threshold };
1073
+ async classifySingleChunk(text, options, metadataInfo) {
1074
+ const payload = { text };
918
1075
  if (options.hook !== void 0) {
919
1076
  payload.hook = options.hook;
920
1077
  }
921
1078
  if (options.toolName !== void 0) {
922
1079
  payload.tool_name = options.toolName;
923
1080
  }
924
- if (options.metadata !== void 0) {
925
- payload.metadata = options.metadata;
926
- }
1081
+ payload.metadata = withSdkMetadata(options.metadata, metadataInfo);
927
1082
  const data = await this.postWithRetry(payload);
928
- return blockResultFromResponse(data, threshold);
1083
+ return blockResultFromResponse(data);
929
1084
  }
930
1085
  };
931
1086
 
@@ -944,15 +1099,25 @@ init_hooks();
944
1099
  DEFAULT_TIMEOUT_MS,
945
1100
  FIREWALL_HOOK_TO_LABEL,
946
1101
  Firewall,
1102
+ FirewallBlockedException,
947
1103
  FirewallHook,
1104
+ HARMFUL_OUTCOMES,
948
1105
  HookLabel,
949
1106
  INPUT_HOOKS,
950
1107
  MAX_INPUT_CHARS,
951
1108
  MAX_INPUT_TOKENS,
1109
+ OUTCOME_DESCRIPTIONS,
952
1110
  OUTPUT_HOOKS,
1111
+ Outcome,
1112
+ PRIMARY_OUTCOMES,
953
1113
  PromptBlockedException,
954
1114
  SilmarilApiError,
955
1115
  chunkText,
1116
+ isHarmfulOutcome,
1117
+ isPrimaryOutcome,
1118
+ normalizeHarmfulOutcome,
1119
+ normalizeHarmfulOutcomeMap,
1120
+ normalizePrimaryOutcome,
956
1121
  prependHook,
957
1122
  prependToolName,
958
1123
  resolveHooks,