longer-agent 0.1.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/LICENSE +21 -0
- package/README.md +227 -0
- package/README.zh-CN.md +227 -0
- package/agent_templates/executor/agent.yaml +22 -0
- package/agent_templates/executor/system_prompt.md +17 -0
- package/agent_templates/explorer/agent.yaml +13 -0
- package/agent_templates/explorer/system_prompt.md +19 -0
- package/agent_templates/main/agent.yaml +7 -0
- package/agent_templates/main/system_prompt.md +45 -0
- package/configExample.yaml +83 -0
- package/dist/agents/agent.d.ts +79 -0
- package/dist/agents/agent.d.ts.map +1 -0
- package/dist/agents/agent.js +156 -0
- package/dist/agents/agent.js.map +1 -0
- package/dist/agents/tool-loop.d.ts +140 -0
- package/dist/agents/tool-loop.d.ts.map +1 -0
- package/dist/agents/tool-loop.js +465 -0
- package/dist/agents/tool-loop.js.map +1 -0
- package/dist/ask.d.ts +81 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/ask.js +34 -0
- package/dist/ask.js.map +1 -0
- package/dist/auth/openai-oauth.d.ts +66 -0
- package/dist/auth/openai-oauth.d.ts.map +1 -0
- package/dist/auth/openai-oauth.js +640 -0
- package/dist/auth/openai-oauth.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +254 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +118 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +862 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +130 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +648 -0
- package/dist/config.js.map +1 -0
- package/dist/context-rendering.d.ts +69 -0
- package/dist/context-rendering.d.ts.map +1 -0
- package/dist/context-rendering.js +250 -0
- package/dist/context-rendering.js.map +1 -0
- package/dist/document-projection.d.ts +12 -0
- package/dist/document-projection.d.ts.map +1 -0
- package/dist/document-projection.js +75 -0
- package/dist/document-projection.js.map +1 -0
- package/dist/ephemeral-log.d.ts +15 -0
- package/dist/ephemeral-log.d.ts.map +1 -0
- package/dist/ephemeral-log.js +173 -0
- package/dist/ephemeral-log.js.map +1 -0
- package/dist/file-attach.d.ts +89 -0
- package/dist/file-attach.d.ts.map +1 -0
- package/dist/file-attach.js +571 -0
- package/dist/file-attach.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/init-wizard.d.ts +13 -0
- package/dist/init-wizard.d.ts.map +1 -0
- package/dist/init-wizard.js +328 -0
- package/dist/init-wizard.js.map +1 -0
- package/dist/log-entry.d.ts +104 -0
- package/dist/log-entry.d.ts.map +1 -0
- package/dist/log-entry.js +292 -0
- package/dist/log-entry.js.map +1 -0
- package/dist/log-projection.d.ts +73 -0
- package/dist/log-projection.d.ts.map +1 -0
- package/dist/log-projection.js +651 -0
- package/dist/log-projection.js.map +1 -0
- package/dist/mcp-client.d.ts +55 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +402 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/model-selection.d.ts +16 -0
- package/dist/model-selection.d.ts.map +1 -0
- package/dist/model-selection.js +181 -0
- package/dist/model-selection.js.map +1 -0
- package/dist/network-retry.d.ts +38 -0
- package/dist/network-retry.d.ts.map +1 -0
- package/dist/network-retry.js +140 -0
- package/dist/network-retry.js.map +1 -0
- package/dist/persistence.d.ts +104 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +644 -0
- package/dist/persistence.js.map +1 -0
- package/dist/primitives/context.d.ts +29 -0
- package/dist/primitives/context.d.ts.map +1 -0
- package/dist/primitives/context.js +85 -0
- package/dist/primitives/context.js.map +1 -0
- package/dist/progress.d.ts +51 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +229 -0
- package/dist/progress.js.map +1 -0
- package/dist/provider-presets.d.ts +34 -0
- package/dist/provider-presets.d.ts.map +1 -0
- package/dist/provider-presets.js +181 -0
- package/dist/provider-presets.js.map +1 -0
- package/dist/providers/anthropic.d.ts +32 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +450 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/base.d.ts +135 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +104 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/glm.d.ts +18 -0
- package/dist/providers/glm.d.ts.map +1 -0
- package/dist/providers/glm.js +59 -0
- package/dist/providers/glm.js.map +1 -0
- package/dist/providers/kimi.d.ts +23 -0
- package/dist/providers/kimi.d.ts.map +1 -0
- package/dist/providers/kimi.js +89 -0
- package/dist/providers/kimi.js.map +1 -0
- package/dist/providers/minimax.d.ts +20 -0
- package/dist/providers/minimax.d.ts.map +1 -0
- package/dist/providers/minimax.js +192 -0
- package/dist/providers/minimax.js.map +1 -0
- package/dist/providers/openai-chat.d.ts +33 -0
- package/dist/providers/openai-chat.d.ts.map +1 -0
- package/dist/providers/openai-chat.js +543 -0
- package/dist/providers/openai-chat.js.map +1 -0
- package/dist/providers/openai-responses.d.ts +26 -0
- package/dist/providers/openai-responses.d.ts.map +1 -0
- package/dist/providers/openai-responses.js +443 -0
- package/dist/providers/openai-responses.js.map +1 -0
- package/dist/providers/openrouter.d.ts +24 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +177 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/registry.d.ts +7 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +38 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/security/path.d.ts +51 -0
- package/dist/security/path.d.ts.map +1 -0
- package/dist/security/path.js +187 -0
- package/dist/security/path.js.map +1 -0
- package/dist/security/sensitive-files.d.ts +3 -0
- package/dist/security/sensitive-files.d.ts.map +1 -0
- package/dist/security/sensitive-files.js +41 -0
- package/dist/security/sensitive-files.js.map +1 -0
- package/dist/session.d.ts +446 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +4595 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +46 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +134 -0
- package/dist/settings.js.map +1 -0
- package/dist/show-context.d.ts +35 -0
- package/dist/show-context.d.ts.map +1 -0
- package/dist/show-context.js +320 -0
- package/dist/show-context.js.map +1 -0
- package/dist/skills/loader.d.ts +49 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +166 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/summarize-context.d.ts +29 -0
- package/dist/summarize-context.d.ts.map +1 -0
- package/dist/summarize-context.js +247 -0
- package/dist/summarize-context.js.map +1 -0
- package/dist/templates/loader.d.ts +104 -0
- package/dist/templates/loader.d.ts.map +1 -0
- package/dist/templates/loader.js +514 -0
- package/dist/templates/loader.js.map +1 -0
- package/dist/tools/basic.d.ts +29 -0
- package/dist/tools/basic.d.ts.map +1 -0
- package/dist/tools/basic.js +2079 -0
- package/dist/tools/basic.js.map +1 -0
- package/dist/tools/comm.d.ts +17 -0
- package/dist/tools/comm.d.ts.map +1 -0
- package/dist/tools/comm.js +192 -0
- package/dist/tools/comm.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +11 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +237 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +24 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +51 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tui/app.d.ts +35 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +1042 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/checkbox-picker.d.ts +35 -0
- package/dist/tui/checkbox-picker.d.ts.map +1 -0
- package/dist/tui/checkbox-picker.js +85 -0
- package/dist/tui/checkbox-picker.js.map +1 -0
- package/dist/tui/command-picker.d.ts +31 -0
- package/dist/tui/command-picker.d.ts.map +1 -0
- package/dist/tui/command-picker.js +113 -0
- package/dist/tui/command-picker.js.map +1 -0
- package/dist/tui/components/ask-panel.d.ts +21 -0
- package/dist/tui/components/ask-panel.d.ts.map +1 -0
- package/dist/tui/components/ask-panel.js +81 -0
- package/dist/tui/components/ask-panel.js.map +1 -0
- package/dist/tui/components/conversation-panel.d.ts +68 -0
- package/dist/tui/components/conversation-panel.d.ts.map +1 -0
- package/dist/tui/components/conversation-panel.js +611 -0
- package/dist/tui/components/conversation-panel.js.map +1 -0
- package/dist/tui/components/input-panel.d.ts +27 -0
- package/dist/tui/components/input-panel.d.ts.map +1 -0
- package/dist/tui/components/input-panel.js +725 -0
- package/dist/tui/components/input-panel.js.map +1 -0
- package/dist/tui/components/logo-panel.d.ts +14 -0
- package/dist/tui/components/logo-panel.d.ts.map +1 -0
- package/dist/tui/components/logo-panel.js +37 -0
- package/dist/tui/components/logo-panel.js.map +1 -0
- package/dist/tui/components/plan-panel.d.ts +10 -0
- package/dist/tui/components/plan-panel.d.ts.map +1 -0
- package/dist/tui/components/plan-panel.js +8 -0
- package/dist/tui/components/plan-panel.js.map +1 -0
- package/dist/tui/components/status-bar.d.ts +24 -0
- package/dist/tui/components/status-bar.d.ts.map +1 -0
- package/dist/tui/components/status-bar.js +80 -0
- package/dist/tui/components/status-bar.js.map +1 -0
- package/dist/tui/input/editor-state.d.ts +22 -0
- package/dist/tui/input/editor-state.d.ts.map +1 -0
- package/dist/tui/input/editor-state.js +157 -0
- package/dist/tui/input/editor-state.js.map +1 -0
- package/dist/tui/input/keymap.d.ts +3 -0
- package/dist/tui/input/keymap.d.ts.map +1 -0
- package/dist/tui/input/keymap.js +72 -0
- package/dist/tui/input/keymap.js.map +1 -0
- package/dist/tui/input/paste-slots.d.ts +17 -0
- package/dist/tui/input/paste-slots.d.ts.map +1 -0
- package/dist/tui/input/paste-slots.js +46 -0
- package/dist/tui/input/paste-slots.js.map +1 -0
- package/dist/tui/input/paste.d.ts +15 -0
- package/dist/tui/input/paste.d.ts.map +1 -0
- package/dist/tui/input/paste.js +35 -0
- package/dist/tui/input/paste.js.map +1 -0
- package/dist/tui/input/protocol.d.ts +9 -0
- package/dist/tui/input/protocol.d.ts.map +1 -0
- package/dist/tui/input/protocol.js +387 -0
- package/dist/tui/input/protocol.js.map +1 -0
- package/dist/tui/input/sanitize.d.ts +6 -0
- package/dist/tui/input/sanitize.d.ts.map +1 -0
- package/dist/tui/input/sanitize.js +20 -0
- package/dist/tui/input/sanitize.js.map +1 -0
- package/dist/tui/input/types.d.ts +18 -0
- package/dist/tui/input/types.d.ts.map +1 -0
- package/dist/tui/input/types.js +2 -0
- package/dist/tui/input/types.js.map +1 -0
- package/dist/tui/launch.d.ts +23 -0
- package/dist/tui/launch.d.ts.map +1 -0
- package/dist/tui/launch.js +104 -0
- package/dist/tui/launch.js.map +1 -0
- package/dist/tui/theme.d.ts +20 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +29 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/types.d.ts +136 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +9 -0
- package/dist/tui/types.js.map +1 -0
- package/package.json +76 -0
- package/prompts/sections/agents_md.md +23 -0
- package/prompts/sections/important_log.md +16 -0
- package/prompts/sections/system_mechanisms.md +18 -0
- package/prompts/tools/apply_patch.md +31 -0
- package/prompts/tools/ask.md +18 -0
- package/prompts/tools/bash.md +13 -0
- package/prompts/tools/bash_background.md +9 -0
- package/prompts/tools/bash_output.md +9 -0
- package/prompts/tools/check_status.md +3 -0
- package/prompts/tools/diff.md +5 -0
- package/prompts/tools/edit_file.md +11 -0
- package/prompts/tools/glob.md +7 -0
- package/prompts/tools/grep.md +20 -0
- package/prompts/tools/kill_agent.md +3 -0
- package/prompts/tools/kill_shell.md +5 -0
- package/prompts/tools/list_dir.md +5 -0
- package/prompts/tools/plan.md +252 -0
- package/prompts/tools/read_file.md +9 -0
- package/prompts/tools/show_context.md +12 -0
- package/prompts/tools/skill.md +7 -0
- package/prompts/tools/spawn_agent.md +195 -0
- package/prompts/tools/summarize_context.md +122 -0
- package/prompts/tools/test.md +5 -0
- package/prompts/tools/wait.md +17 -0
- package/prompts/tools/web_fetch.md +9 -0
- package/prompts/tools/web_search.md +5 -0
- package/prompts/tools/write_file.md +11 -0
- package/skills/.staging/.gitkeep +0 -0
- package/skills/explain-code/SKILL.md +15 -0
- package/skills/skill-manager/SKILL.md +83 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management — load YAML config, resolve env vars,
|
|
3
|
+
* auto-detect model capabilities from known lookup tables.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import * as yaml from "js-yaml";
|
|
10
|
+
import { readOAuthAccessToken, hasOAuthTokens } from "./auth/openai-oauth.js";
|
|
11
|
+
// ------------------------------------------------------------------
|
|
12
|
+
// Known model lookup tables
|
|
13
|
+
// ------------------------------------------------------------------
|
|
14
|
+
export const KNOWN_CONTEXT_LENGTHS = {
|
|
15
|
+
// OpenAI - GPT-5 family
|
|
16
|
+
"gpt-5.2": 400_000,
|
|
17
|
+
"gpt-5.2-codex": 400_000,
|
|
18
|
+
"gpt-5.3-codex": 400_000,
|
|
19
|
+
"gpt-5.4": 1_050_000,
|
|
20
|
+
// Anthropic
|
|
21
|
+
"claude-opus-4-1-20250805": 200_000,
|
|
22
|
+
"claude-sonnet-4-5-20250929": 200_000,
|
|
23
|
+
"claude-haiku-4-5-20251001": 200_000,
|
|
24
|
+
"claude-haiku-4-5": 200_000,
|
|
25
|
+
"claude-opus-4-5-20251101": 200_000,
|
|
26
|
+
"claude-opus-4-6": 200_000,
|
|
27
|
+
"claude-sonnet-4-6": 200_000,
|
|
28
|
+
// OpenRouter Anthropic aliases
|
|
29
|
+
"claude-haiku-4.5": 200_000,
|
|
30
|
+
"claude-opus-4.6": 200_000,
|
|
31
|
+
"claude-sonnet-4.6": 200_000,
|
|
32
|
+
// Kimi
|
|
33
|
+
"kimi-k2.5": 256_000,
|
|
34
|
+
"kimi-k2-thinking": 256_000,
|
|
35
|
+
"kimi-k2-instruct": 128_000,
|
|
36
|
+
// GLM (Zhipu AI)
|
|
37
|
+
"glm-5": 200_000,
|
|
38
|
+
"glm-4.7": 200_000,
|
|
39
|
+
"glm-4.7-flash": 200_000,
|
|
40
|
+
// MiniMax
|
|
41
|
+
"MiniMax-M2.1": 200_000,
|
|
42
|
+
"MiniMax-M2.5": 204_800,
|
|
43
|
+
"MiniMax-M2.5-highspeed": 204_800,
|
|
44
|
+
"MiniMax-M1-40k": 1_000_000,
|
|
45
|
+
"MiniMax-M1-80k": 1_000_000,
|
|
46
|
+
// MiniMax — lowercase aliases for OpenRouter (minimax/minimax-m2.5 → minimax-m2.5)
|
|
47
|
+
"minimax-m2.1": 200_000,
|
|
48
|
+
"minimax-m2.5": 204_800,
|
|
49
|
+
"minimax-m1": 1_000_000,
|
|
50
|
+
};
|
|
51
|
+
export const KNOWN_MULTIMODAL_MODELS = new Set([
|
|
52
|
+
// OpenAI
|
|
53
|
+
"gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.4",
|
|
54
|
+
// Anthropic
|
|
55
|
+
"claude-opus-4-1-20250805", "claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001",
|
|
56
|
+
"claude-haiku-4-5",
|
|
57
|
+
"claude-opus-4-5-20251101", "claude-opus-4-6", "claude-sonnet-4-6",
|
|
58
|
+
"claude-haiku-4.5", "claude-opus-4.6", "claude-sonnet-4.6",
|
|
59
|
+
// Kimi
|
|
60
|
+
"kimi-k2.5",
|
|
61
|
+
]);
|
|
62
|
+
export const KNOWN_THINKING_MODELS = new Set([
|
|
63
|
+
// OpenAI
|
|
64
|
+
"gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.4",
|
|
65
|
+
// Anthropic
|
|
66
|
+
"claude-opus-4-1-20250805", "claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001",
|
|
67
|
+
"claude-haiku-4-5",
|
|
68
|
+
"claude-opus-4-5-20251101", "claude-opus-4-6", "claude-sonnet-4-6",
|
|
69
|
+
"claude-haiku-4.5", "claude-opus-4.6", "claude-sonnet-4.6",
|
|
70
|
+
// Kimi
|
|
71
|
+
"kimi-k2.5", "kimi-k2-thinking",
|
|
72
|
+
// GLM
|
|
73
|
+
"glm-5", "glm-4.7", "glm-4.7-flash",
|
|
74
|
+
// MiniMax
|
|
75
|
+
"MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1",
|
|
76
|
+
"MiniMax-M1-40k", "MiniMax-M1-80k",
|
|
77
|
+
// MiniMax — lowercase aliases for OpenRouter
|
|
78
|
+
"minimax-m2.5", "minimax-m2.1", "minimax-m1",
|
|
79
|
+
]);
|
|
80
|
+
export const KNOWN_NO_WEB_SEARCH_MODELS = new Set([
|
|
81
|
+
// MiniMax — M1 series lacks native web search
|
|
82
|
+
"MiniMax-M1-40k", "MiniMax-M1-80k",
|
|
83
|
+
]);
|
|
84
|
+
// ------------------------------------------------------------------
|
|
85
|
+
// Max output tokens per model
|
|
86
|
+
// ------------------------------------------------------------------
|
|
87
|
+
export const KNOWN_MAX_OUTPUT_TOKENS = {
|
|
88
|
+
// OpenAI — GPT-5 family
|
|
89
|
+
"gpt-5.2": 128_000,
|
|
90
|
+
"gpt-5.2-codex": 128_000,
|
|
91
|
+
"gpt-5.3-codex": 128_000,
|
|
92
|
+
"gpt-5.4": 128_000,
|
|
93
|
+
// Anthropic — Claude 4.6
|
|
94
|
+
"claude-opus-4-6": 128_000,
|
|
95
|
+
"claude-sonnet-4-6": 64_000,
|
|
96
|
+
// Anthropic — Claude 4.5
|
|
97
|
+
"claude-opus-4-5-20251101": 64_000,
|
|
98
|
+
"claude-sonnet-4-5-20250929": 64_000,
|
|
99
|
+
"claude-haiku-4-5-20251001": 64_000,
|
|
100
|
+
"claude-haiku-4-5": 64_000,
|
|
101
|
+
// Anthropic — Claude 4.1
|
|
102
|
+
"claude-opus-4-1-20250805": 32_000,
|
|
103
|
+
// OpenRouter — Anthropic aliases
|
|
104
|
+
"claude-haiku-4.5": 64_000,
|
|
105
|
+
"claude-opus-4.6": 128_000,
|
|
106
|
+
"claude-sonnet-4.6": 64_000,
|
|
107
|
+
// Kimi
|
|
108
|
+
"kimi-k2.5": 65_536,
|
|
109
|
+
"kimi-k2-thinking": 65_536,
|
|
110
|
+
"kimi-k2-instruct": 65_536,
|
|
111
|
+
// GLM (Zhipu AI)
|
|
112
|
+
"glm-5": 128_000,
|
|
113
|
+
"glm-4.7": 128_000,
|
|
114
|
+
"glm-4.7-flash": 128_000,
|
|
115
|
+
// MiniMax
|
|
116
|
+
"MiniMax-M2.1": 8_192,
|
|
117
|
+
"MiniMax-M2.5": 196_608,
|
|
118
|
+
"MiniMax-M2.5-highspeed": 196_608,
|
|
119
|
+
"MiniMax-M1-40k": 40_000,
|
|
120
|
+
"MiniMax-M1-80k": 80_000,
|
|
121
|
+
// MiniMax — lowercase aliases for OpenRouter
|
|
122
|
+
"minimax-m2.1": 8_192,
|
|
123
|
+
"minimax-m2.5": 196_608,
|
|
124
|
+
"minimax-m1": 80_000,
|
|
125
|
+
};
|
|
126
|
+
/** Resolve max output tokens for a model. Priority: known lookup (exact then normalized) > undefined. */
|
|
127
|
+
export function getModelMaxOutputTokens(model) {
|
|
128
|
+
return KNOWN_MAX_OUTPUT_TOKENS[model]
|
|
129
|
+
?? KNOWN_MAX_OUTPUT_TOKENS[normalizeModelId(model)];
|
|
130
|
+
}
|
|
131
|
+
// ------------------------------------------------------------------
|
|
132
|
+
// Thinking levels per model
|
|
133
|
+
// ------------------------------------------------------------------
|
|
134
|
+
export const KNOWN_THINKING_LEVELS = {
|
|
135
|
+
// OpenAI
|
|
136
|
+
"gpt-5.2": ["none", "low", "medium", "high", "xhigh"],
|
|
137
|
+
"gpt-5.2-codex": ["low", "medium", "high", "xhigh"],
|
|
138
|
+
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
|
|
139
|
+
"gpt-5.4": ["none", "low", "medium", "high", "xhigh"],
|
|
140
|
+
// Anthropic — adaptive + effort (4.6)
|
|
141
|
+
"claude-opus-4-6": ["off", "low", "medium", "high", "max"],
|
|
142
|
+
"claude-sonnet-4-6": ["off", "low", "medium", "high", "max"],
|
|
143
|
+
"claude-opus-4.6": ["off", "low", "medium", "high", "max"],
|
|
144
|
+
"claude-sonnet-4.6": ["off", "low", "medium", "high", "max"],
|
|
145
|
+
// Anthropic — manual extended thinking (4.5 and earlier)
|
|
146
|
+
"claude-opus-4-1-20250805": ["off", "low", "medium", "high"],
|
|
147
|
+
"claude-sonnet-4-5-20250929": ["off", "low", "medium", "high"],
|
|
148
|
+
"claude-haiku-4-5-20251001": ["off", "low", "medium", "high"],
|
|
149
|
+
"claude-haiku-4-5": ["off", "low", "medium", "high"],
|
|
150
|
+
"claude-haiku-4.5": ["off", "low", "medium", "high"],
|
|
151
|
+
"claude-opus-4-5-20251101": ["off", "low", "medium", "high"],
|
|
152
|
+
// GLM
|
|
153
|
+
"glm-5": ["off", "on"], "glm-4.7": ["off", "on"], "glm-4.7-flash": ["off", "on"],
|
|
154
|
+
// Kimi
|
|
155
|
+
"kimi-k2.5": ["off", "on"], "kimi-k2-thinking": ["off", "on"],
|
|
156
|
+
// MiniMax (not configurable)
|
|
157
|
+
"MiniMax-M2.5": ["on"], "MiniMax-M2.5-highspeed": ["on"],
|
|
158
|
+
"MiniMax-M2.1": ["on"], "MiniMax-M1-40k": ["on"], "MiniMax-M1-80k": ["on"],
|
|
159
|
+
// MiniMax — lowercase aliases for OpenRouter
|
|
160
|
+
"minimax-m2.5": ["on"], "minimax-m2.1": ["on"], "minimax-m1": ["on"],
|
|
161
|
+
};
|
|
162
|
+
/** Return available thinking levels for a model, or empty array if not a thinking model. */
|
|
163
|
+
export function getThinkingLevels(model) {
|
|
164
|
+
return KNOWN_THINKING_LEVELS[model]
|
|
165
|
+
?? KNOWN_THINKING_LEVELS[normalizeModelId(model)]
|
|
166
|
+
?? [];
|
|
167
|
+
}
|
|
168
|
+
// ------------------------------------------------------------------
|
|
169
|
+
// Helper functions
|
|
170
|
+
// ------------------------------------------------------------------
|
|
171
|
+
/**
|
|
172
|
+
* Strip the vendor prefix from an OpenRouter-style model ID.
|
|
173
|
+
* e.g. "anthropic/claude-sonnet-4-6" → "claude-sonnet-4-6"
|
|
174
|
+
* If the model ID contains no "/", it is returned unchanged.
|
|
175
|
+
*/
|
|
176
|
+
export function normalizeModelId(model) {
|
|
177
|
+
const idx = model.lastIndexOf("/");
|
|
178
|
+
return idx >= 0 ? model.slice(idx + 1) : model;
|
|
179
|
+
}
|
|
180
|
+
/** Format a short user-facing model label for UI surfaces such as the status bar. */
|
|
181
|
+
export function formatDisplayModelName(provider, model) {
|
|
182
|
+
const safeProvider = String(provider ?? "").trim();
|
|
183
|
+
const safeModel = String(model ?? "").trim();
|
|
184
|
+
if (!safeModel)
|
|
185
|
+
return safeProvider;
|
|
186
|
+
if (safeProvider === "openrouter") {
|
|
187
|
+
return `openrouter/${normalizeModelId(safeModel)}`;
|
|
188
|
+
}
|
|
189
|
+
return safeModel;
|
|
190
|
+
}
|
|
191
|
+
/** Format a provider-scoped user-facing model label for status messages. */
|
|
192
|
+
export function formatScopedModelName(provider, model) {
|
|
193
|
+
const safeProvider = String(provider ?? "").trim();
|
|
194
|
+
const safeModel = String(model ?? "").trim();
|
|
195
|
+
if (!safeProvider)
|
|
196
|
+
return formatDisplayModelName(undefined, safeModel);
|
|
197
|
+
if (!safeModel)
|
|
198
|
+
return safeProvider;
|
|
199
|
+
if (safeProvider === "openrouter") {
|
|
200
|
+
return `openrouter/${normalizeModelId(safeModel)}`;
|
|
201
|
+
}
|
|
202
|
+
return `${safeProvider}/${safeModel}`;
|
|
203
|
+
}
|
|
204
|
+
/** Resolve effective context length. Priority: explicit > known lookup (exact then normalized) > 0. */
|
|
205
|
+
export function getContextLength(model, contextLength = 0) {
|
|
206
|
+
if (contextLength > 0)
|
|
207
|
+
return contextLength;
|
|
208
|
+
return KNOWN_CONTEXT_LENGTHS[model]
|
|
209
|
+
?? KNOWN_CONTEXT_LENGTHS[normalizeModelId(model)]
|
|
210
|
+
?? 0;
|
|
211
|
+
}
|
|
212
|
+
/** Resolve multimodal support. Priority: explicit > known lookup (exact then normalized) > false. */
|
|
213
|
+
export function getMultimodalSupport(model, explicit) {
|
|
214
|
+
if (explicit !== undefined)
|
|
215
|
+
return explicit;
|
|
216
|
+
return KNOWN_MULTIMODAL_MODELS.has(model)
|
|
217
|
+
|| KNOWN_MULTIMODAL_MODELS.has(normalizeModelId(model));
|
|
218
|
+
}
|
|
219
|
+
/** Resolve thinking/reasoning support. Priority: explicit > known lookup (exact then normalized) > false. */
|
|
220
|
+
export function getThinkingSupport(model, explicit) {
|
|
221
|
+
if (explicit !== undefined)
|
|
222
|
+
return explicit;
|
|
223
|
+
return KNOWN_THINKING_MODELS.has(model)
|
|
224
|
+
|| KNOWN_THINKING_MODELS.has(normalizeModelId(model));
|
|
225
|
+
}
|
|
226
|
+
/** Resolve native web search support. Priority: explicit > provider default > blacklist > true. */
|
|
227
|
+
export function getWebSearchSupport(model, explicit, provider) {
|
|
228
|
+
if (explicit !== undefined)
|
|
229
|
+
return explicit;
|
|
230
|
+
// OpenRouter: web search is a paid add-on, default to false.
|
|
231
|
+
// Users can explicitly enable via supports_web_search: true in config.
|
|
232
|
+
if (provider === "openrouter")
|
|
233
|
+
return false;
|
|
234
|
+
if (KNOWN_NO_WEB_SEARCH_MODELS.has(model)
|
|
235
|
+
|| KNOWN_NO_WEB_SEARCH_MODELS.has(normalizeModelId(model)))
|
|
236
|
+
return false;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
// ------------------------------------------------------------------
|
|
240
|
+
// Environment variable resolution
|
|
241
|
+
// ------------------------------------------------------------------
|
|
242
|
+
function resolveEnv(value) {
|
|
243
|
+
if (typeof value === "string" && value.startsWith("${") && value.endsWith("}")) {
|
|
244
|
+
const envName = value.slice(2, -1);
|
|
245
|
+
const resolved = process.env[envName];
|
|
246
|
+
if (resolved === undefined) {
|
|
247
|
+
throw new Error(`Environment variable '${envName}' is not set`);
|
|
248
|
+
}
|
|
249
|
+
return resolved;
|
|
250
|
+
}
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function parseEnvRef(value) {
|
|
254
|
+
if (typeof value === "string" && value.startsWith("${") && value.endsWith("}")) {
|
|
255
|
+
return value.slice(2, -1);
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
function hasResolvableApiKey(value) {
|
|
260
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
261
|
+
return false;
|
|
262
|
+
// OAuth token check
|
|
263
|
+
if (value === "oauth:openai-codex")
|
|
264
|
+
return hasOAuthTokens();
|
|
265
|
+
if (value.startsWith("${") && value.endsWith("}")) {
|
|
266
|
+
const envName = value.slice(2, -1);
|
|
267
|
+
const resolved = process.env[envName];
|
|
268
|
+
return typeof resolved === "string" && resolved.trim() !== "";
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
function requireConfigStringField(modelConfigName, cfg, field) {
|
|
273
|
+
const raw = cfg[field];
|
|
274
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
275
|
+
throw new Error(`Invalid model config '${modelConfigName}': missing required string field '${field}'`);
|
|
276
|
+
}
|
|
277
|
+
return raw;
|
|
278
|
+
}
|
|
279
|
+
function optionalConfigStringField(modelConfigName, cfg, field) {
|
|
280
|
+
const raw = cfg[field];
|
|
281
|
+
if (raw === undefined || raw === null)
|
|
282
|
+
return undefined;
|
|
283
|
+
if (typeof raw !== "string") {
|
|
284
|
+
throw new Error(`Invalid model config '${modelConfigName}': field '${field}' must be a string`);
|
|
285
|
+
}
|
|
286
|
+
return raw;
|
|
287
|
+
}
|
|
288
|
+
function optionalConfigNumberField(modelConfigName, cfg, field) {
|
|
289
|
+
const raw = cfg[field];
|
|
290
|
+
if (raw === undefined || raw === null)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (typeof raw !== "number" || Number.isNaN(raw)) {
|
|
293
|
+
throw new Error(`Invalid model config '${modelConfigName}': field '${field}' must be a number`);
|
|
294
|
+
}
|
|
295
|
+
return raw;
|
|
296
|
+
}
|
|
297
|
+
function optionalConfigBooleanField(modelConfigName, cfg, field) {
|
|
298
|
+
const raw = cfg[field];
|
|
299
|
+
if (raw === undefined || raw === null)
|
|
300
|
+
return undefined;
|
|
301
|
+
if (typeof raw !== "boolean") {
|
|
302
|
+
throw new Error(`Invalid model config '${modelConfigName}': field '${field}' must be a boolean`);
|
|
303
|
+
}
|
|
304
|
+
return raw;
|
|
305
|
+
}
|
|
306
|
+
// ------------------------------------------------------------------
|
|
307
|
+
// Config path resolution
|
|
308
|
+
// ------------------------------------------------------------------
|
|
309
|
+
/** Default home directory for LongerAgent configuration. */
|
|
310
|
+
export const LONGERAGENT_HOME_DIR = ".longeragent";
|
|
311
|
+
/**
|
|
312
|
+
* Discover config, templates, and skills paths.
|
|
313
|
+
*
|
|
314
|
+
* Discovery chain (highest priority first):
|
|
315
|
+
* 1. CLI flags (--config, --templates)
|
|
316
|
+
* 2. ~/.longeragent/
|
|
317
|
+
* 3. Current working directory
|
|
318
|
+
*/
|
|
319
|
+
export function resolveConfigPaths(opts) {
|
|
320
|
+
const home = join(homedir(), LONGERAGENT_HOME_DIR);
|
|
321
|
+
// --- Config ---
|
|
322
|
+
let configPath = null;
|
|
323
|
+
if (opts?.configFlag) {
|
|
324
|
+
configPath = existsSync(opts.configFlag) ? opts.configFlag : null;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
const homeConfig = join(home, "config.yaml");
|
|
328
|
+
const cwdConfig = join(process.cwd(), "config.yaml");
|
|
329
|
+
if (existsSync(homeConfig)) {
|
|
330
|
+
configPath = homeConfig;
|
|
331
|
+
}
|
|
332
|
+
else if (existsSync(cwdConfig)) {
|
|
333
|
+
configPath = cwdConfig;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// --- Templates ---
|
|
337
|
+
let templatesPath = null;
|
|
338
|
+
if (opts?.templatesFlag) {
|
|
339
|
+
templatesPath = isDir(opts.templatesFlag) ? opts.templatesFlag : null;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const homeTemplates = join(home, "agent_templates");
|
|
343
|
+
const cwdTemplates = join(process.cwd(), "agent_templates");
|
|
344
|
+
if (isDir(homeTemplates)) {
|
|
345
|
+
templatesPath = homeTemplates;
|
|
346
|
+
}
|
|
347
|
+
else if (isDir(cwdTemplates)) {
|
|
348
|
+
templatesPath = cwdTemplates;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// --- Prompts ---
|
|
352
|
+
let promptsPath = null;
|
|
353
|
+
if (templatesPath) {
|
|
354
|
+
// Look for prompts/ sibling to templates directory
|
|
355
|
+
const siblingPrompts = join(join(templatesPath, ".."), "prompts");
|
|
356
|
+
if (isDir(siblingPrompts)) {
|
|
357
|
+
promptsPath = siblingPrompts;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!promptsPath) {
|
|
361
|
+
const homePrompts = join(home, "prompts");
|
|
362
|
+
const cwdPrompts = join(process.cwd(), "prompts");
|
|
363
|
+
if (isDir(homePrompts)) {
|
|
364
|
+
promptsPath = homePrompts;
|
|
365
|
+
}
|
|
366
|
+
else if (isDir(cwdPrompts)) {
|
|
367
|
+
promptsPath = cwdPrompts;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// --- Skills ---
|
|
371
|
+
let skillsPath = null;
|
|
372
|
+
if (templatesPath) {
|
|
373
|
+
// Look for skills/ sibling to templates directory
|
|
374
|
+
const siblingSkills = join(join(templatesPath, ".."), "skills");
|
|
375
|
+
if (isDir(siblingSkills)) {
|
|
376
|
+
skillsPath = siblingSkills;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (!skillsPath) {
|
|
380
|
+
const homeSkills = join(home, "skills");
|
|
381
|
+
const cwdSkills = join(process.cwd(), "skills");
|
|
382
|
+
if (isDir(homeSkills)) {
|
|
383
|
+
skillsPath = homeSkills;
|
|
384
|
+
}
|
|
385
|
+
else if (isDir(cwdSkills)) {
|
|
386
|
+
skillsPath = cwdSkills;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return { configPath, templatesPath, promptsPath, skillsPath, homeDir: home };
|
|
390
|
+
}
|
|
391
|
+
function isDir(p) {
|
|
392
|
+
try {
|
|
393
|
+
return existsSync(p) && statSync(p).isDirectory();
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// ------------------------------------------------------------------
|
|
400
|
+
// Bundled assets path
|
|
401
|
+
// ------------------------------------------------------------------
|
|
402
|
+
/** Return the root directory of the installed package (parent of dist/). */
|
|
403
|
+
export function getBundledAssetsDir() {
|
|
404
|
+
// This file compiles to dist/config.js, so ".." reaches the package root.
|
|
405
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
406
|
+
return join(dirname(thisFile), "..");
|
|
407
|
+
}
|
|
408
|
+
// ------------------------------------------------------------------
|
|
409
|
+
// Provider default base URLs
|
|
410
|
+
// ------------------------------------------------------------------
|
|
411
|
+
const PROVIDER_URLS = {
|
|
412
|
+
"openai-codex": "https://chatgpt.com/backend-api/codex",
|
|
413
|
+
"kimi": "https://api.moonshot.ai/v1",
|
|
414
|
+
"kimi-cn": "https://api.moonshot.cn/v1",
|
|
415
|
+
"kimi-ai": "https://api.moonshot.ai/v1",
|
|
416
|
+
"kimi-code": "https://api.kimi.com/coding/v1",
|
|
417
|
+
"glm": "https://open.bigmodel.cn/api/paas/v4",
|
|
418
|
+
"glm-intl": "https://api.z.ai/api/paas/v4",
|
|
419
|
+
"glm-code": "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
420
|
+
"glm-intl-code": "https://api.z.ai/api/coding/paas/v4",
|
|
421
|
+
"minimax": "https://api.minimax.io/v1",
|
|
422
|
+
"minimax-cn": "https://api.minimaxi.com/v1",
|
|
423
|
+
"openrouter": "https://openrouter.ai/api/v1",
|
|
424
|
+
};
|
|
425
|
+
// ------------------------------------------------------------------
|
|
426
|
+
// Config class
|
|
427
|
+
// ------------------------------------------------------------------
|
|
428
|
+
export class Config {
|
|
429
|
+
_data;
|
|
430
|
+
_rawModels;
|
|
431
|
+
_models = new Map();
|
|
432
|
+
_mcpServers;
|
|
433
|
+
constructor(opts) {
|
|
434
|
+
if (opts?.raw) {
|
|
435
|
+
this._data = opts.raw;
|
|
436
|
+
}
|
|
437
|
+
else if (opts?.path) {
|
|
438
|
+
this._data = this._loadYaml(opts.path);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
this._data = { models: {} };
|
|
442
|
+
}
|
|
443
|
+
this._rawModels = this._data["models"] ?? {};
|
|
444
|
+
this._mcpServers = this._parseMcpServers();
|
|
445
|
+
}
|
|
446
|
+
_loadYaml(path) {
|
|
447
|
+
const content = readFileSync(path, "utf-8");
|
|
448
|
+
return yaml.load(content) ?? {};
|
|
449
|
+
}
|
|
450
|
+
_buildModel(name, cfg) {
|
|
451
|
+
const provider = requireConfigStringField(name, cfg, "provider");
|
|
452
|
+
const modelName = requireConfigStringField(name, cfg, "model");
|
|
453
|
+
const apiKeyRaw = requireConfigStringField(name, cfg, "api_key");
|
|
454
|
+
const baseUrl = optionalConfigStringField(name, cfg, "base_url") || PROVIDER_URLS[provider];
|
|
455
|
+
const apiKeyEnv = parseEnvRef(apiKeyRaw);
|
|
456
|
+
const resolvedApiKey = (() => {
|
|
457
|
+
// OAuth token resolution
|
|
458
|
+
if (apiKeyRaw === "oauth:openai-codex") {
|
|
459
|
+
const token = readOAuthAccessToken();
|
|
460
|
+
if (!token) {
|
|
461
|
+
throw new Error(`Missing OAuth token for model config '${name}' (${provider}/${modelName}): ` +
|
|
462
|
+
"no OpenAI OAuth credentials stored.\n" +
|
|
463
|
+
"Run 'longeragent oauth' to log in with your ChatGPT account.");
|
|
464
|
+
}
|
|
465
|
+
return token;
|
|
466
|
+
}
|
|
467
|
+
if (!apiKeyEnv)
|
|
468
|
+
return apiKeyRaw;
|
|
469
|
+
const fromEnv = process.env[apiKeyEnv];
|
|
470
|
+
if (typeof fromEnv === "string" && fromEnv.trim() !== "") {
|
|
471
|
+
return fromEnv;
|
|
472
|
+
}
|
|
473
|
+
throw new Error(`Missing API key for model config '${name}' (${provider}/${modelName}): ` +
|
|
474
|
+
`environment variable '${apiKeyEnv}' is not set.\n` +
|
|
475
|
+
"Run 'longeragent init' to configure API keys, or export that variable and retry.");
|
|
476
|
+
})();
|
|
477
|
+
const knownKeys = new Set([
|
|
478
|
+
"provider", "model", "api_key", "base_url",
|
|
479
|
+
"temperature", "max_tokens", "context_length",
|
|
480
|
+
"supports_multimodal", "supports_thinking", "thinking_budget",
|
|
481
|
+
"supports_web_search",
|
|
482
|
+
]);
|
|
483
|
+
const extra = {};
|
|
484
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
485
|
+
if (!knownKeys.has(k))
|
|
486
|
+
extra[k] = v;
|
|
487
|
+
}
|
|
488
|
+
const explicitCtxLen = optionalConfigNumberField(name, cfg, "context_length") ?? 0;
|
|
489
|
+
const temperature = optionalConfigNumberField(name, cfg, "temperature") ?? 0.7;
|
|
490
|
+
const maxTokens = optionalConfigNumberField(name, cfg, "max_tokens") ?? 32_000;
|
|
491
|
+
const thinkingBudget = optionalConfigNumberField(name, cfg, "thinking_budget") ?? 0;
|
|
492
|
+
const supportsMultimodalOverride = optionalConfigBooleanField(name, cfg, "supports_multimodal");
|
|
493
|
+
const supportsThinkingOverride = optionalConfigBooleanField(name, cfg, "supports_thinking");
|
|
494
|
+
const supportsWebSearchOverride = optionalConfigBooleanField(name, cfg, "supports_web_search");
|
|
495
|
+
return {
|
|
496
|
+
name,
|
|
497
|
+
provider,
|
|
498
|
+
model: modelName,
|
|
499
|
+
apiKey: resolvedApiKey,
|
|
500
|
+
baseUrl,
|
|
501
|
+
temperature,
|
|
502
|
+
maxTokens,
|
|
503
|
+
contextLength: getContextLength(modelName, explicitCtxLen),
|
|
504
|
+
supportsMultimodal: getMultimodalSupport(modelName, supportsMultimodalOverride),
|
|
505
|
+
supportsThinking: getThinkingSupport(modelName, supportsThinkingOverride),
|
|
506
|
+
thinkingBudget,
|
|
507
|
+
supportsWebSearch: getWebSearchSupport(modelName, supportsWebSearchOverride, provider),
|
|
508
|
+
extra,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
getModel(name) {
|
|
512
|
+
const cached = this._models.get(name);
|
|
513
|
+
if (cached)
|
|
514
|
+
return cached;
|
|
515
|
+
const raw = this._rawModels[name];
|
|
516
|
+
if (!raw) {
|
|
517
|
+
const available = Object.keys(this._rawModels).join(", ") || "(none)";
|
|
518
|
+
throw new Error(`Model config '${name}' not found. Available: ${available}`);
|
|
519
|
+
}
|
|
520
|
+
const model = this._buildModel(name, raw);
|
|
521
|
+
this._models.set(name, model);
|
|
522
|
+
return model;
|
|
523
|
+
}
|
|
524
|
+
get modelNames() {
|
|
525
|
+
return Object.keys(this._rawModels);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Return raw model entries without resolving env vars.
|
|
529
|
+
* Useful for UI that needs to show missing API keys instead of throwing.
|
|
530
|
+
*/
|
|
531
|
+
listModelEntries() {
|
|
532
|
+
const out = [];
|
|
533
|
+
for (const [name, cfg] of Object.entries(this._rawModels)) {
|
|
534
|
+
const provider = typeof cfg["provider"] === "string" ? cfg["provider"] : "";
|
|
535
|
+
const model = typeof cfg["model"] === "string" ? cfg["model"] : "";
|
|
536
|
+
const apiKeyRaw = typeof cfg["api_key"] === "string" ? cfg["api_key"] : "";
|
|
537
|
+
out.push({
|
|
538
|
+
name,
|
|
539
|
+
provider,
|
|
540
|
+
model,
|
|
541
|
+
apiKeyRaw,
|
|
542
|
+
hasResolvedApiKey: hasResolvableApiKey(apiKeyRaw),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return out;
|
|
546
|
+
}
|
|
547
|
+
/** Find the first model config name matching provider + model ID exactly. */
|
|
548
|
+
findModelConfigName(provider, model) {
|
|
549
|
+
for (const [name, cfg] of Object.entries(this._rawModels)) {
|
|
550
|
+
if (cfg["provider"] === provider && cfg["model"] === model) {
|
|
551
|
+
return name;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Insert or replace a raw model config at runtime.
|
|
558
|
+
* This mutates in-memory config only; it does not write config.yaml.
|
|
559
|
+
*/
|
|
560
|
+
upsertModelRaw(name, cfg) {
|
|
561
|
+
this._rawModels[name] = { ...cfg };
|
|
562
|
+
if (!this._data["models"] || typeof this._data["models"] !== "object") {
|
|
563
|
+
this._data["models"] = {};
|
|
564
|
+
}
|
|
565
|
+
this._data["models"][name] = this._rawModels[name];
|
|
566
|
+
this._models.delete(name);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Return the best default model name.
|
|
570
|
+
* Priority: explicit default_model > first with resolvable API key > first model.
|
|
571
|
+
*/
|
|
572
|
+
get defaultModel() {
|
|
573
|
+
// 1. Explicit config key
|
|
574
|
+
const explicit = this._data["default_model"];
|
|
575
|
+
if (explicit && explicit in this._rawModels)
|
|
576
|
+
return explicit;
|
|
577
|
+
// 2. First model with resolvable API key
|
|
578
|
+
for (const [name, cfg] of Object.entries(this._rawModels)) {
|
|
579
|
+
const apiKeyRaw = cfg["api_key"] ?? "";
|
|
580
|
+
if (typeof apiKeyRaw === "string" && apiKeyRaw.startsWith("${") && apiKeyRaw.endsWith("}")) {
|
|
581
|
+
const envName = apiKeyRaw.slice(2, -1);
|
|
582
|
+
if (process.env[envName])
|
|
583
|
+
return name;
|
|
584
|
+
}
|
|
585
|
+
else if (apiKeyRaw) {
|
|
586
|
+
return name; // literal key, always available
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// 3. Fallback: first model
|
|
590
|
+
const names = Object.keys(this._rawModels);
|
|
591
|
+
return names.length > 0 ? names[0] : undefined;
|
|
592
|
+
}
|
|
593
|
+
/** Return the sub_agent_model config name, or undefined if not set. */
|
|
594
|
+
get subAgentModelName() {
|
|
595
|
+
return this._data["sub_agent_model"];
|
|
596
|
+
}
|
|
597
|
+
/** Return path overrides from config.yaml `paths:` section. */
|
|
598
|
+
get pathOverrides() {
|
|
599
|
+
const raw = this._data["paths"];
|
|
600
|
+
if (!raw)
|
|
601
|
+
return {};
|
|
602
|
+
const result = {};
|
|
603
|
+
if (raw["project_root"])
|
|
604
|
+
result.projectRoot = raw["project_root"];
|
|
605
|
+
if (raw["session_artifacts"])
|
|
606
|
+
result.sessionArtifacts = raw["session_artifacts"];
|
|
607
|
+
if (raw["system_data"])
|
|
608
|
+
result.systemData = raw["system_data"];
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
// ------------------------------------------------------------------
|
|
612
|
+
// MCP server configuration
|
|
613
|
+
// ------------------------------------------------------------------
|
|
614
|
+
_parseMcpServers() {
|
|
615
|
+
const raw = this._data["mcp_servers"];
|
|
616
|
+
if (!raw)
|
|
617
|
+
return [];
|
|
618
|
+
const servers = [];
|
|
619
|
+
for (const [name, cfg] of Object.entries(raw)) {
|
|
620
|
+
const env = {};
|
|
621
|
+
const rawEnv = cfg["env"];
|
|
622
|
+
if (rawEnv) {
|
|
623
|
+
for (const [k, v] of Object.entries(rawEnv)) {
|
|
624
|
+
env[k] = resolveEnv(String(v));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
servers.push({
|
|
628
|
+
name,
|
|
629
|
+
transport: cfg["transport"] ?? "stdio",
|
|
630
|
+
command: cfg["command"] ?? "",
|
|
631
|
+
args: cfg["args"] ?? [],
|
|
632
|
+
url: cfg["url"] ?? "",
|
|
633
|
+
env,
|
|
634
|
+
envAllowlist: Array.isArray(cfg["env_allowlist"])
|
|
635
|
+
? cfg["env_allowlist"].map((v) => String(v))
|
|
636
|
+
: undefined,
|
|
637
|
+
sensitiveTools: Array.isArray(cfg["sensitive_tools"])
|
|
638
|
+
? cfg["sensitive_tools"].map((v) => String(v))
|
|
639
|
+
: undefined,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
return servers;
|
|
643
|
+
}
|
|
644
|
+
get mcpServerConfigs() {
|
|
645
|
+
return this._mcpServers;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
//# sourceMappingURL=config.js.map
|