winter-super-cli 2026.6.19 → 2026.6.21
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/README.md +144 -641
- package/package.json +1 -1
- package/src/ai/provider-adapters.js +317 -0
- package/src/ai/providers.js +104 -122
- package/src/cli/repl.js +3484 -3484
- package/src/cli/tool-call-adapter.js +24 -2
- package/src/tools/executor.js +37 -36
package/package.json
CHANGED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal provider request/response adapters.
|
|
3
|
+
*
|
|
4
|
+
* Internal callers use OpenAI-style messages and normalized responses. These helpers
|
|
5
|
+
* translate that shape to native provider APIs where needed while preserving the
|
|
6
|
+
* existing OpenAI-compatible path for custom/local gateways.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const DEFAULT_ANTHROPIC_VERSION = '2023-06-01';
|
|
10
|
+
const DEFAULT_MAX_TOKENS = 4096;
|
|
11
|
+
|
|
12
|
+
export const PROVIDER_PRESETS = {
|
|
13
|
+
openai: { name: 'OpenAI', apiFormat: 'openai', baseURL: 'https://api.openai.com/v1', model: 'gpt-4-turbo' },
|
|
14
|
+
azure: { name: 'Azure OpenAI', apiFormat: 'openai', model: 'gpt-4o' },
|
|
15
|
+
groq: { name: 'Groq', apiFormat: 'openai', baseURL: 'https://api.groq.com/openai/v1', model: 'llama-3.1-70b-versatile' },
|
|
16
|
+
ollama: { name: 'Ollama Local', apiFormat: 'openai', baseURL: 'http://localhost:11434/v1', model: 'llama3' },
|
|
17
|
+
openrouter: { name: 'OpenRouter', apiFormat: 'openai', baseURL: 'https://openrouter.ai/api/v1', model: 'anthropic/claude-3.5-sonnet' },
|
|
18
|
+
together: { name: 'Together AI', apiFormat: 'openai', baseURL: 'https://api.together.xyz/v1', model: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' },
|
|
19
|
+
fireworks: { name: 'Fireworks AI', apiFormat: 'openai', baseURL: 'https://api.fireworks.ai/inference/v1', model: 'accounts/fireworks/models/llama-v3p1-70b-instruct' },
|
|
20
|
+
deepseek: { name: 'DeepSeek', apiFormat: 'openai', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
|
|
21
|
+
mistral: { name: 'Mistral AI', apiFormat: 'openai', baseURL: 'https://api.mistral.ai/v1', model: 'mistral-large-latest' },
|
|
22
|
+
xai: { name: 'xAI', apiFormat: 'openai', baseURL: 'https://api.x.ai/v1', model: 'grok-3' },
|
|
23
|
+
perplexity: { name: 'Perplexity', apiFormat: 'openai', baseURL: 'https://api.perplexity.ai', model: 'sonar-pro' },
|
|
24
|
+
cerebras: { name: 'Cerebras', apiFormat: 'openai', baseURL: 'https://api.cerebras.ai/v1', model: 'llama3.1-70b' },
|
|
25
|
+
siliconflow: { name: 'SiliconFlow', apiFormat: 'openai', baseURL: 'https://api.siliconflow.cn/v1', model: 'Qwen/Qwen2.5-72B-Instruct' },
|
|
26
|
+
zhipu: { name: 'Zhipu AI', apiFormat: 'openai', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-plus' },
|
|
27
|
+
baichuan: { name: 'Baichuan AI', apiFormat: 'openai', baseURL: 'https://api.baichuan-ai.com/v1', model: 'Baichuan4' },
|
|
28
|
+
'01ai': { name: '01.AI', apiFormat: 'openai', baseURL: 'https://api.lingyiwanwu.com/v1', model: 'yi-large' },
|
|
29
|
+
qwen: { name: 'Qwen DashScope', apiFormat: 'openai', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
|
|
30
|
+
dashscope: { name: 'Alibaba DashScope', apiFormat: 'openai', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
|
|
31
|
+
kimi: { name: 'Kimi Moonshot', apiFormat: 'openai', baseURL: 'https://api.moonshot.ai/v1', model: 'kimi-k2-0711-preview' },
|
|
32
|
+
moonshot: { name: 'Moonshot AI', apiFormat: 'openai', baseURL: 'https://api.moonshot.ai/v1', model: 'kimi-k2-0711-preview' },
|
|
33
|
+
minimax: { name: 'MiniMax', apiFormat: 'openai', baseURL: 'https://api.minimax.io/v1', model: 'MiniMax-M1' },
|
|
34
|
+
anthropic: { name: 'Anthropic API', apiFormat: 'anthropic', baseURL: 'https://api.anthropic.com/v1', model: 'claude-3-5-sonnet-latest' },
|
|
35
|
+
claude: { name: 'Claude-compatible API', apiFormat: 'openai', baseURL: 'http://localhost:4000/v1', model: 'nvidia/moonshotai/kimi-k2.6' },
|
|
36
|
+
gemini: { name: 'Google Gemini', apiFormat: 'gemini', baseURL: 'https://generativelanguage.googleapis.com/v1beta', model: 'gemini-1.5-pro' },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function getProviderPreset(providerName) {
|
|
40
|
+
return PROVIDER_PRESETS[String(providerName || '').trim().toLowerCase()] || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveProviderApiFormat(provider = {}) {
|
|
44
|
+
const explicit = String(provider.apiFormat || provider.format || '').trim().toLowerCase();
|
|
45
|
+
if (explicit) return explicit;
|
|
46
|
+
|
|
47
|
+
const providerName = String(provider.providerName || provider.key || provider.name || '').toLowerCase();
|
|
48
|
+
const baseURL = String(provider.baseURL || '').toLowerCase();
|
|
49
|
+
const preset = getProviderPreset(providerName);
|
|
50
|
+
if (preset?.apiFormat) return preset.apiFormat;
|
|
51
|
+
|
|
52
|
+
if (providerName.includes('gemini') || baseURL.includes('generativelanguage.googleapis.com')) return 'gemini';
|
|
53
|
+
if (providerName.includes('anthropic') || baseURL.includes('api.anthropic.com')) return 'anthropic';
|
|
54
|
+
return 'openai';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildProviderRequest(provider, messages = [], options = {}) {
|
|
58
|
+
const apiFormat = resolveProviderApiFormat(provider);
|
|
59
|
+
if (apiFormat === 'anthropic') return buildAnthropicRequest(provider, messages, options);
|
|
60
|
+
if (apiFormat === 'gemini') return buildGeminiRequest(provider, messages, options);
|
|
61
|
+
return buildOpenAIRequest(provider, messages, options);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeProviderResponse(provider, data) {
|
|
65
|
+
const apiFormat = resolveProviderApiFormat(provider);
|
|
66
|
+
if (apiFormat === 'anthropic') return normalizeAnthropicResponse(data);
|
|
67
|
+
if (apiFormat === 'gemini') return normalizeGeminiResponse(data);
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function normalizeProviderStreamChunk(provider, data) {
|
|
72
|
+
const apiFormat = resolveProviderApiFormat(provider);
|
|
73
|
+
if (apiFormat === 'anthropic') return normalizeAnthropicStreamChunk(data);
|
|
74
|
+
if (apiFormat === 'gemini') return normalizeGeminiStreamChunk(data);
|
|
75
|
+
return normalizeOpenAIStreamChunk(data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function extractTextFromResponse(data) {
|
|
79
|
+
return data?.choices?.[0]?.message?.content
|
|
80
|
+
?? data?.choices?.[0]?.text
|
|
81
|
+
?? data?.content
|
|
82
|
+
?? '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function trimTrailingSlash(value = '') {
|
|
86
|
+
return String(value || '').replace(/\/+$/, '');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function splitSystemMessages(messages = []) {
|
|
90
|
+
const system = [];
|
|
91
|
+
const chat = [];
|
|
92
|
+
|
|
93
|
+
for (const message of messages) {
|
|
94
|
+
if (!message) continue;
|
|
95
|
+
if (message.role === 'system') {
|
|
96
|
+
const text = contentToText(message.content);
|
|
97
|
+
if (text) system.push(text);
|
|
98
|
+
} else {
|
|
99
|
+
chat.push(message);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { system: system.join('\n\n'), chat };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function contentToText(content) {
|
|
107
|
+
if (typeof content === 'string') return content;
|
|
108
|
+
if (Array.isArray(content)) {
|
|
109
|
+
return content.map(part => part?.text || part?.image_url?.url || '').filter(Boolean).join('\n');
|
|
110
|
+
}
|
|
111
|
+
if (content == null) return '';
|
|
112
|
+
return String(content);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildOpenAIRequest(provider, messages, options) {
|
|
116
|
+
const body = {
|
|
117
|
+
model: options.model || provider.model,
|
|
118
|
+
messages,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (options.stream) body.stream = true;
|
|
122
|
+
if (options.stream && options.includeUsage !== false) body.stream_options = { include_usage: true };
|
|
123
|
+
|
|
124
|
+
if (options.reasoning?.reasoning_effort) body.reasoning_effort = options.reasoning.reasoning_effort;
|
|
125
|
+
if (options.reasoning?.thinking) body.thinking = options.reasoning.thinking;
|
|
126
|
+
if (options.tools?.length) body.tools = options.tools;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
url: `${trimTrailingSlash(provider.baseURL)}/chat/completions`,
|
|
130
|
+
headers: buildBearerHeaders(provider, options.stream ? 'text/event-stream' : 'application/json'),
|
|
131
|
+
body,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildAnthropicRequest(provider, messages, options) {
|
|
136
|
+
const { system, chat } = splitSystemMessages(messages);
|
|
137
|
+
const body = {
|
|
138
|
+
model: options.model || provider.model,
|
|
139
|
+
max_tokens: options.maxTokens || provider.maxTokens || DEFAULT_MAX_TOKENS,
|
|
140
|
+
messages: chat.map(message => ({
|
|
141
|
+
role: message.role === 'assistant' ? 'assistant' : 'user',
|
|
142
|
+
content: contentToText(message.content),
|
|
143
|
+
})),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (system) body.system = system;
|
|
147
|
+
if (options.stream) body.stream = true;
|
|
148
|
+
if (options.reasoning?.thinking) body.thinking = options.reasoning.thinking;
|
|
149
|
+
if (options.tools?.length) body.tools = options.tools.map(toAnthropicTool).filter(Boolean);
|
|
150
|
+
|
|
151
|
+
const headers = {
|
|
152
|
+
'Content-Type': 'application/json',
|
|
153
|
+
'Accept': options.stream ? 'text/event-stream' : 'application/json',
|
|
154
|
+
'anthropic-version': provider.anthropicVersion || DEFAULT_ANTHROPIC_VERSION,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const apiKey = provider.apiKey && provider.apiKey !== 'not-required' ? provider.apiKey : provider.authToken;
|
|
158
|
+
if (apiKey) headers['x-api-key'] = apiKey;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
url: `${trimTrailingSlash(provider.baseURL || 'https://api.anthropic.com/v1')}/messages`,
|
|
162
|
+
headers,
|
|
163
|
+
body,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function buildGeminiRequest(provider, messages, options) {
|
|
168
|
+
const { system, chat } = splitSystemMessages(messages);
|
|
169
|
+
const model = options.model || provider.model;
|
|
170
|
+
const body = {
|
|
171
|
+
contents: chat.map(message => ({
|
|
172
|
+
role: message.role === 'assistant' ? 'model' : 'user',
|
|
173
|
+
parts: [{ text: contentToText(message.content) }],
|
|
174
|
+
})),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (system) body.systemInstruction = { parts: [{ text: system }] };
|
|
178
|
+
if (options.tools?.length) {
|
|
179
|
+
body.tools = [{ functionDeclarations: options.tools.map(toGeminiFunctionDeclaration).filter(Boolean) }]
|
|
180
|
+
.filter(tool => tool.functionDeclarations.length > 0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };
|
|
184
|
+
if (provider.authToken) headers.Authorization = `Bearer ${provider.authToken}`;
|
|
185
|
+
|
|
186
|
+
const baseURL = trimTrailingSlash(provider.baseURL || 'https://generativelanguage.googleapis.com/v1beta');
|
|
187
|
+
const endpoint = options.stream ? 'streamGenerateContent' : 'generateContent';
|
|
188
|
+
const key = provider.apiKey && provider.apiKey !== 'not-required'
|
|
189
|
+
? `?key=${encodeURIComponent(provider.apiKey)}`
|
|
190
|
+
: '';
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
url: `${baseURL}/models/${encodeURIComponent(model)}:${endpoint}${key}`,
|
|
194
|
+
headers,
|
|
195
|
+
body,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function buildBearerHeaders(provider, accept) {
|
|
200
|
+
const headers = {
|
|
201
|
+
'Content-Type': 'application/json',
|
|
202
|
+
'Accept': accept,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
if (provider.authToken) {
|
|
206
|
+
headers.Authorization = `Bearer ${provider.authToken}`;
|
|
207
|
+
} else if (provider.apiKey && provider.apiKey !== 'not-required') {
|
|
208
|
+
headers.Authorization = `Bearer ${provider.apiKey}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return headers;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function toAnthropicTool(tool) {
|
|
215
|
+
const fn = tool?.function || tool;
|
|
216
|
+
if (!fn?.name) return null;
|
|
217
|
+
return {
|
|
218
|
+
name: fn.name,
|
|
219
|
+
description: fn.description || '',
|
|
220
|
+
input_schema: fn.parameters || { type: 'object', properties: {} },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function toGeminiFunctionDeclaration(tool) {
|
|
225
|
+
const fn = tool?.function || tool;
|
|
226
|
+
if (!fn?.name) return null;
|
|
227
|
+
return {
|
|
228
|
+
name: fn.name,
|
|
229
|
+
description: fn.description || '',
|
|
230
|
+
parameters: fn.parameters || { type: 'object', properties: {} },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function normalizeAnthropicResponse(data = {}) {
|
|
235
|
+
const content = Array.isArray(data.content)
|
|
236
|
+
? data.content.map(part => part?.text || '').join('')
|
|
237
|
+
: '';
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
id: data.id,
|
|
241
|
+
model: data.model,
|
|
242
|
+
usage: data.usage,
|
|
243
|
+
choices: [{
|
|
244
|
+
message: {
|
|
245
|
+
role: 'assistant',
|
|
246
|
+
content,
|
|
247
|
+
},
|
|
248
|
+
finish_reason: data.stop_reason || null,
|
|
249
|
+
}],
|
|
250
|
+
raw: data,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function normalizeGeminiResponse(data = {}) {
|
|
255
|
+
const candidate = data.candidates?.[0] || {};
|
|
256
|
+
const content = Array.isArray(candidate.content?.parts)
|
|
257
|
+
? candidate.content.parts.map(part => part?.text || '').join('')
|
|
258
|
+
: '';
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
usage: data.usageMetadata,
|
|
262
|
+
choices: [{
|
|
263
|
+
message: {
|
|
264
|
+
role: 'assistant',
|
|
265
|
+
content,
|
|
266
|
+
},
|
|
267
|
+
finish_reason: candidate.finishReason || null,
|
|
268
|
+
}],
|
|
269
|
+
raw: data,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizeOpenAIStreamChunk(data = {}) {
|
|
274
|
+
const choice = data.choices?.[0] || {};
|
|
275
|
+
return {
|
|
276
|
+
content: choice.delta?.content ?? choice.message?.content ?? choice.text ?? '',
|
|
277
|
+
usage: data.usage,
|
|
278
|
+
raw: data,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function normalizeAnthropicStreamChunk(data = {}) {
|
|
283
|
+
if (data.type === 'content_block_delta') {
|
|
284
|
+
return {
|
|
285
|
+
content: data.delta?.text || '',
|
|
286
|
+
usage: undefined,
|
|
287
|
+
raw: data,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (data.type === 'message_delta') {
|
|
292
|
+
return {
|
|
293
|
+
content: '',
|
|
294
|
+
usage: data.usage,
|
|
295
|
+
raw: data,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (data.type === 'message_stop') {
|
|
300
|
+
return { content: '', usage: undefined, raw: data };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return normalizeOpenAIStreamChunk(data);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function normalizeGeminiStreamChunk(data = {}) {
|
|
307
|
+
const candidate = data.candidates?.[0] || {};
|
|
308
|
+
const content = Array.isArray(candidate.content?.parts)
|
|
309
|
+
? candidate.content.parts.map(part => part?.text || '').join('')
|
|
310
|
+
: '';
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
content,
|
|
314
|
+
usage: data.usageMetadata,
|
|
315
|
+
raw: data,
|
|
316
|
+
};
|
|
317
|
+
}
|