chatccc 0.2.51 → 0.2.53
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 +103 -217
- package/bin/chatccc.mjs +23 -23
- package/config.sample.json +34 -30
- package/demo/ilink_echo_probe.ts +222 -222
- package/im-skills/feishu-skill/download-video.mjs +162 -162
- package/im-skills/feishu-skill/receive-send-file.md +66 -66
- package/im-skills/feishu-skill/receive-send-image.md +27 -27
- package/im-skills/feishu-skill/send-file.mjs +50 -50
- package/im-skills/feishu-skill/send-image.mjs +50 -50
- package/im-skills/feishu-skill/skill.md +10 -10
- package/package.json +59 -59
- package/src/__tests__/agent-image-rpc.test.ts +33 -33
- package/src/__tests__/agent-rpc-body.test.ts +41 -41
- package/src/__tests__/card-plain-text.test.ts +42 -0
- package/src/__tests__/claude-adapter.test.ts +54 -1
- package/src/__tests__/codex-adapter.test.ts +304 -304
- package/src/__tests__/config-reload.test.ts +58 -41
- package/src/__tests__/config-sample.test.ts +19 -0
- package/src/__tests__/crash-logging.test.ts +62 -62
- package/src/__tests__/fixtures/codex_simple_text.jsonl +4 -4
- package/src/__tests__/fixtures/codex_with_tool.jsonl +6 -6
- package/src/__tests__/im-skills.test.ts +46 -46
- package/src/__tests__/session.test.ts +95 -2
- package/src/__tests__/sim-agent.test.ts +173 -173
- package/src/__tests__/sim-platform.test.ts +76 -76
- package/src/__tests__/sim-store.test.ts +213 -213
- package/src/__tests__/stream-state.test.ts +134 -134
- package/src/__tests__/wechat-platform.test.ts +57 -0
- package/src/adapters/claude-adapter.ts +23 -2
- package/src/adapters/codex-adapter.ts +294 -294
- package/src/adapters/codex-session-meta-store.ts +130 -130
- package/src/agent-file-rpc.ts +156 -156
- package/src/agent-image-rpc.ts +152 -152
- package/src/agent-rpc-body.ts +48 -48
- package/src/card-plain-text.ts +108 -0
- package/src/cards.ts +4 -4
- package/src/config.ts +775 -742
- package/src/feishu-api.ts +6 -0
- package/src/im-skills.ts +109 -109
- package/src/index.ts +155 -788
- package/src/orchestrator.ts +1032 -0
- package/src/platform-adapter.ts +61 -0
- package/src/session-chat-binding.ts +7 -0
- package/src/session.ts +278 -113
- package/src/sim-agent.ts +167 -156
- package/src/trace.ts +50 -50
- package/src/web-ui.ts +1891 -1718
- package/src/wechat-platform.ts +517 -0
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { basename } from "node:path";
|
|
3
|
-
|
|
4
|
-
function parseArgs(argv) {
|
|
5
|
-
const result = {};
|
|
6
|
-
for (let i = 0; i < argv.length; i++) {
|
|
7
|
-
const key = argv[i];
|
|
8
|
-
if (!key.startsWith("--")) continue;
|
|
9
|
-
const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
|
|
10
|
-
result[key.slice(2)] = value;
|
|
11
|
-
}
|
|
12
|
-
return result;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function usage() {
|
|
16
|
-
console.error(`Usage:
|
|
17
|
-
node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function main() {
|
|
21
|
-
const args = parseArgs(process.argv.slice(2));
|
|
22
|
-
const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
|
|
23
|
-
const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
|
|
24
|
-
const path = args.path;
|
|
25
|
-
const caption = args.caption || "";
|
|
26
|
-
|
|
27
|
-
if (!url || !sessionId || !path) {
|
|
28
|
-
usage();
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
|
|
33
|
-
const response = await fetch(url, {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: {
|
|
36
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
37
|
-
},
|
|
38
|
-
body,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const text = await response.text();
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
44
|
-
}
|
|
45
|
-
console.log(text);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
main().catch((err) => {
|
|
49
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
50
|
-
process.exit(1);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
|
|
4
|
+
function parseArgs(argv) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (let i = 0; i < argv.length; i++) {
|
|
7
|
+
const key = argv[i];
|
|
8
|
+
if (!key.startsWith("--")) continue;
|
|
9
|
+
const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
|
|
10
|
+
result[key.slice(2)] = value;
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function usage() {
|
|
16
|
+
console.error(`Usage:
|
|
17
|
+
node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = parseArgs(process.argv.slice(2));
|
|
22
|
+
const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
|
|
23
|
+
const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
|
|
24
|
+
const path = args.path;
|
|
25
|
+
const caption = args.caption || "";
|
|
26
|
+
|
|
27
|
+
if (!url || !sessionId || !path) {
|
|
28
|
+
usage();
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
37
|
+
},
|
|
38
|
+
body,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
44
|
+
}
|
|
45
|
+
console.log(text);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main().catch((err) => {
|
|
49
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
50
|
+
process.exit(1);
|
|
51
51
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: feishu-skill
|
|
3
|
-
description: Feishu IM local skills for sending and receiving images, files, and videos.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Current working directory: {{cwd}}
|
|
7
|
-
|
|
8
|
-
Use local endpoints instead of calling Feishu Open Platform directly.
|
|
9
|
-
|
|
10
|
-
- **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
|
|
1
|
+
---
|
|
2
|
+
name: feishu-skill
|
|
3
|
+
description: Feishu IM local skills for sending and receiving images, files, and videos.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Current working directory: {{cwd}}
|
|
7
|
+
|
|
8
|
+
Use local endpoints instead of calling Feishu Open Platform directly.
|
|
9
|
+
|
|
10
|
+
- **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
|
|
11
11
|
- **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
|
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chatccc",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Feishu bot bridge for Claude Code",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./src/index.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"chatccc": "bin/chatccc.mjs"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"src/",
|
|
13
|
-
"bin/",
|
|
14
|
-
"demo/ilink_echo_probe.ts",
|
|
15
|
-
"im-skills/",
|
|
16
|
-
"images/img_readme_*.jpg",
|
|
17
|
-
"images/img_readme_*.png",
|
|
18
|
-
"images/avatars/status_*.png",
|
|
19
|
-
"images/avatars/badges/",
|
|
20
|
-
"package.json",
|
|
21
|
-
"README.md",
|
|
22
|
-
"config.sample.json"
|
|
23
|
-
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"dev": "tsx src/index.ts",
|
|
26
|
-
"chatccc": "tsx src/index.ts",
|
|
27
|
-
"start": "tsx src/index.ts",
|
|
28
|
-
"demo:bot-test": "tsx demo/bot_test.ts",
|
|
29
|
-
"demo:bot-test:local": "tsx demo/bot_test.ts --local",
|
|
30
|
-
"demo:create-group": "tsx src/index.ts",
|
|
31
|
-
"demo:create-group:local": "tsx src/index.ts --local",
|
|
32
|
-
"demo:permission-check": "tsx demo/permission_check.ts",
|
|
33
|
-
"demo:claude-hi": "tsx demo/claude_say_hi.ts",
|
|
34
|
-
"demo:codex-hi": "tsx demo/codex_say_hi.ts",
|
|
35
|
-
"demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
|
|
36
|
-
"test": "vitest run",
|
|
37
|
-
"test:watch": "vitest"
|
|
38
|
-
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.133",
|
|
41
|
-
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
42
|
-
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
43
|
-
"nodemailer": "^8.0.7",
|
|
44
|
-
"qrcode-terminal": "^0.12.0",
|
|
45
|
-
"sharp": "^0.34.5",
|
|
46
|
-
"tsx": "^4.0.0",
|
|
47
|
-
"ws": "^8.18.0"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@types/node": "^20.0.0",
|
|
51
|
-
"@types/qrcode-terminal": "^0.12.2",
|
|
52
|
-
"@types/ws": "^8.18.1",
|
|
53
|
-
"typescript": "^5.0.0",
|
|
54
|
-
"vitest": "^4.1.5"
|
|
55
|
-
},
|
|
56
|
-
"engines": {
|
|
57
|
-
"node": ">=20"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "chatccc",
|
|
3
|
+
"version": "0.2.53",
|
|
4
|
+
"description": "Feishu bot bridge for Claude Code",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"chatccc": "bin/chatccc.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"bin/",
|
|
14
|
+
"demo/ilink_echo_probe.ts",
|
|
15
|
+
"im-skills/",
|
|
16
|
+
"images/img_readme_*.jpg",
|
|
17
|
+
"images/img_readme_*.png",
|
|
18
|
+
"images/avatars/status_*.png",
|
|
19
|
+
"images/avatars/badges/",
|
|
20
|
+
"package.json",
|
|
21
|
+
"README.md",
|
|
22
|
+
"config.sample.json"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "tsx src/index.ts",
|
|
26
|
+
"chatccc": "tsx src/index.ts",
|
|
27
|
+
"start": "tsx src/index.ts",
|
|
28
|
+
"demo:bot-test": "tsx demo/bot_test.ts",
|
|
29
|
+
"demo:bot-test:local": "tsx demo/bot_test.ts --local",
|
|
30
|
+
"demo:create-group": "tsx src/index.ts",
|
|
31
|
+
"demo:create-group:local": "tsx src/index.ts --local",
|
|
32
|
+
"demo:permission-check": "tsx demo/permission_check.ts",
|
|
33
|
+
"demo:claude-hi": "tsx demo/claude_say_hi.ts",
|
|
34
|
+
"demo:codex-hi": "tsx demo/codex_say_hi.ts",
|
|
35
|
+
"demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.133",
|
|
41
|
+
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
42
|
+
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
43
|
+
"nodemailer": "^8.0.7",
|
|
44
|
+
"qrcode-terminal": "^0.12.0",
|
|
45
|
+
"sharp": "^0.34.5",
|
|
46
|
+
"tsx": "^4.0.0",
|
|
47
|
+
"ws": "^8.18.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^20.0.0",
|
|
51
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
52
|
+
"@types/ws": "^8.18.1",
|
|
53
|
+
"typescript": "^5.0.0",
|
|
54
|
+
"vitest": "^4.1.5"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
AGENT_SEND_IMAGE_PATH,
|
|
5
|
-
buildAgentImageCapabilityPrompt,
|
|
6
|
-
} from "../agent-image-rpc.ts";
|
|
7
|
-
|
|
8
|
-
describe("agent image RPC", () => {
|
|
9
|
-
it("builds prompt instructions with session_id", () => {
|
|
10
|
-
const prompt = buildAgentImageCapabilityPrompt({
|
|
11
|
-
url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
|
|
12
|
-
sessionId: "sid-test",
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/send-image");
|
|
16
|
-
expect(prompt).toContain('"session_id":"sid-test"');
|
|
17
|
-
expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
|
|
18
|
-
expect(prompt).toContain("UTF-8 encoded JSON bytes");
|
|
19
|
-
expect(prompt).toContain('"path"');
|
|
20
|
-
expect(prompt).not.toContain("Authorization: Bearer");
|
|
21
|
-
expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_URL");
|
|
22
|
-
expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_TOKEN");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("builds prompt with cwd hint", () => {
|
|
26
|
-
const prompt = buildAgentImageCapabilityPrompt({
|
|
27
|
-
url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
|
|
28
|
-
sessionId: "sid-1",
|
|
29
|
-
cwd: "F:/repo",
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
expect(prompt).toContain("Current working directory: F:/repo");
|
|
33
|
-
});
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AGENT_SEND_IMAGE_PATH,
|
|
5
|
+
buildAgentImageCapabilityPrompt,
|
|
6
|
+
} from "../agent-image-rpc.ts";
|
|
7
|
+
|
|
8
|
+
describe("agent image RPC", () => {
|
|
9
|
+
it("builds prompt instructions with session_id", () => {
|
|
10
|
+
const prompt = buildAgentImageCapabilityPrompt({
|
|
11
|
+
url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
|
|
12
|
+
sessionId: "sid-test",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/send-image");
|
|
16
|
+
expect(prompt).toContain('"session_id":"sid-test"');
|
|
17
|
+
expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
|
|
18
|
+
expect(prompt).toContain("UTF-8 encoded JSON bytes");
|
|
19
|
+
expect(prompt).toContain('"path"');
|
|
20
|
+
expect(prompt).not.toContain("Authorization: Bearer");
|
|
21
|
+
expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_URL");
|
|
22
|
+
expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_TOKEN");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("builds prompt with cwd hint", () => {
|
|
26
|
+
const prompt = buildAgentImageCapabilityPrompt({
|
|
27
|
+
url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
|
|
28
|
+
sessionId: "sid-1",
|
|
29
|
+
cwd: "F:/repo",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(prompt).toContain("Current working directory: F:/repo");
|
|
33
|
+
});
|
|
34
34
|
});
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import type { IncomingMessage } from "node:http";
|
|
2
|
-
import { Readable } from "node:stream";
|
|
3
|
-
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
|
|
6
|
-
import { getRequestCharset, readUtf8JsonBody } from "../agent-rpc-body.ts";
|
|
7
|
-
|
|
8
|
-
function requestFrom(body: Buffer, contentType?: string): IncomingMessage {
|
|
9
|
-
const req = Readable.from([body]) as IncomingMessage;
|
|
10
|
-
req.headers = {};
|
|
11
|
-
if (contentType) req.headers["content-type"] = contentType;
|
|
12
|
-
return req;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe("agent RPC body parsing", () => {
|
|
16
|
-
it("defaults JSON requests to UTF-8 and preserves Unicode captions", async () => {
|
|
17
|
-
const caption = "\u4e2d\u6587 caption";
|
|
18
|
-
const body = Buffer.from(JSON.stringify({ caption }), "utf8");
|
|
19
|
-
const req = requestFrom(body, "application/json");
|
|
20
|
-
|
|
21
|
-
await expect(readUtf8JsonBody(req, 1024)).resolves.toEqual({ caption });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("reads charset from content-type", () => {
|
|
25
|
-
const req = requestFrom(Buffer.from("{}"), 'application/json; charset="utf-8"');
|
|
26
|
-
|
|
27
|
-
expect(getRequestCharset(req)).toBe("utf-8");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("rejects non UTF-8 charsets", async () => {
|
|
31
|
-
const req = requestFrom(Buffer.from("{}"), "application/json; charset=gbk");
|
|
32
|
-
|
|
33
|
-
await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("Unsupported charset");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("rejects invalid UTF-8 bytes", async () => {
|
|
37
|
-
const req = requestFrom(Buffer.from([0xff, 0xfe]), "application/json; charset=utf-8");
|
|
38
|
-
|
|
39
|
-
await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("valid UTF-8");
|
|
40
|
-
});
|
|
41
|
-
});
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { getRequestCharset, readUtf8JsonBody } from "../agent-rpc-body.ts";
|
|
7
|
+
|
|
8
|
+
function requestFrom(body: Buffer, contentType?: string): IncomingMessage {
|
|
9
|
+
const req = Readable.from([body]) as IncomingMessage;
|
|
10
|
+
req.headers = {};
|
|
11
|
+
if (contentType) req.headers["content-type"] = contentType;
|
|
12
|
+
return req;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("agent RPC body parsing", () => {
|
|
16
|
+
it("defaults JSON requests to UTF-8 and preserves Unicode captions", async () => {
|
|
17
|
+
const caption = "\u4e2d\u6587 caption";
|
|
18
|
+
const body = Buffer.from(JSON.stringify({ caption }), "utf8");
|
|
19
|
+
const req = requestFrom(body, "application/json");
|
|
20
|
+
|
|
21
|
+
await expect(readUtf8JsonBody(req, 1024)).resolves.toEqual({ caption });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("reads charset from content-type", () => {
|
|
25
|
+
const req = requestFrom(Buffer.from("{}"), 'application/json; charset="utf-8"');
|
|
26
|
+
|
|
27
|
+
expect(getRequestCharset(req)).toBe("utf-8");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("rejects non UTF-8 charsets", async () => {
|
|
31
|
+
const req = requestFrom(Buffer.from("{}"), "application/json; charset=gbk");
|
|
32
|
+
|
|
33
|
+
await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("Unsupported charset");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("rejects invalid UTF-8 bytes", async () => {
|
|
37
|
+
const req = requestFrom(Buffer.from([0xff, 0xfe]), "application/json; charset=utf-8");
|
|
38
|
+
|
|
39
|
+
await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("valid UTF-8");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { cardJsonToPlainText } from "../card-plain-text.ts";
|
|
4
|
+
import {
|
|
5
|
+
buildHelpCard,
|
|
6
|
+
buildProgressCard,
|
|
7
|
+
buildStatusCard,
|
|
8
|
+
} from "../cards.ts";
|
|
9
|
+
|
|
10
|
+
describe("cardJsonToPlainText", () => {
|
|
11
|
+
it("converts help cards to readable text with commands", () => {
|
|
12
|
+
const text = cardJsonToPlainText(buildHelpCard("hello"));
|
|
13
|
+
|
|
14
|
+
expect(text).toContain("# ChatCCC");
|
|
15
|
+
expect(text).toContain("hello");
|
|
16
|
+
expect(text).toContain("/new");
|
|
17
|
+
expect(text).toContain("/new cursor");
|
|
18
|
+
expect(text).toContain("/new codex");
|
|
19
|
+
expect(text).toContain("/restart");
|
|
20
|
+
expect(text).toContain("/cd");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("converts status cards from v1 card format", () => {
|
|
24
|
+
const text = cardJsonToPlainText(buildStatusCard("status body", "green"));
|
|
25
|
+
|
|
26
|
+
expect(text).toContain("# 会话状态");
|
|
27
|
+
expect(text).toContain("status body");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("converts schema 2.0 progress cards", () => {
|
|
31
|
+
const text = cardJsonToPlainText(buildProgressCard("stream body"));
|
|
32
|
+
|
|
33
|
+
expect(text).toContain("# 生成中...");
|
|
34
|
+
expect(text).toContain("stream body");
|
|
35
|
+
expect(text).toContain("/status");
|
|
36
|
+
expect(text).toContain("/stop");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns null for invalid card json", () => {
|
|
40
|
+
expect(cardJsonToPlainText("{bad json")).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -685,6 +685,14 @@ describe("createClaudeAdapter — env 注入", () => {
|
|
|
685
685
|
// 确保每个用例从干净的 process.env 起步
|
|
686
686
|
delete process.env.ANTHROPIC_API_KEY;
|
|
687
687
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
688
|
+
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
689
|
+
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
690
|
+
delete process.env.ANTHROPIC_MODEL;
|
|
691
|
+
delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
692
|
+
delete process.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
693
|
+
delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
694
|
+
delete process.env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
695
|
+
delete process.env.CLAUDE_CODE_EFFORT_LEVEL;
|
|
688
696
|
});
|
|
689
697
|
|
|
690
698
|
// 用例之间清掉我们写入的 env,避免互相污染
|
|
@@ -780,6 +788,51 @@ describe("createClaudeAdapter — env 注入", () => {
|
|
|
780
788
|
expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://gateway.example/anthropic");
|
|
781
789
|
});
|
|
782
790
|
|
|
791
|
+
it("ChatCCC API config is isolated from Claude settings auth/model env", async () => {
|
|
792
|
+
setupMockCreateSession();
|
|
793
|
+
process.env.ANTHROPIC_AUTH_TOKEN = "token-from-claude-settings";
|
|
794
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth-from-claude-settings";
|
|
795
|
+
process.env.ANTHROPIC_MODEL = "model-from-claude-settings";
|
|
796
|
+
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = "sonnet-from-claude-settings";
|
|
797
|
+
process.env.CLAUDE_CODE_SUBAGENT_MODEL = "subagent-from-claude-settings";
|
|
798
|
+
process.env.CLAUDE_CODE_EFFORT_LEVEL = "max";
|
|
799
|
+
const adapter = createClaudeAdapter({
|
|
800
|
+
model: "chatccc-model",
|
|
801
|
+
effort: "high",
|
|
802
|
+
isEmpty: (v) => v.trim() === "",
|
|
803
|
+
apiKey: "sk-chatccc",
|
|
804
|
+
baseUrl: "https://chatccc-gateway.example/anthropic",
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
await adapter.createSession("/cwd");
|
|
808
|
+
|
|
809
|
+
const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
|
|
810
|
+
expect(opts.env.ANTHROPIC_API_KEY).toBe("sk-chatccc");
|
|
811
|
+
expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://chatccc-gateway.example/anthropic");
|
|
812
|
+
expect(opts.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
|
|
813
|
+
expect(opts.env.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
|
|
814
|
+
expect(opts.env.ANTHROPIC_MODEL).toBeUndefined();
|
|
815
|
+
expect(opts.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBeUndefined();
|
|
816
|
+
expect(opts.env.CLAUDE_CODE_SUBAGENT_MODEL).toBeUndefined();
|
|
817
|
+
expect(opts.env.CLAUDE_CODE_EFFORT_LEVEL).toBeUndefined();
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("第三方 API 配置时仍然加载 CLAUDE.md(settingSources 始终为 project+local)", async () => {
|
|
821
|
+
setupMockCreateSession();
|
|
822
|
+
const adapter = createClaudeAdapter({
|
|
823
|
+
model: "",
|
|
824
|
+
effort: "",
|
|
825
|
+
isEmpty: (v) => v.trim() === "",
|
|
826
|
+
apiKey: "sk-chatccc",
|
|
827
|
+
baseUrl: "https://chatccc-gateway.example/anthropic",
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
await adapter.createSession("/cwd");
|
|
831
|
+
|
|
832
|
+
const opts = sdk.unstable_v2_createSession.mock.calls[0][0];
|
|
833
|
+
expect(opts.settingSources).toEqual(["project", "local"]);
|
|
834
|
+
});
|
|
835
|
+
|
|
783
836
|
it("不修改主进程 process.env(永不污染)", async () => {
|
|
784
837
|
setupMockCreateSession();
|
|
785
838
|
const before = { ...process.env };
|
|
@@ -817,4 +870,4 @@ describe("createClaudeAdapter — env 注入", () => {
|
|
|
817
870
|
expect(opts.env.ANTHROPIC_API_KEY).toBe("from-system-env");
|
|
818
871
|
expect(opts.env.ANTHROPIC_BASE_URL).toBe("https://override.example/anthropic");
|
|
819
872
|
});
|
|
820
|
-
});
|
|
873
|
+
});
|