chatccc 0.2.56 → 0.2.57

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.
@@ -86,33 +86,33 @@ function buildSdkEnv(
86
86
  baseUrl: string | undefined,
87
87
  subagentModel: string | undefined,
88
88
  ): Record<string, string | undefined> | undefined {
89
- const apiKeyTrim = (apiKey ?? "").trim();
90
- const baseUrlTrim = (baseUrl ?? "").trim();
91
- const subagentModelTrim = (subagentModel ?? "").trim();
92
- const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
93
- if (!hasApiOverride) return undefined;
94
-
95
- const env: Record<string, string | undefined> = { ...process.env };
96
- // ChatCCC's third-party Claude API config is authoritative when present.
97
- // Remove Claude Code/user settings env that can silently override gateway/auth/model choice.
98
- delete env.ANTHROPIC_AUTH_TOKEN;
99
- delete env.CLAUDE_CODE_OAUTH_TOKEN;
100
- delete env.ANTHROPIC_MODEL;
101
- delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
102
- delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
103
- delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
104
- delete env.CLAUDE_CODE_SUBAGENT_MODEL;
105
- delete env.CLAUDE_CODE_EFFORT_LEVEL;
106
-
107
- if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
108
- if (baseUrlTrim) {
109
- env.ANTHROPIC_BASE_URL = baseUrlTrim;
110
- } else {
111
- delete env.ANTHROPIC_BASE_URL;
112
- }
113
- if (subagentModelTrim) env.CLAUDE_CODE_SUBAGENT_MODEL = subagentModelTrim;
114
- return env;
115
- }
89
+ const apiKeyTrim = (apiKey ?? "").trim();
90
+ const baseUrlTrim = (baseUrl ?? "").trim();
91
+ const subagentModelTrim = (subagentModel ?? "").trim();
92
+ const hasApiOverride = Boolean(apiKeyTrim || baseUrlTrim);
93
+ if (!hasApiOverride) return undefined;
94
+
95
+ const env: Record<string, string | undefined> = { ...process.env };
96
+ // ChatCCC's third-party Claude API config is authoritative when present.
97
+ // Remove Claude Code/user settings env that can silently override gateway/auth/model choice.
98
+ delete env.ANTHROPIC_AUTH_TOKEN;
99
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
100
+ delete env.ANTHROPIC_MODEL;
101
+ delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
102
+ delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
103
+ delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
104
+ delete env.CLAUDE_CODE_SUBAGENT_MODEL;
105
+ delete env.CLAUDE_CODE_EFFORT_LEVEL;
106
+
107
+ if (apiKeyTrim) env.ANTHROPIC_API_KEY = apiKeyTrim;
108
+ if (baseUrlTrim) {
109
+ env.ANTHROPIC_BASE_URL = baseUrlTrim;
110
+ } else {
111
+ delete env.ANTHROPIC_BASE_URL;
112
+ }
113
+ if (subagentModelTrim) env.CLAUDE_CODE_SUBAGENT_MODEL = subagentModelTrim;
114
+ return env;
115
+ }
116
116
 
117
117
  function resolveSettingSources(
118
118
  _apiKey: string | undefined,
@@ -291,19 +291,19 @@ class ClaudeAdapter implements ToolAdapter {
291
291
  this.baseUrl,
292
292
  this.subagentModel,
293
293
  );
294
- const session = unstable_v2_resumeSession(
295
- sessionId,
296
- sessionOpts as any,
297
- );
298
-
299
- if (signal?.aborted) {
300
- session.close();
301
- return;
302
- }
303
- const onAbort = () => { session.close(); };
304
- signal?.addEventListener("abort", onAbort, { once: true });
305
-
306
- await session.send(userText);
294
+ const session = unstable_v2_resumeSession(
295
+ sessionId,
296
+ sessionOpts as any,
297
+ );
298
+
299
+ if (signal?.aborted) {
300
+ session.close();
301
+ return;
302
+ }
303
+ const onAbort = () => { session.close(); };
304
+ signal?.addEventListener("abort", onAbort, { once: true });
305
+
306
+ await session.send(userText);
307
307
 
308
308
  const stream = session.stream();
309
309
 
package/src/index.ts CHANGED
@@ -84,9 +84,9 @@ import {
84
84
  updateCardKitCard,
85
85
  } from "./cardkit.ts";
86
86
  import {
87
- MAX_PROCESSED,
88
- clearAdapterCache,
89
- loadSessionRegistryForBinding,
87
+ MAX_PROCESSED,
88
+ clearAdapterCache,
89
+ loadSessionRegistryForBinding,
90
90
  processedMessages,
91
91
  rebuildBindingsFromRegistry,
92
92
  resetState,
@@ -744,12 +744,12 @@ async function main(): Promise<void> {
744
744
  // "保存并启动" 时,web-ui 会调用本回调,把磁盘上刚保存的 config.json
745
745
  // 刷进进程内的 export let 常量(live binding 让 CLAUDE_MODEL 等下次创建
746
746
  // 会话时自动看到新值)。setup 首次激活走 onActivate 路径,不依赖此 hook。
747
- setReloadConfigHook(() => {
748
- reloadConfigFromDisk();
749
- clearAdapterCache();
750
- appendStartupTrace("reload-from-ui: config reloaded", {
751
- appIdMask: maskAppId(APP_ID),
752
- });
747
+ setReloadConfigHook(() => {
748
+ reloadConfigFromDisk();
749
+ clearAdapterCache();
750
+ appendStartupTrace("reload-from-ui: config reloaded", {
751
+ appIdMask: maskAppId(APP_ID),
752
+ });
753
753
  });
754
754
  setExtraApiHandler(async (req, res) => {
755
755
  return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res));
@@ -769,12 +769,12 @@ async function main(): Promise<void> {
769
769
  // 凭证不全:进 setup 向导。注入 onActivate 回调让用户点"保存并启动"
770
770
  // 时,原地(同进程)调用 startBotService,复用 setup HTTP server。
771
771
  startSetupMode(CHATCCC_PORT, {
772
- onActivate: async (httpServer: Server) => {
773
- reloadConfigFromDisk();
774
- clearAdapterCache();
775
- appendStartupTrace("setup-activate: reloaded config from disk", {
776
- appIdMaskAfterReload: maskAppId(APP_ID),
777
- });
772
+ onActivate: async (httpServer: Server) => {
773
+ reloadConfigFromDisk();
774
+ clearAdapterCache();
775
+ appendStartupTrace("setup-activate: reloaded config from disk", {
776
+ appIdMaskAfterReload: maskAppId(APP_ID),
777
+ });
778
778
  try {
779
779
  await startBotService({ httpServer, port: CHATCCC_PORT });
780
780
  installShutdownHandlers(httpServer);
package/src/privacy.ts CHANGED
@@ -1,68 +1,68 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { USER_DATA_DIR } from "./config.ts";
4
-
5
- // ---------------------------------------------------------------------------
6
- // 隐私替换规则
7
- // ---------------------------------------------------------------------------
8
-
9
- interface PrivacyRules {
10
- [key: string]: string;
11
- }
12
-
13
- let rules: PrivacyRules | null = null;
14
- let loaded = false;
15
-
16
- function loadRules(): PrivacyRules {
17
- const filePath = join(USER_DATA_DIR, "privacy.json");
18
- if (!existsSync(filePath)) {
19
- return {};
20
- }
21
- try {
22
- const raw = readFileSync(filePath, "utf-8");
23
- const parsed = JSON.parse(raw);
24
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
25
- console.error(`[PRIVACY] privacy.json 格式错误:应为对象`);
26
- return {};
27
- }
28
- for (const [k, v] of Object.entries(parsed)) {
29
- if (typeof v !== "string") {
30
- console.error(`[PRIVACY] privacy.json 值必须为字符串,跳过 "${k}"`);
31
- delete (parsed as Record<string, unknown>)[k];
32
- }
33
- }
34
- return parsed as PrivacyRules;
35
- } catch (err) {
36
- console.error(`[PRIVACY] 读取 privacy.json 失败: ${(err as Error).message}`);
37
- return {};
38
- }
39
- }
40
-
41
- export function getPrivacyRules(): PrivacyRules {
42
- if (!loaded) {
43
- rules = loadRules();
44
- loaded = true;
45
- }
46
- return rules!;
47
- }
48
-
49
- /** 重新加载规则(热更新用) */
50
- export function reloadPrivacyRules(): void {
51
- loaded = false;
52
- rules = null;
53
- }
54
-
55
- /**
56
- * 对文本应用隐私替换规则。
57
- * 若无规则或文本为空,直接返回原文。
58
- */
59
- export function applyPrivacy(text: string): string {
60
- const r = getPrivacyRules();
61
- if (Object.keys(r).length === 0 || !text) return text;
62
- let result = text;
63
- for (const [from, to] of Object.entries(r)) {
64
- // 用 split+join 替代 replaceAll,避免正则特殊字符问题
65
- result = result.split(from).join(to);
66
- }
67
- return result;
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { USER_DATA_DIR } from "./config.ts";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // 隐私替换规则
7
+ // ---------------------------------------------------------------------------
8
+
9
+ interface PrivacyRules {
10
+ [key: string]: string;
11
+ }
12
+
13
+ let rules: PrivacyRules | null = null;
14
+ let loaded = false;
15
+
16
+ function loadRules(): PrivacyRules {
17
+ const filePath = join(USER_DATA_DIR, "privacy.json");
18
+ if (!existsSync(filePath)) {
19
+ return {};
20
+ }
21
+ try {
22
+ const raw = readFileSync(filePath, "utf-8");
23
+ const parsed = JSON.parse(raw);
24
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
25
+ console.error(`[PRIVACY] privacy.json 格式错误:应为对象`);
26
+ return {};
27
+ }
28
+ for (const [k, v] of Object.entries(parsed)) {
29
+ if (typeof v !== "string") {
30
+ console.error(`[PRIVACY] privacy.json 值必须为字符串,跳过 "${k}"`);
31
+ delete (parsed as Record<string, unknown>)[k];
32
+ }
33
+ }
34
+ return parsed as PrivacyRules;
35
+ } catch (err) {
36
+ console.error(`[PRIVACY] 读取 privacy.json 失败: ${(err as Error).message}`);
37
+ return {};
38
+ }
39
+ }
40
+
41
+ export function getPrivacyRules(): PrivacyRules {
42
+ if (!loaded) {
43
+ rules = loadRules();
44
+ loaded = true;
45
+ }
46
+ return rules!;
47
+ }
48
+
49
+ /** 重新加载规则(热更新用) */
50
+ export function reloadPrivacyRules(): void {
51
+ loaded = false;
52
+ rules = null;
53
+ }
54
+
55
+ /**
56
+ * 对文本应用隐私替换规则。
57
+ * 若无规则或文本为空,直接返回原文。
58
+ */
59
+ export function applyPrivacy(text: string): string {
60
+ const r = getPrivacyRules();
61
+ if (Object.keys(r).length === 0 || !text) return text;
62
+ let result = text;
63
+ for (const [from, to] of Object.entries(r)) {
64
+ // 用 split+join 替代 replaceAll,避免正则特殊字符问题
65
+ result = result.split(from).join(to);
66
+ }
67
+ return result;
68
68
  }
package/src/session.ts CHANGED
@@ -393,7 +393,8 @@ export function accumulateBlockContent(
393
393
  switch (block.type) {
394
394
  case "thinking":
395
395
  state.chunkCount++;
396
- state.accumulatedContent += block.thinking;
396
+ // 用引用块标记思考内容(中文无法斜体,引用块有视觉区分)
397
+ state.accumulatedContent += `\n> ${block.thinking.replace(/\n/g, "\n> ")}\n`;
397
398
  break;
398
399
  case "tool_use": {
399
400
  // 记录 tool_use 信息供后续 tool_result 使用
package/src/simplify.ts CHANGED
@@ -1,120 +1,120 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { resolve as resolvePath } from "node:path";
3
- import { PROJECT_ROOT } from "./config.ts";
4
-
5
- // ---------------------------------------------------------------------------
6
- // 消息简化规则 —— 数据驱动,在 simplify.json 中配置
7
- // ---------------------------------------------------------------------------
8
-
9
- interface ToolRule {
10
- template: string;
11
- maxLength: number;
12
- }
13
-
14
- interface SimplifyConfig {
15
- tool_use?: Record<string, ToolRule>;
16
- tool_result?: Record<string, ToolRule>;
17
- }
18
-
19
- let config: SimplifyConfig | null = null;
20
- let loaded = false;
21
-
22
- function loadConfig(): SimplifyConfig {
23
- const filePath = resolvePath(PROJECT_ROOT, "simplify.json");
24
- if (!existsSync(filePath)) return {};
25
- try {
26
- const raw = readFileSync(filePath, "utf-8");
27
- const parsed = JSON.parse(raw);
28
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
29
- console.error("[SIMPLIFY] simplify.json 格式错误:应为对象");
30
- return {};
31
- }
32
- return parsed as SimplifyConfig;
33
- } catch (err) {
34
- console.error(`[SIMPLIFY] 读取 simplify.json 失败: ${(err as Error).message}`);
35
- return {};
36
- }
37
- }
38
-
39
- function getConfig(): SimplifyConfig {
40
- if (!loaded) {
41
- config = loadConfig();
42
- loaded = true;
43
- }
44
- return config!;
45
- }
46
-
47
- /** 热重载 */
48
- export function reloadSimplifyConfig(): void {
49
- loaded = false;
50
- config = null;
51
- }
52
-
53
- /**
54
- * 对模板字符串中的 {field} 占位符做替换。
55
- * fields 为可用字段映射,extra 是额外的上下文(如 tool_use_id 的 id)。
56
- */
57
- function resolveTemplate(template: string, fields: Record<string, unknown>, extra?: Record<string, string>): string {
58
- let result = template;
59
- if (extra) {
60
- for (const [k, v] of Object.entries(extra)) {
61
- result = result.split(`{${k}}`).join(v);
62
- }
63
- }
64
- for (const [k, v] of Object.entries(fields)) {
65
- const strVal = typeof v === "string" ? v : JSON.stringify(v);
66
- result = result.split(`{${k}}`).join(strVal);
67
- }
68
- return result;
69
- }
70
-
71
- /**
72
- * 简化 tool_use 展示。
73
- * 返回 null 表示无规则,调用方应回退到默认格式化。
74
- */
75
- export function simplifyToolUse(name: string, input: unknown): string | null {
76
- const cfg = getConfig();
77
- const rules = cfg.tool_use;
78
- if (!rules) return null;
79
- const rule = rules[name];
80
- if (!rule) return null;
81
-
82
- const fields = typeof input === "object" && input !== null
83
- ? input as Record<string, unknown>
84
- : {};
85
- let result = resolveTemplate(rule.template, fields);
86
- if (result.length > rule.maxLength) {
87
- result = result.slice(0, rule.maxLength) + "...";
88
- }
89
- return result;
90
- }
91
-
92
- /**
93
- * 简化 tool_result 展示。
94
- * 返回 null 表示无规则,调用方应回退到默认格式化。
95
- * toolCallInput 为对应的 tool_use 输入(可选),用于在 result 模板中引用输入字段。
96
- */
97
- export function simplifyToolResult(
98
- name: string,
99
- toolUseId: string,
100
- isError: boolean,
101
- toolCallInput?: unknown,
102
- ): string | null {
103
- const cfg = getConfig();
104
- const rules = cfg.tool_result;
105
- if (!rules) return null;
106
- const rule = rules[name];
107
- if (!rule) return null;
108
-
109
- const id = toolUseId.slice(-6);
110
- const extra = { id };
111
- const fields = toolCallInput && typeof toolCallInput === "object"
112
- ? toolCallInput as Record<string, unknown>
113
- : {};
114
- let result = resolveTemplate(rule.template, fields, extra);
115
- if (isError) result = "❌ " + result;
116
- if (result.length > rule.maxLength) {
117
- result = result.slice(0, rule.maxLength) + "...";
118
- }
119
- return result;
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve as resolvePath } from "node:path";
3
+ import { PROJECT_ROOT } from "./config.ts";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // 消息简化规则 —— 数据驱动,在 simplify.json 中配置
7
+ // ---------------------------------------------------------------------------
8
+
9
+ interface ToolRule {
10
+ template: string;
11
+ maxLength: number;
12
+ }
13
+
14
+ interface SimplifyConfig {
15
+ tool_use?: Record<string, ToolRule>;
16
+ tool_result?: Record<string, ToolRule>;
17
+ }
18
+
19
+ let config: SimplifyConfig | null = null;
20
+ let loaded = false;
21
+
22
+ function loadConfig(): SimplifyConfig {
23
+ const filePath = resolvePath(PROJECT_ROOT, "simplify.json");
24
+ if (!existsSync(filePath)) return {};
25
+ try {
26
+ const raw = readFileSync(filePath, "utf-8");
27
+ const parsed = JSON.parse(raw);
28
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
29
+ console.error("[SIMPLIFY] simplify.json 格式错误:应为对象");
30
+ return {};
31
+ }
32
+ return parsed as SimplifyConfig;
33
+ } catch (err) {
34
+ console.error(`[SIMPLIFY] 读取 simplify.json 失败: ${(err as Error).message}`);
35
+ return {};
36
+ }
37
+ }
38
+
39
+ function getConfig(): SimplifyConfig {
40
+ if (!loaded) {
41
+ config = loadConfig();
42
+ loaded = true;
43
+ }
44
+ return config!;
45
+ }
46
+
47
+ /** 热重载 */
48
+ export function reloadSimplifyConfig(): void {
49
+ loaded = false;
50
+ config = null;
51
+ }
52
+
53
+ /**
54
+ * 对模板字符串中的 {field} 占位符做替换。
55
+ * fields 为可用字段映射,extra 是额外的上下文(如 tool_use_id 的 id)。
56
+ */
57
+ function resolveTemplate(template: string, fields: Record<string, unknown>, extra?: Record<string, string>): string {
58
+ let result = template;
59
+ if (extra) {
60
+ for (const [k, v] of Object.entries(extra)) {
61
+ result = result.split(`{${k}}`).join(v);
62
+ }
63
+ }
64
+ for (const [k, v] of Object.entries(fields)) {
65
+ const strVal = typeof v === "string" ? v : JSON.stringify(v);
66
+ result = result.split(`{${k}}`).join(strVal);
67
+ }
68
+ return result;
69
+ }
70
+
71
+ /**
72
+ * 简化 tool_use 展示。
73
+ * 返回 null 表示无规则,调用方应回退到默认格式化。
74
+ */
75
+ export function simplifyToolUse(name: string, input: unknown): string | null {
76
+ const cfg = getConfig();
77
+ const rules = cfg.tool_use;
78
+ if (!rules) return null;
79
+ const rule = rules[name];
80
+ if (!rule) return null;
81
+
82
+ const fields = typeof input === "object" && input !== null
83
+ ? input as Record<string, unknown>
84
+ : {};
85
+ let result = resolveTemplate(rule.template, fields);
86
+ if (result.length > rule.maxLength) {
87
+ result = result.slice(0, rule.maxLength) + "...";
88
+ }
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * 简化 tool_result 展示。
94
+ * 返回 null 表示无规则,调用方应回退到默认格式化。
95
+ * toolCallInput 为对应的 tool_use 输入(可选),用于在 result 模板中引用输入字段。
96
+ */
97
+ export function simplifyToolResult(
98
+ name: string,
99
+ toolUseId: string,
100
+ isError: boolean,
101
+ toolCallInput?: unknown,
102
+ ): string | null {
103
+ const cfg = getConfig();
104
+ const rules = cfg.tool_result;
105
+ if (!rules) return null;
106
+ const rule = rules[name];
107
+ if (!rule) return null;
108
+
109
+ const id = toolUseId.slice(-6);
110
+ const extra = { id };
111
+ const fields = toolCallInput && typeof toolCallInput === "object"
112
+ ? toolCallInput as Record<string, unknown>
113
+ : {};
114
+ let result = resolveTemplate(rule.template, fields, extra);
115
+ if (isError) result = "❌ " + result;
116
+ if (result.length > rule.maxLength) {
117
+ result = result.slice(0, rule.maxLength) + "...";
118
+ }
119
+ return result;
120
120
  }