winter-super-cli 2026.5.27 → 2026.5.28
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/package.json +1 -1
- package/src/ai/model-capabilities.js +185 -0
- package/src/ai/prompts/system-prompt.js +96 -8
- package/src/ai/prompts/task-classifier.js +5 -1
- package/src/ai/providers.js +141 -7
- package/src/ai/reasoning.js +266 -0
- package/src/cli/repl-commands.js +1 -0
- package/src/cli/repl.js +3 -1
- package/src/context/resource-loader.js +136 -0
- package/src/context/router.js +77 -20
package/package.json
CHANGED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ❄️ MODEL CAPABILITIES ❄️
|
|
3
|
+
* Detect AI model capability tier from model name.
|
|
4
|
+
* Small models need aggressive prompting to compete with large ones.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const MODEL_TIERS = {
|
|
8
|
+
TINY: 'tiny', // <3B params — barely functional for code
|
|
9
|
+
SMALL: 'small', // 3B-15B params — basic code ability
|
|
10
|
+
MEDIUM: 'medium', // 15B-40B params — decent code ability
|
|
11
|
+
LARGE: 'large', // 40B-120B params — strong code ability, could be flagship
|
|
12
|
+
FLAGSHIP: 'flagship', // 120B+ or proprietary frontier models
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ordered tiers from weakest to strongest (for comparison).
|
|
17
|
+
*/
|
|
18
|
+
const TIER_ORDER = [MODEL_TIERS.TINY, MODEL_TIERS.SMALL, MODEL_TIERS.MEDIUM, MODEL_TIERS.LARGE, MODEL_TIERS.FLAGSHIP];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Classify a model name into a capability tier.
|
|
22
|
+
* @param {string} modelName - e.g. "llama3", "gpt-4", "qwen2.5:7b"
|
|
23
|
+
* @param {string} [provider] - e.g. "ollama", "openai" (optional, helps disambiguate)
|
|
24
|
+
* @returns {string} One of MODEL_TIERS
|
|
25
|
+
*/
|
|
26
|
+
export function classifyModelTier(modelName, provider = '') {
|
|
27
|
+
const name = (modelName || '').toLowerCase().trim();
|
|
28
|
+
const prov = (provider || '').toLowerCase().trim();
|
|
29
|
+
|
|
30
|
+
// ===== FLAGSHIP (frontier models) =====
|
|
31
|
+
const flagshipPatterns = [
|
|
32
|
+
/claude-3-5-sonnet/i, /claude-opus/i, /claude-4/i, /claude-sonnet-4/i,
|
|
33
|
+
/gpt-4o/i, /gpt-4-turbo/i, /o1/i, /o3/i,
|
|
34
|
+
/gemini-2\.5-pro/i, /gemini-2\.0-ultra/i,
|
|
35
|
+
/deepseek-v3/i, /deepseek-r1/i,
|
|
36
|
+
/llama-4/i, /llama-3-70b/i, /llama3-70b/i, /llama3\.1-70b/i, /llama3\.2-90b/i, /llama3\.3/i,
|
|
37
|
+
/qwen2\.5-?72b/i, /qwen2\.5-?70b/i, /qwen-?2\.5-?72b/i,
|
|
38
|
+
/mistral-large/i, /mixtral-8x22b/i,
|
|
39
|
+
/command-r-plus/i, /command-a/i,
|
|
40
|
+
/yi-?34b/i,
|
|
41
|
+
/dbrx-instruct/i,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// If using a cloud provider like OpenAI/Anthropic/Groq, their default models are typically large+
|
|
45
|
+
if (prov === 'openai' || prov === 'anthropic' || prov === 'claude') {
|
|
46
|
+
if (name.includes('gpt-3.5') || name.includes('gpt-3')) return MODEL_TIERS.MEDIUM;
|
|
47
|
+
if (name.includes('claude-3-haiku') || name.includes('claude-3-5-haiku')) return MODEL_TIERS.MEDIUM;
|
|
48
|
+
return MODEL_TIERS.LARGE; // Default for OpenAI/Anthropic is >= gpt-4 level
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (prov === 'groq') {
|
|
52
|
+
// Groq runs open models, most are large but some are not
|
|
53
|
+
if (/llama.*8b|llama3.*8b|llama3\.2.*3b/i.test(name)) return MODEL_TIERS.SMALL;
|
|
54
|
+
if (/gemma2.*9b/i.test(name)) return MODEL_TIERS.SMALL;
|
|
55
|
+
if (/mixtral-8x7|llama.*70b|llama3.*70b|llama3\.1.*70b|qwen/i.test(name)) return MODEL_TIERS.LARGE;
|
|
56
|
+
return MODEL_TIERS.MEDIUM; // Default for Groq
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check patterns for any provider
|
|
60
|
+
for (const pattern of flagshipPatterns) {
|
|
61
|
+
if (pattern.test(name)) return MODEL_TIERS.FLAGSHIP;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ===== LARGE MODELS =====
|
|
65
|
+
const largePatterns = [
|
|
66
|
+
/claude-sonnet/i, /claude-3/i, /claude-2/i,
|
|
67
|
+
/gpt-4/i, /gpt-4-32k/i,
|
|
68
|
+
/llama-3\.1-?70b/i, /llama-3\.2-?70b/i, /llama3-?70b/i,
|
|
69
|
+
/llama-2-?70b/i,
|
|
70
|
+
/qwen-?2\.5-?32b/i, /qwen-?2-?72b/i,
|
|
71
|
+
/codellama-?70b/i,
|
|
72
|
+
/mixtral/i,
|
|
73
|
+
/deepseek-?v2/i,
|
|
74
|
+
/gemini-1\.5-pro/i, /gemini-2\.0-flash/i,
|
|
75
|
+
/command-r/i,
|
|
76
|
+
/yi-?34b/i,
|
|
77
|
+
/mistral-medium/i,
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const pattern of largePatterns) {
|
|
81
|
+
if (pattern.test(name)) return MODEL_TIERS.LARGE;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ===== MEDIUM MODELS =====
|
|
85
|
+
const mediumPatterns = [
|
|
86
|
+
/qwen-?2\.5-?14b/i, /qwen-?2\.5-?7b/i, /qwen-?2/i,
|
|
87
|
+
/llama-3-?8b/i, /llama-3\.1-?8b/i, /llama-3\.2-?11b/i,
|
|
88
|
+
/llama-2-?13b/i, /llama-2-?7b/i,
|
|
89
|
+
/deepseek-coder-?6\.7b/i, /deepseek-coder-?33b/i,
|
|
90
|
+
/codellama-?34b/i, /codellama-?13b/i, /codellama-?7b/i,
|
|
91
|
+
/mistral/i, /mistral-7b/i,
|
|
92
|
+
/gemma-2-?9b/i, /gemma-?7b/i,
|
|
93
|
+
/phi-3/i, /phi-3-medium/i,
|
|
94
|
+
/nemotron/i,
|
|
95
|
+
/solar/i,
|
|
96
|
+
/dbrx/i,
|
|
97
|
+
/starcoder2/i,
|
|
98
|
+
/deepseek-llm/i,
|
|
99
|
+
/yi-?6b/i, /yi-?9b/i,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const pattern of mediumPatterns) {
|
|
103
|
+
if (pattern.test(name)) return MODEL_TIERS.MEDIUM;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ===== SMALL MODELS =====
|
|
107
|
+
const smallPatterns = [
|
|
108
|
+
/llama-3\.2-?3b/i, /llama-3\.2-?1b/i, /tinyllama/i,
|
|
109
|
+
/qwen-?2\.5-?3b/i, /qwen-?2\.5-?1\.5b/i, /qwen-?2\.5-?0\.5b/i,
|
|
110
|
+
/phi-?3-?mini/i, /phi-?2/i, /phi-?1/i,
|
|
111
|
+
/gemma-?2-?2b/i,
|
|
112
|
+
/stablelm/i,
|
|
113
|
+
/orca/i,
|
|
114
|
+
/falcon/i,
|
|
115
|
+
/red-pajama/i,
|
|
116
|
+
/pythia/i,
|
|
117
|
+
/opt/i,
|
|
118
|
+
/bloom/i,
|
|
119
|
+
/mpnet/i,
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const pattern of smallPatterns) {
|
|
123
|
+
if (pattern.test(name)) return MODEL_TIERS.SMALL;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (/tiny/i.test(name) || /mini/i.test(name) || /small/i.test(name) || /nano/i.test(name)) {
|
|
127
|
+
return MODEL_TIERS.TINY;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fallback: if Ollama, likely small
|
|
131
|
+
if (prov === 'ollama' || prov === 'local') return MODEL_TIERS.SMALL;
|
|
132
|
+
|
|
133
|
+
// Default: assume medium
|
|
134
|
+
return MODEL_TIERS.MEDIUM;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a model tier is considered "small" (needs aggressive prompting).
|
|
139
|
+
*/
|
|
140
|
+
export function isSmallModel(tier) {
|
|
141
|
+
return tier === MODEL_TIERS.TINY || tier === MODEL_TIERS.SMALL;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the index of a tier in the order array (0=weakest).
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
function tierIndex(tier) {
|
|
149
|
+
const idx = TIER_ORDER.indexOf(tier);
|
|
150
|
+
return idx >= 0 ? idx : 2; // Default to medium index
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compare two tiers. Returns negative if a < b, positive if a > b, 0 if equal.
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
function compareTiers(a, b) {
|
|
158
|
+
return tierIndex(a) - tierIndex(b);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get recommended reasoning level bump for small models.
|
|
163
|
+
* Small models need more aggressive reasoning prompting to compensate.
|
|
164
|
+
*/
|
|
165
|
+
export function getReasoningBump(tier) {
|
|
166
|
+
switch (tier) {
|
|
167
|
+
case MODEL_TIERS.TINY: return 2; // bump 2 levels
|
|
168
|
+
case MODEL_TIERS.SMALL: return 1; // bump 1 level
|
|
169
|
+
default: return 0;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build a short string describing model capability for system prompt injection.
|
|
175
|
+
*/
|
|
176
|
+
export function getModelCapabilityLabel(tier) {
|
|
177
|
+
switch (tier) {
|
|
178
|
+
case MODEL_TIERS.TINY: return 'tiny local model — needs maximum guidance';
|
|
179
|
+
case MODEL_TIERS.SMALL: return 'small local model — needs extra structure';
|
|
180
|
+
case MODEL_TIERS.MEDIUM: return 'medium-capability model';
|
|
181
|
+
case MODEL_TIERS.LARGE: return 'high-capability model';
|
|
182
|
+
case MODEL_TIERS.FLAGSHIP: return 'frontier model — full capability expected';
|
|
183
|
+
default: return '';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dynamic System Prompt Builder
|
|
3
3
|
* Builds context-aware system prompts based on task, role, and session state.
|
|
4
|
+
* Small models get aggressive structural guidance to compensate for limited capability.
|
|
4
5
|
*/
|
|
5
6
|
|
|
7
|
+
import { isSmallModel, getModelCapabilityLabel } from '../model-capabilities.js';
|
|
8
|
+
|
|
6
9
|
const BASE_PRINCIPLES = [
|
|
10
|
+
'Execute, don\'t describe - Do the work, don\'t write plans about doing the work',
|
|
7
11
|
'Think Before Coding - State assumptions, ask when unclear',
|
|
8
12
|
'Simplicity First - Minimum code that solves the problem',
|
|
9
13
|
'Surgical Changes - Touch only what you must',
|
|
@@ -36,15 +40,36 @@ function buildEnvironmentSummary() {
|
|
|
36
40
|
].join('\n');
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Build a "boosted" system prompt for small/tiny models.
|
|
45
|
+
* Small models need: more explicit structure, strict formats, explicit step-by-step forcing.
|
|
46
|
+
*/
|
|
47
|
+
function buildSmallModelSystemPrompt({
|
|
40
48
|
role = 'coding',
|
|
41
49
|
context,
|
|
42
50
|
tools = [],
|
|
43
51
|
session,
|
|
44
52
|
environment,
|
|
53
|
+
design,
|
|
54
|
+
resourceContext,
|
|
55
|
+
modelTier,
|
|
45
56
|
} = {}) {
|
|
46
57
|
const parts = [
|
|
47
|
-
`You are Winter, an expert AI coding assistant.`,
|
|
58
|
+
`You are Winter, an expert AI coding assistant. You are running on a ${getModelCapabilityLabel(modelTier)}.`,
|
|
59
|
+
'',
|
|
60
|
+
'## CRITICAL: YOU MUST THINK STEP BY STEP',
|
|
61
|
+
'',
|
|
62
|
+
'Because you are a smaller model, you MUST use structured thinking to produce quality results.',
|
|
63
|
+
'Before any response, use <thinking> tags to reason through the problem.',
|
|
64
|
+
'',
|
|
65
|
+
'Your thinking must cover:',
|
|
66
|
+
'1. What does the user want? (restate briefly)',
|
|
67
|
+
'2. What files/tools do I need to use?',
|
|
68
|
+
'3. What is the best approach?',
|
|
69
|
+
'4. What could go wrong? Edge cases?',
|
|
70
|
+
'5. Is my solution complete and correct?',
|
|
71
|
+
'',
|
|
72
|
+
'After thinking, THEN act. Never skip the thinking step.',
|
|
48
73
|
'',
|
|
49
74
|
'## Core Principles',
|
|
50
75
|
...BASE_PRINCIPLES.map((p, i) => `${i + 1}. ${p}`),
|
|
@@ -70,21 +95,79 @@ export function buildSystemPrompt({
|
|
|
70
95
|
parts.push('');
|
|
71
96
|
}
|
|
72
97
|
|
|
98
|
+
if (design) {
|
|
99
|
+
parts.push('## Design Guidelines');
|
|
100
|
+
if (design.brand) {
|
|
101
|
+
parts.push(`Brand: ${design.brand}`);
|
|
102
|
+
parts.push('');
|
|
103
|
+
const lines = design.content.split('\n').filter(Boolean);
|
|
104
|
+
const preview = lines.slice(0, 40).join('\n');
|
|
105
|
+
parts.push(preview);
|
|
106
|
+
if (lines.length > 40) parts.push('... (design file truncated)');
|
|
107
|
+
} else if (design.type === 'design_hint') {
|
|
108
|
+
parts.push('Design-related task detected. Consider applying one of the available design systems.');
|
|
109
|
+
parts.push(`Available: ${design.brands.join(', ')}`);
|
|
110
|
+
}
|
|
111
|
+
parts.push('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (resourceContext) {
|
|
115
|
+
parts.push(resourceContext);
|
|
116
|
+
}
|
|
117
|
+
|
|
73
118
|
parts.push(
|
|
74
|
-
'##
|
|
75
|
-
'-
|
|
76
|
-
'-
|
|
77
|
-
'-
|
|
78
|
-
'-
|
|
119
|
+
'## Execution Rules (STRICT)',
|
|
120
|
+
'- EXECUTE FIRST. Read files, then edit. Do NOT describe what you will do — just do it.',
|
|
121
|
+
'- Keep explanations under 2 sentences. Say what you changed, not what you could do.',
|
|
122
|
+
'- After using tools, give only a one-line summary of what was done.',
|
|
123
|
+
'- Answer questions directly — no disclaimers or warnings.',
|
|
124
|
+
'- If a request is unsafe, refuse briefly and stop.',
|
|
125
|
+
'',
|
|
126
|
+
'## Thinking Format (MANDATORY)',
|
|
127
|
+
'<thinking>',
|
|
128
|
+
'Step-by-step reasoning here...',
|
|
129
|
+
'</thinking>',
|
|
130
|
+
'[Your action/answer here]',
|
|
79
131
|
);
|
|
80
132
|
|
|
81
133
|
return parts.join('\n');
|
|
82
134
|
}
|
|
135
|
+
export function buildSystemPrompt({
|
|
136
|
+
role = 'coding',
|
|
137
|
+
context,
|
|
138
|
+
tools = [],
|
|
139
|
+
session,
|
|
140
|
+
environment,
|
|
141
|
+
design,
|
|
142
|
+
resourceContext,
|
|
143
|
+
modelTier,
|
|
144
|
+
} = {}) {
|
|
145
|
+
// ALL models get the deep-thinking system prompt for maximum code quality
|
|
146
|
+
return buildSmallModelSystemPrompt({
|
|
147
|
+
role,
|
|
148
|
+
context,
|
|
149
|
+
tools,
|
|
150
|
+
session,
|
|
151
|
+
environment,
|
|
152
|
+
design,
|
|
153
|
+
resourceContext,
|
|
154
|
+
modelTier,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
83
157
|
|
|
84
158
|
export function buildFastSystemPrompt({
|
|
85
159
|
role = 'coding',
|
|
86
160
|
tools = [],
|
|
161
|
+
modelTier,
|
|
87
162
|
} = {}) {
|
|
163
|
+
if (modelTier && isSmallModel(modelTier)) {
|
|
164
|
+
return [
|
|
165
|
+
'Winter (fast mode - small model). Be concise. Use tools when needed.',
|
|
166
|
+
tools.length > 0 ? `Tools: ${tools.join(', ')}` : '',
|
|
167
|
+
'THINK inside <thinking> before acting. Keep responses to 1 sentence.',
|
|
168
|
+
].filter(Boolean).join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
88
171
|
return [
|
|
89
172
|
'You are Winter (fast mode). Be concise. Use tools when needed.',
|
|
90
173
|
tools.length > 0 ? `Tools: ${tools.join(', ')}` : '',
|
|
@@ -92,7 +175,7 @@ export function buildFastSystemPrompt({
|
|
|
92
175
|
].filter(Boolean).join('\n');
|
|
93
176
|
}
|
|
94
177
|
|
|
95
|
-
export function buildAgentSystemPrompt(role, { tools = [] } = {}) {
|
|
178
|
+
export function buildAgentSystemPrompt(role, { tools = [], modelTier } = {}) {
|
|
96
179
|
const roleConfigs = {
|
|
97
180
|
plan: 'You analyze codebases and plan multi-step implementations. Output clear steps.',
|
|
98
181
|
review: 'You review code for bugs, style issues, and improvements. Be critical but constructive.',
|
|
@@ -102,10 +185,15 @@ export function buildAgentSystemPrompt(role, { tools = [] } = {}) {
|
|
|
102
185
|
};
|
|
103
186
|
|
|
104
187
|
const base = roleConfigs[role] || roleConfigs.coding;
|
|
188
|
+
const smallNote = modelTier && isSmallModel(modelTier)
|
|
189
|
+
? '\n\nYou are running on a small model. Use <thinking> tags and reason step by step before each action.'
|
|
190
|
+
: '';
|
|
191
|
+
|
|
105
192
|
return [
|
|
106
193
|
`You are Winter (${role} agent).`,
|
|
107
194
|
base,
|
|
108
195
|
tools.length > 0 ? `\nTools: ${tools.join(', ')}` : '',
|
|
196
|
+
smallNote,
|
|
109
197
|
'\nCRITICAL: Output only the requested format. No extra commentary.',
|
|
110
198
|
].filter(Boolean).join('\n');
|
|
111
199
|
}
|
|
@@ -24,6 +24,8 @@ export const TASK_CATEGORIES = {
|
|
|
24
24
|
TEST: 'test',
|
|
25
25
|
CONFIG: 'config',
|
|
26
26
|
INSTALL: 'install',
|
|
27
|
+
DESIGN: 'design',
|
|
28
|
+
UI: 'ui',
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const TYPE_KEYWORDS = {
|
|
@@ -39,6 +41,8 @@ const TYPE_KEYWORDS = {
|
|
|
39
41
|
test: ['test', 'unit test', 'integration test', 'assert', 'spec'],
|
|
40
42
|
config: ['config', 'setup', 'install', 'configure', 'initialize'],
|
|
41
43
|
install: ['install', 'npm install', 'pip install', 'gem install', 'cargo install', 'brew install'],
|
|
44
|
+
design: ['design', 'ui', 'brand', 'style guide', 'make it look', 'pixel perfect', 'layout', 'color scheme', 'palette', 'typography', 'theme'],
|
|
45
|
+
ui: ['ui', 'interface', 'component', 'button', 'card', 'modal', 'form', 'navbar', 'sidebar', 'header', 'footer', 'dashboard', 'landing page'],
|
|
42
46
|
};
|
|
43
47
|
|
|
44
48
|
const COMPLEXITY_SIGNALS = {
|
|
@@ -108,7 +112,7 @@ export function classifyTask(userInput) {
|
|
|
108
112
|
wordCount,
|
|
109
113
|
estimatedTokens,
|
|
110
114
|
requiresTools: bestCategory !== TASK_CATEGORIES.EXPLAIN,
|
|
111
|
-
requiresContext: ['edit', 'refactor', 'debug', 'review', 'test'].includes(bestCategory),
|
|
115
|
+
requiresContext: ['edit', 'refactor', 'debug', 'review', 'test', 'design', 'ui'].includes(bestCategory),
|
|
112
116
|
};
|
|
113
117
|
}
|
|
114
118
|
|
package/src/ai/providers.js
CHANGED
|
@@ -8,6 +8,9 @@ import { selectExecutionProfile } from '../context/router.js';
|
|
|
8
8
|
import { buildSystemPrompt, buildFastSystemPrompt, buildAgentSystemPrompt } from './prompts/system-prompt.js';
|
|
9
9
|
import { classifyTask } from './prompts/task-classifier.js';
|
|
10
10
|
import SuccessCriteria from './prompts/success-criteria.js';
|
|
11
|
+
import { ReasoningConfig, REASONING_LEVELS, complexityToReasoningLevel } from './reasoning.js';
|
|
12
|
+
import { buildResourceContext, getRelevantDesignGuide } from '../context/resource-loader.js';
|
|
13
|
+
import { classifyModelTier } from './model-capabilities.js';
|
|
11
14
|
|
|
12
15
|
function isAuthError(error) {
|
|
13
16
|
const msg = String(error?.message || error || '');
|
|
@@ -23,6 +26,10 @@ export class AIProviderManager {
|
|
|
23
26
|
this.tools = [];
|
|
24
27
|
this.initialized = false;
|
|
25
28
|
this.authToken = null;
|
|
29
|
+
this._cachedResourceContext = '';
|
|
30
|
+
this._cachedDesignGuide = null;
|
|
31
|
+
this._fallbackWarned = false;
|
|
32
|
+
this._modelTier = null;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
async init() {
|
|
@@ -96,6 +103,13 @@ export class AIProviderManager {
|
|
|
96
103
|
if (available) this.activeProvider = available;
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
// Auto-detect model capability tier
|
|
107
|
+
const providerConfig = this.providers[this.activeProvider] || {};
|
|
108
|
+
this._modelTier = classifyModelTier(providerConfig.model, this.activeProvider);
|
|
109
|
+
|
|
110
|
+
// Eager-load local resources (design systems, agent instructions) for contextual injection
|
|
111
|
+
this._loadResourceContext(); // fire-and-forget
|
|
112
|
+
|
|
99
113
|
this.initialized = true;
|
|
100
114
|
}
|
|
101
115
|
|
|
@@ -229,17 +243,27 @@ export class AIProviderManager {
|
|
|
229
243
|
const routedProvider = this.providers[executionProfile.provider] || this.providers[this.activeProvider];
|
|
230
244
|
const defaultProvider = this.providers[this.activeProvider];
|
|
231
245
|
|
|
246
|
+
const routingModel = options.model || executionProfile.model;
|
|
247
|
+
const routingReasoning = options.reasoning || executionProfile.reasoningParam;
|
|
248
|
+
|
|
232
249
|
try {
|
|
233
250
|
return await withRetry(() => this.sendRequestToProvider(routedProvider, messages, {
|
|
234
251
|
...options,
|
|
235
|
-
model:
|
|
252
|
+
model: routingModel,
|
|
253
|
+
reasoning: routingReasoning,
|
|
254
|
+
reasoningLevel: options.reasoningLevel || executionProfile.reasoningLevel,
|
|
236
255
|
}), { maxAttempts: 3, baseDelayMs: 150 });
|
|
237
256
|
} catch (error) {
|
|
238
257
|
if (isAuthError(error) && routedProvider !== defaultProvider && defaultProvider) {
|
|
239
|
-
|
|
258
|
+
if (!this._fallbackWarned) {
|
|
259
|
+
console.warn(`[winter] ${executionProfile.provider} auth error, falling back to ${this.activeProvider}`);
|
|
260
|
+
this._fallbackWarned = true;
|
|
261
|
+
}
|
|
240
262
|
return await withRetry(() => this.sendRequestToProvider(defaultProvider, messages, {
|
|
241
263
|
...options,
|
|
242
264
|
model: options.model || defaultProvider.model,
|
|
265
|
+
reasoning: routingReasoning,
|
|
266
|
+
reasoningLevel: options.reasoningLevel || executionProfile.reasoningLevel,
|
|
243
267
|
}), { maxAttempts: 1, baseDelayMs: 0 });
|
|
244
268
|
}
|
|
245
269
|
throw error;
|
|
@@ -257,17 +281,27 @@ export class AIProviderManager {
|
|
|
257
281
|
const routedProvider = this.providers[executionProfile.provider] || this.providers[this.activeProvider];
|
|
258
282
|
const defaultProvider = this.providers[this.activeProvider];
|
|
259
283
|
|
|
284
|
+
const routingModel = options.model || executionProfile.model;
|
|
285
|
+
const routingReasoning = options.reasoning || executionProfile.reasoningParam;
|
|
286
|
+
|
|
260
287
|
try {
|
|
261
288
|
yield* this.streamRequestToProvider(routedProvider, messages, {
|
|
262
289
|
...options,
|
|
263
|
-
model:
|
|
290
|
+
model: routingModel,
|
|
291
|
+
reasoning: routingReasoning,
|
|
292
|
+
reasoningLevel: options.reasoningLevel || executionProfile.reasoningLevel,
|
|
264
293
|
});
|
|
265
294
|
} catch (error) {
|
|
266
295
|
if (isAuthError(error) && routedProvider !== defaultProvider && defaultProvider) {
|
|
267
|
-
|
|
296
|
+
if (!this._fallbackWarned) {
|
|
297
|
+
console.warn(`[winter] ${executionProfile.provider} auth error, falling back to ${this.activeProvider}`);
|
|
298
|
+
this._fallbackWarned = true;
|
|
299
|
+
}
|
|
268
300
|
yield* this.streamRequestToProvider(defaultProvider, messages, {
|
|
269
301
|
...options,
|
|
270
302
|
model: options.model || defaultProvider.model,
|
|
303
|
+
reasoning: routingReasoning,
|
|
304
|
+
reasoningLevel: options.reasoningLevel || executionProfile.reasoningLevel,
|
|
271
305
|
});
|
|
272
306
|
} else {
|
|
273
307
|
throw error;
|
|
@@ -285,6 +319,17 @@ export class AIProviderManager {
|
|
|
285
319
|
messages,
|
|
286
320
|
};
|
|
287
321
|
|
|
322
|
+
// Apply reasoning configuration
|
|
323
|
+
const reasoningParam = options.reasoning || this._getReasoningParam(options, provider);
|
|
324
|
+
if (reasoningParam) {
|
|
325
|
+
if (reasoningParam.reasoning_effort) {
|
|
326
|
+
body.reasoning_effort = reasoningParam.reasoning_effort;
|
|
327
|
+
}
|
|
328
|
+
if (reasoningParam.thinking) {
|
|
329
|
+
body.thinking = reasoningParam.thinking;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
288
333
|
if (this.tools.length > 0 && options.enableTools) {
|
|
289
334
|
body.tools = this.tools;
|
|
290
335
|
}
|
|
@@ -329,6 +374,17 @@ export class AIProviderManager {
|
|
|
329
374
|
body.stream_options = { include_usage: true };
|
|
330
375
|
}
|
|
331
376
|
|
|
377
|
+
// Apply reasoning configuration
|
|
378
|
+
const reasoningParam = options.reasoning || this._getReasoningParam(options, provider);
|
|
379
|
+
if (reasoningParam) {
|
|
380
|
+
if (reasoningParam.reasoning_effort) {
|
|
381
|
+
body.reasoning_effort = reasoningParam.reasoning_effort;
|
|
382
|
+
}
|
|
383
|
+
if (reasoningParam.thinking) {
|
|
384
|
+
body.thinking = reasoningParam.thinking;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
332
388
|
if (this.tools.length > 0 && options.enableTools) {
|
|
333
389
|
body.tools = this.tools;
|
|
334
390
|
}
|
|
@@ -519,6 +575,23 @@ export class AIProviderManager {
|
|
|
519
575
|
return { error: 'Tool execution handled by REPL' };
|
|
520
576
|
}
|
|
521
577
|
|
|
578
|
+
_getReasoningParam(options, provider) {
|
|
579
|
+
// 1. Explicit reasoning param passed through options
|
|
580
|
+
if (options.reasoning) return options.reasoning;
|
|
581
|
+
|
|
582
|
+
// 2. Reasoning level specified -> build from level
|
|
583
|
+
if (options.reasoningLevel) {
|
|
584
|
+
const config = new ReasoningConfig({
|
|
585
|
+
level: options.reasoningLevel,
|
|
586
|
+
provider: provider?.name || this.activeProvider,
|
|
587
|
+
});
|
|
588
|
+
return config.getApiReasoningParam();
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// 3. No reasoning config at all
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
|
|
522
595
|
getSystemPrompt(options = {}) {
|
|
523
596
|
const taskInfo = options.task ? classifyTask(options.task) : null;
|
|
524
597
|
const tools = this.tools ? Object.keys(this.tools) : [];
|
|
@@ -527,24 +600,85 @@ export class AIProviderManager {
|
|
|
527
600
|
plans: options.plans || [],
|
|
528
601
|
};
|
|
529
602
|
|
|
603
|
+
// Inject reasoning instructions if applicable
|
|
604
|
+
let reasoningPrompt = '';
|
|
605
|
+
if (options.reasoningLevel || options.reasoningPrompt) {
|
|
606
|
+
reasoningPrompt = options.reasoningPrompt || new ReasoningConfig({
|
|
607
|
+
level: options.reasoningLevel || REASONING_LEVELS.MEDIUM,
|
|
608
|
+
provider: this.activeProvider,
|
|
609
|
+
modelTier: this._modelTier,
|
|
610
|
+
}).getPromptInstructions();
|
|
611
|
+
} else if (taskInfo) {
|
|
612
|
+
// Auto-inject based on task complexity for providers without API reasoning
|
|
613
|
+
const level = complexityToReasoningLevel(taskInfo.type);
|
|
614
|
+
const config = new ReasoningConfig({
|
|
615
|
+
level,
|
|
616
|
+
provider: this.activeProvider,
|
|
617
|
+
modelTier: this._modelTier,
|
|
618
|
+
});
|
|
619
|
+
if (config.needsPromptInjection && level !== REASONING_LEVELS.NONE) {
|
|
620
|
+
reasoningPrompt = config.getPromptInstructions();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
530
624
|
if (options.role === 'agent') {
|
|
531
|
-
return buildAgentSystemPrompt(options.agentRole || 'coding', { tools });
|
|
625
|
+
return buildAgentSystemPrompt(options.agentRole || 'coding', { tools, modelTier: this._modelTier }) + reasoningPrompt;
|
|
532
626
|
}
|
|
533
627
|
|
|
534
628
|
if (options.fast) {
|
|
535
|
-
return buildFastSystemPrompt({ role: 'coding', tools });
|
|
629
|
+
return buildFastSystemPrompt({ role: 'coding', tools, modelTier: this._modelTier });
|
|
536
630
|
}
|
|
537
631
|
|
|
538
632
|
const successPrompt = options.task
|
|
539
633
|
? '\n\n' + SuccessCriteria.fromRequest(options.task).buildPrompt()
|
|
540
634
|
: '';
|
|
541
635
|
|
|
636
|
+
// Use cached resource context (eager-loaded in init())
|
|
637
|
+
const resourceContext = this._cachedResourceContext || '';
|
|
638
|
+
|
|
639
|
+
// Auto-detect relevant design guide for UI/design tasks
|
|
640
|
+
let designGuide = null;
|
|
641
|
+
if (taskInfo && (taskInfo.category === 'design' || taskInfo.category === 'ui')) {
|
|
642
|
+
this._designGuidePromise = this._designGuidePromise || this._loadDesignGuide(options.task);
|
|
643
|
+
}
|
|
644
|
+
const design = this._cachedDesignGuide || null;
|
|
645
|
+
|
|
542
646
|
return buildSystemPrompt({
|
|
543
647
|
role: taskInfo?.category || 'coding',
|
|
544
648
|
context: taskInfo,
|
|
545
649
|
tools,
|
|
546
650
|
session: sessionInfo,
|
|
547
|
-
|
|
651
|
+
design,
|
|
652
|
+
resourceContext,
|
|
653
|
+
modelTier: this._modelTier,
|
|
654
|
+
}) + reasoningPrompt + successPrompt;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Load resource context (cached for session lifetime).
|
|
659
|
+
*/
|
|
660
|
+
async _loadResourceContext() {
|
|
661
|
+
try {
|
|
662
|
+
this._cachedResourceContext = await buildResourceContext();
|
|
663
|
+
} catch (e) {
|
|
664
|
+
this._cachedResourceContext = '';
|
|
665
|
+
}
|
|
666
|
+
return this._cachedResourceContext;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Load relevant design guide for a task.
|
|
671
|
+
*/
|
|
672
|
+
async _loadDesignGuide(task) {
|
|
673
|
+
try {
|
|
674
|
+
const guide = await getRelevantDesignGuide(task);
|
|
675
|
+
if (guide) {
|
|
676
|
+
this._cachedDesignGuide = guide;
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {
|
|
679
|
+
// Silently fail - design context is optional
|
|
680
|
+
}
|
|
681
|
+
return this._cachedDesignGuide;
|
|
548
682
|
}
|
|
549
683
|
|
|
550
684
|
classifyTask(userInput) {
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reasoning Configuration Manager
|
|
3
|
+
*
|
|
4
|
+
* Controls reasoning effort / extended thinking per provider and task complexity.
|
|
5
|
+
*
|
|
6
|
+
* Supported APIs:
|
|
7
|
+
* - OpenAI: reasoning_effort ("low" | "medium" | "high") — o1, o3 models
|
|
8
|
+
* - Anthropic: thinking ({ type: "enabled", budget_tokens: number }) — Claude 3.7+ Sonnet
|
|
9
|
+
* - DeepSeek: built-in CoT reasoning (no explicit param needed)
|
|
10
|
+
* - Others: falls back to prompt-level reasoning instructions
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isSmallModel } from './model-capabilities.js';
|
|
14
|
+
|
|
15
|
+
export const REASONING_LEVELS = {
|
|
16
|
+
NONE: 'none',
|
|
17
|
+
LOW: 'low',
|
|
18
|
+
MEDIUM: 'medium',
|
|
19
|
+
HIGH: 'high',
|
|
20
|
+
MAX: 'max',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const REASONING_EFFORT_MAP = {
|
|
24
|
+
[REASONING_LEVELS.NONE]: null,
|
|
25
|
+
[REASONING_LEVELS.LOW]: 'low',
|
|
26
|
+
[REASONING_LEVELS.MEDIUM]: 'medium',
|
|
27
|
+
[REASONING_LEVELS.HIGH]: 'high',
|
|
28
|
+
[REASONING_LEVELS.MAX]: 'high',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const EXTENDED_THINKING_BUDGET_MAP = {
|
|
32
|
+
[REASONING_LEVELS.NONE]: null,
|
|
33
|
+
[REASONING_LEVELS.LOW]: 1024,
|
|
34
|
+
[REASONING_LEVELS.MEDIUM]: 4096,
|
|
35
|
+
[REASONING_LEVELS.HIGH]: 8192,
|
|
36
|
+
[REASONING_LEVELS.MAX]: 16384,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Standard reasoning prompt templates for API-level reasoning models.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Unified deep reasoning prompts — ALL models use these aggressive templates.
|
|
44
|
+
* Every model, regardless of size, must think step by step with explicit structure.
|
|
45
|
+
* The structured <thinking> format forces deep reasoning, catches edge cases,
|
|
46
|
+
* and produces significantly higher quality code.
|
|
47
|
+
*/
|
|
48
|
+
const REASONING_PROMPT_TEMPLATES = {
|
|
49
|
+
[REASONING_LEVELS.NONE]: '',
|
|
50
|
+
[REASONING_LEVELS.LOW]:
|
|
51
|
+
'Think step by step before responding. Use <thinking> tags for your reasoning, then provide your answer.',
|
|
52
|
+
[REASONING_LEVELS.MEDIUM]:
|
|
53
|
+
'CRITICAL: You MUST think step by step inside <thinking> tags before every response.\n' +
|
|
54
|
+
'\n' +
|
|
55
|
+
'<thinking>\n' +
|
|
56
|
+
'1. What is the user asking for? (restate briefly)\n' +
|
|
57
|
+
'2. What do I know / what files do I need?\n' +
|
|
58
|
+
'3. What is the correct approach?\n' +
|
|
59
|
+
'4. What could go wrong? (edge cases, errors)\n' +
|
|
60
|
+
'5. How do I verify my solution?\n' +
|
|
61
|
+
'</thinking>\n' +
|
|
62
|
+
'Then provide your answer clearly and directly.',
|
|
63
|
+
[REASONING_LEVELS.HIGH]:
|
|
64
|
+
'CRITICAL DEEP REASONING REQUIRED. Use this EXACT structured thinking process:\n' +
|
|
65
|
+
'\n' +
|
|
66
|
+
'<thinking>\n' +
|
|
67
|
+
'## STEP 1: UNDERSTAND\n' +
|
|
68
|
+
'- Restate the problem in your own words\n' +
|
|
69
|
+
'- Identify all key requirements (explicit + implicit)\n' +
|
|
70
|
+
'\n' +
|
|
71
|
+
'## STEP 2: ANALYZE\n' +
|
|
72
|
+
'- What information is provided? What is missing?\n' +
|
|
73
|
+
'- Consider multiple approaches\n' +
|
|
74
|
+
'- List potential edge cases and pitfalls\n' +
|
|
75
|
+
'\n' +
|
|
76
|
+
'## STEP 3: PLAN\n' +
|
|
77
|
+
'- Outline your solution step by step\n' +
|
|
78
|
+
'- For code: plan the exact files and changes needed\n' +
|
|
79
|
+
'- Verify each step makes sense\n' +
|
|
80
|
+
'\n' +
|
|
81
|
+
'## STEP 4: VERIFY\n' +
|
|
82
|
+
'- Check your solution against all requirements\n' +
|
|
83
|
+
'- Look for mistakes, regressions, or missing pieces\n' +
|
|
84
|
+
'- How will you confirm it works?\n' +
|
|
85
|
+
'</thinking>\n' +
|
|
86
|
+
'After thinking, provide your final answer. The thinking is internal — be concise in your response.',
|
|
87
|
+
[REASONING_LEVELS.MAX]:
|
|
88
|
+
'## MANDATORY DEEP REASONING\n' +
|
|
89
|
+
'You MUST do extremely thorough reasoning before every response. Do not skip any step.\n' +
|
|
90
|
+
'\n' +
|
|
91
|
+
'Follow this EXACT thinking structure — fill out every section:\n' +
|
|
92
|
+
'\n' +
|
|
93
|
+
'<thinking>\n' +
|
|
94
|
+
'## PROBLEM RESTATEMENT\n' +
|
|
95
|
+
'State what the user needs in one sentence.\n' +
|
|
96
|
+
'\n' +
|
|
97
|
+
'## REQUIREMENTS ANALYSIS\n' +
|
|
98
|
+
'- Explicit requirements:\n' +
|
|
99
|
+
'- Implicit requirements:\n' +
|
|
100
|
+
'- Constraints / boundaries:\n' +
|
|
101
|
+
'\n' +
|
|
102
|
+
'## CONTEXT & CODEBASE ANALYSIS\n' +
|
|
103
|
+
'- What files are relevant?\n' +
|
|
104
|
+
'- What existing patterns should I follow?\n' +
|
|
105
|
+
'- What assumptions am I making?\n' +
|
|
106
|
+
'\n' +
|
|
107
|
+
'## APPROACH COMPARISON\n' +
|
|
108
|
+
'- Option 1: [describe]\n' +
|
|
109
|
+
' Pros: ... Cons: ...\n' +
|
|
110
|
+
'- Option 2: [describe]\n' +
|
|
111
|
+
' Pros: ... Cons: ...\n' +
|
|
112
|
+
'- Best choice: [pick and explain why]\n' +
|
|
113
|
+
'\n' +
|
|
114
|
+
'## IMPLEMENTATION PLAN\n' +
|
|
115
|
+
'Step-by-step what needs to happen:\n' +
|
|
116
|
+
'1. ...\n' +
|
|
117
|
+
'2. ...\n' +
|
|
118
|
+
'3. ...\n' +
|
|
119
|
+
'(For code: include exact files to read, edit, or create)\n' +
|
|
120
|
+
'\n' +
|
|
121
|
+
'## EDGE CASES & RISKS\n' +
|
|
122
|
+
'- What could go wrong?\n' +
|
|
123
|
+
'- How will I handle errors?\n' +
|
|
124
|
+
'- What about performance / security?\n' +
|
|
125
|
+
'\n' +
|
|
126
|
+
'## VERIFICATION STRATEGY\n' +
|
|
127
|
+
'- How will I confirm this works?\n' +
|
|
128
|
+
'- What tests or checks should be run?\n' +
|
|
129
|
+
'- What could break with these changes?\n' +
|
|
130
|
+
'</thinking>\n' +
|
|
131
|
+
'\n' +
|
|
132
|
+
'After closing </thinking>, provide your final implementation.\n' +
|
|
133
|
+
'Keep the reasoning internal — only show the user your result and a brief summary.',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Maps task complexity to recommended reasoning level.
|
|
138
|
+
*/
|
|
139
|
+
export function complexityToReasoningLevel(taskType) {
|
|
140
|
+
switch (taskType) {
|
|
141
|
+
case 'quick': return REASONING_LEVELS.NONE;
|
|
142
|
+
case 'simple': return REASONING_LEVELS.LOW;
|
|
143
|
+
case 'moderate': return REASONING_LEVELS.MEDIUM;
|
|
144
|
+
case 'complex': return REASONING_LEVELS.HIGH;
|
|
145
|
+
case 'deep': return REASONING_LEVELS.MAX;
|
|
146
|
+
default: return REASONING_LEVELS.MEDIUM;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class ReasoningConfig {
|
|
151
|
+
/**
|
|
152
|
+
* @param {object} options
|
|
153
|
+
* @param {string} options.level - One of REASONING_LEVELS
|
|
154
|
+
* @param {string} options.provider - Provider name (for API-specific config)
|
|
155
|
+
* @param {string} [options.modelTier] - Model capability tier from model-capabilities.js
|
|
156
|
+
* @param {object} [options.modelInfo] - Model metadata
|
|
157
|
+
* @param {object} [options.taskInfo] - Task classification result from task-classifier
|
|
158
|
+
*/
|
|
159
|
+
constructor(options = {}) {
|
|
160
|
+
this.level = options.level || REASONING_LEVELS.MEDIUM;
|
|
161
|
+
this.provider = options.provider || '';
|
|
162
|
+
this.modelTier = options.modelTier || null;
|
|
163
|
+
this.modelInfo = options.modelInfo || {};
|
|
164
|
+
this.taskInfo = options.taskInfo || null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Whether reasoning is enabled at all.
|
|
169
|
+
*/
|
|
170
|
+
get enabled() {
|
|
171
|
+
return this.level !== REASONING_LEVELS.NONE && this.level !== null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Whether this level should inject reasoning instructions into the system prompt.
|
|
176
|
+
*/
|
|
177
|
+
get needsPromptInjection() {
|
|
178
|
+
return this.providerSupportsApiReasoning === false || !this.provider;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if provider has native API-level reasoning support.
|
|
183
|
+
*/
|
|
184
|
+
get providerSupportsApiReasoning() {
|
|
185
|
+
const p = (this.provider || '').toLowerCase();
|
|
186
|
+
if (p === 'openai') return true;
|
|
187
|
+
if (p === 'anthropic' || p === 'claude') return true;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Whether this is a small model that needs aggressive prompting.
|
|
193
|
+
*/
|
|
194
|
+
get isSmall() {
|
|
195
|
+
return this.modelTier ? isSmallModel(this.modelTier) : false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get the API-level reasoning parameter for the request body.
|
|
200
|
+
* Returns null if provider doesn't support API reasoning or level is NONE.
|
|
201
|
+
*/
|
|
202
|
+
getApiReasoningParam() {
|
|
203
|
+
if (!this.enabled) return null;
|
|
204
|
+
|
|
205
|
+
const p = (this.provider || '').toLowerCase();
|
|
206
|
+
|
|
207
|
+
if (p === 'openai') {
|
|
208
|
+
const effort = REASONING_EFFORT_MAP[this.level];
|
|
209
|
+
if (!effort) return null;
|
|
210
|
+
return { reasoning_effort: effort };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (p === 'anthropic' || p === 'claude') {
|
|
214
|
+
const budget = EXTENDED_THINKING_BUDGET_MAP[this.level];
|
|
215
|
+
if (!budget) return null;
|
|
216
|
+
return {
|
|
217
|
+
thinking: {
|
|
218
|
+
type: 'enabled',
|
|
219
|
+
budget_tokens: budget,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get reasoning prompt instructions to inject into the system prompt.
|
|
229
|
+
* ALL models use the unified deep-reasoning templates.
|
|
230
|
+
* @param {string} [modelTier] - Unused, kept for backward compatibility.
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
getPromptInstructions(modelTier) {
|
|
234
|
+
if (!this.enabled || this.providerSupportsApiReasoning) return '';
|
|
235
|
+
return REASONING_PROMPT_TEMPLATES[this.level] || '';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Build a reasoning config for a given provider and task info.
|
|
240
|
+
*/
|
|
241
|
+
static fromTask(taskInfo, provider, options = {}) {
|
|
242
|
+
const level = options.reasoningLevel
|
|
243
|
+
|| (taskInfo ? complexityToReasoningLevel(taskInfo.type) : REASONING_LEVELS.MEDIUM);
|
|
244
|
+
|
|
245
|
+
return new ReasoningConfig({
|
|
246
|
+
level,
|
|
247
|
+
provider: provider || '',
|
|
248
|
+
taskInfo: taskInfo || null,
|
|
249
|
+
...options,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Default reasoning configuration for the system.
|
|
256
|
+
*/
|
|
257
|
+
export const DEFAULT_REASONING_CONFIG = {
|
|
258
|
+
defaultLevel: REASONING_LEVELS.MEDIUM,
|
|
259
|
+
maxBudgetTokens: 16384,
|
|
260
|
+
// Per-provider overrides
|
|
261
|
+
providers: {
|
|
262
|
+
openai: { supports: true, paramType: 'reasoning_effort' },
|
|
263
|
+
anthropic: { supports: true, paramType: 'thinking' },
|
|
264
|
+
claude: { supports: true, paramType: 'thinking' },
|
|
265
|
+
},
|
|
266
|
+
};
|
package/src/cli/repl-commands.js
CHANGED
package/src/cli/repl.js
CHANGED
|
@@ -241,6 +241,7 @@ export class WinterREPL {
|
|
|
241
241
|
// Show banner only if not already shown
|
|
242
242
|
if (!process.env.WINTER_BANNER_SHOWN) {
|
|
243
243
|
console.log(welcomeBanner(this.version, info));
|
|
244
|
+
this.showCommandMenu();
|
|
244
245
|
process.env.WINTER_BANNER_SHOWN = '1';
|
|
245
246
|
} else {
|
|
246
247
|
this.showStatus();
|
|
@@ -315,6 +316,7 @@ export class WinterREPL {
|
|
|
315
316
|
console.log(`${colors.dim}Project: ${this.projectPath}${colors.reset}`);
|
|
316
317
|
console.log(`${colors.dim}Provider: ${this.ai.getActiveProvider()}${colors.reset}`);
|
|
317
318
|
console.log(`${colors.dim}Session: ${this.session.getSessionId().substring(0, 8)}${colors.reset}`);
|
|
319
|
+
console.log(`${colors.dim}Type ${colors.cyan}/help${colors.dim} for commands or ${colors.cyan}/${colors.dim} for menu${colors.reset}`);
|
|
318
320
|
console.log('');
|
|
319
321
|
}
|
|
320
322
|
|
|
@@ -1042,7 +1044,7 @@ ${colors.reset}
|
|
|
1042
1044
|
const currentToolSignature = this.buildToolCallSignature(toolCalls);
|
|
1043
1045
|
if (currentToolSignature && currentToolSignature === lastToolSignature) {
|
|
1044
1046
|
console.log(`
|
|
1045
|
-
${colors.yellow}ℹ AI
|
|
1047
|
+
${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
|
|
1046
1048
|
reachedToolLimit = false;
|
|
1047
1049
|
break;
|
|
1048
1050
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Loader - Auto-discovers and indexes local resources
|
|
3
|
+
* (design systems, agent instructions, skills) for contextual injection.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
13
|
+
const LOCAL_ROOT = path.join(PROJECT_ROOT, 'resources', 'local');
|
|
14
|
+
|
|
15
|
+
// ── Design Systems ──────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const DESIGN_MD_DIR = path.join(LOCAL_ROOT, 'awesome-design-md', 'design-md');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Discover all available design system brands.
|
|
21
|
+
*/
|
|
22
|
+
export async function discoverDesignBrands() {
|
|
23
|
+
try {
|
|
24
|
+
const entries = await fs.readdir(DESIGN_MD_DIR, { withFileTypes: true });
|
|
25
|
+
return entries
|
|
26
|
+
.filter(e => e.isDirectory())
|
|
27
|
+
.map(e => e.name)
|
|
28
|
+
.sort();
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load a DESIGN.md file for a specific brand.
|
|
36
|
+
*/
|
|
37
|
+
export async function loadDesignMd(brand) {
|
|
38
|
+
try {
|
|
39
|
+
const dir = path.join(DESIGN_MD_DIR, brand);
|
|
40
|
+
const files = ['DESIGN.md', 'README.md'];
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const filePath = path.join(dir, file);
|
|
43
|
+
await fs.access(filePath);
|
|
44
|
+
return { brand, file, content: await fs.readFile(filePath, 'utf8') };
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Search design systems by keyword (brand name or description).
|
|
52
|
+
*/
|
|
53
|
+
export async function searchDesignSystems(query) {
|
|
54
|
+
const brands = await discoverDesignBrands();
|
|
55
|
+
const q = query.toLowerCase();
|
|
56
|
+
const matched = brands.filter(b => b.includes(q));
|
|
57
|
+
return matched.slice(0, 10);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Resource Manifest ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
const MANIFEST_PATH = path.join(LOCAL_ROOT, 'manifest.json');
|
|
63
|
+
|
|
64
|
+
export async function loadResourceManifest() {
|
|
65
|
+
try {
|
|
66
|
+
const raw = await fs.readFile(MANIFEST_PATH, 'utf8');
|
|
67
|
+
return JSON.parse(raw);
|
|
68
|
+
} catch {
|
|
69
|
+
return { localResources: [] };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Context Builder ─────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build a concise context summary of all available local resources.
|
|
77
|
+
* Used for automatic injection into system prompts.
|
|
78
|
+
*/
|
|
79
|
+
export async function buildResourceContext() {
|
|
80
|
+
const manifest = await loadResourceManifest();
|
|
81
|
+
const resources = manifest.localResources || [];
|
|
82
|
+
const designBrands = await discoverDesignBrands();
|
|
83
|
+
|
|
84
|
+
const parts = [];
|
|
85
|
+
|
|
86
|
+
if (resources.length > 0) {
|
|
87
|
+
parts.push('## Local Resources');
|
|
88
|
+
resources.forEach(r => {
|
|
89
|
+
parts.push(` - ${r.name}: ${r.fileCount} files, ${(r.size / 1024).toFixed(0)}KB`);
|
|
90
|
+
});
|
|
91
|
+
parts.push('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (designBrands.length > 0) {
|
|
95
|
+
const brandsStr = designBrands.slice(0, 40).join(', ');
|
|
96
|
+
const leftover = designBrands.length - 40;
|
|
97
|
+
parts.push(`## Design Systems (${designBrands.length} available)`);
|
|
98
|
+
parts.push(` ${brandsStr}${leftover > 0 ? `, +${leftover} more` : ''}`);
|
|
99
|
+
parts.push('');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return parts.join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a relevant DESIGN.md content based on the task description.
|
|
107
|
+
* Uses keyword matching between the task text and design system brand names.
|
|
108
|
+
*/
|
|
109
|
+
export async function getRelevantDesignGuide(taskText) {
|
|
110
|
+
if (!taskText) return null;
|
|
111
|
+
|
|
112
|
+
const brands = await discoverDesignBrands();
|
|
113
|
+
const text = taskText.toLowerCase();
|
|
114
|
+
|
|
115
|
+
// Match by brand name in task text
|
|
116
|
+
for (const brand of brands) {
|
|
117
|
+
if (text.includes(brand)) {
|
|
118
|
+
const design = await loadDesignMd(brand);
|
|
119
|
+
if (design) return design;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Match by context clues (e.g., "design", "ui", "looks like", "brand")
|
|
124
|
+
const designHint = /\b(design|ui|looks? like|brand guide|style guide|make it look)\b/i.test(text);
|
|
125
|
+
if (designHint && brands.length > 0) {
|
|
126
|
+
// Return the first few brands as options
|
|
127
|
+
return {
|
|
128
|
+
brand: null,
|
|
129
|
+
type: 'design_hint',
|
|
130
|
+
brands: brands.slice(0, 5),
|
|
131
|
+
note: 'Design-related task detected. Available design systems listed above.',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
package/src/context/router.js
CHANGED
|
@@ -11,26 +11,83 @@ function flattenMessageText(messages) {
|
|
|
11
11
|
: String(messages || '').toLowerCase();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const providerNames = Object.keys(providers).filter(name => providers[name]?.ready || providers[name]?.model);
|
|
17
|
-
const hasProvider = name => providerNames.includes(name);
|
|
14
|
+
import { ReasoningConfig, REASONING_LEVELS } from '../ai/reasoning.js';
|
|
15
|
+
import { classifyModelTier, isSmallModel, getReasoningBump, MODEL_TIERS } from '../ai/model-capabilities.js';
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Bump reasoning level by N steps.
|
|
19
|
+
*/
|
|
20
|
+
function bumpReasoningLevel(level, steps) {
|
|
21
|
+
const order = [REASONING_LEVELS.NONE, REASONING_LEVELS.LOW, REASONING_LEVELS.MEDIUM, REASONING_LEVELS.HIGH, REASONING_LEVELS.MAX];
|
|
22
|
+
const idx = order.indexOf(level);
|
|
23
|
+
if (idx === -1) return level;
|
|
24
|
+
const newIdx = Math.min(idx + steps, order.length - 1);
|
|
25
|
+
return order[newIdx];
|
|
26
|
+
} export function selectExecutionProfile({ messages = [], activeProvider = null, providers = {}, options = {} } = {}) {
|
|
27
|
+
const text = flattenMessageText(messages);
|
|
28
|
+
const providerNames = Object.keys(providers).filter(name => providers[name]?.ready || providers[name]?.model);
|
|
29
|
+
const hasProvider = name => providerNames.includes(name);
|
|
30
|
+
|
|
31
|
+
const explicitProvider = options.provider && hasProvider(options.provider) ? options.provider : null;
|
|
32
|
+
let provider = explicitProvider || (activeProvider && hasProvider(activeProvider) ? activeProvider : providerNames[0] || null);
|
|
33
|
+
|
|
34
|
+
if (explicitProvider) {
|
|
35
|
+
provider = explicitProvider;
|
|
36
|
+
} else if (/\b(review|refactor|debug|fix|bug|error|stack trace|test|tool|patch|code)\b/.test(text) && hasProvider('claude')) {
|
|
37
|
+
provider = 'claude';
|
|
38
|
+
} else if (/\b(summary|summarize|commit message|changelog|docs|explain|rewrite)\b/.test(text) && hasProvider('openai')) {
|
|
39
|
+
provider = 'openai';
|
|
40
|
+
} else if (/\b(local|offline|privacy|private|on-device)\b/.test(text) && hasProvider('ollama')) {
|
|
41
|
+
provider = 'ollama';
|
|
42
|
+
} else if (/\b(quick|brief|short|fast)\b/.test(text) && hasProvider('groq')) {
|
|
43
|
+
provider = 'groq';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const providerConfig = providers[provider] || providers[activeProvider] || {};
|
|
47
|
+
const model = options.model || providerConfig.model || providers[activeProvider]?.model || null;
|
|
48
|
+
|
|
49
|
+
// Detect model capability tier
|
|
50
|
+
const modelTier = classifyModelTier(model, provider);
|
|
51
|
+
const isSmall = isSmallModel(modelTier);
|
|
52
|
+
const reasoningBump = getReasoningBump(modelTier);
|
|
53
|
+
|
|
54
|
+
// Determine reasoning level based on task complexity signals
|
|
55
|
+
// Default: HIGH for coding — all models must think deeply
|
|
56
|
+
let reasoningLevel = options.reasoningLevel || REASONING_LEVELS.HIGH;
|
|
57
|
+
if (!options.reasoningLevel) {
|
|
58
|
+
const hasDeepSignals = /\b(refactor|architecture|redesign|migrate|complex|full stack|e2e|end to end|security|optimize|performance|implement|build|create)\b/.test(text);
|
|
59
|
+
const hasComplexSignals = /\b(debug|fix|test|multiple|integrate|design|plan|review|analyze)\b/.test(text);
|
|
60
|
+
|
|
61
|
+
if (hasDeepSignals && text.length > 30) {
|
|
62
|
+
reasoningLevel = REASONING_LEVELS.MAX;
|
|
63
|
+
} else if (hasComplexSignals && text.length > 20) {
|
|
64
|
+
reasoningLevel = REASONING_LEVELS.MAX;
|
|
65
|
+
} else if (text.split(/\s+/).length > 10) {
|
|
66
|
+
reasoningLevel = REASONING_LEVELS.HIGH;
|
|
67
|
+
} else if (text.split(/\s+/).length < 3) {
|
|
68
|
+
reasoningLevel = REASONING_LEVELS.MEDIUM;
|
|
69
|
+
} else {
|
|
70
|
+
reasoningLevel = REASONING_LEVELS.HIGH;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If small model, bump reasoning level even more to compensate
|
|
74
|
+
if (isSmall && reasoningBump > 0) {
|
|
75
|
+
reasoningLevel = bumpReasoningLevel(reasoningLevel, reasoningBump);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
33
78
|
|
|
34
|
-
const
|
|
35
|
-
|
|
79
|
+
const reasoning = new ReasoningConfig({
|
|
80
|
+
level: reasoningLevel,
|
|
81
|
+
provider: provider || activeProvider,
|
|
82
|
+
modelTier,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
provider,
|
|
87
|
+
model,
|
|
88
|
+
modelTier,
|
|
89
|
+
reasoningLevel,
|
|
90
|
+
reasoningParam: reasoning.getApiReasoningParam(),
|
|
91
|
+
reasoningPrompt: reasoning.getPromptInstructions(),
|
|
92
|
+
};
|
|
36
93
|
}
|