skyloom 1.14.8 → 1.15.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.
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +51 -4
- package/CONVERSION_PLAN.md +191 -191
- package/config/default.yaml +46 -43
- package/config/models.yaml +928 -155
- package/config/providers.yaml +109 -6
- package/dist/agents/snow.d.ts +2 -0
- package/dist/agents/snow.d.ts.map +1 -1
- package/dist/agents/snow.js +36 -5
- package/dist/agents/snow.js.map +1 -1
- package/dist/cli/loom_chat.d.ts.map +1 -1
- package/dist/cli/loom_chat.js +207 -1
- package/dist/cli/loom_chat.js.map +1 -1
- package/dist/cli/main.js +190 -40
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +6 -31
- package/dist/cli/tui.js.map +1 -1
- package/dist/core/agent.d.ts +6 -4
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +61 -20
- package/dist/core/agent.js.map +1 -1
- package/dist/core/catalog.d.ts.map +1 -1
- package/dist/core/catalog.js +30 -9
- package/dist/core/catalog.js.map +1 -1
- package/dist/core/commands.d.ts +110 -0
- package/dist/core/commands.d.ts.map +1 -0
- package/dist/core/commands.js +633 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/concurrency.d.ts +38 -0
- package/dist/core/concurrency.d.ts.map +1 -0
- package/dist/core/concurrency.js +65 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/factory.js +16 -16
- package/dist/core/file_checkpoint.d.ts +9 -0
- package/dist/core/file_checkpoint.d.ts.map +1 -1
- package/dist/core/file_checkpoint.js +33 -1
- package/dist/core/file_checkpoint.js.map +1 -1
- package/dist/core/llm.d.ts.map +1 -1
- package/dist/core/llm.js +66 -13
- package/dist/core/llm.js.map +1 -1
- package/dist/core/memory.js +51 -51
- package/dist/core/schemas.d.ts +16 -0
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +32 -0
- package/dist/core/schemas.js.map +1 -1
- package/dist/core/security.d.ts.map +1 -1
- package/dist/core/security.js +27 -0
- package/dist/core/security.js.map +1 -1
- package/dist/core/skymd.js +14 -14
- package/dist/core/trace.d.ts +105 -0
- package/dist/core/trace.d.ts.map +1 -0
- package/dist/core/trace.js +213 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/tools/builtin.d.ts +2 -6
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +18 -111
- package/dist/tools/builtin.js.map +1 -1
- package/dist/tools/extra.d.ts +13 -0
- package/dist/tools/extra.d.ts.map +1 -0
- package/dist/tools/extra.js +827 -0
- package/dist/tools/extra.js.map +1 -0
- package/dist/tools/guards.d.ts +12 -0
- package/dist/tools/guards.d.ts.map +1 -0
- package/dist/tools/guards.js +143 -0
- package/dist/tools/guards.js.map +1 -0
- package/dist/tools/model_tool.d.ts.map +1 -1
- package/dist/tools/model_tool.js +24 -4
- package/dist/tools/model_tool.js.map +1 -1
- package/dist/web/markdown.d.ts +32 -0
- package/dist/web/markdown.d.ts.map +1 -0
- package/dist/web/markdown.js +202 -0
- package/dist/web/markdown.js.map +1 -0
- package/dist/web/server.d.ts +4 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +14 -582
- package/dist/web/server.js.map +1 -1
- package/dist/web/ui.d.ts +31 -0
- package/dist/web/ui.d.ts.map +1 -0
- package/dist/web/ui.js +1009 -0
- package/dist/web/ui.js.map +1 -0
- package/docs/AESTHETIC_DESIGN.md +152 -152
- package/docs/OPTIMIZATION_PLAN.md +178 -178
- package/package.json +1 -1
- package/src/agents/snow.ts +38 -5
- package/src/cli/commands_md.ts +112 -112
- package/src/cli/input_macros.ts +83 -83
- package/src/cli/loom.ts +1041 -1041
- package/src/cli/loom_chat.ts +772 -603
- package/src/cli/main.ts +853 -723
- package/src/cli/tui.ts +264 -289
- package/src/core/agent/guard.ts +133 -133
- package/src/core/agent/task.ts +100 -100
- package/src/core/agent.ts +1630 -1590
- package/src/core/agent_helpers.ts +500 -500
- package/src/core/bus.ts +221 -221
- package/src/core/cache.ts +153 -153
- package/src/core/catalog.ts +199 -178
- package/src/core/circuit_breaker.ts +119 -119
- package/src/core/commands.ts +704 -0
- package/src/core/concurrency.ts +73 -0
- package/src/core/config.ts +365 -365
- package/src/core/constants.ts +95 -95
- package/src/core/factory.ts +656 -656
- package/src/core/file_checkpoint.ts +163 -136
- package/src/core/hooks.ts +126 -126
- package/src/core/llm.ts +972 -915
- package/src/core/logger.ts +143 -143
- package/src/core/mcp.ts +1001 -1001
- package/src/core/memory.ts +1201 -1201
- package/src/core/middleware.ts +350 -350
- package/src/core/model_config.ts +159 -159
- package/src/core/pipelines.ts +424 -424
- package/src/core/schemas.ts +319 -282
- package/src/core/security.ts +27 -0
- package/src/core/semantic.ts +211 -211
- package/src/core/skill.ts +384 -384
- package/src/core/skymd.ts +143 -143
- package/src/core/theme.ts +65 -65
- package/src/core/tool.ts +457 -457
- package/src/core/trace.ts +236 -0
- package/src/core/verify.ts +71 -71
- package/src/plugins/loader.ts +91 -91
- package/src/skills/loader.ts +75 -75
- package/src/tools/builtin.ts +571 -642
- package/src/tools/computer.ts +279 -279
- package/src/tools/extra.ts +662 -0
- package/src/tools/guards.ts +82 -0
- package/src/tools/model_tool.ts +93 -74
- package/src/tools/todo.ts +76 -76
- package/src/web/markdown.ts +193 -0
- package/src/web/server.ts +117 -693
- package/src/web/ui.ts +949 -0
- package/tests/agent.test.ts +211 -159
- package/tests/agent_helpers.test.ts +48 -48
- package/tests/catalog.test.ts +86 -86
- package/tests/checkpoint_commands.test.ts +124 -124
- package/tests/claude_compat.test.ts +110 -110
- package/tests/commands.test.ts +103 -0
- package/tests/concurrency.test.ts +102 -0
- package/tests/config.test.ts +41 -41
- package/tests/extra_tools.test.ts +212 -0
- package/tests/fence_plugin.test.ts +52 -52
- package/tests/guard.test.ts +75 -75
- package/tests/loom.test.ts +337 -337
- package/tests/memory.test.ts +170 -170
- package/tests/model_config.test.ts +109 -109
- package/tests/skymd.test.ts +146 -146
- package/tests/ssrf.test.ts +38 -38
- package/tests/structured_retry.test.ts +87 -0
- package/tests/task.test.ts +60 -60
- package/tests/todo_toolstats.test.ts +94 -94
- package/tests/trace.test.ts +128 -0
- package/tests/tui.test.ts +67 -67
- package/tests/web.test.ts +169 -0
- package/tsconfig.json +38 -38
package/src/core/model_config.ts
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 模型配置管理 — unified default with optional per-agent overrides.
|
|
3
|
-
*
|
|
4
|
-
* Resolution order (already honored by LLMClient.getModel / getApiKey):
|
|
5
|
-
* model: agents.<name>.model → default_model
|
|
6
|
-
* apiKey: agents.<name>.api_key → env var → api_keys.<provider>
|
|
7
|
-
*
|
|
8
|
-
* This module provides the write path: mutate the *runtime* config object
|
|
9
|
-
* (shared by reference across LLMClient and every agent, so changes apply to
|
|
10
|
-
* the very next call — agents can hot-swap their own model mid-session) and
|
|
11
|
-
* persist a narrow patch to ~/.skyloom/config.yaml (never the merged config,
|
|
12
|
-
* so defaults don't leak into the user file).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import * as fs from 'fs';
|
|
16
|
-
import * as path from 'path';
|
|
17
|
-
import * as yaml from 'yaml';
|
|
18
|
-
import { USER_CONFIG_DIR } from './config';
|
|
19
|
-
import { listProviders, modelsFor, validateModel } from './catalog';
|
|
20
|
-
|
|
21
|
-
export interface ModelDescription {
|
|
22
|
-
model: string;
|
|
23
|
-
/** 'agent' = per-agent override, 'unified' = default_model. */
|
|
24
|
-
source: 'agent' | 'unified';
|
|
25
|
-
provider: string | null;
|
|
26
|
-
/** Where the API key would come from for this agent. */
|
|
27
|
-
keySource: 'agent' | 'env' | 'global' | 'missing';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Find which provider a catalog model id belongs to. */
|
|
31
|
-
export function providerOfModel(modelId: string): string | null {
|
|
32
|
-
if (modelId.includes('/')) return modelId.split('/')[0];
|
|
33
|
-
for (const p of listProviders()) {
|
|
34
|
-
if (modelsFor(p).some(m => m.id === modelId)) return p;
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Read-mutate-write the raw user config file (narrow patch). */
|
|
40
|
-
function patchUserConfig(mutate: (cfg: any) => void, dir: string = USER_CONFIG_DIR): void {
|
|
41
|
-
const file = path.join(dir, 'config.yaml');
|
|
42
|
-
let cfg: any = {};
|
|
43
|
-
if (fs.existsSync(file)) {
|
|
44
|
-
try { cfg = yaml.parse(fs.readFileSync(file, 'utf-8')) || {}; } catch { cfg = {}; }
|
|
45
|
-
}
|
|
46
|
-
mutate(cfg);
|
|
47
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
48
|
-
// config.yaml may hold plaintext API keys — keep it owner-only.
|
|
49
|
-
fs.writeFileSync(file, yaml.stringify(cfg), { encoding: 'utf-8', mode: 0o600 });
|
|
50
|
-
try { fs.chmodSync(file, 0o600); } catch { /* best-effort (e.g. Windows) */ }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Apply the same mutation to the in-memory runtime config (hot effect). */
|
|
54
|
-
function ensureAgentSlot(runtimeConfig: any, agentName: string): any {
|
|
55
|
-
if (!runtimeConfig.agents) runtimeConfig.agents = {};
|
|
56
|
-
if (!runtimeConfig.agents[agentName]) runtimeConfig.agents[agentName] = {};
|
|
57
|
-
return runtimeConfig.agents[agentName];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface SetModelResult {
|
|
61
|
-
ok: boolean;
|
|
62
|
-
suggestions: string[];
|
|
63
|
-
provider: string | null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Per-agent model override (独立配置). */
|
|
67
|
-
export function setAgentModel(
|
|
68
|
-
runtimeConfig: any,
|
|
69
|
-
agentName: string,
|
|
70
|
-
modelId: string,
|
|
71
|
-
dir?: string
|
|
72
|
-
): SetModelResult {
|
|
73
|
-
const v = validateModel(modelId);
|
|
74
|
-
if (!v.ok) return { ok: false, suggestions: v.suggestions, provider: null };
|
|
75
|
-
const provider = providerOfModel(modelId);
|
|
76
|
-
|
|
77
|
-
const slot = ensureAgentSlot(runtimeConfig, agentName);
|
|
78
|
-
slot.model = modelId;
|
|
79
|
-
if (provider) slot.provider = provider;
|
|
80
|
-
|
|
81
|
-
patchUserConfig(cfg => {
|
|
82
|
-
if (!cfg.agents) cfg.agents = {};
|
|
83
|
-
if (!cfg.agents[agentName]) cfg.agents[agentName] = {};
|
|
84
|
-
cfg.agents[agentName].model = modelId;
|
|
85
|
-
if (provider) cfg.agents[agentName].provider = provider;
|
|
86
|
-
}, dir);
|
|
87
|
-
return { ok: true, suggestions: [], provider };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Remove the per-agent override — the agent follows the unified default again. */
|
|
91
|
-
export function clearAgentModel(runtimeConfig: any, agentName: string, dir?: string): void {
|
|
92
|
-
if (runtimeConfig.agents?.[agentName]) {
|
|
93
|
-
delete runtimeConfig.agents[agentName].model;
|
|
94
|
-
delete runtimeConfig.agents[agentName].provider;
|
|
95
|
-
}
|
|
96
|
-
patchUserConfig(cfg => {
|
|
97
|
-
if (cfg.agents?.[agentName]) {
|
|
98
|
-
delete cfg.agents[agentName].model;
|
|
99
|
-
delete cfg.agents[agentName].provider;
|
|
100
|
-
if (Object.keys(cfg.agents[agentName]).length === 0) delete cfg.agents[agentName];
|
|
101
|
-
}
|
|
102
|
-
}, dir);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** 统一配置 — the default model every agent without an override uses. */
|
|
106
|
-
export function setUnifiedModel(runtimeConfig: any, modelId: string, dir?: string): SetModelResult {
|
|
107
|
-
const v = validateModel(modelId);
|
|
108
|
-
if (!v.ok) return { ok: false, suggestions: v.suggestions, provider: null };
|
|
109
|
-
const provider = providerOfModel(modelId);
|
|
110
|
-
|
|
111
|
-
runtimeConfig.default_model = modelId;
|
|
112
|
-
if (provider) runtimeConfig.default_provider = provider;
|
|
113
|
-
|
|
114
|
-
patchUserConfig(cfg => {
|
|
115
|
-
cfg.default_model = modelId;
|
|
116
|
-
if (provider) cfg.default_provider = provider;
|
|
117
|
-
}, dir);
|
|
118
|
-
return { ok: true, suggestions: [], provider };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Per-agent API key (独立 key;该 agent 的所有调用优先用它). */
|
|
122
|
-
export function setAgentApiKey(runtimeConfig: any, agentName: string, key: string, dir?: string): void {
|
|
123
|
-
ensureAgentSlot(runtimeConfig, agentName).api_key = key;
|
|
124
|
-
patchUserConfig(cfg => {
|
|
125
|
-
if (!cfg.agents) cfg.agents = {};
|
|
126
|
-
if (!cfg.agents[agentName]) cfg.agents[agentName] = {};
|
|
127
|
-
cfg.agents[agentName].api_key = key;
|
|
128
|
-
}, dir);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function clearAgentApiKey(runtimeConfig: any, agentName: string, dir?: string): void {
|
|
132
|
-
if (runtimeConfig.agents?.[agentName]) delete runtimeConfig.agents[agentName].api_key;
|
|
133
|
-
patchUserConfig(cfg => {
|
|
134
|
-
if (cfg.agents?.[agentName]) delete cfg.agents[agentName].api_key;
|
|
135
|
-
}, dir);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Describe how an agent's model & key resolve right now. */
|
|
139
|
-
export function describeAgentLLM(runtimeConfig: any, agentName: string, dir: string = USER_CONFIG_DIR): ModelDescription {
|
|
140
|
-
const agentCfg = runtimeConfig.agents?.[agentName] || {};
|
|
141
|
-
const model: string = agentCfg.model
|
|
142
|
-
|| runtimeConfig.default_model
|
|
143
|
-
|| runtimeConfig.llm?.default_model
|
|
144
|
-
|| 'gpt-4o';
|
|
145
|
-
const source: 'agent' | 'unified' = agentCfg.model ? 'agent' : 'unified';
|
|
146
|
-
const provider = providerOfModel(model);
|
|
147
|
-
|
|
148
|
-
let keySource: ModelDescription['keySource'] = 'missing';
|
|
149
|
-
if (agentCfg.api_key) keySource = 'agent';
|
|
150
|
-
else if (provider && process.env[`${provider.toUpperCase()}_API_KEY`]) keySource = 'env';
|
|
151
|
-
else {
|
|
152
|
-
try {
|
|
153
|
-
const file = path.join(dir, 'config.yaml');
|
|
154
|
-
const cfg = fs.existsSync(file) ? yaml.parse(fs.readFileSync(file, 'utf-8')) || {} : {};
|
|
155
|
-
if (provider && cfg.api_keys?.[provider]) keySource = 'global';
|
|
156
|
-
} catch { /* missing */ }
|
|
157
|
-
}
|
|
158
|
-
return { model, source, provider, keySource };
|
|
159
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 模型配置管理 — unified default with optional per-agent overrides.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (already honored by LLMClient.getModel / getApiKey):
|
|
5
|
+
* model: agents.<name>.model → default_model
|
|
6
|
+
* apiKey: agents.<name>.api_key → env var → api_keys.<provider>
|
|
7
|
+
*
|
|
8
|
+
* This module provides the write path: mutate the *runtime* config object
|
|
9
|
+
* (shared by reference across LLMClient and every agent, so changes apply to
|
|
10
|
+
* the very next call — agents can hot-swap their own model mid-session) and
|
|
11
|
+
* persist a narrow patch to ~/.skyloom/config.yaml (never the merged config,
|
|
12
|
+
* so defaults don't leak into the user file).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as yaml from 'yaml';
|
|
18
|
+
import { USER_CONFIG_DIR } from './config';
|
|
19
|
+
import { listProviders, modelsFor, validateModel } from './catalog';
|
|
20
|
+
|
|
21
|
+
export interface ModelDescription {
|
|
22
|
+
model: string;
|
|
23
|
+
/** 'agent' = per-agent override, 'unified' = default_model. */
|
|
24
|
+
source: 'agent' | 'unified';
|
|
25
|
+
provider: string | null;
|
|
26
|
+
/** Where the API key would come from for this agent. */
|
|
27
|
+
keySource: 'agent' | 'env' | 'global' | 'missing';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Find which provider a catalog model id belongs to. */
|
|
31
|
+
export function providerOfModel(modelId: string): string | null {
|
|
32
|
+
if (modelId.includes('/')) return modelId.split('/')[0];
|
|
33
|
+
for (const p of listProviders()) {
|
|
34
|
+
if (modelsFor(p).some(m => m.id === modelId)) return p;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Read-mutate-write the raw user config file (narrow patch). */
|
|
40
|
+
function patchUserConfig(mutate: (cfg: any) => void, dir: string = USER_CONFIG_DIR): void {
|
|
41
|
+
const file = path.join(dir, 'config.yaml');
|
|
42
|
+
let cfg: any = {};
|
|
43
|
+
if (fs.existsSync(file)) {
|
|
44
|
+
try { cfg = yaml.parse(fs.readFileSync(file, 'utf-8')) || {}; } catch { cfg = {}; }
|
|
45
|
+
}
|
|
46
|
+
mutate(cfg);
|
|
47
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
48
|
+
// config.yaml may hold plaintext API keys — keep it owner-only.
|
|
49
|
+
fs.writeFileSync(file, yaml.stringify(cfg), { encoding: 'utf-8', mode: 0o600 });
|
|
50
|
+
try { fs.chmodSync(file, 0o600); } catch { /* best-effort (e.g. Windows) */ }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Apply the same mutation to the in-memory runtime config (hot effect). */
|
|
54
|
+
function ensureAgentSlot(runtimeConfig: any, agentName: string): any {
|
|
55
|
+
if (!runtimeConfig.agents) runtimeConfig.agents = {};
|
|
56
|
+
if (!runtimeConfig.agents[agentName]) runtimeConfig.agents[agentName] = {};
|
|
57
|
+
return runtimeConfig.agents[agentName];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SetModelResult {
|
|
61
|
+
ok: boolean;
|
|
62
|
+
suggestions: string[];
|
|
63
|
+
provider: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Per-agent model override (独立配置). */
|
|
67
|
+
export function setAgentModel(
|
|
68
|
+
runtimeConfig: any,
|
|
69
|
+
agentName: string,
|
|
70
|
+
modelId: string,
|
|
71
|
+
dir?: string
|
|
72
|
+
): SetModelResult {
|
|
73
|
+
const v = validateModel(modelId);
|
|
74
|
+
if (!v.ok) return { ok: false, suggestions: v.suggestions, provider: null };
|
|
75
|
+
const provider = providerOfModel(modelId);
|
|
76
|
+
|
|
77
|
+
const slot = ensureAgentSlot(runtimeConfig, agentName);
|
|
78
|
+
slot.model = modelId;
|
|
79
|
+
if (provider) slot.provider = provider;
|
|
80
|
+
|
|
81
|
+
patchUserConfig(cfg => {
|
|
82
|
+
if (!cfg.agents) cfg.agents = {};
|
|
83
|
+
if (!cfg.agents[agentName]) cfg.agents[agentName] = {};
|
|
84
|
+
cfg.agents[agentName].model = modelId;
|
|
85
|
+
if (provider) cfg.agents[agentName].provider = provider;
|
|
86
|
+
}, dir);
|
|
87
|
+
return { ok: true, suggestions: [], provider };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Remove the per-agent override — the agent follows the unified default again. */
|
|
91
|
+
export function clearAgentModel(runtimeConfig: any, agentName: string, dir?: string): void {
|
|
92
|
+
if (runtimeConfig.agents?.[agentName]) {
|
|
93
|
+
delete runtimeConfig.agents[agentName].model;
|
|
94
|
+
delete runtimeConfig.agents[agentName].provider;
|
|
95
|
+
}
|
|
96
|
+
patchUserConfig(cfg => {
|
|
97
|
+
if (cfg.agents?.[agentName]) {
|
|
98
|
+
delete cfg.agents[agentName].model;
|
|
99
|
+
delete cfg.agents[agentName].provider;
|
|
100
|
+
if (Object.keys(cfg.agents[agentName]).length === 0) delete cfg.agents[agentName];
|
|
101
|
+
}
|
|
102
|
+
}, dir);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 统一配置 — the default model every agent without an override uses. */
|
|
106
|
+
export function setUnifiedModel(runtimeConfig: any, modelId: string, dir?: string): SetModelResult {
|
|
107
|
+
const v = validateModel(modelId);
|
|
108
|
+
if (!v.ok) return { ok: false, suggestions: v.suggestions, provider: null };
|
|
109
|
+
const provider = providerOfModel(modelId);
|
|
110
|
+
|
|
111
|
+
runtimeConfig.default_model = modelId;
|
|
112
|
+
if (provider) runtimeConfig.default_provider = provider;
|
|
113
|
+
|
|
114
|
+
patchUserConfig(cfg => {
|
|
115
|
+
cfg.default_model = modelId;
|
|
116
|
+
if (provider) cfg.default_provider = provider;
|
|
117
|
+
}, dir);
|
|
118
|
+
return { ok: true, suggestions: [], provider };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Per-agent API key (独立 key;该 agent 的所有调用优先用它). */
|
|
122
|
+
export function setAgentApiKey(runtimeConfig: any, agentName: string, key: string, dir?: string): void {
|
|
123
|
+
ensureAgentSlot(runtimeConfig, agentName).api_key = key;
|
|
124
|
+
patchUserConfig(cfg => {
|
|
125
|
+
if (!cfg.agents) cfg.agents = {};
|
|
126
|
+
if (!cfg.agents[agentName]) cfg.agents[agentName] = {};
|
|
127
|
+
cfg.agents[agentName].api_key = key;
|
|
128
|
+
}, dir);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function clearAgentApiKey(runtimeConfig: any, agentName: string, dir?: string): void {
|
|
132
|
+
if (runtimeConfig.agents?.[agentName]) delete runtimeConfig.agents[agentName].api_key;
|
|
133
|
+
patchUserConfig(cfg => {
|
|
134
|
+
if (cfg.agents?.[agentName]) delete cfg.agents[agentName].api_key;
|
|
135
|
+
}, dir);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Describe how an agent's model & key resolve right now. */
|
|
139
|
+
export function describeAgentLLM(runtimeConfig: any, agentName: string, dir: string = USER_CONFIG_DIR): ModelDescription {
|
|
140
|
+
const agentCfg = runtimeConfig.agents?.[agentName] || {};
|
|
141
|
+
const model: string = agentCfg.model
|
|
142
|
+
|| runtimeConfig.default_model
|
|
143
|
+
|| runtimeConfig.llm?.default_model
|
|
144
|
+
|| 'gpt-4o';
|
|
145
|
+
const source: 'agent' | 'unified' = agentCfg.model ? 'agent' : 'unified';
|
|
146
|
+
const provider = providerOfModel(model);
|
|
147
|
+
|
|
148
|
+
let keySource: ModelDescription['keySource'] = 'missing';
|
|
149
|
+
if (agentCfg.api_key) keySource = 'agent';
|
|
150
|
+
else if (provider && process.env[`${provider.toUpperCase()}_API_KEY`]) keySource = 'env';
|
|
151
|
+
else {
|
|
152
|
+
try {
|
|
153
|
+
const file = path.join(dir, 'config.yaml');
|
|
154
|
+
const cfg = fs.existsSync(file) ? yaml.parse(fs.readFileSync(file, 'utf-8')) || {} : {};
|
|
155
|
+
if (provider && cfg.api_keys?.[provider]) keySource = 'global';
|
|
156
|
+
} catch { /* missing */ }
|
|
157
|
+
}
|
|
158
|
+
return { model, source, provider, keySource };
|
|
159
|
+
}
|