kc-beta 0.1.1 → 0.2.1

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 (34) 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 +58 -88
  5. package/src/agent/engine.js +267 -38
  6. package/src/agent/event-log.js +111 -0
  7. package/src/agent/llm-client.js +352 -59
  8. package/src/agent/pipelines/_archive_v1/distillation.js +113 -0
  9. package/src/agent/pipelines/_archive_v1/extraction.js +92 -0
  10. package/src/agent/pipelines/_archive_v1/initializer.js +163 -0
  11. package/src/agent/pipelines/_archive_v1/production-qc.js +99 -0
  12. package/src/agent/pipelines/_archive_v1/skill-authoring.js +83 -0
  13. package/src/agent/pipelines/_archive_v1/skill-testing.js +111 -0
  14. package/src/agent/pipelines/base.js +6 -0
  15. package/src/agent/pipelines/distillation.js +25 -11
  16. package/src/agent/pipelines/extraction.js +26 -7
  17. package/src/agent/pipelines/initializer.js +30 -20
  18. package/src/agent/pipelines/production-qc.js +22 -5
  19. package/src/agent/pipelines/skill-authoring.js +19 -8
  20. package/src/agent/pipelines/skill-testing.js +26 -8
  21. package/src/agent/retry.js +83 -0
  22. package/src/agent/session-state.js +78 -0
  23. package/src/agent/skill-loader.js +139 -0
  24. package/src/agent/token-counter.js +62 -0
  25. package/src/agent/tools/document-parse.js +3 -3
  26. package/src/agent/tools/tier-downgrade.js +11 -2
  27. package/src/agent/tools/web-search.js +107 -0
  28. package/src/agent/tools/worker-llm-call.js +14 -5
  29. package/src/cli/components.js +16 -4
  30. package/src/cli/config.js +246 -0
  31. package/src/cli/index.js +99 -10
  32. package/src/cli/onboard.js +154 -48
  33. package/src/config.js +25 -7
  34. package/src/providers.js +370 -0
@@ -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,29 @@ 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
+ tierHint: "Press Enter to accept defaults",
44
41
  accuracy: "Accuracy Threshold",
45
42
  saved: "Saved to",
46
43
  runHint: "Run {cmd} to start the agent.",
44
+ discovering: "Discovering available models...",
45
+ discoveryFailed: "Could not auto-discover models. Using provider defaults.",
46
+ discoveryFound: "Found {n} models. Suggested tier assignments:",
47
+ discoveryAccept: "Press Enter to accept, or type model name to override",
48
+ enterSkip: "Press Enter to skip",
49
+ enterDefault: "Press Enter to use default",
50
+ bedrockWarn: "AWS Bedrock is not yet fully supported. Authentication will fail at runtime.",
47
51
  },
48
52
  zh: {
49
53
  title: "KC Agent 配置向导",
@@ -51,13 +55,6 @@ const L = {
51
55
  langPrompt: "语言",
52
56
  langOptions: ["English", "中文"],
53
57
  providerPrompt: "大模型服务商",
54
- providerLabels: [
55
- "SiliconFlow(国内推荐)",
56
- "阿里云百炼",
57
- "Anthropic",
58
- "OpenAI",
59
- "自定义(输入接口地址)",
60
- ],
61
58
  current: "当前",
62
59
  choose: "选择",
63
60
  baseUrl: "接口地址",
@@ -66,31 +63,33 @@ const L = {
66
63
  apiKeyRequired: "必填",
67
64
  apiKeyKeep: "回车保留当前密钥",
68
65
  apiKeyMissing: "API 密钥为必填项。请重新运行 'kc-beta onboard'。",
66
+ keyType: "密钥类型",
67
+ keyTypeOptions: ["API Key(按量付费)", "Coding Plan Key(包年包月)"],
69
68
  conductorModel: "主模型",
70
69
  workerTiers: "Worker 模型分层",
71
70
  tierHint: "回车接受默认值",
72
71
  accuracy: "准确率阈值",
73
72
  saved: "已保存至",
74
73
  runHint: "运行 {cmd} 启动 Agent。",
74
+ discovering: "正在发现可用模型...",
75
+ discoveryFailed: "无法自动发现模型,使用默认配置。",
76
+ discoveryFound: "发现 {n} 个模型。建议分层:",
77
+ discoveryAccept: "回车接受,或输入模型名称覆盖",
78
+ enterSkip: "回车跳过",
79
+ enterDefault: "回车使用默认值",
80
+ bedrockWarn: "AWS Bedrock 尚未完全支持。运行时认证将失败。",
75
81
  },
76
82
  };
77
83
 
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 = "") {
84
+ function ask(rl, question, defaultValue = "", hint = "") {
91
85
  const suffix = defaultValue ? ` ${DIM}[${defaultValue}]${RESET}` : "";
86
+ const hintText = hint
87
+ ? ` ${GRAY}(${hint})${RESET}`
88
+ : defaultValue
89
+ ? ` ${GRAY}(Press Enter to keep)${RESET}`
90
+ : "";
92
91
  return new Promise((resolve) => {
93
- rl.question(`${question}${suffix}: `, (answer) => resolve(answer.trim() || defaultValue));
92
+ rl.question(`${question}${suffix}${hintText}: `, (answer) => resolve(answer.trim() || defaultValue));
94
93
  });
95
94
  }
96
95
 
@@ -101,11 +100,14 @@ export async function onboard() {
101
100
  if (fs.existsSync(CONFIG_PATH)) {
102
101
  try { existing = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); } catch { /* ignore */ }
103
102
  }
103
+ const isUpdate = Object.keys(existing).length > 0;
104
104
 
105
105
  console.log();
106
106
  console.log(` ${BOLD}KC Agent Setup / KC Agent 配置向导${RESET}`);
107
107
  console.log(` ${GRAY}${"─".repeat(40)}${RESET}`);
108
108
  console.log();
109
+
110
+ // --- Language ---
109
111
  console.log(` ${CYAN}Language / 语言:${RESET}`);
110
112
  console.log(` 1. English`);
111
113
  console.log(` 2. 中文`);
@@ -115,61 +117,161 @@ export async function onboard() {
115
117
  const t = L[lang];
116
118
  console.log();
117
119
 
118
- if (Object.keys(existing).length > 0) {
120
+ if (isUpdate) {
119
121
  console.log(` ${DIM}${t.existingConfig}${RESET}`);
120
122
  console.log();
121
123
  }
122
124
 
125
+ // --- Provider ---
126
+ const providers = getProviders();
127
+ const labels = getProviderLabels(lang);
123
128
  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}`);
129
+ for (let i = 0; i < labels.length; i++) {
130
+ const marker = providers[i].id === existing.provider ? ` ${GREEN}(${t.current})${RESET}` : "";
131
+ console.log(` ${i + 1}. ${labels[i].label}${marker}`);
127
132
  }
128
133
  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))];
134
+ const provider = providers[Math.max(0, Math.min(providerIdx, providers.length - 1))];
130
135
  console.log();
131
136
 
132
- let baseUrl = provider.base_url;
133
- if (provider.name === "Custom") {
137
+ // Bedrock warning
138
+ if (provider.id === "bedrock") {
139
+ console.log(` ${YELLOW}⚠ ${t.bedrockWarn}${RESET}`);
140
+ console.log();
141
+ }
142
+
143
+ // --- Base URL ---
144
+ let baseUrl = provider.baseUrl;
145
+ if (provider.id === "custom") {
134
146
  baseUrl = await ask(rl, ` ${t.baseUrl}`, existing.base_url || "");
135
147
  if (!baseUrl) { console.log(` ${RED}${t.baseUrlRequired}${RESET}`); rl.close(); process.exit(1); }
148
+ console.log();
136
149
  }
137
150
 
151
+ // --- Aliyun coding plan key sub-option ---
152
+ let useCodingPlan = false;
153
+ if (provider.supportsCodingPlanKey) {
154
+ console.log(` ${CYAN}${t.keyType}:${RESET}`);
155
+ console.log(` 1. ${t.keyTypeOptions[0]}`);
156
+ console.log(` 2. ${t.keyTypeOptions[1]}`);
157
+ const keyTypeChoice = await ask(rl, ` ${GRAY}>${RESET} ${t.choose}`, "1");
158
+ useCodingPlan = keyTypeChoice === "2";
159
+ if (useCodingPlan && provider.codingPlanUrl) {
160
+ baseUrl = provider.codingPlanUrl;
161
+ }
162
+ console.log();
163
+ }
164
+
165
+ // --- API Key ---
138
166
  const maskedExisting = existing.api_key ? existing.api_key.slice(0, 6) + "..." + existing.api_key.slice(-4) : "";
167
+ const keyHint = maskedExisting ? t.apiKeyKeep : t.apiKeyRequired;
139
168
  const keyPrompt = maskedExisting
140
- ? ` ${CYAN}${t.apiKey}${RESET} ${DIM}(${maskedExisting}, ${t.apiKeyKeep})${RESET}`
169
+ ? ` ${CYAN}${t.apiKey}${RESET} ${DIM}(${maskedExisting})${RESET}`
141
170
  : ` ${CYAN}${t.apiKey}${RESET} ${YELLOW}(${t.apiKeyRequired})${RESET}`;
142
- const apiKey = await ask(rl, keyPrompt, "");
171
+ const apiKey = await ask(rl, keyPrompt, "", keyHint);
143
172
  const finalKey = apiKey || existing.api_key || "";
144
173
  if (!finalKey) { console.log(` ${RED}${t.apiKeyMissing}${RESET}`); rl.close(); process.exit(1); }
145
174
  console.log();
146
175
 
147
- const defaultModel = provider.model || existing.conductor_model || "";
148
- const model = await ask(rl, ` ${CYAN}${t.conductorModel}${RESET}`, defaultModel);
176
+ // --- Auto-discovery ---
177
+ let discoveredModels = null;
178
+ let suggestedTiers = null;
179
+ let suggestedConductor = null;
180
+
181
+ // Try curated models first (for providers without /models endpoint)
182
+ const curated = getCuratedModels(provider.id);
183
+
184
+ if (curated) {
185
+ // Use curated model list
186
+ discoveredModels = curated;
187
+ const classified = classifyModels(curated);
188
+ suggestedTiers = classified.tiers;
189
+ suggestedConductor = classified.conductor;
190
+ console.log(` ${GREEN}✓${RESET} ${t.discoveryFound.replace("{n}", curated.length)}`);
191
+ if (suggestedConductor) {
192
+ console.log(` ${DIM}Conductor: ${suggestedConductor}${RESET}`);
193
+ }
194
+ for (const [tier, models] of Object.entries(suggestedTiers)) {
195
+ if (models) console.log(` ${DIM}${tier.toUpperCase()}: ${models}${RESET}`);
196
+ }
197
+ console.log();
198
+ } else if (provider.modelsEndpoint) {
199
+ // Query /models endpoint
200
+ console.log(` ${DIM}${t.discovering}${RESET}`);
201
+ try {
202
+ const tempClient = new LLMClient({
203
+ apiKey: finalKey,
204
+ baseUrl: baseUrl,
205
+ authType: provider.authType,
206
+ apiFormat: provider.apiFormat,
207
+ });
208
+ discoveredModels = await tempClient.listModels();
209
+
210
+ if (discoveredModels && discoveredModels.length > 0) {
211
+ const classified = classifyModels(discoveredModels);
212
+ suggestedTiers = classified.tiers;
213
+ suggestedConductor = classified.conductor;
214
+ console.log(` ${GREEN}✓${RESET} ${t.discoveryFound.replace("{n}", discoveredModels.length)}`);
215
+ if (suggestedConductor) {
216
+ console.log(` ${DIM}Conductor: ${suggestedConductor}${RESET}`);
217
+ }
218
+ for (const [tier, models] of Object.entries(suggestedTiers)) {
219
+ if (models) console.log(` ${DIM}${tier.toUpperCase()}: ${models}${RESET}`);
220
+ }
221
+ } else {
222
+ console.log(` ${DIM}${t.discoveryFailed}${RESET}`);
223
+ }
224
+ } catch {
225
+ console.log(` ${DIM}${t.discoveryFailed}${RESET}`);
226
+ }
227
+ console.log();
228
+ }
229
+
230
+ // --- Conductor model ---
231
+ const defaultModel = suggestedConductor || provider.defaultModel || existing.conductor_model || "";
232
+ const model = await ask(
233
+ rl,
234
+ ` ${CYAN}${t.conductorModel}${RESET}`,
235
+ defaultModel,
236
+ isUpdate ? t.enterDefault : "",
237
+ );
149
238
  console.log();
150
239
 
240
+ // --- Worker tiers ---
151
241
  console.log(` ${CYAN}${t.workerTiers}${RESET} ${DIM}(${t.tierHint})${RESET}`);
152
242
  const tiers = {};
153
243
  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);
244
+ const def = suggestedTiers?.[tier] || provider.defaultTiers[tier] || existing?.tiers?.[tier] || "";
245
+ tiers[tier] = await ask(
246
+ rl,
247
+ ` ${tier.toUpperCase()}`,
248
+ def,
249
+ t.discoveryAccept ? "" : "",
250
+ );
156
251
  }
157
252
  console.log();
158
253
 
159
- const defaultAcc = existing.accuracy_threshold?.toString() || "0.9";
160
- const accuracy = parseFloat(await ask(rl, ` ${CYAN}${t.accuracy}${RESET}`, defaultAcc));
161
- console.log();
162
-
163
254
  rl.close();
164
255
 
256
+ // Preserve existing thresholds or set defaults (editable via 'kc-beta config')
257
+ const accuracy = existing.accuracy_threshold ?? 0.9;
258
+ const systemicThreshold = existing.systemic_threshold ?? 0.10;
259
+ const spotCheckRate = existing.spot_check_rate ?? 0.10;
260
+ const tierTolerance = existing.tier_tolerance ?? 0.05;
261
+
165
262
  const config = {
166
263
  language: lang,
167
- provider: provider.name.toLowerCase(),
264
+ provider: provider.id,
168
265
  api_key: finalKey,
169
266
  base_url: baseUrl,
267
+ auth_type: provider.authType,
268
+ api_format: provider.apiFormat,
170
269
  conductor_model: model,
171
270
  tiers,
172
271
  accuracy_threshold: accuracy,
272
+ systemic_threshold: systemicThreshold,
273
+ spot_check_rate: spotCheckRate,
274
+ tier_tolerance: tierTolerance,
173
275
  };
174
276
 
175
277
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
@@ -178,5 +280,9 @@ export async function onboard() {
178
280
  console.log(` ${GREEN}✓${RESET} ${t.saved} ${GRAY}${CONFIG_PATH}${RESET}`);
179
281
  console.log();
180
282
  console.log(` ${t.runHint.replace("{cmd}", `${BOLD}kc-beta${RESET}`)}`);
283
+ const configHint = lang === "zh"
284
+ ? ` ${DIM}运行 ${BOLD}kc-beta config${RESET}${DIM} 调整阈值和高级设置。${RESET}`
285
+ : ` ${DIM}Run ${BOLD}kc-beta config${RESET}${DIM} to adjust thresholds and advanced settings.${RESET}`;
286
+ console.log(configHint);
181
287
  console.log();
182
288
  }
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,23 +44,29 @@ 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
 
54
+ // Resolve provider metadata for authType/apiFormat defaults
55
+ const provider = gc.provider || "siliconflow";
56
+ const providerDef = getProviderById(provider);
57
+
52
58
  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",
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 || "",
@@ -81,10 +88,21 @@ export function loadSettings(workspacePath) {
81
88
  skillAccuracy: parseFloat(env.SKILL_ACCURACY || gc.accuracy_threshold?.toString() || "0.9"),
82
89
  workflowAccuracy: parseFloat(env.WORKFLOW_ACCURACY || "0.9"),
83
90
 
91
+ // Advanced thresholds (from onboarding or .env)
92
+ systemicThreshold: parseFloat(env.SYSTEMIC_THRESHOLD || gc.systemic_threshold?.toString() || "0.10"),
93
+ spotCheckRate: parseFloat(env.SPOT_CHECK_RATE || gc.spot_check_rate?.toString() || "0.10"),
94
+ tierTolerance: parseFloat(env.TIER_TOLERANCE || gc.tier_tolerance?.toString() || "0.05"),
95
+
84
96
  // Evolution
85
97
  maxIterations: parseInt(env.MAX_ITERATIONS || "20", 10),
86
98
  monitorFrequency: env.MONITOR_FREQUENCY || "mid",
87
99
 
100
+ // Web search
101
+ tavilyApiKey: env.TAVILY_API_KEY || gc.tavily_api_key || "",
102
+
103
+ // Context management
104
+ kcContextLimit: parseInt(env.KC_CONTEXT_LIMIT || "200000", 10),
105
+
88
106
  // Language
89
107
  language: env.LANGUAGE || gc.language || "en",
90
108
  };