plugin-sensitive-filter-xr 0.0.4 → 0.0.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"sensitiveFilter.d.ts","sourceRoot":"","sources":["../../src/lib/sensitiveFilter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,oBAAoB,EAA8B,MAAM,kBAAkB,CAAA;AAGnG,OAAO,EACL,eAAe,EAGf,uBAAuB,EACvB,wBAAwB,EAEzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAOL,qBAAqB,EAMtB,MAAM,YAAY,CAAA;AAwanB,qBAEa,yBAA0B,YAAW,wBAAwB,CAAC,qBAAqB,CAAC;IAE/F,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IAEvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CA6SlC;IAEK,gBAAgB,CACpB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,eAAe,CAAC;IAa3B,OAAO,CAAC,wBAAwB;IA+KhC,OAAO,CAAC,uBAAuB;CA2ShC;AAED,YAAY,EAAE,qBAAqB,EAAE,CAAA"}
1
+ {"version":3,"file":"sensitiveFilter.d.ts","sourceRoot":"","sources":["../../src/lib/sensitiveFilter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,oBAAoB,EAA8B,MAAM,kBAAkB,CAAA;AAGnG,OAAO,EACL,eAAe,EAGf,uBAAuB,EACvB,wBAAwB,EAEzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAOL,qBAAqB,EAMtB,MAAM,YAAY,CAAA;AAienB,qBAEa,yBAA0B,YAAW,wBAAwB,CAAC,qBAAqB,CAAC;IAE/F,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IAEvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAqRlC;IAEK,gBAAgB,CACpB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,eAAe,CAAC;IAa3B,OAAO,CAAC,wBAAwB;IAqOhC,OAAO,CAAC,uBAAuB;CA8YhC;AAED,YAAY,EAAE,qBAAqB,EAAE,CAAA"}
@@ -11,7 +11,7 @@ const DEFAULT_OUTPUT_BLOCK_MESSAGE = '输出内容触发敏感策略,已拦截
11
11
  const DEFAULT_REWRITE_TEXT = '[已过滤]';
12
12
  const CONFIG_PARSE_ERROR = '敏感词过滤配置格式不正确,请检查填写内容。';
13
13
  const BUSINESS_RULES_VALIDATION_ERROR = '请至少配置 1 条有效业务规则(pattern/type/action/scope/severity),或启用通用规则包。';
14
- const LLM_MODE_VALIDATION_ERROR = '请完善 LLM 过滤配置:需填写过滤模型、生效范围、判定提示词、失败处理策略。';
14
+ const LLM_MODE_VALIDATION_ERROR = '请完善 LLM 过滤配置:需填写过滤模型、生效范围、审核规则说明。';
15
15
  function isRecord(value) {
16
16
  return typeof value === 'object' && value !== null;
17
17
  }
@@ -212,6 +212,14 @@ function normalizeRuleDrafts(input) {
212
212
  function modeIncludesScope(scope, phase) {
213
213
  return scope === 'both' || scope === phase;
214
214
  }
215
+ function buildInternalLlmPrompt(rulePrompt) {
216
+ return ('你是敏感内容审核器。请根据用户给出的审核规则判断文本是否命中敏感内容。' +
217
+ '\n你只能返回 JSON,不要输出额外说明。' +
218
+ '\nJSON字段: matched(boolean), action("block"|"rewrite"), replacementText(string), reason(string), categories(string[]).' +
219
+ '\n命中时请尽量给出 replacementText;未命中时返回 {"matched": false}。' +
220
+ '\n说明:系统会统一执行改写策略,即便你返回 action=block 也会按 rewrite 处理。' +
221
+ `\n\n用户审核规则:\n${rulePrompt}`);
222
+ }
215
223
  function parseLlmDecision(raw, rewriteFallbackText) {
216
224
  let payload = raw;
217
225
  if (typeof payload === 'string') {
@@ -248,14 +256,6 @@ function parseLlmDecision(raw, rewriteFallbackText) {
248
256
  categories,
249
257
  };
250
258
  }
251
- if (decision.action === 'block') {
252
- return {
253
- matched: true,
254
- action: 'block',
255
- reason,
256
- categories,
257
- };
258
- }
259
259
  return {
260
260
  matched: true,
261
261
  action: 'rewrite',
@@ -270,30 +270,27 @@ function resolveRuntimeLlmConfig(config) {
270
270
  }
271
271
  const model = isRecord(config.model) ? config.model : null;
272
272
  const scope = toNonEmptyString(config.scope);
273
- const systemPrompt = toNonEmptyString(config.systemPrompt);
274
- const onLlmError = toNonEmptyString(config.onLlmError);
275
- if (!model || !scope || !systemPrompt || !onLlmError) {
273
+ const rulePrompt = toNonEmptyString(config.rulePrompt) ?? toNonEmptyString(config.systemPrompt);
274
+ if (!model || !scope || !rulePrompt) {
276
275
  throw new Error(LLM_MODE_VALIDATION_ERROR);
277
276
  }
278
277
  const outputMethodRaw = toNonEmptyString(config.outputMethod);
279
278
  const outputMethod = ['functionCalling', 'jsonMode', 'jsonSchema'].includes(outputMethodRaw ?? '')
280
279
  ? outputMethodRaw
281
- : 'jsonSchema';
282
- const errorRewriteText = toNonEmptyString(config.errorRewriteText) ?? undefined;
283
- if (onLlmError === 'rewrite' && !errorRewriteText) {
284
- throw new Error('请完善 LLM 过滤配置:当失败处理为改写时,必须填写失败改写文本。');
285
- }
280
+ : 'jsonMode';
286
281
  const timeoutMs = typeof config.timeoutMs === 'number' && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0
287
282
  ? Math.min(Math.floor(config.timeoutMs), 120000)
288
283
  : undefined;
284
+ const legacyOnLlmError = toNonEmptyString(config.onLlmError);
285
+ const legacyErrorRewriteText = toNonEmptyString(config.errorRewriteText) ?? undefined;
289
286
  return {
290
287
  model,
291
288
  scope,
292
- systemPrompt,
289
+ rulePrompt,
290
+ systemPrompt: buildInternalLlmPrompt(rulePrompt),
293
291
  outputMethod,
294
- onLlmError,
295
- errorRewriteText,
296
- blockMessage: toNonEmptyString(config.blockMessage) ?? undefined,
292
+ legacyOnLlmError: legacyOnLlmError ?? undefined,
293
+ legacyErrorRewriteText,
297
294
  rewriteFallbackText: toNonEmptyString(config.rewriteFallbackText) ?? DEFAULT_REWRITE_TEXT,
298
295
  timeoutMs,
299
296
  };
@@ -317,6 +314,39 @@ function withTimeout(promise, timeoutMs) {
317
314
  });
318
315
  });
319
316
  }
317
+ function normalizeConfigurable(input) {
318
+ if (!isRecord(input)) {
319
+ return null;
320
+ }
321
+ return input;
322
+ }
323
+ function buildOutputMethodCandidates(preferred) {
324
+ const queue = [preferred, 'functionCalling', 'jsonMode', 'jsonSchema'];
325
+ const unique = [];
326
+ for (const method of queue) {
327
+ if (!unique.includes(method)) {
328
+ unique.push(method);
329
+ }
330
+ }
331
+ return unique;
332
+ }
333
+ function getErrorText(error) {
334
+ if (error instanceof Error) {
335
+ return error.message;
336
+ }
337
+ return String(error ?? '');
338
+ }
339
+ function isUnsupportedStructuredOutputError(error) {
340
+ const message = getErrorText(error).toLowerCase();
341
+ const patterns = [
342
+ 'response_format type is unavailable',
343
+ 'invalid response_format',
344
+ 'response_format',
345
+ 'unsupported schema',
346
+ 'not support',
347
+ ];
348
+ return patterns.some((pattern) => message.includes(pattern));
349
+ }
320
350
  let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
321
351
  constructor() {
322
352
  this.meta = {
@@ -534,19 +564,27 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
534
564
  },
535
565
  },
536
566
  },
537
- systemPrompt: {
567
+ rulePrompt: {
538
568
  type: 'string',
539
- title: { en_US: 'System Prompt', zh_Hans: '判定提示词' },
569
+ title: { en_US: 'Rule Prompt', zh_Hans: '审核规则说明' },
570
+ description: {
571
+ en_US: 'Describe your moderation rules in natural language. No JSON format is required.',
572
+ zh_Hans: '用自然语言描述审核规则,无需手写 JSON 格式。',
573
+ },
540
574
  'x-ui': {
541
575
  component: 'textarea',
542
576
  span: 2,
577
+ placeholder: {
578
+ en_US: 'e.g. Rewrite violent/privacy-sensitive content into a safe neutral response.',
579
+ zh_Hans: '例如:涉及暴力或隐私泄露内容时,改写为安全中性表达。',
580
+ },
543
581
  },
544
582
  },
545
583
  outputMethod: {
546
584
  type: 'string',
547
585
  title: { en_US: 'Output Method', zh_Hans: '结构化输出方式' },
548
586
  enum: ['functionCalling', 'jsonMode', 'jsonSchema'],
549
- default: 'jsonSchema',
587
+ default: 'jsonMode',
550
588
  'x-ui': {
551
589
  enumLabels: {
552
590
  functionCalling: { en_US: 'Function Calling', zh_Hans: '函数调用' },
@@ -555,25 +593,6 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
555
593
  },
556
594
  },
557
595
  },
558
- onLlmError: {
559
- type: 'string',
560
- title: { en_US: 'On LLM Error', zh_Hans: 'LLM失败处理' },
561
- enum: ['block', 'rewrite'],
562
- 'x-ui': {
563
- enumLabels: {
564
- block: { en_US: 'Block', zh_Hans: '拦截' },
565
- rewrite: { en_US: 'Rewrite', zh_Hans: '改写' },
566
- },
567
- },
568
- },
569
- errorRewriteText: {
570
- type: 'string',
571
- title: { en_US: 'Error Rewrite Text', zh_Hans: '失败改写文本' },
572
- },
573
- blockMessage: {
574
- type: 'string',
575
- title: { en_US: 'Block Message', zh_Hans: '拦截提示文本' },
576
- },
577
596
  rewriteFallbackText: {
578
597
  type: 'string',
579
598
  title: { en_US: 'Rewrite Fallback Text', zh_Hans: '改写兜底文本' },
@@ -583,21 +602,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
583
602
  title: { en_US: 'Timeout (ms)', zh_Hans: '超时毫秒' },
584
603
  },
585
604
  },
586
- required: ['model', 'scope', 'systemPrompt', 'onLlmError'],
587
- allOf: [
588
- {
589
- if: {
590
- properties: {
591
- onLlmError: {
592
- const: 'rewrite',
593
- },
594
- },
595
- },
596
- then: {
597
- required: ['errorRewriteText'],
598
- },
599
- },
600
- ],
605
+ required: ['model', 'scope', 'rulePrompt'],
601
606
  },
602
607
  },
603
608
  required: ['mode'],
@@ -626,9 +631,9 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
626
631
  if (parsed.data.mode === 'llm') {
627
632
  return this.createLlmModeMiddleware(parsed.data, context);
628
633
  }
629
- return this.createRuleModeMiddleware(parsed.data);
634
+ return this.createRuleModeMiddleware(parsed.data, context);
630
635
  }
631
- createRuleModeMiddleware(config) {
636
+ createRuleModeMiddleware(config, context) {
632
637
  const caseSensitive = config.caseSensitive ?? false;
633
638
  const normalize = config.normalize ?? true;
634
639
  const customRules = normalizeRuleDrafts(config.rules ?? []);
@@ -669,6 +674,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
669
674
  let pendingInputRewrite = null;
670
675
  let finalAction = 'pass';
671
676
  let auditEntries = [];
677
+ let runtimeConfigurable = null;
672
678
  const resetRunState = () => {
673
679
  inputBlockedMessage = null;
674
680
  pendingInputRewrite = null;
@@ -682,10 +688,65 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
682
688
  mode: 'rule',
683
689
  });
684
690
  };
691
+ const assignRuntimeConfigurable = (runtimeLike) => {
692
+ const configurable = normalizeConfigurable(runtimeLike?.configurable);
693
+ if (!configurable) {
694
+ return;
695
+ }
696
+ if (configurable.thread_id && configurable.executionId) {
697
+ runtimeConfigurable = configurable;
698
+ }
699
+ };
700
+ const buildAuditSnapshot = () => {
701
+ const summary = {
702
+ total: auditEntries.length,
703
+ matched: auditEntries.filter((entry) => entry.matched).length,
704
+ blocked: auditEntries.filter((entry) => entry.action === 'block').length,
705
+ rewritten: auditEntries.filter((entry) => entry.action === 'rewrite').length,
706
+ errorPolicyTriggered: auditEntries.filter((entry) => entry.errorPolicyTriggered).length,
707
+ };
708
+ return {
709
+ mode: 'rule',
710
+ finalAction,
711
+ records: auditEntries,
712
+ summary,
713
+ };
714
+ };
715
+ const persistAuditSnapshot = async () => {
716
+ const configurable = runtimeConfigurable;
717
+ if (!configurable?.thread_id || !configurable.executionId || !this.commandBus) {
718
+ return;
719
+ }
720
+ const { thread_id, checkpoint_ns, checkpoint_id, subscriber, executionId } = configurable;
721
+ const snapshot = buildAuditSnapshot();
722
+ await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(async () => {
723
+ return {
724
+ state: snapshot,
725
+ output: snapshot,
726
+ };
727
+ }, {
728
+ execution: {
729
+ category: 'workflow',
730
+ type: 'middleware',
731
+ title: `${context.node.title} Audit`,
732
+ inputs: {
733
+ mode: snapshot.mode,
734
+ total: snapshot.summary.total,
735
+ },
736
+ parentId: executionId,
737
+ threadId: thread_id,
738
+ checkpointNs: checkpoint_ns,
739
+ checkpointId: checkpoint_id,
740
+ agentKey: context.node.key,
741
+ },
742
+ subscriber,
743
+ }));
744
+ };
685
745
  return {
686
746
  name: SENSITIVE_FILTER_MIDDLEWARE_NAME,
687
747
  beforeAgent: async (state, runtime) => {
688
748
  resetRunState();
749
+ assignRuntimeConfigurable(runtime);
689
750
  if (!hasEffectiveRules) {
690
751
  throw new Error(BUSINESS_RULES_VALIDATION_ERROR);
691
752
  }
@@ -722,6 +783,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
722
783
  return undefined;
723
784
  },
724
785
  wrapModelCall: async (request, handler) => {
786
+ assignRuntimeConfigurable(request?.runtime);
725
787
  if (!hasEffectiveRules) {
726
788
  throw new Error(BUSINESS_RULES_VALIDATION_ERROR);
727
789
  }
@@ -762,11 +824,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
762
824
  return replaceModelResponseText(response, rewrittenOutput);
763
825
  },
764
826
  afterAgent: async () => {
765
- console.log('[SensitiveFilterMiddleware][audit]', JSON.stringify({
766
- mode: 'rule',
767
- finalAction,
768
- records: auditEntries,
769
- }, null, 2));
827
+ await persistAuditSnapshot();
770
828
  return undefined;
771
829
  },
772
830
  };
@@ -775,7 +833,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
775
833
  const llmDraftConfig = config.llm;
776
834
  let resolvedLlmConfig = null;
777
835
  let modelPromise = null;
778
- let structuredModelPromise = null;
836
+ const structuredModelPromises = new Map();
779
837
  const getLlmConfig = () => {
780
838
  if (!resolvedLlmConfig) {
781
839
  resolvedLlmConfig = resolveRuntimeLlmConfig(llmDraftConfig);
@@ -786,34 +844,36 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
786
844
  const llmConfig = getLlmConfig();
787
845
  if (!modelPromise) {
788
846
  modelPromise = this.commandBus.execute(new CreateModelClientCommand(llmConfig.model, {
789
- usageCallback: (event) => {
790
- console.log('[SensitiveFilterMiddleware][LLM] Model Usage:', event);
791
- },
847
+ usageCallback: () => { },
792
848
  }));
793
849
  }
794
850
  return modelPromise;
795
851
  };
796
- const ensureStructuredModel = async () => {
797
- const llmConfig = getLlmConfig();
798
- if (!structuredModelPromise) {
799
- structuredModelPromise = (async () => {
852
+ const ensureStructuredModel = async (method) => {
853
+ if (!structuredModelPromises.has(method)) {
854
+ structuredModelPromises.set(method, (async () => {
800
855
  const model = await ensureModel();
801
856
  return model.withStructuredOutput?.(llmDecisionSchema, {
802
- method: llmConfig.outputMethod,
857
+ method,
803
858
  }) ?? null;
804
- })();
859
+ })());
805
860
  }
806
- return structuredModelPromise;
861
+ return structuredModelPromises.get(method);
807
862
  };
808
- let inputBlockedMessage = null;
809
863
  let pendingInputRewrite = null;
810
864
  let finalAction = 'pass';
811
865
  let auditEntries = [];
866
+ let runtimeConfigurable = null;
867
+ let resolvedOutputMethod;
868
+ let fallbackTriggered = false;
869
+ let methodAttempts = [];
812
870
  const resetRunState = () => {
813
- inputBlockedMessage = null;
814
871
  pendingInputRewrite = null;
815
872
  finalAction = 'pass';
816
873
  auditEntries = [];
874
+ resolvedOutputMethod = undefined;
875
+ fallbackTriggered = false;
876
+ methodAttempts = [];
817
877
  };
818
878
  const pushAudit = (entry) => {
819
879
  auditEntries.push({
@@ -822,6 +882,77 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
822
882
  mode: 'llm',
823
883
  });
824
884
  };
885
+ const assignRuntimeConfigurable = (runtimeLike) => {
886
+ const configurable = normalizeConfigurable(runtimeLike?.configurable);
887
+ if (!configurable) {
888
+ return;
889
+ }
890
+ if (configurable.thread_id && configurable.executionId) {
891
+ runtimeConfigurable = configurable;
892
+ }
893
+ };
894
+ const captureLlmOutputTrace = (trace) => {
895
+ for (const method of trace.methodAttempts) {
896
+ if (!methodAttempts.includes(method)) {
897
+ methodAttempts.push(method);
898
+ }
899
+ }
900
+ resolvedOutputMethod = trace.resolvedOutputMethod;
901
+ fallbackTriggered = fallbackTriggered || trace.fallbackTriggered;
902
+ };
903
+ const buildAuditSnapshot = () => {
904
+ const summary = {
905
+ total: auditEntries.length,
906
+ matched: auditEntries.filter((entry) => entry.matched).length,
907
+ blocked: auditEntries.filter((entry) => entry.action === 'block').length,
908
+ rewritten: auditEntries.filter((entry) => entry.action === 'rewrite').length,
909
+ errorPolicyTriggered: auditEntries.filter((entry) => entry.errorPolicyTriggered).length,
910
+ };
911
+ return {
912
+ mode: 'llm',
913
+ finalAction,
914
+ records: auditEntries,
915
+ summary,
916
+ llmOutput: resolvedLlmConfig
917
+ ? {
918
+ requestedOutputMethod: resolvedLlmConfig.outputMethod,
919
+ resolvedOutputMethod,
920
+ methodAttempts,
921
+ fallbackTriggered,
922
+ }
923
+ : undefined,
924
+ };
925
+ };
926
+ const persistAuditSnapshot = async () => {
927
+ const configurable = runtimeConfigurable;
928
+ if (!configurable?.thread_id || !configurable.executionId || !this.commandBus) {
929
+ return;
930
+ }
931
+ const { thread_id, checkpoint_ns, checkpoint_id, subscriber, executionId } = configurable;
932
+ const snapshot = buildAuditSnapshot();
933
+ await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(async () => {
934
+ return {
935
+ state: snapshot,
936
+ output: snapshot,
937
+ };
938
+ }, {
939
+ execution: {
940
+ category: 'workflow',
941
+ type: 'middleware',
942
+ title: `${context.node.title} Audit`,
943
+ inputs: {
944
+ mode: snapshot.mode,
945
+ total: snapshot.summary.total,
946
+ },
947
+ parentId: executionId,
948
+ threadId: thread_id,
949
+ checkpointNs: checkpoint_ns,
950
+ checkpointId: checkpoint_id,
951
+ agentKey: context.node.key,
952
+ },
953
+ subscriber,
954
+ }));
955
+ };
825
956
  const buildEvaluationMessages = (phase, text, llmConfig) => {
826
957
  return [
827
958
  { role: 'system', content: llmConfig.systemPrompt },
@@ -836,14 +967,49 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
836
967
  const invokeAndTrack = async (phase, text, runtime, llmConfig) => {
837
968
  const invokeCore = async () => {
838
969
  const messages = buildEvaluationMessages(phase, text, llmConfig);
839
- const [model, structuredModel] = await Promise.all([ensureModel(), ensureStructuredModel()]);
840
- if (structuredModel) {
841
- return withTimeout(structuredModel.invoke(messages), llmConfig.timeoutMs);
970
+ const model = await ensureModel();
971
+ const candidates = buildOutputMethodCandidates(llmConfig.outputMethod);
972
+ const attempts = [];
973
+ for (const method of candidates) {
974
+ attempts.push(method);
975
+ try {
976
+ const structuredModel = await ensureStructuredModel(method);
977
+ if (!structuredModel) {
978
+ throw new Error(`Structured output is not available for method: ${method}`);
979
+ }
980
+ const raw = await withTimeout(structuredModel.invoke(messages), llmConfig.timeoutMs);
981
+ return {
982
+ raw,
983
+ trace: {
984
+ requestedOutputMethod: llmConfig.outputMethod,
985
+ resolvedOutputMethod: method,
986
+ methodAttempts: attempts,
987
+ fallbackTriggered: method !== llmConfig.outputMethod || attempts.length > 1,
988
+ },
989
+ };
990
+ }
991
+ catch (error) {
992
+ if (isUnsupportedStructuredOutputError(error)) {
993
+ continue;
994
+ }
995
+ throw error;
996
+ }
842
997
  }
843
- return withTimeout(model.invoke(messages), llmConfig.timeoutMs);
998
+ attempts.push('plainText');
999
+ const raw = await withTimeout(model.invoke(messages), llmConfig.timeoutMs);
1000
+ return {
1001
+ raw,
1002
+ trace: {
1003
+ requestedOutputMethod: llmConfig.outputMethod,
1004
+ resolvedOutputMethod: 'plainText',
1005
+ methodAttempts: attempts,
1006
+ fallbackTriggered: true,
1007
+ },
1008
+ };
844
1009
  };
845
1010
  const parseCore = async () => {
846
- const raw = await invokeCore();
1011
+ const { raw, trace } = await invokeCore();
1012
+ captureLlmOutputTrace(trace);
847
1013
  return parseLlmDecision(raw, llmConfig.rewriteFallbackText);
848
1014
  };
849
1015
  const configurable = (runtime?.configurable ?? {});
@@ -878,28 +1044,18 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
878
1044
  };
879
1045
  const resolveOnErrorDecision = (llmConfig, error) => {
880
1046
  const reason = `llm-error:${error instanceof Error ? error.message : String(error)}`;
881
- if (llmConfig.onLlmError === 'block') {
882
- return {
883
- matched: true,
884
- action: 'block',
885
- reason,
886
- };
887
- }
888
1047
  return {
889
1048
  matched: true,
890
1049
  action: 'rewrite',
891
- replacementText: llmConfig.errorRewriteText ?? llmConfig.rewriteFallbackText,
1050
+ replacementText: llmConfig.legacyErrorRewriteText ?? llmConfig.rewriteFallbackText,
892
1051
  reason,
893
1052
  };
894
1053
  };
895
- const resolveBlockMessage = (llmConfig, phase) => {
896
- return llmConfig.blockMessage ??
897
- (phase === 'input' ? DEFAULT_INPUT_BLOCK_MESSAGE : DEFAULT_OUTPUT_BLOCK_MESSAGE);
898
- };
899
1054
  return {
900
1055
  name: SENSITIVE_FILTER_MIDDLEWARE_NAME,
901
1056
  beforeAgent: async (state, runtime) => {
902
1057
  resetRunState();
1058
+ assignRuntimeConfigurable(runtime);
903
1059
  const llmConfig = getLlmConfig();
904
1060
  if (!modeIncludesScope(llmConfig.scope, 'input')) {
905
1061
  pushAudit({
@@ -942,20 +1098,13 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
942
1098
  if (!decision.matched || !decision.action) {
943
1099
  return undefined;
944
1100
  }
945
- if (decision.action === 'block') {
946
- finalAction = 'block';
947
- inputBlockedMessage = resolveBlockMessage(llmConfig, 'input');
948
- return undefined;
949
- }
950
1101
  finalAction = 'rewrite';
951
1102
  pendingInputRewrite = toNonEmptyString(decision.replacementText) ?? llmConfig.rewriteFallbackText;
952
1103
  return undefined;
953
1104
  },
954
1105
  wrapModelCall: async (request, handler) => {
1106
+ assignRuntimeConfigurable(request?.runtime);
955
1107
  const llmConfig = getLlmConfig();
956
- if (inputBlockedMessage) {
957
- return new AIMessage(inputBlockedMessage);
958
- }
959
1108
  const modelRequest = pendingInputRewrite ? rewriteModelRequestInput(request, pendingInputRewrite) : request;
960
1109
  pendingInputRewrite = null;
961
1110
  const response = await handler(modelRequest);
@@ -1000,19 +1149,11 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
1000
1149
  if (!decision.matched || !decision.action) {
1001
1150
  return response;
1002
1151
  }
1003
- if (decision.action === 'block') {
1004
- finalAction = 'block';
1005
- return replaceModelResponseText(response, resolveBlockMessage(llmConfig, 'output'));
1006
- }
1007
1152
  finalAction = 'rewrite';
1008
1153
  return replaceModelResponseText(response, toNonEmptyString(decision.replacementText) ?? llmConfig.rewriteFallbackText);
1009
1154
  },
1010
1155
  afterAgent: async () => {
1011
- console.log('[SensitiveFilterMiddleware][audit]', JSON.stringify({
1012
- mode: 'llm',
1013
- finalAction,
1014
- records: auditEntries,
1015
- }, null, 2));
1156
+ await persistAuditSnapshot();
1016
1157
  return undefined;
1017
1158
  },
1018
1159
  };
@@ -26,6 +26,7 @@ export type LlmErrorAction = 'block' | 'rewrite';
26
26
  export type LlmFilterConfig = {
27
27
  model?: ICopilotModel;
28
28
  scope?: LlmScope;
29
+ rulePrompt?: string;
29
30
  systemPrompt?: string;
30
31
  outputMethod?: LlmOutputMethod;
31
32
  onLlmError?: LlmErrorAction;
@@ -134,6 +135,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
134
135
  llm: z.ZodDefault<z.ZodNullable<z.ZodOptional<z.ZodObject<{
135
136
  model: z.ZodNullable<z.ZodOptional<z.ZodType<ICopilotModel, z.ZodTypeDef, ICopilotModel>>>;
136
137
  scope: z.ZodNullable<z.ZodOptional<z.ZodEnum<["input", "output", "both"]>>>;
138
+ rulePrompt: z.ZodNullable<z.ZodOptional<z.ZodString>>;
137
139
  systemPrompt: z.ZodNullable<z.ZodOptional<z.ZodString>>;
138
140
  outputMethod: z.ZodDefault<z.ZodOptional<z.ZodEnum<["functionCalling", "jsonMode", "jsonSchema"]>>>;
139
141
  onLlmError: z.ZodNullable<z.ZodOptional<z.ZodEnum<["block", "rewrite"]>>>;
@@ -144,6 +146,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
144
146
  }, "strip", z.ZodTypeAny, {
145
147
  model?: ICopilotModel;
146
148
  scope?: "input" | "output" | "both";
149
+ rulePrompt?: string;
147
150
  systemPrompt?: string;
148
151
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
149
152
  onLlmError?: "block" | "rewrite";
@@ -154,6 +157,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
154
157
  }, {
155
158
  model?: ICopilotModel;
156
159
  scope?: "input" | "output" | "both";
160
+ rulePrompt?: string;
157
161
  systemPrompt?: string;
158
162
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
159
163
  onLlmError?: "block" | "rewrite";
@@ -171,6 +175,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
171
175
  llm?: {
172
176
  model?: ICopilotModel;
173
177
  scope?: "input" | "output" | "both";
178
+ rulePrompt?: string;
174
179
  systemPrompt?: string;
175
180
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
176
181
  onLlmError?: "block" | "rewrite";
@@ -188,6 +193,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
188
193
  llm?: {
189
194
  model?: ICopilotModel;
190
195
  scope?: "input" | "output" | "both";
196
+ rulePrompt?: string;
191
197
  systemPrompt?: string;
192
198
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
193
199
  onLlmError?: "block" | "rewrite";
@@ -201,9 +207,9 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
201
207
  generalPack?: unknown;
202
208
  caseSensitive?: unknown;
203
209
  }>]>;
204
- export declare const llmDecisionSchema: z.ZodEffects<z.ZodObject<{
210
+ export declare const llmDecisionSchema: z.ZodObject<{
205
211
  matched: z.ZodBoolean;
206
- action: z.ZodOptional<z.ZodEnum<["block", "rewrite"]>>;
212
+ action: z.ZodNullable<z.ZodOptional<z.ZodEnum<["block", "rewrite"]>>>;
207
213
  replacementText: z.ZodNullable<z.ZodOptional<z.ZodString>>;
208
214
  reason: z.ZodNullable<z.ZodOptional<z.ZodString>>;
209
215
  categories: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
@@ -219,18 +225,6 @@ export declare const llmDecisionSchema: z.ZodEffects<z.ZodObject<{
219
225
  matched?: boolean;
220
226
  reason?: string;
221
227
  categories?: string[];
222
- }>, {
223
- action?: "block" | "rewrite";
224
- replacementText?: string;
225
- matched?: boolean;
226
- reason?: string;
227
- categories?: string[];
228
- }, {
229
- action?: "block" | "rewrite";
230
- replacementText?: string;
231
- matched?: boolean;
232
- reason?: string;
233
- categories?: string[];
234
228
  }>;
235
229
  export declare function resolveGeneralPackRules(config?: GeneralPackConfig): SensitiveRule[];
236
230
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAE1B,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;IACzB,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAClC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAA;IAC3B,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,CAAA;IAC5C,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;AAClD,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,UAAU,GAAG,YAAY,CAAA;AAC3E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,CAAA;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,eAAe,CAAA;IAC9B,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,KAAK,CAAA;IACX,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,aAAa,CAAA;AAElE,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,mBAAmB,wSAA8R,CAAA;AAkE9T,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAuD,CAAA;AAE/F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgB1B,CAAA;AAYJ,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,aAAa,EAAE,CA6CnF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAE1B,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;IACzB,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAClC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAA;IAC3B,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,CAAA;IAC5C,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;AAClD,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,UAAU,GAAG,YAAY,CAAA;AAC3E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,CAAA;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,eAAe,CAAA;IAE9B,UAAU,CAAC,EAAE,cAAc,CAAA;IAE3B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,KAAK,CAAA;IACX,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,aAAa,CAAA;AAElE,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,mBAAmB,wSAA8R,CAAA;AAmE9T,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAuD,CAAA;AAE/F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;EAO1B,CAAA;AAYJ,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,aAAa,EAAE,CA6CnF"}
package/dist/lib/types.js CHANGED
@@ -34,8 +34,9 @@ const llmConfigSchema = z
34
34
  .object({
35
35
  model: z.custom().optional().nullable(),
36
36
  scope: z.enum(['input', 'output', 'both']).optional().nullable(),
37
+ rulePrompt: z.string().optional().nullable(),
37
38
  systemPrompt: z.string().optional().nullable(),
38
- outputMethod: z.enum(['functionCalling', 'jsonMode', 'jsonSchema']).optional().default('jsonSchema'),
39
+ outputMethod: z.enum(['functionCalling', 'jsonMode', 'jsonSchema']).optional().default('jsonMode'),
39
40
  onLlmError: z.enum(['block', 'rewrite']).optional().nullable(),
40
41
  errorRewriteText: z.string().optional().nullable(),
41
42
  blockMessage: z.string().optional().nullable(),
@@ -62,19 +63,10 @@ export const sensitiveFilterConfigSchema = z.union([ruleModeConfigSchema, llmMod
62
63
  export const llmDecisionSchema = z
63
64
  .object({
64
65
  matched: z.boolean(),
65
- action: z.enum(['block', 'rewrite']).optional(),
66
+ action: z.enum(['block', 'rewrite']).optional().nullable(),
66
67
  replacementText: z.string().optional().nullable(),
67
68
  reason: z.string().optional().nullable(),
68
69
  categories: z.array(z.string()).optional().nullable(),
69
- })
70
- .superRefine((data, ctx) => {
71
- if (data.matched && !data.action) {
72
- ctx.addIssue({
73
- code: z.ZodIssueCode.custom,
74
- path: ['action'],
75
- message: 'action is required when matched is true',
76
- });
77
- }
78
70
  });
79
71
  function escapeRegExp(value) {
80
72
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-sensitive-filter-xr",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "author": {
5
5
  "name": "XpertAI",
6
6
  "url": "https://xpertai.cn"