chatccc 0.2.244 → 0.2.246
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 +8 -8
- package/deepccc-agent/README.md +20 -0
- package/deepccc-agent/package-lock.json +70 -2
- package/deepccc-agent/package.json +4 -2
- package/deepccc-agent/src/__tests__/chat-session.test.ts +126 -2
- package/deepccc-agent/src/__tests__/config.test.ts +34 -26
- package/deepccc-agent/src/__tests__/permissions.test.ts +199 -195
- package/deepccc-agent/src/__tests__/privacy.test.ts +7 -0
- package/deepccc-agent/src/cli.ts +16 -4
- package/deepccc-agent/src/config.ts +101 -84
- package/deepccc-agent/src/index.ts +74 -12
- package/package.json +2 -1
- package/src/__tests__/builtin-chat-session.test.ts +532 -522
- package/src/__tests__/builtin-permissions.test.ts +219 -211
- package/src/__tests__/ccc-adapter.test.ts +194 -170
- package/src/__tests__/session.test.ts +362 -299
- package/src/adapters/adapter-interface.ts +39 -33
- package/src/adapters/ccc-adapter.ts +150 -145
- package/src/session.ts +321 -318
package/README.md
CHANGED
|
@@ -208,15 +208,15 @@ chatccc
|
|
|
208
208
|
|
|
209
209
|
### 3. AI 工具配置
|
|
210
210
|
|
|
211
|
-
Claude Code、Cursor 和 Codex 需要对应的本地工具;CCC Agent 内置于 ChatCCC,开箱即用,**模型接入不限于 DeepSeek
|
|
211
|
+
Claude Code、Cursor 和 Codex 需要对应的本地工具;CCC Agent 内置于 ChatCCC,开箱即用,**模型接入不限于 DeepSeek**——它支持 OpenAI-compatible 和 Anthropic Messages 两种协议(详见下文 CCC Agent)。
|
|
212
212
|
|
|
213
|
-
#### CCC Agent
|
|
214
|
-
|
|
215
|
-
CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
|
|
216
|
-
|
|
217
|
-
ChatCCC 会把 `ccc.DEEPSEEK_API_KEY`、`ccc.DEEPSEEK_BASE_URL`、模型和 effort 显式传给内置 Agent;这些配置不会回退读取 `~/.deepccc/config.json`,因此用户无需安装或配置独立的 `deepccc` 包。API Key 为空时 CCC Agent 会自动保持禁用。
|
|
213
|
+
#### CCC Agent
|
|
218
214
|
|
|
219
|
-
|
|
215
|
+
CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
|
|
216
|
+
|
|
217
|
+
ChatCCC 会把 `ccc.DEEPSEEK_API_KEY`、`ccc.DEEPSEEK_BASE_URL`、模型和 effort 显式传给内置 Agent;API Key 为空时 CCC Agent 会自动保持禁用。DeepCCC 的传输层选项 `provider`(默认 `openai`)和 `streaming`(默认 `true`)可通过 `~/.deepccc/config.json` 或 `DEEPCCC_PROVIDER` / `DEEPCCC_STREAMING` 配置,无需额外安装独立 CLI。
|
|
218
|
+
|
|
219
|
+
**API 支持不限于 DeepSeek。** 默认的 `provider: "openai"` 使用 OpenAI 兼容协议(`@ai-sdk/openai-compatible`),DeepSeek 只是出厂默认端点;设置 `provider: "anthropic"` 后改用 Anthropic Messages 协议(`@ai-sdk/anthropic`)。两种模式都支持流式输出,并复用相同的 API Key、Base URL 和模型配置。
|
|
220
220
|
|
|
221
221
|
| 服务 | Base URL 示例 | 说明 |
|
|
222
222
|
| --- | --- | --- |
|
|
@@ -371,7 +371,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
|
|
|
371
371
|
|
|
372
372
|
**微信:** 扫码登录后,在机器人私聊里发送 `/new` 或指定 Agent 的 `/new ...` 命令即可开始。功能与飞书基本一致,但展示为纯文本。
|
|
373
373
|
|
|
374
|
-
**会话停滞保护:** 只有 Agent 明确进入“生成回复中”后,连续 3 分钟没有新增回复字符且尚未报告权威终态,ChatCCC 才判定停滞、结束旧 CLI
|
|
374
|
+
**会话停滞保护:** 只有 Agent 明确进入“生成回复中”后,连续 3 分钟没有新增回复字符且尚未报告权威终态,ChatCCC 才判定停滞、结束旧 CLI,并优先补发一次“完成了吗?如果没完成继续”;恢复轮再次发生相同停滞时不再递归续跑。启动、上下文压缩、思考、搜索和工具调用阶段不会触发这项回复停滞计时;DeepCCC 关闭 streaming 时只会在请求完成后一次性返回结果,因此不会启用这项基于流式字符进度的停滞检测。其中 CCC Agent 会单独显示“压缩上下文中”,压缩最多等待 5 分钟,失败时直接报告具体原因且不自动重放。`/new claude` 和 `/new cursor` 等创建会话操作仍有独立的 init 超时,进程资源监控也继续负责识别真正僵死。Codex 只有 `turn.completed` 才算权威终态,阶段性的 `agent_message` 不算;任一 Agent 报告权威终态后若输出流仍超过 10 秒未关闭,ChatCCC 会强制清理该 CLI 并按正常完成收尾,不会重复询问 Agent。
|
|
375
375
|
|
|
376
376
|
**CCC Agent 代码搜索:** `search_code` 使用项目自带的跨平台 ripgrep,不要求系统另行安装 `rg`。如果当前平台没有可用的 bundled/system ripgrep,会自动降级为内置 Node 搜索,并继续支持常用正则、glob、结果上限、中止和超时控制。
|
|
377
377
|
|
package/deepccc-agent/README.md
CHANGED
|
@@ -47,14 +47,17 @@ deepccc 的本地缓存优化实测命中率 **96.7%**,有效降低重复请
|
|
|
47
47
|
export DEEPCCC_API_KEY="sk-..."
|
|
48
48
|
export DEEPCCC_BASE_URL="https://api.deepseek.com/v1"
|
|
49
49
|
export DEEPCCC_MODEL="deepseek-v4-pro"
|
|
50
|
+
export DEEPCCC_STREAMING="true"
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
Windows PowerShell:
|
|
53
54
|
|
|
54
55
|
```powershell
|
|
55
56
|
$env:DEEPCCC_API_KEY="sk-..."
|
|
57
|
+
$env:DEEPCCC_PROVIDER="openai"
|
|
56
58
|
$env:DEEPCCC_BASE_URL="https://api.deepseek.com/v1"
|
|
57
59
|
$env:DEEPCCC_MODEL="deepseek-v4-pro"
|
|
60
|
+
$env:DEEPCCC_STREAMING="true"
|
|
58
61
|
```
|
|
59
62
|
|
|
60
63
|
也兼容这些 DeepSeek 别名:
|
|
@@ -68,10 +71,12 @@ $env:DEEPCCC_MODEL="deepseek-v4-pro"
|
|
|
68
71
|
|
|
69
72
|
```json
|
|
70
73
|
{
|
|
74
|
+
"provider": "openai",
|
|
71
75
|
"apiKey": "sk-...",
|
|
72
76
|
"baseURL": "https://api.deepseek.com/v1",
|
|
73
77
|
"model": "deepseek-v4-pro",
|
|
74
78
|
"effort": "",
|
|
79
|
+
"streaming": true,
|
|
75
80
|
"rawStreamLogs": {
|
|
76
81
|
"enabled": false,
|
|
77
82
|
"maxBytesPerTurn": 1048576,
|
|
@@ -81,6 +86,15 @@ $env:DEEPCCC_MODEL="deepseek-v4-pro"
|
|
|
81
86
|
}
|
|
82
87
|
```
|
|
83
88
|
|
|
89
|
+
`provider` 可选 `openai` 或 `anthropic`,默认 `openai`,也可以通过
|
|
90
|
+
`DEEPCCC_PROVIDER` 或命令行 `--provider` 覆盖:
|
|
91
|
+
|
|
92
|
+
- `openai` 使用 OpenAI-compatible Chat Completions 协议,兼容 DeepSeek、OpenAI、LiteLLM、vLLM 等服务。
|
|
93
|
+
- `anthropic` 使用 Anthropic Messages 协议。配置中的 `baseURL` 可以继续填写同一个服务基址;DeepCCC 会为不带 `/v1` 的地址自动补上 `/v1`。`effort` 是 OpenAI/DeepSeek 扩展参数,在 Anthropic 模式下不会发送。
|
|
94
|
+
|
|
95
|
+
`streaming` 控制主对话是否使用流式请求,默认 `true`;也可以通过
|
|
96
|
+
`DEEPCCC_STREAMING=true|false` 覆盖。关闭后,终端会在整条模型响应完成后一次性显示结果。
|
|
97
|
+
|
|
84
98
|
## 命令行交互
|
|
85
99
|
|
|
86
100
|
在当前目录启动一个交互式 Agent:
|
|
@@ -95,6 +109,12 @@ deepccc
|
|
|
95
109
|
deepccc --base-url https://api.openai.com/v1 --api-key "$OPENAI_API_KEY" --model gpt-4.1
|
|
96
110
|
```
|
|
97
111
|
|
|
112
|
+
使用 Anthropic Messages 协议(同样支持流式输出):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
deepccc --provider anthropic --base-url https://api.example.com --api-key "$API_KEY" --model claude-sonnet-4-6
|
|
116
|
+
```
|
|
117
|
+
|
|
98
118
|
指定工作目录:
|
|
99
119
|
|
|
100
120
|
```bash
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepccc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "deepccc",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.16",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@ai-sdk/anthropic": "^3.0.105",
|
|
12
13
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
13
14
|
"@vscode/ripgrep": "^1.18.0",
|
|
14
15
|
"ai": "^6.0.184"
|
|
@@ -26,6 +27,52 @@
|
|
|
26
27
|
"node": ">=20"
|
|
27
28
|
}
|
|
28
29
|
},
|
|
30
|
+
"node_modules/@ai-sdk/anthropic": {
|
|
31
|
+
"version": "3.0.105",
|
|
32
|
+
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.105.tgz",
|
|
33
|
+
"integrity": "sha512-6B9UjgK14JwG4PtyhHKMQrqg49s90FClsreNZ9HmMdSWZ9tJFHuYmy4cauS/BqO/6rKC+qcNEXTP45+qivdypA==",
|
|
34
|
+
"license": "Apache-2.0",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@ai-sdk/provider": "3.0.14",
|
|
37
|
+
"@ai-sdk/provider-utils": "4.0.41"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"zod": "^3.25.76 || ^4.1.8"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider": {
|
|
47
|
+
"version": "3.0.14",
|
|
48
|
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.14.tgz",
|
|
49
|
+
"integrity": "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA==",
|
|
50
|
+
"license": "Apache-2.0",
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"json-schema": "^0.4.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": {
|
|
59
|
+
"version": "4.0.41",
|
|
60
|
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.41.tgz",
|
|
61
|
+
"integrity": "sha512-I7hhjfw01yEI8NkuAsT8Mv6xbWFr/lqLXMdaJQ2zWfXEpxog1eT7skDcv1+RY29/+5btzH8wD+vVvy48bk9oNQ==",
|
|
62
|
+
"license": "Apache-2.0",
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@ai-sdk/provider": "3.0.14",
|
|
65
|
+
"@standard-schema/spec": "^1.1.0",
|
|
66
|
+
"eventsource-parser": "^3.0.8",
|
|
67
|
+
"undici": "^5.29.0"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=18"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"zod": "^3.25.76 || ^4.1.8"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
29
76
|
"node_modules/@ai-sdk/gateway": {
|
|
30
77
|
"version": "3.0.143",
|
|
31
78
|
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.143.tgz",
|
|
@@ -530,6 +577,15 @@
|
|
|
530
577
|
"node": ">=18"
|
|
531
578
|
}
|
|
532
579
|
},
|
|
580
|
+
"node_modules/@fastify/busboy": {
|
|
581
|
+
"version": "2.1.1",
|
|
582
|
+
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
|
583
|
+
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
|
584
|
+
"license": "MIT",
|
|
585
|
+
"engines": {
|
|
586
|
+
"node": ">=14"
|
|
587
|
+
}
|
|
588
|
+
},
|
|
533
589
|
"node_modules/@jridgewell/sourcemap-codec": {
|
|
534
590
|
"version": "1.5.5",
|
|
535
591
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
|
@@ -1749,6 +1805,18 @@
|
|
|
1749
1805
|
"node": ">=14.17"
|
|
1750
1806
|
}
|
|
1751
1807
|
},
|
|
1808
|
+
"node_modules/undici": {
|
|
1809
|
+
"version": "5.29.0",
|
|
1810
|
+
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
|
1811
|
+
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
|
1812
|
+
"license": "MIT",
|
|
1813
|
+
"dependencies": {
|
|
1814
|
+
"@fastify/busboy": "^2.0.0"
|
|
1815
|
+
},
|
|
1816
|
+
"engines": {
|
|
1817
|
+
"node": ">=14.0"
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1752
1820
|
"node_modules/undici-types": {
|
|
1753
1821
|
"version": "6.21.0",
|
|
1754
1822
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepccc",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A lightweight
|
|
3
|
+
"version": "0.1.16",
|
|
4
|
+
"description": "A lightweight coding agent with OpenAI-compatible and Anthropic Messages API support.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
8
8
|
"deepseek",
|
|
9
9
|
"coding-agent",
|
|
10
10
|
"openai-compatible",
|
|
11
|
+
"anthropic",
|
|
11
12
|
"cli"
|
|
12
13
|
],
|
|
13
14
|
"repository": {
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"typecheck": "node node_modules/typescript/bin/tsc --noEmit"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
51
|
+
"@ai-sdk/anthropic": "^3.0.105",
|
|
50
52
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
51
53
|
"@vscode/ripgrep": "^1.18.0",
|
|
52
54
|
"ai": "^6.0.184"
|
|
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
7
|
|
|
8
8
|
import { config } from "../config.js";
|
|
9
9
|
import { estimateBuiltinContextTokens } from "../context.js";
|
|
@@ -14,9 +14,17 @@ const createRawStreamLogMock = vi.fn();
|
|
|
14
14
|
const rawLogWriteLineMock = vi.fn();
|
|
15
15
|
const rawLogCloseMock = vi.fn();
|
|
16
16
|
const originalRawStreamLogs = structuredClone(config.rawStreamLogs);
|
|
17
|
+
const originalStreaming = config.streaming;
|
|
18
|
+
const originalProvider = config.provider;
|
|
19
|
+
const createOpenAICompatibleMock = vi.fn(() => (modelId: string) => ({ modelId }));
|
|
20
|
+
const createAnthropicMock = vi.fn(() => (modelId: string) => ({ modelId, provider: "anthropic" }));
|
|
17
21
|
|
|
18
22
|
vi.mock("@ai-sdk/openai-compatible", () => ({
|
|
19
|
-
createOpenAICompatible:
|
|
23
|
+
createOpenAICompatible: createOpenAICompatibleMock,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
vi.mock("@ai-sdk/anthropic", () => ({
|
|
27
|
+
createAnthropic: createAnthropicMock,
|
|
20
28
|
}));
|
|
21
29
|
|
|
22
30
|
vi.mock("ai", () => ({
|
|
@@ -46,6 +54,11 @@ async function* fullStream(...parts: unknown[]): AsyncIterable<unknown> {
|
|
|
46
54
|
for (const part of parts) yield part;
|
|
47
55
|
}
|
|
48
56
|
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
config.provider = "openai";
|
|
59
|
+
config.streaming = true;
|
|
60
|
+
});
|
|
61
|
+
|
|
49
62
|
afterEach(() => {
|
|
50
63
|
streamTextMock.mockReset();
|
|
51
64
|
generateTextMock.mockReset();
|
|
@@ -53,9 +66,120 @@ afterEach(() => {
|
|
|
53
66
|
rawLogWriteLineMock.mockReset();
|
|
54
67
|
rawLogCloseMock.mockReset();
|
|
55
68
|
config.rawStreamLogs = structuredClone(originalRawStreamLogs);
|
|
69
|
+
config.provider = originalProvider;
|
|
70
|
+
config.streaming = originalStreaming;
|
|
71
|
+
createOpenAICompatibleMock.mockClear();
|
|
72
|
+
createAnthropicMock.mockClear();
|
|
56
73
|
vi.useRealTimers();
|
|
57
74
|
});
|
|
58
75
|
|
|
76
|
+
describe("ChatSession response transport", () => {
|
|
77
|
+
it("uses the OpenAI-compatible provider by default", async () => {
|
|
78
|
+
const { ChatSession } = await import("../index.js");
|
|
79
|
+
|
|
80
|
+
new ChatSession({ apiKey: "sk-test", baseURL: "https://gateway.example", model: "model-a" });
|
|
81
|
+
|
|
82
|
+
expect(createOpenAICompatibleMock).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
83
|
+
baseURL: "https://gateway.example",
|
|
84
|
+
apiKey: "sk-test",
|
|
85
|
+
}));
|
|
86
|
+
expect(createAnthropicMock).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("uses Anthropic Messages with the same base URL and appends /v1 when needed", async () => {
|
|
90
|
+
const { ChatSession } = await import("../index.js");
|
|
91
|
+
|
|
92
|
+
new ChatSession({
|
|
93
|
+
provider: "anthropic",
|
|
94
|
+
apiKey: "sk-test",
|
|
95
|
+
baseURL: "https://gateway.example/",
|
|
96
|
+
model: "model-a",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(createAnthropicMock).toHaveBeenLastCalledWith({
|
|
100
|
+
baseURL: "https://gateway.example/v1",
|
|
101
|
+
apiKey: "sk-test",
|
|
102
|
+
});
|
|
103
|
+
expect(createOpenAICompatibleMock).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("keeps an existing /v1 suffix for Anthropic and streams without OpenAI-only effort options", async () => {
|
|
107
|
+
const { ChatSession } = await import("../index.js");
|
|
108
|
+
streamTextMock.mockReturnValueOnce({ textStream: textStream("done") });
|
|
109
|
+
const session = new ChatSession({
|
|
110
|
+
provider: "anthropic",
|
|
111
|
+
apiKey: "sk-test",
|
|
112
|
+
baseURL: "https://gateway.example/v1/",
|
|
113
|
+
model: "model-a",
|
|
114
|
+
effort: "high",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await collect(session.chat("hello"));
|
|
118
|
+
|
|
119
|
+
expect(createAnthropicMock).toHaveBeenLastCalledWith({
|
|
120
|
+
baseURL: "https://gateway.example/v1",
|
|
121
|
+
apiKey: "sk-test",
|
|
122
|
+
});
|
|
123
|
+
expect(streamTextMock).toHaveBeenCalledOnce();
|
|
124
|
+
expect(streamTextMock.mock.calls[0]?.[0]).not.toHaveProperty("providerOptions");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("asks the provider to include usage in streaming responses", async () => {
|
|
128
|
+
const { ChatSession } = await import("../index.js");
|
|
129
|
+
|
|
130
|
+
new ChatSession({ apiKey: "sk-test" });
|
|
131
|
+
|
|
132
|
+
expect(createOpenAICompatibleMock).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
133
|
+
includeUsage: true,
|
|
134
|
+
}));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("uses generateText and preserves text/tool events when streaming is disabled", async () => {
|
|
138
|
+
const { ChatSession } = await import("../index.js");
|
|
139
|
+
config.streaming = false;
|
|
140
|
+
generateTextMock.mockResolvedValueOnce({
|
|
141
|
+
text: "done",
|
|
142
|
+
steps: [{
|
|
143
|
+
text: "done",
|
|
144
|
+
toolCalls: [{
|
|
145
|
+
type: "tool-call",
|
|
146
|
+
toolCallId: "call-1",
|
|
147
|
+
toolName: "read_file",
|
|
148
|
+
input: { path: "package.json" },
|
|
149
|
+
}],
|
|
150
|
+
toolResults: [{
|
|
151
|
+
type: "tool-result",
|
|
152
|
+
toolCallId: "call-1",
|
|
153
|
+
toolName: "read_file",
|
|
154
|
+
input: { path: "package.json" },
|
|
155
|
+
output: { content: "{}" },
|
|
156
|
+
}],
|
|
157
|
+
}],
|
|
158
|
+
});
|
|
159
|
+
const session = new ChatSession({ apiKey: "sk-test" });
|
|
160
|
+
|
|
161
|
+
const events = await collect(session.chat("read package"));
|
|
162
|
+
|
|
163
|
+
expect(streamTextMock).not.toHaveBeenCalled();
|
|
164
|
+
expect(generateTextMock).toHaveBeenCalledOnce();
|
|
165
|
+
expect(events).toContainEqual({
|
|
166
|
+
type: "tool_use",
|
|
167
|
+
id: "call-1",
|
|
168
|
+
name: "read_file",
|
|
169
|
+
input: { path: "package.json" },
|
|
170
|
+
});
|
|
171
|
+
expect(events).toContainEqual({
|
|
172
|
+
type: "tool_result",
|
|
173
|
+
tool_use_id: "call-1",
|
|
174
|
+
name: "read_file",
|
|
175
|
+
content: { content: "{}" },
|
|
176
|
+
is_error: false,
|
|
177
|
+
});
|
|
178
|
+
expect(events).toContainEqual({ type: "text", text: "done", accumulated: "done" });
|
|
179
|
+
expect(events).toContainEqual({ type: "done", text: "done" });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
59
183
|
describe("ChatSession context management", () => {
|
|
60
184
|
it("keeps the generalized evidence gate in the stable system prompt prefix", async () => {
|
|
61
185
|
const { ChatSession } = await import("../index.js");
|
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { ChatSession } from "../index.js";
|
|
4
|
-
import { config } from "../config.js";
|
|
5
|
-
|
|
6
|
-
const originalDeepSeekApiKey = process.env.DEEPSEEK_API_KEY;
|
|
7
|
-
const originalDeepCccApiKey = config.apiKey;
|
|
8
|
-
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
if (originalDeepSeekApiKey === undefined) {
|
|
11
|
-
delete process.env.DEEPSEEK_API_KEY;
|
|
12
|
-
} else {
|
|
13
|
-
process.env.DEEPSEEK_API_KEY = originalDeepSeekApiKey;
|
|
14
|
-
}
|
|
15
|
-
config.apiKey = originalDeepCccApiKey;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe("builtin ChatSession config", () => {
|
|
19
|
-
it("
|
|
20
|
-
expect(()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
expect(() =>
|
|
25
|
-
});
|
|
26
|
-
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { ChatSession } from "../index.js";
|
|
4
|
+
import { config, normalizeDeepCccProvider } from "../config.js";
|
|
5
|
+
|
|
6
|
+
const originalDeepSeekApiKey = process.env.DEEPSEEK_API_KEY;
|
|
7
|
+
const originalDeepCccApiKey = config.apiKey;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (originalDeepSeekApiKey === undefined) {
|
|
11
|
+
delete process.env.DEEPSEEK_API_KEY;
|
|
12
|
+
} else {
|
|
13
|
+
process.env.DEEPSEEK_API_KEY = originalDeepSeekApiKey;
|
|
14
|
+
}
|
|
15
|
+
config.apiKey = originalDeepCccApiKey;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("builtin ChatSession config", () => {
|
|
19
|
+
it("defaults provider selection to openai and accepts anthropic case-insensitively", () => {
|
|
20
|
+
expect(normalizeDeepCccProvider(undefined)).toBe("openai");
|
|
21
|
+
expect(normalizeDeepCccProvider("")).toBe("openai");
|
|
22
|
+
expect(normalizeDeepCccProvider("OPENAI")).toBe("openai");
|
|
23
|
+
expect(normalizeDeepCccProvider("Anthropic")).toBe("anthropic");
|
|
24
|
+
expect(() => normalizeDeepCccProvider("antropic")).toThrow(/openai.*anthropic/i);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("uses the builtin ~/.deepccc config when no apiKey is passed", () => {
|
|
28
|
+
expect(() => new ChatSession()).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("allows constructor parameters to override config defaults", () => {
|
|
32
|
+
expect(() => new ChatSession({ apiKey: "sk-test" })).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
});
|