chatccc 0.2.199 → 0.2.200

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.
Files changed (90) hide show
  1. package/README.md +1 -1
  2. package/agent-prompts/claude_specific.md +45 -45
  3. package/agent-prompts/codex_specific.md +2 -2
  4. package/agent-prompts/cursor_specific.md +13 -13
  5. package/bin/cccagent.mjs +17 -17
  6. package/config.sample.json +27 -27
  7. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  8. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  9. package/im-skills/feishu-skill/skill.md +3 -3
  10. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  11. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  12. package/im-skills/wechat-file-skill/skill.md +10 -10
  13. package/im-skills/wechat-image-skill/skill.md +10 -10
  14. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  15. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  16. package/im-skills/wechat-video-skill/skill.md +10 -10
  17. package/package.json +1 -1
  18. package/scripts/postinstall-sharp-check.mjs +58 -58
  19. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  20. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  21. package/src/__tests__/builtin-chat-session.test.ts +277 -277
  22. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  23. package/src/__tests__/builtin-config.test.ts +33 -33
  24. package/src/__tests__/builtin-context.test.ts +163 -163
  25. package/src/__tests__/builtin-file-tools.test.ts +224 -224
  26. package/src/__tests__/builtin-session-select.test.ts +116 -116
  27. package/src/__tests__/builtin-sigint.test.ts +56 -56
  28. package/src/__tests__/card-plain-text.test.ts +5 -5
  29. package/src/__tests__/cardkit.test.ts +60 -60
  30. package/src/__tests__/cards.test.ts +109 -109
  31. package/src/__tests__/ccc-adapter.test.ts +114 -114
  32. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  33. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  34. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  35. package/src/__tests__/claude-adapter.test.ts +592 -592
  36. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  37. package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
  38. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  39. package/src/__tests__/config-reload.test.ts +10 -10
  40. package/src/__tests__/config-sample.test.ts +18 -18
  41. package/src/__tests__/feishu-api.test.ts +60 -60
  42. package/src/__tests__/feishu-avatar.test.ts +87 -45
  43. package/src/__tests__/feishu-platform.test.ts +22 -22
  44. package/src/__tests__/format-message.test.ts +47 -47
  45. package/src/__tests__/jsonl-stream.test.ts +79 -79
  46. package/src/__tests__/orchestrator.test.ts +227 -202
  47. package/src/__tests__/privacy.test.ts +198 -198
  48. package/src/__tests__/raw-stream-log.test.ts +106 -106
  49. package/src/__tests__/session.test.ts +40 -40
  50. package/src/__tests__/shared-prefix.test.ts +36 -36
  51. package/src/__tests__/sim-platform.test.ts +12 -12
  52. package/src/__tests__/stream-state.test.ts +42 -42
  53. package/src/__tests__/web-ui.test.ts +209 -209
  54. package/src/adapters/ccc-adapter.ts +121 -121
  55. package/src/adapters/claude-adapter.ts +603 -603
  56. package/src/adapters/claude-session-meta-store.ts +120 -120
  57. package/src/adapters/codex-adapter.ts +380 -380
  58. package/src/adapters/jsonl-stream.ts +157 -157
  59. package/src/adapters/raw-stream-log.ts +124 -124
  60. package/src/adapters/resource-monitor.ts +140 -140
  61. package/src/agent-delegate-task-rpc.ts +153 -153
  62. package/src/agent-delegate-task.ts +81 -81
  63. package/src/agent-reload-config-rpc.ts +34 -34
  64. package/src/agent-stop-stuck.ts +129 -129
  65. package/src/builtin/cli.ts +473 -473
  66. package/src/builtin/context.ts +323 -323
  67. package/src/builtin/file-tools.ts +1072 -1072
  68. package/src/builtin/index.ts +404 -404
  69. package/src/builtin/session-select.ts +48 -48
  70. package/src/builtin/sigint.ts +50 -50
  71. package/src/cards.ts +195 -195
  72. package/src/chatgpt-subscription-rpc.ts +27 -27
  73. package/src/chatgpt-subscription.ts +299 -299
  74. package/src/chrome-devtools-guard.ts +318 -318
  75. package/src/codex-reset-actions.ts +184 -184
  76. package/src/config.ts +125 -125
  77. package/src/feishu-api.ts +86 -63
  78. package/src/feishu-platform.ts +20 -20
  79. package/src/format-message.ts +293 -293
  80. package/src/litellm-proxy.ts +374 -374
  81. package/src/orchestrator.ts +183 -183
  82. package/src/privacy.ts +118 -118
  83. package/src/runtime-reload.ts +34 -34
  84. package/src/session-chat-binding.ts +6 -6
  85. package/src/session-name.ts +8 -8
  86. package/src/session.ts +141 -141
  87. package/src/shared-prefix.ts +29 -29
  88. package/src/sim-platform.ts +20 -20
  89. package/src/turn-cards.ts +117 -117
  90. package/src/web-ui.ts +205 -205
@@ -1,18 +1,18 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import {
3
- buildProgressCard,
4
- buildHelpCard,
2
+ import {
3
+ buildProgressCard,
4
+ buildHelpCard,
5
5
  buildCdContent,
6
6
  buildCdCard,
7
7
  buildSessionsCard,
8
- buildStatusCard,
9
- buildCodexUsageCard,
10
- buildCodexResetConfirmCard,
11
- buildButtons,
12
- truncateContent,
13
- getToolEmoji,
14
- } from "../cards.ts";
15
- import { ABD_HELP_LINE } from "../shared-prefix.ts";
8
+ buildStatusCard,
9
+ buildCodexUsageCard,
10
+ buildCodexResetConfirmCard,
11
+ buildButtons,
12
+ truncateContent,
13
+ getToolEmoji,
14
+ } from "../cards.ts";
15
+ import { ABD_HELP_LINE } from "../shared-prefix.ts";
16
16
 
17
17
  // ---------------------------------------------------------------------------
18
18
  // truncateContent
@@ -154,32 +154,32 @@ describe("buildHelpCard", () => {
154
154
  expect(parsed.elements[0].text.content).toContain("你好");
155
155
  });
156
156
 
157
- it("includes action buttons", () => {
158
- const card = buildHelpCard("test");
159
- const parsed = JSON.parse(card);
160
- const action = parsed.elements[2];
161
- expect(action.tag).toBe("action");
162
- expect(action.actions).toHaveLength(7);
163
- });
164
-
165
- it("adds ABD prefix help as the final help line", () => {
166
- const card = buildHelpCard("test");
167
- const parsed = JSON.parse(card);
168
- const lines = parsed.elements[1].text.content.split("\n");
169
-
170
- expect(lines).toContain("发送 **/usage** 查看 Codex 5h/周用量,以及查询/使用主动重置卡");
171
- expect(lines.at(-1)).toBe(ABD_HELP_LINE);
172
- });
173
-
174
- it("does not advertise the hidden ccc agent", () => {
175
- const card = buildHelpCard("test");
176
- const parsed = JSON.parse(card);
177
- const text = JSON.stringify(parsed);
178
-
179
- expect(text).not.toContain("/new ccc");
180
- expect(text).not.toContain("CCC Agent");
181
- });
182
- });
157
+ it("includes action buttons", () => {
158
+ const card = buildHelpCard("test");
159
+ const parsed = JSON.parse(card);
160
+ const action = parsed.elements[2];
161
+ expect(action.tag).toBe("action");
162
+ expect(action.actions).toHaveLength(7);
163
+ });
164
+
165
+ it("adds ABD prefix help as the final help line", () => {
166
+ const card = buildHelpCard("test");
167
+ const parsed = JSON.parse(card);
168
+ const lines = parsed.elements[1].text.content.split("\n");
169
+
170
+ expect(lines).toContain("发送 **/usage** 查看 Codex 实际存在的 5h/7天用量窗口,以及查询/使用主动重置卡");
171
+ expect(lines.at(-1)).toBe(ABD_HELP_LINE);
172
+ });
173
+
174
+ it("does not advertise the hidden ccc agent", () => {
175
+ const card = buildHelpCard("test");
176
+ const parsed = JSON.parse(card);
177
+ const text = JSON.stringify(parsed);
178
+
179
+ expect(text).not.toContain("/new ccc");
180
+ expect(text).not.toContain("CCC Agent");
181
+ });
182
+ });
183
183
 
184
184
  // ---------------------------------------------------------------------------
185
185
  // buildCdContent
@@ -373,29 +373,29 @@ describe("buildSessionsCard", () => {
373
373
  expect(content).toContain("Cursor 会话");
374
374
  });
375
375
 
376
- it("omits Cursor section when no Cursor sessions", () => {
377
- const card = buildSessionsCard([
378
- { sessionId: "c1", chatName: "", chatId: "oc_c1", active: false, turnCount: 1, elapsedSeconds: null, model: "(留空)", tool: "claude" },
379
- ]);
380
- const parsed = JSON.parse(card);
381
- const content: string = parsed.elements[0].text.content;
382
- expect(content).toContain("Claude Code 会话");
383
- expect(content).not.toContain("Cursor 会话");
384
- });
385
-
386
- it("labels hidden ccc sessions without grouping them as Claude Code", () => {
387
- const card = buildSessionsCard([
388
- { sessionId: "session-20260702-121530-a1b2c3", chatName: "", chatId: "oc_ccc", active: false, turnCount: 1, elapsedSeconds: null, model: "deepseek-v4-pro", tool: "ccc" },
389
- ]);
390
- const parsed = JSON.parse(card);
391
- const content: string = parsed.elements[0].text.content;
392
-
393
- expect(content).toContain("CCC Agent 会话");
394
- expect(content).toContain("工具: CCC Agent");
395
- expect(content).not.toContain("Claude Code 会话");
396
- });
397
-
398
- it("displays chatName when provided", () => {
376
+ it("omits Cursor section when no Cursor sessions", () => {
377
+ const card = buildSessionsCard([
378
+ { sessionId: "c1", chatName: "", chatId: "oc_c1", active: false, turnCount: 1, elapsedSeconds: null, model: "(留空)", tool: "claude" },
379
+ ]);
380
+ const parsed = JSON.parse(card);
381
+ const content: string = parsed.elements[0].text.content;
382
+ expect(content).toContain("Claude Code 会话");
383
+ expect(content).not.toContain("Cursor 会话");
384
+ });
385
+
386
+ it("labels hidden ccc sessions without grouping them as Claude Code", () => {
387
+ const card = buildSessionsCard([
388
+ { sessionId: "session-20260702-121530-a1b2c3", chatName: "", chatId: "oc_ccc", active: false, turnCount: 1, elapsedSeconds: null, model: "deepseek-v4-pro", tool: "ccc" },
389
+ ]);
390
+ const parsed = JSON.parse(card);
391
+ const content: string = parsed.elements[0].text.content;
392
+
393
+ expect(content).toContain("CCC Agent 会话");
394
+ expect(content).toContain("工具: CCC Agent");
395
+ expect(content).not.toContain("Claude Code 会话");
396
+ });
397
+
398
+ it("displays chatName when provided", () => {
399
399
  const card = buildSessionsCard([
400
400
  { sessionId: "abc123", chatName: "帮我写代码-src", chatId: "oc_abc123", active: false, turnCount: 2, elapsedSeconds: null, model: "Claude Opus 4.7", tool: "claude" },
401
401
  ]);
@@ -446,7 +446,7 @@ describe("buildSessionsCard", () => {
446
446
  // buildStatusCard
447
447
  // ---------------------------------------------------------------------------
448
448
 
449
- describe("buildStatusCard", () => {
449
+ describe("buildStatusCard", () => {
450
450
  it("returns valid JSON with status text", () => {
451
451
  const card = buildStatusCard("一切正常");
452
452
  const parsed = JSON.parse(card);
@@ -466,54 +466,54 @@ describe("buildStatusCard", () => {
466
466
  const action = parsed.elements[2];
467
467
  expect(action.actions[0].text.content).toBe("收起");
468
468
  });
469
- });
470
-
471
- // ---------------------------------------------------------------------------
472
- // buildCodexUsageCard / buildCodexResetConfirmCard
473
- // ---------------------------------------------------------------------------
474
-
475
- describe("Codex usage reset cards", () => {
476
- it("shows the reset button only when reset credits are available", () => {
477
- const card = buildCodexUsageCard("Codex 用量", 2);
478
- const parsed = JSON.parse(card);
479
- const action = parsed.elements.find((element: any) => element.tag === "action");
480
- expect(action.actions[0].text.content).toBe("发起重置");
481
- expect(action.actions[0].value).toEqual({ action: "codex_reset_request", availableCount: 2 });
482
-
483
- const noCreditCard = buildCodexUsageCard("Codex 用量", 0);
484
- const noCreditParsed = JSON.parse(noCreditCard);
485
- expect(noCreditParsed.elements.some((element: any) => element.tag === "action")).toBe(false);
486
- });
487
-
488
- it("builds yes/no confirmation buttons tied to the parent usage card", () => {
489
- const card = buildCodexResetConfirmCard({
490
- availableCount: 2,
491
- parentMessageId: "usage-message",
492
- requestId: "request-1",
493
- });
494
- const parsed = JSON.parse(card);
495
- const action = parsed.elements.find((element: any) => element.tag === "action");
496
-
497
- expect(action.actions[0].text.content).toBe("是,发起重置");
498
- expect(action.actions[0].value).toEqual({
499
- action: "codex_reset_confirm",
500
- decision: "yes",
501
- parentMessageId: "usage-message",
502
- requestId: "request-1",
503
- });
504
- expect(action.actions[1].text.content).toBe("否");
505
- expect(action.actions[1].value).toEqual({
506
- action: "codex_reset_confirm",
507
- decision: "no",
508
- parentMessageId: "usage-message",
509
- requestId: "request-1",
510
- });
511
- });
512
- });
513
-
514
- // ---------------------------------------------------------------------------
515
- // buildButtons
516
- // ---------------------------------------------------------------------------
469
+ });
470
+
471
+ // ---------------------------------------------------------------------------
472
+ // buildCodexUsageCard / buildCodexResetConfirmCard
473
+ // ---------------------------------------------------------------------------
474
+
475
+ describe("Codex usage reset cards", () => {
476
+ it("shows the reset button only when reset credits are available", () => {
477
+ const card = buildCodexUsageCard("Codex 用量", 2);
478
+ const parsed = JSON.parse(card);
479
+ const action = parsed.elements.find((element: any) => element.tag === "action");
480
+ expect(action.actions[0].text.content).toBe("发起重置");
481
+ expect(action.actions[0].value).toEqual({ action: "codex_reset_request", availableCount: 2 });
482
+
483
+ const noCreditCard = buildCodexUsageCard("Codex 用量", 0);
484
+ const noCreditParsed = JSON.parse(noCreditCard);
485
+ expect(noCreditParsed.elements.some((element: any) => element.tag === "action")).toBe(false);
486
+ });
487
+
488
+ it("builds yes/no confirmation buttons tied to the parent usage card", () => {
489
+ const card = buildCodexResetConfirmCard({
490
+ availableCount: 2,
491
+ parentMessageId: "usage-message",
492
+ requestId: "request-1",
493
+ });
494
+ const parsed = JSON.parse(card);
495
+ const action = parsed.elements.find((element: any) => element.tag === "action");
496
+
497
+ expect(action.actions[0].text.content).toBe("是,发起重置");
498
+ expect(action.actions[0].value).toEqual({
499
+ action: "codex_reset_confirm",
500
+ decision: "yes",
501
+ parentMessageId: "usage-message",
502
+ requestId: "request-1",
503
+ });
504
+ expect(action.actions[1].text.content).toBe("否");
505
+ expect(action.actions[1].value).toEqual({
506
+ action: "codex_reset_confirm",
507
+ decision: "no",
508
+ parentMessageId: "usage-message",
509
+ requestId: "request-1",
510
+ });
511
+ });
512
+ });
513
+
514
+ // ---------------------------------------------------------------------------
515
+ // buildButtons
516
+ // ---------------------------------------------------------------------------
517
517
 
518
518
  describe("buildButtons", () => {
519
519
  it("returns action with buttons", () => {
@@ -1,114 +1,114 @@
1
- import { mkdtemp } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- import { afterEach, describe, expect, it, vi } from "vitest";
6
-
7
- const streamTextMock = vi.fn();
8
- const generateTextMock = vi.fn();
9
-
10
- vi.mock("@ai-sdk/openai-compatible", () => ({
11
- createOpenAICompatible: vi.fn(() => (modelId: string) => ({ modelId })),
12
- }));
13
-
14
- vi.mock("ai", () => ({
15
- streamText: streamTextMock,
16
- generateText: generateTextMock,
17
- isLoopFinished: vi.fn(() => ({ loopFinished: true })),
18
- stepCountIs: vi.fn((count: number) => ({ count })),
19
- jsonSchema: vi.fn((schema: unknown) => schema),
20
- tool: vi.fn((definition: unknown) => definition),
21
- }));
22
-
23
- async function* textStream(...chunks: string[]): AsyncIterable<string> {
24
- for (const chunk of chunks) yield chunk;
25
- }
26
-
27
- async function* fullStream(...parts: unknown[]): AsyncIterable<unknown> {
28
- for (const part of parts) yield part;
29
- }
30
-
31
- afterEach(() => {
32
- streamTextMock.mockReset();
33
- generateTextMock.mockReset();
34
- });
35
-
36
- describe("createCccAdapter", () => {
37
- it("creates a persisted ccc session and exposes model/cwd metadata", async () => {
38
- const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
39
- const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-meta-"));
40
- const adapter = createCccAdapter({
41
- apiKey: "sk-test",
42
- contextDir,
43
- model: "deepseek-v4-pro",
44
- });
45
-
46
- const created = await adapter.createSession("F:\\repo");
47
- const info = await adapter.getSessionInfo(created.sessionId);
48
-
49
- expect(created.sessionId).toMatch(/^session-\d{8}-\d{6}-[a-f0-9]{6}$/);
50
- expect(info).toEqual(expect.objectContaining({
51
- sessionId: created.sessionId,
52
- cwd: "F:\\repo",
53
- model: "deepseek-v4-pro",
54
- }));
55
- });
56
-
57
- it("maps ChatSession text chunks to unified assistant text blocks", async () => {
58
- const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
59
- const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-stream-"));
60
- const adapter = createCccAdapter({
61
- apiKey: "sk-test",
62
- contextDir,
63
- model: "deepseek-v4-flash",
64
- });
65
- const { sessionId } = await adapter.createSession("F:\\repo");
66
- streamTextMock.mockReturnValueOnce({ textStream: textStream("hello", " world") });
67
-
68
- const messages = [];
69
- for await (const message of adapter.prompt(sessionId, "hi", "F:\\repo")) {
70
- messages.push(message);
71
- }
72
-
73
- expect(messages).toEqual([
74
- { type: "assistant", blocks: [{ type: "text", text: "hello" }] },
75
- { type: "assistant", blocks: [{ type: "text", text: " world" }] },
76
- ]);
77
- expect(streamTextMock).toHaveBeenCalledWith(expect.objectContaining({
78
- model: { modelId: "deepseek-v4-flash" },
79
- }));
80
- });
81
-
82
- it("maps ChatSession tool events to unified tool blocks", async () => {
83
- const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
84
- const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-tools-"));
85
- const adapter = createCccAdapter({
86
- apiKey: "sk-test",
87
- contextDir,
88
- model: "deepseek-v4-flash",
89
- });
90
- const { sessionId } = await adapter.createSession("F:\\repo");
91
- streamTextMock.mockReturnValueOnce({
92
- fullStream: fullStream(
93
- { type: "tool-call", toolCallId: "call-1", toolName: "read_file", input: { path: "README.md" } },
94
- { type: "tool-result", toolCallId: "call-1", toolName: "read_file", output: { content: "hello" } },
95
- ),
96
- });
97
-
98
- const messages = [];
99
- for await (const message of adapter.prompt(sessionId, "read", "F:\\repo")) {
100
- messages.push(message);
101
- }
102
-
103
- expect(messages).toEqual([
104
- {
105
- type: "assistant",
106
- blocks: [{ type: "tool_use", id: "call-1", name: "read_file", input: { path: "README.md" } }],
107
- },
108
- {
109
- type: "assistant",
110
- blocks: [{ type: "tool_result", tool_use_id: "call-1", content: { content: "hello" }, is_error: false }],
111
- },
112
- ]);
113
- });
114
- });
1
+ import { mkdtemp } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+
7
+ const streamTextMock = vi.fn();
8
+ const generateTextMock = vi.fn();
9
+
10
+ vi.mock("@ai-sdk/openai-compatible", () => ({
11
+ createOpenAICompatible: vi.fn(() => (modelId: string) => ({ modelId })),
12
+ }));
13
+
14
+ vi.mock("ai", () => ({
15
+ streamText: streamTextMock,
16
+ generateText: generateTextMock,
17
+ isLoopFinished: vi.fn(() => ({ loopFinished: true })),
18
+ stepCountIs: vi.fn((count: number) => ({ count })),
19
+ jsonSchema: vi.fn((schema: unknown) => schema),
20
+ tool: vi.fn((definition: unknown) => definition),
21
+ }));
22
+
23
+ async function* textStream(...chunks: string[]): AsyncIterable<string> {
24
+ for (const chunk of chunks) yield chunk;
25
+ }
26
+
27
+ async function* fullStream(...parts: unknown[]): AsyncIterable<unknown> {
28
+ for (const part of parts) yield part;
29
+ }
30
+
31
+ afterEach(() => {
32
+ streamTextMock.mockReset();
33
+ generateTextMock.mockReset();
34
+ });
35
+
36
+ describe("createCccAdapter", () => {
37
+ it("creates a persisted ccc session and exposes model/cwd metadata", async () => {
38
+ const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
39
+ const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-meta-"));
40
+ const adapter = createCccAdapter({
41
+ apiKey: "sk-test",
42
+ contextDir,
43
+ model: "deepseek-v4-pro",
44
+ });
45
+
46
+ const created = await adapter.createSession("F:\\repo");
47
+ const info = await adapter.getSessionInfo(created.sessionId);
48
+
49
+ expect(created.sessionId).toMatch(/^session-\d{8}-\d{6}-[a-f0-9]{6}$/);
50
+ expect(info).toEqual(expect.objectContaining({
51
+ sessionId: created.sessionId,
52
+ cwd: "F:\\repo",
53
+ model: "deepseek-v4-pro",
54
+ }));
55
+ });
56
+
57
+ it("maps ChatSession text chunks to unified assistant text blocks", async () => {
58
+ const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
59
+ const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-stream-"));
60
+ const adapter = createCccAdapter({
61
+ apiKey: "sk-test",
62
+ contextDir,
63
+ model: "deepseek-v4-flash",
64
+ });
65
+ const { sessionId } = await adapter.createSession("F:\\repo");
66
+ streamTextMock.mockReturnValueOnce({ textStream: textStream("hello", " world") });
67
+
68
+ const messages = [];
69
+ for await (const message of adapter.prompt(sessionId, "hi", "F:\\repo")) {
70
+ messages.push(message);
71
+ }
72
+
73
+ expect(messages).toEqual([
74
+ { type: "assistant", blocks: [{ type: "text", text: "hello" }] },
75
+ { type: "assistant", blocks: [{ type: "text", text: " world" }] },
76
+ ]);
77
+ expect(streamTextMock).toHaveBeenCalledWith(expect.objectContaining({
78
+ model: { modelId: "deepseek-v4-flash" },
79
+ }));
80
+ });
81
+
82
+ it("maps ChatSession tool events to unified tool blocks", async () => {
83
+ const { createCccAdapter } = await import("../adapters/ccc-adapter.ts");
84
+ const contextDir = await mkdtemp(join(tmpdir(), "chatccc-ccc-adapter-tools-"));
85
+ const adapter = createCccAdapter({
86
+ apiKey: "sk-test",
87
+ contextDir,
88
+ model: "deepseek-v4-flash",
89
+ });
90
+ const { sessionId } = await adapter.createSession("F:\\repo");
91
+ streamTextMock.mockReturnValueOnce({
92
+ fullStream: fullStream(
93
+ { type: "tool-call", toolCallId: "call-1", toolName: "read_file", input: { path: "README.md" } },
94
+ { type: "tool-result", toolCallId: "call-1", toolName: "read_file", output: { content: "hello" } },
95
+ ),
96
+ });
97
+
98
+ const messages = [];
99
+ for await (const message of adapter.prompt(sessionId, "read", "F:\\repo")) {
100
+ messages.push(message);
101
+ }
102
+
103
+ expect(messages).toEqual([
104
+ {
105
+ type: "assistant",
106
+ blocks: [{ type: "tool_use", id: "call-1", name: "read_file", input: { path: "README.md" } }],
107
+ },
108
+ {
109
+ type: "assistant",
110
+ blocks: [{ type: "tool_result", tool_use_id: "call-1", content: { content: "hello" }, is_error: false }],
111
+ },
112
+ ]);
113
+ });
114
+ });
@@ -1,89 +1,89 @@
1
- import { Readable } from "node:stream";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
-
4
- const mockGetChatGptSubscriptionStatus = vi.hoisted(() => vi.fn());
5
-
6
- vi.mock("../chatgpt-subscription.ts", () => ({
7
- getChatGptSubscriptionStatus: mockGetChatGptSubscriptionStatus,
8
- }));
9
-
10
- import {
11
- CHATGPT_SUBSCRIPTION_PATH,
12
- handleChatGptSubscriptionRequest,
13
- } from "../chatgpt-subscription-rpc.ts";
14
-
15
- function request(path = CHATGPT_SUBSCRIPTION_PATH, method = "POST"): Readable & {
16
- url?: string;
17
- method?: string;
18
- headers: Record<string, string>;
19
- } {
20
- const req = Readable.from([]) as Readable & {
21
- url?: string;
22
- method?: string;
23
- headers: Record<string, string>;
24
- };
25
- req.url = path;
26
- req.method = method;
27
- req.headers = {};
28
- return req;
29
- }
30
-
31
- function response() {
32
- const res = {
33
- statusCode: 0,
34
- headers: {} as Record<string, string>,
35
- body: "",
36
- writeHead(status: number, headers: Record<string, string>) {
37
- this.statusCode = status;
38
- this.headers = headers;
39
- return this;
40
- },
41
- end(chunk?: string) {
42
- this.body += chunk ?? "";
43
- return this;
44
- },
45
- };
46
- return res;
47
- }
48
-
49
- describe("ChatGPT subscription RPC", () => {
50
- beforeEach(() => {
51
- mockGetChatGptSubscriptionStatus.mockReset();
52
- mockGetChatGptSubscriptionStatus.mockResolvedValue({
53
- ok: false,
54
- code: "chrome_cdp_disabled",
55
- reason: "Chrome CDP guard is disabled in ChatCCC config.",
56
- chromeCdp: { enabled: false, port: 15166, status: "skipped" },
57
- });
58
- });
59
-
60
- it("returns the structured subscription lookup JSON", async () => {
61
- const req = request();
62
- const res = response();
63
-
64
- await expect(handleChatGptSubscriptionRequest(req as never, res as never)).resolves.toBe(true);
65
-
66
- expect(res.statusCode).toBe(200);
67
- expect(JSON.parse(res.body)).toEqual({
68
- ok: false,
69
- code: "chrome_cdp_disabled",
70
- reason: "Chrome CDP guard is disabled in ChatCCC config.",
71
- chromeCdp: { enabled: false, port: 15166, status: "skipped" },
72
- });
73
- });
74
-
75
- it("does not handle other paths", async () => {
76
- const res = response();
77
-
78
- await expect(handleChatGptSubscriptionRequest(request("/api/other") as never, res as never)).resolves.toBe(false);
79
- expect(res.body).toBe("");
80
- });
81
-
82
- it("rejects non-POST methods", async () => {
83
- const res = response();
84
-
85
- await expect(handleChatGptSubscriptionRequest(request(CHATGPT_SUBSCRIPTION_PATH, "GET") as never, res as never)).resolves.toBe(true);
86
- expect(res.statusCode).toBe(405);
87
- expect(JSON.parse(res.body)).toMatchObject({ ok: false, code: "method_not_allowed" });
88
- });
89
- });
1
+ import { Readable } from "node:stream";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const mockGetChatGptSubscriptionStatus = vi.hoisted(() => vi.fn());
5
+
6
+ vi.mock("../chatgpt-subscription.ts", () => ({
7
+ getChatGptSubscriptionStatus: mockGetChatGptSubscriptionStatus,
8
+ }));
9
+
10
+ import {
11
+ CHATGPT_SUBSCRIPTION_PATH,
12
+ handleChatGptSubscriptionRequest,
13
+ } from "../chatgpt-subscription-rpc.ts";
14
+
15
+ function request(path = CHATGPT_SUBSCRIPTION_PATH, method = "POST"): Readable & {
16
+ url?: string;
17
+ method?: string;
18
+ headers: Record<string, string>;
19
+ } {
20
+ const req = Readable.from([]) as Readable & {
21
+ url?: string;
22
+ method?: string;
23
+ headers: Record<string, string>;
24
+ };
25
+ req.url = path;
26
+ req.method = method;
27
+ req.headers = {};
28
+ return req;
29
+ }
30
+
31
+ function response() {
32
+ const res = {
33
+ statusCode: 0,
34
+ headers: {} as Record<string, string>,
35
+ body: "",
36
+ writeHead(status: number, headers: Record<string, string>) {
37
+ this.statusCode = status;
38
+ this.headers = headers;
39
+ return this;
40
+ },
41
+ end(chunk?: string) {
42
+ this.body += chunk ?? "";
43
+ return this;
44
+ },
45
+ };
46
+ return res;
47
+ }
48
+
49
+ describe("ChatGPT subscription RPC", () => {
50
+ beforeEach(() => {
51
+ mockGetChatGptSubscriptionStatus.mockReset();
52
+ mockGetChatGptSubscriptionStatus.mockResolvedValue({
53
+ ok: false,
54
+ code: "chrome_cdp_disabled",
55
+ reason: "Chrome CDP guard is disabled in ChatCCC config.",
56
+ chromeCdp: { enabled: false, port: 15166, status: "skipped" },
57
+ });
58
+ });
59
+
60
+ it("returns the structured subscription lookup JSON", async () => {
61
+ const req = request();
62
+ const res = response();
63
+
64
+ await expect(handleChatGptSubscriptionRequest(req as never, res as never)).resolves.toBe(true);
65
+
66
+ expect(res.statusCode).toBe(200);
67
+ expect(JSON.parse(res.body)).toEqual({
68
+ ok: false,
69
+ code: "chrome_cdp_disabled",
70
+ reason: "Chrome CDP guard is disabled in ChatCCC config.",
71
+ chromeCdp: { enabled: false, port: 15166, status: "skipped" },
72
+ });
73
+ });
74
+
75
+ it("does not handle other paths", async () => {
76
+ const res = response();
77
+
78
+ await expect(handleChatGptSubscriptionRequest(request("/api/other") as never, res as never)).resolves.toBe(false);
79
+ expect(res.body).toBe("");
80
+ });
81
+
82
+ it("rejects non-POST methods", async () => {
83
+ const res = response();
84
+
85
+ await expect(handleChatGptSubscriptionRequest(request(CHATGPT_SUBSCRIPTION_PATH, "GET") as never, res as never)).resolves.toBe(true);
86
+ expect(res.statusCode).toBe(405);
87
+ expect(JSON.parse(res.body)).toMatchObject({ ok: false, code: "method_not_allowed" });
88
+ });
89
+ });