@dsh-edge/dsh-edge-agent 0.0.0-stage → 0.1.2
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/CHANGELOG.md +158 -0
- package/LICENSE +21 -0
- package/README.md +181 -2
- package/config/edge.example.json +179 -0
- package/config/privacy-policy.json +202 -0
- package/cordis.desktop-safe.patch.yml +129 -0
- package/cordis.patch.yml +382 -0
- package/cordis.yml +374 -0
- package/lib/attestation.d.ts +2 -0
- package/lib/attestation.d.ts.map +1 -0
- package/lib/attestation.js +2 -0
- package/lib/audit-log.d.ts +2 -0
- package/lib/audit-log.d.ts.map +1 -0
- package/lib/audit-log.js +2 -0
- package/lib/client.js +87 -0
- package/lib/cloud-llm-adapter.d.ts +2 -0
- package/lib/cloud-llm-adapter.d.ts.map +1 -0
- package/lib/cloud-llm-adapter.js +2 -0
- package/lib/dsh-adapter/attestation.d.ts +42 -0
- package/lib/dsh-adapter/attestation.d.ts.map +1 -0
- package/lib/dsh-adapter/attestation.js +40 -0
- package/lib/dsh-adapter/audit-log.d.ts +139 -0
- package/lib/dsh-adapter/audit-log.d.ts.map +1 -0
- package/lib/dsh-adapter/audit-log.js +376 -0
- package/lib/dsh-adapter/cloud-egress.d.ts +181 -0
- package/lib/dsh-adapter/cloud-egress.d.ts.map +1 -0
- package/lib/dsh-adapter/cloud-egress.js +257 -0
- package/lib/dsh-adapter/cloud-llm-adapter.d.ts +416 -0
- package/lib/dsh-adapter/cloud-llm-adapter.d.ts.map +1 -0
- package/lib/dsh-adapter/cloud-llm-adapter.js +1472 -0
- package/lib/dsh-adapter/contract.d.ts +80 -0
- package/lib/dsh-adapter/contract.d.ts.map +1 -0
- package/lib/dsh-adapter/contract.js +194 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.d.ts +214 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.d.ts.map +1 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.js +409 -0
- package/lib/dsh-adapter/dsh-edge-agent.d.ts +48 -0
- package/lib/dsh-adapter/dsh-edge-agent.d.ts.map +1 -0
- package/lib/dsh-adapter/dsh-edge-agent.js +64 -0
- package/lib/dsh-adapter/edge-config.d.ts +484 -0
- package/lib/dsh-adapter/edge-config.d.ts.map +1 -0
- package/lib/dsh-adapter/edge-config.js +898 -0
- package/lib/dsh-adapter/edge-context-view.d.ts +184 -0
- package/lib/dsh-adapter/edge-context-view.d.ts.map +1 -0
- package/lib/dsh-adapter/edge-context-view.js +483 -0
- package/lib/dsh-adapter/effective-model.d.ts +85 -0
- package/lib/dsh-adapter/effective-model.d.ts.map +1 -0
- package/lib/dsh-adapter/effective-model.js +171 -0
- package/lib/dsh-adapter/etscli-shell-guard.d.ts +244 -0
- package/lib/dsh-adapter/etscli-shell-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/etscli-shell-guard.js +420 -0
- package/lib/dsh-adapter/etscli-tools.d.ts +112 -0
- package/lib/dsh-adapter/etscli-tools.d.ts.map +1 -0
- package/lib/dsh-adapter/etscli-tools.js +393 -0
- package/lib/dsh-adapter/file-logger.d.ts +294 -0
- package/lib/dsh-adapter/file-logger.d.ts.map +1 -0
- package/lib/dsh-adapter/file-logger.js +564 -0
- package/lib/dsh-adapter/function-call-external.d.ts +357 -0
- package/lib/dsh-adapter/function-call-external.d.ts.map +1 -0
- package/lib/dsh-adapter/function-call-external.js +1005 -0
- package/lib/dsh-adapter/host-egress-guard.d.ts +161 -0
- package/lib/dsh-adapter/host-egress-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/host-egress-guard.js +213 -0
- package/lib/dsh-adapter/host-llm-trace.d.ts +198 -0
- package/lib/dsh-adapter/host-llm-trace.d.ts.map +1 -0
- package/lib/dsh-adapter/host-llm-trace.js +344 -0
- package/lib/dsh-adapter/http-api-routes.d.ts +61 -0
- package/lib/dsh-adapter/http-api-routes.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api-routes.js +79 -0
- package/lib/dsh-adapter/http-api-sessions.d.ts +105 -0
- package/lib/dsh-adapter/http-api-sessions.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api-sessions.js +173 -0
- package/lib/dsh-adapter/http-api.d.ts +221 -0
- package/lib/dsh-adapter/http-api.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api.js +1811 -0
- package/lib/dsh-adapter/index.d.ts +62 -0
- package/lib/dsh-adapter/index.d.ts.map +1 -0
- package/lib/dsh-adapter/index.js +97 -0
- package/lib/dsh-adapter/input-guard-messages.d.ts +56 -0
- package/lib/dsh-adapter/input-guard-messages.d.ts.map +1 -0
- package/lib/dsh-adapter/input-guard-messages.js +136 -0
- package/lib/dsh-adapter/input-guard.d.ts +508 -0
- package/lib/dsh-adapter/input-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/input-guard.js +2174 -0
- package/lib/dsh-adapter/intent-prompts.d.ts +77 -0
- package/lib/dsh-adapter/intent-prompts.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-prompts.js +97 -0
- package/lib/dsh-adapter/intent-router.d.ts +710 -0
- package/lib/dsh-adapter/intent-router.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-router.js +2802 -0
- package/lib/dsh-adapter/intent-signals.d.ts +208 -0
- package/lib/dsh-adapter/intent-signals.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-signals.js +391 -0
- package/lib/dsh-adapter/invariant-install.d.ts +16 -0
- package/lib/dsh-adapter/invariant-install.d.ts.map +1 -0
- package/lib/dsh-adapter/invariant-install.js +15 -0
- package/lib/dsh-adapter/invariant.d.ts +16 -0
- package/lib/dsh-adapter/invariant.d.ts.map +1 -0
- package/lib/dsh-adapter/invariant.js +22 -0
- package/lib/dsh-adapter/llm-adapters.d.ts +19 -0
- package/lib/dsh-adapter/llm-adapters.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-adapters.js +16 -0
- package/lib/dsh-adapter/llm-internal-caller.d.ts +92 -0
- package/lib/dsh-adapter/llm-internal-caller.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-internal-caller.js +100 -0
- package/lib/dsh-adapter/llm-max-turns.d.ts +55 -0
- package/lib/dsh-adapter/llm-max-turns.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-max-turns.js +127 -0
- package/lib/dsh-adapter/llm-trace-store.d.ts +131 -0
- package/lib/dsh-adapter/llm-trace-store.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-trace-store.js +205 -0
- package/lib/dsh-adapter/llm-trace.d.ts +450 -0
- package/lib/dsh-adapter/llm-trace.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-trace.js +463 -0
- package/lib/dsh-adapter/local-llm-adapter.d.ts +141 -0
- package/lib/dsh-adapter/local-llm-adapter.d.ts.map +1 -0
- package/lib/dsh-adapter/local-llm-adapter.js +787 -0
- package/lib/dsh-adapter/local-storage.d.ts +96 -0
- package/lib/dsh-adapter/local-storage.d.ts.map +1 -0
- package/lib/dsh-adapter/local-storage.js +198 -0
- package/lib/dsh-adapter/model-router.d.ts +79 -0
- package/lib/dsh-adapter/model-router.d.ts.map +1 -0
- package/lib/dsh-adapter/model-router.js +107 -0
- package/lib/dsh-adapter/ohttp-gateway.d.ts +49 -0
- package/lib/dsh-adapter/ohttp-gateway.d.ts.map +1 -0
- package/lib/dsh-adapter/ohttp-gateway.js +32 -0
- package/lib/dsh-adapter/openai-sse.d.ts +85 -0
- package/lib/dsh-adapter/openai-sse.d.ts.map +1 -0
- package/lib/dsh-adapter/openai-sse.js +462 -0
- package/lib/dsh-adapter/pii-utils.d.ts +96 -0
- package/lib/dsh-adapter/pii-utils.d.ts.map +1 -0
- package/lib/dsh-adapter/pii-utils.js +324 -0
- package/lib/dsh-adapter/plugin-host.d.ts +38 -0
- package/lib/dsh-adapter/plugin-host.d.ts.map +1 -0
- package/lib/dsh-adapter/plugin-host.js +38 -0
- package/lib/dsh-adapter/plugin.d.ts +46 -0
- package/lib/dsh-adapter/plugin.d.ts.map +1 -0
- package/lib/dsh-adapter/plugin.js +100 -0
- package/lib/dsh-adapter/privacy-policy.d.ts +237 -0
- package/lib/dsh-adapter/privacy-policy.d.ts.map +1 -0
- package/lib/dsh-adapter/privacy-policy.js +391 -0
- package/lib/dsh-adapter/prompt-guard.d.ts +152 -0
- package/lib/dsh-adapter/prompt-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/prompt-guard.js +872 -0
- package/lib/dsh-adapter/realm-skill-bridge.d.ts +365 -0
- package/lib/dsh-adapter/realm-skill-bridge.d.ts.map +1 -0
- package/lib/dsh-adapter/realm-skill-bridge.js +1180 -0
- package/lib/dsh-adapter/sanitizer-pipeline.d.ts +140 -0
- package/lib/dsh-adapter/sanitizer-pipeline.d.ts.map +1 -0
- package/lib/dsh-adapter/sanitizer-pipeline.js +431 -0
- package/lib/dsh-adapter/secure-bridge.d.ts +47 -0
- package/lib/dsh-adapter/secure-bridge.d.ts.map +1 -0
- package/lib/dsh-adapter/secure-bridge.js +35 -0
- package/lib/dsh-adapter/session-attribution.d.ts +70 -0
- package/lib/dsh-adapter/session-attribution.d.ts.map +1 -0
- package/lib/dsh-adapter/session-attribution.js +76 -0
- package/lib/dsh-adapter/sessions.d.ts +86 -0
- package/lib/dsh-adapter/sessions.d.ts.map +1 -0
- package/lib/dsh-adapter/sessions.js +179 -0
- package/lib/dsh-adapter/tcm.d.ts +55 -0
- package/lib/dsh-adapter/tcm.d.ts.map +1 -0
- package/lib/dsh-adapter/tcm.js +94 -0
- package/lib/dsh-adapter/td-bus.d.ts +87 -0
- package/lib/dsh-adapter/td-bus.d.ts.map +1 -0
- package/lib/dsh-adapter/td-bus.js +196 -0
- package/lib/dsh-adapter/tool-runner.d.ts +185 -0
- package/lib/dsh-adapter/tool-runner.d.ts.map +1 -0
- package/lib/dsh-adapter/tool-runner.js +271 -0
- package/lib/dsh-adapter/tools.d.ts +118 -0
- package/lib/dsh-adapter/tools.d.ts.map +1 -0
- package/lib/dsh-adapter/tools.js +292 -0
- package/lib/dsh-adapter/types.d.ts +23 -0
- package/lib/dsh-adapter/types.d.ts.map +1 -0
- package/lib/dsh-adapter/types.js +16 -0
- package/lib/dsh-adapter/voice-input.d.ts +30 -0
- package/lib/dsh-adapter/voice-input.d.ts.map +1 -0
- package/lib/dsh-adapter/voice-input.js +34 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.d.ts +152 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.d.ts.map +1 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.js +289 -0
- package/lib/http-api.d.ts +2 -0
- package/lib/http-api.d.ts.map +1 -0
- package/lib/http-api.js +2 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +49 -0
- package/lib/intent-router.d.ts +2 -0
- package/lib/intent-router.d.ts.map +1 -0
- package/lib/intent-router.js +2 -0
- package/lib/llm-adapters.d.ts +2 -0
- package/lib/llm-adapters.d.ts.map +1 -0
- package/lib/llm-adapters.js +2 -0
- package/lib/local-llm-adapter.d.ts +2 -0
- package/lib/local-llm-adapter.d.ts.map +1 -0
- package/lib/local-llm-adapter.js +2 -0
- package/lib/local-storage.d.ts +2 -0
- package/lib/local-storage.d.ts.map +1 -0
- package/lib/local-storage.js +2 -0
- package/lib/model-router.d.ts +2 -0
- package/lib/model-router.d.ts.map +1 -0
- package/lib/model-router.js +2 -0
- package/lib/ohttp-gateway.d.ts +2 -0
- package/lib/ohttp-gateway.d.ts.map +1 -0
- package/lib/ohttp-gateway.js +2 -0
- package/lib/plugin-host.d.ts +2 -0
- package/lib/plugin-host.d.ts.map +1 -0
- package/lib/plugin-host.js +2 -0
- package/lib/privacy-policy.d.ts +2 -0
- package/lib/privacy-policy.d.ts.map +1 -0
- package/lib/privacy-policy.js +2 -0
- package/lib/prompt-guard.d.ts +2 -0
- package/lib/prompt-guard.d.ts.map +1 -0
- package/lib/prompt-guard.js +2 -0
- package/lib/sanitizer-pipeline.d.ts +2 -0
- package/lib/sanitizer-pipeline.d.ts.map +1 -0
- package/lib/sanitizer-pipeline.js +2 -0
- package/lib/secure-bridge.d.ts +2 -0
- package/lib/secure-bridge.d.ts.map +1 -0
- package/lib/secure-bridge.js +2 -0
- package/lib/sessions.d.ts +2 -0
- package/lib/sessions.d.ts.map +1 -0
- package/lib/sessions.js +2 -0
- package/lib/tcm.d.ts +2 -0
- package/lib/tcm.d.ts.map +1 -0
- package/lib/tcm.js +2 -0
- package/lib/td-bus.d.ts +2 -0
- package/lib/td-bus.d.ts.map +1 -0
- package/lib/td-bus.js +2 -0
- package/lib/tools.d.ts +2 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +2 -0
- package/lib/voice-input.d.ts +2 -0
- package/lib/voice-input.d.ts.map +1 -0
- package/lib/voice-input.js +2 -0
- package/package.json +253 -3
- package/presets/agent-preset-edge.json +50 -0
- package/presets/edge.desktop-safe.patch.yml +503 -0
- package/presets/edge.patch.yml +557 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* input-guard — 端侧隐私安全管线的编排器。
|
|
3
|
+
*
|
|
4
|
+
* 把安全/隐私/脱敏逻辑从 transport 层(http-api)上移到 Agent Loop 的
|
|
5
|
+
* 事件链上,让所有入口(http-api / Web UI / CLI / SDK / 未来的 WebSocket
|
|
6
|
+
* 或 gRPC)自动统一走同一套管线。谁也无法绕过。
|
|
7
|
+
*
|
|
8
|
+
* 正向(agent/pre-step):fail-closed 路由闸门 → 攻击检测 → 策略判定 →
|
|
9
|
+
* 意图分类 → 脱敏
|
|
10
|
+
* 反向(llm/stream):对 text-delta chunk 流式反脱敏
|
|
11
|
+
*
|
|
12
|
+
* §7.175 (2026-11-11): 路由改写退役。input-guard 不在 agent/request 里改写
|
|
13
|
+
* provider/model(宿主的 prepareRequest() 会对改写直接抛错)。
|
|
14
|
+
*
|
|
15
|
+
* §7.184 (2026-11-11,主人裁决「选择器完全不动,实际请求改道到本地池」):
|
|
16
|
+
* - **会话选型不再被改写**(§7.176 的钉选 / §7.179 的补钉 / §7.183 的回钉全部
|
|
17
|
+
* 退役)⇒ 宿主模型选择器保持主人选的那个,不跳。
|
|
18
|
+
* - 「本轮必须留在设备上」这条不变式改由**出网那一跳**保证:LOCAL 轮次的请求
|
|
19
|
+
* 在 `llm/stream` 被换成 provider = 本地池(见 handleLlmStream)。
|
|
20
|
+
* - 旧口径(§7.175:判定 LOCAL 而选型不是本地 ⇒ 拒绝这一轮)随之退役 ——
|
|
21
|
+
* 能改道就不必拒;改不动时仍然 fail-closed(抛错,绝不放行到云端)。
|
|
22
|
+
*
|
|
23
|
+
* 设计详见:docs/input-guard-design.md
|
|
24
|
+
*
|
|
25
|
+
* §7.123 (2026-10-27): the pure message-array helpers (find / replace user
|
|
26
|
+
* turns) moved to `./input-guard-messages.ts` — they encode the
|
|
27
|
+
* payload.messages vs decision.messages contract, which is the part of this
|
|
28
|
+
* module that upstream keeps changing. This file keeps the hook LOGIC.
|
|
29
|
+
*
|
|
30
|
+
* @module dsh-edge-agent/input-guard
|
|
31
|
+
*/
|
|
32
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
33
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
34
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
35
|
+
import { type LocalPoolShape } from './effective-model.js';
|
|
36
|
+
export declare const name = "input-guard";
|
|
37
|
+
export declare const inject: readonly ["promptGuard", "privacyPolicy", "intentRouter", "sanitizer", "auditLog", "realmSkillBridge"];
|
|
38
|
+
export interface Config {
|
|
39
|
+
/** 总开关。false 时所有 hook 都直接 pass-through(debug / 紧急回退用)。*/
|
|
40
|
+
enableInputGuard: boolean;
|
|
41
|
+
/** LOCAL 路径是否也走脱敏(默认 false:LOCAL LLM 看到原文,不上云无需脱敏)。*/
|
|
42
|
+
sanitizeLocalTraffic: boolean;
|
|
43
|
+
/** 攻击检测 / privacy-policy REJECT → 阻断 step(user 行为不端)。
|
|
44
|
+
* false = 仅审计 + 放行(fail-open on policy;不推荐)。*/
|
|
45
|
+
rejectOnPolicyViolation: boolean;
|
|
46
|
+
/** Sanitize 失败 → 是否阻断本轮(系统故障而非 user 错)。
|
|
47
|
+
*
|
|
48
|
+
* §7.175 (2026-11-11): 曾经这个开关表示「降级到 LOCAL 路由」——
|
|
49
|
+
* input-guard 在 agent/request 里改写 provider/model。**改写已经退役**
|
|
50
|
+
* (宿主的 prepareRequest() 会因为改写直接抛错),路由恒由宿主的
|
|
51
|
+
* modelSelection 决定。现在它只表示「脱敏失败要不要拦下这一轮」:
|
|
52
|
+
* false = REJECT step(user 看到空 result);true = 放行。
|
|
53
|
+
* 默认 true — 边缘模式恒走本地模型,原始 PII 不出境。*/
|
|
54
|
+
downgradeOnSanitizeFailure: boolean;
|
|
55
|
+
/** 本地 provider 名。必须与本地 LLM adapter 注册名一致,也是 §7.175
|
|
56
|
+
* fail-closed 闸门认定的「本地」:宿主 modelSelection.provider 必须等于
|
|
57
|
+
* 它,否则本轮被拒(绝不放行到可能上云的 provider)。*/
|
|
58
|
+
localProvider: string;
|
|
59
|
+
localModel: string;
|
|
60
|
+
/** 2026-09-03 §7.35: Cloud adapter 读 key 的 env slot 名。
|
|
61
|
+
* 与 cloud-llm-adapter.Config.apiKeyEnv 默认一致('DEEPSEEK_API_KEY'),
|
|
62
|
+
* 也尊重 cordis.patch.yml:41 的 DSH_EDGE_CLOUD_KEY_ENV 覆盖。
|
|
63
|
+
*
|
|
64
|
+
* §7.175 (2026-11-11): **input-guard 不再消费本项**。边缘模式恒用本地
|
|
65
|
+
* 模型(主人裁决),云池不参与任何路由判定;「云端不可用 → 降级 LOCAL」
|
|
66
|
+
* 那条路径随路由改写一起退役。本项保留在 schema 里只是为了不破坏既有
|
|
67
|
+
* 配置文件(cloud-llm-adapter 仍按自己的同名项读它)。*/
|
|
68
|
+
cloudApiKeyEnv: string;
|
|
69
|
+
/** 2026-09-07 §7.43 fix: TUI / dsh-web-app chat 入口不会写 agent._edgeRoute
|
|
70
|
+
* (只有 http-api 写),所以 handlePreStep 拿不到"如果没降级本来会路由到
|
|
71
|
+
* 哪个 provider"。曾用作「降级前原路由」的 fallback。
|
|
72
|
+
*
|
|
73
|
+
* §7.175 (2026-11-11): 降级机制退役后本项**不再被 input-guard 读取**
|
|
74
|
+
* (路由恒由宿主的 modelSelection 决定)。保留在 schema 里只是为了不
|
|
75
|
+
* 破坏既有配置文件。*/
|
|
76
|
+
defaultCloudProvider: string;
|
|
77
|
+
defaultCloudModel: string;
|
|
78
|
+
/** §7.179 (2026-11-11): 本插件所在 preset 的 id。
|
|
79
|
+
*
|
|
80
|
+
* 用途:`agent-preset/selected` 事件是**全局**事件(宿主 registry 直接
|
|
81
|
+
* `ctx.emit('agent-preset/selected', session.id, presetId)`,不带 scope 载体)
|
|
82
|
+
* ⇒ 所有工作方式的监听器都收到 ⇒ 必须自己按 presetId 过滤,否则会给
|
|
83
|
+
* 标准模式的会话也钉上 local-llm(那会改掉宿主的选择,违反 §7.140
|
|
84
|
+
* 「标准模式零 edge 事件」)。
|
|
85
|
+
*
|
|
86
|
+
* 这个值必须与 preset yml 里的 `config.id` 一致 —— scripts/verify-preset-isolate.ts
|
|
87
|
+
* 复核这一点(不一致即门禁失败),所以它不是「两份各写各的」,而是**被门禁
|
|
88
|
+
* 锁住的一致量**。*/
|
|
89
|
+
presetId: string;
|
|
90
|
+
}
|
|
91
|
+
export declare const Config: Schema<Config>;
|
|
92
|
+
/** Minimal prompt-guard 接口(只暴露 input-guard 用到的方法)。*/
|
|
93
|
+
interface PromptGuardLike {
|
|
94
|
+
checkInput(text: string, sessionId: string): Promise<{
|
|
95
|
+
safe: boolean;
|
|
96
|
+
reason?: string;
|
|
97
|
+
riskLevel?: 'low' | 'medium' | 'high';
|
|
98
|
+
}>;
|
|
99
|
+
checkOutput?(text: string, context?: {
|
|
100
|
+
sanitized?: boolean;
|
|
101
|
+
}): Promise<{
|
|
102
|
+
filtered: string;
|
|
103
|
+
redacted: Array<{
|
|
104
|
+
type: string;
|
|
105
|
+
masked: string;
|
|
106
|
+
}>;
|
|
107
|
+
}>;
|
|
108
|
+
}
|
|
109
|
+
/** Minimal privacy-policy 接口。*/
|
|
110
|
+
interface PrivacyPolicyLike {
|
|
111
|
+
matchRouting(text: string): {
|
|
112
|
+
realm?: string;
|
|
113
|
+
confidence?: number;
|
|
114
|
+
rationale?: string;
|
|
115
|
+
} | null | undefined;
|
|
116
|
+
}
|
|
117
|
+
/** Minimal intent-router 接口。*/
|
|
118
|
+
interface IntentRouterLike {
|
|
119
|
+
classify(text: string, context?: {
|
|
120
|
+
recentMessages?: unknown[];
|
|
121
|
+
sessionId?: string;
|
|
122
|
+
}): Promise<{
|
|
123
|
+
category: 'LOCAL' | 'CLOUD' | 'SANITIZED' | 'REJECT';
|
|
124
|
+
confidence: number;
|
|
125
|
+
rationale?: string;
|
|
126
|
+
}>;
|
|
127
|
+
}
|
|
128
|
+
/** Minimal sanitizer 接口。
|
|
129
|
+
* §7.123 (2026-10-27): the replacement shape was STALE here — it declared
|
|
130
|
+
* `{ type, original, token }` while sanitizer-pipeline's real `Replacement`
|
|
131
|
+
* is `{ original, placeholder, piiType, dataClass, keptPrefix? }`. The
|
|
132
|
+
* mismatch was invisible while input-guard only read `.length`; now that
|
|
133
|
+
* the audit records which PII types were hit, the interface must match
|
|
134
|
+
* the real contract. */
|
|
135
|
+
interface SanitizerLike {
|
|
136
|
+
sanitize(text: string, context?: {
|
|
137
|
+
sessionId?: string;
|
|
138
|
+
}): Promise<{
|
|
139
|
+
sanitizedText: string;
|
|
140
|
+
sessionId: string;
|
|
141
|
+
replacements: Array<{
|
|
142
|
+
original: string;
|
|
143
|
+
placeholder: string;
|
|
144
|
+
piiType: string;
|
|
145
|
+
dataClass: string;
|
|
146
|
+
keptPrefix?: string;
|
|
147
|
+
}>;
|
|
148
|
+
}>;
|
|
149
|
+
desanitize(text: string, sessionId: string): Promise<string>;
|
|
150
|
+
}
|
|
151
|
+
/** Minimal audit-log 接口。*/
|
|
152
|
+
interface AuditLogLike {
|
|
153
|
+
log(entry: {
|
|
154
|
+
level: 'debug' | 'info' | 'warn' | 'error' | 'security';
|
|
155
|
+
kind: string;
|
|
156
|
+
source: string;
|
|
157
|
+
payload: Record<string, unknown>;
|
|
158
|
+
}): Promise<unknown>;
|
|
159
|
+
}
|
|
160
|
+
/** StreamChunk 形状(来自 @deepseek-ai/dsh-llm,避免深层依赖)。*/
|
|
161
|
+
interface StreamChunkLike {
|
|
162
|
+
type: string;
|
|
163
|
+
text?: string;
|
|
164
|
+
[k: string]: unknown;
|
|
165
|
+
}
|
|
166
|
+
declare module '@deepseek-ai/cordis' {
|
|
167
|
+
interface Context {
|
|
168
|
+
inputGuard: InputGuardServiceImpl;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Per-session 的"这一步做了脱敏"状态。按 sessionId 存在普通 Map 里
|
|
172
|
+
* (pre-step 的 session 引用与 llm/stream 的 sessionId 字符串不是同一
|
|
173
|
+
* 对象,WeakMap 回收不了),靠 SESSION_STATE_CAP 的 LRU 淘汰兜住。*/
|
|
174
|
+
export interface StepSanitizationState {
|
|
175
|
+
/** 当前 turn/step 是否做了脱敏。每次 agent/pre-step 入口重置。*/
|
|
176
|
+
sanitized: boolean;
|
|
177
|
+
/** 替换次数(0 = 没替换,无需 desanitize)。*/
|
|
178
|
+
replacements: number;
|
|
179
|
+
/** 触发脱敏时的 session id(冗余但便于审计)。*/
|
|
180
|
+
sessionId: string;
|
|
181
|
+
/** 时间戳,便于陈旧检查。*/
|
|
182
|
+
timestamp: number;
|
|
183
|
+
/** 入口路径('http-api' = _edgeRoute 已设;'webui' = 未设)。
|
|
184
|
+
* §7.175 起它只描述「这一轮的路由决策从哪来」,不再用于改写路由。*/
|
|
185
|
+
routeSource?: 'http-api' | 'webui';
|
|
186
|
+
}
|
|
187
|
+
/** 上一路由决策:被 input-guard 在 agent/pre-step 读取,避免重复 classify()。*/
|
|
188
|
+
interface AgentEdgeRoute {
|
|
189
|
+
realm: 'LOCAL' | 'CLOUD' | 'SANITIZED';
|
|
190
|
+
category: 'LOCAL' | 'CLOUD' | 'SANITIZED' | 'REJECT';
|
|
191
|
+
provider: string;
|
|
192
|
+
model: string;
|
|
193
|
+
confidence: number;
|
|
194
|
+
rationale?: string;
|
|
195
|
+
}
|
|
196
|
+
/** agent/request waterfall 返回的 config 形状(DSH agent-loop 的 contract)。*/
|
|
197
|
+
interface AgentRequestConfig {
|
|
198
|
+
provider: string;
|
|
199
|
+
model: string;
|
|
200
|
+
reasoningEffort?: number;
|
|
201
|
+
maxTokens?: number;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* input-guard 服务。
|
|
205
|
+
*
|
|
206
|
+
* 三个全局 hook:
|
|
207
|
+
* - `agent/pre-step`(正向):**fail-closed 路由闸门** → 攻击 → 策略 →
|
|
208
|
+
* 分类 → 脱敏
|
|
209
|
+
* - `agent/request`(只读放行):§7.175 起**不再改写** provider/model
|
|
210
|
+
* - `llm/stream`(反向):§7.184 起把 LOCAL 轮次的请求**改道**到本地池;
|
|
211
|
+
* 并对 text-delta chunk 流式反脱敏
|
|
212
|
+
*
|
|
213
|
+
* 跨 hook 状态用 Map<sessionId, StepSanitizationState> 维护。
|
|
214
|
+
*/
|
|
215
|
+
export declare class InputGuardServiceImpl extends Service {
|
|
216
|
+
private readonly config;
|
|
217
|
+
private readonly promptGuard;
|
|
218
|
+
private readonly privacyPolicy;
|
|
219
|
+
private readonly intentRouter;
|
|
220
|
+
private readonly sanitizer;
|
|
221
|
+
private readonly auditLog;
|
|
222
|
+
/** sessionId → 当前 step 的脱敏状态。用 Map<string, state> 而不是 WeakMap<session>
|
|
223
|
+
* 是因为 pre-step 的 session 引用和 llm/stream options 上的 sessionId
|
|
224
|
+
* 对应的不是同一个对象(pre-step 拿到的是 agent 实例的 session,
|
|
225
|
+
* llm/stream 拿到的是 LLM request envelope 上的 sessionId 字符串)。*/
|
|
226
|
+
private readonly stepState;
|
|
227
|
+
/**
|
|
228
|
+
* §7.106 (2026-10-23): last routed category per session. The attack-block
|
|
229
|
+
* gate consults it: a LOCAL turn cannot egress anything, so credential-flavored
|
|
230
|
+
* "leak" detections are the user operating their own device (密盒登录类),
|
|
231
|
+
* which the operator ruled must pass — warn instead of block. Injection-class
|
|
232
|
+
* reasons stay blocked even under LOCAL (injection can drive local tools).
|
|
233
|
+
* The egress risk of the same credentials later riding session history into a
|
|
234
|
+
* CLOUD turn is closed by the sanitizer's new `credential` PII class, not by
|
|
235
|
+
* this exemption.
|
|
236
|
+
*/
|
|
237
|
+
private readonly turnCategory;
|
|
238
|
+
/**
|
|
239
|
+
* §7.144: LLM trace 的行为配置(`audit.llmTrace` 段)。
|
|
240
|
+
*
|
|
241
|
+
* 构造时读一次 —— 这是**观测**配置,不是路由配置,读一次足够;每次
|
|
242
|
+
* 读盘会在每个 LLM 往返上多一次 IO。
|
|
243
|
+
*/
|
|
244
|
+
private readonly traceConfig;
|
|
245
|
+
/**
|
|
246
|
+
* §7.151: 出网守卫(宿主原生 provider 路径的 §7.131 裁剪)。
|
|
247
|
+
*
|
|
248
|
+
* 必须在**请求派发之前**裁 —— 而宿主把 LOOP 构建的请求 deep-frozen
|
|
249
|
+
* 送来(监听器只读不改),所以裁完要**换掉请求体重新派发**。守卫自己
|
|
250
|
+
* 持有「已裁过」的标记集合,避免重新派发时无限递归。
|
|
251
|
+
*/
|
|
252
|
+
private readonly hostEgressGuard;
|
|
253
|
+
/**
|
|
254
|
+
* §7.175 (2026-11-11): 「云池现在到底可用吗」的探针(§7.35 / §7.100)随
|
|
255
|
+
* 路由改写一起退役 —— 边缘模式恒用本地模型(主人裁决),云池不参与任何
|
|
256
|
+
* 路由判定。留着它只会把「云端不可用」报成「路由缺失」。*/
|
|
257
|
+
/**
|
|
258
|
+
* §7.171 (2026-11-10): "本地路由现在到底可用吗" —— local-llm 有两种上线方式:
|
|
259
|
+
* 配置了 `llm.local.baseUrl` → 直连自有池;没配置 → DELEGATE 模式(转发宿主
|
|
260
|
+
* 提供商,见 local-llm-adapter.apply)。**两种情况下路由都是注册了的** ⇒
|
|
261
|
+
* `modelRouter.isAvailable('local-llm')` 即可判据,与直连时完全一致。
|
|
262
|
+
*
|
|
263
|
+
* 只有 `llm.local.delegate=false`(kill switch)才会让路由不存在 —— 那时
|
|
264
|
+
* intent=LOCAL 按隐私裁决必须走本地模型,在改写 provider 之前必须确认目标
|
|
265
|
+
* 路由真的存在,否则会把「本地」意图发给一条不存在的路由。
|
|
266
|
+
*
|
|
267
|
+
* 旧上游 / fake ctx 没有 listProviders 时无法证伪 → 保持旧行为(照常改写),
|
|
268
|
+
* 不让老环境因这次收紧而回归。
|
|
269
|
+
*/
|
|
270
|
+
private localAvailable;
|
|
271
|
+
/**
|
|
272
|
+
* §7.180: 本地池现状 —— 直连(配了 `llm.local.baseUrl`)还是 DELEGATE
|
|
273
|
+
* (没配端点,转发宿主提供商,§7.171),连同 model / delegate 旋钮。
|
|
274
|
+
*
|
|
275
|
+
* 补钉用的 model id 由这份现状**推导**(而不是另读一份宿主选择)⇒ 与
|
|
276
|
+
* wire、模型目录同源(见 §7.180)。
|
|
277
|
+
*
|
|
278
|
+
* protected 而不是 private:单测子类化覆写它,不必依赖机器上那份
|
|
279
|
+
* `~/.dsh-edge-agent/edge.json` 的真实内容。
|
|
280
|
+
*/
|
|
281
|
+
protected localPoolShape(): LocalPoolShape;
|
|
282
|
+
/**
|
|
283
|
+
* §7.184 (2026-11-11): 本轮判定 LOCAL 时,把实际请求**改道**到本地池。
|
|
284
|
+
*
|
|
285
|
+
* 背景(主人裁决 2026-11-11):「选择器完全不动,实际请求改道到本地池」。
|
|
286
|
+
*
|
|
287
|
+
* - 边缘模式的会话**不再改写选型**(§7.176 的钉选 / §7.179 的补钉 /
|
|
288
|
+
* §7.183 的回钉全部退役)⇒ 宿主模型选择器保持主人选的那个,不跳。
|
|
289
|
+
* - 但「本轮必须留在设备上」这条不变式**不能松** ⇒ 真正跑哪儿由**出网
|
|
290
|
+
* 那一跳**决定:LOCAL 轮次的请求在 `llm/stream` 被换成 provider = 本地池。
|
|
291
|
+
*
|
|
292
|
+
* 为什么能在这一跳改:`llm/stream` 的 waterfall **默认处理**才是解析适配器的
|
|
293
|
+
* 地方(dsh-llm `adapterStream()`:`registration = this.registration(options.provider)`)
|
|
294
|
+
* ⇒ 换掉 `options.provider` 就换掉了真正发出去的适配器。
|
|
295
|
+
* 而且 cordis 的 waterfall **不接受替换载荷**(`next()` 无参,载荷被闭包捕获)
|
|
296
|
+
* ⇒ 只能换一个请求对象**重新派发**(与 §7.151 的出网裁剪同一个姿势)。
|
|
297
|
+
*
|
|
298
|
+
* 重新派发会再进一次 waterfall ⇒ 那趟的 provider 已是本地池 ⇒ 天然不再改道
|
|
299
|
+
* (**防自锁**),且本地池按 §7.131 带**全部**历史(本地池不出网,不裁)。
|
|
300
|
+
*
|
|
301
|
+
* fail-closed:改不动(本地路由不存在 / 解析不到 model id / 宿主 LLM runtime
|
|
302
|
+
* 不可用)⇒ **抛错**,绝不放行到可能上云的 provider。
|
|
303
|
+
*/
|
|
304
|
+
/** §7.184: 这一通请求要不要改道到本地池? */
|
|
305
|
+
private needsLocalReroute;
|
|
306
|
+
/**
|
|
307
|
+
* §7.184: 换掉请求对象**重新派发**到本地池(与 §7.151 的出网裁剪同一姿势)。
|
|
308
|
+
*
|
|
309
|
+
* 为什么不能就地改写:宿主契约明写 LOOP 构建的请求是 **deep-frozen**
|
|
310
|
+
* (`markAgentLoopRequest` + `deepFreeze`),"listeners read it, never rewrite it"。
|
|
311
|
+
*/
|
|
312
|
+
private dispatchRerouted;
|
|
313
|
+
constructor(ctx: Context, config: Config, promptGuard?: PromptGuardLike, privacyPolicy?: PrivacyPolicyLike, intentRouter?: IntentRouterLike, sanitizer?: SanitizerLike, auditLog?: AuditLogLike);
|
|
314
|
+
private audit;
|
|
315
|
+
/** 当前 session 是否有活跃的脱敏状态(desanitize 用)。*/
|
|
316
|
+
getStepState(sessionId: string): StepSanitizationState | undefined;
|
|
317
|
+
/** Exposed for tests: the step-state map the turn/end restore listener reads. */
|
|
318
|
+
setStepState(sessionId: string, state: StepSanitizationState): void;
|
|
319
|
+
/** Seed/override the session's last routed category. Tests drive the
|
|
320
|
+
* LOCAL-credential exemption with it; http-api could pre-seed a known
|
|
321
|
+
* route the same way. */
|
|
322
|
+
setTurnCategory(sessionId: string, category: string): void;
|
|
323
|
+
/** 会话状态 Map 的 LRU 上限。活跃会话远小于此(http-api 默认
|
|
324
|
+
* maxActiveSessions=8),被淘汰的是早已结束的 session —— 这两个
|
|
325
|
+
* Map 曾经只增不减,长驻进程里随历史会话数无限增长。 */
|
|
326
|
+
private static readonly SESSION_STATE_CAP;
|
|
327
|
+
/** LRU 写入:delete-then-set 让最近路由的 session 排到队尾,超限
|
|
328
|
+
* 淘汰队头(最久未访问的 session)。 */
|
|
329
|
+
private putStepState;
|
|
330
|
+
private putTurnCategory;
|
|
331
|
+
private evictOldestSessionState;
|
|
332
|
+
/**
|
|
333
|
+
* 清洗 messages 数组中的孤儿 tool_call。
|
|
334
|
+
*
|
|
335
|
+
* 背景(2026-08-31):当 session 历史里残留一条 assistant 消息含
|
|
336
|
+
* `tool_calls`,但后续没有匹配的 `role: tool` + `tool_call_id` 时,
|
|
337
|
+
* OpenAI / DeepSeek API 会返回 400 invalid_request_error
|
|
338
|
+
* "An assistant message with 'tool_calls' must be followed by tool
|
|
339
|
+
* messages responding to each 'tool_call_id'."
|
|
340
|
+
*
|
|
341
|
+
* 根因:webui 复用先前 http-api 创建的会话做 session.prompt,但
|
|
342
|
+
* agent-loop 把整个 history 投影到 OpenAI 兼容 schema 时保留了
|
|
343
|
+
* 没有 tool 响应的 tool_call。
|
|
344
|
+
*
|
|
345
|
+
* 修复策略:对每个 orphan tool_call,在 assistant 之后紧邻位置插入一条
|
|
346
|
+
* 合成的 `{role:'user', content:[{type:'tool-result', toolCallId, content:'{\"error\":\"aborted\"}...'}]}`。
|
|
347
|
+
* 这样:
|
|
348
|
+
* - assistant 消息被原样保留(tool-call 块不动)——必须保证 wire 端
|
|
349
|
+
* assistant.tool_calls 包含所有 ids,synthetic 才能与之匹配
|
|
350
|
+
* - wire 转换后 API 看到完整的 tool_calls ↔ tool 配对,校验通过
|
|
351
|
+
* - LLM 收到"之前那次工具调用被中止了"的信号,自然续写
|
|
352
|
+
*
|
|
353
|
+
* 关键:不要剥离 orphan tool-call 块(初版犯的错)。如果剥离,Case B
|
|
354
|
+
* (assistant 同时含 responded + orphan ids)会让合成的 tool-result
|
|
355
|
+
* 找不到匹配的 tool_call_id,wire 端再次 400。
|
|
356
|
+
*
|
|
357
|
+
* 副作用:上游 provider 可能继续推演被中止的工具调用(如果 LLM 智能
|
|
358
|
+
* 不足)。优先级:先让请求能跑通,再考虑 LLM 行为。这是 input-guard
|
|
359
|
+
* 的职责边界:保证 schema 合规,不保证 LLM 行为合理。
|
|
360
|
+
*
|
|
361
|
+
* §7.58 (2026-09-16) Pass 0——重复 tool_call id 唯一化:同一次 sanitize 内
|
|
362
|
+
* 第 2..n 次出现的同 id tool-call 块(及其第 2..n 个配对 tool-result 块,
|
|
363
|
+
* 含 message.source.callId)改名为 `${id}-dup${k}`,避免 DeepSeek 400
|
|
364
|
+
* "Duplicate value for 'tool_call_id' … in message[N]"。来源:(a) 旧版
|
|
365
|
+
* openai-sse.ts delta / block-end 索引错位使一次调用在一条 assistant 消息
|
|
366
|
+
* 里落成两个同 id 块(已源头修复),修复前落盘的 session 历史仍在——
|
|
367
|
+
* 2026-09-15 session 24303d6a turn 2 实锤;(b) 未来其他模型 / adapter 的
|
|
368
|
+
* 同类重复输出。Pass 0 先于 orphan 扫描,保证后续 pass 看到全局唯一 id。
|
|
369
|
+
*
|
|
370
|
+
* 调用方:cloud-llm-adapter / local-llm-adapter 在 buildChatCompletionsBody
|
|
371
|
+
* 前调用。如 input-guard 未启用(enableInputGuard=false)则跳过。
|
|
372
|
+
*/
|
|
373
|
+
sanitizeOrphanToolCalls(messages: ReadonlyArray<Record<string, unknown>>, sessionId?: string): {
|
|
374
|
+
messages: Array<Record<string, unknown>>;
|
|
375
|
+
orphanCount: number;
|
|
376
|
+
syntheticInjected: number;
|
|
377
|
+
/** §7.58 (2026-09-16): 本消息集内被改名的重复 tool-call 块数。 */
|
|
378
|
+
duplicateCount: number;
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
381
|
+
* 把拒收原因挂到 agent 对象上,供 http-api 在订阅 `session/event` 的
|
|
382
|
+
* turn/end 监听器里读取(因为 agent-loop 把 `{ kind: 'reject' }` 翻译成
|
|
383
|
+
* `turnEnds = { kind: 'blocked' }` 时会丢掉 reason 字符串)。
|
|
384
|
+
*
|
|
385
|
+
* 只在 reject 路径写入;正常 turn 不污染 agent 状态。
|
|
386
|
+
*/
|
|
387
|
+
private attachRejection;
|
|
388
|
+
/**
|
|
389
|
+
* 读取 agent 上的 edge-route(由 http-api / 其他入口在 followup 前写入)。
|
|
390
|
+
* 避免 input-guard 重复跑 intent-router.classify()(每次 ~0.5-1s)。*/
|
|
391
|
+
private readEdgeRoute;
|
|
392
|
+
/**
|
|
393
|
+
* §7.175 (2026-11-11): 读宿主给本轮选的模型(agent/pre-step payload **顶层**
|
|
394
|
+
* 的 `modelSelection`)。
|
|
395
|
+
*
|
|
396
|
+
* 宿主契约(dsh-agent-loop 0.2.2-rc.1,实测 app.asar):
|
|
397
|
+
* `preStep` 里 `const selection = this.model.current()`,再以
|
|
398
|
+
* `waterfall('agent/pre-step', { messages, ...position, signal, modelSelection: selection })`
|
|
399
|
+
* 派发 —— `modelSelection` 在 payload 顶层,形状 `{ provider, model, reasoningEffort? }`。
|
|
400
|
+
*
|
|
401
|
+
* 返回 undefined = **拿不到**(缺失 / 不是对象 / provider 不是非空字符串)
|
|
402
|
+
* ⇒ 无法证明本轮走本地 ⇒ 调用方必须 fail-closed。
|
|
403
|
+
*/
|
|
404
|
+
private readModelSelection;
|
|
405
|
+
/** 正向 hook:agent/pre-step handler。暴露给测试。*/
|
|
406
|
+
handlePreStep(payload: {
|
|
407
|
+
agent: {
|
|
408
|
+
session: object & {
|
|
409
|
+
id?: string;
|
|
410
|
+
};
|
|
411
|
+
id?: string;
|
|
412
|
+
_edgeRoute?: AgentEdgeRoute;
|
|
413
|
+
} & Record<string, unknown>;
|
|
414
|
+
messages: ReadonlyArray<Record<string, unknown>>;
|
|
415
|
+
step: number;
|
|
416
|
+
signal?: AbortSignal;
|
|
417
|
+
/** §7.175: 宿主给本轮选的模型 —— payload **顶层**的
|
|
418
|
+
* `{ provider, model, reasoningEffort? }`(dsh-agent-loop 0.2.2-rc.1)。
|
|
419
|
+
* 缺失 ⇒ 无法证明本轮走本地 ⇒ fail-closed 拒收。*/
|
|
420
|
+
modelSelection?: unknown;
|
|
421
|
+
}, next: () => Promise<{
|
|
422
|
+
kind: string;
|
|
423
|
+
messages?: ReadonlyArray<Record<string, unknown>>;
|
|
424
|
+
reason?: string;
|
|
425
|
+
}>): Promise<{
|
|
426
|
+
kind: string;
|
|
427
|
+
messages?: ReadonlyArray<Record<string, unknown>>;
|
|
428
|
+
reason?: string;
|
|
429
|
+
}>;
|
|
430
|
+
/** `agent/request` handler —— §7.175 起**只放行,不改路由**。
|
|
431
|
+
*
|
|
432
|
+
* 为什么不再改写:宿主 dsh-agent-loop 的 prepareRequest() 会拿
|
|
433
|
+
* `proposedConfig` 与 `this.model.current()`(宿主给本轮选的模型)比对,
|
|
434
|
+
* provider / model / reasoningEffort 任一不同就**直接抛错**。所以在
|
|
435
|
+
* agent/request 里改写 provider/model 不是「降级到本地」,而是让整轮
|
|
436
|
+
* 直接失败。
|
|
437
|
+
*
|
|
438
|
+
* 现在:路由恒由宿主的 modelSelection 决定,边缘模式恒为本地模型
|
|
439
|
+
* (主人裁决 2026-11-10);「本该本地却可能上云」的判定与 fail-closed
|
|
440
|
+
* 拦截都在 handlePreStep 里做(见那里的 modelSelection 闸门)。
|
|
441
|
+
*
|
|
442
|
+
* 这里保留这个 handler 只为把「不改路由」这条契约钉死:seedConfig 原样
|
|
443
|
+
* 返回,一个字节都不改。
|
|
444
|
+
*
|
|
445
|
+
* 注意 DSH waterfall 契约:监听器真实签名是 `(payload, next)`,
|
|
446
|
+
* 其中 `payload` 已被 dsh-agent 的 fused wrapper 注入 `agent` 字段
|
|
447
|
+
* (见 @deepseek-ai/dsh-agent/lib/types/dispatch.js#agentEvents)。
|
|
448
|
+
* 上一层 apply() 必须先 `await next()` 取出 seedConfig 后再传进来,
|
|
449
|
+
* 因为 next() 是 chain 的下一步,handler 不能跳过它直接返回。*/
|
|
450
|
+
handleAgentRequest(payload: {
|
|
451
|
+
turn?: number;
|
|
452
|
+
step?: number;
|
|
453
|
+
signal?: AbortSignal;
|
|
454
|
+
agent?: {
|
|
455
|
+
session?: {
|
|
456
|
+
id?: string;
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
}, seedConfig: AgentRequestConfig, next: () => Promise<AgentRequestConfig>): Promise<AgentRequestConfig>;
|
|
460
|
+
/**
|
|
461
|
+
* §7.144: 宿主 provider 的调用要不要记?返回一个记录器,或 undefined。
|
|
462
|
+
*
|
|
463
|
+
* **不记**的三种情形:
|
|
464
|
+
*
|
|
465
|
+
* 1. provider 属于本项目自管池(`local-llm` / `cloud-llm`)—— adapter
|
|
466
|
+
* 自己已经记过(REQ + RESP),这里再记一遍面板上就是同一通调用出现
|
|
467
|
+
* 两条。
|
|
468
|
+
* 2. `purpose` 非空 —— 宿主的内部用途(compaction / session-title),
|
|
469
|
+
* 不是主人关心的「这一轮调用」,记了只给面板添噪音。
|
|
470
|
+
* 3. provider 缺失 / trace 总开关关闭 / 任何异常 ⇒ 不记(观测路径永不
|
|
471
|
+
* 拖垮主链路)。
|
|
472
|
+
*
|
|
473
|
+
* 记的那一类是**宿主自己的 provider**(delegate 出去的云池,如
|
|
474
|
+
* `qwen-token-plan-cn`)—— 它不经过本项目任何 adapter,是面板里唯一
|
|
475
|
+
* 「完全看不见」的一跳。
|
|
476
|
+
*/
|
|
477
|
+
private hostCallRecorder;
|
|
478
|
+
/**
|
|
479
|
+
* §7.151 (2026-10-29): 换掉请求体**重新派发**这一通出网调用。
|
|
480
|
+
*
|
|
481
|
+
* 为什么不能就地改写:宿主契约明写 LOOP 构建的请求是 **deep-frozen**
|
|
482
|
+
* (`markAgentLoopRequest` + `deepFreeze`),"listeners read it, never rewrite
|
|
483
|
+
* it"。所以唯一能换掉请求体的姿势就是换一个请求对象再派发一次 ——
|
|
484
|
+
* 与 `cloud-llm-adapter` 的 delegate 路径同一个姿势,宿主侧已验证可行。
|
|
485
|
+
*
|
|
486
|
+
* 重新派发会**再进一次 waterfall**:本方法给裁过的请求对象打标记
|
|
487
|
+
* (`HostEgressGuard.markTrimmed`),守卫下一次看到它直接放行 ⇒ 不递归、
|
|
488
|
+
* 不二次裁剪;那一趟里 trace 记录的是**真正发出去的那一份**。
|
|
489
|
+
*
|
|
490
|
+
* fail-closed:裁不动就不许出网 —— 放行等于把本地历史原样送上去。
|
|
491
|
+
*/
|
|
492
|
+
private dispatchTrimmed;
|
|
493
|
+
/**
|
|
494
|
+
* 反向 hook:llm/stream handler。暴露给测试。
|
|
495
|
+
*
|
|
496
|
+
* `options` 的索引签名是**如实描述**宿主的载荷:`llm/stream` 派发的是
|
|
497
|
+
* 完整的 `GenerateOptions`(provider / model / messages / tools / …),
|
|
498
|
+
* §7.144 的宿主调用记录正是从这些字段里取「发出去的那一坨」。
|
|
499
|
+
*/
|
|
500
|
+
handleLlmStream(options: {
|
|
501
|
+
signal?: AbortSignal;
|
|
502
|
+
sessionId?: string;
|
|
503
|
+
[key: string]: unknown;
|
|
504
|
+
}, next: () => AsyncIterable<StreamChunkLike>): AsyncIterable<StreamChunkLike>;
|
|
505
|
+
}
|
|
506
|
+
export declare function apply(ctx: Context, config: Config): Promise<() => Promise<void>>;
|
|
507
|
+
export {};
|
|
508
|
+
//# sourceMappingURL=input-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-guard.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/input-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAC7C,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAgB7C,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;AAoB7B,eAAO,MAAM,IAAI,gBAAgB,CAAA;AAEjC,eAAO,MAAM,MAAM,wGAgCT,CAAA;AAEV,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,gBAAgB,EAAE,OAAO,CAAA;IACzB,sDAAsD;IACtD,oBAAoB,EAAE,OAAO,CAAA;IAC7B;oDACgD;IAChD,uBAAuB,EAAE,OAAO,CAAA;IAChC;;;;;;;0CAOsC;IACtC,0BAA0B,EAAE,OAAO,CAAA;IACnC;;wCAEoC;IACpC,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;;;6CAOyC;IACzC,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;mBAMe;IACf,oBAAoB,EAAE,MAAM,CAAA;IAC5B,iBAAiB,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;mBAUe;IACf,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CAuBhC,CAAA;AAEF,qDAAqD;AACrD,UAAU,eAAe;IACvB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QACnD,IAAI,EAAE,OAAO,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;KACtC,CAAC,CAAA;IACF,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;QACrE,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAClD,CAAC,CAAA;CACH;AAED,gCAAgC;AAChC,UAAU,iBAAiB;IACzB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,GAAG,IAAI,GAAG,SAAS,CAAA;CACrB;AAED,+BAA+B;AAC/B,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAC5F,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAA;QACpD,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAC,CAAA;CACH;AAED;;;;;;yBAMyB;AACzB,UAAU,aAAa;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAChE,aAAa,EAAE,MAAM,CAAA;QACrB,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,KAAK,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAA;YAChB,WAAW,EAAE,MAAM,CAAA;YACnB,OAAO,EAAE,MAAM,CAAA;YACf,SAAS,EAAE,MAAM,CAAA;YACjB,UAAU,CAAC,EAAE,MAAM,CAAA;SACpB,CAAC,CAAA;KACH,CAAC,CAAA;IACF,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAC7D;AAED,2BAA2B;AAC3B,UAAU,YAAY;IACpB,GAAG,CAAC,KAAK,EAAE;QACT,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAA;QACvD,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACjC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACrB;AAaD,qDAAqD;AACrD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB;AAiBD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,UAAU,EAAE,qBAAqB,CAAA;KAClC;CACF;AAED;;sDAEsD;AACtD,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAA;IAClB,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAA;IACpB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB;6CACyC;IACzC,WAAW,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;CACnC;AAED,+DAA+D;AAC/D,UAAU,cAAc;IACtB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,WAAW,CAAA;IACtC,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAA;IACpD,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAqBD,uEAAuE;AACvE,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAGD;;;;;;;;;;;GAWG;AACH,qBAAa,qBAAsB,SAAQ,OAAO;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA+B;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8B;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IAEnD;;;iEAG6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;IACrE;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;IAEzD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAE5C;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IAEjD;;;oCAGgC;IAChC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc;IAqBtB;;;;;;;;;OASG;IACH,SAAS,CAAC,cAAc,IAAI,cAAc;IAQ1C;;;;;;;;;;;;;;;;;;;;;OAqBG;IAEH,8BAA8B;IAC9B,OAAO,CAAC,iBAAiB;IAazB;;;;;OAKG;YACa,gBAAgB;gBAuG9B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,eAAe,EAC7B,aAAa,CAAC,EAAE,iBAAiB,EACjC,YAAY,CAAC,EAAE,gBAAgB,EAC/B,SAAS,CAAC,EAAE,aAAa,EACzB,QAAQ,CAAC,EAAE,YAAY;IAoBzB,OAAO,CAAC,KAAK;IASb,0CAA0C;IAC1C,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS;IAIlE,iFAAiF;IACjF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI;IAInE;;8BAE0B;IAC1B,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI1D;;sCAEkC;IAClC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IAE/C;gCAC4B;IAC5B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,uBAAuB;IAQ/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,uBAAuB,CACrB,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAChD,SAAS,CAAC,EAAE,MAAM,GACjB;QACD,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;QACxC,WAAW,EAAE,MAAM,CAAA;QACnB,iBAAiB,EAAE,MAAM,CAAA;QACzB,oDAAoD;QACpD,cAAc,EAAE,MAAM,CAAA;KACvB;IAkTD;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAOvB;;kEAE8D;IAC9D,OAAO,CAAC,aAAa;IAQrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IAW1B,2CAA2C;IACrC,aAAa,CACjB,OAAO,EAAE;QACP,KAAK,EAAE;YAAE,OAAO,EAAE,MAAM,GAAG;gBAAE,EAAE,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC;YAAC,EAAE,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,cAAc,CAAA;SAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAChH,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;QAChD,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,WAAW,CAAA;QACpB;;8CAEsC;QACtC,cAAc,CAAC,EAAE,OAAO,CAAA;KACzB,EACD,IAAI,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GACxG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAonBhG;;;;;;;;;;;;;;;;;;;mDAmB+C;IACzC,kBAAkB,CACtB,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE;gBAAE,EAAE,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,EACtG,UAAU,EAAE,kBAAkB,EAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,GACtC,OAAO,CAAC,kBAAkB,CAAC;IAK9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,gBAAgB;IA0DxB;;;;;;;;;;;;;OAaG;YACa,eAAe;IAkE/B;;;;;;OAMG;IACH,eAAe,CACb,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,EAC7E,IAAI,EAAE,MAAM,aAAa,CAAC,eAAe,CAAC,GACzC,aAAa,CAAC,eAAe,CAAC;CAuVlC;AA0DD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAyLtF"}
|