chatccc 0.2.252 → 0.2.253
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 +1 -1
- package/bin/cccagent.mjs +12 -3
- package/bin/chatccc.mjs +15 -6
- package/deepccc-agent/os-prompts/darwin.md +8 -8
- package/deepccc-agent/os-prompts/linux.md +8 -8
- package/deepccc-agent/os-prompts/win32.md +11 -11
- package/deepccc-agent/package.json +65 -65
- package/dist/deepccc-agent/src/cli.js +634 -0
- package/dist/deepccc-agent/src/config.js +76 -0
- package/dist/deepccc-agent/src/context.js +348 -0
- package/dist/deepccc-agent/src/file-log.js +34 -0
- package/dist/deepccc-agent/src/file-tools.js +1208 -0
- package/dist/deepccc-agent/src/index.js +571 -0
- package/dist/deepccc-agent/src/permissions.js +170 -0
- package/dist/deepccc-agent/src/privacy.js +124 -0
- package/dist/deepccc-agent/src/proc-tree-kill.js +60 -0
- package/dist/deepccc-agent/src/progress/cards-helpers.js +70 -0
- package/dist/deepccc-agent/src/progress/reducer.js +102 -0
- package/dist/deepccc-agent/src/progress/terminal-renderer.js +264 -0
- package/dist/deepccc-agent/src/progress/view.js +30 -0
- package/dist/deepccc-agent/src/raw-stream-log.js +106 -0
- package/dist/deepccc-agent/src/session-search.js +276 -0
- package/dist/deepccc-agent/src/session-select.js +23 -0
- package/dist/deepccc-agent/src/sigint.js +26 -0
- package/dist/deepccc-agent/src/skills.js +178 -0
- package/dist/deepccc-agent/src/web-tools.js +246 -0
- package/dist/src/adapters/adapter-interface.js +19 -0
- package/dist/src/adapters/ccc-adapter.js +112 -0
- package/dist/src/adapters/claude-adapter.js +497 -0
- package/dist/src/adapters/claude-session-meta-store.js +92 -0
- package/dist/src/adapters/codex-adapter.js +279 -0
- package/dist/src/adapters/codex-session-meta-store.js +94 -0
- package/dist/src/adapters/cursor-adapter.js +491 -0
- package/dist/src/adapters/cursor-session-meta-store.js +116 -0
- package/dist/src/adapters/jsonl-stream.js +104 -0
- package/{src/adapters/proc-tree-kill.ts → dist/src/adapters/proc-tree-kill.js} +94 -97
- package/dist/src/adapters/raw-stream-log.js +106 -0
- package/dist/src/adapters/resource-monitor.js +113 -0
- package/dist/src/agent-activity.js +133 -0
- package/dist/src/agent-delegate-task-rpc.js +129 -0
- package/dist/src/agent-delegate-task.js +48 -0
- package/dist/src/agent-file-rpc.js +152 -0
- package/dist/src/agent-image-rpc.js +148 -0
- package/dist/src/agent-platform-routing.js +13 -0
- package/dist/src/agent-reload-config-rpc.js +23 -0
- package/dist/src/agent-rpc-body.js +87 -0
- package/dist/src/agent-stop-stuck.js +110 -0
- package/dist/src/card-action-routing.js +7 -0
- package/dist/src/card-plain-text.js +101 -0
- package/dist/src/cardkit.js +158 -0
- package/dist/src/cards.js +573 -0
- package/dist/src/chatgpt-subscription-rpc.js +18 -0
- package/dist/src/chatgpt-subscription.js +199 -0
- package/dist/src/chrome-devtools-guard.js +238 -0
- package/dist/src/claude-sdk-installer.js +249 -0
- package/dist/src/codex-reset-actions.js +143 -0
- package/dist/src/config-utils.js +149 -0
- package/dist/src/config.js +804 -0
- package/dist/src/cursor-usage.js +77 -0
- package/dist/src/exit-banner.js +28 -0
- package/dist/src/feishu-api.js +1404 -0
- package/dist/src/feishu-message-ingress.js +137 -0
- package/dist/src/feishu-platform.js +97 -0
- package/dist/src/format-message.js +252 -0
- package/dist/src/git-command.js +155 -0
- package/dist/src/im-skills.js +121 -0
- package/dist/src/index.js +833 -0
- package/dist/src/litellm-proxy.js +300 -0
- package/dist/src/orchestrator.js +2078 -0
- package/dist/src/package-root.js +26 -0
- package/dist/src/platform-adapter.js +7 -0
- package/dist/src/platform-startup.js +6 -0
- package/dist/src/privacy.js +100 -0
- package/dist/src/progress/reducer.js +102 -0
- package/dist/src/progress/terminal-renderer.js +264 -0
- package/dist/src/progress/view.js +30 -0
- package/dist/src/response-stall.js +14 -0
- package/dist/src/runtime-entry.js +13 -0
- package/dist/src/runtime-reload.js +19 -0
- package/dist/src/session-chat-binding.js +183 -0
- package/dist/src/session-name.js +7 -0
- package/dist/src/session.js +2144 -0
- package/dist/src/shared-prefix.js +16 -0
- package/dist/src/shared.js +493 -0
- package/dist/src/sim-agent.js +105 -0
- package/dist/src/sim-platform.js +142 -0
- package/dist/src/sim-store.js +231 -0
- package/dist/src/simplify.js +99 -0
- package/dist/src/startup-lifecycle.js +209 -0
- package/dist/src/stream-state.js +141 -0
- package/dist/src/terminal-error.js +100 -0
- package/dist/src/trace.js +50 -0
- package/dist/src/turn-cards.js +92 -0
- package/dist/src/update-command-guard.js +114 -0
- package/{src/web-ui.ts → dist/src/web-ui.js} +749 -823
- package/dist/src/wechat-platform.js +545 -0
- package/package.json +76 -74
- package/deepccc-agent/LICENSE +0 -201
- package/deepccc-agent/bin/deepccc.mjs +0 -26
- package/deepccc-agent/docs/cache-hit-rate-1.jpg +0 -0
- package/deepccc-agent/docs/cache-hit-rate-2.jpg +0 -0
- package/deepccc-agent/package-lock.json +0 -2027
- package/deepccc-agent/src/__tests__/chat-session.test.ts +0 -877
- package/deepccc-agent/src/__tests__/cli-json.test.ts +0 -49
- package/deepccc-agent/src/__tests__/config.test.ts +0 -34
- package/deepccc-agent/src/__tests__/context.test.ts +0 -341
- package/deepccc-agent/src/__tests__/file-tools.test.ts +0 -240
- package/deepccc-agent/src/__tests__/permissions.test.ts +0 -199
- package/deepccc-agent/src/__tests__/privacy.test.ts +0 -318
- package/deepccc-agent/src/__tests__/progress-reducer.test.ts +0 -121
- package/deepccc-agent/src/__tests__/session-search.test.ts +0 -262
- package/deepccc-agent/src/__tests__/session-select.test.ts +0 -116
- package/deepccc-agent/src/__tests__/sigint.test.ts +0 -56
- package/deepccc-agent/src/__tests__/skills.test.ts +0 -284
- package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +0 -247
- package/deepccc-agent/src/__tests__/web-tools.test.ts +0 -220
- package/deepccc-agent/src/cli.ts +0 -682
- package/deepccc-agent/src/config.ts +0 -101
- package/deepccc-agent/src/context.ts +0 -465
- package/deepccc-agent/src/file-log.ts +0 -38
- package/deepccc-agent/src/file-tools.ts +0 -1493
- package/deepccc-agent/src/index.ts +0 -710
- package/deepccc-agent/src/permissions.ts +0 -226
- package/deepccc-agent/src/privacy.ts +0 -141
- package/deepccc-agent/src/proc-tree-kill.ts +0 -61
- package/deepccc-agent/src/progress/cards-helpers.ts +0 -76
- package/deepccc-agent/src/progress/reducer.ts +0 -113
- package/deepccc-agent/src/progress/terminal-renderer.ts +0 -294
- package/deepccc-agent/src/progress/view.ts +0 -77
- package/deepccc-agent/src/raw-stream-log.ts +0 -124
- package/deepccc-agent/src/session-search.ts +0 -370
- package/deepccc-agent/src/session-select.ts +0 -48
- package/deepccc-agent/src/sigint.ts +0 -50
- package/deepccc-agent/src/skills.ts +0 -205
- package/deepccc-agent/src/web-tools.ts +0 -313
- package/deepccc-agent/tsconfig.build.json +0 -13
- package/deepccc-agent/tsconfig.json +0 -13
- package/deepccc-agent/vitest.config.ts +0 -7
- package/src/__tests__/adapter-interface.test.ts +0 -152
- package/src/__tests__/agent-activity.test.ts +0 -86
- package/src/__tests__/agent-delegate-task-rpc.test.ts +0 -165
- package/src/__tests__/agent-image-rpc.test.ts +0 -34
- package/src/__tests__/agent-platform-routing.test.ts +0 -26
- package/src/__tests__/agent-reload-config-rpc.test.ts +0 -99
- package/src/__tests__/agent-rpc-body.test.ts +0 -42
- package/src/__tests__/builtin-chat-session.test.ts +0 -532
- package/src/__tests__/builtin-cli-json.test.ts +0 -39
- package/src/__tests__/builtin-config.test.ts +0 -26
- package/src/__tests__/builtin-context.test.ts +0 -319
- package/src/__tests__/builtin-file-tools.test.ts +0 -240
- package/src/__tests__/builtin-permissions.test.ts +0 -219
- package/src/__tests__/builtin-session-search.test.ts +0 -262
- package/src/__tests__/builtin-session-select.test.ts +0 -116
- package/src/__tests__/builtin-sigint.test.ts +0 -56
- package/src/__tests__/builtin-skills.test.ts +0 -284
- package/src/__tests__/builtin-web-tools.test.ts +0 -220
- package/src/__tests__/card-action-routing.test.ts +0 -18
- package/src/__tests__/card-plain-text.test.ts +0 -45
- package/src/__tests__/cardkit.test.ts +0 -60
- package/src/__tests__/cards.test.ts +0 -607
- package/src/__tests__/ccc-adapter.test.ts +0 -194
- package/src/__tests__/chatgpt-subscription-rpc.test.ts +0 -89
- package/src/__tests__/chatgpt-subscription.test.ts +0 -135
- package/src/__tests__/chrome-devtools-guard.test.ts +0 -165
- package/src/__tests__/claude-adapter.test.ts +0 -614
- package/src/__tests__/claude-raw-stream-log.test.ts +0 -96
- package/src/__tests__/claude-sdk-installer.test.ts +0 -285
- package/src/__tests__/codex-adapter.test.ts +0 -331
- package/src/__tests__/codex-raw-stream-log.test.ts +0 -170
- package/src/__tests__/codex-reset-actions.test.ts +0 -146
- package/src/__tests__/config-reload.test.ts +0 -284
- package/src/__tests__/config-sample.test.ts +0 -97
- package/src/__tests__/config-utils.test.ts +0 -40
- package/src/__tests__/config.test.ts +0 -395
- package/src/__tests__/crash-logging.test.ts +0 -360
- package/src/__tests__/cursor-adapter.test.ts +0 -890
- package/src/__tests__/cursor-session-meta-store.test.ts +0 -212
- package/src/__tests__/feishu-api.test.ts +0 -60
- package/src/__tests__/feishu-avatar.test.ts +0 -504
- package/src/__tests__/feishu-message-ingress.test.ts +0 -138
- package/src/__tests__/feishu-platform.test.ts +0 -75
- package/src/__tests__/fixtures/codex_simple_text.jsonl +0 -4
- package/src/__tests__/fixtures/codex_with_tool.jsonl +0 -6
- package/src/__tests__/fixtures/cursor_partial_only.jsonl +0 -5
- package/src/__tests__/fixtures/cursor_partial_with_final.jsonl +0 -13
- package/src/__tests__/fixtures/cursor_with_tool_call.jsonl +0 -12
- package/src/__tests__/format-message.test.ts +0 -316
- package/src/__tests__/git-command.test.ts +0 -288
- package/src/__tests__/im-skills.test.ts +0 -125
- package/src/__tests__/jsonl-stream.test.ts +0 -79
- package/src/__tests__/orchestrator.test.ts +0 -1268
- package/src/__tests__/package-files.test.ts +0 -24
- package/src/__tests__/platform-startup.test.ts +0 -19
- package/src/__tests__/privacy.test.ts +0 -198
- package/src/__tests__/proc-tree-kill.test.ts +0 -108
- package/src/__tests__/progress-reducer.test.ts +0 -121
- package/src/__tests__/raw-stream-log.test.ts +0 -106
- package/src/__tests__/response-stall.test.ts +0 -49
- package/src/__tests__/restart.test.ts +0 -232
- package/src/__tests__/session-ccc-config.test.ts +0 -66
- package/src/__tests__/session.test.ts +0 -3004
- package/src/__tests__/shared-prefix.test.ts +0 -36
- package/src/__tests__/sim-agent.test.ts +0 -174
- package/src/__tests__/sim-platform.test.ts +0 -93
- package/src/__tests__/sim-store.test.ts +0 -214
- package/src/__tests__/simplify.test.ts +0 -283
- package/src/__tests__/startup-lifecycle.test.ts +0 -231
- package/src/__tests__/stop-session.test.ts +0 -162
- package/src/__tests__/stream-state.test.ts +0 -164
- package/src/__tests__/terminal-error.test.ts +0 -54
- package/src/__tests__/terminal-renderer.test.ts +0 -247
- package/src/__tests__/update-command-guard.test.ts +0 -144
- package/src/__tests__/web-ui.test.ts +0 -438
- package/src/__tests__/wechat-platform.test.ts +0 -111
- package/src/adapters/adapter-interface.ts +0 -217
- package/src/adapters/ccc-adapter.ts +0 -150
- package/src/adapters/claude-adapter.ts +0 -673
- package/src/adapters/claude-session-meta-store.ts +0 -120
- package/src/adapters/codex-adapter.ts +0 -426
- package/src/adapters/codex-session-meta-store.ts +0 -131
- package/src/adapters/cursor-adapter.ts +0 -681
- package/src/adapters/cursor-session-meta-store.ts +0 -154
- package/src/adapters/jsonl-stream.ts +0 -157
- package/src/adapters/raw-stream-log.ts +0 -124
- package/src/adapters/resource-monitor.ts +0 -141
- package/src/agent-activity.ts +0 -175
- package/src/agent-delegate-task-rpc.ts +0 -153
- package/src/agent-delegate-task.ts +0 -91
- package/src/agent-file-rpc.ts +0 -172
- package/src/agent-image-rpc.ts +0 -168
- package/src/agent-platform-routing.ts +0 -28
- package/src/agent-reload-config-rpc.ts +0 -34
- package/src/agent-rpc-body.ts +0 -92
- package/src/agent-stop-stuck.ts +0 -129
- package/src/card-action-routing.ts +0 -14
- package/src/card-plain-text.ts +0 -108
- package/src/cardkit.ts +0 -179
- package/src/cards.ts +0 -684
- package/src/chatgpt-subscription-rpc.ts +0 -27
- package/src/chatgpt-subscription.ts +0 -299
- package/src/chrome-devtools-guard.ts +0 -318
- package/src/claude-sdk-installer.ts +0 -324
- package/src/codex-reset-actions.ts +0 -184
- package/src/config-utils.ts +0 -211
- package/src/config.ts +0 -1063
- package/src/cursor-usage.ts +0 -128
- package/src/exit-banner.ts +0 -33
- package/src/feishu-api.ts +0 -1616
- package/src/feishu-message-ingress.ts +0 -195
- package/src/feishu-platform.ts +0 -159
- package/src/format-message.ts +0 -293
- package/src/git-command.ts +0 -202
- package/src/im-skills.ts +0 -149
- package/src/index.ts +0 -1089
- package/src/litellm-proxy.ts +0 -374
- package/src/orchestrator.ts +0 -2543
- package/src/platform-adapter.ts +0 -70
- package/src/platform-startup.ts +0 -16
- package/src/privacy.ts +0 -118
- package/src/progress/reducer.ts +0 -113
- package/src/progress/terminal-renderer.ts +0 -294
- package/src/progress/view.ts +0 -77
- package/src/response-stall.ts +0 -28
- package/src/runtime-reload.ts +0 -34
- package/src/session-chat-binding.ts +0 -292
- package/src/session-name.ts +0 -8
- package/src/session.ts +0 -2659
- package/src/shared-prefix.ts +0 -29
- package/src/shared.ts +0 -552
- package/src/sim-agent.ts +0 -167
- package/src/sim-platform.ts +0 -177
- package/src/sim-store.ts +0 -317
- package/src/simplify.ts +0 -120
- package/src/startup-lifecycle.ts +0 -250
- package/src/stream-state.ts +0 -177
- package/src/terminal-error.ts +0 -129
- package/src/trace.ts +0 -51
- package/src/turn-cards.ts +0 -118
- package/src/update-command-guard.ts +0 -165
- package/src/wechat-platform.ts +0 -680
|
@@ -1,710 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeepCCC builtin Agent core API — 同步自 ChatCCC(保留 DeepCCC 英文品牌)
|
|
3
|
-
*
|
|
4
|
-
* ChatSession 是程序化入口,既可以被 CLI 调用,也可以被其他模块调用。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
8
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
9
|
-
import type { JSONObject } from "@ai-sdk/provider";
|
|
10
|
-
import { generateText, isLoopFinished, stepCountIs, streamText, type TextStreamPart } from "ai";
|
|
11
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
-
import { homedir } from "node:os";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
config as appConfig,
|
|
18
|
-
normalizeDeepCccProvider,
|
|
19
|
-
RAW_STREAM_LOGS_DIR,
|
|
20
|
-
type DeepCccProvider,
|
|
21
|
-
} from "./config.js";
|
|
22
|
-
import {
|
|
23
|
-
createRawStreamLog,
|
|
24
|
-
type RawStreamLogHandle,
|
|
25
|
-
} from "./raw-stream-log.js";
|
|
26
|
-
import {
|
|
27
|
-
buildPersistedAssistantMessage,
|
|
28
|
-
buildSummaryPrompt,
|
|
29
|
-
BuiltinContextManager,
|
|
30
|
-
defaultBuiltinSessionId,
|
|
31
|
-
type BuiltinContextMessage,
|
|
32
|
-
} from "./context.js";
|
|
33
|
-
import { createBuiltinFileTools } from "./file-tools.js";
|
|
34
|
-
import { PermissionGate, type PermissionMode, type PermissionResolver } from "./permissions.js";
|
|
35
|
-
import {
|
|
36
|
-
buildDefaultSkillDirs,
|
|
37
|
-
buildSkillsIndexPrompt,
|
|
38
|
-
scanSkillsDirs,
|
|
39
|
-
type BuiltinSkill,
|
|
40
|
-
type SkillDirSpec,
|
|
41
|
-
} from "./skills.js";
|
|
42
|
-
import { applyPrivacy, applyPrivacyToJson } from "./privacy.js";
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// 系统提示词 — 编译期冻结常量(DeepCCC 英文品牌)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
const SYSTEM_PROMPT = [
|
|
49
|
-
"你是 DeepCCC,一个运行在终端工作区的轻量级 AI 编程智能体。",
|
|
50
|
-
"",
|
|
51
|
-
"## 固定规则",
|
|
52
|
-
"- 除非用户另有要求,否则用用户的语言回复。",
|
|
53
|
-
"- 优先给出直接、可用的答案和具体行动,而非长篇解释。",
|
|
54
|
-
"- 代码任务:编辑前先阅读相关文件,并在可行时用测试或检查验证。",
|
|
55
|
-
"- 保护用户的工作。未经用户明确要求,不要覆盖并发修改。",
|
|
56
|
-
"- 平台不可变规则优先于项目指引和运行时细节。",
|
|
57
|
-
"",
|
|
58
|
-
"## 证据门控结论",
|
|
59
|
-
"- 在可能影响代码、数据、部署或用户决策的重大结论或行动前,以及可用证据为间接证据时,先应用本门控。",
|
|
60
|
-
"- 明确结论及其权威事实来源。区分直接观察与推断,并在选定结论前检验合理的替代解释。",
|
|
61
|
-
"- 在与结论同一语义层上使用最强可行的决定性检查:运行时结论用运行时行为、配置结论用有效配置、部署结论用部署状态、转换结论用转换后的输出。",
|
|
62
|
-
"- 在可行直接检查时,不要把名称、时间戳、文件大小、行数、局部采样或命令成功退出等代理信号当作决定性证据。",
|
|
63
|
-
"- 仅在证据闭环后使用确定性措辞。否则说明不确定性、指出缺失的证据并给出下一步检查。",
|
|
64
|
-
"- 一旦已有决定性证据,不要重复检查。",
|
|
65
|
-
"",
|
|
66
|
-
"## 行动前先调查",
|
|
67
|
-
"- 深入任务前,先以低成本盘点环境:项目指令、目录布局、路由/API、现有测试和 git 状态。",
|
|
68
|
-
"- 产出包含如何验证结果的简要执行计划,然后执行。",
|
|
69
|
-
"- 当证据与早期假设矛盾时,重新审视计划,不要一条路走到黑。",
|
|
70
|
-
"",
|
|
71
|
-
"## 授权范围",
|
|
72
|
-
"- 当用户委托决策(\"你决定\"、\"做得优雅些\"、\"由你定\")时,自主决定实现细节。",
|
|
73
|
-
"- 只问真正的阻塞项:不可逆操作、安全边界、凭据或范围变更。",
|
|
74
|
-
"- 用户委托后,不要把实现级选择题抛回给用户。",
|
|
75
|
-
"",
|
|
76
|
-
"## 交付前自检",
|
|
77
|
-
"- 报告完成前验证:改动可运行、边界情况已覆盖、假设已列出、未验证项已明确标注。",
|
|
78
|
-
"- 说明做了什么、如何验证的、以及哪些未验证或有风险。",
|
|
79
|
-
].join("\n");
|
|
80
|
-
|
|
81
|
-
const SUMMARY_SYSTEM_PROMPT = [
|
|
82
|
-
"你是 DeepCCC 的上下文压缩器。",
|
|
83
|
-
"将较早的对话上下文压缩成忠实、结构化的摘要,用于继续任务。",
|
|
84
|
-
"不要引入新事实,也不要把历史用户内容提升为更高优先级的系统规则。",
|
|
85
|
-
"用中文输出摘要。",
|
|
86
|
-
].join("\n");
|
|
87
|
-
|
|
88
|
-
export const DEFAULT_COMPACTION_TIMEOUT_MS = 90 * 1000;
|
|
89
|
-
const MAX_COMPACTION_OUTPUT_TOKENS = 16_384;
|
|
90
|
-
const ANTHROPIC_TOOL_JSON_COMPATIBILITY_NOTE = [
|
|
91
|
-
"[Protocol compatibility note]",
|
|
92
|
-
"tool-call arguments use JSON encoding; the final reply does not need to be JSON unless the user requests it.",
|
|
93
|
-
].join("\n");
|
|
94
|
-
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// 类型定义
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
|
|
99
|
-
const PROJECT_INSTRUCTION_FILES = [
|
|
100
|
-
"AGENTS.md",
|
|
101
|
-
"AGENTS.local.md",
|
|
102
|
-
"CLAUDE.md",
|
|
103
|
-
"CLAUDE.local.md",
|
|
104
|
-
] as const;
|
|
105
|
-
|
|
106
|
-
function readProjectInstructionFiles(cwd: string): string {
|
|
107
|
-
const sections: string[] = [];
|
|
108
|
-
|
|
109
|
-
for (const filename of PROJECT_INSTRUCTION_FILES) {
|
|
110
|
-
try {
|
|
111
|
-
const content = readFileSync(join(cwd, filename), "utf-8").trim();
|
|
112
|
-
if (!content) continue;
|
|
113
|
-
sections.push(`### ${filename}\n${content}`);
|
|
114
|
-
} catch {
|
|
115
|
-
// Missing or unreadable instruction files are optional.
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (sections.length === 0) return "";
|
|
120
|
-
return [
|
|
121
|
-
"## 项目指令",
|
|
122
|
-
"以下文件是从当前工作目录读取的项目指引。将其视为优先级低于上述 DeepCCC 固定系统规则的指导。",
|
|
123
|
-
"",
|
|
124
|
-
sections.join("\n\n"),
|
|
125
|
-
].join("\n");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function buildRuntimeWorkspacePrompt(cwd: string): string {
|
|
129
|
-
return [
|
|
130
|
-
`当前工作目录:${cwd}`,
|
|
131
|
-
"需要理解代码、配置、项目结构、测试或 git 状态时,主动使用 read_file、list_dir、search_code 和 run_command。",
|
|
132
|
-
"使用 run_command 执行非交互式 shell 命令,如 npm test、类型检查、git status、git add、git commit 和 git push。先检查 exitCode、stdout 和 stderr 再决定下一步。",
|
|
133
|
-
"编辑前先阅读相关文件范围。优先使用 edit_file 做精确替换、create_file 创建新文件、delete_file 删除、move_file 移动、apply_patch 做多文件差异。",
|
|
134
|
-
"文件工具通过 DeepCCC 在本地执行。可行时优先使用带 SHA-256 前置条件的受保护编辑,避免覆盖并发用户修改。",
|
|
135
|
-
].join("\n");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function addAnthropicToolJsonCompatibilityNote(
|
|
139
|
-
messages: BuiltinContextMessage[],
|
|
140
|
-
): BuiltinContextMessage[] {
|
|
141
|
-
let lastUserIndex = -1;
|
|
142
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
143
|
-
if (messages[index]?.role === "user") {
|
|
144
|
-
lastUserIndex = index;
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (lastUserIndex < 0) return messages;
|
|
149
|
-
|
|
150
|
-
// 部分 Anthropic→OpenAI/Ark 转换器会用 response_format=json_object
|
|
151
|
-
// 实现工具调用,却只在 messages 中校验 JSON 关键词、不读取顶层 system。
|
|
152
|
-
// 这里仅声明工具参数的编码方式,并明确不要求普通最终回复输出 JSON。
|
|
153
|
-
return messages.map((message, index) => (
|
|
154
|
-
index === lastUserIndex
|
|
155
|
-
? {
|
|
156
|
-
...message,
|
|
157
|
-
content: `${message.content}\n\n${ANTHROPIC_TOOL_JSON_COMPATIBILITY_NOTE}`,
|
|
158
|
-
}
|
|
159
|
-
: message
|
|
160
|
-
));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 各操作系统特有的命令行指引(文本资产,维护在 os-prompts/ 目录,而非硬编码):
|
|
165
|
-
*
|
|
166
|
-
* - 内置文件:包内 os-prompts/<platform>.md(win32/darwin/linux),随 npm 包分发;
|
|
167
|
-
* - 用户覆盖:~/.deepccc/prompts/<platform>.md 存在时完全替代内置内容(可自定义)。
|
|
168
|
-
*
|
|
169
|
-
* 文件内容自带标题(如 "## Windows Command-Line Notes"),读取后 trim 直接作为
|
|
170
|
-
* 一个段落注入固定规则区(项目指令之前)。未知平台或文件缺失时返回空字符串。
|
|
171
|
-
*/
|
|
172
|
-
export function loadPlatformCommandPrompt(
|
|
173
|
-
platform: string = process.platform,
|
|
174
|
-
dirs: { builtinDir?: string; userDir?: string } = {},
|
|
175
|
-
): string {
|
|
176
|
-
const filename =
|
|
177
|
-
platform === "win32" ? "win32.md" :
|
|
178
|
-
platform === "darwin" ? "darwin.md" :
|
|
179
|
-
platform === "linux" ? "linux.md" :
|
|
180
|
-
null;
|
|
181
|
-
if (!filename) return "";
|
|
182
|
-
|
|
183
|
-
// import.meta.url 定位包根:chatccc 源码运行时指向 deepccc-agent/os-prompts/,
|
|
184
|
-
// deepccc dist 运行时指向包根 os-prompts/(dist/index.js 的 ../os-prompts/)。
|
|
185
|
-
const builtinDir =
|
|
186
|
-
dirs.builtinDir ?? fileURLToPath(new URL("../os-prompts/", import.meta.url));
|
|
187
|
-
const userDir = dirs.userDir ?? join(homedir(), ".deepccc", "prompts");
|
|
188
|
-
|
|
189
|
-
// 用户覆盖优先;读取失败时静默回退内置,内置也失败则返回空。
|
|
190
|
-
const userFile = join(userDir, filename);
|
|
191
|
-
if (existsSync(userFile)) {
|
|
192
|
-
try {
|
|
193
|
-
return readFileSync(userFile, "utf-8").trim();
|
|
194
|
-
} catch {
|
|
195
|
-
// fall through to builtin
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const builtinFile = join(builtinDir, filename);
|
|
199
|
-
if (existsSync(builtinFile)) {
|
|
200
|
-
try {
|
|
201
|
-
return readFileSync(builtinFile, "utf-8").trim();
|
|
202
|
-
} catch {
|
|
203
|
-
return "";
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return "";
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function normalizeMaxSteps(value: number | undefined): number | undefined {
|
|
210
|
-
if (value === undefined) return undefined;
|
|
211
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
212
|
-
throw new Error("maxSteps must be a positive integer when provided");
|
|
213
|
-
}
|
|
214
|
-
return value;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function normalizeAnthropicBaseURL(baseURL: string): string {
|
|
218
|
-
const normalized = baseURL.trim().replace(/\/+$/, "");
|
|
219
|
-
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export interface ChatSessionConfig {
|
|
223
|
-
/** API protocol/provider. Defaults to DEEPCCC_PROVIDER/config, then openai. */
|
|
224
|
-
provider?: DeepCccProvider;
|
|
225
|
-
/** Provider service base URL. Defaults to DEEPCCC_BASE_URL/config. */
|
|
226
|
-
baseURL?: string;
|
|
227
|
-
/** API key. Defaults to DEEPCCC_API_KEY/config. */
|
|
228
|
-
apiKey?: string;
|
|
229
|
-
/** Model id. Defaults to DEEPCCC_MODEL/config. */
|
|
230
|
-
model?: string;
|
|
231
|
-
/**
|
|
232
|
-
* Reasoning effort (none/minimal/low/medium/high/xhigh/max);
|
|
233
|
-
* overrides config.effort; empty omits the reasoning_effort request field.
|
|
234
|
-
*/
|
|
235
|
-
effort?: string;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export interface ChatSessionOptions {
|
|
239
|
-
/** Session working directory. */
|
|
240
|
-
cwd?: string;
|
|
241
|
-
/** Extra system guidance appended after project instructions. */
|
|
242
|
-
systemPrompt?: string;
|
|
243
|
-
/** Persist context to disk. CLI enables this by default; programmatic usage defaults to false. */
|
|
244
|
-
persist?: boolean;
|
|
245
|
-
/** Context directory. Defaults to ~/.deepccc/sessions. */
|
|
246
|
-
contextDir?: string;
|
|
247
|
-
/** Persistent session id. Defaults to a cwd-derived id when omitted. */
|
|
248
|
-
sessionId?: string;
|
|
249
|
-
/** Compact older context when the rough token estimate exceeds this value. */
|
|
250
|
-
compactAtTokens?: number;
|
|
251
|
-
/** Number of recent raw messages retained after compaction. */
|
|
252
|
-
keepRecentMessages?: number;
|
|
253
|
-
/** Hard deadline for all context-compaction passes in one turn. */
|
|
254
|
-
compactionTimeoutMs?: number;
|
|
255
|
-
/** Optional tool-step limit. Leave unset for no step limit. */
|
|
256
|
-
maxSteps?: number;
|
|
257
|
-
/**
|
|
258
|
-
* Custom skill directories (<dir>/<name>/SKILL.md). When set, these are
|
|
259
|
-
* scanned with the highest priority (deepccc source). Defaults to the
|
|
260
|
-
* combined Claude/Codex/Cursor/DeepCCC directories (see buildDefaultSkillDirs).
|
|
261
|
-
*/
|
|
262
|
-
skillsDirs?: string[];
|
|
263
|
-
/**
|
|
264
|
-
* 权限模式:ask(默认,高危命令询问)/ bypass(全部放行,等价
|
|
265
|
-
* --dangerously-bypass-permissions;chatccc 等无终端环境集成时使用)。
|
|
266
|
-
*/
|
|
267
|
-
permissionMode?: PermissionMode;
|
|
268
|
-
/**
|
|
269
|
-
* ask 模式下高危操作的交互确认回调;缺省时非交互环境(JSONL / 程序化
|
|
270
|
-
* 调用)自动拒绝高危命令,常规文件操作与低危命令不受影响。
|
|
271
|
-
*/
|
|
272
|
-
permissionResolver?: PermissionResolver;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* 流式响应事件
|
|
277
|
-
*/
|
|
278
|
-
export type ChatEvent =
|
|
279
|
-
| { type: "status"; phase: "compacting" | "generating" }
|
|
280
|
-
| { type: "compact"; compactedMessages: number }
|
|
281
|
-
| { type: "tool_use"; id?: string; name: string; input: unknown }
|
|
282
|
-
| { type: "tool_result"; tool_use_id: string; name?: string; content: unknown; is_error?: boolean }
|
|
283
|
-
| { type: "text"; text: string; accumulated: string }
|
|
284
|
-
| { type: "done"; text: string }
|
|
285
|
-
| { type: "error"; message: string };
|
|
286
|
-
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// ChatSession
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
|
|
291
|
-
/** 消息角色 */
|
|
292
|
-
type MessageRole = "system" | "user" | "assistant" | "tool";
|
|
293
|
-
|
|
294
|
-
/** 内部消息类型 */
|
|
295
|
-
interface ChatMessage {
|
|
296
|
-
role: MessageRole;
|
|
297
|
-
content: string;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export class ChatSession {
|
|
301
|
-
private model: any;
|
|
302
|
-
private provider: DeepCccProvider;
|
|
303
|
-
private cwd: string;
|
|
304
|
-
private context: BuiltinContextManager;
|
|
305
|
-
private compactionTimeoutMs: number;
|
|
306
|
-
private maxSteps?: number;
|
|
307
|
-
private effort: string;
|
|
308
|
-
private permissionGate: PermissionGate;
|
|
309
|
-
private skillDirs: SkillDirSpec[];
|
|
310
|
-
private customSystemPrompt: string;
|
|
311
|
-
/** 最近一次 chat() 使用的 system prompt(供 history 等读取) */
|
|
312
|
-
private systemPrompt = "";
|
|
313
|
-
|
|
314
|
-
constructor(
|
|
315
|
-
overrides: ChatSessionConfig = {},
|
|
316
|
-
options: ChatSessionOptions = {},
|
|
317
|
-
) {
|
|
318
|
-
const apiKey = overrides.apiKey ?? appConfig.apiKey;
|
|
319
|
-
if (!apiKey) {
|
|
320
|
-
throw new Error(
|
|
321
|
-
"DEEPCCC_API_KEY is not set. Configure ~/.deepccc/config.json, set an environment variable, or pass --api-key.",
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const baseURL = overrides.baseURL ?? appConfig.baseURL;
|
|
326
|
-
const modelId = overrides.model ?? appConfig.model;
|
|
327
|
-
this.provider = normalizeDeepCccProvider(overrides.provider ?? appConfig.provider);
|
|
328
|
-
this.effort = (overrides.effort ?? appConfig.effort ?? "").trim();
|
|
329
|
-
|
|
330
|
-
if (this.provider === "anthropic") {
|
|
331
|
-
const provider = createAnthropic({
|
|
332
|
-
baseURL: normalizeAnthropicBaseURL(baseURL),
|
|
333
|
-
apiKey,
|
|
334
|
-
});
|
|
335
|
-
this.model = provider(modelId);
|
|
336
|
-
} else {
|
|
337
|
-
const provider = createOpenAICompatible({
|
|
338
|
-
name: "deepccc",
|
|
339
|
-
baseURL,
|
|
340
|
-
apiKey,
|
|
341
|
-
includeUsage: true,
|
|
342
|
-
});
|
|
343
|
-
this.model = provider(modelId);
|
|
344
|
-
}
|
|
345
|
-
this.cwd = options.cwd ?? process.cwd();
|
|
346
|
-
this.maxSteps = normalizeMaxSteps(options.maxSteps);
|
|
347
|
-
this.compactionTimeoutMs = Math.max(1, options.compactionTimeoutMs ?? DEFAULT_COMPACTION_TIMEOUT_MS);
|
|
348
|
-
this.customSystemPrompt = options.systemPrompt ?? "";
|
|
349
|
-
// 技能目录在构造时确定;技能内容在每次 chat() 前重新扫描(mtime 热加载),
|
|
350
|
-
// 因此创建/修改技能后下一次对话自动生效,无需重启。
|
|
351
|
-
this.skillDirs =
|
|
352
|
-
options.skillsDirs?.map((d) => ({ dir: d, source: "deepccc" as const, scope: "project" as const })) ??
|
|
353
|
-
buildDefaultSkillDirs(this.cwd);
|
|
354
|
-
this.context = new BuiltinContextManager({
|
|
355
|
-
persist: options.persist ?? false,
|
|
356
|
-
contextDir: options.contextDir,
|
|
357
|
-
sessionId: options.sessionId ?? defaultBuiltinSessionId(this.cwd),
|
|
358
|
-
cwd: this.cwd,
|
|
359
|
-
compactAtTokens: options.compactAtTokens,
|
|
360
|
-
keepRecentMessages: options.keepRecentMessages,
|
|
361
|
-
});
|
|
362
|
-
this.permissionGate = new PermissionGate(
|
|
363
|
-
options.permissionMode ?? "ask",
|
|
364
|
-
options.permissionResolver,
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* 组装系统提示词。顺序遵循“稳定性优先”原则(缓存命中友好):
|
|
370
|
-
* 固定规则 → 项目指令 → runtime 上下文 → 用户补充 → 技能索引(最后)。
|
|
371
|
-
* 技能索引是最易变的部分(热加载,任何 SKILL.md 变化都会改前缀),
|
|
372
|
-
* 放最后可以让前面的稳定内容尽量命中缓存,只丢尾段。
|
|
373
|
-
*/
|
|
374
|
-
private buildSystemPrompt(skills: BuiltinSkill[]): string {
|
|
375
|
-
const systemContent = [SYSTEM_PROMPT];
|
|
376
|
-
const platformPrompt = loadPlatformCommandPrompt();
|
|
377
|
-
if (platformPrompt) {
|
|
378
|
-
systemContent.push("", platformPrompt);
|
|
379
|
-
}
|
|
380
|
-
const projectInstructions = readProjectInstructionFiles(this.cwd);
|
|
381
|
-
if (projectInstructions) {
|
|
382
|
-
systemContent.push("", projectInstructions);
|
|
383
|
-
}
|
|
384
|
-
systemContent.push("", buildRuntimeWorkspacePrompt(this.cwd));
|
|
385
|
-
if (this.customSystemPrompt) {
|
|
386
|
-
systemContent.push("", this.customSystemPrompt);
|
|
387
|
-
}
|
|
388
|
-
const skillsPrompt = buildSkillsIndexPrompt(skills);
|
|
389
|
-
if (skillsPrompt) {
|
|
390
|
-
systemContent.push("", skillsPrompt);
|
|
391
|
-
}
|
|
392
|
-
return systemContent.join("\n");
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async *chat(
|
|
396
|
-
userMessage: string,
|
|
397
|
-
signal?: AbortSignal,
|
|
398
|
-
): AsyncIterable<ChatEvent> {
|
|
399
|
-
this.context.appendMessage({ role: "user", content: userMessage });
|
|
400
|
-
|
|
401
|
-
let fullText = "";
|
|
402
|
-
let safeAccumulated = "";
|
|
403
|
-
let rawLog: RawStreamLogHandle | null = null;
|
|
404
|
-
let completed = false;
|
|
405
|
-
// 结构化工具调用存档:按 toolCallId 关联入参/出参/错误,落盘到 context.json 的
|
|
406
|
-
// assistant 消息 toolCalls 字段;[Tool transcript] 文本视图仍按原格式生成。
|
|
407
|
-
const toolCallsById = new Map<string, { name: string; input?: string; output?: string; is_error?: boolean }>();
|
|
408
|
-
const toolCallOrder: string[] = [];
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
if (this.context.planCompaction()) {
|
|
412
|
-
yield { type: "status", phase: "compacting" };
|
|
413
|
-
const compactedMessages = await this.compactIfNeeded(signal);
|
|
414
|
-
if (compactedMessages > 0) {
|
|
415
|
-
yield { type: "compact", compactedMessages };
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
yield { type: "status", phase: "generating" };
|
|
419
|
-
|
|
420
|
-
const rawLogConfig = appConfig.rawStreamLogs;
|
|
421
|
-
try {
|
|
422
|
-
rawLog = await createRawStreamLog({
|
|
423
|
-
enabled: rawLogConfig.enabled,
|
|
424
|
-
rootDir: RAW_STREAM_LOGS_DIR,
|
|
425
|
-
tool: "deepccc",
|
|
426
|
-
sessionId: this.context.sessionId,
|
|
427
|
-
label: "prompt",
|
|
428
|
-
maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
|
|
429
|
-
retentionDays: rawLogConfig.retentionDays,
|
|
430
|
-
});
|
|
431
|
-
} catch (err) {
|
|
432
|
-
console.error(`[DeepCCC raw stream log] create failed: ${errorMessage(err)}`);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const toolContext: string[] = [];
|
|
436
|
-
const maxSteps = this.maxSteps;
|
|
437
|
-
// 每次对话前重新扫描技能索引(并行 + mtime 缓存,开销极小):
|
|
438
|
-
// 新技能/修改的技能在下一次对话自动生效(热加载)。
|
|
439
|
-
const skills = await scanSkillsDirs(this.skillDirs);
|
|
440
|
-
const system = this.buildSystemPrompt(skills);
|
|
441
|
-
this.systemPrompt = system;
|
|
442
|
-
const contextMessages = this.context.buildModelMessages();
|
|
443
|
-
const modelMessages = this.provider === "anthropic"
|
|
444
|
-
? addAnthropicToolJsonCompatibilityNote(contextMessages)
|
|
445
|
-
: contextMessages;
|
|
446
|
-
// effort 按协议映射:
|
|
447
|
-
// - OpenAI 兼容:providerOptions.deepseek.reasoningEffort 由 @ai-sdk/openai-compatible
|
|
448
|
-
// 自动映射为请求体 reasoning_effort 字段(DeepSeek 原生支持);
|
|
449
|
-
// - Anthropic:providerOptions.anthropic.effort 由 @ai-sdk/anthropic 组装为请求体
|
|
450
|
-
// output_config.effort(官方 Effort API,见 platform.claude.com/docs/en/build-with-claude/effort)
|
|
451
|
-
let effortProviderOptions: Record<string, JSONObject> | undefined;
|
|
452
|
-
if (this.effort) {
|
|
453
|
-
effortProviderOptions = this.provider === "openai"
|
|
454
|
-
? { deepseek: { reasoningEffort: this.effort } }
|
|
455
|
-
: { anthropic: { effort: this.effort } };
|
|
456
|
-
}
|
|
457
|
-
const generationOptions = {
|
|
458
|
-
model: this.model,
|
|
459
|
-
system,
|
|
460
|
-
messages: modelMessages as any,
|
|
461
|
-
tools: createBuiltinFileTools(this.cwd, { permissionGate: this.permissionGate }),
|
|
462
|
-
stopWhen: maxSteps !== undefined ? stepCountIs(maxSteps) : isLoopFinished(),
|
|
463
|
-
abortSignal: signal,
|
|
464
|
-
...(effortProviderOptions ? { providerOptions: effortProviderOptions } : {}),
|
|
465
|
-
};
|
|
466
|
-
let stream: AsyncIterable<TextStreamPart<any>>;
|
|
467
|
-
if (appConfig.streaming) {
|
|
468
|
-
const result = streamText(generationOptions);
|
|
469
|
-
stream = result.fullStream ?? textStreamToFullStream(result.textStream);
|
|
470
|
-
} else {
|
|
471
|
-
const result = await generateText(generationOptions);
|
|
472
|
-
stream = generateResultToFullStream(result);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
for await (const part of stream as AsyncIterable<TextStreamPart<any>>) {
|
|
476
|
-
rawLog?.writeLine(safeRawStreamJson(part));
|
|
477
|
-
if (part.type === "text-delta") {
|
|
478
|
-
fullText += part.text;
|
|
479
|
-
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
480
|
-
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
481
|
-
const safeText = applyPrivacy(part.text);
|
|
482
|
-
safeAccumulated += safeText;
|
|
483
|
-
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
484
|
-
} else if (part.type === "tool-call") {
|
|
485
|
-
toolContext.push(`tool_call ${part.toolName}: ${safeJson(part.input)}`);
|
|
486
|
-
toolCallsById.set(part.toolCallId, { name: part.toolName, input: safeJson(part.input) });
|
|
487
|
-
toolCallOrder.push(part.toolCallId);
|
|
488
|
-
yield {
|
|
489
|
-
type: "tool_use",
|
|
490
|
-
id: part.toolCallId,
|
|
491
|
-
name: part.toolName,
|
|
492
|
-
input: applyPrivacyToJson(part.input),
|
|
493
|
-
};
|
|
494
|
-
} else if (part.type === "tool-result") {
|
|
495
|
-
toolContext.push(`tool_result ${part.toolName}: ${truncateToolContext(safeJson(part.output))}`);
|
|
496
|
-
const call = toolCallsById.get(part.toolCallId);
|
|
497
|
-
if (call) {
|
|
498
|
-
call.output = truncateToolContext(safeJson(part.output));
|
|
499
|
-
}
|
|
500
|
-
yield {
|
|
501
|
-
type: "tool_result",
|
|
502
|
-
tool_use_id: part.toolCallId,
|
|
503
|
-
name: part.toolName,
|
|
504
|
-
content: applyPrivacyToJson(part.output),
|
|
505
|
-
is_error: false,
|
|
506
|
-
};
|
|
507
|
-
} else if (part.type === "tool-error") {
|
|
508
|
-
const message = errorMessage(part.error);
|
|
509
|
-
toolContext.push(`tool_error ${part.toolName}: ${message}`);
|
|
510
|
-
const call = toolCallsById.get(part.toolCallId);
|
|
511
|
-
if (call) {
|
|
512
|
-
call.output = message;
|
|
513
|
-
call.is_error = true;
|
|
514
|
-
}
|
|
515
|
-
yield {
|
|
516
|
-
type: "tool_result",
|
|
517
|
-
tool_use_id: part.toolCallId,
|
|
518
|
-
name: part.toolName,
|
|
519
|
-
content: applyPrivacy(message),
|
|
520
|
-
is_error: true,
|
|
521
|
-
};
|
|
522
|
-
} else if (part.type === "error") {
|
|
523
|
-
const message = errorMessage(part.error);
|
|
524
|
-
yield { type: "error", message: applyPrivacy(message) };
|
|
525
|
-
throw new Error(message);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
completed = true;
|
|
529
|
-
|
|
530
|
-
const collectedToolCalls = toolCallOrder
|
|
531
|
-
.map((id) => toolCallsById.get(id))
|
|
532
|
-
.filter((call): call is { name: string; input?: string; output?: string; is_error?: boolean } => call !== undefined);
|
|
533
|
-
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
534
|
-
fullText,
|
|
535
|
-
transcriptLines: toolContext,
|
|
536
|
-
toolCalls: collectedToolCalls,
|
|
537
|
-
}));
|
|
538
|
-
yield { type: "done", text: safeAccumulated };
|
|
539
|
-
} catch (err) {
|
|
540
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
541
|
-
if ((err as Error).name === "AbortError" || signal?.aborted) {
|
|
542
|
-
// 被中断时,不保存不完整的助手消息
|
|
543
|
-
if (fullText) {
|
|
544
|
-
this.context.appendMessage({ role: "assistant", content: `${fullText}\n[interrupted]` });
|
|
545
|
-
}
|
|
546
|
-
yield { type: "done", text: safeAccumulated };
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
yield { type: "error", message: applyPrivacy(message) };
|
|
550
|
-
throw err;
|
|
551
|
-
} finally {
|
|
552
|
-
const rawLogConfig = appConfig.rawStreamLogs;
|
|
553
|
-
await rawLog?.close({
|
|
554
|
-
keep: rawLogConfig.keepCompleted || signal?.aborted === true || !completed,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/** 返回当前的会话历史(只读) */
|
|
560
|
-
get history(): ReadonlyArray<ChatMessage> {
|
|
561
|
-
const history: ChatMessage[] = [{ role: "system", content: this.systemPrompt }];
|
|
562
|
-
if (this.context.summary) {
|
|
563
|
-
history.push({
|
|
564
|
-
role: "system",
|
|
565
|
-
content: [
|
|
566
|
-
"更早的对话摘要:",
|
|
567
|
-
"",
|
|
568
|
-
this.context.summary,
|
|
569
|
-
].join("\n"),
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
history.push(...this.context.messages as ChatMessage[]);
|
|
573
|
-
return history;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/** 返回当前轮数(不含 system 消息) */
|
|
577
|
-
get turnCount(): number {
|
|
578
|
-
return this.context.totalMessages;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/** 清空会话历史,保留 system 消息 */
|
|
582
|
-
reset(): void {
|
|
583
|
-
this.context.reset();
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
private async compactIfNeeded(signal?: AbortSignal): Promise<number> {
|
|
587
|
-
if (!this.context.planCompaction()) return 0;
|
|
588
|
-
|
|
589
|
-
const timeoutController = new AbortController();
|
|
590
|
-
const timeout = setTimeout(() => timeoutController.abort(), this.compactionTimeoutMs);
|
|
591
|
-
timeout.unref?.();
|
|
592
|
-
const compactionSignal = signal
|
|
593
|
-
? AbortSignal.any([signal, timeoutController.signal])
|
|
594
|
-
: timeoutController.signal;
|
|
595
|
-
|
|
596
|
-
try {
|
|
597
|
-
const plan = this.context.planCompaction();
|
|
598
|
-
if (!plan) return 0;
|
|
599
|
-
|
|
600
|
-
const result = await generateText({
|
|
601
|
-
model: this.model,
|
|
602
|
-
system: SUMMARY_SYSTEM_PROMPT,
|
|
603
|
-
messages: [{ role: "user", content: buildSummaryPrompt(plan) }],
|
|
604
|
-
abortSignal: compactionSignal,
|
|
605
|
-
temperature: 0,
|
|
606
|
-
// 摘要单轮生成:显式放宽 maxOutputTokens,避免 AI SDK 对未知模型的
|
|
607
|
-
// 兼容模式默认 4096 上限导致摘要生成不完(那是旧版多轮压缩的根因);
|
|
608
|
-
// 同时锁低 effort(OpenAI reasoning_effort=none / Anthropic
|
|
609
|
-
// output_config.effort=low),避免继承主对话的高 effort 拖慢"压缩上下文中"阶段。
|
|
610
|
-
maxOutputTokens: MAX_COMPACTION_OUTPUT_TOKENS,
|
|
611
|
-
providerOptions: this.provider === "openai"
|
|
612
|
-
? { deepseek: { reasoningEffort: "none" } }
|
|
613
|
-
: { anthropic: { effort: "low" } },
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
if (!result.text.trim()) {
|
|
617
|
-
throw new Error("Context compaction returned an empty summary");
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
this.context.applyCompaction(result.text, plan);
|
|
621
|
-
// 单轮压缩:不再反复迭代重试。若上下文仍超预算(如 recent 消息本身超大),
|
|
622
|
-
// 留给下一次对话前再次压缩,避免阻塞当前回复生成(业界同步压缩的标准取舍)。
|
|
623
|
-
return plan.oldMessages.length;
|
|
624
|
-
} catch (error) {
|
|
625
|
-
if (timeoutController.signal.aborted && !signal?.aborted) {
|
|
626
|
-
throw new Error(`Context compaction timed out after ${formatDuration(this.compactionTimeoutMs)}`);
|
|
627
|
-
}
|
|
628
|
-
throw error;
|
|
629
|
-
} finally {
|
|
630
|
-
clearTimeout(timeout);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
async function* textStreamToFullStream(stream: AsyncIterable<string>): AsyncIterable<{ type: "text-delta"; text: string }> {
|
|
636
|
-
for await (const text of stream) {
|
|
637
|
-
yield { type: "text-delta", text };
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
async function* generateResultToFullStream(result: any): AsyncIterable<TextStreamPart<any>> {
|
|
642
|
-
let emittedText = false;
|
|
643
|
-
for (const step of result.steps ?? []) {
|
|
644
|
-
for (const call of step.toolCalls ?? []) {
|
|
645
|
-
yield {
|
|
646
|
-
type: "tool-call",
|
|
647
|
-
toolCallId: call.toolCallId,
|
|
648
|
-
toolName: call.toolName,
|
|
649
|
-
input: call.input,
|
|
650
|
-
} as TextStreamPart<any>;
|
|
651
|
-
}
|
|
652
|
-
for (const toolResult of step.toolResults ?? []) {
|
|
653
|
-
yield {
|
|
654
|
-
type: "tool-result",
|
|
655
|
-
toolCallId: toolResult.toolCallId,
|
|
656
|
-
toolName: toolResult.toolName,
|
|
657
|
-
output: toolResult.output,
|
|
658
|
-
} as TextStreamPart<any>;
|
|
659
|
-
}
|
|
660
|
-
if (step.text) {
|
|
661
|
-
emittedText = true;
|
|
662
|
-
yield { type: "text-delta", text: step.text } as TextStreamPart<any>;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
if (!emittedText && result.text) {
|
|
666
|
-
yield { type: "text-delta", text: result.text } as TextStreamPart<any>;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function safeJson(value: unknown): string {
|
|
671
|
-
try {
|
|
672
|
-
return JSON.stringify(value);
|
|
673
|
-
} catch {
|
|
674
|
-
return String(value);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function safeRawStreamJson(value: unknown): string {
|
|
679
|
-
try {
|
|
680
|
-
const serialized = JSON.stringify(value, (_key, nested) => {
|
|
681
|
-
if (nested instanceof Error) {
|
|
682
|
-
return {
|
|
683
|
-
name: nested.name,
|
|
684
|
-
message: nested.message,
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
return nested;
|
|
688
|
-
});
|
|
689
|
-
return serialized ?? "null";
|
|
690
|
-
} catch (err) {
|
|
691
|
-
return JSON.stringify({
|
|
692
|
-
type: "deepccc_raw_stream_log_serialize_error",
|
|
693
|
-
message: errorMessage(err),
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function truncateToolContext(value: string): string {
|
|
699
|
-
return value.length > 8000 ? `${value.slice(0, 8000)}...[truncated]` : value;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function formatDuration(ms: number): string {
|
|
703
|
-
if (ms % 60_000 === 0) return `${ms / 60_000} minutes`;
|
|
704
|
-
if (ms % 1_000 === 0) return `${ms / 1_000} seconds`;
|
|
705
|
-
return `${ms} ms`;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function errorMessage(value: unknown): string {
|
|
709
|
-
return value instanceof Error ? value.message : String(value);
|
|
710
|
-
}
|