cowork-os 0.3.21 → 0.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +293 -6
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/daemon.js +25 -0
  48. package/dist/electron/electron/agent/executor.js +181 -26
  49. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  50. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  51. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  52. package/dist/electron/electron/agent/llm/index.js +11 -1
  53. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  54. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  55. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  56. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  57. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  58. package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
  59. package/dist/electron/electron/agent/llm/types.js +66 -1
  60. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  61. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  62. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  63. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  64. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  65. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  66. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  67. package/dist/electron/electron/agent/tools/registry.js +541 -0
  68. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  69. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  70. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  71. package/dist/electron/electron/gateway/index.js +1 -0
  72. package/dist/electron/electron/gateway/router.js +123 -143
  73. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  74. package/dist/electron/electron/ipc/handlers.js +627 -158
  75. package/dist/electron/electron/main.js +63 -0
  76. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  77. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  78. package/dist/electron/electron/memory/MemoryService.js +1 -1
  79. package/dist/electron/electron/preload.js +74 -1
  80. package/dist/electron/electron/settings/box-manager.js +54 -0
  81. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  82. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  83. package/dist/electron/electron/settings/notion-manager.js +56 -0
  84. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  85. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  86. package/dist/electron/electron/utils/box-api.js +153 -0
  87. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  88. package/dist/electron/electron/utils/env-migration.js +19 -0
  89. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  90. package/dist/electron/electron/utils/notion-api.js +103 -0
  91. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  92. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  93. package/dist/electron/electron/utils/validation.js +82 -3
  94. package/dist/electron/electron/utils/x-cli.js +1 -1
  95. package/dist/electron/shared/channelMessages.js +284 -3
  96. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  97. package/dist/electron/shared/types.js +88 -1
  98. package/package.json +12 -2
  99. package/src/electron/agent/executor.ts +205 -28
  100. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  101. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  102. package/src/electron/agent/llm/groq-provider.ts +39 -0
  103. package/src/electron/agent/llm/index.ts +5 -0
  104. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  105. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  106. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  107. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  108. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  109. package/src/electron/agent/llm/provider-factory.ts +414 -6
  110. package/src/electron/agent/llm/types.ts +90 -1
  111. package/src/electron/agent/llm/xai-provider.ts +39 -0
  112. package/src/electron/agent/tools/box-tools.ts +239 -0
  113. package/src/electron/agent/tools/builtin-settings.ts +34 -0
  114. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  115. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  116. package/src/electron/agent/tools/notion-tools.ts +330 -0
  117. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  118. package/src/electron/agent/tools/registry.ts +565 -0
  119. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  120. package/src/electron/agent/tools/shell-tools.ts +11 -3
  121. package/src/electron/agent/tools/x-tools.ts +1 -1
  122. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  123. package/src/electron/gateway/index.ts +1 -0
  124. package/src/electron/gateway/router.ts +134 -149
  125. package/src/electron/ipc/canvas-handlers.ts +10 -0
  126. package/src/electron/ipc/handlers.ts +673 -153
  127. package/src/electron/main.ts +35 -0
  128. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  129. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  130. package/src/electron/memory/MemoryService.ts +5 -1
  131. package/src/electron/preload.ts +167 -4
  132. package/src/electron/settings/box-manager.ts +58 -0
  133. package/src/electron/settings/dropbox-manager.ts +58 -0
  134. package/src/electron/settings/google-drive-manager.ts +58 -0
  135. package/src/electron/settings/notion-manager.ts +60 -0
  136. package/src/electron/settings/onedrive-manager.ts +58 -0
  137. package/src/electron/settings/sharepoint-manager.ts +58 -0
  138. package/src/electron/utils/box-api.ts +184 -0
  139. package/src/electron/utils/dropbox-api.ts +171 -0
  140. package/src/electron/utils/env-migration.ts +22 -0
  141. package/src/electron/utils/google-drive-api.ts +183 -0
  142. package/src/electron/utils/notion-api.ts +126 -0
  143. package/src/electron/utils/onedrive-api.ts +137 -0
  144. package/src/electron/utils/sharepoint-api.ts +132 -0
  145. package/src/electron/utils/validation.ts +102 -1
  146. package/src/electron/utils/x-cli.ts +1 -1
  147. package/src/renderer/App.tsx +20 -2
  148. package/src/renderer/components/BoxSettings.tsx +203 -0
  149. package/src/renderer/components/BrowserView.tsx +101 -0
  150. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  151. package/src/renderer/components/CanvasPreview.tsx +68 -1
  152. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  153. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  154. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  155. package/src/renderer/components/DropboxSettings.tsx +202 -0
  156. package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
  157. package/src/renderer/components/MCPSettings.tsx +56 -0
  158. package/src/renderer/components/MainContent.tsx +270 -34
  159. package/src/renderer/components/NotionSettings.tsx +231 -0
  160. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  161. package/src/renderer/components/OnboardingModal.tsx +70 -1
  162. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  163. package/src/renderer/components/Settings.tsx +611 -8
  164. package/src/renderer/components/SharePointSettings.tsx +224 -0
  165. package/src/renderer/components/Sidebar.tsx +25 -9
  166. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  167. package/src/renderer/styles/index.css +438 -25
  168. package/src/shared/channelMessages.ts +367 -4
  169. package/src/shared/llm-provider-catalog.ts +217 -0
  170. package/src/shared/types.ts +226 -1
@@ -16,11 +16,114 @@ import { OllamaProvider } from './ollama-provider';
16
16
  import { GeminiProvider } from './gemini-provider';
17
17
  import { OpenRouterProvider } from './openrouter-provider';
18
18
  import { OpenAIProvider } from './openai-provider';
19
+ import { GroqProvider } from './groq-provider';
20
+ import { XAIProvider } from './xai-provider';
21
+ import { KimiProvider } from './kimi-provider';
22
+ import { AnthropicCompatibleProvider } from './anthropic-compatible-provider';
23
+ import { OpenAICompatibleProvider } from './openai-compatible-provider';
24
+ import { GitHubCopilotProvider } from './github-copilot-provider';
19
25
  import { SecureSettingsRepository } from '../../database/SecureSettingsRepository';
26
+ import {
27
+ CUSTOM_PROVIDER_CATALOG,
28
+ CUSTOM_PROVIDER_MAP,
29
+ CUSTOM_PROVIDER_IDS,
30
+ type ProviderCatalogEntry,
31
+ } from '../../../shared/llm-provider-catalog';
32
+ import type { CustomProviderConfig } from '../../../shared/types';
20
33
 
21
34
  const LEGACY_SETTINGS_FILE = 'llm-settings.json';
22
35
  const MASKED_VALUE = '***configured***';
23
36
  const ENCRYPTED_PREFIX = 'encrypted:';
37
+ const CUSTOM_PROVIDER_ALIASES: Partial<Record<LLMProviderType, LLMProviderType>> = {
38
+ 'kimi-coding': 'kimi-code',
39
+ };
40
+
41
+ function resolveCustomProviderId(providerType: LLMProviderType): LLMProviderType {
42
+ return CUSTOM_PROVIDER_ALIASES[providerType] || providerType;
43
+ }
44
+
45
+ function getCustomProviderEntry(providerType: LLMProviderType): ProviderCatalogEntry | undefined {
46
+ return CUSTOM_PROVIDER_MAP.get(resolveCustomProviderId(providerType));
47
+ }
48
+
49
+ function getCustomProviderConfig(
50
+ customProviders: Record<string, CustomProviderConfig> | undefined,
51
+ providerType: LLMProviderType
52
+ ): CustomProviderConfig | undefined {
53
+ if (!customProviders) return undefined;
54
+ const resolved = resolveCustomProviderId(providerType);
55
+ const resolvedConfig = customProviders[resolved];
56
+ if (resolvedConfig) {
57
+ return resolvedConfig;
58
+ }
59
+ const fallbackConfig = customProviders[providerType];
60
+ if (fallbackConfig && resolved !== providerType) {
61
+ console.log(`[LLMProviderFactory] Custom provider config not found for "${resolved}", falling back to "${providerType}".`);
62
+ }
63
+ return fallbackConfig;
64
+ }
65
+
66
+ function isCustomProviderConfigured(
67
+ entry: ProviderCatalogEntry,
68
+ config?: CustomProviderConfig
69
+ ): boolean {
70
+ if (!config) return false;
71
+ const hasApiKey = !!config.apiKey?.trim();
72
+ const hasBaseUrl = !!config.baseUrl?.trim() || !!entry.baseUrl;
73
+ const hasUserConfig = hasApiKey || !!config.baseUrl?.trim() || !!config.model?.trim();
74
+
75
+ if (!hasUserConfig) return false;
76
+
77
+ if (entry.apiKeyOptional) {
78
+ return entry.requiresBaseUrl ? hasBaseUrl : hasApiKey || hasBaseUrl;
79
+ }
80
+
81
+ return entry.requiresBaseUrl ? hasApiKey && hasBaseUrl : hasApiKey;
82
+ }
83
+
84
+ function createCustomProvider(
85
+ config: LLMProviderConfig,
86
+ entry: ProviderCatalogEntry,
87
+ resolvedType: LLMProviderType
88
+ ): LLMProvider {
89
+ if (resolvedType === 'github-copilot') {
90
+ return new GitHubCopilotProvider(config);
91
+ }
92
+
93
+ const apiKey = config.providerApiKey || '';
94
+ const baseUrl = config.providerBaseUrl || entry.baseUrl || '';
95
+
96
+ if (entry.requiresBaseUrl && !baseUrl) {
97
+ throw new Error(`${entry.name} base URL is required. Configure it in Settings.`);
98
+ }
99
+
100
+ if (!apiKey && !entry.apiKeyOptional) {
101
+ throw new Error(`${entry.name} API key is required. Configure it in Settings.`);
102
+ }
103
+
104
+ const model = config.model || entry.defaultModel;
105
+ if (!model) {
106
+ throw new Error(`${entry.name} model is required. Configure it in Settings.`);
107
+ }
108
+
109
+ if (entry.compatibility === 'openai') {
110
+ return new OpenAICompatibleProvider({
111
+ type: resolvedType,
112
+ providerName: entry.name,
113
+ apiKey,
114
+ baseUrl,
115
+ defaultModel: model,
116
+ });
117
+ }
118
+
119
+ return new AnthropicCompatibleProvider({
120
+ type: resolvedType,
121
+ providerName: entry.name,
122
+ apiKey,
123
+ baseUrl,
124
+ defaultModel: model,
125
+ });
126
+ }
24
127
 
25
128
  // ============ Legacy Encryption Functions (for migration only) ============
26
129
  // These functions are only used to decrypt settings from legacy JSON files
@@ -159,6 +262,38 @@ function sanitizeSettings(settings: LLMSettings): LLMSettings {
159
262
  };
160
263
  }
161
264
 
265
+ if (sanitized.groq) {
266
+ sanitized.groq = {
267
+ ...sanitized.groq,
268
+ apiKey: decryptSecret(sanitized.groq.apiKey),
269
+ };
270
+ }
271
+
272
+ if (sanitized.xai) {
273
+ sanitized.xai = {
274
+ ...sanitized.xai,
275
+ apiKey: decryptSecret(sanitized.xai.apiKey),
276
+ };
277
+ }
278
+
279
+ if (sanitized.kimi) {
280
+ sanitized.kimi = {
281
+ ...sanitized.kimi,
282
+ apiKey: decryptSecret(sanitized.kimi.apiKey),
283
+ };
284
+ }
285
+
286
+ if (sanitized.customProviders) {
287
+ const normalized: Record<string, CustomProviderConfig> = {};
288
+ for (const [key, value] of Object.entries(sanitized.customProviders)) {
289
+ normalized[key] = {
290
+ ...value,
291
+ apiKey: decryptSecret(value.apiKey),
292
+ };
293
+ }
294
+ sanitized.customProviders = normalized;
295
+ }
296
+
162
297
  return sanitized;
163
298
  }
164
299
 
@@ -204,6 +339,7 @@ export interface LLMSettings {
204
339
  openrouter?: {
205
340
  apiKey?: string;
206
341
  model?: string;
342
+ baseUrl?: string;
207
343
  };
208
344
  openai?: {
209
345
  apiKey?: string;
@@ -214,12 +350,31 @@ export interface LLMSettings {
214
350
  tokenExpiresAt?: number;
215
351
  authMethod?: 'api_key' | 'oauth';
216
352
  };
353
+ groq?: {
354
+ apiKey?: string;
355
+ model?: string;
356
+ baseUrl?: string;
357
+ };
358
+ xai?: {
359
+ apiKey?: string;
360
+ model?: string;
361
+ baseUrl?: string;
362
+ };
363
+ kimi?: {
364
+ apiKey?: string;
365
+ model?: string;
366
+ baseUrl?: string;
367
+ };
368
+ customProviders?: Record<string, CustomProviderConfig>;
217
369
  // Cached models from API (populated when user refreshes)
218
370
  cachedGeminiModels?: CachedModelInfo[];
219
371
  cachedOpenRouterModels?: CachedModelInfo[];
220
372
  cachedOllamaModels?: CachedModelInfo[];
221
373
  cachedBedrockModels?: CachedModelInfo[];
222
374
  cachedOpenAIModels?: CachedModelInfo[];
375
+ cachedGroqModels?: CachedModelInfo[];
376
+ cachedXaiModels?: CachedModelInfo[];
377
+ cachedKimiModels?: CachedModelInfo[];
223
378
  }
224
379
 
225
380
  const DEFAULT_SETTINGS: LLMSettings = {
@@ -235,6 +390,22 @@ export class LLMProviderFactory {
235
390
  private static cachedSettings: LLMSettings | null = null;
236
391
  private static migrationCompleted = false;
237
392
 
393
+ private static normalizeCustomProviders(settings: LLMSettings): void {
394
+ if (!settings.customProviders) return;
395
+
396
+ const legacyKey = settings.customProviders['kimi-coding'];
397
+ if (legacyKey && !settings.customProviders['kimi-code']) {
398
+ settings.customProviders['kimi-code'] = legacyKey;
399
+ }
400
+ if (settings.customProviders['kimi-coding']) {
401
+ delete settings.customProviders['kimi-coding'];
402
+ }
403
+
404
+ if (settings.providerType === 'kimi-coding') {
405
+ settings.providerType = 'kimi-code';
406
+ }
407
+ }
408
+
238
409
  /**
239
410
  * Initialize the factory
240
411
  */
@@ -330,6 +501,7 @@ export class LLMProviderFactory {
330
501
  const stored = repository.load<LLMSettings>('llm');
331
502
  if (stored) {
332
503
  settings = { ...DEFAULT_SETTINGS, ...stored };
504
+ this.normalizeCustomProviders(settings);
333
505
  settingsExist = true;
334
506
  }
335
507
  }
@@ -369,6 +541,15 @@ export class LLMProviderFactory {
369
541
  if (settings.openai?.apiKey || settings.openai?.accessToken) {
370
542
  return 'openai';
371
543
  }
544
+ if (settings.groq?.apiKey) {
545
+ return 'groq';
546
+ }
547
+ if (settings.xai?.apiKey) {
548
+ return 'xai';
549
+ }
550
+ if (settings.kimi?.apiKey) {
551
+ return 'kimi';
552
+ }
372
553
  if (settings.bedrock?.accessKeyId || settings.bedrock?.profile) {
373
554
  return 'bedrock';
374
555
  }
@@ -376,6 +557,15 @@ export class LLMProviderFactory {
376
557
  return 'ollama';
377
558
  }
378
559
 
560
+ if (settings.customProviders) {
561
+ for (const entry of CUSTOM_PROVIDER_CATALOG) {
562
+ const config = getCustomProviderConfig(settings.customProviders, entry.id);
563
+ if (isCustomProviderConfigured(entry, config)) {
564
+ return entry.id;
565
+ }
566
+ }
567
+ }
568
+
379
569
  // No valid credentials detected - user needs to configure via Settings
380
570
  return null;
381
571
  }
@@ -418,10 +608,22 @@ export class LLMProviderFactory {
418
608
  static createProvider(overrideConfig?: Partial<LLMProviderConfig>): LLMProvider {
419
609
  const settings = this.loadSettings();
420
610
  const providerType = overrideConfig?.type || settings.providerType;
611
+ const customConfig = getCustomProviderConfig(settings.customProviders, providerType);
421
612
 
422
613
  const config: LLMProviderConfig = {
423
614
  type: providerType,
424
- model: this.getModelId(settings.modelKey, providerType, settings.ollama?.model, settings.gemini?.model, settings.openrouter?.model, settings.openai?.model),
615
+ model: this.getModelId(
616
+ settings.modelKey,
617
+ providerType,
618
+ settings.ollama?.model,
619
+ settings.gemini?.model,
620
+ settings.openrouter?.model,
621
+ settings.openai?.model,
622
+ settings.groq?.model,
623
+ settings.xai?.model,
624
+ settings.kimi?.model,
625
+ settings.customProviders
626
+ ),
425
627
  // Anthropic config - from settings only
426
628
  anthropicApiKey: normalizeSecret(overrideConfig?.anthropicApiKey) || settings.anthropic?.apiKey,
427
629
  // Bedrock config - from settings only
@@ -437,11 +639,24 @@ export class LLMProviderFactory {
437
639
  geminiApiKey: normalizeSecret(overrideConfig?.geminiApiKey) || settings.gemini?.apiKey,
438
640
  // OpenRouter config - from settings only
439
641
  openrouterApiKey: normalizeSecret(overrideConfig?.openrouterApiKey) || settings.openrouter?.apiKey,
642
+ openrouterBaseUrl: overrideConfig?.openrouterBaseUrl || settings.openrouter?.baseUrl,
440
643
  // OpenAI config - from settings only
441
644
  openaiApiKey: normalizeSecret(overrideConfig?.openaiApiKey) || settings.openai?.apiKey,
442
645
  openaiAccessToken: normalizeSecret(overrideConfig?.openaiAccessToken) || settings.openai?.accessToken,
443
646
  openaiRefreshToken: settings.openai?.refreshToken,
444
647
  openaiTokenExpiresAt: settings.openai?.tokenExpiresAt,
648
+ // Groq config - from settings only
649
+ groqApiKey: normalizeSecret(overrideConfig?.groqApiKey) || settings.groq?.apiKey,
650
+ groqBaseUrl: overrideConfig?.groqBaseUrl || settings.groq?.baseUrl,
651
+ // xAI config - from settings only
652
+ xaiApiKey: normalizeSecret(overrideConfig?.xaiApiKey) || settings.xai?.apiKey,
653
+ xaiBaseUrl: overrideConfig?.xaiBaseUrl || settings.xai?.baseUrl,
654
+ // Kimi config - from settings only
655
+ kimiApiKey: normalizeSecret(overrideConfig?.kimiApiKey) || settings.kimi?.apiKey,
656
+ kimiBaseUrl: overrideConfig?.kimiBaseUrl || settings.kimi?.baseUrl,
657
+ // Custom provider config
658
+ providerApiKey: normalizeSecret(overrideConfig?.providerApiKey) || customConfig?.apiKey,
659
+ providerBaseUrl: overrideConfig?.providerBaseUrl || customConfig?.baseUrl,
445
660
  };
446
661
 
447
662
  return this.createProviderFromConfig(config);
@@ -451,6 +666,12 @@ export class LLMProviderFactory {
451
666
  * Create a provider from explicit config
452
667
  */
453
668
  static createProviderFromConfig(config: LLMProviderConfig): LLMProvider {
669
+ const customEntry = getCustomProviderEntry(config.type);
670
+ if (customEntry) {
671
+ const resolvedType = resolveCustomProviderId(config.type);
672
+ return createCustomProvider(config, customEntry, resolvedType);
673
+ }
674
+
454
675
  switch (config.type) {
455
676
  case 'anthropic':
456
677
  return new AnthropicProvider(config);
@@ -464,6 +685,12 @@ export class LLMProviderFactory {
464
685
  return new OpenRouterProvider(config);
465
686
  case 'openai':
466
687
  return new OpenAIProvider(config);
688
+ case 'groq':
689
+ return new GroqProvider(config);
690
+ case 'xai':
691
+ return new XAIProvider(config);
692
+ case 'kimi':
693
+ return new KimiProvider(config);
467
694
  default:
468
695
  throw new Error(`Unknown provider type: ${config.type}`);
469
696
  }
@@ -472,7 +699,24 @@ export class LLMProviderFactory {
472
699
  /**
473
700
  * Get the model ID for a provider
474
701
  */
475
- static getModelId(modelKey: ModelKey | string, providerType: LLMProviderType, ollamaModel?: string, geminiModel?: string, openrouterModel?: string, openaiModel?: string): string {
702
+ static getModelId(
703
+ modelKey: ModelKey | string,
704
+ providerType: LLMProviderType,
705
+ ollamaModel?: string,
706
+ geminiModel?: string,
707
+ openrouterModel?: string,
708
+ openaiModel?: string,
709
+ groqModel?: string,
710
+ xaiModel?: string,
711
+ kimiModel?: string,
712
+ customProviders?: Record<string, CustomProviderConfig>
713
+ ): string {
714
+ const customEntry = getCustomProviderEntry(providerType);
715
+ if (customEntry) {
716
+ const customConfig = getCustomProviderConfig(customProviders, providerType);
717
+ return customConfig?.model || customEntry.defaultModel;
718
+ }
719
+
476
720
  // For Ollama, use the specific Ollama model if provided
477
721
  if (providerType === 'ollama') {
478
722
  return ollamaModel || 'gpt-oss:20b';
@@ -493,6 +737,21 @@ export class LLMProviderFactory {
493
737
  return openaiModel || 'gpt-4o-mini';
494
738
  }
495
739
 
740
+ // For Groq, use the specific model if provided or default
741
+ if (providerType === 'groq') {
742
+ return groqModel || 'llama-3.1-8b-instant';
743
+ }
744
+
745
+ // For xAI, use the specific model if provided or default
746
+ if (providerType === 'xai') {
747
+ return xaiModel || 'grok-4-fast-non-reasoning';
748
+ }
749
+
750
+ // For Kimi, use the specific model if provided or default
751
+ if (providerType === 'kimi') {
752
+ return kimiModel || 'kimi-k2.5';
753
+ }
754
+
496
755
  // For other providers, look up in MODELS
497
756
  const model = MODELS[modelKey as ModelKey];
498
757
  if (!model) {
@@ -529,7 +788,7 @@ export class LLMProviderFactory {
529
788
  }> {
530
789
  const settings = this.loadSettings();
531
790
 
532
- return [
791
+ const builtIns = [
533
792
  {
534
793
  type: 'anthropic' as LLMProviderType,
535
794
  name: 'Anthropic API',
@@ -550,6 +809,21 @@ export class LLMProviderFactory {
550
809
  name: 'OpenAI',
551
810
  configured: !!(settings.openai?.apiKey || settings.openai?.accessToken),
552
811
  },
812
+ {
813
+ type: 'groq' as LLMProviderType,
814
+ name: 'Groq',
815
+ configured: !!settings.groq?.apiKey,
816
+ },
817
+ {
818
+ type: 'xai' as LLMProviderType,
819
+ name: 'xAI (Grok)',
820
+ configured: !!settings.xai?.apiKey,
821
+ },
822
+ {
823
+ type: 'kimi' as LLMProviderType,
824
+ name: 'Kimi',
825
+ configured: !!settings.kimi?.apiKey,
826
+ },
553
827
  {
554
828
  type: 'bedrock' as LLMProviderType,
555
829
  name: 'AWS Bedrock',
@@ -561,6 +835,17 @@ export class LLMProviderFactory {
561
835
  configured: !!(settings.ollama?.baseUrl || settings.ollama?.model),
562
836
  },
563
837
  ];
838
+
839
+ const customProviders = CUSTOM_PROVIDER_CATALOG.map((entry: ProviderCatalogEntry) => {
840
+ const config = getCustomProviderConfig(settings.customProviders, entry.id);
841
+ return {
842
+ type: entry.id,
843
+ name: entry.name,
844
+ configured: isCustomProviderConfigured(entry, config),
845
+ };
846
+ });
847
+
848
+ return [...builtIns, ...customProviders];
564
849
  }
565
850
 
566
851
  /**
@@ -755,11 +1040,13 @@ export class LLMProviderFactory {
755
1040
  /**
756
1041
  * Fetch available OpenRouter models from the API
757
1042
  */
758
- static async getOpenRouterModels(apiKey?: string): Promise<Array<{ id: string; name: string; context_length: number }>> {
1043
+ static async getOpenRouterModels(apiKey?: string, baseUrl?: string): Promise<Array<{ id: string; name: string; context_length: number }>> {
759
1044
  const settings = this.loadSettings();
760
1045
  // Normalize empty strings to undefined
761
1046
  const normalizedApiKey = apiKey?.trim() || undefined;
762
1047
  const key = normalizedApiKey || settings.openrouter?.apiKey;
1048
+ const normalizedBaseUrl = baseUrl?.trim() || undefined;
1049
+ const resolvedBaseUrl = normalizedBaseUrl || settings.openrouter?.baseUrl;
763
1050
 
764
1051
  const defaultModels = [
765
1052
  { id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet', context_length: 200000 },
@@ -780,6 +1067,7 @@ export class LLMProviderFactory {
780
1067
  type: 'openrouter',
781
1068
  model: '',
782
1069
  openrouterApiKey: key,
1070
+ openrouterBaseUrl: resolvedBaseUrl,
783
1071
  });
784
1072
  return await provider.getAvailableModels();
785
1073
  } catch (error: any) {
@@ -871,6 +1159,109 @@ export class LLMProviderFactory {
871
1159
  }
872
1160
  }
873
1161
 
1162
+ /**
1163
+ * Fetch available Groq models from the API
1164
+ */
1165
+ static async getGroqModels(apiKey?: string, baseUrl?: string): Promise<Array<{ id: string; name: string }>> {
1166
+ const settings = this.loadSettings();
1167
+ const normalizedApiKey = apiKey?.trim() || undefined;
1168
+ const key = normalizedApiKey || settings.groq?.apiKey;
1169
+ const normalizedBaseUrl = baseUrl?.trim() || undefined;
1170
+ const resolvedBaseUrl = normalizedBaseUrl || settings.groq?.baseUrl;
1171
+
1172
+ const defaultModels = [
1173
+ { id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B Instant' },
1174
+ { id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B Versatile' },
1175
+ ];
1176
+
1177
+ if (!key) {
1178
+ return defaultModels;
1179
+ }
1180
+
1181
+ try {
1182
+ const provider = new GroqProvider({
1183
+ type: 'groq',
1184
+ model: '',
1185
+ groqApiKey: key,
1186
+ groqBaseUrl: resolvedBaseUrl,
1187
+ });
1188
+ return await provider.getAvailableModels();
1189
+ } catch (error: any) {
1190
+ console.error('Failed to fetch Groq models:', error);
1191
+ return defaultModels;
1192
+ }
1193
+ }
1194
+
1195
+ /**
1196
+ * Fetch available xAI models from the API
1197
+ */
1198
+ static async getXAIModels(apiKey?: string, baseUrl?: string): Promise<Array<{ id: string; name: string }>> {
1199
+ const settings = this.loadSettings();
1200
+ const normalizedApiKey = apiKey?.trim() || undefined;
1201
+ const key = normalizedApiKey || settings.xai?.apiKey;
1202
+ const normalizedBaseUrl = baseUrl?.trim() || undefined;
1203
+ const resolvedBaseUrl = normalizedBaseUrl || settings.xai?.baseUrl;
1204
+
1205
+ const defaultModels = [
1206
+ { id: 'grok-4', name: 'Grok 4' },
1207
+ { id: 'grok-4-fast-non-reasoning', name: 'Grok 4 Fast (Non-Reasoning)' },
1208
+ { id: 'grok-4-fast-reasoning', name: 'Grok 4 Fast (Reasoning)' },
1209
+ ];
1210
+
1211
+ if (!key) {
1212
+ return defaultModels;
1213
+ }
1214
+
1215
+ try {
1216
+ const provider = new XAIProvider({
1217
+ type: 'xai',
1218
+ model: '',
1219
+ xaiApiKey: key,
1220
+ xaiBaseUrl: resolvedBaseUrl,
1221
+ });
1222
+ return await provider.getAvailableModels();
1223
+ } catch (error: any) {
1224
+ console.error('Failed to fetch xAI models:', error);
1225
+ return defaultModels;
1226
+ }
1227
+ }
1228
+
1229
+ /**
1230
+ * Fetch available Kimi models from the API
1231
+ */
1232
+ static async getKimiModels(apiKey?: string, baseUrl?: string): Promise<Array<{ id: string; name: string }>> {
1233
+ const settings = this.loadSettings();
1234
+ const normalizedApiKey = apiKey?.trim() || undefined;
1235
+ const key = normalizedApiKey || settings.kimi?.apiKey;
1236
+ const normalizedBaseUrl = baseUrl?.trim() || undefined;
1237
+ const resolvedBaseUrl = normalizedBaseUrl || settings.kimi?.baseUrl;
1238
+
1239
+ const defaultModels = [
1240
+ { id: 'kimi-k2.5', name: 'Kimi K2.5' },
1241
+ { id: 'kimi-k2-0905-preview', name: 'Kimi K2.5 Preview' },
1242
+ { id: 'kimi-k2-turbo-preview', name: 'Kimi K2 Turbo (Preview)' },
1243
+ { id: 'kimi-k2-thinking', name: 'Kimi K2 Thinking' },
1244
+ { id: 'kimi-k2-thinking-turbo', name: 'Kimi K2 Thinking Turbo' },
1245
+ ];
1246
+
1247
+ if (!key) {
1248
+ return defaultModels;
1249
+ }
1250
+
1251
+ try {
1252
+ const provider = new KimiProvider({
1253
+ type: 'kimi',
1254
+ model: '',
1255
+ kimiApiKey: key,
1256
+ kimiBaseUrl: resolvedBaseUrl,
1257
+ });
1258
+ return await provider.getAvailableModels();
1259
+ } catch (error: any) {
1260
+ console.error('Failed to fetch Kimi models:', error);
1261
+ return defaultModels;
1262
+ }
1263
+ }
1264
+
874
1265
  /**
875
1266
  * Format OpenAI model ID to display name
876
1267
  */
@@ -921,7 +1312,7 @@ export class LLMProviderFactory {
921
1312
  * Save cached models for a provider
922
1313
  */
923
1314
  static saveCachedModels(
924
- providerType: 'gemini' | 'openrouter' | 'ollama' | 'bedrock' | 'openai',
1315
+ providerType: 'gemini' | 'openrouter' | 'ollama' | 'bedrock' | 'openai' | 'groq' | 'xai' | 'kimi',
925
1316
  models: CachedModelInfo[]
926
1317
  ): void {
927
1318
  const settings = this.loadSettings();
@@ -942,6 +1333,15 @@ export class LLMProviderFactory {
942
1333
  case 'openai':
943
1334
  settings.cachedOpenAIModels = models;
944
1335
  break;
1336
+ case 'groq':
1337
+ settings.cachedGroqModels = models;
1338
+ break;
1339
+ case 'xai':
1340
+ settings.cachedXaiModels = models;
1341
+ break;
1342
+ case 'kimi':
1343
+ settings.cachedKimiModels = models;
1344
+ break;
945
1345
  }
946
1346
 
947
1347
  this.saveSettings(settings);
@@ -950,7 +1350,9 @@ export class LLMProviderFactory {
950
1350
  /**
951
1351
  * Get cached models for a provider
952
1352
  */
953
- static getCachedModels(providerType: 'gemini' | 'openrouter' | 'ollama' | 'bedrock' | 'openai'): CachedModelInfo[] | undefined {
1353
+ static getCachedModels(
1354
+ providerType: 'gemini' | 'openrouter' | 'ollama' | 'bedrock' | 'openai' | 'groq' | 'xai' | 'kimi'
1355
+ ): CachedModelInfo[] | undefined {
954
1356
  const settings = this.loadSettings();
955
1357
 
956
1358
  switch (providerType) {
@@ -964,6 +1366,12 @@ export class LLMProviderFactory {
964
1366
  return settings.cachedBedrockModels;
965
1367
  case 'openai':
966
1368
  return settings.cachedOpenAIModels;
1369
+ case 'groq':
1370
+ return settings.cachedGroqModels;
1371
+ case 'xai':
1372
+ return settings.cachedXaiModels;
1373
+ case 'kimi':
1374
+ return settings.cachedKimiModels;
967
1375
  default:
968
1376
  return undefined;
969
1377
  }