chatccc 0.2.239 → 0.2.240

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 CHANGED
@@ -210,9 +210,11 @@ chatccc
210
210
 
211
211
  Claude Code、Cursor 和 Codex 需要对应的本地工具;CCC Agent 内置于 ChatCCC,开箱即用,**模型接入不限于 DeepSeek**——它走 OpenAI 兼容协议,任意兼容端点都可以直接替换(详见下文 CCC Agent)。
212
212
 
213
- #### CCC Agent
214
-
215
- CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
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 会自动保持禁用。
216
218
 
217
219
  **API 支持不限于 DeepSeek。** CCC Agent 底层使用 OpenAI 兼容协议(`@ai-sdk/openai-compatible`),DeepSeek 只是出厂默认端点。`ccc.DEEPSEEK_API_KEY` / `ccc.DEEPSEEK_BASE_URL` 可以指向**任意 OpenAI 兼容服务**,例如:
218
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.239",
3
+ "version": "0.2.240",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -15,8 +15,23 @@ import {
15
15
  autoDetectCursorPath,
16
16
  normalizeOptionalConfigField,
17
17
  parseGitTimeoutSeconds,
18
- readToolCliPath,
19
- } from "../config-utils.ts";
18
+ readToolCliPath,
19
+ resolveCccEnabled,
20
+ } from "../config-utils.ts";
21
+
22
+ describe("resolveCccEnabled", () => {
23
+ it("never enables CCC Agent without a ChatCCC API key", () => {
24
+ expect(resolveCccEnabled(true, "")).toBe(false);
25
+ expect(resolveCccEnabled(true, " ")).toBe(false);
26
+ expect(resolveCccEnabled(undefined, undefined)).toBe(false);
27
+ });
28
+
29
+ it("respects the explicit enabled flag when a ChatCCC API key exists", () => {
30
+ expect(resolveCccEnabled(undefined, "sk-chatccc")).toBe(true);
31
+ expect(resolveCccEnabled(true, "sk-chatccc")).toBe(true);
32
+ expect(resolveCccEnabled(false, "sk-chatccc")).toBe(false);
33
+ });
34
+ });
20
35
 
21
36
  describe("parseGitTimeoutSeconds", () => {
22
37
  it("returns default when raw is undefined", () => {
@@ -0,0 +1,45 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const createCccAdapterMock = vi.hoisted(() => vi.fn(() => ({
4
+ displayName: "CCC Agent",
5
+ sessionDescPrefix: "CCC Session:",
6
+ createSession: vi.fn(),
7
+ prompt: vi.fn(),
8
+ getSessionInfo: vi.fn(),
9
+ closeSession: vi.fn(),
10
+ })));
11
+
12
+ vi.mock("../adapters/ccc-adapter.ts", () => ({
13
+ createCccAdapter: createCccAdapterMock,
14
+ }));
15
+
16
+ import { config } from "../config.ts";
17
+ import { _clearAdapterCacheForTest, getAdapterForTool } from "../session.ts";
18
+
19
+ describe("CCC Agent ChatCCC configuration", () => {
20
+ const original = { ...config.ccc };
21
+
22
+ afterEach(() => {
23
+ Object.assign(config.ccc, original);
24
+ _clearAdapterCacheForTest();
25
+ createCccAdapterMock.mockClear();
26
+ });
27
+
28
+ it("injects ChatCCC credentials and endpoint instead of relying on ~/.deepccc", () => {
29
+ Object.assign(config.ccc, {
30
+ DEEPSEEK_API_KEY: "chatccc-api-key",
31
+ DEEPSEEK_BASE_URL: "https://chatccc.example.com/v1",
32
+ model: "chatccc-model",
33
+ effort: "high",
34
+ });
35
+
36
+ getAdapterForTool("ccc");
37
+
38
+ expect(createCccAdapterMock).toHaveBeenCalledWith({
39
+ apiKey: "chatccc-api-key",
40
+ baseURL: "https://chatccc.example.com/v1",
41
+ model: "chatccc-model",
42
+ effort: "high",
43
+ });
44
+ });
45
+ });
@@ -42,8 +42,12 @@ function toChatSessionOptions(
42
42
  }
43
43
 
44
44
  export function createCccAdapter(options: CccAdapterOptions = {}): ToolAdapter {
45
+ if (!options.apiKey?.trim()) {
46
+ throw new Error("ChatCCC 未配置 CCC Agent API Key。请先填写 ccc.DEEPSEEK_API_KEY 后再启用 CCC Agent。");
47
+ }
48
+
45
49
  const chatConfig: ChatSessionConfig = {
46
- ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
50
+ apiKey: options.apiKey,
47
51
  ...(options.baseURL !== undefined ? { baseURL: options.baseURL } : {}),
48
52
  ...(options.model !== undefined ? { model: options.model } : {}),
49
53
  ...(options.effort !== undefined ? { effort: options.effort } : {}),
@@ -20,7 +20,7 @@ import { join } from "node:path";
20
20
  * - `value` 去除空白后等于 `"default"`(不区分大小写)→ 视作 `""` 并 warn。
21
21
  * - 其余情况原样返回(不裁剪两端空白,留给具体调用方决定)。
22
22
  */
23
- export function normalizeOptionalConfigField(
23
+ export function normalizeOptionalConfigField(
24
24
  value: unknown,
25
25
  options: { label: string; fallback?: string },
26
26
  ): string {
@@ -33,8 +33,18 @@ export function normalizeOptionalConfigField(
33
33
  );
34
34
  return "";
35
35
  }
36
- return value;
37
- }
36
+ return value;
37
+ }
38
+
39
+ /**
40
+ * CCC Agent requires credentials owned by ChatCCC. An explicit enabled=true
41
+ * must not make the agent selectable when that credential is absent.
42
+ */
43
+ export function resolveCccEnabled(rawEnabled: unknown, apiKey: unknown): boolean {
44
+ const hasApiKey = typeof apiKey === "string" && apiKey.trim().length > 0;
45
+ if (!hasApiKey) return false;
46
+ return typeof rawEnabled === "boolean" ? rawEnabled : true;
47
+ }
38
48
 
39
49
  // ---------------------------------------------------------------------------
40
50
  // /git 超时配置相关
package/src/config.ts CHANGED
@@ -11,9 +11,10 @@ import {
11
11
  anthropicConfigDisplay,
12
12
  autoDetectCodexPath,
13
13
  autoDetectCursorPath,
14
- normalizeOptionalConfigField,
15
- readToolCliPath,
16
- } from "./config-utils.ts";
14
+ normalizeOptionalConfigField,
15
+ readToolCliPath,
16
+ resolveCccEnabled,
17
+ } from "./config-utils.ts";
17
18
 
18
19
  // 重新导出 config-utils 中的纯函数/常量,保持对外 API 不变
19
20
  // (历史上这些符号都从 ./config.ts 导入;新代码可直接从 ./config-utils.ts 导入以避免触发本文件的副作用)
@@ -22,10 +23,11 @@ export {
22
23
  MIN_GIT_TIMEOUT_SECONDS,
23
24
  MAX_GIT_TIMEOUT_SECONDS,
24
25
  parseGitTimeoutSeconds,
25
- normalizeOptionalConfigField,
26
- isAnthropicConfigEmpty,
27
- anthropicConfigDisplay,
28
- } from "./config-utils.ts";
26
+ normalizeOptionalConfigField,
27
+ isAnthropicConfigEmpty,
28
+ anthropicConfigDisplay,
29
+ resolveCccEnabled,
30
+ } from "./config-utils.ts";
29
31
  export type { ParsedGitTimeout } from "./config-utils.ts";
30
32
 
31
33
  // ---------------------------------------------------------------------------
@@ -595,13 +597,10 @@ function loadConfig(): AppConfig {
595
597
  );
596
598
  // 旧版 ccc 配置没有 enabled。只用 API Key 推断启用,避免 sample 中自带的
597
599
  // 默认 Base URL / model 让升级用户在未配置凭证时意外启用 CCC Agent。
598
- const cccNonEmpty = (): boolean =>
599
- Boolean(typeof cccRaw.DEEPSEEK_API_KEY === "string" && cccRaw.DEEPSEEK_API_KEY.trim());
600
-
601
- const claudeEnabled = resolveEnabled(claude.enabled, claudeNonEmpty);
602
- const cursorEnabled = resolveEnabled(cursorRaw.enabled, cursorNonEmpty);
603
- const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
604
- const cccEnabled = resolveEnabled(cccRaw.enabled, cccNonEmpty);
600
+ const claudeEnabled = resolveEnabled(claude.enabled, claudeNonEmpty);
601
+ const cursorEnabled = resolveEnabled(cursorRaw.enabled, cursorNonEmpty);
602
+ const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
603
+ const cccEnabled = resolveCccEnabled(cccRaw.enabled, cccRaw.DEEPSEEK_API_KEY);
605
604
  const chromeDevtoolsPort = Number(chromeDevtoolsRaw.port);
606
605
  const explicitDefaultTool: AgentTool | null =
607
606
  typeof claude.defaultAgent === "boolean" && claude.defaultAgent && claudeEnabled ? "claude" :
package/src/session.ts CHANGED
@@ -707,11 +707,13 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
707
707
  effort: effectiveEffort || undefined,
708
708
  fastMode: effectiveFastMode,
709
709
  });
710
- } else if (tool === "ccc") {
711
- adapter = createCccAdapter({
712
- model: effectiveModel || undefined,
713
- effort: effectiveEffort || undefined,
714
- });
710
+ } else if (tool === "ccc") {
711
+ adapter = createCccAdapter({
712
+ apiKey: config.ccc.DEEPSEEK_API_KEY,
713
+ baseURL: config.ccc.DEEPSEEK_BASE_URL,
714
+ model: effectiveModel || undefined,
715
+ effort: effectiveEffort || undefined,
716
+ });
715
717
  } else {
716
718
  adapter = createClaudeAdapter({
717
719
  model: effectiveModel,