chatccc 0.2.103 → 0.2.107
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 +6 -3
- package/config.sample.json +1 -2
- package/package.json +63 -63
- package/src/__tests__/claude-adapter.test.ts +442 -912
- package/src/__tests__/config-reload.test.ts +6 -6
- package/src/__tests__/web-ui.test.ts +83 -195
- package/src/adapters/claude-adapter.ts +455 -372
- package/src/adapters/claude-session-meta-store.ts +120 -0
- package/src/cards.ts +0 -31
- package/src/config.ts +5 -17
- package/src/orchestrator.ts +0 -134
- package/src/session.ts +1 -1
- package/src/web-ui.ts +43 -285
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// claude-session-meta-store.ts — Claude 会话 sessionId → meta 持久化映射
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// 背景:切换到直接调用 Claude CLI 后,不再有 SDK 的 getSessionInfo 可用。
|
|
5
|
+
// ChatCCC 必须自己维护 sessionId → { cwd, model } 映射。
|
|
6
|
+
//
|
|
7
|
+
// 存储:
|
|
8
|
+
// 文件 state/claude-session-meta.json,结构:
|
|
9
|
+
// { "<sessionId>": { "cwd": "...", "model": "..." } }
|
|
10
|
+
//
|
|
11
|
+
// API 设计:
|
|
12
|
+
// set(sid, partial) → 部分合并写入;只更新非空字段,不会清空其他字段
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { USER_DATA_DIR } from "../config.ts";
|
|
19
|
+
|
|
20
|
+
export const CLAUDE_SESSION_META_FILE = join(
|
|
21
|
+
USER_DATA_DIR,
|
|
22
|
+
"state",
|
|
23
|
+
"claude-session-meta.json",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export interface ClaudeSessionMeta {
|
|
27
|
+
cwd: string;
|
|
28
|
+
model?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ClaudeSessionMetaStore {
|
|
32
|
+
get(sessionId: string): Promise<ClaudeSessionMeta | undefined>;
|
|
33
|
+
set(sessionId: string, partial: Partial<ClaudeSessionMeta>): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface RawEntry {
|
|
37
|
+
cwd?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isNonEmptyString(v: unknown): v is string {
|
|
42
|
+
return typeof v === "string" && v.length > 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseEntry(raw: unknown): RawEntry | null {
|
|
46
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
47
|
+
return { cwd: raw };
|
|
48
|
+
}
|
|
49
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
50
|
+
const obj = raw as Record<string, unknown>;
|
|
51
|
+
const out: RawEntry = {};
|
|
52
|
+
if (isNonEmptyString(obj.cwd)) out.cwd = obj.cwd;
|
|
53
|
+
if (isNonEmptyString(obj.model)) out.model = obj.model;
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createClaudeSessionMetaStore(
|
|
60
|
+
filePath: string = CLAUDE_SESSION_META_FILE,
|
|
61
|
+
): ClaudeSessionMetaStore {
|
|
62
|
+
let cache: Record<string, RawEntry> | null = null;
|
|
63
|
+
|
|
64
|
+
async function load(): Promise<Record<string, RawEntry>> {
|
|
65
|
+
if (cache) return cache;
|
|
66
|
+
try {
|
|
67
|
+
const raw = await readFile(filePath, "utf-8");
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
70
|
+
const out: Record<string, RawEntry> = {};
|
|
71
|
+
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
|
72
|
+
const entry = parseEntry(v);
|
|
73
|
+
if (entry) out[k] = entry;
|
|
74
|
+
}
|
|
75
|
+
cache = out;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// 文件不存在 / JSON 损坏 → 视为空映射
|
|
80
|
+
}
|
|
81
|
+
cache = {};
|
|
82
|
+
return cache;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
async get(sessionId: string): Promise<ClaudeSessionMeta | undefined> {
|
|
87
|
+
const map = await load();
|
|
88
|
+
const entry = map[sessionId];
|
|
89
|
+
if (!entry || !isNonEmptyString(entry.cwd)) return undefined;
|
|
90
|
+
return entry.model
|
|
91
|
+
? { cwd: entry.cwd, model: entry.model }
|
|
92
|
+
: { cwd: entry.cwd };
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async set(
|
|
96
|
+
sessionId: string,
|
|
97
|
+
partial: Partial<ClaudeSessionMeta>,
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
const map = await load();
|
|
100
|
+
const existing = map[sessionId] ?? {};
|
|
101
|
+
const merged: RawEntry = { ...existing };
|
|
102
|
+
if (isNonEmptyString(partial.cwd)) merged.cwd = partial.cwd;
|
|
103
|
+
if (isNonEmptyString(partial.model)) merged.model = partial.model;
|
|
104
|
+
|
|
105
|
+
if (existing.cwd === merged.cwd && existing.model === merged.model) return;
|
|
106
|
+
|
|
107
|
+
map[sessionId] = merged;
|
|
108
|
+
try {
|
|
109
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
110
|
+
await writeFile(filePath, JSON.stringify(map, null, 2), "utf-8");
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(
|
|
113
|
+
`[claude-session-meta] failed to persist ${filePath}: ${(err as Error).message}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const defaultClaudeSessionMetaStore = createClaudeSessionMetaStore();
|
package/src/cards.ts
CHANGED
|
@@ -131,7 +131,6 @@ export function buildHelpCard(
|
|
|
131
131
|
"发送 **/new cursor** 创建新 Cursor 会话",
|
|
132
132
|
"发送 **/new codex** 创建新 Codex 会话",
|
|
133
133
|
"发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
|
|
134
|
-
"发送 **/claude** 查看或切换 Claude API 模式(官方/第三方)",
|
|
135
134
|
].join("\n");
|
|
136
135
|
return JSON.stringify({
|
|
137
136
|
config: { wide_screen_mode: true },
|
|
@@ -463,33 +462,3 @@ export function buildModelCard(
|
|
|
463
462
|
});
|
|
464
463
|
}
|
|
465
464
|
|
|
466
|
-
/** Claude API 模式切换卡片(/claude 命令) */
|
|
467
|
-
export function buildClaudeCard(
|
|
468
|
-
currentMode: "official" | "thirdparty",
|
|
469
|
-
missingFields: string[],
|
|
470
|
-
): string {
|
|
471
|
-
const modeLabel = currentMode === "thirdparty" ? "第三方 API(自定义网关)" : "官方 API(Anthropic 直连)";
|
|
472
|
-
const modeDesc = currentMode === "thirdparty"
|
|
473
|
-
? "使用第三方兼容网关,需配置 API Key 和 Base URL。"
|
|
474
|
-
: "直连 Anthropic 官方 API,使用系统环境变量中的 ANTHROPIC_API_KEY。";
|
|
475
|
-
const lines = [`**当前模式:** ${modeLabel}`, "", modeDesc];
|
|
476
|
-
|
|
477
|
-
if (missingFields.length > 0) {
|
|
478
|
-
lines.push("", `**切换失败:** 以下必填项未配置: ${missingFields.map(f => `\`${f}\``).join(", ")}`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const buttons: ButtonDef[] = [
|
|
482
|
-
{ text: "/claude official", value: JSON.stringify({ cmd: "/claude official" }), type: currentMode === "official" ? "default" : "primary" },
|
|
483
|
-
{ text: "/claude 3rd", value: JSON.stringify({ cmd: "/claude 3rd" }), type: currentMode === "thirdparty" ? "default" : "primary" },
|
|
484
|
-
];
|
|
485
|
-
|
|
486
|
-
return JSON.stringify({
|
|
487
|
-
config: { wide_screen_mode: true },
|
|
488
|
-
header: { template: "blue", title: { content: "Claude API 模式", tag: "plain_text" } },
|
|
489
|
-
elements: [
|
|
490
|
-
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
491
|
-
{ tag: "hr" },
|
|
492
|
-
buildButtons(buttons),
|
|
493
|
-
],
|
|
494
|
-
});
|
|
495
|
-
}
|
package/src/config.ts
CHANGED
|
@@ -65,10 +65,10 @@ export interface ClaudeConfig {
|
|
|
65
65
|
model: string;
|
|
66
66
|
subagentModel: string;
|
|
67
67
|
effort: string;
|
|
68
|
+
/** Anthropic API Key(选填,留空则使用 Claude CLI 默认认证) */
|
|
68
69
|
apiKey: string;
|
|
70
|
+
/** Anthropic 兼容 API Base URL(选填,留空则使用默认端点) */
|
|
69
71
|
baseUrl: string;
|
|
70
|
-
/** 是否使用第三方 API(非 Anthropic 官方);为 true 时 baseUrl 必须配置 */
|
|
71
|
-
useThirdPartyApi: boolean;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export interface CursorConfig {
|
|
@@ -324,7 +324,7 @@ function loadConfig(): AppConfig {
|
|
|
324
324
|
port: 18080,
|
|
325
325
|
gitTimeoutSeconds: 180,
|
|
326
326
|
allowInterrupt: false,
|
|
327
|
-
claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: ""
|
|
327
|
+
claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "" },
|
|
328
328
|
cursor: { enabled: false, defaultAgent: false, path: "", model: "claude-opus-4-7-max" },
|
|
329
329
|
codex: { enabled: false, defaultAgent: false, path: "", model: "", effort: "" },
|
|
330
330
|
};
|
|
@@ -466,9 +466,8 @@ function loadConfig(): AppConfig {
|
|
|
466
466
|
model: normalizeOptionalConfigField(claude.model, { label: "claude.model" }),
|
|
467
467
|
subagentModel: normalizeOptionalConfigField(claude.subagentModel, { label: "claude.subagentModel" }),
|
|
468
468
|
effort: normalizeOptionalConfigField(claude.effort, { label: "claude.effort" }),
|
|
469
|
-
apiKey: claude.apiKey
|
|
470
|
-
baseUrl: claude.baseUrl
|
|
471
|
-
useThirdPartyApi: typeof claude.useThirdPartyApi === "boolean" ? claude.useThirdPartyApi : false,
|
|
469
|
+
apiKey: normalizeOptionalConfigField(claude.apiKey, { label: "claude.apiKey" }),
|
|
470
|
+
baseUrl: normalizeOptionalConfigField(claude.baseUrl, { label: "claude.baseUrl" }),
|
|
472
471
|
},
|
|
473
472
|
cursor: {
|
|
474
473
|
enabled: cursorEnabled,
|
|
@@ -496,12 +495,6 @@ function loadConfig(): AppConfig {
|
|
|
496
495
|
*/
|
|
497
496
|
export const config: AppConfig = loadConfig();
|
|
498
497
|
|
|
499
|
-
// 注:历史上这里曾把 config.claude.apiKey / baseUrl 写入 process.env 以便
|
|
500
|
-
// @anthropic-ai/claude-agent-sdk 子进程读取,但这会污染主进程的环境变量。
|
|
501
|
-
// 现在改为:在 ClaudeAdapter 构造时把这两个值传进去,由 adapter 在调用 SDK
|
|
502
|
-
// 时通过 SDK 的 `Options.env` 字段(仅作用于子进程)传递,主进程 env 保持
|
|
503
|
-
// 干净。详见 src/adapters/claude-adapter.ts buildSdkEnv()。
|
|
504
|
-
|
|
505
498
|
// ---------------------------------------------------------------------------
|
|
506
499
|
// Re-exported config values
|
|
507
500
|
// ---------------------------------------------------------------------------
|
|
@@ -528,12 +521,8 @@ export const LOCAL_RELAY_URL = `ws://127.0.0.1:${CHATCCC_PORT}`;
|
|
|
528
521
|
export let CLAUDE_MODEL = config.claude.model;
|
|
529
522
|
export let CLAUDE_SUBAGENT_MODEL = config.claude.subagentModel;
|
|
530
523
|
export let CLAUDE_EFFORT = config.claude.effort;
|
|
531
|
-
/** Anthropic 兼容网关的 API key(仅经 SDK 子进程 env 传递,从不写入主进程 process.env) */
|
|
532
524
|
export let CLAUDE_API_KEY = config.claude.apiKey;
|
|
533
|
-
/** Anthropic 兼容网关的 base URL(仅经 SDK 子进程 env 传递,从不写入主进程 process.env) */
|
|
534
525
|
export let CLAUDE_BASE_URL = config.claude.baseUrl;
|
|
535
|
-
/** 是否使用第三方 API(非 Anthropic 官方) */
|
|
536
|
-
export let CLAUDE_USE_THIRD_PARTY_API = config.claude.useThirdPartyApi;
|
|
537
526
|
|
|
538
527
|
/** 返回当前生效的 Claude 模型(per-session 覆盖由 session.ts 管理,此处仅返回全局配置) */
|
|
539
528
|
export function getEffectiveClaudeModel(): string {
|
|
@@ -613,7 +602,6 @@ export function applyLoadedConfig(next: AppConfig): void {
|
|
|
613
602
|
CLAUDE_EFFORT = next.claude.effort;
|
|
614
603
|
CLAUDE_API_KEY = next.claude.apiKey;
|
|
615
604
|
CLAUDE_BASE_URL = next.claude.baseUrl;
|
|
616
|
-
CLAUDE_USE_THIRD_PARTY_API = next.claude.useThirdPartyApi;
|
|
617
605
|
GIT_TIMEOUT_SECONDS = next.gitTimeoutSeconds;
|
|
618
606
|
GIT_TIMEOUT_MS = GIT_TIMEOUT_SECONDS * 1000;
|
|
619
607
|
ALLOW_INTERRUPT = next.allowInterrupt;
|
package/src/orchestrator.ts
CHANGED
|
@@ -16,9 +16,6 @@ import {
|
|
|
16
16
|
CLAUDE_EFFORT,
|
|
17
17
|
CLAUDE_MODEL,
|
|
18
18
|
CLAUDE_SUBAGENT_MODEL,
|
|
19
|
-
CLAUDE_API_KEY,
|
|
20
|
-
CLAUDE_BASE_URL,
|
|
21
|
-
CLAUDE_USE_THIRD_PARTY_API,
|
|
22
19
|
CONFIG_FILE,
|
|
23
20
|
GIT_TIMEOUT_MS,
|
|
24
21
|
PROJECT_ROOT,
|
|
@@ -46,7 +43,6 @@ import {
|
|
|
46
43
|
buildSessionsCard,
|
|
47
44
|
buildQueuedCard,
|
|
48
45
|
buildQueueFullCard,
|
|
49
|
-
buildClaudeCard,
|
|
50
46
|
} from "./cards.ts";
|
|
51
47
|
import {
|
|
52
48
|
formatGitResult,
|
|
@@ -390,7 +386,6 @@ export async function handleCommand(
|
|
|
390
386
|
`**工作目录:** \`${cwd}\`\n\n` +
|
|
391
387
|
`直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
|
|
392
388
|
`发送 **/cd** 切换新建会话的默认目录。\n` +
|
|
393
|
-
`发送 **/claude** 查看或切换 Claude API 模式(官方/第三方)。\n` +
|
|
394
389
|
`发送 **/model** 查看或切换当前会话的模型。\n` +
|
|
395
390
|
`发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
|
|
396
391
|
`发送 **/sessions** 查看所有会话状态。\n` +
|
|
@@ -1086,72 +1081,6 @@ export async function handleCommand(
|
|
|
1086
1081
|
return;
|
|
1087
1082
|
}
|
|
1088
1083
|
|
|
1089
|
-
// /claude — 查看/切换 Claude API 模式(官方 vs 第三方)
|
|
1090
|
-
if (textLower === "/claude" || textLower === "/claude official" || textLower === "/claude 3rd") {
|
|
1091
|
-
logTrace(tid, "BRANCH", { cmd: "/claude", arg: text.slice(8).trim() || "(none)" });
|
|
1092
|
-
|
|
1093
|
-
if (textLower === "/claude official") {
|
|
1094
|
-
// 切换到官方模式:设置 useThirdPartyApi=false,清空 apiKey 和 baseUrl
|
|
1095
|
-
try {
|
|
1096
|
-
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
1097
|
-
const cfg = JSON.parse(raw);
|
|
1098
|
-
cfg.claude = cfg.claude || {};
|
|
1099
|
-
cfg.claude.useThirdPartyApi = false;
|
|
1100
|
-
cfg.claude.apiKey = "";
|
|
1101
|
-
cfg.claude.baseUrl = "";
|
|
1102
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1103
|
-
reloadConfigFromDisk();
|
|
1104
|
-
logTrace(tid, "DONE", { outcome: "claude_official" });
|
|
1105
|
-
const card = buildClaudeCard("official", []);
|
|
1106
|
-
await platform.sendRawCard(chatId, card);
|
|
1107
|
-
} catch (err) {
|
|
1108
|
-
logTrace(tid, "DONE", { outcome: "claude_official_fail", error: (err as Error).message });
|
|
1109
|
-
await platform.sendText(chatId, `切换失败: ${(err as Error).message}`).catch(() => {});
|
|
1110
|
-
}
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
if (textLower === "/claude 3rd") {
|
|
1115
|
-
// 切换到第三方模式:检查 apiKey 和 baseUrl 是否已配置
|
|
1116
|
-
const missing: string[] = [];
|
|
1117
|
-
if (!CLAUDE_API_KEY.trim()) missing.push("API Key");
|
|
1118
|
-
if (!CLAUDE_BASE_URL.trim()) missing.push("Base URL");
|
|
1119
|
-
|
|
1120
|
-
if (missing.length > 0) {
|
|
1121
|
-
logTrace(tid, "DONE", { outcome: "claude_3rd_missing", missing });
|
|
1122
|
-
const card = buildClaudeCard(
|
|
1123
|
-
CLAUDE_USE_THIRD_PARTY_API ? "thirdparty" : "official",
|
|
1124
|
-
missing,
|
|
1125
|
-
);
|
|
1126
|
-
await platform.sendRawCard(chatId, card);
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
try {
|
|
1131
|
-
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
1132
|
-
const cfg = JSON.parse(raw);
|
|
1133
|
-
cfg.claude = cfg.claude || {};
|
|
1134
|
-
cfg.claude.useThirdPartyApi = true;
|
|
1135
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1136
|
-
reloadConfigFromDisk();
|
|
1137
|
-
logTrace(tid, "DONE", { outcome: "claude_3rd" });
|
|
1138
|
-
const card = buildClaudeCard("thirdparty", []);
|
|
1139
|
-
await platform.sendRawCard(chatId, card);
|
|
1140
|
-
} catch (err) {
|
|
1141
|
-
logTrace(tid, "DONE", { outcome: "claude_3rd_fail", error: (err as Error).message });
|
|
1142
|
-
await platform.sendText(chatId, `切换失败: ${(err as Error).message}`).catch(() => {});
|
|
1143
|
-
}
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
// /claude — 显示当前模式和切换按钮
|
|
1148
|
-
const currentMode: "official" | "thirdparty" = CLAUDE_USE_THIRD_PARTY_API ? "thirdparty" : "official";
|
|
1149
|
-
const card = buildClaudeCard(currentMode, []);
|
|
1150
|
-
await platform.sendRawCard(chatId, card);
|
|
1151
|
-
logTrace(tid, "DONE", { outcome: "claude_query", mode: currentMode });
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
1084
|
// /git <args>:在「当前会话工作目录」执行 git 命令
|
|
1156
1085
|
if (textLower.startsWith("/git ") || textLower === "/git") {
|
|
1157
1086
|
const args = text === "/git" ? "" : text.slice(5).trim();
|
|
@@ -1283,69 +1212,6 @@ export async function handleCommand(
|
|
|
1283
1212
|
return;
|
|
1284
1213
|
}
|
|
1285
1214
|
|
|
1286
|
-
// 无会话上下文 → 检查是否是 /claude 查询
|
|
1287
|
-
if (textLower === "/claude" || textLower === "/claude official" || textLower === "/claude 3rd") {
|
|
1288
|
-
logTrace(tid, "BRANCH", { cmd: "/claude", arg: text.slice(8).trim() || "(none)", noSession: true });
|
|
1289
|
-
|
|
1290
|
-
if (textLower === "/claude official") {
|
|
1291
|
-
try {
|
|
1292
|
-
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
1293
|
-
const cfg = JSON.parse(raw);
|
|
1294
|
-
cfg.claude = cfg.claude || {};
|
|
1295
|
-
cfg.claude.useThirdPartyApi = false;
|
|
1296
|
-
cfg.claude.apiKey = "";
|
|
1297
|
-
cfg.claude.baseUrl = "";
|
|
1298
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1299
|
-
reloadConfigFromDisk();
|
|
1300
|
-
logTrace(tid, "DONE", { outcome: "claude_official" });
|
|
1301
|
-
const card = buildClaudeCard("official", []);
|
|
1302
|
-
await platform.sendRawCard(chatId, card);
|
|
1303
|
-
} catch (err) {
|
|
1304
|
-
logTrace(tid, "DONE", { outcome: "claude_official_fail", error: (err as Error).message });
|
|
1305
|
-
await platform.sendText(chatId, `切换失败: ${(err as Error).message}`).catch(() => {});
|
|
1306
|
-
}
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
if (textLower === "/claude 3rd") {
|
|
1311
|
-
const missing: string[] = [];
|
|
1312
|
-
if (!CLAUDE_API_KEY.trim()) missing.push("API Key");
|
|
1313
|
-
if (!CLAUDE_BASE_URL.trim()) missing.push("Base URL");
|
|
1314
|
-
|
|
1315
|
-
if (missing.length > 0) {
|
|
1316
|
-
logTrace(tid, "DONE", { outcome: "claude_3rd_missing", missing });
|
|
1317
|
-
const card = buildClaudeCard(
|
|
1318
|
-
CLAUDE_USE_THIRD_PARTY_API ? "thirdparty" : "official",
|
|
1319
|
-
missing,
|
|
1320
|
-
);
|
|
1321
|
-
await platform.sendRawCard(chatId, card);
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
try {
|
|
1326
|
-
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
1327
|
-
const cfg = JSON.parse(raw);
|
|
1328
|
-
cfg.claude = cfg.claude || {};
|
|
1329
|
-
cfg.claude.useThirdPartyApi = true;
|
|
1330
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
1331
|
-
reloadConfigFromDisk();
|
|
1332
|
-
logTrace(tid, "DONE", { outcome: "claude_3rd" });
|
|
1333
|
-
const card = buildClaudeCard("thirdparty", []);
|
|
1334
|
-
await platform.sendRawCard(chatId, card);
|
|
1335
|
-
} catch (err) {
|
|
1336
|
-
logTrace(tid, "DONE", { outcome: "claude_3rd_fail", error: (err as Error).message });
|
|
1337
|
-
await platform.sendText(chatId, `切换失败: ${(err as Error).message}`).catch(() => {});
|
|
1338
|
-
}
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
const currentMode: "official" | "thirdparty" = CLAUDE_USE_THIRD_PARTY_API ? "thirdparty" : "official";
|
|
1343
|
-
const card = buildClaudeCard(currentMode, []);
|
|
1344
|
-
await platform.sendRawCard(chatId, card);
|
|
1345
|
-
logTrace(tid, "DONE", { outcome: "claude_query", mode: currentMode });
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
1215
|
// 无会话上下文 → 检查是否是 /model 查询
|
|
1350
1216
|
if (textLower === "/model") {
|
|
1351
1217
|
const defaultTool = resolveDefaultAgentTool();
|
package/src/session.ts
CHANGED
|
@@ -363,9 +363,9 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
|
|
|
363
363
|
model: effectiveModel,
|
|
364
364
|
subagentModel: CLAUDE_SUBAGENT_MODEL,
|
|
365
365
|
effort: CLAUDE_EFFORT,
|
|
366
|
-
isEmpty: isAnthropicConfigEmpty,
|
|
367
366
|
apiKey: CLAUDE_API_KEY,
|
|
368
367
|
baseUrl: CLAUDE_BASE_URL,
|
|
368
|
+
isEmpty: isAnthropicConfigEmpty,
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
371
|
adapterCache.set(cacheKey, adapter);
|