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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended Thinking System for NeuroCLI
|
|
3
|
+
*
|
|
4
|
+
* Handles thinking/reasoning blocks in AI responses, providing configurable
|
|
5
|
+
* thinking modes, budget management, streaming support, and statistics tracking.
|
|
6
|
+
*/
|
|
7
|
+
export type ThinkingMode = 'none' | 'brief' | 'full' | 'ultrathink';
|
|
8
|
+
export interface ThinkingConfig {
|
|
9
|
+
mode: ThinkingMode;
|
|
10
|
+
maxThinkingTokens: number;
|
|
11
|
+
showThinking: boolean;
|
|
12
|
+
effortBudget: Record<ThinkingMode, number>;
|
|
13
|
+
}
|
|
14
|
+
export interface ThinkingBlock {
|
|
15
|
+
content: string;
|
|
16
|
+
tokenCount: number;
|
|
17
|
+
duration?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ThinkingResult {
|
|
20
|
+
thinkingBlocks: ThinkingBlock[];
|
|
21
|
+
totalThinkingTokens: number;
|
|
22
|
+
cleanedResponse: string;
|
|
23
|
+
hadThinking: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare class ExtendedThinking {
|
|
26
|
+
private config;
|
|
27
|
+
private totalThinkingTokens;
|
|
28
|
+
private thinkingHistory;
|
|
29
|
+
static readonly DEFAULT_CONFIG: ThinkingConfig;
|
|
30
|
+
constructor(config?: Partial<ThinkingConfig>);
|
|
31
|
+
/**
|
|
32
|
+
* Parse an AI response, extracting any <thinking> blocks and returning
|
|
33
|
+
* the cleaned response text together with extracted thinking data.
|
|
34
|
+
*/
|
|
35
|
+
parseResponse(response: string): ThinkingResult;
|
|
36
|
+
/** Switch to a different thinking mode and update the token budget. */
|
|
37
|
+
setMode(mode: ThinkingMode): void;
|
|
38
|
+
/** Return the current thinking mode. */
|
|
39
|
+
getMode(): ThinkingMode;
|
|
40
|
+
/** Toggle the display of thinking blocks. Returns the new state. */
|
|
41
|
+
toggleDisplay(): boolean;
|
|
42
|
+
/** Whether thinking blocks should be shown to the user. */
|
|
43
|
+
isDisplayEnabled(): boolean;
|
|
44
|
+
/** Return the token budget for the current mode. */
|
|
45
|
+
getEffortBudget(): number;
|
|
46
|
+
/**
|
|
47
|
+
* Return a system-prompt fragment that instructs the model to use
|
|
48
|
+
* <thinking> tags for its internal reasoning process, scoped to the
|
|
49
|
+
* current mode and token budget.
|
|
50
|
+
*/
|
|
51
|
+
getSystemPromptAddition(): string;
|
|
52
|
+
/** Return aggregate statistics across all parsed responses. */
|
|
53
|
+
getStats(): {
|
|
54
|
+
totalThinkingTokens: number;
|
|
55
|
+
totalBlocks: number;
|
|
56
|
+
avgBlockLength: number;
|
|
57
|
+
};
|
|
58
|
+
/** Reset accumulated statistics and history. */
|
|
59
|
+
resetStats(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Check whether a partial streaming buffer currently contains an open
|
|
62
|
+
* thinking tag that has not yet been closed. Useful for deciding whether
|
|
63
|
+
* incoming tokens belong to a thinking region.
|
|
64
|
+
*/
|
|
65
|
+
static isInThinkingBlock(buffer: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Attempt to extract any complete thinking blocks from a streaming buffer
|
|
68
|
+
* without mutating it. Returns completed blocks and leaves partial blocks
|
|
69
|
+
* untouched (they remain in the buffer for subsequent chunks).
|
|
70
|
+
*/
|
|
71
|
+
static extractStreamingBlocks(buffer: string): {
|
|
72
|
+
completedBlocks: ThinkingBlock[];
|
|
73
|
+
remaining: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Extract all <thinking>...</thinking> blocks from a complete response
|
|
77
|
+
* string, returning the parsed blocks and the cleaned response with the
|
|
78
|
+
* thinking tags removed.
|
|
79
|
+
*/
|
|
80
|
+
private extractThinkingBlocks;
|
|
81
|
+
/** Estimate the number of tokens in a piece of text. */
|
|
82
|
+
private estimateTokens;
|
|
83
|
+
/** Static version for use without an instance. */
|
|
84
|
+
private static estimateTokensStatic;
|
|
85
|
+
/**
|
|
86
|
+
* Serialize the display preference and mode so it can be persisted
|
|
87
|
+
* (e.g. to a config file or localStorage).
|
|
88
|
+
*/
|
|
89
|
+
serializePreferences(): {
|
|
90
|
+
mode: ThinkingMode;
|
|
91
|
+
showThinking: boolean;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Restore a previously serialized preference. Only mode and display
|
|
95
|
+
* toggle are restored; statistics remain untouched.
|
|
96
|
+
*/
|
|
97
|
+
restorePreferences(prefs: {
|
|
98
|
+
mode?: ThinkingMode;
|
|
99
|
+
showThinking?: boolean;
|
|
100
|
+
}): void;
|
|
101
|
+
}
|
|
102
|
+
export default ExtendedThinking;
|
|
103
|
+
//# sourceMappingURL=extended-thinking.d.ts.map
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended Thinking System for NeuroCLI
|
|
3
|
+
*
|
|
4
|
+
* Handles thinking/reasoning blocks in AI responses, providing configurable
|
|
5
|
+
* thinking modes, budget management, streaming support, and statistics tracking.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Constants
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const THINKING_OPEN_TAG = '<thinking>';
|
|
11
|
+
const THINKING_CLOSE_TAG = '</thinking>';
|
|
12
|
+
const OPEN_TAG_LEN = THINKING_OPEN_TAG.length;
|
|
13
|
+
const CLOSE_TAG_LEN = THINKING_CLOSE_TAG.length;
|
|
14
|
+
// Rough heuristic: 1 token ~ 4 characters for English text
|
|
15
|
+
const CHARS_PER_TOKEN = 4;
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// ExtendedThinking
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
export class ExtendedThinking {
|
|
20
|
+
config;
|
|
21
|
+
totalThinkingTokens;
|
|
22
|
+
thinkingHistory;
|
|
23
|
+
static DEFAULT_CONFIG = {
|
|
24
|
+
mode: 'none',
|
|
25
|
+
maxThinkingTokens: 0,
|
|
26
|
+
showThinking: false,
|
|
27
|
+
effortBudget: {
|
|
28
|
+
none: 0,
|
|
29
|
+
brief: 1000,
|
|
30
|
+
full: 4000,
|
|
31
|
+
ultrathink: 16000,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = {
|
|
36
|
+
...ExtendedThinking.DEFAULT_CONFIG,
|
|
37
|
+
...config,
|
|
38
|
+
effortBudget: {
|
|
39
|
+
...ExtendedThinking.DEFAULT_CONFIG.effortBudget,
|
|
40
|
+
...(config?.effortBudget ?? {}),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
// Sync maxThinkingTokens with the budget for the chosen mode
|
|
44
|
+
this.config.maxThinkingTokens = this.config.effortBudget[this.config.mode];
|
|
45
|
+
this.totalThinkingTokens = 0;
|
|
46
|
+
this.thinkingHistory = [];
|
|
47
|
+
}
|
|
48
|
+
// -------------------------------------------------------------------------
|
|
49
|
+
// Public API
|
|
50
|
+
// -------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* Parse an AI response, extracting any <thinking> blocks and returning
|
|
53
|
+
* the cleaned response text together with extracted thinking data.
|
|
54
|
+
*/
|
|
55
|
+
parseResponse(response) {
|
|
56
|
+
const { blocks, cleaned } = this.extractThinkingBlocks(response);
|
|
57
|
+
const blockTokenTotal = blocks.reduce((sum, block) => sum + block.tokenCount, 0);
|
|
58
|
+
// Accumulate history and stats
|
|
59
|
+
for (const block of blocks) {
|
|
60
|
+
this.thinkingHistory.push(block);
|
|
61
|
+
this.totalThinkingTokens += block.tokenCount;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
thinkingBlocks: blocks,
|
|
65
|
+
totalThinkingTokens: blockTokenTotal,
|
|
66
|
+
cleanedResponse: cleaned,
|
|
67
|
+
hadThinking: blocks.length > 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Switch to a different thinking mode and update the token budget. */
|
|
71
|
+
setMode(mode) {
|
|
72
|
+
this.config.mode = mode;
|
|
73
|
+
this.config.maxThinkingTokens = this.config.effortBudget[mode];
|
|
74
|
+
}
|
|
75
|
+
/** Return the current thinking mode. */
|
|
76
|
+
getMode() {
|
|
77
|
+
return this.config.mode;
|
|
78
|
+
}
|
|
79
|
+
/** Toggle the display of thinking blocks. Returns the new state. */
|
|
80
|
+
toggleDisplay() {
|
|
81
|
+
this.config.showThinking = !this.config.showThinking;
|
|
82
|
+
return this.config.showThinking;
|
|
83
|
+
}
|
|
84
|
+
/** Whether thinking blocks should be shown to the user. */
|
|
85
|
+
isDisplayEnabled() {
|
|
86
|
+
return this.config.showThinking;
|
|
87
|
+
}
|
|
88
|
+
/** Return the token budget for the current mode. */
|
|
89
|
+
getEffortBudget() {
|
|
90
|
+
return this.config.effortBudget[this.config.mode];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Return a system-prompt fragment that instructs the model to use
|
|
94
|
+
* <thinking> tags for its internal reasoning process, scoped to the
|
|
95
|
+
* current mode and token budget.
|
|
96
|
+
*/
|
|
97
|
+
getSystemPromptAddition() {
|
|
98
|
+
if (this.config.mode === 'none') {
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
const budget = this.config.effortBudget[this.config.mode];
|
|
102
|
+
const modeDescriptions = {
|
|
103
|
+
none: '',
|
|
104
|
+
brief: 'Provide condensed, focused reasoning highlighting only the key decision points.',
|
|
105
|
+
full: 'Provide thorough, step-by-step reasoning that walks through your entire thought process.',
|
|
106
|
+
ultrathink: 'Provide exhaustive, maximum-depth reasoning. Explore every angle, consider alternatives, verify logic at each step, and leave no stone unturned. This is the highest reasoning effort mode.',
|
|
107
|
+
};
|
|
108
|
+
const description = modeDescriptions[this.config.mode];
|
|
109
|
+
return [
|
|
110
|
+
'You have an extended thinking capability. When you need to reason through a problem, enclose your reasoning inside <thinking>...</thinking> tags.',
|
|
111
|
+
`Current thinking mode: ${this.config.mode}.`,
|
|
112
|
+
`Maximum thinking token budget: ${budget} tokens.`,
|
|
113
|
+
description,
|
|
114
|
+
'Place your thinking blocks BEFORE the visible response content. You may include multiple thinking blocks if needed, but stay within the token budget.',
|
|
115
|
+
'Do NOT include <thinking> tags in the final visible output -- they are only for internal reasoning.',
|
|
116
|
+
].join('\n');
|
|
117
|
+
}
|
|
118
|
+
/** Return aggregate statistics across all parsed responses. */
|
|
119
|
+
getStats() {
|
|
120
|
+
const totalBlocks = this.thinkingHistory.length;
|
|
121
|
+
return {
|
|
122
|
+
totalThinkingTokens: this.totalThinkingTokens,
|
|
123
|
+
totalBlocks,
|
|
124
|
+
avgBlockLength: totalBlocks > 0
|
|
125
|
+
? Math.round(this.totalThinkingTokens / totalBlocks)
|
|
126
|
+
: 0,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Reset accumulated statistics and history. */
|
|
130
|
+
resetStats() {
|
|
131
|
+
this.totalThinkingTokens = 0;
|
|
132
|
+
this.thinkingHistory = [];
|
|
133
|
+
}
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
// Streaming helpers (static utilities for use during streaming)
|
|
136
|
+
// -------------------------------------------------------------------------
|
|
137
|
+
/**
|
|
138
|
+
* Check whether a partial streaming buffer currently contains an open
|
|
139
|
+
* thinking tag that has not yet been closed. Useful for deciding whether
|
|
140
|
+
* incoming tokens belong to a thinking region.
|
|
141
|
+
*/
|
|
142
|
+
static isInThinkingBlock(buffer) {
|
|
143
|
+
const lastOpen = buffer.lastIndexOf(THINKING_OPEN_TAG);
|
|
144
|
+
if (lastOpen === -1)
|
|
145
|
+
return false;
|
|
146
|
+
const lastClose = buffer.lastIndexOf(THINKING_CLOSE_TAG);
|
|
147
|
+
return lastClose < lastOpen;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Attempt to extract any complete thinking blocks from a streaming buffer
|
|
151
|
+
* without mutating it. Returns completed blocks and leaves partial blocks
|
|
152
|
+
* untouched (they remain in the buffer for subsequent chunks).
|
|
153
|
+
*/
|
|
154
|
+
static extractStreamingBlocks(buffer) {
|
|
155
|
+
const completedBlocks = [];
|
|
156
|
+
let remaining = buffer;
|
|
157
|
+
let searchStart = 0;
|
|
158
|
+
// eslint-disable-next-line no-constant-condition
|
|
159
|
+
while (true) {
|
|
160
|
+
const openIdx = remaining.indexOf(THINKING_OPEN_TAG, searchStart);
|
|
161
|
+
if (openIdx === -1)
|
|
162
|
+
break;
|
|
163
|
+
const closeIdx = remaining.indexOf(THINKING_CLOSE_TAG, openIdx + OPEN_TAG_LEN);
|
|
164
|
+
if (closeIdx === -1) {
|
|
165
|
+
// Partial block -- still streaming; leave it in remaining
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
const content = remaining.slice(openIdx + OPEN_TAG_LEN, closeIdx).trim();
|
|
169
|
+
completedBlocks.push({
|
|
170
|
+
content,
|
|
171
|
+
tokenCount: ExtendedThinking.estimateTokensStatic(content),
|
|
172
|
+
});
|
|
173
|
+
// Remove the parsed block from remaining
|
|
174
|
+
const before = remaining.slice(0, openIdx);
|
|
175
|
+
const after = remaining.slice(closeIdx + CLOSE_TAG_LEN);
|
|
176
|
+
remaining = before + after;
|
|
177
|
+
searchStart = before.length;
|
|
178
|
+
}
|
|
179
|
+
return { completedBlocks, remaining };
|
|
180
|
+
}
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// Private helpers
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
/**
|
|
185
|
+
* Extract all <thinking>...</thinking> blocks from a complete response
|
|
186
|
+
* string, returning the parsed blocks and the cleaned response with the
|
|
187
|
+
* thinking tags removed.
|
|
188
|
+
*/
|
|
189
|
+
extractThinkingBlocks(response) {
|
|
190
|
+
const blocks = [];
|
|
191
|
+
let cleaned = response;
|
|
192
|
+
let searchStart = 0;
|
|
193
|
+
// eslint-disable-next-line no-constant-condition
|
|
194
|
+
while (true) {
|
|
195
|
+
const openIdx = cleaned.indexOf(THINKING_OPEN_TAG, searchStart);
|
|
196
|
+
if (openIdx === -1)
|
|
197
|
+
break;
|
|
198
|
+
const closeIdx = cleaned.indexOf(THINKING_CLOSE_TAG, openIdx + OPEN_TAG_LEN);
|
|
199
|
+
if (closeIdx === -1) {
|
|
200
|
+
// Malformed -- unclosed tag; treat the rest as thinking content
|
|
201
|
+
const content = cleaned
|
|
202
|
+
.slice(openIdx + OPEN_TAG_LEN)
|
|
203
|
+
.trim();
|
|
204
|
+
if (content.length > 0) {
|
|
205
|
+
blocks.push({
|
|
206
|
+
content,
|
|
207
|
+
tokenCount: this.estimateTokens(content),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
cleaned = cleaned.slice(0, openIdx);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
const content = cleaned
|
|
214
|
+
.slice(openIdx + OPEN_TAG_LEN, closeIdx)
|
|
215
|
+
.trim();
|
|
216
|
+
if (content.length > 0) {
|
|
217
|
+
blocks.push({
|
|
218
|
+
content,
|
|
219
|
+
tokenCount: this.estimateTokens(content),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Splice out the thinking block
|
|
223
|
+
const before = cleaned.slice(0, openIdx);
|
|
224
|
+
const after = cleaned.slice(closeIdx + CLOSE_TAG_LEN);
|
|
225
|
+
cleaned = before + after;
|
|
226
|
+
searchStart = before.length;
|
|
227
|
+
}
|
|
228
|
+
// Collapse excessive whitespace left behind after removal
|
|
229
|
+
cleaned = cleaned.replace(/\n{3,}/g, '\n\n').trim();
|
|
230
|
+
return { blocks, cleaned };
|
|
231
|
+
}
|
|
232
|
+
/** Estimate the number of tokens in a piece of text. */
|
|
233
|
+
estimateTokens(text) {
|
|
234
|
+
return ExtendedThinking.estimateTokensStatic(text);
|
|
235
|
+
}
|
|
236
|
+
/** Static version for use without an instance. */
|
|
237
|
+
static estimateTokensStatic(text) {
|
|
238
|
+
if (text.length === 0)
|
|
239
|
+
return 0;
|
|
240
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
241
|
+
}
|
|
242
|
+
// -------------------------------------------------------------------------
|
|
243
|
+
// Persistence helpers
|
|
244
|
+
// -------------------------------------------------------------------------
|
|
245
|
+
/**
|
|
246
|
+
* Serialize the display preference and mode so it can be persisted
|
|
247
|
+
* (e.g. to a config file or localStorage).
|
|
248
|
+
*/
|
|
249
|
+
serializePreferences() {
|
|
250
|
+
return {
|
|
251
|
+
mode: this.config.mode,
|
|
252
|
+
showThinking: this.config.showThinking,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Restore a previously serialized preference. Only mode and display
|
|
257
|
+
* toggle are restored; statistics remain untouched.
|
|
258
|
+
*/
|
|
259
|
+
restorePreferences(prefs) {
|
|
260
|
+
if (prefs.mode !== undefined) {
|
|
261
|
+
this.setMode(prefs.mode);
|
|
262
|
+
}
|
|
263
|
+
if (prefs.showThinking !== undefined) {
|
|
264
|
+
this.config.showThinking = prefs.showThinking;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export default ExtendedThinking;
|
|
269
|
+
//# sourceMappingURL=extended-thinking.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { OpenRouterClient, TokenUsage } from '../api/openrouter.js';
|
|
2
|
+
import { Message } from './types.js';
|
|
3
|
+
export interface FallbackConfig {
|
|
4
|
+
models: string[];
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
retryDelayMs: number;
|
|
7
|
+
fallbackOnErrors: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface FallbackResult {
|
|
10
|
+
content: string;
|
|
11
|
+
modelUsed: string;
|
|
12
|
+
attempts: Array<{
|
|
13
|
+
model: string;
|
|
14
|
+
success: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
}>;
|
|
17
|
+
usage: TokenUsage;
|
|
18
|
+
toolCalls: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
type: 'function';
|
|
21
|
+
function: {
|
|
22
|
+
name: string;
|
|
23
|
+
arguments: string;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare class FallbackChain {
|
|
28
|
+
private config;
|
|
29
|
+
private client;
|
|
30
|
+
constructor(client: OpenRouterClient, config?: Partial<FallbackConfig>);
|
|
31
|
+
/**
|
|
32
|
+
* Execute a chat request with fallback chain
|
|
33
|
+
*/
|
|
34
|
+
chatWithFallback(primaryModel: string, messages: Message[], options: {
|
|
35
|
+
tools?: unknown[];
|
|
36
|
+
temperature?: number;
|
|
37
|
+
maxTokens?: number;
|
|
38
|
+
stream?: boolean;
|
|
39
|
+
}, onModelSwitch?: (from: string, to: string) => void): Promise<FallbackResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Set fallback models
|
|
42
|
+
*/
|
|
43
|
+
setFallbackModels(models: string[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Add a fallback model
|
|
46
|
+
*/
|
|
47
|
+
addFallbackModel(model: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Get current fallback chain
|
|
50
|
+
*/
|
|
51
|
+
getFallbackChain(): string[];
|
|
52
|
+
private sleep;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Fallback Model Chain
|
|
3
|
+
// Automatic model fallback on failure
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { MODELS } from '../api/models.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
export class FallbackChain {
|
|
8
|
+
config;
|
|
9
|
+
client;
|
|
10
|
+
constructor(client, config) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.config = {
|
|
13
|
+
models: [],
|
|
14
|
+
maxRetries: 2,
|
|
15
|
+
retryDelayMs: 3000,
|
|
16
|
+
fallbackOnErrors: ['rate_limit', 'overloaded', 'context_length_exceeded', 'timeout', 'server_error'],
|
|
17
|
+
...config,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Execute a chat request with fallback chain
|
|
22
|
+
*/
|
|
23
|
+
async chatWithFallback(primaryModel, messages, options, onModelSwitch) {
|
|
24
|
+
const chain = [primaryModel, ...this.config.models.filter(m => m !== primaryModel)];
|
|
25
|
+
const attempts = [];
|
|
26
|
+
let totalUsage = { inputTokens: 0, outputTokens: 0, cost: 0 };
|
|
27
|
+
for (const model of chain) {
|
|
28
|
+
for (let retry = 0; retry <= this.config.maxRetries; retry++) {
|
|
29
|
+
try {
|
|
30
|
+
const modelInfo = MODELS[model];
|
|
31
|
+
console.log(chalk.gray(` → Trying ${modelInfo?.name || model}${retry > 0 ? ` (retry ${retry})` : ''}...`));
|
|
32
|
+
const response = await this.client.chat({
|
|
33
|
+
model,
|
|
34
|
+
messages,
|
|
35
|
+
tools: options.tools,
|
|
36
|
+
temperature: options.temperature,
|
|
37
|
+
maxTokens: options.maxTokens,
|
|
38
|
+
stream: options.stream ?? false,
|
|
39
|
+
});
|
|
40
|
+
totalUsage.inputTokens += response.usage.inputTokens;
|
|
41
|
+
totalUsage.outputTokens += response.usage.outputTokens;
|
|
42
|
+
totalUsage.cost += response.usage.cost;
|
|
43
|
+
attempts.push({ model, success: true });
|
|
44
|
+
// If we used a fallback model, notify
|
|
45
|
+
if (model !== primaryModel && onModelSwitch) {
|
|
46
|
+
onModelSwitch(primaryModel, model);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
content: response.content,
|
|
50
|
+
modelUsed: model,
|
|
51
|
+
attempts,
|
|
52
|
+
usage: totalUsage,
|
|
53
|
+
toolCalls: response.toolCalls || [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
58
|
+
attempts.push({ model, success: false, error: errMsg });
|
|
59
|
+
// Check if this error should trigger fallback
|
|
60
|
+
const shouldFallback = this.config.fallbackOnErrors.some(pattern => errMsg.toLowerCase().includes(pattern.toLowerCase()) ||
|
|
61
|
+
errMsg.includes('429') ||
|
|
62
|
+
errMsg.includes('503') ||
|
|
63
|
+
errMsg.includes('500'));
|
|
64
|
+
if (shouldFallback) {
|
|
65
|
+
console.log(chalk.yellow(` ⚠ ${model} failed: ${errMsg.slice(0, 80)}`));
|
|
66
|
+
break; // Try next model in chain
|
|
67
|
+
}
|
|
68
|
+
// For other errors, retry on same model
|
|
69
|
+
if (retry < this.config.maxRetries) {
|
|
70
|
+
const delay = this.config.retryDelayMs * (retry + 1);
|
|
71
|
+
console.log(chalk.gray(` ↻ Retrying in ${delay / 1000}s...`));
|
|
72
|
+
await this.sleep(delay);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// All models failed
|
|
78
|
+
throw new Error(`All models in fallback chain failed. Attempts: ${JSON.stringify(attempts)}`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Set fallback models
|
|
82
|
+
*/
|
|
83
|
+
setFallbackModels(models) {
|
|
84
|
+
this.config.models = models;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Add a fallback model
|
|
88
|
+
*/
|
|
89
|
+
addFallbackModel(model) {
|
|
90
|
+
if (!this.config.models.includes(model)) {
|
|
91
|
+
this.config.models.push(model);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get current fallback chain
|
|
96
|
+
*/
|
|
97
|
+
getFallbackChain() {
|
|
98
|
+
return [...this.config.models];
|
|
99
|
+
}
|
|
100
|
+
sleep(ms) {
|
|
101
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface WorktreeInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
branch: string;
|
|
6
|
+
isCurrent: boolean;
|
|
7
|
+
isBare: boolean;
|
|
8
|
+
/** The commit hash the worktree is on */
|
|
9
|
+
head: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ConflictInfo {
|
|
12
|
+
filePath: string;
|
|
13
|
+
type: 'content' | 'delete_modify' | 'rename' | 'add_add';
|
|
14
|
+
worktreeA: string;
|
|
15
|
+
worktreeB: string;
|
|
16
|
+
/** Lines with conflicts (approximate) */
|
|
17
|
+
conflictingLines: number;
|
|
18
|
+
/** Whether this conflict can be auto-resolved */
|
|
19
|
+
autoResolvable: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface MergeResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
targetBranch: string;
|
|
24
|
+
sourceWorktree: string;
|
|
25
|
+
mergedFiles: string[];
|
|
26
|
+
conflictFiles: string[];
|
|
27
|
+
message: string;
|
|
28
|
+
/** If there were conflicts, the raw conflict output */
|
|
29
|
+
conflictOutput?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface WorktreeStatus {
|
|
32
|
+
name: string;
|
|
33
|
+
branch: string;
|
|
34
|
+
modified: number;
|
|
35
|
+
added: number;
|
|
36
|
+
deleted: number;
|
|
37
|
+
untracked: number;
|
|
38
|
+
staged: number;
|
|
39
|
+
isClean: boolean;
|
|
40
|
+
/** List of modified file paths */
|
|
41
|
+
modifiedFiles: string[];
|
|
42
|
+
}
|
|
43
|
+
export declare class GitWorktreeManager extends EventEmitter {
|
|
44
|
+
private repoRoot;
|
|
45
|
+
private agentBindings;
|
|
46
|
+
constructor(repoRoot?: string);
|
|
47
|
+
/**
|
|
48
|
+
* Create a new git worktree for parallel development.
|
|
49
|
+
* If no branch is specified, creates a new branch named after the worktree.
|
|
50
|
+
*/
|
|
51
|
+
createWorktree(name: string, branch?: string): Promise<WorktreeInfo>;
|
|
52
|
+
/**
|
|
53
|
+
* Remove a git worktree. If merge is true, attempt to merge the branch first.
|
|
54
|
+
*/
|
|
55
|
+
removeWorktree(name: string, options?: {
|
|
56
|
+
force?: boolean;
|
|
57
|
+
merge?: boolean;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* List all git worktrees in the repository.
|
|
61
|
+
*/
|
|
62
|
+
listWorktrees(): Promise<WorktreeInfo[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Bind an agent to a specific worktree. The agent will operate
|
|
65
|
+
* within that worktree's directory.
|
|
66
|
+
*/
|
|
67
|
+
bindAgentToWorktree(agentId: string, worktreeName: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Unbind an agent from its worktree.
|
|
70
|
+
*/
|
|
71
|
+
unbindAgent(agentId: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Get the worktree name bound to an agent, or null if unbound.
|
|
74
|
+
*/
|
|
75
|
+
getWorktreeForAgent(agentId: string): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Get all agent bindings.
|
|
78
|
+
*/
|
|
79
|
+
getAgentBindings(): Array<{
|
|
80
|
+
agentId: string;
|
|
81
|
+
worktreeName: string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Detect potential conflicts between two worktrees by comparing
|
|
85
|
+
* their modified files and checking for overlaps.
|
|
86
|
+
*/
|
|
87
|
+
detectConflicts(worktreeA: string, worktreeB: string): Promise<ConflictInfo[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Attempt to automatically merge a worktree's branch into the target branch.
|
|
90
|
+
* Defaults to merging into the main branch.
|
|
91
|
+
*/
|
|
92
|
+
autoMerge(worktreeName: string, targetBranch?: string): Promise<MergeResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Get the status of a worktree (modified, added, deleted files, etc.).
|
|
95
|
+
*/
|
|
96
|
+
getWorktreeStatus(name: string): Promise<WorktreeStatus>;
|
|
97
|
+
/**
|
|
98
|
+
* Remove all worktrees (except the main one) and optionally delete branches.
|
|
99
|
+
*/
|
|
100
|
+
cleanup(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Execute a git command and return its output.
|
|
103
|
+
*/
|
|
104
|
+
private execGit;
|
|
105
|
+
/**
|
|
106
|
+
* Parse the porcelain output of `git worktree list`.
|
|
107
|
+
*/
|
|
108
|
+
private parseWorktreeList;
|
|
109
|
+
/**
|
|
110
|
+
* Derive a human-readable name from the worktree path or branch.
|
|
111
|
+
*/
|
|
112
|
+
private worktreeNameFromPath;
|
|
113
|
+
/**
|
|
114
|
+
* Check whether a branch exists in the repository.
|
|
115
|
+
*/
|
|
116
|
+
private branchExists;
|
|
117
|
+
/**
|
|
118
|
+
* Get the list of modified files in a worktree.
|
|
119
|
+
*/
|
|
120
|
+
private getModifiedFiles;
|
|
121
|
+
/**
|
|
122
|
+
* Determine the type of conflict between two file modifications.
|
|
123
|
+
*/
|
|
124
|
+
private determineConflictType;
|
|
125
|
+
/**
|
|
126
|
+
* Analyze the diff between the same file in two worktrees to estimate
|
|
127
|
+
* conflict severity.
|
|
128
|
+
*/
|
|
129
|
+
private analyzeFileDiff;
|
|
130
|
+
/**
|
|
131
|
+
* Extract changed line ranges from a unified diff output.
|
|
132
|
+
*/
|
|
133
|
+
private extractChangedLineRanges;
|
|
134
|
+
/**
|
|
135
|
+
* Get the list of files changed in the last merge.
|
|
136
|
+
*/
|
|
137
|
+
private getChangedFilesInMerge;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=git-worktree.d.ts.map
|