@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,1729 @@
1
+ import { detectIntent } from "./intent.js";
2
+ import { createExecutionPlan } from "./planner.js";
3
+ import { retrieveContext } from "./rag.js";
4
+ import { buildContext } from "./contextBuilder.js";
5
+ import { routeRequest } from "./router.js";
6
+ import { configureThinking } from "./thinking.js";
7
+ import { getFallbackRoute } from "./fallback.js";
8
+ import {
9
+ createOpenRouterTools,
10
+ } from "../providers/openrouter.js";
11
+ import {
12
+ executeAuthorizedTool,
13
+ } from "../core/tool.js";
14
+
15
+ import {
16
+ createToolCallFailure,
17
+ resolveToolCalls,
18
+ } from "./toolCalls.js";
19
+
20
+ import {
21
+ createModelSelector,
22
+ } from "./modelSelector.js";
23
+
24
+ import {
25
+ createRagSelector,
26
+ } from "./ragSelector.js";
27
+
28
+
29
+ function getAvailableTools(tools) {
30
+ if (!tools) {
31
+ return [];
32
+ }
33
+
34
+ if (
35
+ typeof tools.describe !== "function" ||
36
+ typeof tools.get !== "function"
37
+ ) {
38
+ throw new TypeError(
39
+ "Invalid tool registry."
40
+ );
41
+ }
42
+
43
+ return tools.describe();
44
+ }
45
+ function getRepositoryToolPolicy(
46
+ rag
47
+ ) {
48
+ if (
49
+ !rag?.enabled ||
50
+ !Array.isArray(rag.results)
51
+ ) {
52
+ return {
53
+ suppressSearch: false,
54
+ suppressRead: false,
55
+ evidenceLevel: "none",
56
+ };
57
+ }
58
+
59
+ const implementationResults =
60
+ rag.results.filter(
61
+ (result) =>
62
+ result?.reason ===
63
+ "implementation" &&
64
+ typeof result?.content ===
65
+ "string" &&
66
+ result.content.trim().length >
67
+ 0
68
+ );
69
+
70
+ const substantialResults =
71
+ implementationResults.filter(
72
+ (result) =>
73
+ result.content.trim().length >=
74
+ 500
75
+ );
76
+
77
+ const totalImplementationChars =
78
+ implementationResults.reduce(
79
+ (total, result) =>
80
+ total +
81
+ result.content.trim().length,
82
+ 0
83
+ );
84
+
85
+ const utilization =
86
+ rag?.stats?.utilization ?? 0;
87
+
88
+ const strongEvidence =
89
+ implementationResults.length >= 2;
90
+
91
+ const comprehensiveEvidence =
92
+ substantialResults.length >= 3 ||
93
+ (
94
+ implementationResults.length >= 3 &&
95
+ totalImplementationChars >= 2500 &&
96
+ utilization >= 0.5
97
+ );
98
+
99
+ return {
100
+ suppressSearch:
101
+ strongEvidence,
102
+
103
+ suppressRead:
104
+ comprehensiveEvidence,
105
+
106
+ evidenceLevel:
107
+ comprehensiveEvidence
108
+ ? "comprehensive"
109
+ : strongEvidence
110
+ ? "strong"
111
+ : "weak",
112
+ };
113
+ }
114
+
115
+ function normalizeUsage(usage = {}) {
116
+ const promptTokens =
117
+ usage.promptTokens ??
118
+ usage.prompt_tokens ??
119
+ 0;
120
+
121
+ const completionTokens =
122
+ usage.completionTokens ??
123
+ usage.completion_tokens ??
124
+ 0;
125
+
126
+ const reasoningTokens =
127
+ usage.completionTokensDetails?.reasoningTokens ??
128
+ usage.completion_tokens_details?.reasoning_tokens ??
129
+ 0;
130
+
131
+ const totalTokens =
132
+ usage.totalTokens ??
133
+ usage.total_tokens ??
134
+ promptTokens + completionTokens;
135
+
136
+ const cost =
137
+ typeof usage.cost === "number"
138
+ ? usage.cost
139
+ : null;
140
+
141
+ return {
142
+ promptTokens,
143
+ completionTokens,
144
+ reasoningTokens,
145
+ totalTokens,
146
+ cost,
147
+ };
148
+ }
149
+
150
+ function estimateTextTokens(text = "") {
151
+ if (typeof text !== "string") {
152
+ return 0;
153
+ }
154
+
155
+ return Math.ceil(text.length / 4);
156
+ }
157
+
158
+ function measureModelContext({
159
+ messages = [],
160
+ tools = [],
161
+ }) {
162
+ let systemChars = 0;
163
+ let userChars = 0;
164
+ let assistantChars = 0;
165
+ let toolResultChars = 0;
166
+
167
+ for (const message of messages) {
168
+ const content =
169
+ typeof message?.content === "string"
170
+ ? message.content
171
+ : "";
172
+
173
+ switch (message?.role) {
174
+ case "system":
175
+ systemChars += content.length;
176
+ break;
177
+
178
+ case "user":
179
+ userChars += content.length;
180
+ break;
181
+
182
+ case "assistant":
183
+ assistantChars += content.length;
184
+ break;
185
+
186
+ case "tool":
187
+ toolResultChars += content.length;
188
+ break;
189
+
190
+ default:
191
+ break;
192
+ }
193
+ }
194
+
195
+ const toolSchemaChars =
196
+ JSON.stringify(
197
+ tools ?? []
198
+ ).length;
199
+
200
+ const messageChars =
201
+ systemChars +
202
+ userChars +
203
+ assistantChars +
204
+ toolResultChars;
205
+
206
+ const totalChars =
207
+ messageChars +
208
+ toolSchemaChars;
209
+
210
+ return {
211
+ messages:
212
+ messages.length,
213
+
214
+ chars: {
215
+ system:
216
+ systemChars,
217
+
218
+ user:
219
+ userChars,
220
+
221
+ assistant:
222
+ assistantChars,
223
+
224
+ toolResults:
225
+ toolResultChars,
226
+
227
+ toolSchemas:
228
+ toolSchemaChars,
229
+
230
+ total:
231
+ totalChars,
232
+ },
233
+
234
+ estimatedTokens: {
235
+ system:
236
+ Math.ceil(
237
+ systemChars / 4
238
+ ),
239
+
240
+ user:
241
+ Math.ceil(
242
+ userChars / 4
243
+ ),
244
+
245
+ assistant:
246
+ Math.ceil(
247
+ assistantChars / 4
248
+ ),
249
+
250
+ toolResults:
251
+ Math.ceil(
252
+ toolResultChars / 4
253
+ ),
254
+
255
+ toolSchemas:
256
+ Math.ceil(
257
+ toolSchemaChars / 4
258
+ ),
259
+
260
+ total:
261
+ Math.ceil(
262
+ totalChars / 4
263
+ ),
264
+ },
265
+ };
266
+ }
267
+
268
+ function getRetrievedContext(rag = {}) {
269
+ const results = Array.isArray(rag.results)
270
+ ? rag.results
271
+ : [];
272
+
273
+ return results
274
+ .filter((result) => result?.path)
275
+ .map((result) => ({
276
+ path: result.path,
277
+ score:
278
+ Number.isFinite(result.score)
279
+ ? result.score
280
+ : null,
281
+ reason: result.reason ?? null,
282
+ }));
283
+ }
284
+
285
+ function stableSerialize(value) {
286
+ if (
287
+ value === null ||
288
+ typeof value !== "object"
289
+ ) {
290
+ return JSON.stringify(value);
291
+ }
292
+
293
+ if (Array.isArray(value)) {
294
+ return `[${value
295
+ .map(stableSerialize)
296
+ .join(",")}]`;
297
+ }
298
+
299
+ const keys =
300
+ Object.keys(value).sort();
301
+
302
+ return `{${keys
303
+ .map(
304
+ (key) =>
305
+ `${JSON.stringify(key)}:${stableSerialize(
306
+ value[key]
307
+ )}`
308
+ )
309
+ .join(",")}}`;
310
+ }
311
+
312
+ function createToolCallFingerprint(
313
+ name,
314
+ args
315
+ ) {
316
+ return `${name}:${stableSerialize(
317
+ args ?? {}
318
+ )}`;
319
+ }
320
+
321
+ function createDuplicateToolResult({
322
+ call,
323
+ previous,
324
+ }) {
325
+ const now = Date.now();
326
+
327
+ return {
328
+ success: false,
329
+ tool: call.name,
330
+
331
+ output: null,
332
+
333
+ error: {
334
+ code: "duplicate_tool_call",
335
+
336
+ message:
337
+ `Duplicate tool call skipped: ${call.name}`,
338
+
339
+ details: {
340
+ tool: call.name,
341
+ previousToolCallId:
342
+ previous?.id ?? null,
343
+ },
344
+ },
345
+
346
+ metadata: {
347
+ source: "agent",
348
+ sideEffect:
349
+ call.tool?.sideEffect ??
350
+ "none",
351
+
352
+ approval:
353
+ call.tool?.approval ??
354
+ "never",
355
+
356
+ authorization: {
357
+ allowed: false,
358
+ code: "duplicate_tool_call",
359
+ reason:
360
+ "An identical side-effect-free tool call already completed during this execution.",
361
+ requiresApproval: false,
362
+ },
363
+ },
364
+
365
+ timing: {
366
+ startedAt: now,
367
+ completedAt: now,
368
+ durationMs: 0,
369
+ },
370
+ };
371
+ }
372
+
373
+ function createToolBudgetResult({
374
+ call,
375
+ budget,
376
+ }) {
377
+ const now = Date.now();
378
+
379
+ return {
380
+ success: false,
381
+ tool: call.name,
382
+
383
+ output: null,
384
+
385
+ error: {
386
+ code: "tool_budget_exceeded",
387
+
388
+ message:
389
+ `Tool budget exhausted for ${call.name}. Use the repository context and tool results already collected.`,
390
+
391
+ details: {
392
+ tool: call.name,
393
+ used: budget.used,
394
+ max: budget.max,
395
+ },
396
+ },
397
+
398
+ metadata: {
399
+ source: "agent",
400
+
401
+ sideEffect:
402
+ call.tool?.sideEffect ??
403
+ "none",
404
+
405
+ approval:
406
+ call.tool?.approval ??
407
+ "never",
408
+
409
+ authorization: {
410
+ allowed: false,
411
+ code: "tool_budget_exceeded",
412
+ reason:
413
+ "The per-execution exploration budget for this tool has been exhausted.",
414
+ requiresApproval: false,
415
+ },
416
+ },
417
+
418
+ timing: {
419
+ startedAt: now,
420
+ completedAt: now,
421
+ durationMs: 0,
422
+ },
423
+ };
424
+ }
425
+
426
+ async function executeResolvedToolCalls({
427
+ toolCalls,
428
+ registry,
429
+ projectRoot,
430
+ approvedTools = new Set(),
431
+ allowedCapabilities,
432
+ requestApproval = null,
433
+ toolCallHistory = null,
434
+ toolBudget = null,
435
+ emit,
436
+ }) {
437
+ const resolvedCalls =
438
+ resolveToolCalls(
439
+ toolCalls,
440
+ registry
441
+ );
442
+
443
+ const results = [];
444
+
445
+ for (const call of resolvedCalls) {
446
+ const fingerprint =
447
+ createToolCallFingerprint(
448
+ call.name,
449
+ call.arguments
450
+ );
451
+
452
+ const canDeduplicate =
453
+ call.tool?.sideEffect ===
454
+ "none" &&
455
+ toolCallHistory instanceof Map;
456
+
457
+ const previous =
458
+ canDeduplicate
459
+ ? toolCallHistory.get(
460
+ fingerprint
461
+ )
462
+ : null;
463
+
464
+ if (previous) {
465
+ const result =
466
+ createDuplicateToolResult({
467
+ call,
468
+ previous,
469
+ });
470
+
471
+ results.push({
472
+ id: call.id,
473
+ name: call.name,
474
+ arguments: call.arguments,
475
+ result,
476
+ });
477
+
478
+ emit({
479
+ type: "stage:error",
480
+ stage: "tool",
481
+
482
+ detail:
483
+ `${call.name}: duplicate call skipped`,
484
+
485
+ data: {
486
+ tool: call.name,
487
+ toolCallId: call.id,
488
+ success: false,
489
+ duplicate: true,
490
+
491
+ previousToolCallId:
492
+ previous.id,
493
+
494
+ error: result.error,
495
+ },
496
+ });
497
+
498
+ continue;
499
+ }
500
+
501
+ const budget =
502
+ toolBudget &&
503
+ typeof toolBudget === "object"
504
+ ? toolBudget[call.name]
505
+ : null;
506
+
507
+ if (
508
+ budget &&
509
+ budget.used >= budget.max
510
+ ) {
511
+ const result =
512
+ createToolBudgetResult({
513
+ call,
514
+ budget,
515
+ });
516
+
517
+ results.push({
518
+ id: call.id,
519
+ name: call.name,
520
+ arguments: call.arguments,
521
+ result,
522
+ });
523
+
524
+ emit({
525
+ type: "stage:error",
526
+ stage: "tool",
527
+
528
+ detail:
529
+ `${call.name}: tool budget exhausted`,
530
+
531
+ data: {
532
+ tool: call.name,
533
+ toolCallId: call.id,
534
+ success: false,
535
+ budgetExceeded: true,
536
+ used: budget.used,
537
+ max: budget.max,
538
+ error: result.error,
539
+ },
540
+ });
541
+
542
+ continue;
543
+ }
544
+
545
+ if (budget) {
546
+ budget.used += 1;
547
+ }
548
+
549
+ emit({
550
+ type: "stage:start",
551
+ stage: "tool",
552
+ detail: `Running ${call.name}`,
553
+ data: {
554
+ tool: call.name,
555
+ toolCallId: call.id,
556
+ },
557
+ });
558
+ const approvalKey =
559
+ createToolCallFingerprint(
560
+ call.name,
561
+ call.arguments
562
+ );
563
+
564
+ const previouslyApproved =
565
+ approvedTools.has(approvalKey);
566
+ let result =
567
+ await executeAuthorizedTool(
568
+ call.tool,
569
+ call.arguments,
570
+ {
571
+ projectRoot,
572
+ },
573
+ {
574
+ allowedCapabilities,
575
+ approval:
576
+ previouslyApproved
577
+ ? "approved"
578
+ : null,
579
+ }
580
+ );
581
+
582
+ if (
583
+ !result.success &&
584
+ result.error?.code ===
585
+ "approval_required" &&
586
+ typeof requestApproval ===
587
+ "function"
588
+ ) {
589
+ emit({
590
+ type: "stage:start",
591
+ stage: "approval",
592
+ detail:
593
+ `Approval required for ${call.name}`,
594
+ data: {
595
+ tool: call.name,
596
+ toolCallId: call.id,
597
+ arguments: call.arguments,
598
+ },
599
+ });
600
+
601
+ const approved =
602
+ await requestApproval({
603
+ tool: call.tool,
604
+ toolName: call.name,
605
+ toolCallId: call.id,
606
+ arguments: call.arguments,
607
+ authorization:
608
+ result.metadata?.authorization ??
609
+ null,
610
+ });
611
+
612
+ if (approved === true) {
613
+ approvedTools.add(approvalKey);
614
+ result =
615
+ await executeAuthorizedTool(
616
+ call.tool,
617
+ call.arguments,
618
+ {
619
+ projectRoot,
620
+ },
621
+ {
622
+ allowedCapabilities,
623
+ approval: "approved",
624
+ }
625
+ );
626
+ }
627
+
628
+ else {
629
+ result = {
630
+ ...result,
631
+
632
+ error: {
633
+ code: "approval_denied",
634
+ message:
635
+ `Approval denied for tool: ${call.name}`,
636
+ details: {
637
+ tool: call.name,
638
+ toolCallId: call.id,
639
+ },
640
+ },
641
+
642
+ metadata: {
643
+ ...result.metadata,
644
+
645
+ authorization: {
646
+ ...result.metadata?.authorization,
647
+ allowed: false,
648
+ code: "approval_denied",
649
+ reason:
650
+ "User denied tool execution.",
651
+ requiresApproval: true,
652
+ },
653
+ },
654
+ };
655
+ }
656
+
657
+ emit({
658
+ type:
659
+ approved === true &&
660
+ result.success
661
+ ? "stage:success"
662
+ : "stage:error",
663
+
664
+ stage: "approval",
665
+
666
+ detail:
667
+ approved === true
668
+ ? result.success
669
+ ? `${call.name} approved`
670
+ : `${call.name} approval execution failed`
671
+ : `${call.name} denied`,
672
+
673
+ data: {
674
+ tool: call.name,
675
+ toolCallId: call.id,
676
+ approved:
677
+ approved === true,
678
+ },
679
+ });
680
+ }
681
+
682
+ results.push({
683
+ id: call.id,
684
+ name: call.name,
685
+ arguments: call.arguments,
686
+ result,
687
+ });
688
+ if (
689
+ canDeduplicate &&
690
+ result.success
691
+ ) {
692
+ toolCallHistory.set(
693
+ fingerprint,
694
+ {
695
+ id: call.id,
696
+ name: call.name,
697
+ arguments:
698
+ call.arguments,
699
+ }
700
+ );
701
+ }
702
+ emit({
703
+ type: result.success
704
+ ? "stage:success"
705
+ : "stage:error",
706
+
707
+ stage: "tool",
708
+
709
+ detail: result.success
710
+ ? `${call.name} completed`
711
+ : `${call.name}: ${result.error?.message ??
712
+ "Tool execution failed"
713
+ }`,
714
+
715
+ data: {
716
+ tool: call.name,
717
+ toolCallId: call.id,
718
+ success: result.success,
719
+ error:
720
+ result.error ?? null,
721
+ },
722
+ });
723
+ }
724
+
725
+ return results;
726
+ }
727
+
728
+ function createToolResultMessages(
729
+ toolResults
730
+ ) {
731
+ return toolResults.map(
732
+ ({ id, result }) => ({
733
+ role: "tool",
734
+
735
+ tool_call_id: id,
736
+
737
+ content: JSON.stringify(
738
+ result.success
739
+ ? {
740
+ success: true,
741
+ output: result.output,
742
+ }
743
+ : {
744
+ success: false,
745
+ error: result.error,
746
+ }
747
+ ),
748
+ })
749
+ );
750
+ }
751
+
752
+ export async function execute({
753
+ prompt,
754
+ repository,
755
+ provider,
756
+ providers = [],
757
+ tools = null,
758
+ requestApproval = null,
759
+ config = {},
760
+ project = {},
761
+ git = {},
762
+ memory = {},
763
+ systemPrompt = "",
764
+ onEvent = null,
765
+ }) {
766
+ const emit = (event) => {
767
+ if (typeof onEvent === "function") {
768
+ onEvent(event);
769
+ }
770
+ };
771
+ const availableTools =
772
+ getAvailableTools(tools);
773
+ const approvedTools = new Set();
774
+ emit({
775
+ type: "run:start",
776
+ stage: "run",
777
+ data: {
778
+ toolCount: availableTools.length,
779
+ tools: availableTools.map(
780
+ (tool) => tool.name
781
+ ),
782
+ },
783
+ });
784
+ // 1. Detect intent
785
+ emit({
786
+ type: "stage:start",
787
+ stage: "intent",
788
+ detail: "Detecting request intent",
789
+ });
790
+
791
+ const intent = detectIntent(prompt);
792
+
793
+ emit({
794
+ type: "stage:success",
795
+ stage: "intent",
796
+ detail:
797
+ typeof intent === "string"
798
+ ? intent
799
+ : intent?.type ?? "Detected",
800
+ });
801
+
802
+ // 2. Build execution plan
803
+ emit({
804
+ type: "stage:start",
805
+ stage: "plan",
806
+ detail: "Building execution plan",
807
+ });
808
+
809
+ const plan =
810
+ createExecutionPlan(
811
+ intent,
812
+ {
813
+ prompt,
814
+ }
815
+ );
816
+
817
+ emit({
818
+ type: "stage:success",
819
+ stage: "plan",
820
+ detail: Array.isArray(plan?.steps)
821
+ ? plan.steps.join(" → ")
822
+ : "Plan ready",
823
+ });
824
+
825
+ emit({
826
+ type: "stage:start",
827
+ stage: "rag_select",
828
+ detail: "Deciding whether repository context is needed",
829
+ });
830
+
831
+ const selectorProvider =
832
+ config.selectors?.provider
833
+ ? providers.find(
834
+ (item) =>
835
+ item?.name ===
836
+ config.selectors.provider
837
+ )?.provider ?? null
838
+ : provider;
839
+
840
+ const ragSelector =
841
+ createRagSelector({
842
+ provider:
843
+ selectorProvider ?? provider,
844
+
845
+ model:
846
+ config.selectors?.rag ?? null,
847
+
848
+ maxTokens:
849
+ config.selectors?.maxTokens ?? 128,
850
+ });
851
+
852
+ const ragDecision =
853
+ await ragSelector.select({
854
+ prompt,
855
+ plan,
856
+ });
857
+
858
+ const modelSelector =
859
+ createModelSelector({
860
+ provider:
861
+ selectorProvider ?? provider,
862
+
863
+ model:
864
+ config.selectors?.model ?? null,
865
+
866
+ maxTokens:
867
+ config.selectors?.modelMaxTokens ?? 256,
868
+ });
869
+
870
+ const modelDecision =
871
+ await modelSelector.select({
872
+ prompt,
873
+ intent,
874
+ plan,
875
+ ragDecision,
876
+
877
+ availableRoles:
878
+ Object.keys(
879
+ config.models ?? {}
880
+ ).filter(
881
+ (role) =>
882
+ ![
883
+ "fallback",
884
+ "emergency",
885
+ ].includes(role)
886
+ ),
887
+ });
888
+
889
+ emit({
890
+ type: "stage:success",
891
+ stage: "model_select",
892
+ detail:
893
+ `${modelDecision.role} · ${Math.round(
894
+ modelDecision.confidence * 100
895
+ )}%`,
896
+ data: modelDecision,
897
+ });
898
+ const effectivePlan = {
899
+ ...plan,
900
+
901
+ requiresRAG:
902
+ ragDecision.required &&
903
+ !plan.directFileTarget,
904
+
905
+ ragScope:
906
+ ragDecision.scope,
907
+
908
+ requiresThinking:
909
+ modelDecision.reasoningRequired ||
910
+ plan.requiresThinking,
911
+
912
+ requiresTools:
913
+ modelDecision.toolRequired ||
914
+ plan.requiresTools,
915
+
916
+ modelRole:
917
+ modelDecision.role,
918
+
919
+ modelDecision,
920
+ ragDecision,
921
+ };
922
+
923
+ if (effectivePlan?.requiresRAG) {
924
+ emit({
925
+ type: "stage:start",
926
+ stage: "retrieve",
927
+ detail: "Searching repository context",
928
+ });
929
+ }
930
+
931
+ let rag = {
932
+ enabled: false,
933
+ results: [],
934
+ stats: {
935
+ retrieved: 0,
936
+ selected: 0,
937
+ estimatedTokens: 0,
938
+ tokenBudget: 0,
939
+ remainingTokens: 0,
940
+ utilization: 0,
941
+ },
942
+ };
943
+
944
+ if (effectivePlan.requiresRAG) {
945
+ rag =
946
+ await retrieveContext({
947
+ repository,
948
+ plan: effectivePlan,
949
+ query: prompt,
950
+ });
951
+ }
952
+
953
+ const retrievalCount =
954
+ rag?.results?.length ??
955
+ rag?.chunks?.length ??
956
+ rag?.documents?.length ??
957
+ 0;
958
+
959
+ const retrievedContext =
960
+ getRetrievedContext(rag);
961
+
962
+ const retrievedFiles =
963
+ retrievedContext.map(
964
+ (result) =>
965
+ result.path
966
+ );
967
+
968
+ if (effectivePlan?.requiresRAG) {
969
+ emit({
970
+ type: "stage:success",
971
+ stage: "retrieve",
972
+ detail:
973
+ `${retrievalCount} result${retrievalCount === 1
974
+ ? ""
975
+ : "s"
976
+ }`,
977
+ data: {
978
+ count: retrievalCount,
979
+ files: retrievedFiles,
980
+ results: retrievedContext,
981
+ },
982
+ });
983
+ }
984
+ const repositoryToolPolicy =
985
+ getRepositoryToolPolicy(
986
+ rag
987
+ );
988
+
989
+ const shouldAnswerFromRAG =
990
+ repositoryToolPolicy
991
+ .evidenceLevel ===
992
+ "comprehensive";
993
+
994
+ const modelAvailableTools =
995
+ shouldAnswerFromRAG
996
+ ? []
997
+ : availableTools.filter(
998
+ (tool) => {
999
+ if (
1000
+ repositoryToolPolicy
1001
+ .suppressSearch &&
1002
+ tool.name ===
1003
+ "search_files"
1004
+ ) {
1005
+ return false;
1006
+ }
1007
+
1008
+ if (
1009
+ repositoryToolPolicy
1010
+ .suppressRead &&
1011
+ tool.name ===
1012
+ "read_file"
1013
+ ) {
1014
+ return false;
1015
+ }
1016
+
1017
+ return true;
1018
+ }
1019
+ );
1020
+
1021
+ const modelTools =
1022
+ createOpenRouterTools(
1023
+ modelAvailableTools
1024
+ );
1025
+
1026
+ emit({
1027
+ type: "stage:start",
1028
+ stage: "context",
1029
+ detail: "Building model context",
1030
+ });
1031
+
1032
+ const context = buildContext({
1033
+ prompt,
1034
+ plan: effectivePlan,
1035
+ project,
1036
+ git,
1037
+ memory,
1038
+ rag,
1039
+ systemPrompt,
1040
+ });
1041
+
1042
+ emit({
1043
+ type: "stage:success",
1044
+ stage: "context",
1045
+ detail: "Context ready",
1046
+ });
1047
+
1048
+ emit({
1049
+ type: "stage:start",
1050
+ stage: "route",
1051
+ detail: "Selecting provider and model",
1052
+ });
1053
+
1054
+ let route = routeRequest({
1055
+ plan: effectivePlan,
1056
+ config,
1057
+ providers,
1058
+ });
1059
+
1060
+ emit({
1061
+ type: "stage:success",
1062
+ stage: "route",
1063
+ detail:
1064
+ [
1065
+ route?.provider,
1066
+ route?.modelRole,
1067
+ route?.model,
1068
+ ]
1069
+ .filter(Boolean)
1070
+ .join(" · ") ||
1071
+ "Route selected",
1072
+ data: {
1073
+ provider:
1074
+ route?.provider ?? null,
1075
+
1076
+ modelRole:
1077
+ route?.modelRole ?? null,
1078
+
1079
+ model:
1080
+ route?.model ?? null,
1081
+ },
1082
+ });
1083
+
1084
+ emit({
1085
+ type: "stage:start",
1086
+ stage: "thinking",
1087
+ detail: "Configuring reasoning",
1088
+ });
1089
+
1090
+ const thinking = configureThinking({
1091
+ plan: effectivePlan,
1092
+ route,
1093
+ });
1094
+
1095
+ emit({
1096
+ type: "stage:success",
1097
+ stage: "thinking",
1098
+ detail:
1099
+ thinking?.enabled === false
1100
+ ? "Disabled"
1101
+ : [
1102
+ thinking?.mode,
1103
+ thinking?.budget,
1104
+ ]
1105
+ .filter(
1106
+ (value) =>
1107
+ value !== undefined &&
1108
+ value !== null
1109
+ )
1110
+ .join(" · ") ||
1111
+ "Configured",
1112
+ data: {
1113
+ enabled:
1114
+ thinking?.enabled ?? false,
1115
+ mode:
1116
+ thinking?.mode ?? null,
1117
+ budget:
1118
+ thinking?.budget ?? null,
1119
+ },
1120
+ });
1121
+
1122
+ let attempt = 0;
1123
+
1124
+ const maxAttempts =
1125
+ config.maxAttempts ?? 3;
1126
+
1127
+ const configuredMaxToolRounds =
1128
+ config.maxToolRounds ?? 10;
1129
+
1130
+ const maxToolRounds =
1131
+ repositoryToolPolicy
1132
+ .evidenceLevel ===
1133
+ "comprehensive"
1134
+ ? Math.min(
1135
+ configuredMaxToolRounds,
1136
+ 3
1137
+ )
1138
+ : configuredMaxToolRounds;
1139
+ const maxToolProtocolErrors = 2;
1140
+
1141
+ const verification = {
1142
+ required:
1143
+ effectivePlan.requiresWrite === true,
1144
+
1145
+ attempted: false,
1146
+
1147
+ succeeded: false,
1148
+
1149
+ writeSinceLastVerification: false,
1150
+ };
1151
+
1152
+ const messages = [];
1153
+
1154
+ if (context.system) {
1155
+ messages.push({
1156
+ role: "system",
1157
+ content: context.system,
1158
+ });
1159
+ }
1160
+
1161
+ messages.push({
1162
+ role: "user",
1163
+ content:
1164
+ context.modelUser ??
1165
+ context.user,
1166
+ });
1167
+
1168
+ while (attempt < maxAttempts) {
1169
+ const currentAttempt = attempt + 1;
1170
+
1171
+ try {
1172
+ emit({
1173
+ type: "stage:start",
1174
+ stage: "generate",
1175
+ detail: `Calling ${route?.model ?? "model"
1176
+ } · attempt ${currentAttempt}/${maxAttempts}`,
1177
+ data: {
1178
+ provider:
1179
+ route?.provider ?? null,
1180
+
1181
+ modelRole:
1182
+ route?.modelRole ?? null,
1183
+
1184
+ model:
1185
+ route?.model ?? null,
1186
+
1187
+ attempt: currentAttempt,
1188
+ maxAttempts,
1189
+ },
1190
+ });
1191
+
1192
+ let response = null;
1193
+ let toolResults = [];
1194
+ let toolRound = 0;
1195
+ let toolProtocolErrors = 0;
1196
+
1197
+ const toolCallHistory =
1198
+ new Map();
1199
+
1200
+ const toolBudget = {
1201
+ search_files: {
1202
+ used: 0,
1203
+ max: 2,
1204
+ },
1205
+
1206
+ read_file: {
1207
+ used: 0,
1208
+ max: 6,
1209
+ },
1210
+
1211
+ execute_command: {
1212
+ used: 0,
1213
+ max: 3,
1214
+ },
1215
+ };
1216
+
1217
+ while (true) {
1218
+ const contextMetrics =
1219
+ measureModelContext({
1220
+ messages,
1221
+ tools: modelTools,
1222
+ });
1223
+
1224
+ emit({
1225
+ type: "context:metrics",
1226
+ stage: "context",
1227
+ data: {
1228
+ toolRound,
1229
+ ...contextMetrics,
1230
+ },
1231
+ });
1232
+ response =
1233
+ await provider.generate({
1234
+ messages,
1235
+ model: route.model,
1236
+ temperature:
1237
+ route.temperature,
1238
+ maxTokens:
1239
+ route.maxTokens,
1240
+ thinking,
1241
+ tools: modelTools,
1242
+ stream: route.stream,
1243
+ });
1244
+ if (
1245
+ response &&
1246
+ Array.isArray(response.toolCalls) &&
1247
+ response.toolCalls.length === 0
1248
+ ) {
1249
+ emit({
1250
+ type: "stage:info",
1251
+ stage: "generate",
1252
+ detail: "Final model response received",
1253
+ data: {
1254
+ hasMessage: Boolean(response.message),
1255
+ contentType:
1256
+ typeof response.message?.content,
1257
+ contentLength:
1258
+ typeof response.message?.content === "string"
1259
+ ? response.message.content.length
1260
+ : 0,
1261
+ finishReason:
1262
+ response.finishReason ?? null,
1263
+ model:
1264
+ response.model ?? null,
1265
+ },
1266
+ });
1267
+ }
1268
+ const responseToolCalls =
1269
+ Array.isArray(response?.toolCalls)
1270
+ ? response.toolCalls
1271
+ : [];
1272
+
1273
+ // Model produced a normal final response.
1274
+ // Model produced a normal final response.
1275
+ if (responseToolCalls.length === 0) {
1276
+ if (
1277
+ verification.required &&
1278
+ verification.writeSinceLastVerification &&
1279
+ !verification.attempted
1280
+ ) {
1281
+ messages.push({
1282
+ role: "user",
1283
+ content: [
1284
+ "Verification is required before completing this coding task.",
1285
+ "A project change was made, but no verification command has been executed yet.",
1286
+ "Use execute_command to run an appropriate verification command.",
1287
+ "Choose the verification command from the actual project context and available project tooling.",
1288
+ "Do not invent imports, exports, APIs, commands, or interfaces solely for verification.",
1289
+ "Do not provide the final answer until verification has been attempted.",
1290
+ ].join("\n"),
1291
+ });
1292
+
1293
+ emit({
1294
+ type: "stage:error",
1295
+ stage: "verification",
1296
+ detail:
1297
+ "Model attempted to finish before verification.",
1298
+ data: {
1299
+ required:
1300
+ verification.required,
1301
+ attempted:
1302
+ verification.attempted,
1303
+ succeeded:
1304
+ verification.succeeded,
1305
+ },
1306
+ });
1307
+
1308
+ continue;
1309
+ }
1310
+
1311
+ if (
1312
+ verification.required &&
1313
+ verification.attempted &&
1314
+ !verification.succeeded
1315
+ ) {
1316
+ messages.push({
1317
+ role: "user",
1318
+ content: [
1319
+ "The verification command failed.",
1320
+ "Do not provide the final answer yet.",
1321
+ "Use the verification failure output already provided in the tool result to diagnose the requested change.",
1322
+ "Only modify the implementation if the failure is relevant to the requested change.",
1323
+ "After making a relevant correction, run execute_command again to verify the change.",
1324
+ "Do not invent imports, exports, APIs, commands, or interfaces solely to make verification pass.",
1325
+ ].join("\n"),
1326
+ });
1327
+
1328
+ emit({
1329
+ type: "stage:error",
1330
+ stage: "verification",
1331
+ detail:
1332
+ "Verification failed; model must diagnose and repair before completing.",
1333
+ data: {
1334
+ required:
1335
+ verification.required,
1336
+ attempted:
1337
+ verification.attempted,
1338
+ succeeded:
1339
+ verification.succeeded,
1340
+ },
1341
+ });
1342
+
1343
+ continue;
1344
+ }
1345
+
1346
+ break;
1347
+ }
1348
+
1349
+ if (toolRound >= maxToolRounds) {
1350
+ const error =
1351
+ new Error(
1352
+ `Maximum tool rounds exceeded (${maxToolRounds}).`
1353
+ );
1354
+
1355
+ error.code =
1356
+ "max_tool_rounds_exceeded";
1357
+
1358
+ throw error;
1359
+ }
1360
+
1361
+ emit({
1362
+ type: "stage:start",
1363
+ stage: "tool_round",
1364
+ detail:
1365
+ `Tool round ${toolRound + 1}/${maxToolRounds}`,
1366
+ data: {
1367
+ round: toolRound + 1,
1368
+ maxToolRounds,
1369
+ count:
1370
+ responseToolCalls.length,
1371
+ },
1372
+ });
1373
+
1374
+ let roundResults;
1375
+
1376
+ try {
1377
+ roundResults =
1378
+ await executeResolvedToolCalls({
1379
+ toolCalls:
1380
+ responseToolCalls,
1381
+
1382
+ registry:
1383
+ tools,
1384
+
1385
+ projectRoot:
1386
+ project.root,
1387
+
1388
+ approvedTools,
1389
+
1390
+ allowedCapabilities: [
1391
+ "filesystem.read",
1392
+ "filesystem.search",
1393
+ "filesystem.write",
1394
+ "process.execute",
1395
+ ],
1396
+
1397
+ requestApproval,
1398
+
1399
+ toolCallHistory,
1400
+ toolBudget,
1401
+
1402
+ emit,
1403
+ });
1404
+ } catch (error) {
1405
+ const recoverableToolErrors =
1406
+ new Set([
1407
+ "invalid_tool_arguments",
1408
+ "invalid_tool_call",
1409
+ "invalid_tool_name",
1410
+ "invalid_tool_calls",
1411
+ "tool_not_found",
1412
+ ]);
1413
+
1414
+ const recoverable =
1415
+ recoverableToolErrors.has(
1416
+ error?.code
1417
+ );
1418
+
1419
+ if (!recoverable) {
1420
+ throw error;
1421
+ }
1422
+ toolProtocolErrors += 1;
1423
+
1424
+ if (
1425
+ toolProtocolErrors >
1426
+ maxToolProtocolErrors
1427
+ ) {
1428
+ const protocolError =
1429
+ new Error(
1430
+ `Maximum tool protocol errors exceeded (${maxToolProtocolErrors}).`
1431
+ );
1432
+
1433
+ protocolError.code =
1434
+ "max_tool_protocol_errors_exceeded";
1435
+
1436
+ protocolError.cause =
1437
+ error;
1438
+
1439
+ throw protocolError;
1440
+ }
1441
+
1442
+ /*
1443
+ * Do not execute malformed calls.
1444
+ * Return a protocol error to the model so it can
1445
+ * correct the arguments on the next round.
1446
+ */
1447
+ roundResults =
1448
+ responseToolCalls.map(
1449
+ (toolCall) =>
1450
+ createToolCallFailure({
1451
+ toolCall,
1452
+ error,
1453
+ })
1454
+ );
1455
+
1456
+ emit({
1457
+ type: "stage:error",
1458
+ stage: "tool",
1459
+ detail:
1460
+ `${error.message} Retrying with model correction.`,
1461
+ data: {
1462
+ code:
1463
+ error.code,
1464
+
1465
+ protocolError:
1466
+ toolProtocolErrors,
1467
+
1468
+ maxToolProtocolErrors,
1469
+ },
1470
+ });
1471
+ }
1472
+
1473
+ for (const toolResult of roundResults) {
1474
+ if (
1475
+ toolResult.result?.success &&
1476
+ (
1477
+ toolResult.name === "edit_file" ||
1478
+ toolResult.name === "write_file"
1479
+ )
1480
+ ) {
1481
+ verification.writeSinceLastVerification =
1482
+ true;
1483
+ }
1484
+
1485
+ if (
1486
+ toolResult.name === "execute_command" &&
1487
+ verification.required &&
1488
+ verification.writeSinceLastVerification
1489
+ ) {
1490
+ verification.attempted = true;
1491
+
1492
+ verification.succeeded =
1493
+ toolResult.result?.success === true;
1494
+
1495
+ verification.writeSinceLastVerification =
1496
+ !verification.succeeded;
1497
+ }
1498
+ }
1499
+
1500
+ toolResults.push(
1501
+ ...roundResults
1502
+ );
1503
+
1504
+ /*
1505
+ * Preserve the assistant turn that requested
1506
+ * the tools.
1507
+ */
1508
+ if (response.message) {
1509
+ messages.push(
1510
+ response.message
1511
+ );
1512
+ }
1513
+
1514
+ const toolMessages =
1515
+ createToolResultMessages(
1516
+ roundResults
1517
+ );
1518
+
1519
+ messages.push(
1520
+ ...toolMessages
1521
+ );
1522
+
1523
+ toolRound += 1;
1524
+
1525
+ emit({
1526
+ type: "stage:success",
1527
+ stage: "tool_round",
1528
+ detail:
1529
+ `Tool round ${toolRound} completed`,
1530
+ data: {
1531
+ round: toolRound,
1532
+ maxToolRounds,
1533
+ count:
1534
+ roundResults.length,
1535
+ },
1536
+ });
1537
+ }
1538
+
1539
+ const usage =
1540
+ normalizeUsage(response?.usage);
1541
+
1542
+ emit({
1543
+ type: "stage:success",
1544
+ stage: "generate",
1545
+ detail: `${route?.model ?? "Model"
1546
+ } completed`,
1547
+ data: {
1548
+ provider:
1549
+ route?.provider ?? null,
1550
+
1551
+ modelRole:
1552
+ route?.modelRole ?? null,
1553
+
1554
+ model:
1555
+ response?.model ??
1556
+ route?.model ??
1557
+ null,
1558
+
1559
+ attempt: currentAttempt,
1560
+ maxAttempts,
1561
+
1562
+ finishReason:
1563
+ response?.finishReason ??
1564
+ null,
1565
+
1566
+ toolRounds: toolRound,
1567
+
1568
+ usage,
1569
+ },
1570
+ });
1571
+
1572
+ emit({
1573
+ type: "stage:success",
1574
+ stage: "complete",
1575
+ detail: "Execution complete",
1576
+ data: {
1577
+ attempts: currentAttempt,
1578
+ retrievalCount,
1579
+ retrievedFiles,
1580
+ retrievedContext,
1581
+
1582
+ provider:
1583
+ route?.provider ?? null,
1584
+
1585
+ modelRole:
1586
+ route?.modelRole ?? null,
1587
+
1588
+ model:
1589
+ response?.model ??
1590
+ route?.model ??
1591
+ null,
1592
+
1593
+ toolRounds:
1594
+ toolRound,
1595
+
1596
+ usage,
1597
+ },
1598
+ });
1599
+
1600
+ return {
1601
+ success: true,
1602
+ intent,
1603
+ plan,
1604
+ route,
1605
+ thinking,
1606
+ context,
1607
+ response,
1608
+ toolResults,
1609
+ telemetry: {
1610
+ attempts: currentAttempt,
1611
+ toolRounds: toolRound,
1612
+ retrievalCount,
1613
+ retrievedFiles,
1614
+ retrievedContext,
1615
+ usage,
1616
+ verification: {
1617
+ required:
1618
+ verification.required,
1619
+
1620
+ attempted:
1621
+ verification.attempted,
1622
+
1623
+ succeeded:
1624
+ verification.succeeded,
1625
+ },
1626
+ },
1627
+ };
1628
+ } catch (error) {
1629
+ emit({
1630
+ type: "stage:error",
1631
+ stage: "generate",
1632
+ detail:
1633
+ error?.message ??
1634
+ "Model request failed",
1635
+ data: {
1636
+ provider:
1637
+ route?.provider ?? null,
1638
+ model:
1639
+ route?.model ?? null,
1640
+ attempt: currentAttempt,
1641
+ maxAttempts,
1642
+ },
1643
+ });
1644
+
1645
+ attempt += 1;
1646
+
1647
+ emit({
1648
+ type: "stage:start",
1649
+ stage: "fallback",
1650
+ detail:
1651
+ "Checking fallback route",
1652
+ });
1653
+
1654
+ const fallback =
1655
+ getFallbackRoute({
1656
+ error,
1657
+ route,
1658
+ providers,
1659
+ models: config.models,
1660
+ options: {
1661
+ maxAttempts,
1662
+ },
1663
+ });
1664
+
1665
+ if (
1666
+ !fallback ||
1667
+ attempt >= maxAttempts
1668
+ ) {
1669
+ emit({
1670
+ type: "stage:error",
1671
+ stage: "fallback",
1672
+ detail: fallback
1673
+ ? "Maximum attempts reached"
1674
+ : "No fallback available",
1675
+ data: {
1676
+ attempts: attempt,
1677
+ maxAttempts,
1678
+ },
1679
+ });
1680
+
1681
+ emit({
1682
+ type: "stage:error",
1683
+ stage: "complete",
1684
+ detail:
1685
+ error?.message ??
1686
+ "Execution failed",
1687
+ data: {
1688
+ attempts: attempt,
1689
+ retrievalCount,
1690
+ retrievedFiles,
1691
+ },
1692
+ });
1693
+
1694
+ throw error;
1695
+ }
1696
+
1697
+ route = fallback;
1698
+
1699
+ emit({
1700
+ type: "stage:success",
1701
+ stage: "fallback",
1702
+ detail:
1703
+ [
1704
+ route?.provider,
1705
+ route?.modelRole,
1706
+ route?.model,
1707
+ ]
1708
+ .filter(Boolean)
1709
+ .join(" · ") ||
1710
+ "Fallback selected",
1711
+ data: {
1712
+ provider:
1713
+ route?.provider ?? null,
1714
+
1715
+ modelRole:
1716
+ route?.modelRole ?? null,
1717
+
1718
+ model:
1719
+ route?.model ?? null,
1720
+
1721
+ nextAttempt:
1722
+ attempt + 1,
1723
+
1724
+ maxAttempts,
1725
+ },
1726
+ });
1727
+ }
1728
+ }
1729
+ }