@caretive/caret-cli 0.0.1

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 (85) hide show
  1. package/.npmrc.tmp +2 -0
  2. package/README.md +72 -0
  3. package/cmd/cline/main.go +348 -0
  4. package/cmd/cline-host/main.go +71 -0
  5. package/e2e/default_update_test.go +154 -0
  6. package/e2e/helpers_test.go +378 -0
  7. package/e2e/main_test.go +47 -0
  8. package/e2e/mixed_stress_test.go +120 -0
  9. package/e2e/sqlite_helper.go +161 -0
  10. package/e2e/start_list_test.go +178 -0
  11. package/go.mod +64 -0
  12. package/go.sum +162 -0
  13. package/man/cline.1 +331 -0
  14. package/man/cline.1.md +332 -0
  15. package/package.json +54 -0
  16. package/pkg/cli/auth/auth_cline_provider.go +285 -0
  17. package/pkg/cli/auth/auth_menu.go +323 -0
  18. package/pkg/cli/auth/auth_subscription.go +130 -0
  19. package/pkg/cli/auth/byo_quick_setup.go +247 -0
  20. package/pkg/cli/auth/models_cline.go +141 -0
  21. package/pkg/cli/auth/models_list_fetch.go +156 -0
  22. package/pkg/cli/auth/models_list_static.go +69 -0
  23. package/pkg/cli/auth/providers_byo.go +184 -0
  24. package/pkg/cli/auth/providers_list.go +517 -0
  25. package/pkg/cli/auth/update_api_configurations.go +647 -0
  26. package/pkg/cli/auth/wizard_byo.go +764 -0
  27. package/pkg/cli/auth/wizard_byo_bedrock.go +193 -0
  28. package/pkg/cli/auth/wizard_byo_oca.go +366 -0
  29. package/pkg/cli/auth.go +43 -0
  30. package/pkg/cli/clerror/cline_error.go +187 -0
  31. package/pkg/cli/config/manager.go +208 -0
  32. package/pkg/cli/config/settings_renderer.go +198 -0
  33. package/pkg/cli/config.go +152 -0
  34. package/pkg/cli/display/ansi.go +27 -0
  35. package/pkg/cli/display/banner.go +211 -0
  36. package/pkg/cli/display/deduplicator.go +95 -0
  37. package/pkg/cli/display/markdown_renderer.go +139 -0
  38. package/pkg/cli/display/renderer.go +304 -0
  39. package/pkg/cli/display/segment_streamer.go +212 -0
  40. package/pkg/cli/display/streaming.go +134 -0
  41. package/pkg/cli/display/system_renderer.go +269 -0
  42. package/pkg/cli/display/tool_renderer.go +455 -0
  43. package/pkg/cli/display/tool_result_parser.go +371 -0
  44. package/pkg/cli/display/typewriter.go +210 -0
  45. package/pkg/cli/doctor.go +65 -0
  46. package/pkg/cli/global/cline-clients.go +501 -0
  47. package/pkg/cli/global/global.go +113 -0
  48. package/pkg/cli/global/registry.go +304 -0
  49. package/pkg/cli/handlers/ask_handlers.go +339 -0
  50. package/pkg/cli/handlers/handler.go +130 -0
  51. package/pkg/cli/handlers/say_handlers.go +521 -0
  52. package/pkg/cli/instances.go +506 -0
  53. package/pkg/cli/logs.go +382 -0
  54. package/pkg/cli/output/coordinator.go +167 -0
  55. package/pkg/cli/output/input_model.go +497 -0
  56. package/pkg/cli/sqlite/locks.go +366 -0
  57. package/pkg/cli/task/history_handler.go +72 -0
  58. package/pkg/cli/task/input_handler.go +577 -0
  59. package/pkg/cli/task/manager.go +1283 -0
  60. package/pkg/cli/task/settings_parser.go +754 -0
  61. package/pkg/cli/task/stream_coordinator.go +60 -0
  62. package/pkg/cli/task.go +675 -0
  63. package/pkg/cli/terminal/keyboard.go +695 -0
  64. package/pkg/cli/tui/HELP_WANTED.md +1 -0
  65. package/pkg/cli/types/history.go +17 -0
  66. package/pkg/cli/types/messages.go +329 -0
  67. package/pkg/cli/types/state.go +59 -0
  68. package/pkg/cli/updater/updater.go +409 -0
  69. package/pkg/cli/version.go +43 -0
  70. package/pkg/common/constants.go +6 -0
  71. package/pkg/common/schema.go +54 -0
  72. package/pkg/common/types.go +54 -0
  73. package/pkg/common/utils.go +185 -0
  74. package/pkg/generated/field_overrides.go +39 -0
  75. package/pkg/generated/providers.go +1584 -0
  76. package/pkg/hostbridge/diff.go +351 -0
  77. package/pkg/hostbridge/disabled/watch.go +39 -0
  78. package/pkg/hostbridge/disabled/window.go +63 -0
  79. package/pkg/hostbridge/disabled/workspace.go +66 -0
  80. package/pkg/hostbridge/env.go +166 -0
  81. package/pkg/hostbridge/grpc_server.go +113 -0
  82. package/pkg/hostbridge/simple.go +43 -0
  83. package/pkg/hostbridge/simple_workspace.go +85 -0
  84. package/pkg/hostbridge/window.go +129 -0
  85. package/scripts/publish-caret-cli.sh +39 -0
@@ -0,0 +1,517 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "strings"
8
+ "time"
9
+
10
+ "github.com/cline/cli/pkg/cli/global"
11
+ "github.com/cline/cli/pkg/cli/task"
12
+ "github.com/cline/grpc-go/cline"
13
+ )
14
+
15
+ // ProviderDisplay represents a configured provider for display purposes
16
+ type ProviderDisplay struct {
17
+ Mode string // "Plan" or "Act"
18
+ Provider cline.ApiProvider // Provider enum
19
+ ModelID string // Model identifier
20
+ HasAPIKey bool // Whether an API key is configured (never show actual key)
21
+ BaseURL string // Base URL for providers like Ollama (can be shown publicly)
22
+ }
23
+
24
+ // ProviderListResult holds the parsed provider configuration from state
25
+ type ProviderListResult struct {
26
+ PlanProvider *ProviderDisplay
27
+ ActProvider *ProviderDisplay
28
+ apiConfig map[string]interface{} // Store the raw apiConfig for scanning all providers
29
+ }
30
+
31
+ // GetProviderConfigurations retrieves and parses provider configurations from Cline Core state
32
+ func GetProviderConfigurations(ctx context.Context, manager *task.Manager) (*ProviderListResult, error) {
33
+ if global.Config.Verbose {
34
+ fmt.Println("[DEBUG] Retrieving provider configurations from Cline Core")
35
+ }
36
+
37
+ // Get latest state from Cline Core
38
+ state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
39
+ if err != nil {
40
+ return nil, fmt.Errorf("failed to get state: %w", err)
41
+ }
42
+
43
+ stateJSON := state.StateJson
44
+
45
+ if global.Config.Verbose {
46
+ fmt.Printf("[DEBUG] Retrieved state, parsing JSON (length: %d)\n", len(stateJSON))
47
+ }
48
+
49
+ // Parse state_json as map[string]interface{}
50
+ var stateData map[string]interface{}
51
+ if err := json.Unmarshal([]byte(stateJSON), &stateData); err != nil {
52
+ return nil, fmt.Errorf("failed to parse state JSON: %w", err)
53
+ }
54
+
55
+ if global.Config.Verbose {
56
+ fmt.Printf("[DEBUG] Parsed state data with %d keys\n", len(stateData))
57
+ }
58
+
59
+ // Extract apiConfiguration object from state
60
+ apiConfig, ok := stateData["apiConfiguration"].(map[string]interface{})
61
+ if !ok {
62
+ if global.Config.Verbose {
63
+ fmt.Println("[DEBUG] No apiConfiguration found in state")
64
+ }
65
+ return &ProviderListResult{
66
+ apiConfig: make(map[string]interface{}),
67
+ }, nil
68
+ }
69
+
70
+ if global.Config.Verbose {
71
+ fmt.Printf("[DEBUG] Found apiConfiguration with %d keys\n", len(apiConfig))
72
+ }
73
+
74
+ // Extract plan mode configuration
75
+ planProvider := extractProviderFromState(apiConfig, "plan")
76
+ if global.Config.Verbose && planProvider != nil {
77
+ fmt.Printf("[DEBUG] Plan mode: provider=%v, model=%s\n", planProvider.Provider, planProvider.ModelID)
78
+ }
79
+
80
+ // Extract act mode configuration
81
+ actProvider := extractProviderFromState(apiConfig, "act")
82
+ if global.Config.Verbose && actProvider != nil {
83
+ fmt.Printf("[DEBUG] Act mode: provider=%v, model=%s\n", actProvider.Provider, actProvider.ModelID)
84
+ }
85
+
86
+ return &ProviderListResult{
87
+ PlanProvider: planProvider,
88
+ ActProvider: actProvider,
89
+ apiConfig: apiConfig,
90
+ }, nil
91
+ }
92
+
93
+ // GetAllReadyProviders returns all providers that have both a model and API key configured
94
+ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
95
+ if r.apiConfig == nil {
96
+ return []*ProviderDisplay{}
97
+ }
98
+
99
+ var readyProviders []*ProviderDisplay
100
+ seenProviders := make(map[cline.ApiProvider]bool)
101
+
102
+ // Check all possible providers
103
+ allProviders := []cline.ApiProvider{
104
+ cline.ApiProvider_CLINE,
105
+ cline.ApiProvider_ANTHROPIC,
106
+ cline.ApiProvider_OPENAI,
107
+ cline.ApiProvider_OPENAI_NATIVE,
108
+ cline.ApiProvider_OPENROUTER,
109
+ cline.ApiProvider_XAI,
110
+ cline.ApiProvider_BEDROCK,
111
+ cline.ApiProvider_GEMINI,
112
+ cline.ApiProvider_OLLAMA,
113
+ cline.ApiProvider_CEREBRAS,
114
+ cline.ApiProvider_NOUSRESEARCH,
115
+ cline.ApiProvider_OCA,
116
+ cline.ApiProvider_HICAP,
117
+ }
118
+
119
+ // Check each provider to see if it's ready to use
120
+ // We use "plan" mode to check, since both plan and act should have the same providers configured
121
+ for _, provider := range allProviders {
122
+ // Skip if we've already seen this provider
123
+ if seenProviders[provider] {
124
+ continue
125
+ }
126
+
127
+ // Check if this provider has a model configured
128
+ modelID := getProviderSpecificModelID(r.apiConfig, "plan", provider)
129
+
130
+ // Determine if credentials exist
131
+ hasCreds := checkAPIKeyExists(r.apiConfig, provider)
132
+
133
+ // Determine readiness: OCA uses auth state presence; others need creds and model
134
+ if provider == cline.ApiProvider_OCA {
135
+ state, _ := GetLatestOCAState(context.Background(), 2 *time.Second)
136
+ if state == nil || state.User == nil {
137
+ continue
138
+ }
139
+ } else {
140
+ // Provider is not ready unless it has credentials AND a model configured
141
+ if !hasCreds || modelID == "" {
142
+ continue
143
+ }
144
+ }
145
+
146
+ // Get base URL for Ollama
147
+ baseURL := ""
148
+ if provider == cline.ApiProvider_OLLAMA {
149
+ if url, ok := r.apiConfig["ollamaBaseUrl"].(string); ok {
150
+ baseURL = url
151
+ }
152
+ }
153
+
154
+ // This provider is ready to use
155
+ readyProviders = append(readyProviders, &ProviderDisplay{
156
+ Mode: "Ready",
157
+ Provider: provider,
158
+ ModelID: modelID,
159
+ HasAPIKey: checkAPIKeyExists(r.apiConfig, provider),
160
+ BaseURL: baseURL,
161
+ })
162
+ seenProviders[provider] = true
163
+ }
164
+
165
+ return readyProviders
166
+ }
167
+
168
+ // extractProviderFromState extracts provider configuration for specific plan/act mode
169
+ func extractProviderFromState(stateData map[string]interface{}, mode string) *ProviderDisplay {
170
+ // Build key names based on mode
171
+ providerKey := mode + "ModeApiProvider"
172
+
173
+ // Extract provider string from state
174
+ providerStr, ok := stateData[providerKey].(string)
175
+ if !ok || providerStr == "" {
176
+ if global.Config.Verbose {
177
+ fmt.Printf("[DEBUG] No provider configured for %s mode\n", mode)
178
+ }
179
+ return nil
180
+ }
181
+
182
+ // Map provider string to enum
183
+ provider, ok := mapProviderStringToEnum(providerStr)
184
+ if !ok {
185
+ if global.Config.Verbose {
186
+ fmt.Printf("[DEBUG] Unknown provider type: %s\n", providerStr)
187
+ }
188
+ return nil
189
+ }
190
+
191
+ // Get provider-specific model ID
192
+ modelID := getProviderSpecificModelID(stateData, mode, provider)
193
+
194
+ // Check if API key exists
195
+ hasAPIKey := checkAPIKeyExists(stateData, provider)
196
+
197
+ // Get base URL for Ollama (can be shown publicly)
198
+ baseURL := ""
199
+ if provider == cline.ApiProvider_OLLAMA {
200
+ if url, ok := stateData["ollamaBaseUrl"].(string); ok {
201
+ baseURL = url
202
+ }
203
+ }
204
+
205
+ return &ProviderDisplay{
206
+ Mode: capitalizeMode(mode),
207
+ Provider: provider,
208
+ ModelID: modelID,
209
+ HasAPIKey: hasAPIKey,
210
+ BaseURL: baseURL,
211
+ }
212
+ }
213
+
214
+ // mapProviderStringToEnum converts provider string from state to ApiProvider enum
215
+ // Returns (provider, ok) where ok is false if the provider is unknown
216
+ func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
217
+ normalizedStr := strings.ToLower(providerStr)
218
+
219
+ // Map string values to enum values
220
+ switch normalizedStr {
221
+ case "anthropic":
222
+ return cline.ApiProvider_ANTHROPIC, true
223
+ case "openai", "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
224
+ return cline.ApiProvider_OPENAI, true
225
+ case "openai-native": // This is the native, official Open AI provider
226
+ return cline.ApiProvider_OPENAI_NATIVE, true
227
+ case "openrouter":
228
+ return cline.ApiProvider_OPENROUTER, true
229
+ case "xai":
230
+ return cline.ApiProvider_XAI, true
231
+ case "bedrock":
232
+ return cline.ApiProvider_BEDROCK, true
233
+ case "gemini":
234
+ return cline.ApiProvider_GEMINI, true
235
+ case "ollama":
236
+ return cline.ApiProvider_OLLAMA, true
237
+ case "cerebras":
238
+ return cline.ApiProvider_CEREBRAS, true
239
+ case "cline":
240
+ return cline.ApiProvider_CLINE, true
241
+ case "oca":
242
+ return cline.ApiProvider_OCA, true
243
+ case "hicap":
244
+ return cline.ApiProvider_HICAP, true
245
+ case "nousResearch":
246
+ return cline.ApiProvider_NOUSRESEARCH, true
247
+ default:
248
+ return cline.ApiProvider_ANTHROPIC, false // Return 0 value with false
249
+ }
250
+ }
251
+
252
+ // GetProviderIDForEnum converts a provider enum to the provider ID string
253
+ // This is the inverse of mapProviderStringToEnum and is used for provider definitions
254
+ func GetProviderIDForEnum(provider cline.ApiProvider) string {
255
+ switch provider {
256
+ case cline.ApiProvider_ANTHROPIC:
257
+ return "anthropic"
258
+ case cline.ApiProvider_OPENAI:
259
+ return "openai-compatible"
260
+ case cline.ApiProvider_OPENAI_NATIVE:
261
+ return "openai-native"
262
+ case cline.ApiProvider_OPENROUTER:
263
+ return "openrouter"
264
+ case cline.ApiProvider_XAI:
265
+ return "xai"
266
+ case cline.ApiProvider_BEDROCK:
267
+ return "bedrock"
268
+ case cline.ApiProvider_GEMINI:
269
+ return "gemini"
270
+ case cline.ApiProvider_OLLAMA:
271
+ return "ollama"
272
+ case cline.ApiProvider_CEREBRAS:
273
+ return "cerebras"
274
+ case cline.ApiProvider_CLINE:
275
+ return "cline"
276
+ case cline.ApiProvider_OCA:
277
+ return "oca"
278
+ case cline.ApiProvider_HICAP:
279
+ return "hicap"
280
+ case cline.ApiProvider_NOUSRESEARCH:
281
+ return "nousResearch"
282
+ default:
283
+ return ""
284
+ }
285
+ }
286
+
287
+ // getProviderSpecificModelID gets the provider-specific model ID field from state
288
+ func getProviderSpecificModelID(stateData map[string]interface{}, mode string, provider cline.ApiProvider) string {
289
+ modelKey, err := GetModelIDFieldName(provider, mode)
290
+ if err != nil {
291
+ if global.Config.Verbose {
292
+ fmt.Printf("[DEBUG] Error getting model ID field name: %v\n", err)
293
+ }
294
+ return ""
295
+ }
296
+
297
+ if global.Config.Verbose {
298
+ fmt.Printf("[DEBUG] Looking for model ID in key: %s\n", modelKey)
299
+ }
300
+
301
+ // Extract model ID from state
302
+ modelID, _ := stateData[modelKey].(string)
303
+ return modelID
304
+ }
305
+
306
+ // checkAPIKeyExists checks if API key field exists in state (never retrieve actual key)
307
+ func checkAPIKeyExists(stateData map[string]interface{}, provider cline.ApiProvider) bool {
308
+ // Get field mapping from centralized function
309
+ fields, err := GetProviderFields(provider)
310
+ if err != nil {
311
+ return false
312
+ }
313
+
314
+ keyField := fields.APIKeyField
315
+
316
+ // Check if the key exists and is not empty
317
+ if value, ok := stateData[keyField]; ok {
318
+ if str, ok := value.(string); ok && str != "" {
319
+ return true
320
+ }
321
+ }
322
+
323
+ return false
324
+ }
325
+
326
+ // capitalizeMode capitalizes the mode string for display
327
+ func capitalizeMode(mode string) string {
328
+ if len(mode) == 0 {
329
+ return mode
330
+ }
331
+ return strings.ToUpper(mode[:1]) + mode[1:]
332
+ }
333
+
334
+ // GetProviderDisplayName returns a user-friendly name for the provider
335
+ func GetProviderDisplayName(provider cline.ApiProvider) string {
336
+ switch provider {
337
+ case cline.ApiProvider_ANTHROPIC:
338
+ return "Anthropic"
339
+ case cline.ApiProvider_OPENAI:
340
+ return "OpenAI Compatible"
341
+ case cline.ApiProvider_OPENAI_NATIVE:
342
+ return "OpenAI (Official)"
343
+ case cline.ApiProvider_OPENROUTER:
344
+ return "OpenRouter"
345
+ case cline.ApiProvider_XAI:
346
+ return "X AI (Grok)"
347
+ case cline.ApiProvider_BEDROCK:
348
+ return "AWS Bedrock"
349
+ case cline.ApiProvider_GEMINI:
350
+ return "Google Gemini"
351
+ case cline.ApiProvider_OLLAMA:
352
+ return "Ollama"
353
+ case cline.ApiProvider_CEREBRAS:
354
+ return "Cerebras"
355
+ case cline.ApiProvider_CLINE:
356
+ return "Cline (Official)"
357
+ case cline.ApiProvider_OCA:
358
+ return "Oracle Code Assist"
359
+ case cline.ApiProvider_HICAP:
360
+ return "Hicap"
361
+ case cline.ApiProvider_NOUSRESEARCH:
362
+ return "NousResearch"
363
+ default:
364
+ return "Unknown"
365
+ }
366
+ }
367
+
368
+ // FormatProviderList formats the complete provider list for console display
369
+ // This now shows ALL providers that have both a model and API key configured
370
+ func FormatProviderList(result *ProviderListResult) string {
371
+ var output strings.Builder
372
+
373
+ output.WriteString("\n=== Configured API Providers ===\n\n")
374
+
375
+ // Get the currently active provider
376
+ var activeProvider cline.ApiProvider
377
+ var activeProviderSet bool
378
+ if result.ActProvider != nil {
379
+ activeProvider = result.ActProvider.Provider
380
+ activeProviderSet = true
381
+ }
382
+
383
+ // Get all ready-to-use providers (those with both API key and model configured)
384
+ readyProviders := result.GetAllReadyProviders()
385
+
386
+ if len(readyProviders) == 0 {
387
+ output.WriteString(" No providers ready to use.\n")
388
+ output.WriteString(" A provider is ready when it has both a model and API key configured.\n")
389
+ output.WriteString(" Use 'Configure a new provider' to configure one.\n\n")
390
+ } else {
391
+ //output.WriteString(fmt.Sprintf(" %d provider(s) ready to use:\n\n", len(readyProviders)))
392
+
393
+ for _, display := range readyProviders {
394
+ // Check if this is the active provider
395
+ isActive := activeProviderSet && display.Provider == activeProvider
396
+
397
+ if isActive {
398
+ output.WriteString(fmt.Sprintf(" ✓ %s (ACTIVE)\n", GetProviderDisplayName(display.Provider)))
399
+ } else {
400
+ output.WriteString(fmt.Sprintf(" • %s\n", GetProviderDisplayName(display.Provider)))
401
+ }
402
+
403
+ output.WriteString(fmt.Sprintf(" Model: %s\n", display.ModelID))
404
+
405
+ // Show status based on provider type
406
+ if display.Provider == cline.ApiProvider_OLLAMA {
407
+ if display.BaseURL != "" {
408
+ output.WriteString(fmt.Sprintf(" Base URL: %s\n", display.BaseURL))
409
+ } else {
410
+ output.WriteString(" Base URL: (default)\n")
411
+ }
412
+ } else if display.Provider == cline.ApiProvider_CLINE || display.Provider == cline.ApiProvider_OCA {
413
+ output.WriteString(" Status: Authenticated\n")
414
+ } else {
415
+ output.WriteString(" API Key: Configured\n")
416
+ }
417
+
418
+ output.WriteString("\n")
419
+ }
420
+ }
421
+
422
+ output.WriteString("================================\n")
423
+
424
+ return output.String()
425
+ }
426
+
427
+ // DetectAllConfiguredProviders scans the state to find all providers that have API keys configured.
428
+ // This allows switching between multiple providers even when only one is currently active.
429
+ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([]cline.ApiProvider, error) {
430
+ verboseLog("[DEBUG] Detecting all configured providers...")
431
+
432
+ // Get latest state from Cline Core
433
+ state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
434
+ if err != nil {
435
+ return nil, fmt.Errorf("failed to get state: %w", err)
436
+ }
437
+
438
+ stateJSON := state.StateJson
439
+
440
+ // Parse state_json as map[string]interface{}
441
+ var stateData map[string]interface{}
442
+ if err := json.Unmarshal([]byte(stateJSON), &stateData); err != nil {
443
+ return nil, fmt.Errorf("failed to parse state JSON: %w", err)
444
+ }
445
+
446
+ // Extract apiConfiguration object from state
447
+ apiConfig, ok := stateData["apiConfiguration"].(map[string]interface{})
448
+ if !ok {
449
+ verboseLog("[DEBUG] No apiConfiguration found in state")
450
+ verboseLog("[DEBUG] Available keys in stateData: %v", getMapKeys(stateData))
451
+ return []cline.ApiProvider{}, nil
452
+ }
453
+
454
+ verboseLog("[DEBUG] apiConfiguration keys: %v", getMapKeys(apiConfig))
455
+
456
+ var configuredProviders []cline.ApiProvider
457
+
458
+ // Check for Cline provider (uses authentication instead of API key)
459
+ if IsAuthenticated(ctx) {
460
+ configuredProviders = append(configuredProviders, cline.ApiProvider_CLINE)
461
+ verboseLog("[DEBUG] Cline provider is authenticated")
462
+ }
463
+
464
+ // Check OCA provider via global auth subscription (state presence)
465
+ if state, _ := GetLatestOCAState(context.Background(), 2*time.Second); state != nil && state.User != nil {
466
+ configuredProviders = append(configuredProviders, cline.ApiProvider_OCA)
467
+ verboseLog("[DEBUG] OCA provider has active auth state")
468
+ }
469
+
470
+ // Check each BYO provider for API key presence
471
+ providersToCheck := []struct {
472
+ provider cline.ApiProvider
473
+ keyField string
474
+ }{
475
+ {cline.ApiProvider_ANTHROPIC, "apiKey"},
476
+ {cline.ApiProvider_OPENAI, "openAiApiKey"},
477
+ {cline.ApiProvider_OPENAI_NATIVE, "openAiNativeApiKey"},
478
+ {cline.ApiProvider_OPENROUTER, "openRouterApiKey"},
479
+ {cline.ApiProvider_XAI, "xaiApiKey"},
480
+ {cline.ApiProvider_BEDROCK, "awsAccessKey"},
481
+ {cline.ApiProvider_GEMINI, "geminiApiKey"},
482
+ {cline.ApiProvider_OLLAMA, "ollamaBaseUrl"}, // Ollama uses baseUrl instead of API key
483
+ {cline.ApiProvider_CEREBRAS, "cerebrasApiKey"},
484
+ {cline.ApiProvider_HICAP, "hicapApiKey"},
485
+ {cline.ApiProvider_NOUSRESEARCH, "nousResearchApiKey"},
486
+ }
487
+
488
+ for _, providerCheck := range providersToCheck {
489
+ verboseLog("[DEBUG] Checking for %s key: %s", GetProviderDisplayName(providerCheck.provider), providerCheck.keyField)
490
+ if value, ok := apiConfig[providerCheck.keyField]; ok {
491
+ verboseLog("[DEBUG] Found key, value type: %T, is empty: %v", value, value == "")
492
+ if str, ok := value.(string); ok && str != "" {
493
+ configuredProviders = append(configuredProviders, providerCheck.provider)
494
+ verboseLog("[DEBUG] ✓ Provider %s is configured", GetProviderDisplayName(providerCheck.provider))
495
+ }
496
+ } else {
497
+ verboseLog("[DEBUG] Key %s not found", providerCheck.keyField)
498
+ }
499
+ }
500
+
501
+
502
+ verboseLog("[DEBUG] Total configured providers: %d", len(configuredProviders))
503
+ for _, p := range configuredProviders {
504
+ verboseLog("[DEBUG] - %s", GetProviderDisplayName(p))
505
+ }
506
+
507
+ return configuredProviders, nil
508
+ }
509
+
510
+ // getMapKeys returns the keys of a map for debugging
511
+ func getMapKeys(m map[string]interface{}) []string {
512
+ keys := make([]string, 0, len(m))
513
+ for k := range m {
514
+ keys = append(keys, k)
515
+ }
516
+ return keys
517
+ }