kc-beta 0.1.2 → 0.3.0

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 (55) hide show
  1. package/bin/kc-beta.js +14 -2
  2. package/package.json +1 -1
  3. package/src/agent/context-window.js +151 -0
  4. package/src/agent/context.js +8 -4
  5. package/src/agent/engine.js +261 -8
  6. package/src/agent/event-log.js +111 -0
  7. package/src/agent/llm-client.js +352 -59
  8. package/src/agent/pipelines/base.js +6 -0
  9. package/src/agent/pipelines/distillation.js +18 -0
  10. package/src/agent/pipelines/extraction.js +21 -0
  11. package/src/agent/pipelines/initializer.js +75 -14
  12. package/src/agent/pipelines/production-qc.js +19 -0
  13. package/src/agent/pipelines/skill-authoring.js +14 -0
  14. package/src/agent/pipelines/skill-testing.js +20 -0
  15. package/src/agent/retry.js +83 -0
  16. package/src/agent/session-state.js +79 -0
  17. package/src/agent/skill-loader.js +13 -1
  18. package/src/agent/token-counter.js +62 -0
  19. package/src/agent/tools/document-parse.js +104 -21
  20. package/src/agent/tools/document-search.js +24 -8
  21. package/src/agent/tools/sandbox-exec.js +16 -5
  22. package/src/agent/tools/web-search.js +107 -0
  23. package/src/agent/tools/worker-llm-call.js +14 -5
  24. package/src/agent/tools/workspace-file.js +47 -20
  25. package/src/agent/workspace.js +24 -1
  26. package/src/cli/components.js +24 -5
  27. package/src/cli/config.js +340 -0
  28. package/src/cli/index.js +113 -11
  29. package/src/cli/onboard.js +216 -53
  30. package/src/config.js +63 -10
  31. package/src/model-tiers.json +153 -0
  32. package/src/providers.js +367 -0
  33. package/template/AGENT.md +20 -0
  34. package/template/skills/en/meta/compliance-judgment/SKILL.md +10 -42
  35. package/template/skills/en/meta/document-chunking/SKILL.md +32 -0
  36. package/template/skills/en/meta/document-parsing/SKILL.md +11 -18
  37. package/template/skills/en/meta/entity-extraction/SKILL.md +13 -28
  38. package/template/skills/en/meta/tree-processing/SKILL.md +19 -1
  39. package/template/skills/en/meta-meta/auto-model-selection/SKILL.md +53 -0
  40. package/template/skills/en/meta-meta/pdf-review-dashboard/SKILL.md +57 -0
  41. package/template/skills/en/meta-meta/pdf-review-dashboard/scripts/generate_review.js +262 -0
  42. package/template/skills/en/meta-meta/rule-extraction/SKILL.md +24 -1
  43. package/template/skills/en/meta-meta/skill-authoring/SKILL.md +6 -0
  44. package/template/skills/en/meta-meta/skill-to-workflow/SKILL.md +4 -0
  45. package/template/skills/zh/meta/compliance-judgment/SKILL.md +41 -262
  46. package/template/skills/zh/meta/document-chunking/SKILL.md +32 -0
  47. package/template/skills/zh/meta/document-parsing/SKILL.md +65 -132
  48. package/template/skills/zh/meta/entity-extraction/SKILL.md +68 -230
  49. package/template/skills/zh/meta/tree-processing/SKILL.md +82 -194
  50. package/template/skills/zh/meta-meta/auto-model-selection/SKILL.md +51 -0
  51. package/template/skills/zh/meta-meta/pdf-review-dashboard/SKILL.md +55 -0
  52. package/template/skills/zh/meta-meta/pdf-review-dashboard/scripts/generate_review.js +262 -0
  53. package/template/skills/zh/meta-meta/rule-extraction/SKILL.md +79 -164
  54. package/template/skills/zh/meta-meta/skill-authoring/SKILL.md +64 -185
  55. package/template/skills/zh/meta-meta/skill-to-workflow/SKILL.md +95 -216
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import readline from "node:readline";
4
4
  import os from "node:os";
5
+ import { getProviders, getProviderById, getProviderLabels, classifyModels, getCuratedModels } from "../providers.js";
6
+ import { LLMClient } from "../agent/llm-client.js";
5
7
 
6
8
  const CONFIG_DIR = path.join(os.homedir(), ".kc_agent");
7
9
  const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
@@ -23,27 +25,33 @@ const L = {
23
25
  langPrompt: "Language",
24
26
  langOptions: ["English", "中文"],
25
27
  providerPrompt: "LLM Provider",
26
- providerLabels: [
27
- "SiliconFlow (recommended for China)",
28
- "Aliyun Bailian",
29
- "Anthropic",
30
- "OpenAI",
31
- "Custom (enter base URL)",
32
- ],
33
28
  current: "current",
34
29
  choose: "Choose",
35
30
  baseUrl: "Base URL",
36
31
  baseUrlRequired: "Base URL is required for custom provider.",
37
32
  apiKey: "API Key",
38
33
  apiKeyRequired: "required",
39
- apiKeyKeep: "Enter to keep",
34
+ apiKeyKeep: "Press Enter to keep",
40
35
  apiKeyMissing: "API key is required. Run 'kc-beta onboard' again.",
36
+ keyType: "Key Type",
37
+ keyTypeOptions: ["API Key (pay-per-use)", "Coding Plan Key (subscription)"],
41
38
  conductorModel: "Conductor Model",
42
39
  workerTiers: "Worker LLM Tiers",
43
- tierHint: "Enter to accept defaults",
40
+ vlmTiers: "VLM Tiers (Vision/OCR)",
41
+ tierHint: "Press Enter to accept defaults",
42
+ workerConfig: "Worker LLM Provider",
43
+ workerSameProvider: "Use same provider for worker LLMs?",
44
+ yesNo: "Y/n",
44
45
  accuracy: "Accuracy Threshold",
45
46
  saved: "Saved to",
46
47
  runHint: "Run {cmd} to start the agent.",
48
+ discovering: "Discovering available models...",
49
+ discoveryFailed: "Could not auto-discover models. Using provider defaults.",
50
+ discoveryFound: "Found {n} models. Suggested tier assignments:",
51
+ discoveryAccept: "Press Enter to accept, or type model name to override",
52
+ enterSkip: "Press Enter to skip",
53
+ enterDefault: "Press Enter to use default",
54
+ bedrockWarn: "AWS Bedrock is not yet fully supported. Authentication will fail at runtime.",
47
55
  },
48
56
  zh: {
49
57
  title: "KC Agent 配置向导",
@@ -51,13 +59,6 @@ const L = {
51
59
  langPrompt: "语言",
52
60
  langOptions: ["English", "中文"],
53
61
  providerPrompt: "大模型服务商",
54
- providerLabels: [
55
- "SiliconFlow(国内推荐)",
56
- "阿里云百炼",
57
- "Anthropic",
58
- "OpenAI",
59
- "自定义(输入接口地址)",
60
- ],
61
62
  current: "当前",
62
63
  choose: "选择",
63
64
  baseUrl: "接口地址",
@@ -66,31 +67,37 @@ const L = {
66
67
  apiKeyRequired: "必填",
67
68
  apiKeyKeep: "回车保留当前密钥",
68
69
  apiKeyMissing: "API 密钥为必填项。请重新运行 'kc-beta onboard'。",
70
+ keyType: "密钥类型",
71
+ keyTypeOptions: ["API Key(按量付费)", "Coding Plan Key(包年包月)"],
69
72
  conductorModel: "主模型",
70
73
  workerTiers: "Worker 模型分层",
74
+ vlmTiers: "VLM 视觉模型分层(OCR)",
71
75
  tierHint: "回车接受默认值",
76
+ workerConfig: "Worker LLM 服务商",
77
+ workerSameProvider: "Worker LLM 使用同一服务商?",
78
+ yesNo: "Y/n",
72
79
  accuracy: "准确率阈值",
73
80
  saved: "已保存至",
74
81
  runHint: "运行 {cmd} 启动 Agent。",
82
+ discovering: "正在发现可用模型...",
83
+ discoveryFailed: "无法自动发现模型,使用默认配置。",
84
+ discoveryFound: "发现 {n} 个模型。建议分层:",
85
+ discoveryAccept: "回车接受,或输入模型名称覆盖",
86
+ enterSkip: "回车跳过",
87
+ enterDefault: "回车使用默认值",
88
+ bedrockWarn: "AWS Bedrock 尚未完全支持。运行时认证将失败。",
75
89
  },
76
90
  };
77
91
 
78
- const PROVIDERS = [
79
- { name: "SiliconFlow", base_url: "https://api.siliconflow.cn/v1", model: "Pro/zai-org/GLM-5",
80
- tiers: { tier1: "Pro/zai-org/GLM-5, Pro/moonshotai/Kimi-K2.5", tier2: "Pro/deepseek-ai/DeepSeek-V3.2, Pro/MiniMaxAI/MiniMax-M2.5", tier3: "Qwen/Qwen3.5-122B-A10B", tier4: "Qwen/Qwen3.5-35B-A3B" } },
81
- { name: "Aliyun", base_url: "https://coding.dashscope.aliyuncs.com/v1", model: "glm-5",
82
- tiers: { tier1: "glm-5", tier2: "deepseek-v3", tier3: "qwen-plus", tier4: "qwen-turbo" } },
83
- { name: "Anthropic", base_url: "https://api.anthropic.com/v1", model: "claude-sonnet-4-20250514",
84
- tiers: { tier1: "claude-sonnet-4-20250514", tier2: "claude-sonnet-4-20250514", tier3: "claude-haiku-4-5-20251001", tier4: "claude-haiku-4-5-20251001" } },
85
- { name: "OpenAI", base_url: "https://api.openai.com/v1", model: "gpt-4o",
86
- tiers: { tier1: "gpt-4o", tier2: "gpt-4o-mini", tier3: "gpt-4o-mini", tier4: "gpt-4o-mini" } },
87
- { name: "Custom", base_url: "", model: "", tiers: { tier1: "", tier2: "", tier3: "", tier4: "" } },
88
- ];
89
-
90
- function ask(rl, question, defaultValue = "") {
92
+ function ask(rl, question, defaultValue = "", hint = "") {
91
93
  const suffix = defaultValue ? ` ${DIM}[${defaultValue}]${RESET}` : "";
94
+ const hintText = hint
95
+ ? ` ${GRAY}(${hint})${RESET}`
96
+ : defaultValue
97
+ ? ` ${GRAY}(Press Enter to keep)${RESET}`
98
+ : "";
92
99
  return new Promise((resolve) => {
93
- rl.question(`${question}${suffix}: `, (answer) => resolve(answer.trim() || defaultValue));
100
+ rl.question(`${question}${suffix}${hintText}: `, (answer) => resolve(answer.trim() || defaultValue));
94
101
  });
95
102
  }
96
103
 
@@ -101,11 +108,14 @@ export async function onboard() {
101
108
  if (fs.existsSync(CONFIG_PATH)) {
102
109
  try { existing = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); } catch { /* ignore */ }
103
110
  }
111
+ const isUpdate = Object.keys(existing).length > 0;
104
112
 
105
113
  console.log();
106
114
  console.log(` ${BOLD}KC Agent Setup / KC Agent 配置向导${RESET}`);
107
115
  console.log(` ${GRAY}${"─".repeat(40)}${RESET}`);
108
116
  console.log();
117
+
118
+ // --- Language ---
109
119
  console.log(` ${CYAN}Language / 语言:${RESET}`);
110
120
  console.log(` 1. English`);
111
121
  console.log(` 2. 中文`);
@@ -115,69 +125,218 @@ export async function onboard() {
115
125
  const t = L[lang];
116
126
  console.log();
117
127
 
118
- if (Object.keys(existing).length > 0) {
128
+ if (isUpdate) {
119
129
  console.log(` ${DIM}${t.existingConfig}${RESET}`);
120
130
  console.log();
121
131
  }
122
132
 
133
+ // --- Provider ---
134
+ const providers = getProviders();
135
+ const labels = getProviderLabels(lang);
123
136
  console.log(` ${CYAN}${t.providerPrompt}:${RESET}`);
124
- for (let i = 0; i < PROVIDERS.length; i++) {
125
- const marker = PROVIDERS[i].name.toLowerCase() === existing.provider ? ` ${GREEN}(${t.current})${RESET}` : "";
126
- console.log(` ${i + 1}. ${t.providerLabels[i]}${marker}`);
137
+ for (let i = 0; i < labels.length; i++) {
138
+ const marker = providers[i].id === existing.provider ? ` ${GREEN}(${t.current})${RESET}` : "";
139
+ console.log(` ${i + 1}. ${labels[i].label}${marker}`);
127
140
  }
128
141
  const providerIdx = parseInt(await ask(rl, ` ${GRAY}>${RESET} ${t.choose}`, "1"), 10) - 1;
129
- const provider = PROVIDERS[Math.max(0, Math.min(providerIdx, PROVIDERS.length - 1))];
142
+ const provider = providers[Math.max(0, Math.min(providerIdx, providers.length - 1))];
130
143
  console.log();
131
144
 
132
- let baseUrl = provider.base_url;
133
- if (provider.name === "Custom") {
145
+ // Bedrock warning
146
+ if (provider.id === "bedrock") {
147
+ console.log(` ${YELLOW}⚠ ${t.bedrockWarn}${RESET}`);
148
+ console.log();
149
+ }
150
+
151
+ // --- Base URL ---
152
+ let baseUrl = provider.baseUrl;
153
+ if (provider.id === "custom") {
134
154
  baseUrl = await ask(rl, ` ${t.baseUrl}`, existing.base_url || "");
135
155
  if (!baseUrl) { console.log(` ${RED}${t.baseUrlRequired}${RESET}`); rl.close(); process.exit(1); }
156
+ console.log();
136
157
  }
137
158
 
159
+ // --- Aliyun coding plan key sub-option ---
160
+ let useCodingPlan = false;
161
+ if (provider.supportsCodingPlanKey) {
162
+ console.log(` ${CYAN}${t.keyType}:${RESET}`);
163
+ console.log(` 1. ${t.keyTypeOptions[0]}`);
164
+ console.log(` 2. ${t.keyTypeOptions[1]}`);
165
+ const keyTypeChoice = await ask(rl, ` ${GRAY}>${RESET} ${t.choose}`, "1");
166
+ useCodingPlan = keyTypeChoice === "2";
167
+ if (useCodingPlan && provider.codingPlanUrl) {
168
+ baseUrl = provider.codingPlanUrl;
169
+ }
170
+ console.log();
171
+ }
172
+
173
+ // --- API Key ---
138
174
  const maskedExisting = existing.api_key ? existing.api_key.slice(0, 6) + "..." + existing.api_key.slice(-4) : "";
175
+ const keyHint = maskedExisting ? t.apiKeyKeep : t.apiKeyRequired;
139
176
  const keyPrompt = maskedExisting
140
- ? ` ${CYAN}${t.apiKey}${RESET} ${DIM}(${maskedExisting}, ${t.apiKeyKeep})${RESET}`
177
+ ? ` ${CYAN}${t.apiKey}${RESET} ${DIM}(${maskedExisting})${RESET}`
141
178
  : ` ${CYAN}${t.apiKey}${RESET} ${YELLOW}(${t.apiKeyRequired})${RESET}`;
142
- const apiKey = await ask(rl, keyPrompt, "");
179
+ const apiKey = await ask(rl, keyPrompt, "", keyHint);
143
180
  const finalKey = apiKey || existing.api_key || "";
144
181
  if (!finalKey) { console.log(` ${RED}${t.apiKeyMissing}${RESET}`); rl.close(); process.exit(1); }
145
182
  console.log();
146
183
 
147
- const defaultModel = provider.model || existing.conductor_model || "";
148
- const model = await ask(rl, ` ${CYAN}${t.conductorModel}${RESET}`, defaultModel);
184
+ // --- Auto-discovery ---
185
+ let discoveredModels = null;
186
+ let suggestedTiers = null;
187
+ let suggestedConductor = null;
188
+
189
+ // Try curated models first (for providers without /models endpoint)
190
+ const curated = getCuratedModels(provider.id);
191
+
192
+ if (curated) {
193
+ // Use curated model list
194
+ discoveredModels = curated;
195
+ const classified = classifyModels(curated);
196
+ suggestedTiers = classified.tiers;
197
+ suggestedConductor = classified.conductor;
198
+ console.log(` ${GREEN}✓${RESET} ${t.discoveryFound.replace("{n}", curated.length)}`);
199
+ if (suggestedConductor) {
200
+ console.log(` ${DIM}Conductor: ${suggestedConductor}${RESET}`);
201
+ }
202
+ for (const [tier, models] of Object.entries(suggestedTiers)) {
203
+ if (models) console.log(` ${DIM}${tier.toUpperCase()}: ${models}${RESET}`);
204
+ }
205
+ console.log();
206
+ } else if (provider.modelsEndpoint) {
207
+ // Query /models endpoint
208
+ console.log(` ${DIM}${t.discovering}${RESET}`);
209
+ try {
210
+ const tempClient = new LLMClient({
211
+ apiKey: finalKey,
212
+ baseUrl: baseUrl,
213
+ authType: provider.authType,
214
+ apiFormat: provider.apiFormat,
215
+ });
216
+ discoveredModels = await tempClient.listModels();
217
+
218
+ if (discoveredModels && discoveredModels.length > 0) {
219
+ const classified = classifyModels(discoveredModels);
220
+ suggestedTiers = classified.tiers;
221
+ suggestedConductor = classified.conductor;
222
+ console.log(` ${GREEN}✓${RESET} ${t.discoveryFound.replace("{n}", discoveredModels.length)}`);
223
+ if (suggestedConductor) {
224
+ console.log(` ${DIM}Conductor: ${suggestedConductor}${RESET}`);
225
+ }
226
+ for (const [tier, models] of Object.entries(suggestedTiers)) {
227
+ if (models) console.log(` ${DIM}${tier.toUpperCase()}: ${models}${RESET}`);
228
+ }
229
+ } else {
230
+ console.log(` ${DIM}${t.discoveryFailed}${RESET}`);
231
+ }
232
+ } catch {
233
+ console.log(` ${DIM}${t.discoveryFailed}${RESET}`);
234
+ }
235
+ console.log();
236
+ }
237
+
238
+ // --- Conductor model ---
239
+ const defaultModel = suggestedConductor || provider.defaultModel || existing.conductor_model || "";
240
+ const model = await ask(
241
+ rl,
242
+ ` ${CYAN}${t.conductorModel}${RESET}`,
243
+ defaultModel,
244
+ isUpdate ? t.enterDefault : "",
245
+ );
149
246
  console.log();
150
247
 
248
+ // --- Worker LLM tiers ---
151
249
  console.log(` ${CYAN}${t.workerTiers}${RESET} ${DIM}(${t.tierHint})${RESET}`);
152
250
  const tiers = {};
153
251
  for (const tier of ["tier1", "tier2", "tier3", "tier4"]) {
154
- const def = provider.tiers[tier] || existing?.tiers?.[tier] || "";
155
- tiers[tier] = await ask(rl, ` ${tier.toUpperCase()}`, def);
252
+ const def = suggestedTiers?.[tier] || provider.defaultTiers[tier] || existing?.tiers?.[tier] || "";
253
+ tiers[tier] = await ask(
254
+ rl,
255
+ ` ${tier.toUpperCase()}`,
256
+ def,
257
+ t.discoveryAccept ? "" : "",
258
+ );
156
259
  }
157
260
  console.log();
158
261
 
159
- const defaultAcc = existing.accuracy_threshold?.toString() || "0.9";
160
- const accuracy = parseFloat(await ask(rl, ` ${CYAN}${t.accuracy}${RESET}`, defaultAcc));
262
+ // --- VLM tiers (vision/OCR) ---
263
+ console.log(` ${CYAN}${t.vlmTiers}${RESET} ${DIM}(${t.tierHint})${RESET}`);
264
+ const vlmTiers = {};
265
+ for (const tier of ["tier1", "tier2", "tier3"]) {
266
+ const def = provider.defaultVlm?.[tier] || existing?.vlm_tiers?.[tier] || "";
267
+ vlmTiers[tier] = await ask(
268
+ rl,
269
+ ` ${tier.toUpperCase()}`,
270
+ def,
271
+ );
272
+ }
161
273
  console.log();
162
274
 
163
- // Advanced thresholds (Enter to keep defaults)
164
- const advLabel = lang === "zh" ? "高级阈值" : "Advanced Thresholds";
165
- const skipHint = lang === "zh" ? "回车使用默认值" : "Enter to keep defaults";
166
- console.log(` ${CYAN}${advLabel}${RESET} ${DIM}(${skipHint})${RESET}`);
167
- const systemicThreshold = parseFloat(await ask(rl, ` ${lang === "zh" ? "系统性问题阈值" : "Systemic threshold"}`, existing.systemic_threshold?.toString() || "0.10"));
168
- const spotCheckRate = parseFloat(await ask(rl, ` ${lang === "zh" ? "抽查比率" : "Spot-check rate"}`, existing.spot_check_rate?.toString() || "0.10"));
169
- const tierTolerance = parseFloat(await ask(rl, ` ${lang === "zh" ? "降级容差" : "Tier downgrade tolerance"}`, existing.tier_tolerance?.toString() || "0.05"));
275
+ // --- Worker LLM provider (optional) ---
276
+ console.log(` ${CYAN}${t.workerConfig}${RESET}`);
277
+ const sameProvider = await ask(rl, ` ${t.workerSameProvider}`, "Y", t.yesNo);
278
+ let workerProvider = "";
279
+ let workerApiKey = "";
280
+ let workerBaseUrl = "";
281
+ let workerAuthType = "";
282
+ let workerApiFormat = "";
283
+
284
+ if (sameProvider.toLowerCase() === "n" || sameProvider.toLowerCase() === "no") {
285
+ // Pick a different provider for workers
286
+ console.log();
287
+ console.log(` ${CYAN}${t.providerPrompt} (Worker):${RESET}`);
288
+ for (let i = 0; i < labels.length; i++) {
289
+ console.log(` ${i + 1}. ${labels[i].label}`);
290
+ }
291
+ const wIdx = parseInt(await ask(rl, ` ${GRAY}>${RESET} ${t.choose}`, "1"), 10) - 1;
292
+ const wp = providers[Math.max(0, Math.min(wIdx, providers.length - 1))];
293
+ workerProvider = wp.id;
294
+ workerAuthType = wp.authType;
295
+ workerApiFormat = wp.apiFormat;
296
+ workerBaseUrl = wp.baseUrl;
297
+
298
+ if (wp.id === "custom") {
299
+ workerBaseUrl = await ask(rl, ` ${t.baseUrl}`, existing.worker_base_url || "");
300
+ }
301
+
302
+ // Worker API key
303
+ const wMasked = existing.worker_api_key ? existing.worker_api_key.slice(0, 6) + "..." + existing.worker_api_key.slice(-4) : "";
304
+ const wKeyHint = wMasked ? t.apiKeyKeep : t.apiKeyRequired;
305
+ workerApiKey = await ask(
306
+ rl,
307
+ ` ${CYAN}${t.apiKey} (Worker)${RESET}`,
308
+ "",
309
+ wKeyHint,
310
+ );
311
+ workerApiKey = workerApiKey || existing.worker_api_key || "";
312
+ }
170
313
  console.log();
171
314
 
172
315
  rl.close();
173
316
 
317
+ // Preserve existing thresholds or set defaults (editable via 'kc-beta config')
318
+ const accuracy = existing.accuracy_threshold ?? 0.9;
319
+ const systemicThreshold = existing.systemic_threshold ?? 0.10;
320
+ const spotCheckRate = existing.spot_check_rate ?? 0.10;
321
+ const tierTolerance = existing.tier_tolerance ?? 0.05;
322
+
174
323
  const config = {
175
324
  language: lang,
176
- provider: provider.name.toLowerCase(),
325
+ provider: provider.id,
177
326
  api_key: finalKey,
178
327
  base_url: baseUrl,
328
+ auth_type: provider.authType,
329
+ api_format: provider.apiFormat,
179
330
  conductor_model: model,
180
331
  tiers,
332
+ vlm_tiers: vlmTiers,
333
+ // Worker LLM (optional — empty means use conductor config)
334
+ worker_provider: workerProvider,
335
+ worker_api_key: workerApiKey,
336
+ worker_base_url: workerBaseUrl,
337
+ worker_auth_type: workerAuthType,
338
+ worker_api_format: workerApiFormat,
339
+ // Thresholds
181
340
  accuracy_threshold: accuracy,
182
341
  systemic_threshold: systemicThreshold,
183
342
  spot_check_rate: spotCheckRate,
@@ -190,5 +349,9 @@ export async function onboard() {
190
349
  console.log(` ${GREEN}✓${RESET} ${t.saved} ${GRAY}${CONFIG_PATH}${RESET}`);
191
350
  console.log();
192
351
  console.log(` ${t.runHint.replace("{cmd}", `${BOLD}kc-beta${RESET}`)}`);
352
+ const configHint = lang === "zh"
353
+ ? ` ${DIM}运行 ${BOLD}kc-beta config${RESET}${DIM} 调整阈值和高级设置。${RESET}`
354
+ : ` ${DIM}Run ${BOLD}kc-beta config${RESET}${DIM} to adjust thresholds and advanced settings.${RESET}`;
355
+ console.log(configHint);
193
356
  console.log();
194
357
  }
package/src/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
+ import { getProviderById } from "./providers.js";
4
5
 
5
6
  const GLOBAL_CONFIG_DIR = path.join(os.homedir(), ".kc_agent");
6
7
  const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, "config.json");
@@ -43,31 +44,46 @@ function loadEnvFile(envPath) {
43
44
 
44
45
  /**
45
46
  * Load settings by merging: global config (lowest) -> workspace .env (highest).
47
+ * Supports both new generic keys (LLM_API_KEY) and legacy keys (SILICONFLOW_API_KEY).
46
48
  * @param {string} [workspacePath] - Optional workspace directory for .env override
47
49
  */
48
50
  export function loadSettings(workspacePath) {
49
51
  const gc = loadGlobalConfig();
50
52
  const env = workspacePath ? loadEnvFile(path.join(workspacePath, ".env")) : {};
51
53
 
52
- return {
53
- // Conductor LLM
54
- llmApiKey: env.SILICONFLOW_API_KEY || gc.api_key || "",
55
- llmBaseUrl: env.SILICONFLOW_BASE_URL || gc.base_url || "https://api.siliconflow.cn/v1",
54
+ // Resolve provider metadata for authType/apiFormat defaults
55
+ const provider = gc.provider || "siliconflow";
56
+ const providerDef = getProviderById(provider);
57
+
58
+ const settings = {
59
+ // Provider identity
60
+ provider,
61
+ authType: gc.auth_type || providerDef?.authType || "bearer",
62
+ apiFormat: gc.api_format || providerDef?.apiFormat || "openai",
63
+
64
+ // Conductor LLM (generic keys with legacy fallback)
65
+ llmApiKey: env.LLM_API_KEY || env.SILICONFLOW_API_KEY || gc.api_key || "",
66
+ llmBaseUrl: env.LLM_BASE_URL || env.SILICONFLOW_BASE_URL || gc.base_url || "https://api.siliconflow.cn/v1",
56
67
  kcModel: gc.conductor_model || "glm-5",
57
68
  kcMaxTokens: 65536,
58
69
 
59
- // Worker LLMs (SiliconFlow)
60
- siliconflowApiKey: env.SILICONFLOW_API_KEY || gc.api_key || "",
61
- siliconflowBaseUrl: env.SILICONFLOW_BASE_URL || gc.base_url || "https://api.siliconflow.cn/v1",
62
-
63
70
  // Tier models (from .env or global config tiers)
64
71
  tier1: env.TIER1 || gc.tiers?.tier1 || "",
65
72
  tier2: env.TIER2 || gc.tiers?.tier2 || "",
66
73
  tier3: env.TIER3 || gc.tiers?.tier3 || "",
67
74
  tier4: env.TIER4 || gc.tiers?.tier4 || "",
68
75
 
69
- // OCR models
70
- ocrModelTier1: env.OCR_MODEL_TIER1 || "zai-org/GLM-4.6V",
76
+ // VLM tiers (vision/OCR models)
77
+ vlmTier1: env.VLM_TIER1 || gc.vlm_tiers?.tier1 || "",
78
+ vlmTier2: env.VLM_TIER2 || gc.vlm_tiers?.tier2 || "",
79
+ vlmTier3: env.VLM_TIER3 || gc.vlm_tiers?.tier3 || "",
80
+
81
+ // Worker LLM — optional, defaults to conductor config
82
+ workerProvider: gc.worker_provider || "",
83
+ workerApiKey: env.WORKER_API_KEY || gc.worker_api_key || "",
84
+ workerBaseUrl: env.WORKER_BASE_URL || gc.worker_base_url || "",
85
+ workerAuthType: gc.worker_auth_type || "",
86
+ workerApiFormat: gc.worker_api_format || "",
71
87
 
72
88
  // Document parsing
73
89
  mineruApiUrl: env.MINERU_API_URL || "",
@@ -90,9 +106,46 @@ export function loadSettings(workspacePath) {
90
106
  maxIterations: parseInt(env.MAX_ITERATIONS || "20", 10),
91
107
  monitorFrequency: env.MONITOR_FREQUENCY || "mid",
92
108
 
109
+ // Web search
110
+ tavilyApiKey: env.TAVILY_API_KEY || gc.tavily_api_key || "",
111
+
112
+ // Context management
113
+ kcContextLimit: parseInt(env.KC_CONTEXT_LIMIT || "200000", 10),
114
+
93
115
  // Language
94
116
  language: env.LANGUAGE || gc.language || "en",
95
117
  };
118
+
119
+ // Effective worker config (falls back to conductor config)
120
+ settings.effectiveWorkerProvider = () => settings.workerProvider || settings.provider;
121
+ settings.effectiveWorkerApiKey = () => settings.workerApiKey || settings.llmApiKey;
122
+ settings.effectiveWorkerBaseUrl = () => {
123
+ if (settings.workerBaseUrl) return settings.workerBaseUrl;
124
+ // If worker uses a different provider, use that provider's default base URL
125
+ if (settings.workerProvider && settings.workerProvider !== settings.provider) {
126
+ const wp = getProviderById(settings.workerProvider);
127
+ return wp?.baseUrl || settings.llmBaseUrl;
128
+ }
129
+ return settings.llmBaseUrl;
130
+ };
131
+ settings.effectiveWorkerAuthType = () => {
132
+ if (settings.workerAuthType) return settings.workerAuthType;
133
+ if (settings.workerProvider && settings.workerProvider !== settings.provider) {
134
+ const wp = getProviderById(settings.workerProvider);
135
+ return wp?.authType || settings.authType;
136
+ }
137
+ return settings.authType;
138
+ };
139
+ settings.effectiveWorkerApiFormat = () => {
140
+ if (settings.workerApiFormat) return settings.workerApiFormat;
141
+ if (settings.workerProvider && settings.workerProvider !== settings.provider) {
142
+ const wp = getProviderById(settings.workerProvider);
143
+ return wp?.apiFormat || settings.apiFormat;
144
+ }
145
+ return settings.apiFormat;
146
+ };
147
+
148
+ return settings;
96
149
  }
97
150
 
98
151
  export { GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_PATH };
@@ -0,0 +1,153 @@
1
+ {
2
+ "_comment": "Model selections per provider. LLM tiers 1-4, VLM tiers 1-3. Edit this file directly to update model assignments.",
3
+
4
+ "siliconflow": {
5
+ "conductor": "Pro/zai-org/GLM-5",
6
+ "llm": {
7
+ "tier1": "Pro/zai-org/GLM-5, Pro/moonshotai/Kimi-K2.5",
8
+ "tier2": "Pro/deepseek-ai/DeepSeek-V3.2, Pro/MiniMaxAI/MiniMax-M2.5",
9
+ "tier3": "Qwen/Qwen3.5-122B-A10B",
10
+ "tier4": "Qwen/Qwen3.5-35B-A3B"
11
+ },
12
+ "vlm": {
13
+ "tier1": "Pro/Qwen/Qwen2.5-VL-72B-Instruct",
14
+ "tier2": "Qwen/Qwen2.5-VL-32B-Instruct",
15
+ "tier3": "Qwen/Qwen2.5-VL-7B-Instruct"
16
+ }
17
+ },
18
+
19
+ "aliyun": {
20
+ "conductor": "qwen3.6-plus",
21
+ "llm": {
22
+ "tier1": "qwen3.6-plus",
23
+ "tier2": "",
24
+ "tier3": "",
25
+ "tier4": ""
26
+ },
27
+ "vlm": {
28
+ "tier1": "qwen-vl-max",
29
+ "tier2": "qwen-vl-plus",
30
+ "tier3": ""
31
+ }
32
+ },
33
+
34
+ "volcanocloud": {
35
+ "conductor": "doubao-seed-2-0-pro-260215",
36
+ "llm": {
37
+ "tier1": "doubao-seed-2-0-pro-260215, deepseek-v3-2-251201",
38
+ "tier2": "glm-4-7-251222, doubao-1-5-pro-32k-250115",
39
+ "tier3": "doubao-seed-2-0-mini-260215",
40
+ "tier4": "doubao-seed-2-0-lite-260215, doubao-1-5-lite-32k-250115"
41
+ },
42
+ "vlm": {
43
+ "tier1": "doubao-vision-pro-32k-241028",
44
+ "tier2": "doubao-vision-lite-32k-241028",
45
+ "tier3": ""
46
+ }
47
+ },
48
+
49
+ "anthropic": {
50
+ "conductor": "claude-sonnet-4-20250514",
51
+ "llm": {
52
+ "tier1": "claude-sonnet-4-20250514",
53
+ "tier2": "claude-sonnet-4-20250514",
54
+ "tier3": "claude-haiku-4-5-20251001",
55
+ "tier4": "claude-haiku-4-5-20251001"
56
+ },
57
+ "vlm": {
58
+ "tier1": "claude-sonnet-4-20250514",
59
+ "tier2": "claude-haiku-4-5-20251001",
60
+ "tier3": "claude-haiku-4-5-20251001"
61
+ }
62
+ },
63
+
64
+ "openai": {
65
+ "conductor": "gpt-4o",
66
+ "llm": {
67
+ "tier1": "gpt-4o",
68
+ "tier2": "gpt-4o-mini",
69
+ "tier3": "gpt-4o-mini",
70
+ "tier4": "gpt-4o-mini"
71
+ },
72
+ "vlm": {
73
+ "tier1": "gpt-4o",
74
+ "tier2": "gpt-4o-mini",
75
+ "tier3": "gpt-4o-mini"
76
+ }
77
+ },
78
+
79
+ "zhipu": {
80
+ "conductor": "glm-4-plus",
81
+ "llm": {
82
+ "tier1": "glm-4-plus",
83
+ "tier2": "glm-4-air",
84
+ "tier3": "glm-4-flash",
85
+ "tier4": "glm-4-flash"
86
+ },
87
+ "vlm": {
88
+ "tier1": "glm-4v-plus",
89
+ "tier2": "glm-4v",
90
+ "tier3": "glm-4v-flash"
91
+ }
92
+ },
93
+
94
+ "minimax": {
95
+ "conductor": "MiniMax-M2.5",
96
+ "llm": {
97
+ "tier1": "MiniMax-M2.5",
98
+ "tier2": "MiniMax-M2.5",
99
+ "tier3": "MiniMax-M1",
100
+ "tier4": "MiniMax-M1"
101
+ },
102
+ "vlm": {
103
+ "tier1": "",
104
+ "tier2": "",
105
+ "tier3": ""
106
+ }
107
+ },
108
+
109
+ "openrouter": {
110
+ "conductor": "anthropic/claude-sonnet-4-20250514",
111
+ "llm": {
112
+ "tier1": "anthropic/claude-sonnet-4-20250514",
113
+ "tier2": "google/gemini-2.5-flash",
114
+ "tier3": "google/gemini-2.5-flash",
115
+ "tier4": "google/gemini-2.5-flash"
116
+ },
117
+ "vlm": {
118
+ "tier1": "anthropic/claude-sonnet-4-20250514",
119
+ "tier2": "google/gemini-2.5-flash",
120
+ "tier3": "google/gemini-2.5-flash"
121
+ }
122
+ },
123
+
124
+ "bedrock": {
125
+ "conductor": "anthropic.claude-sonnet-4-20250514-v1:0",
126
+ "llm": {
127
+ "tier1": "anthropic.claude-sonnet-4-20250514-v1:0",
128
+ "tier2": "anthropic.claude-sonnet-4-20250514-v1:0",
129
+ "tier3": "anthropic.claude-haiku-4-5-20251001-v1:0",
130
+ "tier4": "anthropic.claude-haiku-4-5-20251001-v1:0"
131
+ },
132
+ "vlm": {
133
+ "tier1": "anthropic.claude-sonnet-4-20250514-v1:0",
134
+ "tier2": "anthropic.claude-haiku-4-5-20251001-v1:0",
135
+ "tier3": "anthropic.claude-haiku-4-5-20251001-v1:0"
136
+ }
137
+ },
138
+
139
+ "custom": {
140
+ "conductor": "",
141
+ "llm": {
142
+ "tier1": "",
143
+ "tier2": "",
144
+ "tier3": "",
145
+ "tier4": ""
146
+ },
147
+ "vlm": {
148
+ "tier1": "",
149
+ "tier2": "",
150
+ "tier3": ""
151
+ }
152
+ }
153
+ }