@silmaril-security/sdk 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -278,10 +278,90 @@ var init_extract = __esm({
278
278
  }
279
279
  });
280
280
 
281
+ // src/adapters/thread-identity.ts
282
+ function record(value) {
283
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
284
+ }
285
+ function validThreadId(value) {
286
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
287
+ }
288
+ function nativeThreadId(runtime, metadata, inherited, currentConfig) {
289
+ const explicit = record(record(runtime)?.configurable);
290
+ const current = record(record(currentConfig)?.configurable);
291
+ for (const candidate of [explicit, current, record(metadata)]) {
292
+ if (candidate && Object.prototype.hasOwnProperty.call(candidate, "thread_id")) {
293
+ return validThreadId(candidate.thread_id);
294
+ }
295
+ }
296
+ return inherited;
297
+ }
298
+ function rejectConversationOptions(options) {
299
+ if ("conversationId" in options || "conversation_id" in options) {
300
+ throw new TypeError("conversationId/conversation_id was removed. Pass the native configurable.thread_id when invoking the agent or runnable instead.");
301
+ }
302
+ }
303
+ var RunThreadIdentity;
304
+ var init_thread_identity = __esm({
305
+ "src/adapters/thread-identity.ts"() {
306
+ "use strict";
307
+ RunThreadIdentity = class {
308
+ constructor(boundIdentity, currentConfig = () => void 0) {
309
+ this.boundIdentity = boundIdentity;
310
+ this.currentConfig = currentConfig;
311
+ }
312
+ boundIdentity;
313
+ currentConfig;
314
+ runs = /* @__PURE__ */ new Map();
315
+ children = /* @__PURE__ */ new Map();
316
+ start(runId, parentRunId, metadata) {
317
+ const discarded = this.finish(runId);
318
+ if (this.runs.size >= 4096) {
319
+ const oldestRoot = [...this.runs].find(([, run]) => run.parentRunId === void 0 || !this.runs.has(run.parentRunId))?.[0] ?? this.runs.keys().next().value;
320
+ if (oldestRoot !== void 0) discarded.push(...this.finish(oldestRoot));
321
+ }
322
+ const inherited = parentRunId === void 0 ? void 0 : this.runs.get(parentRunId)?.threadId;
323
+ const exposed = record(metadata);
324
+ const threadId = this.boundIdentity && (parentRunId === void 0 || !this.runs.has(parentRunId)) ? this.boundIdentity.threadId : exposed && Object.prototype.hasOwnProperty.call(exposed, "thread_id") ? typeof exposed.langgraph_node === "string" ? nativeThreadId(void 0, exposed, inherited, this.currentConfig()) : validThreadId(exposed.thread_id) : inherited;
325
+ this.runs.set(runId, { threadId, parentRunId });
326
+ if (parentRunId !== void 0 && parentRunId !== runId) {
327
+ const children = this.children.get(parentRunId) ?? /* @__PURE__ */ new Set();
328
+ children.add(runId);
329
+ this.children.set(parentRunId, children);
330
+ }
331
+ return discarded;
332
+ }
333
+ get(runId) {
334
+ const run = this.runs.get(runId);
335
+ return run ? run.threadId : this.boundIdentity?.threadId;
336
+ }
337
+ finish(runId) {
338
+ const pending = [runId];
339
+ const seen = /* @__PURE__ */ new Set();
340
+ while (pending.length) {
341
+ const id = pending.pop();
342
+ if (seen.has(id)) continue;
343
+ seen.add(id);
344
+ pending.push(...this.children.get(id) ?? []);
345
+ this.children.delete(id);
346
+ const parent = this.runs.get(id)?.parentRunId;
347
+ this.runs.delete(id);
348
+ if (parent !== void 0) {
349
+ const siblings = this.children.get(parent);
350
+ siblings?.delete(id);
351
+ if (siblings?.size === 0) this.children.delete(parent);
352
+ }
353
+ }
354
+ return [...seen];
355
+ }
356
+ };
357
+ }
358
+ });
359
+
281
360
  // src/adapters/langchain.ts
282
361
  var langchain_exports = {};
283
362
  __export(langchain_exports, {
284
- createLangChainHandler: () => createLangChainHandler
363
+ createLangChainHandler: () => createLangChainHandler,
364
+ protectLangChainRunnable: () => protectLangChainRunnable
285
365
  });
286
366
  function getMessageRole(message) {
287
367
  if (typeof message.role === "string") {
@@ -290,7 +370,8 @@ function getMessageRole(message) {
290
370
  if (typeof message.type === "string") {
291
371
  return message.type.toLowerCase();
292
372
  }
293
- return "";
373
+ const legacyType = message.getType?.() ?? message._getType?.();
374
+ return typeof legacyType === "string" ? legacyType.toLowerCase() : "";
294
375
  }
295
376
  function extractMessageText(content) {
296
377
  if (content === void 0) {
@@ -319,7 +400,16 @@ function findLastUserMessage2(messages) {
319
400
  return void 0;
320
401
  }
321
402
  async function createLangChainHandler(firewall, options = {}) {
322
- const { BaseCallbackHandler } = await import("@langchain/core/callbacks/base");
403
+ return buildLangChainHandler(firewall, options);
404
+ }
405
+ async function buildLangChainHandler(firewall, options, boundIdentity) {
406
+ rejectConversationOptions(options);
407
+ const { ensureConfig } = await import("@langchain/core/runnables");
408
+ const threads = new RunThreadIdentity(boundIdentity, () => ensureConfig());
409
+ const { BaseTracer } = await import("@langchain/core/tracers/base");
410
+ let finishRun = (runId) => {
411
+ threads.finish(runId);
412
+ };
323
413
  const enabledHooks = resolveHooks(options.hooks);
324
414
  const includeTool = options.includeTool ?? true;
325
415
  const failOpen = options.failOpen ?? true;
@@ -338,14 +428,14 @@ async function createLangChainHandler(firewall, options = {}) {
338
428
  logger("onClassify callback threw", err);
339
429
  }
340
430
  };
341
- const classify = async (text, hookLabel, runId, toolName) => {
431
+ const classifyEvent = async (text, hookLabel, runId, toolName) => {
342
432
  let result;
343
433
  try {
344
434
  result = await firewall.classify(text, {
345
435
  hook: hookLabel,
346
436
  metadata: {
347
437
  langgraph: { run_id: runId },
348
- ...options.conversationId === void 0 ? {} : { conversationId: options.conversationId }
438
+ ...threads.get(runId) === void 0 ? {} : { conversationId: threads.get(runId) }
349
439
  },
350
440
  ...requestedMode !== void 0 ? { mode: requestedMode } : {},
351
441
  ...toolName !== void 0 ? { toolName } : {}
@@ -389,97 +479,325 @@ async function createLangChainHandler(firewall, options = {}) {
389
479
  result
390
480
  });
391
481
  };
392
- class SilmarilFirewallHandler extends BaseCallbackHandler {
482
+ const classify = async (text, hook, runId, toolName) => {
483
+ try {
484
+ await classifyEvent(text, hook, runId, toolName);
485
+ } catch (error) {
486
+ finishRun(runId);
487
+ throw error;
488
+ }
489
+ };
490
+ function serialized(value, name) {
491
+ if (value && typeof value === "object" && "id" in value && Array.isArray(value.id)) return value;
492
+ return { lc: 1, type: "not_implemented", id: [name] };
493
+ }
494
+ class SilmarilFirewallHandler extends BaseTracer {
393
495
  name = "silmaril_firewall_handler";
394
496
  raiseError = true;
395
497
  awaitHandlers = true;
396
- async handleChatModelStart(_llm, messages, runId) {
397
- if (!enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {
398
- return;
399
- }
400
- const batches = messages;
401
- const flat = [];
402
- for (const batch of batches) {
403
- for (const m of batch) {
404
- flat.push(m);
405
- }
498
+ // LangChain clears runnable context inside ordinary callbacks. Its synchronous
499
+ // tracer pre-start hooks capture native config before that boundary. Runs are
500
+ // memory-only and every terminal path discards both native tracer maps.
501
+ async persistRun() {
502
+ }
503
+ getRunById(runId) {
504
+ return runId === void 0 ? void 0 : this.runMap.get(runId);
505
+ }
506
+ // Only lifecycle identity is needed. Do not retain prompts, outputs, token
507
+ // events, or completed children when upstream omits a cancellation callback.
508
+ _addRunToRunMap(run) {
509
+ return super._addRunToRunMap({
510
+ ...run,
511
+ inputs: {},
512
+ extra: {},
513
+ serialized: {},
514
+ tags: [],
515
+ child_runs: [],
516
+ events: []
517
+ });
518
+ }
519
+ _addChildRun() {
520
+ }
521
+ async _endTrace(run) {
522
+ try {
523
+ await super._endTrace(run);
524
+ } finally {
525
+ run.inputs = {};
526
+ delete run.outputs;
527
+ delete run.error;
528
+ run.extra = {};
529
+ run.events = [];
530
+ run.child_runs = [];
406
531
  }
407
- const lastUser = findLastUserMessage2(flat);
408
- if (!lastUser) {
409
- return;
532
+ }
533
+ async handleText() {
534
+ }
535
+ async handleAgentAction() {
536
+ }
537
+ async handleAgentEnd() {
538
+ }
539
+ async handleLLMNewToken(_token, _idx, runId) {
540
+ return this.getRunById(runId) ?? this._createRunForLLMStart(serialized(void 0, "llm"), [], runId);
541
+ }
542
+ capture(runId, parentRunId, metadata) {
543
+ for (const id of threads.start(runId, parentRunId, metadata)) {
544
+ this.runMap.delete(id);
545
+ this.runTreeMap?.delete(id);
410
546
  }
411
- const text = extractMessageText(lastUser.content).trim();
412
- if (!text) {
413
- return;
547
+ }
548
+ finishAll() {
549
+ for (const id of [...this.runMap.keys()]) this.finishRun(id);
550
+ this.runTreeMap?.clear();
551
+ }
552
+ finishRun(runId) {
553
+ for (const id of threads.finish(runId)) {
554
+ this.runMap.delete(id);
555
+ this.runTreeMap?.delete(id);
414
556
  }
415
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);
416
557
  }
417
- async handleLLMStart(_llm, prompts, runId) {
418
- if (!enabledHooks.has(FirewallHook.LLM_START)) {
419
- return;
558
+ _createRunForLLMStart(...args) {
559
+ this.capture(args[2], args[3], args[6]);
560
+ return super._createRunForLLMStart(...args);
561
+ }
562
+ _createRunForChatModelStart(...args) {
563
+ this.capture(args[2], args[3], args[6]);
564
+ return super._createRunForChatModelStart(...args);
565
+ }
566
+ _createRunForChainStart(...args) {
567
+ this.capture(args[2], args[3], args[5]);
568
+ return super._createRunForChainStart(...args);
569
+ }
570
+ _createRunForToolStart(...args) {
571
+ this.capture(args[2], args[3], args[5]);
572
+ return super._createRunForToolStart(...args);
573
+ }
574
+ _createRunForRetrieverStart(...args) {
575
+ this.capture(args[2], args[3], args[5]);
576
+ return super._createRunForRetrieverStart(...args);
577
+ }
578
+ async handleChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata) {
579
+ const batches = messages;
580
+ const run = this.getRunById(runId) ?? this._createRunForChatModelStart(
581
+ serialized(llm, "chat_model"),
582
+ messages,
583
+ runId,
584
+ parentRunId,
585
+ extraParams,
586
+ tags,
587
+ metadata
588
+ );
589
+ if (enabledHooks.has(FirewallHook.CHAT_MODEL_START)) {
590
+ const lastUser = findLastUserMessage2(batches.flat());
591
+ const text = lastUser ? extractMessageText(lastUser.content).trim() : "";
592
+ if (text) await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.CHAT_MODEL_START], runId);
593
+ }
594
+ return run;
595
+ }
596
+ async handleLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata) {
597
+ const run = this.getRunById(runId) ?? this._createRunForLLMStart(
598
+ serialized(llm, "llm"),
599
+ prompts,
600
+ runId,
601
+ parentRunId,
602
+ extraParams,
603
+ tags,
604
+ metadata
605
+ );
606
+ if (enabledHooks.has(FirewallHook.LLM_START)) {
607
+ const text = extractTextFromPrompts(prompts);
608
+ if (text) await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);
609
+ }
610
+ return run;
611
+ }
612
+ async handleToolStart(tool, inputStr, runId, parentRunId, tags, metadata) {
613
+ const run = this.getRunById(runId) ?? this._createRunForToolStart(
614
+ serialized(tool, tool?.name ?? "tool"),
615
+ inputStr,
616
+ runId,
617
+ parentRunId,
618
+ tags,
619
+ metadata
620
+ );
621
+ if (includeTool && enabledHooks.has(FirewallHook.TOOL_START)) {
622
+ const text = extractTextFromToolInput(inputStr);
623
+ if (text) await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, tool?.name);
420
624
  }
421
- const text = extractTextFromPrompts(prompts);
422
- if (!text) {
423
- return;
625
+ return run;
626
+ }
627
+ async handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata) {
628
+ const run = this.getRunById(runId) ?? this._createRunForRetrieverStart(
629
+ serialized(retriever, "retriever"),
630
+ query,
631
+ runId,
632
+ parentRunId,
633
+ tags,
634
+ metadata
635
+ );
636
+ if (enabledHooks.has(FirewallHook.RETRIEVER_START) && query.trim()) {
637
+ await classify(query.trim(), FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);
424
638
  }
425
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_START], runId);
639
+ return run;
426
640
  }
427
- async handleToolStart(tool, inputStr, runId) {
428
- if (!includeTool || !enabledHooks.has(FirewallHook.TOOL_START)) {
429
- return;
641
+ async handleLLMEnd(output, runId) {
642
+ try {
643
+ if (!this.getRunById(runId)) this._createRunForLLMStart(serialized(void 0, "llm"), [], runId);
644
+ if (enabledHooks.has(FirewallHook.LLM_END)) {
645
+ const text = extractTextFromLLMResult(output);
646
+ if (text) await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);
647
+ }
648
+ return await super.handleLLMEnd(output, runId);
649
+ } finally {
650
+ finishRun(runId);
430
651
  }
431
- const text = extractTextFromToolInput(inputStr);
432
- if (!text) {
433
- return;
652
+ }
653
+ async handleToolEnd(output, runId, _parentRunId, _tags, kwargs) {
654
+ try {
655
+ if (!this.getRunById(runId)) this._createRunForToolStart(serialized(void 0, "tool"), "", runId);
656
+ if (includeTool && enabledHooks.has(FirewallHook.TOOL_END)) {
657
+ const text = String(output).trim();
658
+ if (text) await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, kwargs?.name);
659
+ }
660
+ return await super.handleToolEnd(output, runId);
661
+ } finally {
662
+ finishRun(runId);
434
663
  }
435
- const toolName = tool?.name;
436
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_START], runId, toolName);
437
664
  }
438
- async handleRetrieverStart(_retriever, query, runId) {
439
- if (!enabledHooks.has(FirewallHook.RETRIEVER_START)) {
440
- return;
665
+ async handleRetrieverEnd(documents, runId) {
666
+ try {
667
+ if (!this.getRunById(runId)) this._createRunForRetrieverStart(serialized(void 0, "retriever"), "", runId);
668
+ if (enabledHooks.has(FirewallHook.RETRIEVER_END)) {
669
+ const text = extractTextFromDocuments(documents);
670
+ if (text) await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);
671
+ }
672
+ return await super.handleRetrieverEnd(documents, runId);
673
+ } finally {
674
+ finishRun(runId);
441
675
  }
442
- const text = query.trim();
443
- if (!text) {
444
- return;
676
+ }
677
+ async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name) {
678
+ return super.handleChainStart(serialized(chain, "chain"), inputs, runId, parentRunId, tags, metadata, runType, name);
679
+ }
680
+ async handleChainEnd(...args) {
681
+ if (!this.getRunById(args[1])) this._createRunForChainStart(serialized(void 0, "chain"), {}, args[1]);
682
+ try {
683
+ return await super.handleChainEnd(...args);
684
+ } finally {
685
+ finishRun(args[1]);
445
686
  }
446
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_START], runId);
447
687
  }
448
- async handleLLMEnd(output, runId) {
449
- if (!enabledHooks.has(FirewallHook.LLM_END)) {
450
- return;
688
+ async handleChainError(...args) {
689
+ if (!this.getRunById(args[1])) this._createRunForChainStart(serialized(void 0, "chain"), {}, args[1]);
690
+ try {
691
+ return await super.handleChainError(...args);
692
+ } finally {
693
+ finishRun(args[1]);
451
694
  }
452
- const text = extractTextFromLLMResult(output);
453
- if (!text) {
454
- return;
695
+ }
696
+ async handleLLMError(...args) {
697
+ if (!this.getRunById(args[1])) this._createRunForLLMStart(serialized(void 0, "llm"), [], args[1]);
698
+ try {
699
+ return await super.handleLLMError(...args);
700
+ } finally {
701
+ finishRun(args[1]);
455
702
  }
456
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.LLM_END], runId);
457
703
  }
458
- async handleToolEnd(output, runId, _parentRunId, _tags, _kwargs) {
459
- if (!includeTool || !enabledHooks.has(FirewallHook.TOOL_END)) {
460
- return;
704
+ async handleToolError(...args) {
705
+ if (!this.getRunById(args[1])) this._createRunForToolStart(serialized(void 0, "tool"), "", args[1]);
706
+ try {
707
+ return await super.handleToolError(...args);
708
+ } finally {
709
+ finishRun(args[1]);
461
710
  }
462
- const text = String(output).trim();
463
- if (!text) {
464
- return;
711
+ }
712
+ async handleRetrieverError(...args) {
713
+ if (!this.getRunById(args[1])) this._createRunForRetrieverStart(serialized(void 0, "retriever"), "", args[1]);
714
+ try {
715
+ return await super.handleRetrieverError(...args);
716
+ } finally {
717
+ finishRun(args[1]);
465
718
  }
466
- const toolName = _kwargs?.name;
467
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.TOOL_END], runId, toolName);
468
719
  }
469
- async handleRetrieverEnd(documents, runId) {
470
- if (!enabledHooks.has(FirewallHook.RETRIEVER_END)) {
471
- return;
720
+ }
721
+ const handler = new SilmarilFirewallHandler();
722
+ finishRun = (runId) => {
723
+ handler.finishRun(runId);
724
+ };
725
+ handlerCleanups.set(handler, () => handler.finishAll());
726
+ return handler;
727
+ }
728
+ async function protectLangChainRunnable(firewall, runnable, options = {}) {
729
+ rejectConversationOptions(options);
730
+ const { RunnableBinding, RunnableRetry, ensureConfig, mergeConfigs } = await import("@langchain/core/runnables");
731
+ const { IterableReadableStream } = await import("@langchain/core/utils/stream");
732
+ const configFactories = [async (config) => {
733
+ const effective = ensureConfig(config);
734
+ const handler = await buildLangChainHandler(firewall, options, {
735
+ threadId: validThreadId(effective.configurable?.thread_id)
736
+ });
737
+ return { callbacks: [handler] };
738
+ }];
739
+ class ProtectedBinding extends RunnableBinding {
740
+ async scopedConfig(...configs) {
741
+ const created = [];
742
+ const scopedFactories = configFactories.map((factory) => async (config2) => {
743
+ const update = await factory(config2);
744
+ if (Array.isArray(update.callbacks)) created.push(...update.callbacks);
745
+ return update;
746
+ });
747
+ const scoped = new RunnableBinding({ bound: this.bound, config: this.config, configFactories: scopedFactories });
748
+ const config = await scoped._mergeConfig(...configs);
749
+ return { config, dispose: () => {
750
+ for (const handler of created) handlerCleanups.get(handler)?.();
751
+ } };
752
+ }
753
+ async invoke(input, options2) {
754
+ const scoped = await this.scopedConfig(options2, this.kwargs);
755
+ try {
756
+ return await this.bound.invoke(input, scoped.config);
757
+ } finally {
758
+ scoped.dispose();
472
759
  }
473
- const text = extractTextFromDocuments(documents);
474
- if (!text) {
475
- return;
760
+ }
761
+ async stream(input, options2) {
762
+ const scoped = await this.scopedConfig(ensureConfig(options2), this.kwargs);
763
+ let upstream;
764
+ try {
765
+ upstream = await this.bound.stream(input, scoped.config);
766
+ } catch (error) {
767
+ scoped.dispose();
768
+ throw error;
476
769
  }
477
- await classify(text, FIREWALL_HOOK_TO_LABEL[FirewallHook.RETRIEVER_END], runId);
770
+ const managed = async function* () {
771
+ try {
772
+ yield* upstream;
773
+ } finally {
774
+ scoped.dispose();
775
+ }
776
+ };
777
+ return IterableReadableStream.fromAsyncGenerator(managed());
778
+ }
779
+ async *_streamIterator(input, options2) {
780
+ const stream = await this.stream(input, options2);
781
+ yield* stream;
782
+ }
783
+ withConfig(config) {
784
+ return new ProtectedBinding({ bound: this.bound, config: mergeConfigs(this.config, config), configFactories });
785
+ }
786
+ withListeners(fields) {
787
+ return new RunnableBinding({ bound: this, config: {} }).withListeners(fields);
788
+ }
789
+ withRetry(fields) {
790
+ return new RunnableRetry({
791
+ bound: this,
792
+ config: {},
793
+ ...fields?.stopAfterAttempt === void 0 ? {} : { maxAttemptNumber: fields.stopAfterAttempt },
794
+ ...fields?.onFailedAttempt === void 0 ? {} : { onFailedAttempt: fields.onFailedAttempt }
795
+ });
478
796
  }
479
797
  }
480
- return new SilmarilFirewallHandler();
798
+ return new ProtectedBinding({ bound: runnable, config: {}, configFactories });
481
799
  }
482
- var USER_ROLES;
800
+ var handlerCleanups, USER_ROLES;
483
801
  var init_langchain = __esm({
484
802
  "src/adapters/langchain.ts"() {
485
803
  "use strict";
@@ -487,6 +805,8 @@ var init_langchain = __esm({
487
805
  init_hooks();
488
806
  init_modes();
489
807
  init_extract();
808
+ init_thread_identity();
809
+ handlerCleanups = /* @__PURE__ */ new WeakMap();
490
810
  USER_ROLES = /* @__PURE__ */ new Set(["human", "user"]);
491
811
  }
492
812
  });
@@ -844,6 +1164,18 @@ function normalizeHarmfulOutcomeMap(values, fieldName) {
844
1164
  }
845
1165
  return Object.freeze(out);
846
1166
  }
1167
+ function normalizeHarmfulOutcomeCountMap(values, fieldName) {
1168
+ const out = normalizeHarmfulOutcomeMap(values, fieldName);
1169
+ if (out === void 0) {
1170
+ return void 0;
1171
+ }
1172
+ for (const [key, value] of Object.entries(out)) {
1173
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
1174
+ throw new Error(`Firewall: invalid ${fieldName} value for ${JSON.stringify(key)}`);
1175
+ }
1176
+ }
1177
+ return out;
1178
+ }
847
1179
 
848
1180
  // src/sanitization.ts
849
1181
  function isHighSurrogate(code) {
@@ -872,7 +1204,7 @@ function sanitizeText(text) {
872
1204
  }
873
1205
 
874
1206
  // src/firewall.ts
875
- var SDK_VERSION = "0.7.9";
1207
+ var SDK_VERSION = "0.8.0";
876
1208
  var DEFAULT_TIMEOUT_MS = 1e4;
877
1209
  var DEFAULT_MAX_RETRIES = 5;
878
1210
  var MAX_BACKOFF_SECONDS = 30;
@@ -942,7 +1274,10 @@ function blockResultFromResponse(data, requestedMode, requestedReview) {
942
1274
  }
943
1275
  }
944
1276
  if (data.detector_counts !== void 0) {
945
- const detectorCounts = normalizeHarmfulOutcomeMap(data.detector_counts, "detector_counts");
1277
+ const detectorCounts = normalizeHarmfulOutcomeCountMap(
1278
+ data.detector_counts,
1279
+ "detector_counts"
1280
+ );
946
1281
  if (detectorCounts !== void 0) {
947
1282
  result.detectorCounts = detectorCounts;
948
1283
  }