@silmaril-security/sdk 0.3.0 → 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.
@@ -0,0 +1,165 @@
1
+ declare const HookLabel: {
2
+ readonly USER_INPUT: "user_input";
3
+ readonly SYSTEM_PROMPT: "system_prompt";
4
+ readonly TOOL_CALL: "tool_call";
5
+ readonly TOOL_RESPONSE: "tool_response";
6
+ readonly LLM_OUTPUT: "llm_output";
7
+ readonly UNKNOWN: "unknown";
8
+ };
9
+ type HookLabel = (typeof HookLabel)[keyof typeof HookLabel];
10
+ declare const FirewallHook: {
11
+ readonly LLM_START: "on_llm_start";
12
+ readonly CHAT_MODEL_START: "on_chat_model_start";
13
+ readonly TOOL_START: "on_tool_start";
14
+ readonly RETRIEVER_START: "on_retriever_start";
15
+ readonly LLM_END: "on_llm_end";
16
+ readonly TOOL_END: "on_tool_end";
17
+ readonly RETRIEVER_END: "on_retriever_end";
18
+ };
19
+ type FirewallHook = (typeof FirewallHook)[keyof typeof FirewallHook];
20
+ declare const DEFAULT_HOOKS: ReadonlySet<FirewallHook>;
21
+ declare const INPUT_HOOKS: ReadonlySet<FirewallHook>;
22
+ declare const OUTPUT_HOOKS: ReadonlySet<FirewallHook>;
23
+ declare const ALL_HOOKS: ReadonlySet<FirewallHook>;
24
+ declare const FIREWALL_HOOK_TO_LABEL: Readonly<Record<FirewallHook, HookLabel>>;
25
+ declare function resolveHooks(hooks: Iterable<FirewallHook | string> | undefined): ReadonlySet<FirewallHook>;
26
+ declare function prependHook(text: string, hook: HookLabel | string | undefined | null): string;
27
+ declare function prependToolName(text: string, toolName: string | undefined | null): string;
28
+
29
+ type Prediction = "BENIGN" | "MALICIOUS";
30
+ interface BlockResult {
31
+ readonly prediction: Prediction;
32
+ readonly score: number;
33
+ readonly threshold: number;
34
+ readonly primaryOutcome?: string;
35
+ readonly outcomeScores?: Readonly<Record<string, number>>;
36
+ readonly detectorScores?: Readonly<Record<string, number>>;
37
+ readonly detectorCounts?: Readonly<Record<string, number>>;
38
+ }
39
+ interface FirewallOptions {
40
+ apiKey: string;
41
+ apiUrl: string;
42
+ timeoutMs?: number;
43
+ chunkConcurrency?: number;
44
+ shadowMode?: boolean;
45
+ }
46
+ type ClassificationMetadata = Readonly<Record<string, unknown>>;
47
+ interface ClassifyOptions {
48
+ hook?: HookLabel;
49
+ toolName?: string;
50
+ metadata?: ClassificationMetadata;
51
+ requestId?: string;
52
+ }
53
+ interface ClassifyBatchOptions {
54
+ hooks?: readonly HookLabel[];
55
+ toolNames?: readonly (string | undefined)[];
56
+ metadata?: readonly (ClassificationMetadata | undefined)[];
57
+ requestId?: string;
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 LangChainFirewallHandler {
69
+ readonly name: string;
70
+ readonly raiseError: boolean;
71
+ readonly awaitHandlers: boolean;
72
+ handleChatModelStart?: (...args: unknown[]) => unknown;
73
+ handleLLMStart?: (...args: unknown[]) => unknown;
74
+ handleToolStart?: (...args: unknown[]) => unknown;
75
+ handleRetrieverStart?: (...args: unknown[]) => unknown;
76
+ handleLLMEnd?: (...args: unknown[]) => unknown;
77
+ handleToolEnd?: (...args: unknown[]) => unknown;
78
+ handleRetrieverEnd?: (...args: unknown[]) => unknown;
79
+ }
80
+ interface ClassifyEvent {
81
+ readonly hook: HookLabel;
82
+ readonly toolName?: string;
83
+ readonly toolCallId?: string;
84
+ readonly runId?: string;
85
+ readonly text: string;
86
+ readonly result: BlockResult;
87
+ readonly blocked: boolean;
88
+ readonly shadowMode: boolean;
89
+ }
90
+ interface MiddlewareOptions {
91
+ scanInput?: boolean;
92
+ scanOutput?: boolean;
93
+ scanToolCalls?: boolean;
94
+ shadowMode?: boolean;
95
+ onBlocked?: (err: Error) => void;
96
+ onClassify?: (event: ClassifyEvent) => void;
97
+ }
98
+
99
+ interface VercelContentPart {
100
+ type?: string;
101
+ text?: string;
102
+ toolCallId?: string;
103
+ toolName?: string;
104
+ args?: unknown;
105
+ output?: unknown;
106
+ result?: unknown;
107
+ }
108
+ interface VercelPromptMessage {
109
+ role: string;
110
+ content: string | ReadonlyArray<VercelContentPart | string>;
111
+ }
112
+ interface VercelToolCall {
113
+ toolCallType?: string;
114
+ toolCallId?: string;
115
+ toolName?: string;
116
+ args?: unknown;
117
+ input?: unknown;
118
+ }
119
+ interface VercelGenerateResult {
120
+ text?: string;
121
+ toolCalls?: ReadonlyArray<VercelToolCall>;
122
+ }
123
+ interface WrapGenerateArgs<TResult extends VercelGenerateResult & Record<string, unknown>> {
124
+ params: {
125
+ prompt?: ReadonlyArray<VercelPromptMessage>;
126
+ } & Record<string, unknown>;
127
+ doGenerate: () => PromiseLike<TResult>;
128
+ }
129
+ interface WrapStreamArgs<TResult extends {
130
+ stream: ReadableStream<unknown>;
131
+ } & Record<string, unknown>> {
132
+ params: {
133
+ prompt?: ReadonlyArray<VercelPromptMessage>;
134
+ } & Record<string, unknown>;
135
+ doStream: () => PromiseLike<TResult>;
136
+ }
137
+ interface FirewallMiddleware {
138
+ readonly specificationVersion: "v3";
139
+ readonly middlewareVersion: "v2";
140
+ wrapGenerate: <TResult extends VercelGenerateResult & Record<string, unknown>>(args: WrapGenerateArgs<TResult>) => Promise<TResult>;
141
+ wrapStream: <TResult extends {
142
+ stream: ReadableStream<unknown>;
143
+ } & Record<string, unknown>>(args: WrapStreamArgs<TResult>) => Promise<TResult>;
144
+ }
145
+ declare function createMiddleware(firewall: Firewall, options?: MiddlewareOptions): FirewallMiddleware;
146
+
147
+ declare const DEFAULT_TIMEOUT_MS = 10000;
148
+ declare const DEFAULT_CHUNK_CONCURRENCY = 8;
149
+ declare class Firewall {
150
+ readonly apiKey: string;
151
+ readonly apiUrl: string;
152
+ readonly timeoutMs: number;
153
+ readonly chunkConcurrency: number;
154
+ readonly shadowMode: boolean;
155
+ private readonly headers;
156
+ constructor(options: FirewallOptions);
157
+ classify(text: string, options?: ClassifyOptions): Promise<BlockResult>;
158
+ classifyBatch(texts: readonly string[], options?: ClassifyBatchOptions): Promise<BlockResult[]>;
159
+ asLangChainHandler<THandler = LangChainFirewallHandler>(options?: LangChainAdapterOptions): Promise<THandler>;
160
+ asMiddleware(options?: MiddlewareOptions): FirewallMiddleware;
161
+ private postWithRetry;
162
+ private classifySingleChunk;
163
+ }
164
+
165
+ export { ALL_HOOKS as A, type BlockResult as B, type ClassificationMetadata as C, DEFAULT_CHUNK_CONCURRENCY as D, FIREWALL_HOOK_TO_LABEL as F, HookLabel as H, INPUT_HOOKS as I, type LangChainAdapterOptions as L, type MiddlewareOptions as M, OUTPUT_HOOKS as O, type Prediction as P, type ClassifyBatchOptions as a, type ClassifyEvent as b, type ClassifyOptions as c, DEFAULT_HOOKS as d, DEFAULT_TIMEOUT_MS as e, Firewall as f, FirewallHook as g, type FirewallMiddleware as h, type FirewallOptions as i, type LangChainFirewallHandler as j, prependToolName as k, createMiddleware as l, prependHook as p, resolveHooks as r };
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,6 +435,7 @@ __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,
422
440
  HookLabel: () => HookLabel,
423
441
  INPUT_HOOKS: () => INPUT_HOOKS,
@@ -432,7 +450,10 @@ __export(index_exports, {
432
450
  resolveHooks: () => resolveHooks,
433
451
  sanitizeText: () => sanitizeText
434
452
  });
435
- module.exports = __toCommonJS(index_exports);
453
+ module.exports = __toCommonJS(src_exports);
454
+
455
+ // src/firewall.ts
456
+ var import_node_crypto = require("crypto");
436
457
 
437
458
  // src/adapters/vercel.ts
438
459
  init_exceptions();
@@ -450,6 +471,9 @@ function stringifyToolValue(value) {
450
471
  return String(value);
451
472
  }
452
473
  }
474
+ function isRecord(value) {
475
+ return typeof value === "object" && value !== null && !Array.isArray(value);
476
+ }
453
477
  function extractContentText(content) {
454
478
  if (typeof content === "string") {
455
479
  return content;
@@ -474,14 +498,29 @@ function iterateToolResultParts(message) {
474
498
  continue;
475
499
  }
476
500
  if (part.type === "tool-result") {
477
- const text = stringifyToolValue(part.result);
501
+ const text = stringifyToolResult(part.result !== void 0 ? part.result : part.output);
478
502
  if (text.trim()) {
479
- out.push({ text, toolName: part.toolName });
503
+ out.push({ text, toolName: part.toolName, toolCallId: part.toolCallId });
480
504
  }
481
505
  }
482
506
  }
483
507
  return out;
484
508
  }
509
+ function stringifyToolResult(value) {
510
+ if (!isRecord(value)) {
511
+ return stringifyToolValue(value);
512
+ }
513
+ if ((value.type === "text" || value.type === "error-text") && typeof value.value === "string") {
514
+ return value.value;
515
+ }
516
+ if ((value.type === "json" || value.type === "error-json") && value.value !== void 0) {
517
+ return stringifyToolValue(value.value);
518
+ }
519
+ if (value.type === "content" && Array.isArray(value.value)) {
520
+ return value.value.map((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(" ");
521
+ }
522
+ return stringifyToolValue(value);
523
+ }
485
524
  function findLastUserMessage(messages) {
486
525
  for (let i = messages.length - 1; i >= 0; i--) {
487
526
  const msg = messages[i];
@@ -495,33 +534,43 @@ function createMiddleware(firewall, options = {}) {
495
534
  const scanInput = options.scanInput ?? true;
496
535
  const scanOutput = options.scanOutput ?? false;
497
536
  const shadowMode = options.shadowMode ?? firewall.shadowMode;
498
- const classifyOrBlock = async (text, hook, toolName) => {
537
+ const classifyOrBlock = async (text, hook, context = { toolName: void 0, toolCallId: void 0 }) => {
499
538
  if (!text.trim()) {
500
539
  return;
501
540
  }
541
+ const { toolName, toolCallId } = context;
502
542
  const result = await firewall.classify(
503
543
  text,
504
544
  toolName !== void 0 ? { hook, toolName } : { hook }
505
545
  );
506
546
  const threshold = result.threshold;
507
547
  const blocked = result.score >= threshold;
508
- options.onClassify?.({
548
+ const commonEventFields = {
509
549
  hook,
510
550
  ...toolName !== void 0 ? { toolName } : {},
551
+ ...toolCallId !== void 0 ? { toolCallId } : {},
511
552
  text,
512
- result,
553
+ result
554
+ };
555
+ options.onClassify?.({
556
+ ...commonEventFields,
513
557
  blocked,
514
558
  shadowMode
515
559
  });
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;
560
+ if (!blocked || shadowMode) {
561
+ return;
524
562
  }
563
+ const err = new FirewallBlockedException({
564
+ score: result.score,
565
+ threshold,
566
+ promptText: text,
567
+ hook,
568
+ ...toolName !== void 0 ? { toolName } : {},
569
+ ...toolCallId !== void 0 ? { toolCallId } : {},
570
+ result
571
+ });
572
+ options.onBlocked?.(err);
573
+ throw err;
525
574
  };
526
575
  const scanPrompt = async (prompt) => {
527
576
  if (prompt.length === 0) {
@@ -530,8 +579,8 @@ function createMiddleware(firewall, options = {}) {
530
579
  const last = prompt[prompt.length - 1];
531
580
  const role = last.role.toLowerCase();
532
581
  if (role === "tool") {
533
- for (const { text: text2, toolName } of iterateToolResultParts(last)) {
534
- await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, toolName);
582
+ for (const { text: text2, toolName, toolCallId } of iterateToolResultParts(last)) {
583
+ await classifyOrBlock(text2, HookLabel.TOOL_RESPONSE, { toolName, toolCallId });
535
584
  }
536
585
  return;
537
586
  }
@@ -554,21 +603,25 @@ function createMiddleware(firewall, options = {}) {
554
603
  if (!call || typeof call !== "object") {
555
604
  continue;
556
605
  }
557
- const args = typeof call.args === "string" ? call.args : stringifyToolValue(call.args);
606
+ const toolInput = call.args !== void 0 ? call.args : call.input;
607
+ const args = typeof toolInput === "string" ? toolInput : stringifyToolValue(toolInput);
558
608
  const toolName = typeof call.toolName === "string" ? call.toolName : void 0;
609
+ const toolCallId = typeof call.toolCallId === "string" ? call.toolCallId : void 0;
559
610
  if (args.trim()) {
560
- await classifyOrBlock(args, HookLabel.TOOL_CALL, toolName);
611
+ await classifyOrBlock(args, HookLabel.TOOL_CALL, { toolName, toolCallId });
561
612
  }
562
613
  }
563
614
  }
564
615
  };
565
616
  return {
617
+ specificationVersion: "v3",
618
+ middlewareVersion: "v2",
566
619
  async wrapGenerate({
567
620
  params,
568
621
  doGenerate
569
622
  }) {
570
623
  if (scanInput) {
571
- await scanPrompt(params.prompt);
624
+ await scanPrompt(params.prompt ?? []);
572
625
  }
573
626
  const result = await doGenerate();
574
627
  await scanGenerateResult(result);
@@ -579,7 +632,7 @@ function createMiddleware(firewall, options = {}) {
579
632
  doStream
580
633
  }) {
581
634
  if (scanInput) {
582
- await scanPrompt(params.prompt);
635
+ await scanPrompt(params.prompt ?? []);
583
636
  }
584
637
  const { stream, ...rest } = await doStream();
585
638
  if (!scanOutput) {
@@ -603,7 +656,7 @@ function createMiddleware(firewall, options = {}) {
603
656
  try {
604
657
  await classifyOrBlock(buffered, HookLabel.LLM_OUTPUT);
605
658
  } catch (err) {
606
- if (err instanceof PromptBlockedException) {
659
+ if (err instanceof FirewallBlockedException) {
607
660
  controller.enqueue({ type: "error", error: err });
608
661
  return;
609
662
  }
@@ -619,7 +672,7 @@ function createMiddleware(firewall, options = {}) {
619
672
 
620
673
  // src/chunking.ts
621
674
  var CHARS_PER_TOKEN = 4;
622
- var MAX_INPUT_TOKENS = 10240;
675
+ var MAX_INPUT_TOKENS = 81920;
623
676
  var CHUNK_WINDOW = 400;
624
677
  var CHUNK_OVERLAP = 64;
625
678
  var MAX_INPUT_CHARS = MAX_INPUT_TOKENS * CHARS_PER_TOKEN;
@@ -691,33 +744,17 @@ function chunkText(text) {
691
744
 
692
745
  // src/firewall.ts
693
746
  init_exceptions();
694
- var BASE_THRESHOLD = 0.5;
695
- var TARGET_SEQUENCE_FPR = 0.01;
696
- var MAX_ADAPTIVE_THRESHOLD = 0.9;
747
+ var SDK_VERSION = "0.4.1";
697
748
  var DEFAULT_TIMEOUT_MS = 1e4;
698
749
  var DEFAULT_CHUNK_CONCURRENCY = 8;
699
750
  var DEFAULT_MAX_RETRIES = 5;
700
751
  var MAX_BACKOFF_SECONDS = 30;
701
752
  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) {
753
+ function blockResultFromResponse(data) {
717
754
  const result = {
718
755
  prediction: data.prediction,
719
756
  score: Number(data.score),
720
- threshold
757
+ threshold: Number(data.threshold)
721
758
  };
722
759
  if (data.primary_outcome !== void 0) {
723
760
  result.primaryOutcome = data.primary_outcome;
@@ -739,6 +776,26 @@ function blockResultFromResponse(data, threshold) {
739
776
  }
740
777
  return Object.freeze(result);
741
778
  }
779
+ function isRecord2(value) {
780
+ return typeof value === "object" && value !== null && !Array.isArray(value);
781
+ }
782
+ function withSdkMetadata(metadata, info) {
783
+ const payload = { ...metadata ?? {} };
784
+ const existing = payload.silmaril;
785
+ if (existing !== void 0 && !isRecord2(existing)) {
786
+ throw new Error("Firewall: metadata.silmaril must be an object when provided");
787
+ }
788
+ payload.silmaril = {
789
+ ...isRecord2(existing) ? existing : {},
790
+ sdk_language: "typescript",
791
+ sdk_version: SDK_VERSION,
792
+ request_id: info.requestId,
793
+ input_index: info.inputIndex,
794
+ chunk_index: info.chunkIndex,
795
+ chunk_count: info.chunkCount
796
+ };
797
+ return payload;
798
+ }
742
799
  async function readCappedErrorBody(response) {
743
800
  if (!response.body) {
744
801
  return response.text().then((body2) => body2.slice(0, MAX_ERROR_BODY_BYTES)).catch(() => "");
@@ -835,14 +892,24 @@ var Firewall = class {
835
892
  }
836
893
  async classify(text, options = {}) {
837
894
  const chunks = chunkText(text);
838
- const threshold = adaptiveThreshold(chunks.length);
895
+ const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
839
896
  if (chunks.length === 1) {
840
- return this.classifySingleChunk(chunks[0], options, threshold);
897
+ return this.classifySingleChunk(chunks[0], options, {
898
+ requestId,
899
+ inputIndex: 0,
900
+ chunkIndex: 0,
901
+ chunkCount: 1
902
+ });
841
903
  }
842
904
  const results = await mapWithConcurrency(
843
905
  chunks,
844
906
  this.chunkConcurrency,
845
- (chunk) => this.classifySingleChunk(chunk, options, threshold)
907
+ (chunk, index) => this.classifySingleChunk(chunk, options, {
908
+ requestId,
909
+ inputIndex: 0,
910
+ chunkIndex: index,
911
+ chunkCount: chunks.length
912
+ })
846
913
  );
847
914
  return results.reduce((best, r) => r.score > best.score ? r : best);
848
915
  }
@@ -865,10 +932,9 @@ var Firewall = class {
865
932
  `Firewall: metadata length ${options.metadata.length} does not match texts length ${texts.length}`
866
933
  );
867
934
  }
868
- const threshold = adaptiveThreshold(texts.length);
935
+ const requestId = options.requestId ?? (0, import_node_crypto.randomUUID)();
869
936
  const payload = {
870
- texts: texts.map((text) => sanitizeText(text)),
871
- threshold
937
+ texts: texts.map((text) => sanitizeText(text))
872
938
  };
873
939
  if (options.hooks && options.hooks.length > 0) {
874
940
  payload.hooks = options.hooks.map((h) => String(h));
@@ -876,14 +942,21 @@ var Firewall = class {
876
942
  if (options.toolNames && options.toolNames.length > 0) {
877
943
  payload.tool_names = options.toolNames.map((t) => t === void 0 ? null : t);
878
944
  }
879
- if (options.metadata && options.metadata.length > 0) {
880
- payload.metadata = options.metadata.map((m) => m === void 0 ? null : m);
881
- }
945
+ payload.metadata = texts.map(
946
+ (_, index) => withSdkMetadata(options.metadata?.[index], {
947
+ requestId,
948
+ inputIndex: index,
949
+ chunkIndex: 0,
950
+ chunkCount: 1
951
+ })
952
+ );
882
953
  const data = await this.postWithRetry(payload);
883
- return data.predictions.map((p) => blockResultFromResponse(p, threshold));
954
+ return data.predictions.map((p) => blockResultFromResponse(p));
884
955
  }
885
956
  asLangChainHandler(options = {}) {
886
- return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then((m) => m.createLangChainHandler(this, options));
957
+ return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
958
+ (m) => m.createLangChainHandler(this, options)
959
+ );
887
960
  }
888
961
  asMiddleware(options = {}) {
889
962
  return createMiddleware(this, options);
@@ -913,19 +986,17 @@ var Firewall = class {
913
986
  }
914
987
  throw new Error("Firewall: exhausted retries (unreachable)");
915
988
  }
916
- async classifySingleChunk(text, options, threshold) {
917
- const payload = { text, threshold };
989
+ async classifySingleChunk(text, options, metadataInfo) {
990
+ const payload = { text };
918
991
  if (options.hook !== void 0) {
919
992
  payload.hook = options.hook;
920
993
  }
921
994
  if (options.toolName !== void 0) {
922
995
  payload.tool_name = options.toolName;
923
996
  }
924
- if (options.metadata !== void 0) {
925
- payload.metadata = options.metadata;
926
- }
997
+ payload.metadata = withSdkMetadata(options.metadata, metadataInfo);
927
998
  const data = await this.postWithRetry(payload);
928
- return blockResultFromResponse(data, threshold);
999
+ return blockResultFromResponse(data);
929
1000
  }
930
1001
  };
931
1002
 
@@ -944,6 +1015,7 @@ init_hooks();
944
1015
  DEFAULT_TIMEOUT_MS,
945
1016
  FIREWALL_HOOK_TO_LABEL,
946
1017
  Firewall,
1018
+ FirewallBlockedException,
947
1019
  FirewallHook,
948
1020
  HookLabel,
949
1021
  INPUT_HOOKS,