winter-super-cli 2026.6.19 → 2026.6.20

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.
@@ -11,6 +11,7 @@ import SuccessCriteria from './prompts/success-criteria.js';
11
11
  import { ReasoningConfig, REASONING_LEVELS, complexityToReasoningLevel } from './reasoning.js';
12
12
  import { buildResourceContext, getRelevantDesignGuide } from '../context/resource-loader.js';
13
13
  import { classifyModelTier } from './model-capabilities.js';
14
+ import { buildProviderRequest, normalizeProviderResponse, normalizeProviderStreamChunk, getProviderPreset } from './provider-adapters.js';
14
15
 
15
16
  const RESERVED_CONFIG_SECTIONS = new Set([
16
17
  'analytics',
@@ -117,30 +118,50 @@ export class AIProviderManager {
117
118
  // Load auth token from Claude Code's auth.json if available
118
119
  this.authToken = await this.loadAuthToken();
119
120
 
120
- if (claudeConfig?.baseURL || this.authToken) {
121
+ if (claudeConfig?.baseURL || claudeConfig?.apiKey || this.authToken) {
122
+ const claudeIsNativeAnthropic = Boolean(claudeConfig?.apiFormat === 'anthropic' || claudeConfig?.native === true);
123
+ const anthropicPreset = getProviderPreset('anthropic');
121
124
  this.providers.claude = {
122
- name: 'Claude-compatible API',
123
- baseURL: claudeConfig?.baseURL || 'http://localhost:4000/v1',
125
+ name: claudeIsNativeAnthropic ? 'Anthropic API' : 'Claude-compatible API',
126
+ providerName: claudeIsNativeAnthropic ? 'anthropic' : 'claude',
127
+ apiFormat: claudeConfig?.apiFormat || (claudeIsNativeAnthropic ? 'anthropic' : 'openai'),
128
+ baseURL: claudeConfig?.baseURL || (claudeIsNativeAnthropic ? anthropicPreset.baseURL : 'http://localhost:4000/v1'),
124
129
  authToken: this.authToken,
125
130
  apiKey: claudeConfig?.apiKey,
126
- model: claudeConfig?.model || 'nvidia/moonshotai/kimi-k2.6',
131
+ model: claudeConfig?.model || (claudeIsNativeAnthropic ? anthropicPreset.model : 'nvidia/moonshotai/kimi-k2.6'),
127
132
  ready: !!this.authToken || !!claudeConfig?.apiKey || claudeConfig?.apiKey === 'not-required',
128
133
  };
129
134
  }
130
135
 
131
- if (cfg.custom?.baseURL) {
136
+ const customConfig = cfg.custom || null;
137
+ if (this.isProviderConfigSection('custom', customConfig)) {
138
+ const customApiFormat = customConfig.apiFormat || customConfig.format || 'openai';
139
+ const customPreset = getProviderPreset(customApiFormat) || null;
140
+ const customBaseURL = customConfig.baseURL
141
+ || customPreset?.baseURL
142
+ || (customApiFormat === 'anthropic'
143
+ ? getProviderPreset('anthropic')?.baseURL
144
+ : customApiFormat === 'gemini'
145
+ ? getProviderPreset('gemini')?.baseURL
146
+ : 'http://localhost:4000/v1');
147
+
132
148
  this.providers.custom = {
133
- name: 'Custom API',
134
- baseURL: cfg.custom.baseURL,
135
- apiKey: cfg.custom.apiKey || 'not-required',
136
- model: cfg.custom.model || 'gpt-4-turbo',
137
- ready: true,
149
+ name: customConfig.name || 'Custom API',
150
+ providerName: 'custom',
151
+ apiFormat: customApiFormat,
152
+ baseURL: customBaseURL,
153
+ authToken: customConfig.authToken,
154
+ apiKey: customConfig.apiKey || 'not-required',
155
+ model: customConfig.model || customPreset?.model || 'gpt-4-turbo',
156
+ ready: Boolean(customConfig.authToken || customConfig.apiKey || customConfig.baseURL || customPreset?.baseURL),
138
157
  };
139
158
  }
140
159
 
141
160
  if (cfg.ollama?.baseURL) {
142
161
  this.providers.ollama = {
143
162
  name: 'Ollama Local',
163
+ providerName: 'ollama',
164
+ apiFormat: cfg.ollama.apiFormat || 'openai',
144
165
  apiKey: cfg.ollama.apiKey || 'not-required',
145
166
  baseURL: cfg.ollama.baseURL,
146
167
  model: cfg.ollama.model || 'llama3',
@@ -151,6 +172,8 @@ export class AIProviderManager {
151
172
  if (cfg.openai?.apiKey) {
152
173
  this.providers.openai = {
153
174
  name: 'OpenAI',
175
+ providerName: 'openai',
176
+ apiFormat: cfg.openai.apiFormat || 'openai',
154
177
  baseURL: cfg.openai.baseURL || 'https://api.openai.com/v1',
155
178
  apiKey: cfg.openai.apiKey,
156
179
  model: cfg.openai.model || 'gpt-4-turbo',
@@ -161,6 +184,8 @@ export class AIProviderManager {
161
184
  if (cfg.groq?.apiKey) {
162
185
  this.providers.groq = {
163
186
  name: 'Groq',
187
+ providerName: 'groq',
188
+ apiFormat: cfg.groq.apiFormat || 'openai',
164
189
  baseURL: cfg.groq.baseURL || 'https://api.groq.com/openai/v1',
165
190
  apiKey: cfg.groq.apiKey,
166
191
  model: cfg.groq.model || 'llama-3.1-70b-versatile',
@@ -254,26 +279,41 @@ export class AIProviderManager {
254
279
  if (RESERVED_CONFIG_SECTIONS.has(providerName)) return false;
255
280
  if (providerName === 'anthropic') return false;
256
281
  if (!section || typeof section !== 'object' || Array.isArray(section)) return false;
282
+ if (providerName === 'custom') {
283
+ return Boolean(
284
+ section.baseURL ||
285
+ section.apiKey ||
286
+ section.authToken ||
287
+ section.apiFormat ||
288
+ section.format
289
+ );
290
+ }
257
291
 
258
292
  return Boolean(
259
293
  section.baseURL ||
260
294
  section.apiKey ||
261
- section.authToken
295
+ section.authToken ||
296
+ getProviderPreset(providerName)
262
297
  );
263
298
  }
264
299
 
265
300
  buildProviderFromConfig(providerName, section) {
301
+ const preset = getProviderPreset(providerName);
266
302
  return {
267
- name: this.getProviderDisplayName(providerName),
268
- baseURL: section.baseURL || this.getProviderDefaultBaseURL(providerName),
303
+ name: section.name || preset?.name || this.getProviderDisplayName(providerName),
304
+ providerName,
305
+ apiFormat: section.apiFormat || section.format || preset?.apiFormat || 'openai',
306
+ baseURL: section.baseURL || preset?.baseURL || this.getProviderDefaultBaseURL(providerName),
269
307
  authToken: section.authToken,
270
308
  apiKey: section.apiKey || 'not-required',
271
- model: section.model || this.getProviderDefaultModel(providerName),
272
- ready: Boolean(section.authToken || section.apiKey || section.baseURL),
309
+ model: section.model || preset?.model || this.getProviderDefaultModel(providerName),
310
+ ready: Boolean(section.authToken || section.apiKey || section.baseURL || preset?.baseURL),
273
311
  };
274
312
  }
275
313
 
276
314
  getProviderDisplayName(providerName) {
315
+ const preset = getProviderPreset(providerName);
316
+ if (preset?.name) return preset.name;
277
317
  const labels = {
278
318
  anthropic: 'Claude-compatible API',
279
319
  claude: 'Claude-compatible API',
@@ -286,6 +326,8 @@ export class AIProviderManager {
286
326
  }
287
327
 
288
328
  getProviderDefaultBaseURL(providerName) {
329
+ const preset = getProviderPreset(providerName);
330
+ if (preset?.baseURL) return preset.baseURL;
289
331
  if (providerName === 'openai') return 'https://api.openai.com/v1';
290
332
  if (providerName === 'groq') return 'https://api.groq.com/openai/v1';
291
333
  if (providerName === 'ollama') return 'http://localhost:11434/v1';
@@ -293,6 +335,8 @@ export class AIProviderManager {
293
335
  }
294
336
 
295
337
  getProviderDefaultModel(providerName) {
338
+ const preset = getProviderPreset(providerName);
339
+ if (preset?.model) return preset.model;
296
340
  if (providerName === 'groq') return 'llama-3.1-70b-versatile';
297
341
  if (providerName === 'ollama') return 'llama3';
298
342
  return 'gpt-4-turbo';
@@ -490,46 +534,25 @@ export class AIProviderManager {
490
534
  throw new Error('No active provider is configured');
491
535
  }
492
536
  const timeoutMs = getRequestTimeoutMs(options);
493
-
494
- const body = {
495
- model: options.model || provider.model,
496
- messages,
497
- };
498
-
499
- // Apply reasoning configuration
500
537
  const reasoningParam = options.reasoning || this._getReasoningParam(options, provider);
501
- if (reasoningParam) {
502
- if (reasoningParam.reasoning_effort) {
503
- body.reasoning_effort = reasoningParam.reasoning_effort;
504
- }
505
- if (reasoningParam.thinking) {
506
- body.thinking = reasoningParam.thinking;
507
- }
508
- }
509
-
510
- if (this.tools.length > 0 && options.enableTools && !options.toolPromptOnly) {
511
- const tools = this.normalizeToolDefinitionsForApi(this.tools);
512
- if (tools.length > 0) body.tools = tools;
513
- }
514
-
515
- const headers = {
516
- 'Content-Type': 'application/json',
517
- 'Accept': 'application/json',
518
- };
519
-
520
- if (provider.authToken) {
521
- headers['Authorization'] = `Bearer ${provider.authToken}`;
522
- } else if (provider.apiKey && provider.apiKey !== 'not-required') {
523
- headers['Authorization'] = `Bearer ${provider.apiKey}`;
524
- }
538
+ const tools = this.tools.length > 0 && options.enableTools && !options.toolPromptOnly
539
+ ? this.normalizeToolDefinitionsForApi(this.tools)
540
+ : [];
541
+ const request = buildProviderRequest(provider, messages, {
542
+ ...options,
543
+ stream: false,
544
+ model: options.model || provider.model,
545
+ reasoning: reasoningParam,
546
+ tools,
547
+ });
525
548
 
526
549
  const timeout = createTimeoutSignal(timeoutMs, options.signal || options.abortSignal);
527
550
  let response;
528
551
  try {
529
- response = await fetch(`${provider.baseURL}/chat/completions`, {
552
+ response = await fetch(request.url, {
530
553
  method: 'POST',
531
- headers,
532
- body: JSON.stringify(body),
554
+ headers: request.headers,
555
+ body: JSON.stringify(request.body),
533
556
  signal: timeout.signal,
534
557
  });
535
558
  } catch (error) {
@@ -545,7 +568,7 @@ export class AIProviderManager {
545
568
  throw requestError;
546
569
  }
547
570
 
548
- return await response.json();
571
+ return normalizeProviderResponse(provider, await response.json());
549
572
  }
550
573
 
551
574
  async *streamRequestToProvider(provider, messages, options = {}) {
@@ -553,51 +576,25 @@ export class AIProviderManager {
553
576
  throw new Error('No active provider is configured');
554
577
  }
555
578
  const timeoutMs = getRequestTimeoutMs(options);
556
-
557
- const body = {
558
- model: options.model || provider.model,
559
- messages,
560
- stream: true,
561
- };
562
-
563
- if (options.includeUsage !== false) {
564
- body.stream_options = { include_usage: true };
565
- }
566
-
567
- // Apply reasoning configuration
568
579
  const reasoningParam = options.reasoning || this._getReasoningParam(options, provider);
569
- if (reasoningParam) {
570
- if (reasoningParam.reasoning_effort) {
571
- body.reasoning_effort = reasoningParam.reasoning_effort;
572
- }
573
- if (reasoningParam.thinking) {
574
- body.thinking = reasoningParam.thinking;
575
- }
576
- }
577
-
578
- if (this.tools.length > 0 && options.enableTools && !options.toolPromptOnly) {
579
- const tools = this.normalizeToolDefinitionsForApi(this.tools);
580
- if (tools.length > 0) body.tools = tools;
581
- }
582
-
583
- const headers = {
584
- 'Content-Type': 'application/json',
585
- 'Accept': 'text/event-stream',
586
- };
587
-
588
- if (provider.authToken) {
589
- headers['Authorization'] = `Bearer ${provider.authToken}`;
590
- } else if (provider.apiKey && provider.apiKey !== 'not-required') {
591
- headers['Authorization'] = `Bearer ${provider.apiKey}`;
592
- }
580
+ const tools = this.tools.length > 0 && options.enableTools && !options.toolPromptOnly
581
+ ? this.normalizeToolDefinitionsForApi(this.tools)
582
+ : [];
583
+ const request = buildProviderRequest(provider, messages, {
584
+ ...options,
585
+ stream: true,
586
+ model: options.model || provider.model,
587
+ reasoning: reasoningParam,
588
+ tools,
589
+ });
593
590
 
594
591
  const timeout = createTimeoutSignal(timeoutMs, options.signal || options.abortSignal);
595
592
  let response;
596
593
  try {
597
- response = await fetch(`${provider.baseURL}/chat/completions`, {
594
+ response = await fetch(request.url, {
598
595
  method: 'POST',
599
- headers,
600
- body: JSON.stringify(body),
596
+ headers: request.headers,
597
+ body: JSON.stringify(request.body),
601
598
  signal: timeout.signal,
602
599
  });
603
600
 
@@ -640,14 +637,10 @@ export class AIProviderManager {
640
637
  continue;
641
638
  }
642
639
 
643
- const choice = data.choices?.[0] || {};
644
- const content = choice.delta?.content ?? choice.message?.content ?? choice.text ?? '';
640
+ const chunk = normalizeProviderStreamChunk(provider, data);
641
+ if (!chunk.content && !chunk.usage && !chunk.raw) continue;
645
642
  yieldedAny = true;
646
- yield {
647
- content,
648
- usage: data.usage,
649
- raw: data,
650
- };
643
+ yield chunk;
651
644
  }
652
645
  }
653
646
 
@@ -657,13 +650,11 @@ export class AIProviderManager {
657
650
  if (payload && payload !== '[DONE]') {
658
651
  try {
659
652
  const data = JSON.parse(payload);
660
- const choice = data.choices?.[0] || {};
661
- yieldedAny = true;
662
- yield {
663
- content: choice.delta?.content ?? choice.message?.content ?? choice.text ?? '',
664
- usage: data.usage,
665
- raw: data,
666
- };
653
+ const chunk = normalizeProviderStreamChunk(provider, data);
654
+ if (chunk.content || chunk.usage || chunk.raw) {
655
+ yieldedAny = true;
656
+ yield chunk;
657
+ }
667
658
  } catch {}
668
659
  }
669
660
  }
@@ -736,27 +727,18 @@ export class AIProviderManager {
736
727
  while (iterations < maxIterations) {
737
728
  iterations++;
738
729
 
739
- const body = {
730
+ const tools = this.tools.length > 0 ? this.normalizeToolDefinitionsForApi(this.tools) : [];
731
+ const request = buildProviderRequest(provider, currentMessages, {
732
+ ...options,
733
+ stream: false,
740
734
  model: options.model || provider.model,
741
- messages: currentMessages,
742
- tools: this.tools.length > 0 ? this.normalizeToolDefinitionsForApi(this.tools) : undefined,
743
- };
744
-
745
- const headers = {
746
- 'Content-Type': 'application/json',
747
- 'Accept': 'application/json',
748
- };
749
-
750
- if (provider.authToken) {
751
- headers['Authorization'] = `Bearer ${provider.authToken}`;
752
- } else if (provider.apiKey && provider.apiKey !== 'not-required') {
753
- headers['Authorization'] = `Bearer ${provider.apiKey}`;
754
- }
735
+ tools,
736
+ });
755
737
 
756
- const response = await fetch(`${provider.baseURL}/chat/completions`, {
738
+ const response = await fetch(request.url, {
757
739
  method: 'POST',
758
- headers,
759
- body: JSON.stringify(body),
740
+ headers: request.headers,
741
+ body: JSON.stringify(request.body),
760
742
  });
761
743
 
762
744
  if (!response.ok) {
@@ -764,7 +746,7 @@ export class AIProviderManager {
764
746
  throw new Error(`${this.activeProvider} error (${response.status}): ${error}`);
765
747
  }
766
748
 
767
- const data = await response.json();
749
+ const data = normalizeProviderResponse(provider, await response.json());
768
750
  const assistantMsg = data.choices?.[0]?.message;
769
751
 
770
752
  // Check for tool calls