@oisheek_c/codeforge 1.0.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.
- package/LICENSE +0 -0
- package/README.md +7 -0
- package/apps/cli/bin/codeforge.cmd +2 -0
- package/apps/cli/src/bootstrap.js +115 -0
- package/apps/cli/src/cli.js +189 -0
- package/apps/cli/src/dev/git.js +17 -0
- package/apps/cli/src/dev/index.js +68 -0
- package/apps/cli/src/dev/provider.js +48 -0
- package/apps/cli/src/dev/scanner.js +14 -0
- package/apps/cli/src/index.js +31 -0
- package/bin/codeforge.js +2 -0
- package/package.json +63 -0
- package/packages/agent/contextBuilder.js +234 -0
- package/packages/agent/executor.js +1729 -0
- package/packages/agent/fallback.js +129 -0
- package/packages/agent/index.js +26 -0
- package/packages/agent/intent.js +73 -0
- package/packages/agent/modelSelector.js +469 -0
- package/packages/agent/planner.js +379 -0
- package/packages/agent/rag.js +412 -0
- package/packages/agent/ragSelector.js +570 -0
- package/packages/agent/router.js +121 -0
- package/packages/agent/thinking.js +54 -0
- package/packages/agent/toolCalls.js +258 -0
- package/packages/config/config.js +391 -0
- package/packages/config/defaults.js +72 -0
- package/packages/config/index.js +2 -0
- package/packages/core/approvalPolicy.js +140 -0
- package/packages/core/context.js +0 -0
- package/packages/core/message.js +0 -0
- package/packages/core/plan.js +0 -0
- package/packages/core/reasoning.js +0 -0
- package/packages/core/result.js +0 -0
- package/packages/core/retrieval.js +0 -0
- package/packages/core/tool.js +536 -0
- package/packages/git/branch.js +7 -0
- package/packages/git/commit.js +16 -0
- package/packages/git/diff.js +15 -0
- package/packages/git/git.js +35 -0
- package/packages/git/index.js +6 -0
- package/packages/git/remote.js +9 -0
- package/packages/git/status.js +11 -0
- package/packages/memory/index.js +0 -0
- package/packages/memory/project.js +0 -0
- package/packages/memory/session.js +0 -0
- package/packages/prompts/builder.js +43 -0
- package/packages/prompts/coder.js +38 -0
- package/packages/prompts/index.js +5 -0
- package/packages/prompts/planner.js +49 -0
- package/packages/prompts/reviewer.js +38 -0
- package/packages/prompts/system.js +94 -0
- package/packages/providers/index.js +31 -0
- package/packages/providers/openrouter.js +380 -0
- package/packages/reasoning/budget.js +0 -0
- package/packages/reasoning/chain.js +0 -0
- package/packages/reasoning/coder.js +0 -0
- package/packages/reasoning/evaluator.js +0 -0
- package/packages/reasoning/index.js +8 -0
- package/packages/reasoning/planner.js +0 -0
- package/packages/reasoning/reflector.js +0 -0
- package/packages/reasoning/reviewer.js +0 -0
- package/packages/reasoning/router.js +0 -0
- package/packages/retrieval/budget.js +92 -0
- package/packages/retrieval/cache.js +84 -0
- package/packages/retrieval/embeddings.js +147 -0
- package/packages/retrieval/graph.js +151 -0
- package/packages/retrieval/imports.js +74 -0
- package/packages/retrieval/index.js +9 -0
- package/packages/retrieval/parser/extractors/calls.js +43 -0
- package/packages/retrieval/parser/extractors/comments.js +49 -0
- package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
- package/packages/retrieval/parser/extractors/exports.js +71 -0
- package/packages/retrieval/parser/extractors/imports.js +40 -0
- package/packages/retrieval/parser/extractors/index.js +12 -0
- package/packages/retrieval/parser/extractors/metrics.js +78 -0
- package/packages/retrieval/parser/extractors/symbols.js +51 -0
- package/packages/retrieval/parser/extractors/todos.js +53 -0
- package/packages/retrieval/parser/helpers.js +138 -0
- package/packages/retrieval/parser/index.js +10 -0
- package/packages/retrieval/parser/languages/c.js +14 -0
- package/packages/retrieval/parser/languages/cpp.js +21 -0
- package/packages/retrieval/parser/languages/csharp.js +14 -0
- package/packages/retrieval/parser/languages/go.js +14 -0
- package/packages/retrieval/parser/languages/index.js +12 -0
- package/packages/retrieval/parser/languages/java.js +14 -0
- package/packages/retrieval/parser/languages/javascript.js +14 -0
- package/packages/retrieval/parser/languages/php.js +14 -0
- package/packages/retrieval/parser/languages/python.js +14 -0
- package/packages/retrieval/parser/languages/ruby.js +14 -0
- package/packages/retrieval/parser/languages/rust.js +14 -0
- package/packages/retrieval/parser/languages/swift.js +14 -0
- package/packages/retrieval/parser/languages/typescript.js +14 -0
- package/packages/retrieval/parser/languages.js +218 -0
- package/packages/retrieval/parser/parseFile.js +79 -0
- package/packages/retrieval/parser/parseRepository.js +78 -0
- package/packages/retrieval/parser/queries/c.js +148 -0
- package/packages/retrieval/parser/queries/cpp.js +198 -0
- package/packages/retrieval/parser/queries/csharp.js +151 -0
- package/packages/retrieval/parser/queries/go.js +124 -0
- package/packages/retrieval/parser/queries/index.js +35 -0
- package/packages/retrieval/parser/queries/java.js +128 -0
- package/packages/retrieval/parser/queries/javascript.js +181 -0
- package/packages/retrieval/parser/queries/php.js +140 -0
- package/packages/retrieval/parser/queries/python.js +110 -0
- package/packages/retrieval/parser/queries/ruby.js +105 -0
- package/packages/retrieval/parser/queries/rust.js +175 -0
- package/packages/retrieval/parser/queries/swift.js +134 -0
- package/packages/retrieval/parser/queries/typescript.js +202 -0
- package/packages/retrieval/parser/registry.js +107 -0
- package/packages/retrieval/parser/traverse.js +82 -0
- package/packages/retrieval/parser/types.js +239 -0
- package/packages/retrieval/parser.js +78 -0
- package/packages/retrieval/ranker.js +73 -0
- package/packages/retrieval/retriever.js +910 -0
- package/packages/retrieval/symbols.js +145 -0
- package/packages/scanner/detect.js +69 -0
- package/packages/scanner/ignore.js +12 -0
- package/packages/scanner/index.js +2 -0
- package/packages/scanner/root.js +21 -0
- package/packages/scanner/scanner.js +35 -0
- package/packages/scanner/tree.js +45 -0
- package/packages/terminal/activity.js +514 -0
- package/packages/terminal/banner.js +34 -0
- package/packages/terminal/colors.js +16 -0
- package/packages/terminal/dashboard.js +94 -0
- package/packages/terminal/index.js +19 -0
- package/packages/terminal/logger.js +43 -0
- package/packages/terminal/prompt.js +11 -0
- package/packages/terminal/renderer.js +1209 -0
- package/packages/terminal/spinner.js +9 -0
- package/packages/tools/editFile.js +261 -0
- package/packages/tools/filesystem.js +82 -0
- package/packages/tools/index.js +24 -0
- package/packages/tools/readFile.js +101 -0
- package/packages/tools/registry.js +227 -0
- package/packages/tools/searchFiles.js +322 -0
- package/packages/tools/shell.js +352 -0
- package/packages/tools/writeFile.js +212 -0
- package/packages/utils/constants.js +0 -0
- package/packages/utils/helpers.js +0 -0
- package/packages/utils/logger.js +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const DEFAULT_OPTIONS = Object.freeze({
|
|
2
|
+
enabled: false,
|
|
3
|
+
mode: "standard",
|
|
4
|
+
budget: "medium",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export function configureThinking({
|
|
8
|
+
plan,
|
|
9
|
+
route,
|
|
10
|
+
}) {
|
|
11
|
+
if (!plan?.requiresThinking) {
|
|
12
|
+
return DEFAULT_OPTIONS;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let mode = "standard";
|
|
16
|
+
let budget = "medium";
|
|
17
|
+
|
|
18
|
+
switch (plan.intent) {
|
|
19
|
+
case "plan":
|
|
20
|
+
mode = "deep";
|
|
21
|
+
budget = "high";
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
case "debug":
|
|
25
|
+
mode = "deep";
|
|
26
|
+
budget = "high";
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case "review":
|
|
30
|
+
mode = "deep";
|
|
31
|
+
budget = "high";
|
|
32
|
+
break;
|
|
33
|
+
|
|
34
|
+
case "refactor":
|
|
35
|
+
mode = "deep";
|
|
36
|
+
budget = "high";
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case "code":
|
|
40
|
+
mode = "balanced";
|
|
41
|
+
budget = "medium";
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
default:
|
|
45
|
+
mode = "standard";
|
|
46
|
+
budget = "medium";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
enabled: Boolean(route?.thinking),
|
|
51
|
+
mode,
|
|
52
|
+
budget,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
function createToolCallError(
|
|
2
|
+
code,
|
|
3
|
+
message,
|
|
4
|
+
details = null
|
|
5
|
+
) {
|
|
6
|
+
const error = new Error(message);
|
|
7
|
+
|
|
8
|
+
error.code = code;
|
|
9
|
+
error.details = details;
|
|
10
|
+
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseArguments(value) {
|
|
15
|
+
if (value == null || value === "") {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
typeof value === "object" &&
|
|
21
|
+
!Array.isArray(value)
|
|
22
|
+
) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof value !== "string") {
|
|
27
|
+
throw createToolCallError(
|
|
28
|
+
"invalid_tool_arguments",
|
|
29
|
+
"Tool call arguments must be a JSON object or JSON string."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let parsed;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(value);
|
|
37
|
+
} catch {
|
|
38
|
+
throw createToolCallError(
|
|
39
|
+
"invalid_tool_arguments",
|
|
40
|
+
"Tool call arguments contain invalid JSON."
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
parsed == null ||
|
|
46
|
+
typeof parsed !== "object" ||
|
|
47
|
+
Array.isArray(parsed)
|
|
48
|
+
) {
|
|
49
|
+
throw createToolCallError(
|
|
50
|
+
"invalid_tool_arguments",
|
|
51
|
+
"Tool call arguments must resolve to an object."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function normalizeToolCall(
|
|
59
|
+
toolCall
|
|
60
|
+
) {
|
|
61
|
+
if (
|
|
62
|
+
!toolCall ||
|
|
63
|
+
typeof toolCall !== "object"
|
|
64
|
+
) {
|
|
65
|
+
throw createToolCallError(
|
|
66
|
+
"invalid_tool_call",
|
|
67
|
+
"Tool call must be an object."
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const id =
|
|
72
|
+
toolCall.id ?? null;
|
|
73
|
+
|
|
74
|
+
const type =
|
|
75
|
+
toolCall.type ?? "function";
|
|
76
|
+
|
|
77
|
+
const fn =
|
|
78
|
+
toolCall.function;
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
!fn ||
|
|
82
|
+
typeof fn !== "object"
|
|
83
|
+
) {
|
|
84
|
+
throw createToolCallError(
|
|
85
|
+
"invalid_tool_call",
|
|
86
|
+
"Tool call does not contain a function definition."
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
typeof fn.name !== "string" ||
|
|
92
|
+
fn.name.trim().length === 0
|
|
93
|
+
) {
|
|
94
|
+
throw createToolCallError(
|
|
95
|
+
"invalid_tool_name",
|
|
96
|
+
"Tool call does not contain a valid tool name."
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
type,
|
|
103
|
+
name: fn.name.trim(),
|
|
104
|
+
arguments:
|
|
105
|
+
parseArguments(
|
|
106
|
+
fn.arguments
|
|
107
|
+
),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function normalizeToolCalls(
|
|
112
|
+
toolCalls = []
|
|
113
|
+
) {
|
|
114
|
+
if (toolCalls == null) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!Array.isArray(toolCalls)) {
|
|
119
|
+
throw createToolCallError(
|
|
120
|
+
"invalid_tool_calls",
|
|
121
|
+
"Tool calls must be an array."
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return toolCalls.map(
|
|
126
|
+
normalizeToolCall
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function resolveToolCalls(
|
|
131
|
+
toolCalls,
|
|
132
|
+
registry
|
|
133
|
+
) {
|
|
134
|
+
const normalized =
|
|
135
|
+
normalizeToolCalls(toolCalls);
|
|
136
|
+
|
|
137
|
+
if (normalized.length === 0) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
!registry ||
|
|
143
|
+
typeof registry.get !== "function"
|
|
144
|
+
) {
|
|
145
|
+
throw createToolCallError(
|
|
146
|
+
"tool_registry_required",
|
|
147
|
+
"A tool registry is required to resolve tool calls."
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return normalized.map(
|
|
152
|
+
(toolCall) => {
|
|
153
|
+
const tool =
|
|
154
|
+
registry.get(
|
|
155
|
+
toolCall.name
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (!tool) {
|
|
159
|
+
const availableTools =
|
|
160
|
+
typeof registry.describe === "function"
|
|
161
|
+
? registry
|
|
162
|
+
.describe()
|
|
163
|
+
.map((item) => item?.name)
|
|
164
|
+
.filter(Boolean)
|
|
165
|
+
: [];
|
|
166
|
+
|
|
167
|
+
const availableToolText =
|
|
168
|
+
availableTools.length > 0
|
|
169
|
+
? ` Available tools: ${availableTools.join(", ")}.`
|
|
170
|
+
: "";
|
|
171
|
+
|
|
172
|
+
throw createToolCallError(
|
|
173
|
+
"tool_not_found",
|
|
174
|
+
`Tool is not registered: ${toolCall.name}.${availableToolText} Use only registered tools.`,
|
|
175
|
+
{
|
|
176
|
+
tool:
|
|
177
|
+
toolCall.name,
|
|
178
|
+
|
|
179
|
+
availableTools,
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
...toolCall,
|
|
186
|
+
tool,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function createToolCallFailure({
|
|
194
|
+
toolCall,
|
|
195
|
+
error,
|
|
196
|
+
}) {
|
|
197
|
+
const id =
|
|
198
|
+
typeof toolCall?.id === "string" &&
|
|
199
|
+
toolCall.id.trim().length > 0
|
|
200
|
+
? toolCall.id
|
|
201
|
+
: `invalid_tool_call_${Date.now()}_${Math.random()
|
|
202
|
+
.toString(36)
|
|
203
|
+
.slice(2, 10)}`;
|
|
204
|
+
|
|
205
|
+
const name =
|
|
206
|
+
typeof toolCall?.function?.name === "string"
|
|
207
|
+
? toolCall.function.name.trim()
|
|
208
|
+
: null;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
id,
|
|
212
|
+
name,
|
|
213
|
+
arguments: null,
|
|
214
|
+
|
|
215
|
+
result: {
|
|
216
|
+
success: false,
|
|
217
|
+
tool: name,
|
|
218
|
+
|
|
219
|
+
output: null,
|
|
220
|
+
|
|
221
|
+
error: {
|
|
222
|
+
code:
|
|
223
|
+
error?.code ??
|
|
224
|
+
"invalid_tool_call",
|
|
225
|
+
|
|
226
|
+
message:
|
|
227
|
+
error?.message ??
|
|
228
|
+
"Invalid tool call.",
|
|
229
|
+
|
|
230
|
+
details:
|
|
231
|
+
error?.details ?? null,
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
metadata: {
|
|
235
|
+
source: "model",
|
|
236
|
+
sideEffect: "none",
|
|
237
|
+
approval: "never",
|
|
238
|
+
|
|
239
|
+
authorization: {
|
|
240
|
+
allowed: false,
|
|
241
|
+
code:
|
|
242
|
+
error?.code ??
|
|
243
|
+
"invalid_tool_call",
|
|
244
|
+
reason:
|
|
245
|
+
error?.message ??
|
|
246
|
+
"Invalid tool call.",
|
|
247
|
+
requiresApproval: false,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
timing: {
|
|
252
|
+
startedAt: Date.now(),
|
|
253
|
+
completedAt: Date.now(),
|
|
254
|
+
durationMs: 0,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
|
|
5
|
+
import { defaultConfig } from "./defaults.js";
|
|
6
|
+
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), ".codeforge");
|
|
8
|
+
const CURRENT_CONFIG_VERSION = 2;
|
|
9
|
+
|
|
10
|
+
const MODEL_ROLES = [
|
|
11
|
+
"fast",
|
|
12
|
+
"general",
|
|
13
|
+
"coding",
|
|
14
|
+
"heavyCoding",
|
|
15
|
+
"planner",
|
|
16
|
+
"subagent",
|
|
17
|
+
"vision",
|
|
18
|
+
"documentVision",
|
|
19
|
+
"embedding",
|
|
20
|
+
"reranker",
|
|
21
|
+
"fallback",
|
|
22
|
+
"emergency",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const SELECTOR_ROLES = [
|
|
26
|
+
"rag",
|
|
27
|
+
"model",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
31
|
+
const SESSION_FILE = path.join(CONFIG_DIR, "session.json");
|
|
32
|
+
|
|
33
|
+
const CACHE_DIR = path.join(CONFIG_DIR, "cache");
|
|
34
|
+
const LOGS_DIR = path.join(CONFIG_DIR, "logs");
|
|
35
|
+
const HISTORY_DIR = path.join(CONFIG_DIR, "history");
|
|
36
|
+
|
|
37
|
+
function ensureConfigDirectory() {
|
|
38
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
39
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
40
|
+
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
41
|
+
fs.mkdirSync(HISTORY_DIR, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ensureConfigFile() {
|
|
45
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
CONFIG_FILE,
|
|
48
|
+
JSON.stringify(
|
|
49
|
+
{
|
|
50
|
+
...defaultConfig,
|
|
51
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
52
|
+
},
|
|
53
|
+
null,
|
|
54
|
+
2
|
|
55
|
+
),
|
|
56
|
+
"utf8"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function ensureSessionFile() {
|
|
62
|
+
if (!fs.existsSync(SESSION_FILE)) {
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
SESSION_FILE,
|
|
65
|
+
JSON.stringify({}, null, 2),
|
|
66
|
+
"utf8"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function validateConfig(config) {
|
|
72
|
+
if (
|
|
73
|
+
!config ||
|
|
74
|
+
typeof config !== "object" ||
|
|
75
|
+
Array.isArray(config)
|
|
76
|
+
) {
|
|
77
|
+
throw new TypeError(
|
|
78
|
+
"Configuration must be an object."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
typeof config.provider !== "string" ||
|
|
84
|
+
config.provider.trim().length === 0
|
|
85
|
+
) {
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
"Configuration provider must be a non-empty string."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
!config.models ||
|
|
93
|
+
typeof config.models !== "object" ||
|
|
94
|
+
Array.isArray(config.models)
|
|
95
|
+
) {
|
|
96
|
+
throw new TypeError(
|
|
97
|
+
"Configuration models must be an object."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const role of MODEL_ROLES) {
|
|
102
|
+
if (
|
|
103
|
+
typeof config.models[role] !== "string" ||
|
|
104
|
+
config.models[role].trim().length === 0
|
|
105
|
+
) {
|
|
106
|
+
throw new TypeError(
|
|
107
|
+
`Configuration model "${role}" must be a non-empty string.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
!config.selectors ||
|
|
114
|
+
typeof config.selectors !== "object" ||
|
|
115
|
+
Array.isArray(config.selectors)
|
|
116
|
+
) {
|
|
117
|
+
throw new TypeError(
|
|
118
|
+
"Configuration selectors must be an object."
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
typeof config.selectors.provider !== "string" ||
|
|
124
|
+
config.selectors.provider.trim().length === 0
|
|
125
|
+
) {
|
|
126
|
+
throw new TypeError(
|
|
127
|
+
"Configuration selector provider must be a non-empty string."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const field of SELECTOR_ROLES) {
|
|
132
|
+
if (
|
|
133
|
+
typeof config.selectors[field] !== "string" ||
|
|
134
|
+
config.selectors[field].trim().length === 0
|
|
135
|
+
) {
|
|
136
|
+
throw new TypeError(
|
|
137
|
+
`Configuration selector "${field}" must be a non-empty string.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const field of [
|
|
143
|
+
"maxTokens",
|
|
144
|
+
"modelMaxTokens",
|
|
145
|
+
]) {
|
|
146
|
+
if (
|
|
147
|
+
!Number.isFinite(config.selectors[field]) ||
|
|
148
|
+
config.selectors[field] <= 0
|
|
149
|
+
) {
|
|
150
|
+
throw new TypeError(
|
|
151
|
+
`Configuration selector "${field}" must be a positive number.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!Number.isFinite(config.temperature)) {
|
|
157
|
+
throw new TypeError(
|
|
158
|
+
"Configuration temperature must be a finite number."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
!Number.isFinite(config.maxTokens) ||
|
|
164
|
+
config.maxTokens <= 0
|
|
165
|
+
) {
|
|
166
|
+
throw new TypeError(
|
|
167
|
+
"Configuration maxTokens must be a positive number."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Migrate an existing configuration without destroying
|
|
176
|
+
* explicit user customizations.
|
|
177
|
+
*
|
|
178
|
+
* Legacy values that exactly match the old defaults are
|
|
179
|
+
* replaced with the current defaults.
|
|
180
|
+
*
|
|
181
|
+
* Values that differ from the old defaults are preserved.
|
|
182
|
+
*/
|
|
183
|
+
export function migrateConfig(config) {
|
|
184
|
+
if (
|
|
185
|
+
!config ||
|
|
186
|
+
typeof config !== "object" ||
|
|
187
|
+
Array.isArray(config)
|
|
188
|
+
) {
|
|
189
|
+
return {
|
|
190
|
+
config: {
|
|
191
|
+
...defaultConfig,
|
|
192
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
193
|
+
},
|
|
194
|
+
migrated: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const version = Number(config.configVersion ?? 0);
|
|
199
|
+
|
|
200
|
+
if (version >= CURRENT_CONFIG_VERSION) {
|
|
201
|
+
return {
|
|
202
|
+
config,
|
|
203
|
+
migrated: false,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const migratedConfig = {
|
|
208
|
+
...defaultConfig,
|
|
209
|
+
...config,
|
|
210
|
+
|
|
211
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
212
|
+
|
|
213
|
+
models: {
|
|
214
|
+
...defaultConfig.models,
|
|
215
|
+
...(config.models ?? {}),
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
selectors: {
|
|
219
|
+
...defaultConfig.selectors,
|
|
220
|
+
...(config.selectors ?? {}),
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
config: migratedConfig,
|
|
226
|
+
migrated: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function ensureConfig() {
|
|
231
|
+
ensureConfigDirectory();
|
|
232
|
+
ensureConfigFile();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getEnvOverride(name, fallback) {
|
|
236
|
+
const value = process.env[name];
|
|
237
|
+
|
|
238
|
+
return typeof value === "string" && value.trim().length > 0
|
|
239
|
+
? value
|
|
240
|
+
: fallback;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function loadConfig() {
|
|
244
|
+
ensureConfig();
|
|
245
|
+
|
|
246
|
+
let config = {};
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
config = JSON.parse(
|
|
250
|
+
fs.readFileSync(CONFIG_FILE, "utf8")
|
|
251
|
+
);
|
|
252
|
+
} catch {
|
|
253
|
+
config = {};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const migration = migrateConfig(config);
|
|
257
|
+
config = migration.config;
|
|
258
|
+
|
|
259
|
+
if (migration.migrated) {
|
|
260
|
+
saveConfig(config);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const configuredModels = {
|
|
264
|
+
...defaultConfig.models,
|
|
265
|
+
...(config.models ?? {}),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const configuredSelectors = {
|
|
269
|
+
...defaultConfig.selectors,
|
|
270
|
+
...(config.selectors ?? {}),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const models = {
|
|
274
|
+
fast: getEnvOverride(
|
|
275
|
+
"CODEFORGE_MODEL_FAST",
|
|
276
|
+
configuredModels.fast
|
|
277
|
+
),
|
|
278
|
+
|
|
279
|
+
general: getEnvOverride(
|
|
280
|
+
"CODEFORGE_MODEL_GENERAL",
|
|
281
|
+
configuredModels.general
|
|
282
|
+
),
|
|
283
|
+
|
|
284
|
+
coding: getEnvOverride(
|
|
285
|
+
"CODEFORGE_MODEL_CODING",
|
|
286
|
+
configuredModels.coding
|
|
287
|
+
),
|
|
288
|
+
|
|
289
|
+
heavyCoding: getEnvOverride(
|
|
290
|
+
"CODEFORGE_MODEL_HEAVY_CODING",
|
|
291
|
+
configuredModels.heavyCoding
|
|
292
|
+
),
|
|
293
|
+
|
|
294
|
+
planner: getEnvOverride(
|
|
295
|
+
"CODEFORGE_MODEL_PLANNER",
|
|
296
|
+
configuredModels.planner
|
|
297
|
+
),
|
|
298
|
+
|
|
299
|
+
subagent: getEnvOverride(
|
|
300
|
+
"CODEFORGE_MODEL_SUBAGENT",
|
|
301
|
+
configuredModels.subagent
|
|
302
|
+
),
|
|
303
|
+
|
|
304
|
+
vision: getEnvOverride(
|
|
305
|
+
"CODEFORGE_MODEL_VISION",
|
|
306
|
+
configuredModels.vision
|
|
307
|
+
),
|
|
308
|
+
|
|
309
|
+
documentVision: getEnvOverride(
|
|
310
|
+
"CODEFORGE_MODEL_DOCUMENT_VISION",
|
|
311
|
+
configuredModels.documentVision
|
|
312
|
+
),
|
|
313
|
+
|
|
314
|
+
embedding: getEnvOverride(
|
|
315
|
+
"CODEFORGE_MODEL_EMBEDDING",
|
|
316
|
+
configuredModels.embedding
|
|
317
|
+
),
|
|
318
|
+
|
|
319
|
+
reranker: getEnvOverride(
|
|
320
|
+
"CODEFORGE_MODEL_RERANKER",
|
|
321
|
+
configuredModels.reranker
|
|
322
|
+
),
|
|
323
|
+
|
|
324
|
+
fallback: getEnvOverride(
|
|
325
|
+
"CODEFORGE_MODEL_FALLBACK",
|
|
326
|
+
configuredModels.fallback
|
|
327
|
+
),
|
|
328
|
+
|
|
329
|
+
emergency: getEnvOverride(
|
|
330
|
+
"CODEFORGE_MODEL_EMERGENCY",
|
|
331
|
+
configuredModels.emergency
|
|
332
|
+
),
|
|
333
|
+
};
|
|
334
|
+
const selectors = {
|
|
335
|
+
provider: getEnvOverride(
|
|
336
|
+
"CODEFORGE_SELECTOR_PROVIDER",
|
|
337
|
+
configuredSelectors.provider
|
|
338
|
+
),
|
|
339
|
+
|
|
340
|
+
rag: getEnvOverride(
|
|
341
|
+
"CODEFORGE_SELECTOR_RAG",
|
|
342
|
+
configuredSelectors.rag
|
|
343
|
+
),
|
|
344
|
+
|
|
345
|
+
model: getEnvOverride(
|
|
346
|
+
"CODEFORGE_SELECTOR_MODEL",
|
|
347
|
+
configuredSelectors.model
|
|
348
|
+
),
|
|
349
|
+
|
|
350
|
+
maxTokens: configuredSelectors.maxTokens,
|
|
351
|
+
|
|
352
|
+
modelMaxTokens:
|
|
353
|
+
configuredSelectors.modelMaxTokens,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
...defaultConfig,
|
|
358
|
+
...config,
|
|
359
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
360
|
+
apiKey: getEnvOverride(
|
|
361
|
+
"OPENROUTER_API_KEY",
|
|
362
|
+
config.apiKey ?? ""
|
|
363
|
+
),
|
|
364
|
+
models,
|
|
365
|
+
selectors,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function saveConfig(config) {
|
|
370
|
+
ensureConfigDirectory();
|
|
371
|
+
|
|
372
|
+
fs.writeFileSync(
|
|
373
|
+
CONFIG_FILE,
|
|
374
|
+
JSON.stringify(config, null, 2),
|
|
375
|
+
"utf8"
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export {
|
|
380
|
+
CONFIG_DIR,
|
|
381
|
+
CONFIG_FILE,
|
|
382
|
+
SESSION_FILE,
|
|
383
|
+
CACHE_DIR,
|
|
384
|
+
LOGS_DIR,
|
|
385
|
+
HISTORY_DIR,
|
|
386
|
+
MODEL_ROLES,
|
|
387
|
+
SELECTOR_ROLES,
|
|
388
|
+
ensureConfigDirectory,
|
|
389
|
+
ensureConfigFile,
|
|
390
|
+
ensureSessionFile,
|
|
391
|
+
};
|