plugin-sensitive-filter-xr 0.0.5 → 0.0.7

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/README.md CHANGED
@@ -60,18 +60,19 @@
60
60
  | `model` | `ICopilotModel` | 是 | - | 用于判定的过滤模型。 |
61
61
  | `scope` | `'input' \| 'output' \| 'both'` | 是 | - | 生效范围。 |
62
62
  | `rulePrompt` | `string` | 是 | - | 审核规则说明(自然语言,不需要 JSON)。 |
63
- | `outputMethod` | `'functionCalling' \| 'jsonMode' \| 'jsonSchema'` | 否 | `jsonMode` | 结构化输出首选方式(不支持时自动降级)。 |
64
63
  | `rewriteFallbackText` | `string` | 否 | `[已过滤]` | 命中但未返回改写文本时兜底文案。 |
65
64
  | `timeoutMs` | `number` | 否 | 不限 | 判定超时(毫秒,上限 `120000`)。 |
66
65
 
67
66
  ### llm 模式行为说明
68
67
 
69
68
  - 用户只需填写自然语言规则,不需要书写 JSON 协议。
69
+ - `outputMethod` 由中间件内部自动自适应(兼容历史配置读取),默认不在配置面板展示。
70
70
  - 命中后统一执行 `rewrite`。
71
71
  - LLM 调用异常时也统一执行 `rewrite`。
72
72
  - 若配置了 `timeoutMs` 且判定超时,会直接使用 `rewriteFallbackText` 作为改写结果。
73
73
  - 若模型不支持当前 `outputMethod` 的 `response_format`,会自动尝试其它结构化方式并最终降级到纯文本 JSON 解析。
74
74
  - 若历史配置仍携带 `onLlmError/systemPrompt/errorRewriteText`,会按兼容逻辑处理并给出弃用告警。
75
+ - 内部判定与审计追踪默认静默执行,不会把 `{"matched":false}` 这类内部 JSON 透传到聊天回复。
75
76
 
76
77
  ## 界面怎么填(最简)
77
78
 
@@ -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;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"}
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;AAqgBnB,qBAEa,yBAA0B,YAAW,wBAAwB,CAAC,qBAAqB,CAAC;IAE/F,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IAEvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAwQlC;IAEK,gBAAgB,CACpB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,eAAe,CAAC;IAa3B,OAAO,CAAC,wBAAwB;IA8OhC,OAAO,CAAC,uBAAuB;CA4ahC;AAED,YAAY,EAAE,qBAAqB,EAAE,CAAA"}
@@ -12,6 +12,10 @@ const DEFAULT_REWRITE_TEXT = '[已过滤]';
12
12
  const CONFIG_PARSE_ERROR = '敏感词过滤配置格式不正确,请检查填写内容。';
13
13
  const BUSINESS_RULES_VALIDATION_ERROR = '请至少配置 1 条有效业务规则(pattern/type/action/scope/severity),或启用通用规则包。';
14
14
  const LLM_MODE_VALIDATION_ERROR = '请完善 LLM 过滤配置:需填写过滤模型、生效范围、审核规则说明。';
15
+ const INTERNAL_LLM_INVOKE_TAG = 'sensitive-filter/internal-eval';
16
+ const INTERNAL_LLM_INVOKE_OPTIONS = {
17
+ tags: [INTERNAL_LLM_INVOKE_TAG],
18
+ };
15
19
  function isRecord(value) {
16
20
  return typeof value === 'object' && value !== null;
17
21
  }
@@ -22,6 +26,16 @@ function toNonEmptyString(value) {
22
26
  const trimmed = value.trim();
23
27
  return trimmed ? trimmed : null;
24
28
  }
29
+ function buildInternalModelConfig(model) {
30
+ const options = isRecord(model.options) ? model.options : {};
31
+ return {
32
+ ...model,
33
+ options: {
34
+ ...options,
35
+ streaming: false,
36
+ },
37
+ };
38
+ }
25
39
  function normalizeForMatching(text, normalize, caseSensitive) {
26
40
  const source = normalize ? text.trim().replace(/\s+/g, ' ') : text;
27
41
  return caseSensitive ? source : source.toLowerCase();
@@ -347,6 +361,21 @@ function isUnsupportedStructuredOutputError(error) {
347
361
  ];
348
362
  return patterns.some((pattern) => message.includes(pattern));
349
363
  }
364
+ function isMissingWrapWorkflowHandlerError(error) {
365
+ const message = getErrorText(error).toLowerCase();
366
+ return message.includes('no handler found') && message.includes('wrapworkflownodeexecutioncommand');
367
+ }
368
+ async function runWithWrapWorkflowFallback(runTracked, runFallback) {
369
+ try {
370
+ return await runTracked();
371
+ }
372
+ catch (error) {
373
+ if (isMissingWrapWorkflowHandlerError(error)) {
374
+ return runFallback();
375
+ }
376
+ throw error;
377
+ }
378
+ }
350
379
  let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
351
380
  constructor() {
352
381
  this.meta = {
@@ -580,19 +609,6 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
580
609
  },
581
610
  },
582
611
  },
583
- outputMethod: {
584
- type: 'string',
585
- title: { en_US: 'Output Method', zh_Hans: '结构化输出方式' },
586
- enum: ['functionCalling', 'jsonMode', 'jsonSchema'],
587
- default: 'jsonMode',
588
- 'x-ui': {
589
- enumLabels: {
590
- functionCalling: { en_US: 'Function Calling', zh_Hans: '函数调用' },
591
- jsonMode: { en_US: 'JSON Mode', zh_Hans: 'JSON模式' },
592
- jsonSchema: { en_US: 'JSON Schema', zh_Hans: 'JSON架构' },
593
- },
594
- },
595
- },
596
612
  rewriteFallbackText: {
597
613
  type: 'string',
598
614
  title: { en_US: 'Rewrite Fallback Text', zh_Hans: '改写兜底文本' },
@@ -717,30 +733,36 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
717
733
  if (!configurable?.thread_id || !configurable.executionId || !this.commandBus) {
718
734
  return;
719
735
  }
720
- const { thread_id, checkpoint_ns, checkpoint_id, subscriber, executionId } = configurable;
736
+ const { thread_id, checkpoint_ns, checkpoint_id, executionId } = configurable;
721
737
  const snapshot = buildAuditSnapshot();
722
- await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(async () => {
738
+ const writeSnapshot = async () => {
723
739
  return {
724
740
  state: snapshot,
725
741
  output: snapshot,
726
742
  };
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,
743
+ };
744
+ await runWithWrapWorkflowFallback(async () => {
745
+ await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(writeSnapshot, {
746
+ execution: {
747
+ category: 'workflow',
748
+ type: 'middleware',
749
+ title: `${context.node.title} Audit`,
750
+ inputs: {
751
+ mode: snapshot.mode,
752
+ total: snapshot.summary.total,
753
+ },
754
+ parentId: executionId,
755
+ threadId: thread_id,
756
+ checkpointNs: checkpoint_ns,
757
+ checkpointId: checkpoint_id,
758
+ agentKey: context.node.key,
735
759
  },
736
- parentId: executionId,
737
- threadId: thread_id,
738
- checkpointNs: checkpoint_ns,
739
- checkpointId: checkpoint_id,
740
- agentKey: context.node.key,
741
- },
742
- subscriber,
743
- }));
760
+ }));
761
+ return undefined;
762
+ }, async () => {
763
+ await writeSnapshot();
764
+ return undefined;
765
+ });
744
766
  };
745
767
  return {
746
768
  name: SENSITIVE_FILTER_MIDDLEWARE_NAME,
@@ -843,7 +865,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
843
865
  const ensureModel = async () => {
844
866
  const llmConfig = getLlmConfig();
845
867
  if (!modelPromise) {
846
- modelPromise = this.commandBus.execute(new CreateModelClientCommand(llmConfig.model, {
868
+ modelPromise = this.commandBus.execute(new CreateModelClientCommand(buildInternalModelConfig(llmConfig.model), {
847
869
  usageCallback: () => { },
848
870
  }));
849
871
  }
@@ -928,30 +950,36 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
928
950
  if (!configurable?.thread_id || !configurable.executionId || !this.commandBus) {
929
951
  return;
930
952
  }
931
- const { thread_id, checkpoint_ns, checkpoint_id, subscriber, executionId } = configurable;
953
+ const { thread_id, checkpoint_ns, checkpoint_id, executionId } = configurable;
932
954
  const snapshot = buildAuditSnapshot();
933
- await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(async () => {
955
+ const writeSnapshot = async () => {
934
956
  return {
935
957
  state: snapshot,
936
958
  output: snapshot,
937
959
  };
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,
960
+ };
961
+ await runWithWrapWorkflowFallback(async () => {
962
+ await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(writeSnapshot, {
963
+ execution: {
964
+ category: 'workflow',
965
+ type: 'middleware',
966
+ title: `${context.node.title} Audit`,
967
+ inputs: {
968
+ mode: snapshot.mode,
969
+ total: snapshot.summary.total,
970
+ },
971
+ parentId: executionId,
972
+ threadId: thread_id,
973
+ checkpointNs: checkpoint_ns,
974
+ checkpointId: checkpoint_id,
975
+ agentKey: context.node.key,
946
976
  },
947
- parentId: executionId,
948
- threadId: thread_id,
949
- checkpointNs: checkpoint_ns,
950
- checkpointId: checkpoint_id,
951
- agentKey: context.node.key,
952
- },
953
- subscriber,
954
- }));
977
+ }));
978
+ return undefined;
979
+ }, async () => {
980
+ await writeSnapshot();
981
+ return undefined;
982
+ });
955
983
  };
956
984
  const buildEvaluationMessages = (phase, text, llmConfig) => {
957
985
  return [
@@ -977,7 +1005,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
977
1005
  if (!structuredModel) {
978
1006
  throw new Error(`Structured output is not available for method: ${method}`);
979
1007
  }
980
- const raw = await withTimeout(structuredModel.invoke(messages), llmConfig.timeoutMs);
1008
+ const raw = await withTimeout(structuredModel.invoke(messages, INTERNAL_LLM_INVOKE_OPTIONS), llmConfig.timeoutMs);
981
1009
  return {
982
1010
  raw,
983
1011
  trace: {
@@ -996,7 +1024,7 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
996
1024
  }
997
1025
  }
998
1026
  attempts.push('plainText');
999
- const raw = await withTimeout(model.invoke(messages), llmConfig.timeoutMs);
1027
+ const raw = await withTimeout(model.invoke(messages, INTERNAL_LLM_INVOKE_OPTIONS), llmConfig.timeoutMs);
1000
1028
  return {
1001
1029
  raw,
1002
1030
  trace: {
@@ -1013,34 +1041,44 @@ let SensitiveFilterMiddleware = class SensitiveFilterMiddleware {
1013
1041
  return parseLlmDecision(raw, llmConfig.rewriteFallbackText);
1014
1042
  };
1015
1043
  const configurable = (runtime?.configurable ?? {});
1016
- const { thread_id, checkpoint_ns, checkpoint_id, subscriber, executionId } = configurable;
1044
+ const { thread_id, checkpoint_ns, checkpoint_id, executionId } = configurable;
1017
1045
  if (!thread_id || !executionId) {
1018
1046
  return parseCore();
1019
1047
  }
1020
- const tracked = await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(async () => {
1021
- const decision = await parseCore();
1022
- return {
1023
- state: decision,
1024
- output: decision,
1025
- };
1026
- }, {
1027
- execution: {
1028
- category: 'workflow',
1029
- type: 'middleware',
1030
- inputs: {
1031
- phase,
1032
- text,
1048
+ let trackedDecision = null;
1049
+ await runWithWrapWorkflowFallback(async () => {
1050
+ await this.commandBus.execute(new WrapWorkflowNodeExecutionCommand(async () => {
1051
+ const decision = await parseCore();
1052
+ trackedDecision = decision;
1053
+ return {
1054
+ state: decision,
1055
+ output: undefined,
1056
+ };
1057
+ }, {
1058
+ execution: {
1059
+ category: 'workflow',
1060
+ type: 'middleware',
1061
+ inputs: {
1062
+ phase,
1063
+ text,
1064
+ },
1065
+ parentId: executionId,
1066
+ threadId: thread_id,
1067
+ checkpointNs: checkpoint_ns,
1068
+ checkpointId: checkpoint_id,
1069
+ agentKey: context.node.key,
1070
+ title: context.node.title,
1033
1071
  },
1034
- parentId: executionId,
1035
- threadId: thread_id,
1036
- checkpointNs: checkpoint_ns,
1037
- checkpointId: checkpoint_id,
1038
- agentKey: context.node.key,
1039
- title: context.node.title,
1040
- },
1041
- subscriber,
1042
- }));
1043
- return tracked;
1072
+ }));
1073
+ return undefined;
1074
+ }, async () => {
1075
+ trackedDecision = await parseCore();
1076
+ return undefined;
1077
+ });
1078
+ if (!trackedDecision) {
1079
+ throw new Error('LLM decision tracking failed: no decision resolved');
1080
+ }
1081
+ return trackedDecision;
1044
1082
  };
1045
1083
  const resolveOnErrorDecision = (llmConfig, error) => {
1046
1084
  const reason = `llm-error:${error instanceof Error ? error.message : String(error)}`;
@@ -65,17 +65,17 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
65
65
  action: z.ZodNullable<z.ZodOptional<z.ZodEnum<["block", "rewrite"]>>>;
66
66
  replacementText: z.ZodNullable<z.ZodOptional<z.ZodString>>;
67
67
  }, "strip", z.ZodTypeAny, {
68
- type?: "keyword" | "regex";
69
68
  id?: string;
70
69
  pattern?: string;
70
+ type?: "keyword" | "regex";
71
71
  scope?: "input" | "output" | "both";
72
72
  severity?: "high" | "medium";
73
73
  action?: "block" | "rewrite";
74
74
  replacementText?: string;
75
75
  }, {
76
- type?: "keyword" | "regex";
77
76
  id?: string;
78
77
  pattern?: string;
78
+ type?: "keyword" | "regex";
79
79
  scope?: "input" | "output" | "both";
80
80
  severity?: "high" | "medium";
81
81
  action?: "block" | "rewrite";
@@ -95,13 +95,12 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
95
95
  normalize: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
96
96
  llm: z.ZodOptional<z.ZodUnknown>;
97
97
  }, "strip", z.ZodTypeAny, {
98
- normalize?: boolean;
99
98
  llm?: unknown;
100
99
  mode?: "rule";
101
100
  rules?: {
102
- type?: "keyword" | "regex";
103
101
  id?: string;
104
102
  pattern?: string;
103
+ type?: "keyword" | "regex";
105
104
  scope?: "input" | "output" | "both";
106
105
  severity?: "high" | "medium";
107
106
  action?: "block" | "rewrite";
@@ -112,14 +111,14 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
112
111
  profile?: "strict" | "balanced";
113
112
  };
114
113
  caseSensitive?: boolean;
115
- }, {
116
114
  normalize?: boolean;
115
+ }, {
117
116
  llm?: unknown;
118
117
  mode?: "rule";
119
118
  rules?: {
120
- type?: "keyword" | "regex";
121
119
  id?: string;
122
120
  pattern?: string;
121
+ type?: "keyword" | "regex";
123
122
  scope?: "input" | "output" | "both";
124
123
  severity?: "high" | "medium";
125
124
  action?: "block" | "rewrite";
@@ -130,6 +129,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
130
129
  profile?: "strict" | "balanced";
131
130
  };
132
131
  caseSensitive?: boolean;
132
+ normalize?: boolean;
133
133
  }>, z.ZodObject<{
134
134
  mode: z.ZodLiteral<"llm">;
135
135
  llm: z.ZodDefault<z.ZodNullable<z.ZodOptional<z.ZodObject<{
@@ -144,8 +144,8 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
144
144
  rewriteFallbackText: z.ZodNullable<z.ZodOptional<z.ZodString>>;
145
145
  timeoutMs: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
146
146
  }, "strip", z.ZodTypeAny, {
147
- model?: ICopilotModel;
148
147
  scope?: "input" | "output" | "both";
148
+ model?: ICopilotModel;
149
149
  rulePrompt?: string;
150
150
  systemPrompt?: string;
151
151
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
@@ -155,8 +155,8 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
155
155
  rewriteFallbackText?: string;
156
156
  timeoutMs?: number;
157
157
  }, {
158
- model?: ICopilotModel;
159
158
  scope?: "input" | "output" | "both";
159
+ model?: ICopilotModel;
160
160
  rulePrompt?: string;
161
161
  systemPrompt?: string;
162
162
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
@@ -171,10 +171,9 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
171
171
  caseSensitive: z.ZodOptional<z.ZodUnknown>;
172
172
  normalize: z.ZodOptional<z.ZodUnknown>;
173
173
  }, "strip", z.ZodTypeAny, {
174
- normalize?: unknown;
175
174
  llm?: {
176
- model?: ICopilotModel;
177
175
  scope?: "input" | "output" | "both";
176
+ model?: ICopilotModel;
178
177
  rulePrompt?: string;
179
178
  systemPrompt?: string;
180
179
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
@@ -188,11 +187,11 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
188
187
  rules?: unknown;
189
188
  generalPack?: unknown;
190
189
  caseSensitive?: unknown;
191
- }, {
192
190
  normalize?: unknown;
191
+ }, {
193
192
  llm?: {
194
- model?: ICopilotModel;
195
193
  scope?: "input" | "output" | "both";
194
+ model?: ICopilotModel;
196
195
  rulePrompt?: string;
197
196
  systemPrompt?: string;
198
197
  outputMethod?: "functionCalling" | "jsonMode" | "jsonSchema";
@@ -206,6 +205,7 @@ export declare const sensitiveFilterConfigSchema: z.ZodUnion<[z.ZodObject<{
206
205
  rules?: unknown;
207
206
  generalPack?: unknown;
208
207
  caseSensitive?: unknown;
208
+ normalize?: unknown;
209
209
  }>]>;
210
210
  export declare const llmDecisionSchema: z.ZodObject<{
211
211
  matched: z.ZodBoolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-sensitive-filter-xr",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "author": {
5
5
  "name": "XpertAI",
6
6
  "url": "https://xpertai.cn"