@silmaril-security/sdk 0.3.1 → 0.4.1

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.d.ts CHANGED
@@ -1,147 +1,5 @@
1
- import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
2
-
3
- declare const HookLabel: {
4
- readonly USER_INPUT: "user_input";
5
- readonly SYSTEM_PROMPT: "system_prompt";
6
- readonly TOOL_CALL: "tool_call";
7
- readonly TOOL_RESPONSE: "tool_response";
8
- readonly LLM_OUTPUT: "llm_output";
9
- readonly UNKNOWN: "unknown";
10
- };
11
- type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];
12
- declare const FirewallHook: {
13
- readonly LLM_START: "on_llm_start";
14
- readonly CHAT_MODEL_START: "on_chat_model_start";
15
- readonly TOOL_START: "on_tool_start";
16
- readonly RETRIEVER_START: "on_retriever_start";
17
- readonly LLM_END: "on_llm_end";
18
- readonly TOOL_END: "on_tool_end";
19
- readonly RETRIEVER_END: "on_retriever_end";
20
- };
21
- type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];
22
- declare const DEFAULT_HOOKS: ReadonlySet<FirewallHook>;
23
- declare const INPUT_HOOKS: ReadonlySet<FirewallHook>;
24
- declare const OUTPUT_HOOKS: ReadonlySet<FirewallHook>;
25
- declare const ALL_HOOKS: ReadonlySet<FirewallHook>;
26
- declare const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>>;
27
- declare function resolveHooks(hooks: Iterable<FirewallHook | string> | undefined): ReadonlySet<FirewallHook>;
28
- declare function prependHook(text: string, hook: HookLabel | string | undefined | null): string;
29
- declare function prependToolName(text: string, toolName: string | undefined | null): string;
30
-
31
- type Prediction = "BENIGN" | "MALICIOUS";
32
- interface BlockResult {
33
- readonly prediction: Prediction;
34
- readonly score: number;
35
- readonly threshold: number;
36
- readonly primaryOutcome?: string;
37
- readonly outcomeScores?: Readonly<Record<string, number>>;
38
- readonly detectorScores?: Readonly<Record<string, number>>;
39
- readonly detectorCounts?: Readonly<Record<string, number>>;
40
- }
41
- interface FirewallOptions {
42
- apiKey: string;
43
- apiUrl: string;
44
- timeoutMs?: number;
45
- chunkConcurrency?: number;
46
- shadowMode?: boolean;
47
- }
48
- type ClassificationMetadata = Readonly<Record<string, unknown>>;
49
- interface ClassifyOptions {
50
- hook?: HookLabel;
51
- toolName?: string;
52
- metadata?: ClassificationMetadata;
53
- }
54
- interface ClassifyBatchOptions {
55
- hooks?: readonly HookLabel[];
56
- toolNames?: readonly (string | undefined)[];
57
- metadata?: readonly (ClassificationMetadata | undefined)[];
58
- }
59
- interface LangChainAdapterOptions {
60
- hooks?: ReadonlySet<FirewallHook>;
61
- includeSystem?: boolean;
62
- includeTool?: boolean;
63
- failOpen?: boolean;
64
- logger?: (message: string, error: unknown) => void;
65
- shadowMode?: boolean;
66
- onClassify?: (event: ClassifyEvent) => void;
67
- }
68
- interface ClassifyEvent {
69
- readonly hook: HookLabel;
70
- readonly toolName?: string;
71
- readonly text: string;
72
- readonly result: BlockResult;
73
- readonly blocked: boolean;
74
- readonly shadowMode: boolean;
75
- }
76
- interface MiddlewareOptions {
77
- scanInput?: boolean;
78
- scanOutput?: boolean;
79
- scanToolCalls?: boolean;
80
- shadowMode?: boolean;
81
- onBlocked?: (err: Error) => void;
82
- onClassify?: (event: ClassifyEvent) => void;
83
- }
84
-
85
- interface VercelContentPart {
86
- type?: string;
87
- text?: string;
88
- toolCallId?: string;
89
- toolName?: string;
90
- args?: unknown;
91
- result?: unknown;
92
- }
93
- interface VercelPromptMessage {
94
- role: string;
95
- content: string | ReadonlyArray<VercelContentPart | string>;
96
- }
97
- interface VercelToolCall {
98
- toolCallType?: string;
99
- toolCallId?: string;
100
- toolName?: string;
101
- args?: unknown;
102
- }
103
- interface VercelGenerateResult {
104
- text?: string;
105
- toolCalls?: ReadonlyArray<VercelToolCall>;
106
- }
107
- interface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {
108
- params: {
109
- prompt: ReadonlyArray<VercelPromptMessage>;
110
- } & Record<string, unknown>;
111
- doGenerate: () => PromiseLike<TResult>;
112
- }
113
- interface WrapStreamArgs<TResult extends {
114
- stream: ReadableStream<unknown>;
115
- } & Record<string, unknown>> {
116
- params: {
117
- prompt: ReadonlyArray<VercelPromptMessage>;
118
- } & Record<string, unknown>;
119
- doStream: () => PromiseLike<TResult>;
120
- }
121
- interface FirewallMiddleware {
122
- wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(args: WrapGenerateArgs<TResult>) => Promise<TResult>;
123
- wrapStream: <TResult extends {
124
- stream: ReadableStream<unknown>;
125
- } & Record<string, unknown>>(args: WrapStreamArgs<TResult>) => Promise<TResult>;
126
- }
127
-
128
- declare const DEFAULT_TIMEOUT_MS = 10000;
129
- declare const DEFAULT_CHUNK_CONCURRENCY = 8;
130
- declare class Firewall {
131
- readonly apiKey: string;
132
- readonly apiUrl: string;
133
- readonly timeoutMs: number;
134
- readonly chunkConcurrency: number;
135
- readonly shadowMode: boolean;
136
- private readonly headers;
137
- constructor(options: FirewallOptions);
138
- classify(text: string, options?: ClassifyOptions): Promise<BlockResult>;
139
- classifyBatch(texts: readonly string[], options?: ClassifyBatchOptions): Promise<BlockResult[]>;
140
- asLangChainHandler(options?: LangChainAdapterOptions): Promise<BaseCallbackHandler>;
141
- asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
142
- private postWithRetry;
143
- private classifySingleChunk;
144
- }
1
+ import { H as HookLabel, B as BlockResult } from './firewall-CEV5hItA.js';
2
+ export { A as ALL_HOOKS, C as ClassificationMetadata, a as ClassifyBatchOptions, b as ClassifyEvent, c as ClassifyOptions, D as DEFAULT_CHUNK_CONCURRENCY, d as DEFAULT_HOOKS, e as DEFAULT_TIMEOUT_MS, F as FIREWALL_HOOK_TO_LABEL, f as Firewall, g as FirewallHook, h as FirewallMiddleware, i as FirewallOptions, I as INPUT_HOOKS, L as LangChainAdapterOptions, j as LangChainFirewallHandler, M as MiddlewareOptions, O as OUTPUT_HOOKS, P as Prediction, p as prependHook, k as prependToolName, r as resolveHooks } from './firewall-CEV5hItA.js';
145
3
 
146
4
  interface MalformedInputDetails {
147
5
  field?: string;
@@ -151,19 +9,31 @@ interface MalformedInputDetails {
151
9
  codePoint?: string;
152
10
  reason?: string;
153
11
  }
154
- declare class PromptBlockedException extends Error {
12
+ declare class FirewallBlockedException extends Error {
155
13
  readonly score: number;
156
14
  readonly threshold: number;
157
15
  readonly promptText: string;
158
16
  readonly runId: string | undefined;
17
+ readonly hook: HookLabel | undefined;
18
+ readonly toolName: string | undefined;
19
+ readonly toolCallId: string | undefined;
20
+ readonly result: BlockResult | undefined;
159
21
  constructor(params: {
160
22
  score: number;
161
23
  threshold: number;
162
24
  promptText: string;
163
25
  runId?: string;
26
+ hook?: HookLabel;
27
+ toolName?: string;
28
+ toolCallId?: string;
29
+ result?: BlockResult;
164
30
  });
165
31
  private static formatMessage;
166
32
  }
33
+ /** @deprecated Use FirewallBlockedException instead. */
34
+ declare const PromptBlockedException: typeof FirewallBlockedException;
35
+ /** @deprecated Use FirewallBlockedException instead. */
36
+ type PromptBlockedException = FirewallBlockedException;
167
37
  declare class SilmarilApiError extends Error {
168
38
  readonly status: number;
169
39
  readonly statusText: string;
@@ -190,4 +60,4 @@ declare const CHUNK_OVERLAP_CHARS: number;
190
60
  declare function sanitizeText(text: string): string;
191
61
  declare function chunkText(text: string): string[];
192
62
 
193
- export { ALL_HOOKS, type BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, type ClassificationMetadata, type ClassifyBatchOptions, type ClassifyEvent, type ClassifyOptions, DEFAULT_CHUNK_CONCURRENCY, DEFAULT_HOOKS, DEFAULT_TIMEOUT_MS, FIREWALL_HOOK_TO_LABEL, Firewall, FirewallHook, type FirewallMiddleware, type FirewallOptions, HookLabel, INPUT_HOOKS, type LangChainAdapterOptions, MAX_INPUT_CHARS, MAX_INPUT_TOKENS, type MalformedInputDetails, type MiddlewareOptions, OUTPUT_HOOKS, type Prediction, PromptBlockedException, SilmarilApiError, chunkText, prependHook, prependToolName, resolveHooks, sanitizeText };
63
+ export { BlockResult, CHUNK_OVERLAP, CHUNK_OVERLAP_CHARS, CHUNK_WINDOW, CHUNK_WINDOW_CHARS, FirewallBlockedException, HookLabel, MAX_INPUT_CHARS, MAX_INPUT_TOKENS, type MalformedInputDetails, PromptBlockedException, SilmarilApiError, chunkText, sanitizeText };
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
- PromptBlockedException = class _PromptBlockedException extends Error {
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(_PromptBlockedException.formatMessage(params));
40
- this.name = "PromptBlockedException";
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
- Object.setPrototypeOf(this, _PromptBlockedException.prototype);
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 `Prompt blocked by Silmaril Firewall (score=${params.score.toFixed(4)}, threshold=${params.threshold.toFixed(4)}): '${truncated}'`;
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 = (hookLabel, text, result, blocked, toolName) => {
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
- fireOnClassify(hookLabel, text, result, blocked, toolName);
276
- if (blocked && !shadowMode) {
277
- throw new PromptBlockedException({
278
- score: result.score,
279
- threshold,
280
- promptText: text,
281
- runId
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 = stringifyToolValue(part.result);
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
- options.onClassify?.({
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 && !shadowMode) {
466
- const err = new PromptBlockedException({
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 args = typeof call.args === "string" ? call.args : stringifyToolValue(call.args);
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 PromptBlockedException) {
607
+ if (err instanceof FirewallBlockedException) {
556
608
  controller.enqueue({ type: "error", error: err });
557
609
  return;
558
610
  }
@@ -640,33 +692,17 @@ function chunkText(text) {
640
692
 
641
693
  // src/firewall.ts
642
694
  init_exceptions();
643
- var BASE_THRESHOLD = 0.5;
644
- var TARGET_SEQUENCE_FPR = 0.01;
645
- var MAX_ADAPTIVE_THRESHOLD = 0.9;
695
+ var SDK_VERSION = "0.4.1";
646
696
  var DEFAULT_TIMEOUT_MS = 1e4;
647
697
  var DEFAULT_CHUNK_CONCURRENCY = 8;
648
698
  var DEFAULT_MAX_RETRIES = 5;
649
699
  var MAX_BACKOFF_SECONDS = 30;
650
700
  var MAX_ERROR_BODY_BYTES = 1 << 16;
651
- function adaptiveThreshold(scoringOpportunityCount) {
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) {
701
+ function blockResultFromResponse(data) {
666
702
  const result = {
667
703
  prediction: data.prediction,
668
704
  score: Number(data.score),
669
- threshold
705
+ threshold: Number(data.threshold)
670
706
  };
671
707
  if (data.primary_outcome !== void 0) {
672
708
  result.primaryOutcome = data.primary_outcome;
@@ -688,6 +724,26 @@ function blockResultFromResponse(data, threshold) {
688
724
  }
689
725
  return Object.freeze(result);
690
726
  }
727
+ function isRecord2(value) {
728
+ return typeof value === "object" && value !== null && !Array.isArray(value);
729
+ }
730
+ function withSdkMetadata(metadata, info) {
731
+ const payload = { ...metadata ?? {} };
732
+ const existing = payload.silmaril;
733
+ if (existing !== void 0 && !isRecord2(existing)) {
734
+ throw new Error("Firewall: metadata.silmaril must be an object when provided");
735
+ }
736
+ payload.silmaril = {
737
+ ...isRecord2(existing) ? existing : {},
738
+ sdk_language: "typescript",
739
+ sdk_version: SDK_VERSION,
740
+ request_id: info.requestId,
741
+ input_index: info.inputIndex,
742
+ chunk_index: info.chunkIndex,
743
+ chunk_count: info.chunkCount
744
+ };
745
+ return payload;
746
+ }
691
747
  async function readCappedErrorBody(response) {
692
748
  if (!response.body) {
693
749
  return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
@@ -784,14 +840,24 @@ var Firewall = class {
784
840
  }
785
841
  async classify(text, options = {}) {
786
842
  const chunks = chunkText(text);
787
- const threshold = adaptiveThreshold(chunks.length);
843
+ const requestId = options.requestId ?? randomUUID();
788
844
  if (chunks.length === 1) {
789
- return this.classifySingleChunk(chunks[0], options, threshold);
845
+ return this.classifySingleChunk(chunks[0], options, {
846
+ requestId,
847
+ inputIndex: 0,
848
+ chunkIndex: 0,
849
+ chunkCount: 1
850
+ });
790
851
  }
791
852
  const results = await mapWithConcurrency(
792
853
  chunks,
793
854
  this.chunkConcurrency,
794
- (chunk) => this.classifySingleChunk(chunk, options, threshold)
855
+ (chunk, index) => this.classifySingleChunk(chunk, options, {
856
+ requestId,
857
+ inputIndex: 0,
858
+ chunkIndex: index,
859
+ chunkCount: chunks.length
860
+ })
795
861
  );
796
862
  return results.reduce((best, r) => r.score > best.score ? r : best);
797
863
  }
@@ -814,10 +880,9 @@ var Firewall = class {
814
880
  `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
815
881
  );
816
882
  }
817
- const threshold = adaptiveThreshold(texts.length);
883
+ const requestId = options.requestId ?? randomUUID();
818
884
  const payload = {
819
- texts: texts.map((text) => sanitizeText(text)),
820
- threshold
885
+ texts: texts.map((text) => sanitizeText(text))
821
886
  };
822
887
  if (options.hooks && options.hooks.length > 0) {
823
888
  payload.hooks = options.hooks.map((h) => String(h));
@@ -825,14 +890,21 @@ var Firewall = class {
825
890
  if (options.toolNames && options.toolNames.length > 0) {
826
891
  payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
827
892
  }
828
- if (options.metadata && options.metadata.length > 0) {
829
- payload.metadata = options.metadata.map((m) => m === void 0 ? null : m);
830
- }
893
+ payload.metadata = texts.map(
894
+ (_, index) => withSdkMetadata(options.metadata?.[index], {
895
+ requestId,
896
+ inputIndex: index,
897
+ chunkIndex: 0,
898
+ chunkCount: 1
899
+ })
900
+ );
831
901
  const data = await this.postWithRetry(payload);
832
- return data.predictions.map((p) => blockResultFromResponse(p, threshold));
902
+ return data.predictions.map((p) => blockResultFromResponse(p));
833
903
  }
834
904
  asLangChainHandler(options = {}) {
835
- return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
905
+ return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
906
+ (m) => m.createLangChainHandler(this, options)
907
+ );
836
908
  }
837
909
  asMiddleware(options = {}) {
838
910
  return createMiddleware(this, options);
@@ -862,19 +934,17 @@ var Firewall = class {
862
934
  }
863
935
  throw new Error("Firewall: exhausted retries (unreachable)");
864
936
  }
865
- async classifySingleChunk(text, options, threshold) {
866
- const payload = { text, threshold };
937
+ async classifySingleChunk(text, options, metadataInfo) {
938
+ const payload = { text };
867
939
  if (options.hook !== void 0) {
868
940
  payload.hook = options.hook;
869
941
  }
870
942
  if (options.toolName !== void 0) {
871
943
  payload.tool_name = options.toolName;
872
944
  }
873
- if (options.metadata !== void 0) {
874
- payload.metadata = options.metadata;
875
- }
945
+ payload.metadata = withSdkMetadata(options.metadata, metadataInfo);
876
946
  const data = await this.postWithRetry(payload);
877
- return blockResultFromResponse(data, threshold);
947
+ return blockResultFromResponse(data);
878
948
  }
879
949
  };
880
950
 
@@ -892,6 +962,7 @@ export {
892
962
  DEFAULT_TIMEOUT_MS,
893
963
  FIREWALL_HOOK_TO_LABEL,
894
964
  Firewall,
965
+ FirewallBlockedException,
895
966
  FirewallHook,
896
967
  HookLabel,
897
968
  INPUT_HOOKS,