@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,570 @@
|
|
|
1
|
+
const RAG_SCOPES = Object.freeze([
|
|
2
|
+
"none",
|
|
3
|
+
"file",
|
|
4
|
+
"symbol",
|
|
5
|
+
"repository",
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
const DEFAULT_DECISION = Object.freeze({
|
|
9
|
+
required: false,
|
|
10
|
+
scope: "none",
|
|
11
|
+
confidence: 0,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function clampConfidence(value) {
|
|
15
|
+
const number = Number(value);
|
|
16
|
+
|
|
17
|
+
if (!Number.isFinite(number)) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return Math.max(
|
|
22
|
+
0,
|
|
23
|
+
Math.min(1, number)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeScope(scope) {
|
|
28
|
+
if (
|
|
29
|
+
typeof scope !== "string"
|
|
30
|
+
) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const normalized =
|
|
35
|
+
scope.trim().toLowerCase();
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
!RAG_SCOPES.includes(
|
|
39
|
+
normalized
|
|
40
|
+
)
|
|
41
|
+
) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractJson(text) {
|
|
49
|
+
if (
|
|
50
|
+
typeof text !== "string"
|
|
51
|
+
) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const trimmed =
|
|
56
|
+
text.trim();
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* Normal JSON response.
|
|
60
|
+
*/
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(
|
|
63
|
+
trimmed
|
|
64
|
+
);
|
|
65
|
+
} catch {
|
|
66
|
+
// Continue.
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Fenced JSON.
|
|
71
|
+
*/
|
|
72
|
+
const fenced =
|
|
73
|
+
trimmed.match(
|
|
74
|
+
/```(?:json)?\s*([\s\S]*?)\s*```/i
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (fenced) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(
|
|
80
|
+
fenced[1]
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
// Continue.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/*
|
|
88
|
+
* Embedded JSON object.
|
|
89
|
+
*/
|
|
90
|
+
const start =
|
|
91
|
+
trimmed.indexOf("{");
|
|
92
|
+
|
|
93
|
+
const end =
|
|
94
|
+
trimmed.lastIndexOf("}");
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
start >= 0 &&
|
|
98
|
+
end > start
|
|
99
|
+
) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(
|
|
102
|
+
trimmed.slice(
|
|
103
|
+
start,
|
|
104
|
+
end + 1
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeDecision(
|
|
116
|
+
value,
|
|
117
|
+
fallback = DEFAULT_DECISION
|
|
118
|
+
) {
|
|
119
|
+
if (
|
|
120
|
+
!value ||
|
|
121
|
+
typeof value !== "object"
|
|
122
|
+
) {
|
|
123
|
+
return {
|
|
124
|
+
...fallback,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const scope =
|
|
129
|
+
normalizeScope(
|
|
130
|
+
value.scope
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (!scope) {
|
|
134
|
+
return {
|
|
135
|
+
...fallback,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const required =
|
|
140
|
+
Boolean(
|
|
141
|
+
value.required
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* "none" and "required: true" are contradictory.
|
|
146
|
+
* Normalize this rather than allowing an invalid
|
|
147
|
+
* execution decision through the pipeline.
|
|
148
|
+
*/
|
|
149
|
+
if (
|
|
150
|
+
required &&
|
|
151
|
+
scope === "none"
|
|
152
|
+
) {
|
|
153
|
+
return {
|
|
154
|
+
...fallback,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/*
|
|
159
|
+
* If the model says RAG is not required,
|
|
160
|
+
* scope must be none.
|
|
161
|
+
*/
|
|
162
|
+
if (
|
|
163
|
+
!required &&
|
|
164
|
+
scope !== "none"
|
|
165
|
+
) {
|
|
166
|
+
return {
|
|
167
|
+
...fallback,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
required,
|
|
173
|
+
|
|
174
|
+
scope,
|
|
175
|
+
|
|
176
|
+
confidence:
|
|
177
|
+
clampConfidence(
|
|
178
|
+
value.confidence
|
|
179
|
+
),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function hasExplicitRepositorySignal(
|
|
184
|
+
prompt = ""
|
|
185
|
+
) {
|
|
186
|
+
if (
|
|
187
|
+
typeof prompt !== "string"
|
|
188
|
+
) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const normalized =
|
|
193
|
+
prompt.toLowerCase();
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
/\b(repository|repo|codebase)\b/.test(
|
|
197
|
+
normalized
|
|
198
|
+
) ||
|
|
199
|
+
/\b(our|this|current)\s+(system|project|codebase|repository|implementation|architecture)\b/.test(
|
|
200
|
+
normalized
|
|
201
|
+
) ||
|
|
202
|
+
/\b(our|this)\s+(routing|router|fallback|provider|executor|retrieval|rag|planner|agent|terminal|cli|tool|tools?)\b/.test(
|
|
203
|
+
normalized
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isGeneralCodeRequest({
|
|
209
|
+
prompt = "",
|
|
210
|
+
plan = {},
|
|
211
|
+
} = {}) {
|
|
212
|
+
if (
|
|
213
|
+
plan?.directFileTarget
|
|
214
|
+
) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
plan?.intent !== "code"
|
|
220
|
+
) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return !hasExplicitRepositorySignal(
|
|
225
|
+
prompt
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function buildDeterministicFallback({
|
|
230
|
+
prompt = "",
|
|
231
|
+
plan = {},
|
|
232
|
+
} = {}) {
|
|
233
|
+
/*
|
|
234
|
+
* These rules are deliberately conservative.
|
|
235
|
+
*
|
|
236
|
+
* They are NOT the primary decision mechanism.
|
|
237
|
+
* They only protect execution if the selector
|
|
238
|
+
* model is unavailable or returns invalid output.
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
plan?.directFileTarget
|
|
243
|
+
) {
|
|
244
|
+
return {
|
|
245
|
+
required: true,
|
|
246
|
+
scope: "file",
|
|
247
|
+
confidence: 0,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
typeof prompt !== "string"
|
|
253
|
+
) {
|
|
254
|
+
return {
|
|
255
|
+
...DEFAULT_DECISION,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const normalized =
|
|
260
|
+
prompt.toLowerCase();
|
|
261
|
+
|
|
262
|
+
/*
|
|
263
|
+
* Explicit repository references.
|
|
264
|
+
*/
|
|
265
|
+
if (
|
|
266
|
+
/\b(repository|repo|codebase)\b/.test(
|
|
267
|
+
normalized
|
|
268
|
+
)
|
|
269
|
+
) {
|
|
270
|
+
return {
|
|
271
|
+
required: true,
|
|
272
|
+
scope: "repository",
|
|
273
|
+
confidence: 0,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/*
|
|
278
|
+
* Explicit current-project references.
|
|
279
|
+
*/
|
|
280
|
+
if (
|
|
281
|
+
/\b(our|this|current)\s+(system|project|codebase|repository|implementation|architecture)\b/.test(
|
|
282
|
+
normalized
|
|
283
|
+
)
|
|
284
|
+
) {
|
|
285
|
+
return {
|
|
286
|
+
required: true,
|
|
287
|
+
scope: "repository",
|
|
288
|
+
confidence: 0,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/*
|
|
293
|
+
* Explicit implementation questions about
|
|
294
|
+
* the current project.
|
|
295
|
+
*/
|
|
296
|
+
if (
|
|
297
|
+
/\b(our|this)\s+(routing|router|fallback|provider|executor|retrieval|rag|planner|agent|terminal|cli|tool|tools?)\b/.test(
|
|
298
|
+
normalized
|
|
299
|
+
)
|
|
300
|
+
) {
|
|
301
|
+
return {
|
|
302
|
+
required: true,
|
|
303
|
+
scope: "repository",
|
|
304
|
+
confidence: 0,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/*
|
|
309
|
+
* General programming requests do not require
|
|
310
|
+
* repository context unless an explicit project
|
|
311
|
+
* reference was already matched above.
|
|
312
|
+
*/
|
|
313
|
+
if (
|
|
314
|
+
plan?.intent === "code" &&
|
|
315
|
+
!plan?.directFileTarget
|
|
316
|
+
) {
|
|
317
|
+
return {
|
|
318
|
+
...DEFAULT_DECISION,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/*
|
|
323
|
+
* A plan that explicitly requires repository
|
|
324
|
+
* information remains a safe fallback signal.
|
|
325
|
+
*/
|
|
326
|
+
if (
|
|
327
|
+
plan?.requiresRAG
|
|
328
|
+
) {
|
|
329
|
+
return {
|
|
330
|
+
required: true,
|
|
331
|
+
scope: "repository",
|
|
332
|
+
confidence: 0,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
...DEFAULT_DECISION,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function buildSelectorPrompt({
|
|
342
|
+
prompt,
|
|
343
|
+
plan,
|
|
344
|
+
}) {
|
|
345
|
+
return [
|
|
346
|
+
"You are CodeForge's repository-context selector.",
|
|
347
|
+
"",
|
|
348
|
+
"Your ONLY task is to decide whether the user's request",
|
|
349
|
+
"requires information from the current repository.",
|
|
350
|
+
"",
|
|
351
|
+
"Do NOT answer the user's question.",
|
|
352
|
+
"Do NOT explain your decision.",
|
|
353
|
+
"Return ONLY valid JSON.",
|
|
354
|
+
"",
|
|
355
|
+
"Use one of these scopes:",
|
|
356
|
+
"- none: repository information is not needed",
|
|
357
|
+
"- file: a specific repository file is needed",
|
|
358
|
+
"- symbol: a specific repository symbol is needed",
|
|
359
|
+
"- repository: multiple repository files or architectural context are needed",
|
|
360
|
+
"",
|
|
361
|
+
"Return exactly this shape:",
|
|
362
|
+
JSON.stringify(
|
|
363
|
+
{
|
|
364
|
+
required: false,
|
|
365
|
+
scope: "none",
|
|
366
|
+
confidence: 0.0,
|
|
367
|
+
},
|
|
368
|
+
null,
|
|
369
|
+
2
|
|
370
|
+
),
|
|
371
|
+
"",
|
|
372
|
+
"Decision rules:",
|
|
373
|
+
"",
|
|
374
|
+
"NONE — general programming/CS knowledge that does not depend on this codebase.",
|
|
375
|
+
"Examples: \"explain recursion\", \"write a JavaScript function\", \"what is dependency injection?\", \"how does HTTP work?\", \"explain binary search\".",
|
|
376
|
+
"",
|
|
377
|
+
"REPOSITORY — requires understanding how this project's components are built or interact. Answering accurately depends on the actual implementation, not general knowledge.",
|
|
378
|
+
"Examples: \"how does our routing system work?\", \"how does model selection interact with routing and execution?\", \"how does our fallback system work?\", \"explain our executor\", \"why does CodeForge use this provider?\", \"how is retrieval implemented in this project?\".",
|
|
379
|
+
"",
|
|
380
|
+
"FILE — the user names one or more specific files or paths, and the question stays scoped to that file's contents rather than how it fits into the broader system.",
|
|
381
|
+
"Examples: \"open packages/agent/router.js\", \"explain packages/agent/executor.js\", \"what functions are defined in packages/agent/router.js?\".",
|
|
382
|
+
"",
|
|
383
|
+
"SYMBOL — the user asks about a specific function, class, or variable rather than a whole file or the system as a whole.",
|
|
384
|
+
"",
|
|
385
|
+
"Critical rule:",
|
|
386
|
+
"- If the user asks how multiple components of the current system interact, choose repository scope.",
|
|
387
|
+
"- If the user asks about \"our\", \"this system\", or a named CodeForge component in an implementation context, choose repository scope.",
|
|
388
|
+
"- If answering accurately requires inspecting the current project's implementation, choose repository scope.",
|
|
389
|
+
"- Never choose \"none\" merely because the question is phrased generally. Determine whether \"our\" or the surrounding context makes it repository-specific.",
|
|
390
|
+
"- If the user names a specific file AND asks how it interacts with other parts of the system (not just what's inside that file), choose repository scope, not file scope. File scope is only for questions that stay contained within the named file(s).",
|
|
391
|
+
"- If ambiguous between \"none\" and \"repository\", prefer repository scope — a missed repository lookup produces a generic answer to a project-specific question, which is worse than an unnecessary lookup.",
|
|
392
|
+
"- If ambiguous between \"file\" and \"repository\", prefer repository scope, since it has access to full project context and can still resolve file-specific questions accurately.",
|
|
393
|
+
"",
|
|
394
|
+
`User request: ${prompt}`,
|
|
395
|
+
`Planner signal (fallback only): ${Boolean(
|
|
396
|
+
plan?.requiresRAG
|
|
397
|
+
)}`,
|
|
398
|
+
`Direct file target: ${Boolean(
|
|
399
|
+
plan?.directFileTarget
|
|
400
|
+
)}`,
|
|
401
|
+
].join("\n");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Create CodeForge's dedicated RAG-selection model.
|
|
406
|
+
*
|
|
407
|
+
* This model decides whether repository context is needed.
|
|
408
|
+
* It does not perform retrieval.
|
|
409
|
+
*/
|
|
410
|
+
export function createRagSelector({
|
|
411
|
+
provider,
|
|
412
|
+
model,
|
|
413
|
+
maxTokens = 128,
|
|
414
|
+
} = {}) {
|
|
415
|
+
async function select({
|
|
416
|
+
prompt = "",
|
|
417
|
+
plan = {},
|
|
418
|
+
} = {}) {
|
|
419
|
+
const fallback =
|
|
420
|
+
buildDeterministicFallback({
|
|
421
|
+
prompt,
|
|
422
|
+
plan,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
/*
|
|
428
|
+
* No configured selector model:
|
|
429
|
+
* preserve safe deterministic behavior.
|
|
430
|
+
*/
|
|
431
|
+
|
|
432
|
+
/*
|
|
433
|
+
* No configured selector model:
|
|
434
|
+
* preserve safe deterministic behavior.
|
|
435
|
+
*/
|
|
436
|
+
if (
|
|
437
|
+
!provider ||
|
|
438
|
+
!model
|
|
439
|
+
) {
|
|
440
|
+
return {
|
|
441
|
+
...fallback,
|
|
442
|
+
source: "fallback",
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const selectorPrompt =
|
|
447
|
+
buildSelectorPrompt({
|
|
448
|
+
prompt,
|
|
449
|
+
plan,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const response =
|
|
454
|
+
await provider.generate({
|
|
455
|
+
model,
|
|
456
|
+
temperature: 0,
|
|
457
|
+
maxTokens,
|
|
458
|
+
messages: [
|
|
459
|
+
{
|
|
460
|
+
role: "system",
|
|
461
|
+
content:
|
|
462
|
+
"You are a repository-context classifier. Output JSON only.",
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
role: "user",
|
|
466
|
+
content:
|
|
467
|
+
selectorPrompt,
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const content =
|
|
473
|
+
response?.message?.content;
|
|
474
|
+
|
|
475
|
+
const parsed =
|
|
476
|
+
extractJson(content);
|
|
477
|
+
|
|
478
|
+
const decision =
|
|
479
|
+
normalizeDecision(
|
|
480
|
+
parsed,
|
|
481
|
+
fallback
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
/*
|
|
485
|
+
* Invalid model output must never
|
|
486
|
+
* disable safe fallback behavior.
|
|
487
|
+
*/
|
|
488
|
+
if (
|
|
489
|
+
!parsed ||
|
|
490
|
+
!normalizeScope(
|
|
491
|
+
parsed.scope
|
|
492
|
+
)
|
|
493
|
+
) {
|
|
494
|
+
return {
|
|
495
|
+
...fallback,
|
|
496
|
+
source: "fallback",
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/*
|
|
501
|
+
* Reject contradictory decisions.
|
|
502
|
+
*/
|
|
503
|
+
if (
|
|
504
|
+
Boolean(
|
|
505
|
+
parsed.required
|
|
506
|
+
) &&
|
|
507
|
+
normalizeScope(
|
|
508
|
+
parsed.scope
|
|
509
|
+
) === "none"
|
|
510
|
+
) {
|
|
511
|
+
return {
|
|
512
|
+
...fallback,
|
|
513
|
+
source: "fallback",
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (
|
|
518
|
+
!Boolean(
|
|
519
|
+
parsed.required
|
|
520
|
+
) &&
|
|
521
|
+
normalizeScope(
|
|
522
|
+
parsed.scope
|
|
523
|
+
) !== "none"
|
|
524
|
+
) {
|
|
525
|
+
return {
|
|
526
|
+
...fallback,
|
|
527
|
+
source: "fallback",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (
|
|
532
|
+
isGeneralCodeRequest({
|
|
533
|
+
prompt,
|
|
534
|
+
plan,
|
|
535
|
+
})
|
|
536
|
+
) {
|
|
537
|
+
return {
|
|
538
|
+
required: false,
|
|
539
|
+
scope: "none",
|
|
540
|
+
confidence: decision.confidence,
|
|
541
|
+
source: "model",
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
...decision,
|
|
547
|
+
source: "model",
|
|
548
|
+
};
|
|
549
|
+
} catch {
|
|
550
|
+
/*
|
|
551
|
+
* RAG selection is advisory.
|
|
552
|
+
* Selector failure must never crash execution.
|
|
553
|
+
*/
|
|
554
|
+
return {
|
|
555
|
+
...fallback,
|
|
556
|
+
source: "fallback",
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
select,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
export {
|
|
568
|
+
RAG_SCOPES,
|
|
569
|
+
normalizeDecision,
|
|
570
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const CODING_INTENTS = new Set([
|
|
2
|
+
"code",
|
|
3
|
+
"debug",
|
|
4
|
+
"review",
|
|
5
|
+
"refactor",
|
|
6
|
+
"test",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
function resolveModelRole(plan) {
|
|
10
|
+
if (!plan) {
|
|
11
|
+
return "general";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (
|
|
15
|
+
typeof plan.modelRole === "string" &&
|
|
16
|
+
plan.modelRole.trim()
|
|
17
|
+
) {
|
|
18
|
+
return plan.modelRole.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (plan.requiresThinking) {
|
|
22
|
+
return "planner";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (plan.intent === "chat") {
|
|
26
|
+
return "fast";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (CODING_INTENTS.has(plan.intent)) {
|
|
30
|
+
return "coding";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return "general";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveModel(
|
|
37
|
+
role,
|
|
38
|
+
models = {}
|
|
39
|
+
) {
|
|
40
|
+
return (
|
|
41
|
+
models[role] ??
|
|
42
|
+
models.fallback ??
|
|
43
|
+
models.emergency ??
|
|
44
|
+
null
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function routeRequest({
|
|
49
|
+
plan,
|
|
50
|
+
config = {},
|
|
51
|
+
providers = [],
|
|
52
|
+
}) {
|
|
53
|
+
let modelRole =
|
|
54
|
+
resolveModelRole(plan);
|
|
55
|
+
|
|
56
|
+
const availableModels =
|
|
57
|
+
config.models ?? {};
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
typeof plan?.modelRole === "string" &&
|
|
61
|
+
plan.modelRole.trim() &&
|
|
62
|
+
!Object.prototype.hasOwnProperty.call(
|
|
63
|
+
availableModels,
|
|
64
|
+
modelRole
|
|
65
|
+
)
|
|
66
|
+
) {
|
|
67
|
+
modelRole =
|
|
68
|
+
resolveModelRole({
|
|
69
|
+
...plan,
|
|
70
|
+
modelRole: null,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const defaults = {
|
|
75
|
+
provider:
|
|
76
|
+
config.provider ??
|
|
77
|
+
"openrouter",
|
|
78
|
+
|
|
79
|
+
modelRole,
|
|
80
|
+
|
|
81
|
+
model: resolveModel(
|
|
82
|
+
modelRole,
|
|
83
|
+
config.models
|
|
84
|
+
),
|
|
85
|
+
|
|
86
|
+
stream:
|
|
87
|
+
config.stream ??
|
|
88
|
+
false,
|
|
89
|
+
|
|
90
|
+
temperature:
|
|
91
|
+
config.temperature ??
|
|
92
|
+
0.2,
|
|
93
|
+
|
|
94
|
+
maxTokens:
|
|
95
|
+
config.maxTokens ??
|
|
96
|
+
4096,
|
|
97
|
+
|
|
98
|
+
thinking: Boolean(
|
|
99
|
+
plan?.requiresThinking
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const route = {
|
|
104
|
+
...defaults,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Validate provider
|
|
108
|
+
if (
|
|
109
|
+
providers.length > 0 &&
|
|
110
|
+
!providers.some(
|
|
111
|
+
(provider) =>
|
|
112
|
+
provider.name ===
|
|
113
|
+
route.provider
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
route.provider =
|
|
117
|
+
providers[0].name;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return route;
|
|
121
|
+
}
|