@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.
Files changed (141) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +7 -0
  3. package/apps/cli/bin/codeforge.cmd +2 -0
  4. package/apps/cli/src/bootstrap.js +115 -0
  5. package/apps/cli/src/cli.js +189 -0
  6. package/apps/cli/src/dev/git.js +17 -0
  7. package/apps/cli/src/dev/index.js +68 -0
  8. package/apps/cli/src/dev/provider.js +48 -0
  9. package/apps/cli/src/dev/scanner.js +14 -0
  10. package/apps/cli/src/index.js +31 -0
  11. package/bin/codeforge.js +2 -0
  12. package/package.json +63 -0
  13. package/packages/agent/contextBuilder.js +234 -0
  14. package/packages/agent/executor.js +1729 -0
  15. package/packages/agent/fallback.js +129 -0
  16. package/packages/agent/index.js +26 -0
  17. package/packages/agent/intent.js +73 -0
  18. package/packages/agent/modelSelector.js +469 -0
  19. package/packages/agent/planner.js +379 -0
  20. package/packages/agent/rag.js +412 -0
  21. package/packages/agent/ragSelector.js +570 -0
  22. package/packages/agent/router.js +121 -0
  23. package/packages/agent/thinking.js +54 -0
  24. package/packages/agent/toolCalls.js +258 -0
  25. package/packages/config/config.js +391 -0
  26. package/packages/config/defaults.js +72 -0
  27. package/packages/config/index.js +2 -0
  28. package/packages/core/approvalPolicy.js +140 -0
  29. package/packages/core/context.js +0 -0
  30. package/packages/core/message.js +0 -0
  31. package/packages/core/plan.js +0 -0
  32. package/packages/core/reasoning.js +0 -0
  33. package/packages/core/result.js +0 -0
  34. package/packages/core/retrieval.js +0 -0
  35. package/packages/core/tool.js +536 -0
  36. package/packages/git/branch.js +7 -0
  37. package/packages/git/commit.js +16 -0
  38. package/packages/git/diff.js +15 -0
  39. package/packages/git/git.js +35 -0
  40. package/packages/git/index.js +6 -0
  41. package/packages/git/remote.js +9 -0
  42. package/packages/git/status.js +11 -0
  43. package/packages/memory/index.js +0 -0
  44. package/packages/memory/project.js +0 -0
  45. package/packages/memory/session.js +0 -0
  46. package/packages/prompts/builder.js +43 -0
  47. package/packages/prompts/coder.js +38 -0
  48. package/packages/prompts/index.js +5 -0
  49. package/packages/prompts/planner.js +49 -0
  50. package/packages/prompts/reviewer.js +38 -0
  51. package/packages/prompts/system.js +94 -0
  52. package/packages/providers/index.js +31 -0
  53. package/packages/providers/openrouter.js +380 -0
  54. package/packages/reasoning/budget.js +0 -0
  55. package/packages/reasoning/chain.js +0 -0
  56. package/packages/reasoning/coder.js +0 -0
  57. package/packages/reasoning/evaluator.js +0 -0
  58. package/packages/reasoning/index.js +8 -0
  59. package/packages/reasoning/planner.js +0 -0
  60. package/packages/reasoning/reflector.js +0 -0
  61. package/packages/reasoning/reviewer.js +0 -0
  62. package/packages/reasoning/router.js +0 -0
  63. package/packages/retrieval/budget.js +92 -0
  64. package/packages/retrieval/cache.js +84 -0
  65. package/packages/retrieval/embeddings.js +147 -0
  66. package/packages/retrieval/graph.js +151 -0
  67. package/packages/retrieval/imports.js +74 -0
  68. package/packages/retrieval/index.js +9 -0
  69. package/packages/retrieval/parser/extractors/calls.js +43 -0
  70. package/packages/retrieval/parser/extractors/comments.js +49 -0
  71. package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
  72. package/packages/retrieval/parser/extractors/exports.js +71 -0
  73. package/packages/retrieval/parser/extractors/imports.js +40 -0
  74. package/packages/retrieval/parser/extractors/index.js +12 -0
  75. package/packages/retrieval/parser/extractors/metrics.js +78 -0
  76. package/packages/retrieval/parser/extractors/symbols.js +51 -0
  77. package/packages/retrieval/parser/extractors/todos.js +53 -0
  78. package/packages/retrieval/parser/helpers.js +138 -0
  79. package/packages/retrieval/parser/index.js +10 -0
  80. package/packages/retrieval/parser/languages/c.js +14 -0
  81. package/packages/retrieval/parser/languages/cpp.js +21 -0
  82. package/packages/retrieval/parser/languages/csharp.js +14 -0
  83. package/packages/retrieval/parser/languages/go.js +14 -0
  84. package/packages/retrieval/parser/languages/index.js +12 -0
  85. package/packages/retrieval/parser/languages/java.js +14 -0
  86. package/packages/retrieval/parser/languages/javascript.js +14 -0
  87. package/packages/retrieval/parser/languages/php.js +14 -0
  88. package/packages/retrieval/parser/languages/python.js +14 -0
  89. package/packages/retrieval/parser/languages/ruby.js +14 -0
  90. package/packages/retrieval/parser/languages/rust.js +14 -0
  91. package/packages/retrieval/parser/languages/swift.js +14 -0
  92. package/packages/retrieval/parser/languages/typescript.js +14 -0
  93. package/packages/retrieval/parser/languages.js +218 -0
  94. package/packages/retrieval/parser/parseFile.js +79 -0
  95. package/packages/retrieval/parser/parseRepository.js +78 -0
  96. package/packages/retrieval/parser/queries/c.js +148 -0
  97. package/packages/retrieval/parser/queries/cpp.js +198 -0
  98. package/packages/retrieval/parser/queries/csharp.js +151 -0
  99. package/packages/retrieval/parser/queries/go.js +124 -0
  100. package/packages/retrieval/parser/queries/index.js +35 -0
  101. package/packages/retrieval/parser/queries/java.js +128 -0
  102. package/packages/retrieval/parser/queries/javascript.js +181 -0
  103. package/packages/retrieval/parser/queries/php.js +140 -0
  104. package/packages/retrieval/parser/queries/python.js +110 -0
  105. package/packages/retrieval/parser/queries/ruby.js +105 -0
  106. package/packages/retrieval/parser/queries/rust.js +175 -0
  107. package/packages/retrieval/parser/queries/swift.js +134 -0
  108. package/packages/retrieval/parser/queries/typescript.js +202 -0
  109. package/packages/retrieval/parser/registry.js +107 -0
  110. package/packages/retrieval/parser/traverse.js +82 -0
  111. package/packages/retrieval/parser/types.js +239 -0
  112. package/packages/retrieval/parser.js +78 -0
  113. package/packages/retrieval/ranker.js +73 -0
  114. package/packages/retrieval/retriever.js +910 -0
  115. package/packages/retrieval/symbols.js +145 -0
  116. package/packages/scanner/detect.js +69 -0
  117. package/packages/scanner/ignore.js +12 -0
  118. package/packages/scanner/index.js +2 -0
  119. package/packages/scanner/root.js +21 -0
  120. package/packages/scanner/scanner.js +35 -0
  121. package/packages/scanner/tree.js +45 -0
  122. package/packages/terminal/activity.js +514 -0
  123. package/packages/terminal/banner.js +34 -0
  124. package/packages/terminal/colors.js +16 -0
  125. package/packages/terminal/dashboard.js +94 -0
  126. package/packages/terminal/index.js +19 -0
  127. package/packages/terminal/logger.js +43 -0
  128. package/packages/terminal/prompt.js +11 -0
  129. package/packages/terminal/renderer.js +1209 -0
  130. package/packages/terminal/spinner.js +9 -0
  131. package/packages/tools/editFile.js +261 -0
  132. package/packages/tools/filesystem.js +82 -0
  133. package/packages/tools/index.js +24 -0
  134. package/packages/tools/readFile.js +101 -0
  135. package/packages/tools/registry.js +227 -0
  136. package/packages/tools/searchFiles.js +322 -0
  137. package/packages/tools/shell.js +352 -0
  138. package/packages/tools/writeFile.js +212 -0
  139. package/packages/utils/constants.js +0 -0
  140. package/packages/utils/helpers.js +0 -0
  141. 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
+ }