@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,647 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/cline/cli/pkg/cli/global"
8
+ "github.com/cline/cli/pkg/cli/task"
9
+ "github.com/cline/grpc-go/cline"
10
+ "google.golang.org/protobuf/proto"
11
+ "google.golang.org/protobuf/types/known/fieldmaskpb"
12
+ )
13
+
14
+ // updateApiConfigurationPartial is a helper that calls the gRPC method with optional verbose logging.
15
+ // This replaces the Manager.updateApiConfigurationPartial method to keep auth-specific code in the auth package.
16
+ func updateApiConfigurationPartial(ctx context.Context, manager *task.Manager, request *cline.UpdateApiConfigurationPartialRequest) error {
17
+ if global.Config.Verbose {
18
+ fmt.Println("[DEBUG] Updating API configuration (partial)")
19
+ if request.UpdateMask != nil && len(request.UpdateMask.Paths) > 0 {
20
+ fmt.Printf("[DEBUG] Field mask paths: %v\n", request.UpdateMask.Paths)
21
+ }
22
+ if request.ApiConfiguration != nil {
23
+ apiConfig := request.ApiConfiguration
24
+ if apiConfig.PlanModeApiProvider != nil {
25
+ fmt.Printf("[DEBUG] Plan mode provider: %s\n", *apiConfig.PlanModeApiProvider)
26
+ }
27
+ if apiConfig.ActModeApiProvider != nil {
28
+ fmt.Printf("[DEBUG] Act mode provider: %s\n", *apiConfig.ActModeApiProvider)
29
+ }
30
+ }
31
+ }
32
+
33
+ // Call the Models service to update API configuration
34
+ _, err := manager.GetClient().Models.UpdateApiConfigurationPartial(ctx, request)
35
+ if err != nil {
36
+ return fmt.Errorf("failed to update API configuration (partial): %w", err)
37
+ }
38
+
39
+ if global.Config.Verbose {
40
+ fmt.Println("[DEBUG] API configuration updated successfully (partial)")
41
+ }
42
+
43
+ return nil
44
+ }
45
+
46
+ // ProviderFields defines all the field names associated with a specific provider
47
+ type ProviderFields struct {
48
+ APIKeyField string // API key field name (e.g., "apiKey", "openAiApiKey")
49
+ BaseURLField string // Base URL field name (optional, empty if not applicable)
50
+ PlanModeModelIDField string // Plan mode model ID field (e.g., "planModeApiModelId")
51
+ ActModeModelIDField string // Act mode model ID field (e.g., "actModeApiModelId")
52
+ PlanModeModelInfoField string // Plan mode model info field (optional, empty if not applicable)
53
+ ActModeModelInfoField string // Act mode model info field (optional, empty if not applicable)
54
+ // Provider-specific additional model ID fields
55
+ PlanModeProviderSpecificModelIDField string // e.g., "planModeOpenRouterModelId"
56
+ ActModeProviderSpecificModelIDField string // e.g., "actModeOpenRouterModelId"
57
+ }
58
+
59
+ // GetProviderFields returns the field mapping for a given provider
60
+ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
61
+ switch provider {
62
+ case cline.ApiProvider_ANTHROPIC:
63
+ return ProviderFields{
64
+ APIKeyField: "apiKey",
65
+ PlanModeModelIDField: "planModeApiModelId",
66
+ ActModeModelIDField: "actModeApiModelId",
67
+ }, nil
68
+
69
+ case cline.ApiProvider_OPENAI:
70
+ return ProviderFields{
71
+ APIKeyField: "openAiApiKey",
72
+ BaseURLField: "openAiBaseUrl",
73
+ PlanModeModelIDField: "planModeApiModelId",
74
+ ActModeModelIDField: "actModeApiModelId",
75
+ PlanModeProviderSpecificModelIDField: "planModeOpenAiModelId",
76
+ ActModeProviderSpecificModelIDField: "actModeOpenAiModelId",
77
+ }, nil
78
+
79
+ case cline.ApiProvider_OPENROUTER:
80
+ return ProviderFields{
81
+ APIKeyField: "openRouterApiKey",
82
+ PlanModeModelIDField: "planModeApiModelId",
83
+ ActModeModelIDField: "actModeApiModelId",
84
+ PlanModeModelInfoField: "planModeOpenRouterModelInfo",
85
+ ActModeModelInfoField: "actModeOpenRouterModelInfo",
86
+ PlanModeProviderSpecificModelIDField: "planModeOpenRouterModelId",
87
+ ActModeProviderSpecificModelIDField: "actModeOpenRouterModelId",
88
+ }, nil
89
+
90
+ case cline.ApiProvider_XAI:
91
+ return ProviderFields{
92
+ APIKeyField: "xaiApiKey",
93
+ PlanModeModelIDField: "planModeApiModelId",
94
+ ActModeModelIDField: "actModeApiModelId",
95
+ }, nil
96
+
97
+ case cline.ApiProvider_BEDROCK:
98
+ return ProviderFields{
99
+ APIKeyField: "awsAccessKey",
100
+ PlanModeModelIDField: "planModeApiModelId",
101
+ ActModeModelIDField: "actModeApiModelId",
102
+ PlanModeProviderSpecificModelIDField: "planModeAwsBedrockCustomModelBaseId",
103
+ ActModeProviderSpecificModelIDField: "actModeAwsBedrockCustomModelBaseId",
104
+ }, nil
105
+
106
+ case cline.ApiProvider_GEMINI:
107
+ return ProviderFields{
108
+ APIKeyField: "geminiApiKey",
109
+ PlanModeModelIDField: "planModeApiModelId",
110
+ ActModeModelIDField: "actModeApiModelId",
111
+ }, nil
112
+
113
+ case cline.ApiProvider_OPENAI_NATIVE:
114
+ return ProviderFields{
115
+ APIKeyField: "openAiNativeApiKey",
116
+ PlanModeModelIDField: "planModeApiModelId",
117
+ ActModeModelIDField: "actModeApiModelId",
118
+ }, nil
119
+
120
+ case cline.ApiProvider_OLLAMA:
121
+ return ProviderFields{
122
+ APIKeyField: "ollamaBaseUrl",
123
+ PlanModeModelIDField: "planModeApiModelId",
124
+ ActModeModelIDField: "actModeApiModelId",
125
+ PlanModeProviderSpecificModelIDField: "planModeOllamaModelId",
126
+ ActModeProviderSpecificModelIDField: "actModeOllamaModelId",
127
+ }, nil
128
+
129
+ case cline.ApiProvider_CEREBRAS:
130
+ return ProviderFields{
131
+ APIKeyField: "cerebrasApiKey",
132
+ PlanModeModelIDField: "planModeApiModelId",
133
+ ActModeModelIDField: "actModeApiModelId",
134
+ }, nil
135
+
136
+ case cline.ApiProvider_CLINE:
137
+ return ProviderFields{
138
+ APIKeyField: "clineApiKey",
139
+ PlanModeModelIDField: "planModeApiModelId",
140
+ ActModeModelIDField: "actModeApiModelId",
141
+ PlanModeModelInfoField: "planModeOpenRouterModelInfo",
142
+ ActModeModelInfoField: "actModeOpenRouterModelInfo",
143
+ PlanModeProviderSpecificModelIDField: "planModeOpenRouterModelId",
144
+ ActModeProviderSpecificModelIDField: "actModeOpenRouterModelId",
145
+ }, nil
146
+
147
+ case cline.ApiProvider_OCA:
148
+ return ProviderFields{
149
+ APIKeyField: "ocaApiKey",
150
+ PlanModeModelIDField: "planModeApiModelId",
151
+ ActModeModelIDField: "actModeApiModelId",
152
+ PlanModeModelInfoField: "planModeOcaModelInfo",
153
+ ActModeModelInfoField: "actModeOcaModelInfo",
154
+ PlanModeProviderSpecificModelIDField: "planModeOcaModelId",
155
+ ActModeProviderSpecificModelIDField: "actModeOcaModelId",
156
+ }, nil
157
+ case cline.ApiProvider_HICAP:
158
+ return ProviderFields{
159
+ APIKeyField: "hicapApiKey",
160
+ PlanModeModelInfoField: "planModeHicapModelInfo",
161
+ ActModeModelInfoField: "actModeHicapModelInfo",
162
+ PlanModeProviderSpecificModelIDField: "planModeHicapModelId",
163
+ ActModeProviderSpecificModelIDField: "actModeHicapModelId",
164
+ }, nil
165
+
166
+ case cline.ApiProvider_NOUSRESEARCH:
167
+ return ProviderFields{
168
+ APIKeyField: "nousResearchApiKey",
169
+ PlanModeModelIDField: "planModeApiModelId",
170
+ ActModeModelIDField: "actModeApiModelId",
171
+ PlanModeProviderSpecificModelIDField: "planModeNousResearchModelId",
172
+ ActModeProviderSpecificModelIDField: "actModeNousResearchModelId",
173
+ }, nil
174
+
175
+ default:
176
+ return ProviderFields{}, fmt.Errorf("unsupported provider: %v", provider)
177
+ }
178
+ }
179
+
180
+ // ProviderUpdatesPartial defines optional fields for partial provider updates
181
+ // Uses pointers to distinguish between "not provided" and "set to empty"
182
+ type ProviderUpdatesPartial struct {
183
+ ModelID *string // New model ID (optional)
184
+ APIKey *string // New API key (optional)
185
+ ModelInfo interface{} // New model info (optional, provider-specific)
186
+ BaseURL *string // New base URL (optional, e.g., for OCA, Ollama)
187
+ RefreshToken *string // New refresh token (optional, e.g., for OCA)
188
+ Mode *string // New mode (optional, e.g., "internal" or "external" for OCA)
189
+ }
190
+
191
+ // GetModelIDFieldName returns the appropriate model ID field name for a provider and mode.
192
+ // This helper centralizes the logic for determining whether to use provider-specific
193
+ // or generic model ID fields.
194
+ func GetModelIDFieldName(provider cline.ApiProvider, mode string) (string, error) {
195
+ fields, err := GetProviderFields(provider)
196
+ if err != nil {
197
+ return "", err
198
+ }
199
+
200
+ if mode == "plan" {
201
+ // Use provider-specific field if available, otherwise use generic field
202
+ if fields.PlanModeProviderSpecificModelIDField != "" {
203
+ return fields.PlanModeProviderSpecificModelIDField, nil
204
+ }
205
+ return fields.PlanModeModelIDField, nil
206
+ }
207
+
208
+ // Act mode
209
+ if fields.ActModeProviderSpecificModelIDField != "" {
210
+ return fields.ActModeProviderSpecificModelIDField, nil
211
+ }
212
+ return fields.ActModeModelIDField, nil
213
+ }
214
+
215
+ // buildProviderFieldMask builds a list of camelCase field paths for the field mask.
216
+ // When includeProviderEnums is true, the provider enum fields are included (for setting active provider).
217
+ // When false, only the data fields are included (for configuring without activating).
218
+ func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeModelID bool, includeModelInfo bool, includeBaseURL bool, includeProviderEnums bool) []string {
219
+ var fieldPaths []string
220
+
221
+ // Include provider enums if requested (used when setting active provider)
222
+ if includeProviderEnums {
223
+ fieldPaths = append(fieldPaths, "planModeApiProvider", "actModeApiProvider")
224
+ }
225
+
226
+ // Add API key field if requested
227
+ if includeAPIKey {
228
+ fieldPaths = append(fieldPaths, fields.APIKeyField)
229
+ // Special case: Bedrock also needs secret key
230
+ if fields.APIKeyField == "awsAccessKey" {
231
+ fieldPaths = append(fieldPaths, "awsSecretKey")
232
+ }
233
+ }
234
+
235
+ // Add base URL field if requested and applicable
236
+ if includeBaseURL && fields.BaseURLField != "" {
237
+ fieldPaths = append(fieldPaths, fields.BaseURLField)
238
+ }
239
+
240
+ // Add model ID fields if requested
241
+ if includeModelID {
242
+ // Only include provider-specific fields if they exist, otherwise use generic fields
243
+ if fields.PlanModeProviderSpecificModelIDField != "" {
244
+ // Provider has specific fields - use ONLY those
245
+ fieldPaths = append(fieldPaths, fields.PlanModeProviderSpecificModelIDField)
246
+ fieldPaths = append(fieldPaths, fields.ActModeProviderSpecificModelIDField)
247
+ } else {
248
+ // Provider uses generic fields - update those
249
+ fieldPaths = append(fieldPaths, fields.PlanModeModelIDField)
250
+ fieldPaths = append(fieldPaths, fields.ActModeModelIDField)
251
+ }
252
+ }
253
+
254
+ // Add model info fields if requested and applicable
255
+ if includeModelInfo && fields.PlanModeModelInfoField != "" {
256
+ fieldPaths = append(fieldPaths, fields.PlanModeModelInfoField)
257
+ fieldPaths = append(fieldPaths, fields.ActModeModelInfoField)
258
+ }
259
+
260
+ return fieldPaths
261
+ }
262
+
263
+ // setAPIKeyField sets the appropriate API key field in the config based on the field name
264
+ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
265
+ switch fieldName {
266
+ case "apiKey":
267
+ apiConfig.ApiKey = value
268
+ case "openAiApiKey":
269
+ apiConfig.OpenAiApiKey = value
270
+ case "openAiNativeApiKey":
271
+ apiConfig.OpenAiNativeApiKey = value
272
+ case "openRouterApiKey":
273
+ apiConfig.OpenRouterApiKey = value
274
+ case "xaiApiKey":
275
+ apiConfig.XaiApiKey = value
276
+ case "awsAccessKey":
277
+ apiConfig.AwsAccessKey = value
278
+ case "geminiApiKey":
279
+ apiConfig.GeminiApiKey = value
280
+ case "ollamaBaseUrl":
281
+ apiConfig.OllamaBaseUrl = value
282
+ case "cerebrasApiKey":
283
+ apiConfig.CerebrasApiKey = value
284
+ case "clineApiKey":
285
+ apiConfig.ClineApiKey = value
286
+ case "ocaApiKey":
287
+ apiConfig.OcaApiKey = value
288
+ case "hicapApiKey":
289
+ apiConfig.HicapApiKey = value
290
+ case "nousResearchApiKey":
291
+ apiConfig.NousResearchApiKey = value
292
+ }
293
+ }
294
+
295
+ // setProviderSpecificModelID sets the appropriate provider-specific model ID fields when possible
296
+ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
297
+ switch fieldName {
298
+ case "planModeOpenAiModelId":
299
+ apiConfig.PlanModeOpenAiModelId = value
300
+ apiConfig.ActModeOpenAiModelId = value
301
+ case "planModeOpenRouterModelId":
302
+ apiConfig.PlanModeOpenRouterModelId = value
303
+ apiConfig.ActModeOpenRouterModelId = value
304
+ case "planModeOllamaModelId":
305
+ apiConfig.PlanModeOllamaModelId = value
306
+ apiConfig.ActModeOllamaModelId = value
307
+ case "planModeAwsBedrockCustomModelBaseId":
308
+ apiConfig.PlanModeAwsBedrockCustomModelBaseId = value
309
+ apiConfig.ActModeAwsBedrockCustomModelBaseId = value
310
+ case "planModeOcaModelId":
311
+ apiConfig.PlanModeOcaModelId = value
312
+ apiConfig.ActModeOcaModelId = value
313
+ case "planModeHicapModelId":
314
+ apiConfig.PlanModeHicapModelId = value
315
+ apiConfig.ActModeHicapModelId = value
316
+ case "planModeNousResearchModelId":
317
+ apiConfig.PlanModeNousResearchModelId = value
318
+ apiConfig.ActModeNousResearchModelId = value
319
+ }
320
+ }
321
+
322
+ // AddProviderPartial configures a new provider with all necessary fields using partial updates.
323
+ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID string, apiKey string, baseURL string, modelInfo interface{}) error {
324
+ // Get field mapping for this provider
325
+ fields, err := GetProviderFields(provider)
326
+ if err != nil {
327
+ return err
328
+ }
329
+
330
+ // Build a ModelsApiConfiguration with only the relevant provider fields set
331
+ apiConfig := &cline.ModelsApiConfiguration{}
332
+
333
+ // Set API key field
334
+ if apiKey != "" || fields.APIKeyField != "ollamaBaseUrl" {
335
+ setAPIKeyField(apiConfig, fields.APIKeyField, proto.String(apiKey))
336
+ }
337
+
338
+ // Set base URL field if provided and applicable
339
+ includeBaseURL := false
340
+ if baseURL != "" && fields.BaseURLField != "" {
341
+ setBaseURLField(apiConfig, fields.BaseURLField, proto.String(baseURL))
342
+ includeBaseURL = true
343
+ }
344
+
345
+ // Set model ID fields
346
+ apiConfig.PlanModeApiModelId = proto.String(modelID)
347
+ apiConfig.ActModeApiModelId = proto.String(modelID)
348
+
349
+ // Set provider-specific model ID fields if applicable
350
+ if fields.PlanModeProviderSpecificModelIDField != "" {
351
+ setProviderSpecificModelID(apiConfig, fields.PlanModeProviderSpecificModelIDField, proto.String(modelID))
352
+ }
353
+
354
+ // Set model info if applicable and provided
355
+ if fields.PlanModeModelInfoField != "" && modelInfo != nil {
356
+ if openRouterInfo, ok := modelInfo.(*cline.OpenRouterModelInfo); ok {
357
+ apiConfig.PlanModeOpenRouterModelInfo = openRouterInfo
358
+ apiConfig.ActModeOpenRouterModelInfo = openRouterInfo
359
+ }
360
+ }
361
+
362
+ // Build field mask including all fields we're setting (without provider enums)
363
+ includeModelInfo := fields.PlanModeModelInfoField != "" && modelInfo != nil
364
+ fieldPaths := buildProviderFieldMask(fields, true, true, includeModelInfo, includeBaseURL, false)
365
+
366
+ // Create field mask
367
+ fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
368
+
369
+ // Apply the partial update
370
+ request := &cline.UpdateApiConfigurationPartialRequest{
371
+ ApiConfiguration: apiConfig,
372
+ UpdateMask: fieldMask,
373
+ }
374
+
375
+ if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
376
+ return fmt.Errorf("failed to update API configuration: %w", err)
377
+ }
378
+
379
+ return nil
380
+ }
381
+
382
+ // UpdateProviderPartial updates specific fields for an existing provider using partial updates.
383
+ // If setAsActive is true, this will also set the provider as the active provider for both Plan and Act modes.
384
+ func UpdateProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, updates ProviderUpdatesPartial, setAsActive bool) error {
385
+ // Get field mapping for this provider
386
+ fields, err := GetProviderFields(provider)
387
+ if err != nil {
388
+ return err
389
+ }
390
+
391
+ // Build a ModelsApiConfiguration with only the fields being updated
392
+ apiConfig := &cline.ModelsApiConfiguration{}
393
+
394
+ // Set provider enum for BOTH Plan and Act modes if setAsActive is true
395
+ if setAsActive {
396
+ apiConfig.PlanModeApiProvider = &provider
397
+ apiConfig.ActModeApiProvider = &provider
398
+ }
399
+
400
+ // Track what we're updating for field mask
401
+ includeAPIKey := updates.APIKey != nil
402
+ includeModelID := updates.ModelID != nil
403
+ includeModelInfo := updates.ModelInfo != nil && fields.PlanModeModelInfoField != ""
404
+
405
+ // Update API key if provided
406
+ if updates.APIKey != nil {
407
+ setAPIKeyField(apiConfig, fields.APIKeyField, updates.APIKey)
408
+ }
409
+
410
+ // Update model ID if provided
411
+ if updates.ModelID != nil {
412
+ // Only set provider-specific fields if they exist, otherwise use generic fields
413
+ if fields.PlanModeProviderSpecificModelIDField != "" {
414
+ setProviderSpecificModelID(apiConfig, fields.PlanModeProviderSpecificModelIDField, updates.ModelID)
415
+ } else {
416
+ // Provider uses generic fields - set those
417
+ apiConfig.PlanModeApiModelId = updates.ModelID
418
+ apiConfig.ActModeApiModelId = updates.ModelID
419
+ }
420
+ }
421
+
422
+ // Update model info if provided
423
+ if updates.ModelInfo != nil && fields.PlanModeModelInfoField != "" {
424
+ if openRouterInfo, ok := updates.ModelInfo.(*cline.OpenRouterModelInfo); ok {
425
+ apiConfig.PlanModeOpenRouterModelInfo = openRouterInfo
426
+ apiConfig.ActModeOpenRouterModelInfo = openRouterInfo
427
+ }
428
+ }
429
+
430
+ // Build field mask for only the fields being updated
431
+ fieldPaths := buildProviderFieldMask(fields, includeAPIKey, includeModelID, includeModelInfo, false, setAsActive)
432
+
433
+ // Create field mask
434
+ fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
435
+
436
+ // Apply the partial update
437
+ request := &cline.UpdateApiConfigurationPartialRequest{
438
+ ApiConfiguration: apiConfig,
439
+ UpdateMask: fieldMask,
440
+ }
441
+
442
+ if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
443
+ return fmt.Errorf("failed to update API configuration: %w", err)
444
+ }
445
+
446
+ return nil
447
+ }
448
+
449
+ // RemoveProviderPartial removes a provider by clearing its API key using partial updates
450
+ func RemoveProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider) error {
451
+ // Get field mapping for this provider
452
+ fields, err := GetProviderFields(provider)
453
+ if err != nil {
454
+ return err
455
+ }
456
+
457
+ // Build an EMPTY ModelsApiConfiguration (or one with empty API key field)
458
+ // Fields in the mask without values will be cleared
459
+ apiConfig := &cline.ModelsApiConfiguration{}
460
+
461
+ // Build field mask with only the API key field(s)
462
+ // For Bedrock, include both access key and secret key
463
+ fieldPaths := []string{fields.APIKeyField}
464
+ if provider == cline.ApiProvider_BEDROCK {
465
+ fieldPaths = append(fieldPaths, "awsSecretKey")
466
+ }
467
+
468
+ // Create field mask
469
+ fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
470
+
471
+ // Apply the partial update (clearing API key by including in mask without value)
472
+ request := &cline.UpdateApiConfigurationPartialRequest{
473
+ ApiConfiguration: apiConfig,
474
+ UpdateMask: fieldMask,
475
+ }
476
+
477
+ if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
478
+ return fmt.Errorf("failed to update API configuration: %w", err)
479
+ }
480
+
481
+ return nil
482
+ }
483
+
484
+ // setBaseURLField sets the appropriate base URL field in the config based on the field name
485
+ func setBaseURLField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
486
+ switch fieldName {
487
+ case "ocaBaseUrl":
488
+ apiConfig.OcaBaseUrl = value
489
+ case "ollamaBaseUrl":
490
+ apiConfig.OllamaBaseUrl = value
491
+ case "openAiBaseUrl":
492
+ apiConfig.OpenAiBaseUrl = value
493
+ case "geminiBaseUrl":
494
+ apiConfig.GeminiBaseUrl = value
495
+ case "liteLlmBaseUrl":
496
+ apiConfig.LiteLlmBaseUrl = value
497
+ case "anthropicBaseUrl":
498
+ apiConfig.AnthropicBaseUrl = value
499
+ case "requestyBaseUrl":
500
+ apiConfig.RequestyBaseUrl = value
501
+ case "lmStudioBaseUrl":
502
+ apiConfig.LmStudioBaseUrl = value
503
+ case "oca":
504
+ apiConfig.OcaBaseUrl = value
505
+ }
506
+ }
507
+
508
+ // setRefreshTokenField sets the appropriate refresh token field in the config
509
+ func setRefreshTokenField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
510
+ switch fieldName {
511
+ case "ocaRefreshToken":
512
+ apiConfig.OcaRefreshToken = value
513
+ }
514
+ }
515
+
516
+ // setModeField sets the appropriate mode field in the config
517
+ func setModeField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
518
+ switch fieldName {
519
+ case "ocaMode":
520
+ apiConfig.OcaMode = value
521
+ }
522
+ }
523
+
524
+ // BedrockOptionalFields holds optional configuration fields for AWS Bedrock
525
+ type BedrockOptionalFields struct {
526
+ SessionToken *string // Optional: AWS session token for temporary credentials
527
+ Region *string // Optional: AWS region
528
+ UseCrossRegionInference *bool // Optional: Enable cross-region inference
529
+ UseGlobalInference *bool // Optional: Use global inference endpoint
530
+ UsePromptCache *bool // Optional: Enable prompt caching
531
+ Authentication *string // Optional: Authentication method
532
+ UseProfile *bool // Optional: Use AWS profile
533
+ Profile *string // Optional: AWS profile name
534
+ Endpoint *string // Optional: Custom endpoint URL
535
+ }
536
+
537
+ // OcaOptionalFields holds optional configuration fields for Oracle Code Assist
538
+ type OcaOptionalFields struct {
539
+ BaseURL *string // Optional: Base URL
540
+ Mode *string // Optional: Mode ("internal" or "external")
541
+ }
542
+
543
+ // setBedrockOptionalFields sets optional Bedrock-specific fields in the API configuration
544
+ func setBedrockOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *BedrockOptionalFields) {
545
+ if fields == nil {
546
+ return
547
+ }
548
+
549
+ if fields.SessionToken != nil {
550
+ apiConfig.AwsSessionToken = fields.SessionToken
551
+ }
552
+ if fields.Region != nil {
553
+ apiConfig.AwsRegion = fields.Region
554
+ }
555
+ if fields.UseCrossRegionInference != nil {
556
+ apiConfig.AwsUseCrossRegionInference = fields.UseCrossRegionInference
557
+ }
558
+ if fields.UseGlobalInference != nil {
559
+ apiConfig.AwsUseGlobalInference = fields.UseGlobalInference
560
+ }
561
+ if fields.UsePromptCache != nil {
562
+ apiConfig.AwsBedrockUsePromptCache = fields.UsePromptCache
563
+ }
564
+ if fields.Authentication != nil {
565
+ apiConfig.AwsAuthentication = fields.Authentication
566
+ }
567
+ if fields.UseProfile != nil {
568
+ apiConfig.AwsUseProfile = fields.UseProfile
569
+ }
570
+ if fields.Profile != nil {
571
+ apiConfig.AwsProfile = fields.Profile
572
+ }
573
+ if fields.Endpoint != nil {
574
+ apiConfig.AwsBedrockEndpoint = fields.Endpoint
575
+ }
576
+ }
577
+
578
+ // setOcaOptionalFields sets optional Oca-specific fields in the API configuration
579
+ func setOcaOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *OcaOptionalFields) {
580
+ if fields == nil {
581
+ return
582
+ }
583
+
584
+ if fields.Mode != nil {
585
+ apiConfig.OcaMode = fields.Mode
586
+ }
587
+ if fields.BaseURL != nil {
588
+ apiConfig.OcaBaseUrl = fields.BaseURL
589
+ }
590
+ }
591
+
592
+ // buildBedrockOptionalFieldMask builds field mask paths for Bedrock optional fields that have values
593
+ func buildBedrockOptionalFieldMask(fields *BedrockOptionalFields) []string {
594
+ if fields == nil {
595
+ return nil
596
+ }
597
+
598
+ var fieldPaths []string
599
+
600
+ if fields.SessionToken != nil {
601
+ fieldPaths = append(fieldPaths, "awsSessionToken")
602
+ }
603
+ if fields.Region != nil {
604
+ fieldPaths = append(fieldPaths, "awsRegion")
605
+ }
606
+ if fields.UseCrossRegionInference != nil {
607
+ fieldPaths = append(fieldPaths, "awsUseCrossRegionInference")
608
+ }
609
+ if fields.UseGlobalInference != nil {
610
+ fieldPaths = append(fieldPaths, "awsUseGlobalInference")
611
+ }
612
+ if fields.UsePromptCache != nil {
613
+ fieldPaths = append(fieldPaths, "awsBedrockUsePromptCache")
614
+ }
615
+ if fields.Authentication != nil {
616
+ fieldPaths = append(fieldPaths, "awsAuthentication")
617
+ }
618
+ if fields.UseProfile != nil {
619
+ fieldPaths = append(fieldPaths, "awsUseProfile")
620
+ }
621
+ if fields.Profile != nil {
622
+ fieldPaths = append(fieldPaths, "awsProfile")
623
+ }
624
+ if fields.Endpoint != nil {
625
+ fieldPaths = append(fieldPaths, "awsBedrockEndpoint")
626
+ }
627
+
628
+ return fieldPaths
629
+ }
630
+
631
+ // buildOcaOptionalFieldMask builds field mask paths for Bedrock optional fields that have values
632
+ func buildOcaOptionalFieldMask(fields *OcaOptionalFields) []string {
633
+ if fields == nil {
634
+ return nil
635
+ }
636
+
637
+ var fieldPaths []string
638
+
639
+ if fields.Mode != nil {
640
+ fieldPaths = append(fieldPaths, "ocaMode")
641
+ }
642
+ if fields.BaseURL != nil {
643
+ fieldPaths = append(fieldPaths, "ocaBaseUrl")
644
+ }
645
+
646
+ return fieldPaths
647
+ }