neuro-cli 4.1.0

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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,109 @@
1
+ export type Complexity = 'simple' | 'moderate' | 'complex';
2
+ export type EffortLevel = 'low' | 'medium' | 'high' | 'ultrathink';
3
+ export type TaskCategory = 'code' | 'reasoning' | 'creative' | 'analysis' | 'conversation' | 'debugging' | 'review' | 'refactoring';
4
+ export interface RoutingDecision {
5
+ model: string;
6
+ complexity: Complexity;
7
+ category: TaskCategory;
8
+ effort: EffortLevel;
9
+ estimatedTokens: number;
10
+ reasoning: string;
11
+ alternatives: string[];
12
+ }
13
+ export interface RouterConfig {
14
+ defaultModel: string;
15
+ simpleModel: string;
16
+ moderateModel: string;
17
+ complexModel: string;
18
+ effortModels: Record<EffortLevel, string>;
19
+ categoryOverrides: Partial<Record<TaskCategory, string>>;
20
+ maxTokenBudget: number;
21
+ }
22
+ export interface ModelInfo {
23
+ name: string;
24
+ contextWindow: number;
25
+ maxOutput: number;
26
+ }
27
+ export declare class ModelRouter {
28
+ private config;
29
+ private availableModels;
30
+ private currentEffort;
31
+ private forcedModel;
32
+ constructor(config: RouterConfig, availableModels: Record<string, ModelInfo>);
33
+ /**
34
+ * Route a prompt to the best available model.
35
+ */
36
+ route(prompt: string, effort?: EffortLevel): RoutingDecision;
37
+ /**
38
+ * Set the default effort level for subsequent routing calls.
39
+ */
40
+ setEffort(level: EffortLevel): void;
41
+ /**
42
+ * Get the current effort level.
43
+ */
44
+ getEffort(): EffortLevel;
45
+ /**
46
+ * Force all routing decisions to use a specific model.
47
+ */
48
+ overrideModel(modelId: string): void;
49
+ /**
50
+ * Remove any forced model override.
51
+ */
52
+ clearOverride(): void;
53
+ /**
54
+ * Classify the task category of a prompt.
55
+ */
56
+ getCategory(prompt: string): TaskCategory;
57
+ /**
58
+ * Classify the complexity of a prompt.
59
+ */
60
+ getComplexity(prompt: string): Complexity;
61
+ /**
62
+ * Estimate the number of tokens a prompt will consume.
63
+ * Uses a character-based heuristic (~4 chars per token for English,
64
+ * with adjustments for code-heavy content).
65
+ */
66
+ estimateTokens(prompt: string): number;
67
+ /**
68
+ * Print a human-readable routing decision to stdout.
69
+ */
70
+ printDecision(decision: RoutingDecision): void;
71
+ private analyzePrompt;
72
+ /**
73
+ * Score each category based on how many pattern groups match.
74
+ * Each group in CATEGORY_PATTERNS is worth 1 point when at least one
75
+ * pattern in the group matches.
76
+ */
77
+ private scoreCategories;
78
+ /**
79
+ * Determine the winning category and record indicators.
80
+ */
81
+ private pickTopCategory;
82
+ /**
83
+ * Score complexity using a weighted combination of:
84
+ * 1. Pattern match signals
85
+ * 2. Prompt length
86
+ * 3. Multi-step indicators
87
+ */
88
+ private scoreComplexity;
89
+ /**
90
+ * Count the approximate number of distinct steps or sub-tasks in a prompt.
91
+ */
92
+ private countSteps;
93
+ /**
94
+ * Extract a short snippet from the prompt that matched a pattern,
95
+ * for use as a human-readable indicator.
96
+ */
97
+ private truncateMatch;
98
+ private selectModel;
99
+ /**
100
+ * Return alternative models that could handle the same task but were not
101
+ * selected as the primary choice.
102
+ */
103
+ private getAlternatives;
104
+ private buildReasoning;
105
+ private validateConfig;
106
+ }
107
+ export declare const DEFAULT_ROUTER_CONFIG: RouterConfig;
108
+ export declare const DEFAULT_AVAILABLE_MODELS: Record<string, ModelInfo>;
109
+ //# sourceMappingURL=model-router.d.ts.map
@@ -0,0 +1,581 @@
1
+ // ============================================================
2
+ // NeuroCLI - Model Router
3
+ // Automatically selects the best model based on task complexity,
4
+ // category, and effort level using local heuristic analysis.
5
+ // ============================================================
6
+ // ---------------------------------------------------------------------------
7
+ // Keyword / pattern tables
8
+ // ---------------------------------------------------------------------------
9
+ const CATEGORY_PATTERNS = {
10
+ code: [
11
+ /\b(write|create|implement|build|develop|code|program|function|class|method|module|api|endpoint|component|hook|middleware|service|library|package|script|utility|helper|factory|adapter|decorator|singleton|interface|type|enum|struct|trait|impl|abstract)\b/i,
12
+ /\b(generat|scaffold|bootstrap|setup|init|boilerplate|snippet|algorithm|data\s*structure|parse|serialize|deserialize|encode|decode|compile|transpil|bundl)\b/i,
13
+ /\b(react|vue|angular|svelte|next|nuxt|express|fastify|nestjs|django|flask|spring|rails|laravel)\b/i,
14
+ /\b(sql|graphql|rest|grpc|websocket|rpc|crud|orm|migration|schema|query|mutation|subscription)\b/i,
15
+ /\b(typescript|javascript|python|rust|go|java|c\+\+|ruby|swift|kotlin|scala|php|perl|shell|bash)\b/i,
16
+ ],
17
+ reasoning: [
18
+ /\b(analy[zs]|evaluat|reason|deduc|infer|logic|propositional|syllogism|premise|conclusion|argument|proof|theorem|lemma)\b/i,
19
+ /\b(why\s+does|how\s+does|what\s+causes|explain\s+why|explain\s+how|causal|correlat|implication|contradiction|paradox)\b/i,
20
+ /\b(compare|contrast|weigh|trade[\s-]?off|pros?\s+and\s+cons?|advantage|disadvantage|merit|drawback|criterion|criteria)\b/i,
21
+ /\b(decide|decision|choose|select\s+between|opt\s+for|rational|optimal|strategy|heuristic|judg[ei]|assess)\b/i,
22
+ /\b(philosoph|ethical|moral|epistem|ontolog|metaphys|dialectic|hypothesis|hypothes[ie]s)\b/i,
23
+ ],
24
+ creative: [
25
+ /\b(write|creat|compos|draft|story|poem|poetry|novel|fiction|narrative|tale|fable|myth|lore)\b/i,
26
+ /\b(song|lyric|rap|ballad|haiku|limerick|sonnet|verse|stanza|rhyme)\b/i,
27
+ /\b(creative|imagin|inspir|brainstorm|ideat|invent|original|unique|innovative|artistic|expressive)\b/i,
28
+ /\b(screenplay|dialog|monologu|script|scene|plot|character|protagonist|antagonist|setting|world[\s-]?build)\b/i,
29
+ /\b(marketing|copy|slogan|tagline|headline|pitch|campaign|brand|motto|manifesto)\b/i,
30
+ ],
31
+ analysis: [
32
+ /\b(analy[zs]|assess|evaluat|measur|metric|statistic|quantif|quantif|data|dataset|insight|trend|pattern)\b/i,
33
+ /\b(report|dashboard|chart|graph|visuali[zs]|plot|histogram|distribution|correlation|regression|outlier|anomal)\b/i,
34
+ /\b(kpi|roi|conversion|retention|churn|funnel|cohort|segment|benchmark|baseline|variance|deviation)\b/i,
35
+ /\b(survey|questionnaire|feedback|sentiment|opinion|poll|research|study|finding|conclusion|recommendation)\b/i,
36
+ /\b(performance|latency|throughput|bottleneck|profiling|benchmark|load\s*test|stress\s*test|capacity)\b/i,
37
+ ],
38
+ conversation: [
39
+ /^(hi|hello|hey|greetings|good\s+(morning|afternoon|evening)|howdy|sup|yo)\b/i,
40
+ /\b(what\s+is|what\s+are|who\s+is|who\s+are|when\s+did|where\s+is|tell\s+me\s+about|explain\s+briefly)\b/i,
41
+ /\b(how\s+do\s+i|how\s+can\s+i|help\s+me|can\s+you|could\s+you|would\s+you|please\s+(help|explain|tell|show))\b/i,
42
+ /\b(thanks?|thank\s+you|appreciate|got\s*it|understood|makes?\s+sense|right|ok|okay|sure|cool|great|nice|awesome)\b/i,
43
+ /\b(chat|talk|discuss|convers|question|answer|curious|wondering)\b/i,
44
+ ],
45
+ debugging: [
46
+ /\b(debug|bug|error|issue|problem|crash|exception|traceback|stack\s*trace|fault|failure|faulty)\b/i,
47
+ /\b(fix|repair|patch|resolve|troubleshoot|diagnos|investigat|root\s*cause|bisect|pinpoint|isolate)\b/i,
48
+ /\b(not\s+work|broken|wrong|unexpected|incorrect|misbehav|regression|side\s*effect|glitch|hang|freeze)\b/i,
49
+ /\b(log|stderr|stdout|assert|throw|catch|try|raise|panic|fatal|segfault|overflow|underflow|null\s*pointer|nil)\b/i,
50
+ /\b(reproduce|repro|minimal\s+(repro|example|case)|step\s+to\s+reproduce|stack\s*overflow|memory\s*leak)\b/i,
51
+ ],
52
+ review: [
53
+ /\b(review|audit|inspect|examine|check|verify|validat|ensure|confirm|certif|compliance)\b/i,
54
+ /\b(code\s*review|pull\s*request|pr|merge\s*request|diff|change|change\s*set|patch|commit)\b/i,
55
+ /\b(quality|standard|guideline|convention|style|lint|best\s*practice|clean\s*code|solid|dry|kiss)\b/i,
56
+ /\b(secur|vulnerab|exploit|cve|owasp|injection|xss|csrf|sanitize|escape|encrypt|auth|perm)\b/i,
57
+ /\b(performance\s*review|architectur(e|al)\s*review|design\s*review|security\s*review|readability)\b/i,
58
+ ],
59
+ refactoring: [
60
+ /\b(refactor|restructur|reorganiz|rearrang|rearchitect|rewrite|overhaul|moderniz|migrate|port)\b/i,
61
+ /\b(clean\s*up|simplif|duplicat|dead\s*code|unused|redundant|consolidat|merge|split|extract|inline)\b/i,
62
+ /\b(design\s*pattern|solid|dry|kiss|yagni|coupling|cohesion|separation\s*of\s*concerns|single\s*responsib)\b/i,
63
+ /\b(rename|rename|move|promote|demote|encapsulat|abstract|generaliz|speciali[zs]|parametri[zs])\b/i,
64
+ /\b(improv(e|ing)|enhanc(e|ing)|optimi[zs]|speed\s*up|reduc(e|ing)|streamlin|declutter|tidy)\b/i,
65
+ ],
66
+ };
67
+ const COMPLEXITY_SIGNALS = {
68
+ simple: {
69
+ patterns: [
70
+ /^(what\s+is|what\s+are|who\s+is|define|explain\s+briefly|list|show\s+me|tell\s+me|give\s+me\s+a)\b/i,
71
+ /\b(simple|basic|quick|easy|short|small|single|one|trivial|minor|tiny|brief)\b/i,
72
+ /\b(hello|hi|thanks|yes|no|ok|sure|done|correct|right|please)\b/i,
73
+ ],
74
+ lengthThreshold: 80,
75
+ maxSteps: 1,
76
+ },
77
+ moderate: {
78
+ patterns: [
79
+ /\b(add|modify|update|change|extend|enhance|improve|adjust|configure|customize)\b/i,
80
+ /\b(fix|debug|solve|resolve|handle|implement\s+a|write\s+a|create\s+a)\b/i,
81
+ /\b(test|spec|assert|validate|verify)\b/i,
82
+ /\b(review|check|audit|inspect|compare|evaluate)\b/i,
83
+ /\b(refactor|clean|simplify|optimize|restructure)\b/i,
84
+ ],
85
+ lengthThreshold: 300,
86
+ maxSteps: 3,
87
+ },
88
+ complex: {
89
+ patterns: [
90
+ /\b(implement\s+(a\s+)?(full|complete|entire|comprehensive|end[\s-]to[\s-]end|production|scalable|robust))\b/i,
91
+ /\b(build\s+(a\s+)?(system|application|platform|framework|service|architecture|infrastructure|pipeline))\b/i,
92
+ /\b(design\s+(and\s+implement|the\s+architecture|a\s+system|from\s+scratch))\b/i,
93
+ /\b(migrate|migration|port\s+from|rewrite\s+(from|the|entire)|overhaul|modernize)\b/i,
94
+ /\b(multi[\s-]*(step|phase|stage|part|agent|service|module|component)|micro[\s-]*service|distributed)\b/i,
95
+ /\b(orchestrat|coordinat|integrat|synchron[iz]|pipeline|workflow|state\s*machine)\b/i,
96
+ /\b(complex|comprehensive|intricate|sophisticated|elaborate|advanced|enterprise|large[\s-]*scale)\b/i,
97
+ /\b(security\s+(audit|review|hardening)|performance\s+(optim|tuning|profiling)|scalab(ility|le))\b/i,
98
+ ],
99
+ lengthThreshold: 500,
100
+ maxSteps: 6,
101
+ },
102
+ };
103
+ // ---------------------------------------------------------------------------
104
+ // Step-counting heuristics
105
+ // ---------------------------------------------------------------------------
106
+ const STEP_INDICATORS = [
107
+ /\band\b/gi,
108
+ /\bthen\b/gi,
109
+ /\bafter\s+(that|which|complet|finish|done)\b/gi,
110
+ /\bfinally\b/gi,
111
+ /\bnext\b/gi,
112
+ /\bfollowed\s+by\b/gi,
113
+ /\bonce\s+(that|it|complete|done|finish)\b/gi,
114
+ /\bsubsequently\b/gi,
115
+ /\badditionally\b/gi,
116
+ /\bfurthermore\b/gi,
117
+ /\bmoreover\b/gi,
118
+ ];
119
+ // ---------------------------------------------------------------------------
120
+ // ModelRouter
121
+ // ---------------------------------------------------------------------------
122
+ export class ModelRouter {
123
+ config;
124
+ availableModels;
125
+ currentEffort = 'medium';
126
+ forcedModel = null;
127
+ // -----------------------------------------------------------------------
128
+ // Construction
129
+ // -----------------------------------------------------------------------
130
+ constructor(config, availableModels) {
131
+ this.config = config;
132
+ this.availableModels = availableModels;
133
+ this.validateConfig();
134
+ }
135
+ // -----------------------------------------------------------------------
136
+ // Public API
137
+ // -----------------------------------------------------------------------
138
+ /**
139
+ * Route a prompt to the best available model.
140
+ */
141
+ route(prompt, effort) {
142
+ const effectiveEffort = effort ?? this.currentEffort;
143
+ const analysis = this.analyzePrompt(prompt);
144
+ const estimatedTokens = this.estimateTokens(prompt);
145
+ const model = this.selectModel(analysis.complexity, analysis.category, effectiveEffort);
146
+ const alternatives = this.getAlternatives(analysis.complexity, analysis.category);
147
+ const reasoning = this.buildReasoning(analysis, effectiveEffort, model);
148
+ return {
149
+ model,
150
+ complexity: analysis.complexity,
151
+ category: analysis.category,
152
+ effort: effectiveEffort,
153
+ estimatedTokens,
154
+ reasoning,
155
+ alternatives,
156
+ };
157
+ }
158
+ /**
159
+ * Set the default effort level for subsequent routing calls.
160
+ */
161
+ setEffort(level) {
162
+ this.currentEffort = level;
163
+ }
164
+ /**
165
+ * Get the current effort level.
166
+ */
167
+ getEffort() {
168
+ return this.currentEffort;
169
+ }
170
+ /**
171
+ * Force all routing decisions to use a specific model.
172
+ */
173
+ overrideModel(modelId) {
174
+ if (!this.availableModels[modelId]) {
175
+ throw new Error(`Cannot override to unknown model "${modelId}". Available: ${Object.keys(this.availableModels).join(', ')}`);
176
+ }
177
+ this.forcedModel = modelId;
178
+ }
179
+ /**
180
+ * Remove any forced model override.
181
+ */
182
+ clearOverride() {
183
+ this.forcedModel = null;
184
+ }
185
+ /**
186
+ * Classify the task category of a prompt.
187
+ */
188
+ getCategory(prompt) {
189
+ return this.analyzePrompt(prompt).category;
190
+ }
191
+ /**
192
+ * Classify the complexity of a prompt.
193
+ */
194
+ getComplexity(prompt) {
195
+ return this.analyzePrompt(prompt).complexity;
196
+ }
197
+ /**
198
+ * Estimate the number of tokens a prompt will consume.
199
+ * Uses a character-based heuristic (~4 chars per token for English,
200
+ * with adjustments for code-heavy content).
201
+ */
202
+ estimateTokens(prompt) {
203
+ if (prompt.length === 0)
204
+ return 0;
205
+ // Base estimate: ~4 characters per token for typical English text
206
+ let charPerToken = 4;
207
+ // Code-heavy prompts tend to have higher token density
208
+ const codeIndicators = [
209
+ /```[\s\S]*?```/, // fenced code blocks
210
+ /\bfunction\b/, // function keyword
211
+ /\bconst\b|\blet\b|\bvar\b/, // variable declarations
212
+ /[{}()\[\];]/, // braces and brackets
213
+ /=>/, // arrow functions
214
+ /import\s+.+\s+from/, // imports
215
+ ];
216
+ const codeSignalCount = codeIndicators.filter(p => p.test(prompt)).length;
217
+ if (codeSignalCount >= 3) {
218
+ charPerToken = 3.2; // code is denser
219
+ }
220
+ else if (codeSignalCount >= 1) {
221
+ charPerToken = 3.6;
222
+ }
223
+ // Whitespace-heavy prompts have lower density
224
+ const whitespaceRatio = (prompt.match(/\s/g) ?? []).length / prompt.length;
225
+ if (whitespaceRatio > 0.4) {
226
+ charPerToken = 4.5;
227
+ }
228
+ const baseTokens = Math.ceil(prompt.length / charPerToken);
229
+ // Add overhead for special tokens (system, role markers, etc.)
230
+ const overhead = 8;
231
+ return baseTokens + overhead;
232
+ }
233
+ /**
234
+ * Print a human-readable routing decision to stdout.
235
+ */
236
+ printDecision(decision) {
237
+ const border = '-'.repeat(60);
238
+ const lines = [
239
+ border,
240
+ ' MODEL ROUTING DECISION',
241
+ border,
242
+ ` Model : ${decision.model}`,
243
+ ` Complexity : ${decision.complexity}`,
244
+ ` Category : ${decision.category}`,
245
+ ` Effort : ${decision.effort}`,
246
+ ` Est. Tokens : ${decision.estimatedTokens.toLocaleString()}`,
247
+ ` Reasoning : ${decision.reasoning}`,
248
+ ];
249
+ if (decision.alternatives.length > 0) {
250
+ lines.push(` Alternatives: ${decision.alternatives.join(', ')}`);
251
+ }
252
+ lines.push(border);
253
+ for (const line of lines) {
254
+ console.log(line);
255
+ }
256
+ }
257
+ // -----------------------------------------------------------------------
258
+ // Private: prompt analysis
259
+ // -----------------------------------------------------------------------
260
+ analyzePrompt(prompt) {
261
+ const indicators = [];
262
+ // --- Category detection (scored) ---
263
+ const categoryScores = this.scoreCategories(prompt);
264
+ const category = this.pickTopCategory(categoryScores, indicators);
265
+ // --- Complexity detection (scored) ---
266
+ const complexity = this.scoreComplexity(prompt, indicators);
267
+ return { complexity, category, indicators };
268
+ }
269
+ /**
270
+ * Score each category based on how many pattern groups match.
271
+ * Each group in CATEGORY_PATTERNS is worth 1 point when at least one
272
+ * pattern in the group matches.
273
+ */
274
+ scoreCategories(prompt) {
275
+ const scores = new Map();
276
+ for (const [cat, patternGroups] of Object.entries(CATEGORY_PATTERNS)) {
277
+ let score = 0;
278
+ for (const pattern of patternGroups) {
279
+ if (pattern.test(prompt)) {
280
+ score += 1;
281
+ }
282
+ }
283
+ scores.set(cat, score);
284
+ }
285
+ return scores;
286
+ }
287
+ /**
288
+ * Determine the winning category and record indicators.
289
+ */
290
+ pickTopCategory(scores, indicators) {
291
+ let bestCategory = 'conversation';
292
+ let bestScore = 0;
293
+ for (const [cat, score] of scores) {
294
+ if (score > bestScore) {
295
+ bestScore = score;
296
+ bestCategory = cat;
297
+ }
298
+ }
299
+ // If nothing matched at all, default to conversation
300
+ if (bestScore === 0) {
301
+ indicators.push('no strong category signals -- defaulting to conversation');
302
+ return 'conversation';
303
+ }
304
+ indicators.push(`category "${bestCategory}" matched with score ${bestScore}`);
305
+ return bestCategory;
306
+ }
307
+ /**
308
+ * Score complexity using a weighted combination of:
309
+ * 1. Pattern match signals
310
+ * 2. Prompt length
311
+ * 3. Multi-step indicators
312
+ */
313
+ scoreComplexity(prompt, indicators) {
314
+ let simpleScore = 0;
315
+ let moderateScore = 0;
316
+ let complexScore = 0;
317
+ // --- Pattern-based scoring ---
318
+ for (const pattern of COMPLEXITY_SIGNALS.simple.patterns) {
319
+ if (pattern.test(prompt)) {
320
+ simpleScore += 2;
321
+ indicators.push(`simple signal: "${this.truncateMatch(prompt, pattern)}"`);
322
+ }
323
+ }
324
+ for (const pattern of COMPLEXITY_SIGNALS.moderate.patterns) {
325
+ if (pattern.test(prompt)) {
326
+ moderateScore += 2;
327
+ indicators.push(`moderate signal: "${this.truncateMatch(prompt, pattern)}"`);
328
+ }
329
+ }
330
+ for (const pattern of COMPLEXITY_SIGNALS.complex.patterns) {
331
+ if (pattern.test(prompt)) {
332
+ complexScore += 3;
333
+ indicators.push(`complex signal: "${this.truncateMatch(prompt, pattern)}"`);
334
+ }
335
+ }
336
+ // --- Length-based scoring ---
337
+ const length = prompt.length;
338
+ if (length <= COMPLEXITY_SIGNALS.simple.lengthThreshold) {
339
+ simpleScore += 1;
340
+ indicators.push(`short prompt (${length} chars)`);
341
+ }
342
+ else if (length <= COMPLEXITY_SIGNALS.moderate.lengthThreshold) {
343
+ moderateScore += 1;
344
+ indicators.push(`medium prompt (${length} chars)`);
345
+ }
346
+ else if (length <= COMPLEXITY_SIGNALS.complex.lengthThreshold) {
347
+ moderateScore += 1;
348
+ complexScore += 1;
349
+ indicators.push(`long prompt (${length} chars)`);
350
+ }
351
+ else {
352
+ complexScore += 2;
353
+ indicators.push(`very long prompt (${length} chars)`);
354
+ }
355
+ // --- Step-count scoring ---
356
+ const stepCount = this.countSteps(prompt);
357
+ if (stepCount <= COMPLEXITY_SIGNALS.simple.maxSteps) {
358
+ simpleScore += 1;
359
+ }
360
+ else if (stepCount <= COMPLEXITY_SIGNALS.moderate.maxSteps) {
361
+ moderateScore += 1;
362
+ indicators.push(`multi-step request (${stepCount} steps detected)`);
363
+ }
364
+ else {
365
+ complexScore += 2;
366
+ indicators.push(`many-step request (${stepCount} steps detected)`);
367
+ }
368
+ // --- Final decision ---
369
+ const maxScore = Math.max(simpleScore, moderateScore, complexScore);
370
+ // Ties go to the higher complexity (safer default)
371
+ if (complexScore === maxScore)
372
+ return 'complex';
373
+ if (moderateScore === maxScore)
374
+ return 'moderate';
375
+ return 'simple';
376
+ }
377
+ /**
378
+ * Count the approximate number of distinct steps or sub-tasks in a prompt.
379
+ */
380
+ countSteps(prompt) {
381
+ let steps = 1; // at least one step
382
+ for (const pattern of STEP_INDICATORS) {
383
+ const matches = prompt.match(pattern);
384
+ if (matches) {
385
+ steps += matches.length;
386
+ }
387
+ }
388
+ return steps;
389
+ }
390
+ /**
391
+ * Extract a short snippet from the prompt that matched a pattern,
392
+ * for use as a human-readable indicator.
393
+ */
394
+ truncateMatch(prompt, pattern) {
395
+ const match = prompt.match(pattern);
396
+ if (!match)
397
+ return '(no match)';
398
+ const text = match[0];
399
+ return text.length > 40 ? text.slice(0, 37) + '...' : text;
400
+ }
401
+ // -----------------------------------------------------------------------
402
+ // Private: model selection
403
+ // -----------------------------------------------------------------------
404
+ selectModel(complexity, category, effort) {
405
+ // 1. User override takes absolute precedence
406
+ if (this.forcedModel) {
407
+ return this.forcedModel;
408
+ }
409
+ // 2. Effort-level model mapping takes precedence when the effort is
410
+ // explicitly above "medium". This lets users escalate thinking
411
+ // without changing the prompt.
412
+ if (effort === 'ultrathink' || effort === 'high') {
413
+ const effortModel = this.config.effortModels[effort];
414
+ if (effortModel && this.availableModels[effortModel]) {
415
+ return effortModel;
416
+ }
417
+ }
418
+ // 3. Category-specific override
419
+ const categoryModel = this.config.categoryOverrides[category];
420
+ if (categoryModel && this.availableModels[categoryModel]) {
421
+ return categoryModel;
422
+ }
423
+ // 4. Effort-level mapping for low/medium
424
+ if (effort === 'low' || effort === 'medium') {
425
+ const effortModel = this.config.effortModels[effort];
426
+ if (effortModel && this.availableModels[effortModel]) {
427
+ return effortModel;
428
+ }
429
+ }
430
+ // 5. Complexity-based default
431
+ switch (complexity) {
432
+ case 'simple':
433
+ return this.config.simpleModel;
434
+ case 'moderate':
435
+ return this.config.moderateModel;
436
+ case 'complex':
437
+ return this.config.complexModel;
438
+ }
439
+ }
440
+ /**
441
+ * Return alternative models that could handle the same task but were not
442
+ * selected as the primary choice.
443
+ */
444
+ getAlternatives(complexity, category) {
445
+ const selected = this.selectModel(complexity, category, this.currentEffort);
446
+ const candidates = new Set();
447
+ // Add complexity-adjacent models
448
+ candidates.add(this.config.simpleModel);
449
+ candidates.add(this.config.moderateModel);
450
+ candidates.add(this.config.complexModel);
451
+ // Add effort models
452
+ for (const model of Object.values(this.config.effortModels)) {
453
+ candidates.add(model);
454
+ }
455
+ // Add category override if present
456
+ const categoryModel = this.config.categoryOverrides[category];
457
+ if (categoryModel) {
458
+ candidates.add(categoryModel);
459
+ }
460
+ // Remove the selected model and any models not in the available set
461
+ candidates.delete(selected);
462
+ for (const candidate of [...candidates]) {
463
+ if (!this.availableModels[candidate]) {
464
+ candidates.delete(candidate);
465
+ }
466
+ }
467
+ return [...candidates];
468
+ }
469
+ // -----------------------------------------------------------------------
470
+ // Private: reasoning string
471
+ // -----------------------------------------------------------------------
472
+ buildReasoning(analysis, effort, model) {
473
+ const parts = [];
474
+ if (this.forcedModel) {
475
+ parts.push(`User override forced model to "${this.forcedModel}".`);
476
+ return parts.join(' ');
477
+ }
478
+ parts.push(`Prompt classified as ${analysis.complexity} ${analysis.category} task.`);
479
+ switch (analysis.complexity) {
480
+ case 'simple':
481
+ parts.push('Routed to fast model for quick response.');
482
+ break;
483
+ case 'moderate':
484
+ parts.push('Routed to balanced model for capable response.');
485
+ break;
486
+ case 'complex':
487
+ parts.push('Routed to powerful model for thorough analysis.');
488
+ break;
489
+ }
490
+ if (effort === 'ultrathink') {
491
+ parts.push('Ultrathink mode: using highest-capability model for deep reasoning.');
492
+ }
493
+ else if (effort === 'high') {
494
+ parts.push('High effort: using advanced model for detailed output.');
495
+ }
496
+ else if (effort === 'low') {
497
+ parts.push('Low effort: using fast model for concise response.');
498
+ }
499
+ parts.push(`Selected "${model}".`);
500
+ return parts.join(' ');
501
+ }
502
+ // -----------------------------------------------------------------------
503
+ // Private: config validation
504
+ // -----------------------------------------------------------------------
505
+ validateConfig() {
506
+ const requiredModels = [
507
+ this.config.defaultModel,
508
+ this.config.simpleModel,
509
+ this.config.moderateModel,
510
+ this.config.complexModel,
511
+ ];
512
+ for (const modelId of requiredModels) {
513
+ if (!this.availableModels[modelId]) {
514
+ throw new Error(`RouterConfig references model "${modelId}" which is not in the available models list. ` +
515
+ `Available: ${Object.keys(this.availableModels).join(', ')}`);
516
+ }
517
+ }
518
+ for (const [level, modelId] of Object.entries(this.config.effortModels)) {
519
+ if (!this.availableModels[modelId]) {
520
+ throw new Error(`RouterConfig effortModels.${level} references model "${modelId}" which is not in the available models list. ` +
521
+ `Available: ${Object.keys(this.availableModels).join(', ')}`);
522
+ }
523
+ }
524
+ for (const [category, modelId] of Object.entries(this.config.categoryOverrides)) {
525
+ if (modelId && !this.availableModels[modelId]) {
526
+ throw new Error(`RouterConfig categoryOverrides.${category} references model "${modelId}" which is not in the available models list. ` +
527
+ `Available: ${Object.keys(this.availableModels).join(', ')}`);
528
+ }
529
+ }
530
+ if (this.config.maxTokenBudget <= 0) {
531
+ throw new Error('RouterConfig.maxTokenBudget must be a positive integer.');
532
+ }
533
+ }
534
+ }
535
+ // ---------------------------------------------------------------------------
536
+ // Default configuration factory
537
+ // ---------------------------------------------------------------------------
538
+ export const DEFAULT_ROUTER_CONFIG = {
539
+ defaultModel: 'anthropic/claude-sonnet-4',
540
+ simpleModel: 'google/gemini-2.0-flash-001',
541
+ moderateModel: 'anthropic/claude-sonnet-4',
542
+ complexModel: 'anthropic/claude-opus-4',
543
+ effortModels: {
544
+ low: 'google/gemini-2.0-flash-001',
545
+ medium: 'anthropic/claude-sonnet-4',
546
+ high: 'anthropic/claude-opus-4',
547
+ ultrathink: 'openai/o3',
548
+ },
549
+ categoryOverrides: {
550
+ code: 'anthropic/claude-sonnet-4',
551
+ reasoning: 'openai/o3',
552
+ creative: 'anthropic/claude-sonnet-4',
553
+ debugging: 'anthropic/claude-sonnet-4',
554
+ review: 'anthropic/claude-sonnet-4',
555
+ refactoring: 'anthropic/claude-sonnet-4',
556
+ },
557
+ maxTokenBudget: 200_000,
558
+ };
559
+ export const DEFAULT_AVAILABLE_MODELS = {
560
+ 'google/gemini-2.0-flash-001': {
561
+ name: 'Gemini 2.0 Flash',
562
+ contextWindow: 1_048_576,
563
+ maxOutput: 8_192,
564
+ },
565
+ 'anthropic/claude-sonnet-4': {
566
+ name: 'Claude Sonnet 4',
567
+ contextWindow: 200_000,
568
+ maxOutput: 16_384,
569
+ },
570
+ 'anthropic/claude-opus-4': {
571
+ name: 'Claude Opus 4',
572
+ contextWindow: 200_000,
573
+ maxOutput: 32_000,
574
+ },
575
+ 'openai/o3': {
576
+ name: 'OpenAI o3',
577
+ contextWindow: 200_000,
578
+ maxOutput: 100_000,
579
+ },
580
+ };
581
+ //# sourceMappingURL=model-router.js.map