@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,129 @@
|
|
|
1
|
+
const DEFAULT_OPTIONS = Object.freeze({
|
|
2
|
+
maxAttempts: 3,
|
|
3
|
+
|
|
4
|
+
retryableErrors: [
|
|
5
|
+
"rate_limit",
|
|
6
|
+
"timeout",
|
|
7
|
+
"overloaded",
|
|
8
|
+
"network",
|
|
9
|
+
"provider_error",
|
|
10
|
+
],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function isRetryable(
|
|
14
|
+
error,
|
|
15
|
+
options
|
|
16
|
+
) {
|
|
17
|
+
if (!error) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return options.retryableErrors.includes(
|
|
22
|
+
error.code
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function findNextModel(
|
|
27
|
+
route,
|
|
28
|
+
models = {}
|
|
29
|
+
) {
|
|
30
|
+
const candidates = [
|
|
31
|
+
{
|
|
32
|
+
role: "fallback",
|
|
33
|
+
model: models.fallback,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
role: "emergency",
|
|
37
|
+
model: models.emergency,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
candidates.find(
|
|
43
|
+
(candidate) =>
|
|
44
|
+
typeof candidate.model ===
|
|
45
|
+
"string" &&
|
|
46
|
+
candidate.model.length > 0 &&
|
|
47
|
+
candidate.model !== route.model
|
|
48
|
+
) ?? null
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getFallbackRoute({
|
|
53
|
+
error,
|
|
54
|
+
route,
|
|
55
|
+
providers = [],
|
|
56
|
+
models = {},
|
|
57
|
+
options = {},
|
|
58
|
+
}) {
|
|
59
|
+
const config = {
|
|
60
|
+
...DEFAULT_OPTIONS,
|
|
61
|
+
...options,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (!isRetryable(error, config)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const attempt =
|
|
69
|
+
route.attempt ?? 1;
|
|
70
|
+
|
|
71
|
+
if (attempt >= config.maxAttempts) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const nextModel =
|
|
76
|
+
findNextModel(
|
|
77
|
+
route,
|
|
78
|
+
models
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (nextModel) {
|
|
82
|
+
return {
|
|
83
|
+
...route,
|
|
84
|
+
|
|
85
|
+
modelRole:
|
|
86
|
+
nextModel.role,
|
|
87
|
+
|
|
88
|
+
model:
|
|
89
|
+
nextModel.model,
|
|
90
|
+
|
|
91
|
+
attempt:
|
|
92
|
+
attempt + 1,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const currentIndex =
|
|
97
|
+
providers.findIndex(
|
|
98
|
+
(provider) =>
|
|
99
|
+
provider.name ===
|
|
100
|
+
route.provider
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (currentIndex === -1) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const nextProvider =
|
|
108
|
+
providers[currentIndex + 1];
|
|
109
|
+
|
|
110
|
+
if (!nextProvider) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...route,
|
|
116
|
+
|
|
117
|
+
provider:
|
|
118
|
+
nextProvider.name,
|
|
119
|
+
|
|
120
|
+
modelRole: "fallback",
|
|
121
|
+
|
|
122
|
+
model:
|
|
123
|
+
nextProvider.defaultModel ??
|
|
124
|
+
null,
|
|
125
|
+
|
|
126
|
+
attempt:
|
|
127
|
+
attempt + 1,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { Intent, detectIntent } from "./intent.js";
|
|
2
|
+
|
|
3
|
+
export { createExecutionPlan } from "./planner.js";
|
|
4
|
+
|
|
5
|
+
export { retrieveContext } from "./rag.js";
|
|
6
|
+
|
|
7
|
+
export { buildContext } from "./contextBuilder.js";
|
|
8
|
+
|
|
9
|
+
export { routeRequest } from "./router.js";
|
|
10
|
+
|
|
11
|
+
export { configureThinking } from "./thinking.js";
|
|
12
|
+
|
|
13
|
+
export { getFallbackRoute } from "./fallback.js";
|
|
14
|
+
|
|
15
|
+
export { execute } from "./executor.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
createModelSelector,
|
|
19
|
+
MODEL_ROLES,
|
|
20
|
+
COMPLEXITIES,
|
|
21
|
+
} from "./modelSelector.js";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
createRagSelector,
|
|
25
|
+
RAG_SCOPES,
|
|
26
|
+
} from "./ragSelector.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const Intent = Object.freeze({
|
|
2
|
+
CHAT: "chat",
|
|
3
|
+
EXPLAIN: "explain",
|
|
4
|
+
PLAN: "plan",
|
|
5
|
+
CODE: "code",
|
|
6
|
+
DEBUG: "debug",
|
|
7
|
+
REVIEW: "review",
|
|
8
|
+
REFACTOR: "refactor",
|
|
9
|
+
TEST: "test",
|
|
10
|
+
SEARCH: "search",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const PATTERNS = [
|
|
14
|
+
{
|
|
15
|
+
intent: Intent.REVIEW,
|
|
16
|
+
regex:
|
|
17
|
+
/\b(review|audit|inspect|evaluate|analyze|analyse|check|assess)\b/i,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
intent: Intent.DEBUG,
|
|
21
|
+
regex:
|
|
22
|
+
/\b(debug|fix|bug|error|exception|crash|issue|failure|trace|stack)\b/i,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
intent: Intent.REFACTOR,
|
|
26
|
+
regex:
|
|
27
|
+
/\b(refactor|cleanup|clean up|optimize|optimise|rewrite|rename|restructure|improve)\b/i,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
intent: Intent.TEST,
|
|
31
|
+
regex:
|
|
32
|
+
/\b(test|testing|unit test|integration test|e2e|end[- ]to[- ]end|spec)\b/i,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
intent: Intent.PLAN,
|
|
36
|
+
regex:
|
|
37
|
+
/\b(plan|design|architecture|architect|roadmap|strategy|approach)\b/i,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
intent: Intent.SEARCH,
|
|
41
|
+
regex:
|
|
42
|
+
/\b(search|find|locate|lookup|look up|grep|where is|show me)\b/i,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
intent: Intent.EXPLAIN,
|
|
46
|
+
regex:
|
|
47
|
+
/\b(explain|describe|what is|why|how does|understand|clarify)\b/i,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
intent: Intent.CODE,
|
|
51
|
+
regex:
|
|
52
|
+
/\b(create|build|implement|generate|write|add|develop|make)\b/i,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function normalize(text) {
|
|
57
|
+
return text
|
|
58
|
+
.trim()
|
|
59
|
+
.replace(/\s+/g, " ")
|
|
60
|
+
.toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function detectIntent(input = "") {
|
|
64
|
+
const text = normalize(input);
|
|
65
|
+
|
|
66
|
+
for (const { intent, regex } of PATTERNS) {
|
|
67
|
+
if (regex.test(text)) {
|
|
68
|
+
return intent;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Intent.CHAT;
|
|
73
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
const MODEL_ROLES = Object.freeze([
|
|
2
|
+
"fast",
|
|
3
|
+
"general",
|
|
4
|
+
"coding",
|
|
5
|
+
"planner",
|
|
6
|
+
"heavyCoding",
|
|
7
|
+
"subagent",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
const COMPLEXITIES = Object.freeze([
|
|
11
|
+
"low",
|
|
12
|
+
"medium",
|
|
13
|
+
"high",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const DEFAULT_SELECTION = Object.freeze({
|
|
17
|
+
role: "general",
|
|
18
|
+
confidence: 0,
|
|
19
|
+
complexity: "medium",
|
|
20
|
+
reasoningRequired: false,
|
|
21
|
+
toolRequired: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function clampConfidence(value) {
|
|
25
|
+
const number = Number(value);
|
|
26
|
+
|
|
27
|
+
if (!Number.isFinite(number)) {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Math.max(
|
|
32
|
+
0,
|
|
33
|
+
Math.min(1, number)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeRole(
|
|
38
|
+
role,
|
|
39
|
+
availableRoles
|
|
40
|
+
) {
|
|
41
|
+
if (
|
|
42
|
+
typeof role !== "string"
|
|
43
|
+
) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const normalized =
|
|
48
|
+
role.trim().toLowerCase();
|
|
49
|
+
|
|
50
|
+
const canonicalRole =
|
|
51
|
+
MODEL_ROLES.find(
|
|
52
|
+
(candidate) =>
|
|
53
|
+
candidate.toLowerCase() ===
|
|
54
|
+
normalized
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!canonicalRole) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
Array.isArray(availableRoles) &&
|
|
63
|
+
availableRoles.length > 0
|
|
64
|
+
) {
|
|
65
|
+
const availableRole =
|
|
66
|
+
availableRoles.find(
|
|
67
|
+
(candidate) =>
|
|
68
|
+
typeof candidate === "string" &&
|
|
69
|
+
candidate.toLowerCase() ===
|
|
70
|
+
normalized
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!availableRole) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return availableRole;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return canonicalRole;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeComplexity(
|
|
84
|
+
complexity
|
|
85
|
+
) {
|
|
86
|
+
if (
|
|
87
|
+
typeof complexity !== "string"
|
|
88
|
+
) {
|
|
89
|
+
return "medium";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const normalized =
|
|
93
|
+
complexity
|
|
94
|
+
.trim()
|
|
95
|
+
.toLowerCase();
|
|
96
|
+
|
|
97
|
+
return COMPLEXITIES.includes(
|
|
98
|
+
normalized
|
|
99
|
+
)
|
|
100
|
+
? normalized
|
|
101
|
+
: "medium";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractJson(text) {
|
|
105
|
+
if (
|
|
106
|
+
typeof text !== "string"
|
|
107
|
+
) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const trimmed = text.trim();
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* Normal JSON response.
|
|
115
|
+
*/
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(trimmed);
|
|
118
|
+
} catch {
|
|
119
|
+
// Continue with fenced / embedded JSON.
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
* ```json
|
|
124
|
+
* {...}
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
const fenced =
|
|
128
|
+
trimmed.match(
|
|
129
|
+
/```(?:json)?\s*([\s\S]*?)\s*```/i
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (fenced) {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(
|
|
135
|
+
fenced[1]
|
|
136
|
+
);
|
|
137
|
+
} catch {
|
|
138
|
+
// Continue.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
* Some models add a short sentence before
|
|
144
|
+
* the JSON object. Extract the outer object.
|
|
145
|
+
*/
|
|
146
|
+
const start =
|
|
147
|
+
trimmed.indexOf("{");
|
|
148
|
+
|
|
149
|
+
const end =
|
|
150
|
+
trimmed.lastIndexOf("}");
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
start >= 0 &&
|
|
154
|
+
end > start
|
|
155
|
+
) {
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(
|
|
158
|
+
trimmed.slice(
|
|
159
|
+
start,
|
|
160
|
+
end + 1
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeSelection(
|
|
172
|
+
value,
|
|
173
|
+
{
|
|
174
|
+
availableRoles = [],
|
|
175
|
+
fallback = DEFAULT_SELECTION,
|
|
176
|
+
} = {}
|
|
177
|
+
) {
|
|
178
|
+
if (
|
|
179
|
+
!value ||
|
|
180
|
+
typeof value !== "object"
|
|
181
|
+
) {
|
|
182
|
+
return {
|
|
183
|
+
...fallback,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const role =
|
|
188
|
+
normalizeRole(
|
|
189
|
+
value.role,
|
|
190
|
+
availableRoles
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (!role) {
|
|
194
|
+
return {
|
|
195
|
+
...fallback,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
role,
|
|
201
|
+
|
|
202
|
+
confidence:
|
|
203
|
+
clampConfidence(
|
|
204
|
+
value.confidence
|
|
205
|
+
),
|
|
206
|
+
|
|
207
|
+
complexity:
|
|
208
|
+
normalizeComplexity(
|
|
209
|
+
value.complexity
|
|
210
|
+
),
|
|
211
|
+
|
|
212
|
+
reasoningRequired:
|
|
213
|
+
Boolean(
|
|
214
|
+
value.reasoningRequired
|
|
215
|
+
),
|
|
216
|
+
|
|
217
|
+
toolRequired:
|
|
218
|
+
Boolean(
|
|
219
|
+
value.toolRequired
|
|
220
|
+
),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildDeterministicFallback(
|
|
225
|
+
{
|
|
226
|
+
plan = {},
|
|
227
|
+
intent = "",
|
|
228
|
+
availableRoles = [],
|
|
229
|
+
} = {}
|
|
230
|
+
) {
|
|
231
|
+
let role = "general";
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
plan.requiresThinking
|
|
235
|
+
) {
|
|
236
|
+
role = "planner";
|
|
237
|
+
} else if (
|
|
238
|
+
[
|
|
239
|
+
"code",
|
|
240
|
+
"debug",
|
|
241
|
+
"review",
|
|
242
|
+
"refactor",
|
|
243
|
+
"test",
|
|
244
|
+
].includes(intent)
|
|
245
|
+
) {
|
|
246
|
+
role = "coding";
|
|
247
|
+
} else if (
|
|
248
|
+
intent === "chat"
|
|
249
|
+
) {
|
|
250
|
+
role = "fast";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (
|
|
254
|
+
Array.isArray(
|
|
255
|
+
availableRoles
|
|
256
|
+
) &&
|
|
257
|
+
availableRoles.length > 0 &&
|
|
258
|
+
!availableRoles.includes(role)
|
|
259
|
+
) {
|
|
260
|
+
role =
|
|
261
|
+
availableRoles.includes(
|
|
262
|
+
"general"
|
|
263
|
+
)
|
|
264
|
+
? "general"
|
|
265
|
+
: availableRoles[0];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
role,
|
|
270
|
+
|
|
271
|
+
confidence: 0,
|
|
272
|
+
|
|
273
|
+
complexity:
|
|
274
|
+
plan.requiresThinking
|
|
275
|
+
? "high"
|
|
276
|
+
: "medium",
|
|
277
|
+
|
|
278
|
+
reasoningRequired:
|
|
279
|
+
Boolean(
|
|
280
|
+
plan.requiresThinking
|
|
281
|
+
),
|
|
282
|
+
|
|
283
|
+
toolRequired:
|
|
284
|
+
Boolean(
|
|
285
|
+
plan.requiresTools
|
|
286
|
+
),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildSelectorPrompt({
|
|
291
|
+
prompt,
|
|
292
|
+
intent,
|
|
293
|
+
plan,
|
|
294
|
+
ragDecision,
|
|
295
|
+
availableRoles,
|
|
296
|
+
}) {
|
|
297
|
+
return [
|
|
298
|
+
"You are CodeForge's model selection agent.",
|
|
299
|
+
"Your only task is to select the best model role for the user's task.",
|
|
300
|
+
"",
|
|
301
|
+
"Do not solve the user's task.",
|
|
302
|
+
"Do not provide an explanation.",
|
|
303
|
+
"Return ONLY valid JSON.",
|
|
304
|
+
"",
|
|
305
|
+
"Allowed roles:",
|
|
306
|
+
...availableRoles.map(
|
|
307
|
+
(role) => `- ${role}`
|
|
308
|
+
),
|
|
309
|
+
"",
|
|
310
|
+
"Return exactly this shape:",
|
|
311
|
+
JSON.stringify(
|
|
312
|
+
{
|
|
313
|
+
role: "general",
|
|
314
|
+
confidence: 0.0,
|
|
315
|
+
complexity: "medium",
|
|
316
|
+
reasoningRequired: false,
|
|
317
|
+
toolRequired: false,
|
|
318
|
+
},
|
|
319
|
+
null,
|
|
320
|
+
2
|
|
321
|
+
),
|
|
322
|
+
"",
|
|
323
|
+
`User request: ${prompt}`,
|
|
324
|
+
`Detected intent: ${intent}`,
|
|
325
|
+
`RAG required: ${Boolean(
|
|
326
|
+
ragDecision?.required
|
|
327
|
+
)}`,
|
|
328
|
+
`RAG scope: ${ragDecision?.scope ?? "none"}`,
|
|
329
|
+
`Requires thinking: ${Boolean(
|
|
330
|
+
plan?.requiresThinking
|
|
331
|
+
)}`,
|
|
332
|
+
`Requires tools: ${Boolean(
|
|
333
|
+
plan?.requiresTools
|
|
334
|
+
)}`,
|
|
335
|
+
`Requires write: ${Boolean(
|
|
336
|
+
plan?.requiresWrite
|
|
337
|
+
)}`,
|
|
338
|
+
].join("\n");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create the v1 model-selection agent.
|
|
343
|
+
*
|
|
344
|
+
* The selector model makes a small structured decision.
|
|
345
|
+
* It does not execute tools and does not receive repository
|
|
346
|
+
* contents.
|
|
347
|
+
*/
|
|
348
|
+
export function createModelSelector({
|
|
349
|
+
provider,
|
|
350
|
+
model,
|
|
351
|
+
maxTokens = 256,
|
|
352
|
+
} = {}) {
|
|
353
|
+
async function select({
|
|
354
|
+
prompt = "",
|
|
355
|
+
intent = "chat",
|
|
356
|
+
plan = {},
|
|
357
|
+
ragDecision = null,
|
|
358
|
+
availableRoles = MODEL_ROLES,
|
|
359
|
+
} = {}) {
|
|
360
|
+
const fallback =
|
|
361
|
+
buildDeterministicFallback({
|
|
362
|
+
plan,
|
|
363
|
+
intent,
|
|
364
|
+
availableRoles,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
/*
|
|
368
|
+
* Selector is optional. If no selector model has been
|
|
369
|
+
* configured, preserve deterministic v1 routing behavior.
|
|
370
|
+
*/
|
|
371
|
+
if (
|
|
372
|
+
!provider ||
|
|
373
|
+
!model
|
|
374
|
+
) {
|
|
375
|
+
return {
|
|
376
|
+
...fallback,
|
|
377
|
+
source: "fallback",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const selectorPrompt =
|
|
382
|
+
buildSelectorPrompt({
|
|
383
|
+
prompt,
|
|
384
|
+
intent,
|
|
385
|
+
plan,
|
|
386
|
+
ragDecision,
|
|
387
|
+
availableRoles,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const response =
|
|
392
|
+
await provider.generate({
|
|
393
|
+
model,
|
|
394
|
+
temperature: 0,
|
|
395
|
+
maxTokens,
|
|
396
|
+
messages: [
|
|
397
|
+
{
|
|
398
|
+
role: "system",
|
|
399
|
+
content:
|
|
400
|
+
"You are a routing classifier. Output JSON only.",
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
role: "user",
|
|
404
|
+
content:
|
|
405
|
+
selectorPrompt,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const content =
|
|
411
|
+
response?.message?.content;
|
|
412
|
+
|
|
413
|
+
const parsed =
|
|
414
|
+
extractJson(content);
|
|
415
|
+
|
|
416
|
+
const selection =
|
|
417
|
+
normalizeSelection(
|
|
418
|
+
parsed,
|
|
419
|
+
{
|
|
420
|
+
availableRoles,
|
|
421
|
+
fallback,
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
/*
|
|
426
|
+
* A confidence of zero combined with an invalid
|
|
427
|
+
* selector response means we should preserve the
|
|
428
|
+
* deterministic routing decision.
|
|
429
|
+
*/
|
|
430
|
+
if (
|
|
431
|
+
!parsed ||
|
|
432
|
+
!normalizeRole(
|
|
433
|
+
parsed.role,
|
|
434
|
+
availableRoles
|
|
435
|
+
)
|
|
436
|
+
) {
|
|
437
|
+
return {
|
|
438
|
+
...fallback,
|
|
439
|
+
source: "fallback",
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
...selection,
|
|
445
|
+
source: "model",
|
|
446
|
+
};
|
|
447
|
+
} catch {
|
|
448
|
+
/*
|
|
449
|
+
* Model selection must never make CodeForge unusable.
|
|
450
|
+
* If the selector fails, existing deterministic
|
|
451
|
+
* routing remains authoritative.
|
|
452
|
+
*/
|
|
453
|
+
return {
|
|
454
|
+
...fallback,
|
|
455
|
+
source: "fallback",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
select,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export {
|
|
466
|
+
MODEL_ROLES,
|
|
467
|
+
COMPLEXITIES,
|
|
468
|
+
normalizeSelection,
|
|
469
|
+
};
|