Compare commits

...
Author SHA1 Message Date
abeatrix 1cf0a7f93c feat: auto-generate models.proto
- Add new API key fields to ModelsApiSecrets: auth_nonce, hicap_api_key, nous_research_api_key, remote_lite_llm_api_key, mcp_o_auth_secrets
- Add new configuration options to ModelsApiOptions: gemini thinking levels, hicap_model_id, lm_studio_model_id
- Add plan/act mode specific fields: verbosity, oca_reasoning_effort, hicap_model_id, hicap_model_info, nous_research_model_id
- Change integer field types from int64 to int32 for consistency (fireworks tokens, request_timeout_ms)
- Relocate open_ai_headers map field in Settings message to avoid field number conflicts
- Update proto generation script with improved documentation and field mapping logic

This commit was auto-generated using the generate-state-proto.mjs script to ensure proto definitions stay in sync with TypeScript source of truth.
2026-01-13 10:09:09 -08:00
abeatrix 289824ba2b open_ai_headers 2026-01-13 09:41:04 -08:00
abeatrix f6633f5d4e Merge branch 'bee/auto-gen-state-proto' of https://github.com/cline/cline into bee/auto-gen-state-proto 2026-01-13 09:34:36 -08:00
abeatrix c01d9d9192 fix comment format 2026-01-13 09:34:34 -08:00
Bee 1fc8effe4e Merge branch 'main' into bee/auto-gen-state-proto 2026-01-13 09:31:15 -08:00
abeatrix 969190d2f8 add documentation for proto field generation
Add inline documentation to state.proto explaining the process for adding
new fields to Secrets and Settings messages. Also add a note in state-keys.ts
clarifying that the generate-state-proto.mjs script runs automatically on
commit. Remove redundant sync comment from API_HANDLER_SETTINGS_FIELDS.
2026-01-13 09:30:58 -08:00
abeatrix c80ec4ce17 feat(proto): change auto_condense_threshold type from int32 to double
Changed the auto_condense_threshold field type from int32 to double in the
state.proto file to support decimal values. Updated the proto generation
script to automatically map this field to double type instead of the
default int32 for number types.
2026-01-12 23:41:53 -08:00
abeatrix 866968241a feat(proto): change thinking budget token fields to int64
Change plan_mode_thinking_budget_tokens and act_mode_thinking_budget_tokens
from int32 to int64 to support larger token budget values. Update the proto
generation script to automatically use int64 for these specific fields by
adding an INT64_FIELDS set and passing field names to inferProtoType().

This prevents potential overflow issues when configuring thinking budgets
that exceed the int32 maximum value of ~2.1 billion tokens.
2026-01-12 23:29:36 -08:00
abeatrix e6fd4d47d9 PlanActMode 2026-01-12 23:17:42 -08:00
abeatrix 0be57bf9ef feat: add auto-generation of state proto
Add lint-staged hook to automatically regenerate proto/cline/state.proto
when src/shared/storage/state-keys.ts changes. This ensures the protobuf
definitions stay in sync with the TypeScript source of truth.

Changes:
- Add generate-state-proto.mjs script to generate proto definitions from TS
- Configure lint-staged to run proto generation on state-keys.ts changes
- Update state.proto with regenerated field numbers and new OpenTelemetry fields

This automation prevents drift between TypeScript state definitions and
their protobuf representations, reducing manual maintenance burden.
2026-01-12 22:27:33 -08:00
5 changed files with 885 additions and 175 deletions
+4
View File
@@ -403,6 +403,10 @@
"storybook": "cd webview-ui && npm run storybook"
},
"lint-staged": {
"src/shared/storage/state-keys.ts": [
"node scripts/generate-state-proto.mjs",
"git add proto/cline/state.proto"
],
"*": [
"biome check --write --staged --no-errors-on-unmatched --files-ignore-unknown=true"
]
+22 -3
View File
@@ -183,6 +183,11 @@ message ModelsApiSecrets {
optional string oca_refresh_token = 37;
optional string minimax_api_key = 38;
optional string aihubmix_api_key = 39;
optional string auth_nonce = 40;
optional string hicap_api_key = 41;
optional string nous_research_api_key = 42;
optional string remote_lite_llm_api_key = 43;
optional string mcp_o_auth_secrets = 44;
}
// API configuration options (non-secret settings)
@@ -209,12 +214,12 @@ message ModelsApiOptions {
optional string lm_studio_base_url = 19;
optional string gemini_base_url = 20;
optional string requesty_base_url = 21;
optional int64 fireworks_model_max_completion_tokens = 22;
optional int64 fireworks_model_max_tokens = 23;
optional int32 fireworks_model_max_completion_tokens = 22;
optional int32 fireworks_model_max_tokens = 23;
optional string azure_api_version = 24;
optional string qwen_api_line = 25;
optional string asksage_api_url = 26;
optional int64 request_timeout_ms = 27;
optional int32 request_timeout_ms = 27;
optional string sap_ai_resource_group = 28;
optional string sap_ai_core_token_url = 29;
optional string sap_ai_core_base_url = 30;
@@ -232,6 +237,10 @@ message ModelsApiOptions {
optional string aihubmix_base_url = 42;
optional string aihubmix_app_code = 43;
optional bool azure_identity = 44;
optional string gemini_plan_mode_thinking_level = 45;
optional string gemini_act_mode_thinking_level = 46;
optional string hicap_model_id = 47;
optional string lm_studio_model_id = 48;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
@@ -269,6 +278,11 @@ message ModelsApiOptions {
optional OcaModelInfo plan_mode_oca_model_info = 132;
optional string plan_mode_aihubmix_model_id = 133;
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 134;
optional string plan_mode_verbosity = 135;
optional string plan_mode_oca_reasoning_effort = 136;
optional string plan_mode_hicap_model_id = 137;
optional OpenRouterModelInfo plan_mode_hicap_model_info = 138;
optional string plan_mode_nous_research_model_id = 139;
// Act mode configurations
optional ApiProvider act_mode_api_provider = 200;
@@ -306,6 +320,11 @@ message ModelsApiOptions {
optional OcaModelInfo act_mode_oca_model_info = 232;
optional string act_mode_aihubmix_model_id = 233;
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 234;
optional string act_mode_verbosity = 235;
optional string act_mode_oca_reasoning_effort = 236;
optional string act_mode_hicap_model_id = 237;
optional OpenRouterModelInfo act_mode_hicap_model_info = 238;
optional string act_mode_nous_research_model_id = 239;
}
// Request for updating API configuration (legacy - uses combined configuration)
+216 -171
View File
@@ -54,184 +54,229 @@ message AutoApprovalSettings {
optional bool enable_notifications = 3;
}
// NOTE: Add the new secret fields under SECRETS_KEYS in src/shared/storage/state-keys.ts
// and use the scripts/generate-state-proto.mjs script to regenerate this list.
message Secrets {
optional string api_key = 1;
optional string open_router_api_key = 4;
optional string aws_access_key = 5;
optional string aws_secret_key = 6;
optional string aws_session_token = 7;
optional string aws_bedrock_api_key = 8;
optional string open_ai_api_key = 9;
optional string gemini_api_key = 10;
optional string open_ai_native_api_key = 11;
optional string ollama_api_key = 12;
optional string deep_seek_api_key = 13;
optional string requesty_api_key = 14;
optional string together_api_key = 15;
optional string fireworks_api_key = 16;
optional string qwen_api_key = 17;
optional string doubao_api_key = 18;
optional string mistral_api_key = 19;
optional string lite_llm_api_key = 20;
optional string auth_nonce = 21;
optional string asksage_api_key = 22;
optional string xai_api_key = 23;
optional string moonshot_api_key = 24;
optional string zai_api_key = 25;
optional string hugging_face_api_key = 26;
optional string nebius_api_key = 27;
optional string sambanova_api_key = 28;
optional string cerebras_api_key = 29;
optional string sap_ai_core_client_id = 30;
optional string sap_ai_core_client_secret = 31;
optional string groq_api_key = 32;
optional string huawei_cloud_maas_api_key = 33;
optional string baseten_api_key = 34;
optional string vercel_ai_gateway_api_key = 35;
optional string dify_api_key = 36;
optional string oca_api_key = 37;
optional string oca_refresh_token = 38;
optional string hicap_api_key = 39;
optional string mcp_oauth_secrets = 40;
optional string cline_account_id = 2;
optional string open_router_api_key = 3;
optional string aws_access_key = 4;
optional string aws_secret_key = 5;
optional string aws_session_token = 6;
optional string aws_bedrock_api_key = 7;
optional string open_ai_api_key = 8;
optional string gemini_api_key = 9;
optional string open_ai_native_api_key = 10;
optional string ollama_api_key = 11;
optional string deep_seek_api_key = 12;
optional string requesty_api_key = 13;
optional string together_api_key = 14;
optional string fireworks_api_key = 15;
optional string qwen_api_key = 16;
optional string doubao_api_key = 17;
optional string mistral_api_key = 18;
optional string lite_llm_api_key = 19;
optional string auth_nonce = 20;
optional string asksage_api_key = 21;
optional string xai_api_key = 22;
optional string moonshot_api_key = 23;
optional string zai_api_key = 24;
optional string hugging_face_api_key = 25;
optional string nebius_api_key = 26;
optional string sambanova_api_key = 27;
optional string cerebras_api_key = 28;
optional string sap_ai_core_client_id = 29;
optional string sap_ai_core_client_secret = 30;
optional string groq_api_key = 31;
optional string huawei_cloud_maas_api_key = 32;
optional string baseten_api_key = 33;
optional string vercel_ai_gateway_api_key = 34;
optional string dify_api_key = 35;
optional string minimax_api_key = 36;
optional string hicap_api_key = 37;
optional string aihubmix_api_key = 38;
optional string nous_research_api_key = 39;
optional string remote_lite_llm_api_key = 40;
optional string oca_api_key = 41;
optional string oca_refresh_token = 42;
optional string mcp_o_auth_secrets = 43;
}
// NOTE: Add new fields under API_HANDLER_SETTINGS_FIELDS or USER_SETTINGS_FIELDS
// in src/shared/storage/state-keys.ts and use the scripts/generate-state-proto.mjs
// script to regenerate this list.
message Settings {
optional string aws_region = 1;
optional bool aws_use_cross_region_inference = 2;
optional bool aws_bedrock_use_prompt_cache = 3;
optional string aws_bedrock_endpoint = 4;
optional string aws_profile = 5;
optional string aws_authentication = 6;
optional bool aws_use_profile = 7;
optional string vertex_project_id = 8;
optional string vertex_region = 9;
optional string requesty_base_url = 10;
optional string open_ai_base_url = 11;
// map<string, string> open_ai_headers = 12;
optional string ollama_base_url = 13;
optional string ollama_api_options_ctx_num = 14;
optional string lm_studio_base_url = 15;
optional string lm_studio_max_tokens = 16;
optional string anthropic_base_url = 17;
optional string gemini_base_url = 18;
optional string azure_api_version = 19;
optional string open_router_provider_sorting = 20;
optional AutoApprovalSettings auto_approval_settings = 21;
optional BrowserSettings browser_settings = 24;
optional string lite_llm_base_url = 25;
optional bool lite_llm_use_prompt_cache = 26;
optional int32 fireworks_model_max_completion_tokens = 27;
optional int32 fireworks_model_max_tokens = 28;
optional string lite_llm_base_url = 1;
optional bool lite_llm_use_prompt_cache = 2;
optional string anthropic_base_url = 4;
optional string open_router_provider_sorting = 5;
optional string aws_region = 6;
optional bool aws_use_cross_region_inference = 7;
optional bool aws_use_global_inference = 8;
optional bool aws_bedrock_use_prompt_cache = 9;
optional string aws_authentication = 10;
optional bool aws_use_profile = 11;
optional string aws_profile = 12;
optional string aws_bedrock_endpoint = 13;
optional string claude_code_path = 14;
optional string vertex_project_id = 15;
optional string vertex_region = 16;
optional string open_ai_base_url = 17;
optional string ollama_base_url = 18;
optional string ollama_api_options_ctx_num = 19;
optional string lm_studio_base_url = 20;
optional string lm_studio_max_tokens = 21;
optional string gemini_base_url = 22;
optional string requesty_base_url = 23;
optional int32 fireworks_model_max_completion_tokens = 24;
optional int32 fireworks_model_max_tokens = 25;
optional string qwen_code_oauth_path = 26;
optional string azure_api_version = 27;
optional bool azure_identity = 28;
optional string qwen_api_line = 29;
optional string moonshot_api_line = 30;
optional string zai_api_line = 31;
optional string telemetry_setting = 32;
optional string asksage_api_url = 33;
optional bool plan_act_separate_models_setting = 34;
optional bool enable_checkpoints_setting = 35;
optional int32 request_timeout_ms = 36;
optional int32 shell_integration_timeout = 37;
optional string default_terminal_profile = 38;
optional int32 terminal_output_line_limit = 39;
optional string sap_ai_core_token_url = 40;
optional string sap_ai_core_base_url = 41;
optional string sap_ai_resource_group = 42;
optional bool sap_ai_core_use_orchestration_mode = 43;
optional string claude_code_path = 44;
optional string qwen_code_oauth_path = 45;
optional bool strict_plan_mode_enabled = 46;
optional bool yolo_mode_toggled = 47;
optional bool use_auto_condense = 48;
optional string preferred_language = 49;
optional OpenaiReasoningEffort openai_reasoning_effort = 50;
optional PlanActMode mode = 51;
optional DictationSettings dictation_settings = 52;
optional FocusChainSettings focus_chain_settings = 53;
optional string custom_prompt = 54;
optional string dify_base_url = 55;
optional double auto_condense_threshold = 56;
optional string oca_base_url = 57;
optional ApiProvider plan_mode_api_provider = 58;
optional string plan_mode_api_model_id = 59;
optional int64 plan_mode_thinking_budget_tokens = 60;
optional string plan_mode_reasoning_effort = 61;
optional LanguageModelChatSelector plan_mode_vs_code_lm_model_selector = 62;
optional bool plan_mode_aws_bedrock_custom_selected = 63;
optional string plan_mode_aws_bedrock_custom_model_base_id = 64;
optional string plan_mode_open_router_model_id = 65;
optional OpenRouterModelInfo plan_mode_open_router_model_info = 66;
optional string plan_mode_open_ai_model_id = 67;
optional OpenAiCompatibleModelInfo plan_mode_open_ai_model_info = 68;
optional string plan_mode_ollama_model_id = 69;
optional string plan_mode_lm_studio_model_id = 70;
optional string plan_mode_lite_llm_model_id = 71;
optional LiteLLMModelInfo plan_mode_lite_llm_model_info = 72;
optional string plan_mode_requesty_model_id = 73;
optional OpenRouterModelInfo plan_mode_requesty_model_info = 74;
optional string plan_mode_together_model_id = 75;
optional string plan_mode_fireworks_model_id = 76;
optional string plan_mode_sap_ai_core_model_id = 77;
optional string plan_mode_sap_ai_core_deployment_id = 78;
optional string plan_mode_groq_model_id = 79;
optional OpenRouterModelInfo plan_mode_groq_model_info = 80;
optional string plan_mode_baseten_model_id = 81;
optional OpenRouterModelInfo plan_mode_baseten_model_info = 82;
optional string plan_mode_hugging_face_model_id = 83;
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 84;
optional string plan_mode_huawei_cloud_maas_model_id = 85;
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 86;
optional string plan_mode_oca_model_id = 87;
optional OcaModelInfo plan_mode_oca_model_info = 88;
optional ApiProvider act_mode_api_provider = 89;
optional string act_mode_api_model_id = 90;
optional int64 act_mode_thinking_budget_tokens = 91;
optional string act_mode_reasoning_effort = 92;
optional LanguageModelChatSelector act_mode_vs_code_lm_model_selector = 93;
optional bool act_mode_aws_bedrock_custom_selected = 94;
optional string act_mode_aws_bedrock_custom_model_base_id = 95;
optional string act_mode_open_router_model_id = 96;
optional OpenRouterModelInfo act_mode_open_router_model_info = 97;
optional string act_mode_open_ai_model_id = 98;
optional OpenAiCompatibleModelInfo act_mode_open_ai_model_info = 99;
optional string act_mode_ollama_model_id = 100;
optional string act_mode_lm_studio_model_id = 101;
optional string act_mode_lite_llm_model_id = 102;
optional LiteLLMModelInfo act_mode_lite_llm_model_info = 103;
optional string act_mode_requesty_model_id = 104;
optional OpenRouterModelInfo act_mode_requesty_model_info = 105;
optional string act_mode_together_model_id = 106;
optional string act_mode_fireworks_model_id = 107;
optional string act_mode_sap_ai_core_model_id = 108;
optional string act_mode_sap_ai_core_deployment_id = 109;
optional string act_mode_groq_model_id = 110;
optional OpenRouterModelInfo act_mode_groq_model_info = 111;
optional string act_mode_baseten_model_id = 112;
optional OpenRouterModelInfo act_mode_baseten_model_info = 113;
optional string act_mode_hugging_face_model_id = 114;
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 115;
optional string act_mode_huawei_cloud_maas_model_id = 116;
optional OpenRouterModelInfo act_mode_huawei_cloud_maas_model_info = 117;
optional string plan_mode_vercel_ai_gateway_model_id = 118;
optional OpenRouterModelInfo plan_mode_vercel_ai_gateway_model_info = 119;
optional string act_mode_vercel_ai_gateway_model_id = 120;
optional OpenRouterModelInfo act_mode_vercel_ai_gateway_model_info = 121;
optional string act_mode_oca_model_id = 122;
optional OcaModelInfo act_mode_oca_model_info = 123;
optional int32 max_consecutive_mistakes = 124;
optional bool subagents_enabled = 125;
optional int32 subagent_terminal_output_line_limit = 126;
optional string aihubmix_api_key = 127;
optional string aihubmix_base_url = 128;
optional string aihubmix_app_code = 129;
optional string plan_mode_aihubmix_model_id = 130;
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 131;
optional string act_mode_aihubmix_model_id = 132;
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 133;
optional bool cline_web_tools_enabled = 134;
optional bool hooks_enabled = 135;
optional bool azure_identity = 136;
optional bool skills_enabled = 137;
optional bool opt_out_of_remote_config = 138;
optional string asksage_api_url = 31;
optional int32 request_timeout_ms = 32;
optional string sap_ai_resource_group = 33;
optional string sap_ai_core_token_url = 34;
optional string sap_ai_core_base_url = 35;
optional bool sap_ai_core_use_orchestration_mode = 36;
optional string dify_base_url = 37;
optional string zai_api_line = 38;
optional string oca_base_url = 39;
optional string minimax_api_line = 40;
optional string oca_mode = 41;
optional string aihubmix_base_url = 42;
optional string aihubmix_app_code = 43;
optional string plan_mode_api_model_id = 44;
optional int64 plan_mode_thinking_budget_tokens = 45;
optional string gemini_plan_mode_thinking_level = 46;
optional string plan_mode_reasoning_effort = 47;
optional string plan_mode_verbosity = 48;
optional LanguageModelChatSelector plan_mode_vs_code_lm_model_selector = 49;
optional bool plan_mode_aws_bedrock_custom_selected = 50;
optional string plan_mode_aws_bedrock_custom_model_base_id = 51;
optional string plan_mode_open_router_model_id = 52;
optional OpenRouterModelInfo plan_mode_open_router_model_info = 53;
optional string plan_mode_open_ai_model_id = 54;
optional OpenAiCompatibleModelInfo plan_mode_open_ai_model_info = 55;
optional string plan_mode_ollama_model_id = 56;
optional string plan_mode_lm_studio_model_id = 57;
optional string plan_mode_lite_llm_model_id = 58;
optional LiteLLMModelInfo plan_mode_lite_llm_model_info = 59;
optional string plan_mode_requesty_model_id = 60;
optional OpenRouterModelInfo plan_mode_requesty_model_info = 61;
optional string plan_mode_together_model_id = 62;
optional string plan_mode_fireworks_model_id = 63;
optional string plan_mode_sap_ai_core_model_id = 64;
optional string plan_mode_sap_ai_core_deployment_id = 65;
optional string plan_mode_groq_model_id = 66;
optional OpenRouterModelInfo plan_mode_groq_model_info = 67;
optional string plan_mode_baseten_model_id = 68;
optional OpenRouterModelInfo plan_mode_baseten_model_info = 69;
optional string plan_mode_hugging_face_model_id = 70;
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 71;
optional string plan_mode_huawei_cloud_maas_model_id = 72;
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 73;
optional string plan_mode_oca_model_id = 74;
optional OcaModelInfo plan_mode_oca_model_info = 75;
optional string plan_mode_oca_reasoning_effort = 76;
optional string plan_mode_aihubmix_model_id = 77;
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 78;
optional string plan_mode_hicap_model_id = 79;
optional OpenRouterModelInfo plan_mode_hicap_model_info = 80;
optional string plan_mode_nous_research_model_id = 81;
optional string plan_mode_vercel_ai_gateway_model_id = 82;
optional OpenRouterModelInfo plan_mode_vercel_ai_gateway_model_info = 83;
optional string act_mode_api_model_id = 84;
optional int64 act_mode_thinking_budget_tokens = 85;
optional string gemini_act_mode_thinking_level = 86;
optional string act_mode_reasoning_effort = 87;
optional string act_mode_verbosity = 88;
optional LanguageModelChatSelector act_mode_vs_code_lm_model_selector = 89;
optional bool act_mode_aws_bedrock_custom_selected = 90;
optional string act_mode_aws_bedrock_custom_model_base_id = 91;
optional string act_mode_open_router_model_id = 92;
optional OpenRouterModelInfo act_mode_open_router_model_info = 93;
optional string act_mode_open_ai_model_id = 94;
optional OpenAiCompatibleModelInfo act_mode_open_ai_model_info = 95;
optional string act_mode_ollama_model_id = 96;
optional string act_mode_lm_studio_model_id = 97;
optional string act_mode_lite_llm_model_id = 98;
optional LiteLLMModelInfo act_mode_lite_llm_model_info = 99;
optional string act_mode_requesty_model_id = 100;
optional OpenRouterModelInfo act_mode_requesty_model_info = 101;
optional string act_mode_together_model_id = 102;
optional string act_mode_fireworks_model_id = 103;
optional string act_mode_sap_ai_core_model_id = 104;
optional string act_mode_sap_ai_core_deployment_id = 105;
optional string act_mode_groq_model_id = 106;
optional OpenRouterModelInfo act_mode_groq_model_info = 107;
optional string act_mode_baseten_model_id = 108;
optional OpenRouterModelInfo act_mode_baseten_model_info = 109;
optional string act_mode_hugging_face_model_id = 110;
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 111;
optional string act_mode_huawei_cloud_maas_model_id = 112;
optional OpenRouterModelInfo act_mode_huawei_cloud_maas_model_info = 113;
optional string act_mode_oca_model_id = 114;
optional OcaModelInfo act_mode_oca_model_info = 115;
optional string act_mode_oca_reasoning_effort = 116;
optional string act_mode_aihubmix_model_id = 117;
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 118;
optional string act_mode_hicap_model_id = 119;
optional OpenRouterModelInfo act_mode_hicap_model_info = 120;
optional string act_mode_nous_research_model_id = 121;
optional string act_mode_vercel_ai_gateway_model_id = 122;
optional OpenRouterModelInfo act_mode_vercel_ai_gateway_model_info = 123;
optional ApiProvider plan_mode_api_provider = 124;
optional ApiProvider act_mode_api_provider = 125;
optional string hicap_model_id = 126;
optional string lm_studio_model_id = 127;
optional AutoApprovalSettings auto_approval_settings = 128;
optional string global_cline_rules_toggles = 129;
optional string global_workflow_toggles = 130;
optional string global_skills_toggles = 131;
optional BrowserSettings browser_settings = 132;
optional string telemetry_setting = 133;
optional bool plan_act_separate_models_setting = 134;
optional bool enable_checkpoints_setting = 135;
optional int32 shell_integration_timeout = 136;
optional string default_terminal_profile = 137;
optional int32 terminal_output_line_limit = 138;
optional int32 max_consecutive_mistakes = 139;
optional int32 subagent_terminal_output_line_limit = 140;
optional bool strict_plan_mode_enabled = 141;
optional bool yolo_mode_toggled = 142;
optional bool use_auto_condense = 143;
optional bool cline_web_tools_enabled = 144;
optional string preferred_language = 145;
optional OpenaiReasoningEffort openai_reasoning_effort = 146;
optional PlanActMode mode = 147;
optional DictationSettings dictation_settings = 148;
optional FocusChainSettings focus_chain_settings = 149;
optional string custom_prompt = 150;
optional double auto_condense_threshold = 151;
optional bool hooks_enabled = 152;
optional bool subagents_enabled = 153;
optional bool enable_parallel_tool_calling = 154;
optional bool background_edit_enabled = 155;
optional bool skills_enabled = 156;
optional bool opt_out_of_remote_config = 157;
optional bool open_telemetry_enabled = 158;
optional string open_telemetry_metrics_exporter = 159;
optional string open_telemetry_logs_exporter = 160;
optional string open_telemetry_otlp_protocol = 161;
optional string open_telemetry_otlp_endpoint = 162;
optional string open_telemetry_otlp_metrics_protocol = 163;
optional string open_telemetry_otlp_metrics_endpoint = 164;
optional string open_telemetry_otlp_logs_protocol = 165;
optional string open_telemetry_otlp_logs_endpoint = 166;
optional int32 open_telemetry_metric_export_interval = 167;
optional bool open_telemetry_otlp_insecure = 168;
optional int32 open_telemetry_log_batch_size = 169;
optional int32 open_telemetry_log_batch_timeout = 170;
optional int32 open_telemetry_log_max_queue_size = 171;
map<string, string> open_ai_headers = 172;
}
message DictationSettings {
+640
View File
@@ -0,0 +1,640 @@
#!/usr/bin/env node
/**
* Generates proto message definitions from TypeScript source of truth.
*
* This script reads the field definitions from src/shared/storage/state-keys.ts
* and generates the corresponding proto message definitions:
*
* - proto/cline/state.proto:
* - Secrets: Generated from SECRETS_KEYS
* - Settings: Generated from API_HANDLER_SETTINGS_FIELDS + USER_SETTINGS_FIELDS
*
* - proto/cline/models.proto:
* - ModelsApiSecrets: Generated from SECRETS_KEYS
* - ModelsApiOptions: Generated from API_HANDLER_SETTINGS_FIELDS with:
* - Extra 'ulid' field at position 1 (not from state-keys.ts)
* - Global fields at positions 2-99
* - Plan mode fields at positions 100+
* - Act mode fields at positions 200+
*
* Usage: node scripts/generate-state-proto.mjs
*
* The script preserves existing field numbers to maintain backward compatibility.
*/
import * as fs from "node:fs/promises"
import { Project, SyntaxKind } from "ts-morph"
const STATE_KEYS_PATH = "src/shared/storage/state-keys.ts"
const STATE_PROTO_PATH = "proto/cline/state.proto"
const MODELS_PROTO_PATH = "proto/cline/models.proto"
/**
* Convert camelCase to snake_case for proto field names
*/
function camelToSnake(str) {
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
}
// Fields that should use int64 instead of int32
const INT64_FIELDS = new Set(["planModeThinkingBudgetTokens", "actModeThinkingBudgetTokens"])
// Fields that should use double instead of int32
const DOUBLE_FIELDS = new Set(["autoCondenseThreshold"])
/**
* Infer proto type from TypeScript type expression
* @param {string} typeText - The TypeScript type expression
* @param {string} [fieldName] - Optional field name for field-specific overrides
*/
function inferProtoType(typeText, fieldName) {
// Remove 'undefined' from union types
const cleanType = typeText
.replace(/\s*\|\s*undefined/g, "")
.replace(/undefined\s*\|\s*/g, "")
.trim()
// Handle common types
if (cleanType === "string") {
return "string"
}
if (cleanType === "boolean") {
return "bool"
}
if (cleanType === "number") {
// Some number fields need specific numeric types
if (fieldName && INT64_FIELDS.has(fieldName)) {
return "int64"
}
if (fieldName && DOUBLE_FIELDS.has(fieldName)) {
return "double"
}
return "int32"
}
// Handle Record<string, string> as map<string, string>
if (/Record\s*<\s*string\s*,\s*string\s*>/.test(cleanType)) {
return "map<string, string>"
}
// Handle specific known types that map to proto messages/enums
// Order matters! More specific types must come before generic ones
// (e.g., OpenAiCompatibleModelInfo before ModelInfo)
// Check known types BEFORE string literals, since types like `"act" as Mode`
// contain quotes but should map to proto enums
const knownTypes = [
// Specific model info types first
["OpenAiCompatibleModelInfo", "OpenAiCompatibleModelInfo"],
["LiteLLMModelInfo", "LiteLLMModelInfo"],
["OcaModelInfo", "OcaModelInfo"],
// Generic ModelInfo last (catches OpenRouterModelInfo, etc.)
["ModelInfo", "OpenRouterModelInfo"],
// Other types - order matters for substring matching
["AutoApprovalSettings", "AutoApprovalSettings"],
["BrowserSettings", "BrowserSettings"],
["DictationSettings", "DictationSettings"],
["FocusChainSettings", "FocusChainSettings"],
["OpenaiReasoningEffort", "OpenaiReasoningEffort"],
["PlanActMode", "PlanActMode"],
["ApiProvider", "ApiProvider"],
["LanguageModelChatSelector", "LanguageModelChatSelector"], // Must come before "Mode" check
]
for (const [tsType, protoType] of knownTypes) {
if (cleanType.includes(tsType)) {
return protoType
}
}
// Check for Mode type separately with word boundary to avoid matching "VsCodeLmModelSelector"
// This handles TS `Mode` type which maps to proto `PlanActMode`
if (/\bMode\b/.test(cleanType)) {
return "PlanActMode"
}
// Handle specific string literal unions (treat as string)
// This comes after known types check since some types like `"act" as Mode` contain quotes
if (cleanType.includes('"') || cleanType.includes("'")) {
return "string"
}
// Default to string for complex types we can't map
return "string"
}
/**
* Parse the SECRETS_KEYS array from state-keys.ts
*/
function parseSecretsKeys(sourceFile) {
const secretsDecl = sourceFile.getVariableDeclaration("SECRETS_KEYS")
if (!secretsDecl) {
throw new Error("Could not find SECRETS_KEYS declaration")
}
let initializer = secretsDecl.getInitializer()
if (!initializer) {
throw new Error("SECRETS_KEYS has no initializer")
}
// Handle 'as const' expression
if (initializer.getKind() === SyntaxKind.AsExpression) {
initializer = initializer.getExpression()
}
if (initializer.getKind() !== SyntaxKind.ArrayLiteralExpression) {
throw new Error(`SECRETS_KEYS is not an array literal (got ${SyntaxKind[initializer.getKind()]})`)
}
const keys = []
for (const element of initializer.getElements()) {
const text = element.getText()
// Remove quotes and handle special prefixes
const key = text.replace(/^['"]|['"]$/g, "")
// Skip prefixed keys like "cline:clineAccountId"
if (!key.includes(":")) {
keys.push(key)
}
}
return keys
}
/**
* Parse field definitions from an object literal in state-keys.ts
*/
function parseFieldDefinitions(sourceFile, variableName) {
const decl = sourceFile.getVariableDeclaration(variableName)
if (!decl) {
throw new Error(`Could not find ${variableName} declaration`)
}
const initializer = decl.getInitializer()
if (!initializer) {
throw new Error(`${variableName} has no initializer`)
}
// Handle 'satisfies' expression
let objectLiteral = initializer
if (initializer.getKind() === SyntaxKind.SatisfiesExpression) {
objectLiteral = initializer.getExpression()
}
if (objectLiteral.getKind() !== SyntaxKind.ObjectLiteralExpression) {
throw new Error(`${variableName} is not an object literal`)
}
const fields = []
for (const prop of objectLiteral.getProperties()) {
if (prop.getKind() !== SyntaxKind.PropertyAssignment) {
continue
}
const name = prop.getName()
const propInit = prop.getInitializer()
if (!propInit || propInit.getKind() !== SyntaxKind.ObjectLiteralExpression) {
continue
}
// Get the 'default' property to infer the type
const defaultProp = propInit.getProperty("default")
if (!defaultProp) {
continue
}
let typeText = "string"
const defaultInit = defaultProp.getInitializer()
if (defaultInit) {
// Check for 'as' expression to get the type
if (defaultInit.getKind() === SyntaxKind.AsExpression) {
const typeNode = defaultInit.getTypeNode()
if (typeNode) {
typeText = typeNode.getText()
}
} else {
// Infer from literal
const text = defaultInit.getText()
if (text === "true" || text === "false") {
typeText = "boolean"
} else if (/^\d+$/.test(text)) {
typeText = "number"
} else if (/^\d+\.\d+$/.test(text)) {
typeText = "number"
}
}
}
fields.push({
name,
tsType: typeText,
protoType: inferProtoType(typeText, name),
})
}
return fields
}
/**
* Convert snake_case to camelCase for mapping proto fields back to TS keys
*/
function snakeToCamel(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
}
/**
* Parse field numbers from an existing proto message definition
* Returns a map of camelCase field names to their field numbers
*/
function parseProtoMessageFieldNumbers(protoContent, messageName) {
const fieldNumbers = {}
// Match the message block (handles single-level nesting for now)
const messageRegex = new RegExp(`message\\s+${messageName}\\s*\\{([^}]*(?:\\{[^}]*\\}[^}]*)*)\\}`, "s")
const match = protoContent.match(messageRegex)
if (!match) {
return fieldNumbers
}
const messageBody = match[1]
// Match regular field definitions: optional/required/repeated type name = number;
const fieldRegex = /(?:optional|required|repeated)?\s*\w+\s+(\w+)\s*=\s*(\d+)\s*;/g
const matches = messageBody.matchAll(fieldRegex)
for (const fieldMatch of matches) {
const snakeName = fieldMatch[1]
const fieldNum = parseInt(fieldMatch[2], 10)
const camelName = snakeToCamel(snakeName)
fieldNumbers[camelName] = fieldNum
}
// Match map field definitions: map<type, type> name = number;
const mapFieldRegex = /map\s*<[^>]+>\s+(\w+)\s*=\s*(\d+)\s*;/g
const mapMatches = messageBody.matchAll(mapFieldRegex)
for (const mapMatch of mapMatches) {
const snakeName = mapMatch[1]
const fieldNum = parseInt(mapMatch[2], 10)
const camelName = snakeToCamel(snakeName)
fieldNumbers[camelName] = fieldNum
}
return fieldNumbers
}
/**
* Load field number mappings from existing proto file
*/
async function loadFieldNumbersFromProto() {
try {
const protoContent = await fs.readFile(STATE_PROTO_PATH, "utf-8")
const secrets = parseProtoMessageFieldNumbers(protoContent, "Secrets")
const settings = parseProtoMessageFieldNumbers(protoContent, "Settings")
console.log(` Found ${Object.keys(secrets).length} existing Secrets fields`)
console.log(` Found ${Object.keys(settings).length} existing Settings fields`)
return { Secrets: secrets, Settings: settings }
} catch {
// Proto file doesn't exist, start fresh
return { Secrets: {}, Settings: {} }
}
}
/**
* Load field number mappings from existing models.proto file
*/
async function loadFieldNumbersFromModelsProto() {
try {
const protoContent = await fs.readFile(MODELS_PROTO_PATH, "utf-8")
const modelsApiSecrets = parseProtoMessageFieldNumbers(protoContent, "ModelsApiSecrets")
const modelsApiOptions = parseProtoMessageFieldNumbers(protoContent, "ModelsApiOptions")
console.log(` Found ${Object.keys(modelsApiSecrets).length} existing ModelsApiSecrets fields`)
console.log(` Found ${Object.keys(modelsApiOptions).length} existing ModelsApiOptions fields`)
return { ModelsApiSecrets: modelsApiSecrets, ModelsApiOptions: modelsApiOptions }
} catch {
// Proto file doesn't exist, start fresh
return { ModelsApiSecrets: {}, ModelsApiOptions: {} }
}
}
/**
* Assign field numbers, preserving existing assignments and adding new ones
*/
function assignFieldNumbers(fields, existingNumbers, startNumber = 1) {
const result = {}
let nextNumber = startNumber
// Find the highest existing number
for (const num of Object.values(existingNumbers)) {
if (num >= nextNumber) {
nextNumber = num + 1
}
}
// Preserve existing assignments
for (const field of fields) {
if (existingNumbers[field.name] !== undefined) {
result[field.name] = existingNumbers[field.name]
}
}
// Assign new numbers for new fields
for (const field of fields) {
if (result[field.name] === undefined) {
result[field.name] = nextNumber++
}
}
return result
}
/**
* Generate proto message definition
*/
function generateProtoMessage(messageName, fields, fieldNumbers) {
const lines = [`message ${messageName} {`]
// Sort fields by field number for consistent output
const sortedFields = [...fields].sort((a, b) => fieldNumbers[a.name] - fieldNumbers[b.name])
for (const field of sortedFields) {
const snakeName = camelToSnake(field.name)
const fieldNum = fieldNumbers[field.name]
// Map types cannot have the 'optional' modifier in proto3
const prefix = field.protoType.startsWith("map<") ? "" : "optional "
lines.push(` ${prefix}${field.protoType} ${snakeName} = ${fieldNum};`)
}
lines.push("}")
return lines.join("\n")
}
/**
* Generate Secrets message from SECRETS_KEYS
*/
function generateSecretsMessage(secretsKeys, fieldNumbers) {
const fields = secretsKeys.map((key) => ({
name: key,
protoType: "string",
}))
return generateProtoMessage("Secrets", fields, fieldNumbers)
}
// Extra fields for ModelsApiSecrets that are not in SECRETS_KEYS
// These fields exist in the proto but not in the TypeScript source
const MODELS_API_SECRETS_EXTRA_FIELDS = ["clineApiKey"]
/**
* Generate ModelsApiSecrets message from SECRETS_KEYS
* This is similar to Secrets but includes extra fields like clineApiKey
*/
function generateModelsApiSecretsMessage(secretsKeys, fieldNumbers) {
// Start with extra fields that aren't in SECRETS_KEYS
const fields = MODELS_API_SECRETS_EXTRA_FIELDS.map((name) => ({
name,
protoType: "string",
}))
// Add all fields from SECRETS_KEYS
for (const key of secretsKeys) {
fields.push({
name: key,
protoType: "string",
})
}
return generateProtoMessage("ModelsApiSecrets", fields, fieldNumbers)
}
/**
* Categorize API handler fields into global, plan mode, and act mode
*/
function categorizeApiHandlerFields(fields) {
const globalFields = []
const planModeFields = []
const actModeFields = []
for (const field of fields) {
if (field.name.startsWith("planMode")) {
planModeFields.push(field)
} else if (field.name.startsWith("actMode")) {
actModeFields.push(field)
} else {
globalFields.push(field)
}
}
return { globalFields, planModeFields, actModeFields }
}
/**
* Generate ModelsApiOptions message with sections for global, plan mode, and act mode fields
* Includes an extra 'ulid' field at position 1 that's not in state-keys.ts
*/
function generateModelsApiOptionsMessage(apiHandlerFields, existingFieldNumbers) {
const { globalFields, planModeFields, actModeFields } = categorizeApiHandlerFields(apiHandlerFields)
// Assign field numbers with specific ranges:
// - ulid: 1 (extra field not from state-keys.ts)
// - Global fields: 2-99
// - Plan mode fields: 100+
// - Act mode fields: 200+
const lines = ["message ModelsApiOptions {"]
lines.push(" // Global configuration fields (not mode-specific)")
// Always include ulid as field 1 (extra field)
lines.push(" optional string ulid = 1;")
// Assign numbers to global fields starting at 2
const globalFieldNumbers = assignFieldNumbersForSection(globalFields, existingFieldNumbers, 2)
const sortedGlobalFields = [...globalFields].sort((a, b) => globalFieldNumbers[a.name] - globalFieldNumbers[b.name])
for (const field of sortedGlobalFields) {
const snakeName = camelToSnake(field.name)
const fieldNum = globalFieldNumbers[field.name]
const prefix = field.protoType.startsWith("map<") ? "" : "optional "
lines.push(` ${prefix}${field.protoType} ${snakeName} = ${fieldNum};`)
}
lines.push("")
lines.push(" // Plan mode configurations")
// Assign numbers to plan mode fields starting at 100
const planFieldNumbers = assignFieldNumbersForSection(planModeFields, existingFieldNumbers, 100)
const sortedPlanFields = [...planModeFields].sort((a, b) => planFieldNumbers[a.name] - planFieldNumbers[b.name])
for (const field of sortedPlanFields) {
const snakeName = camelToSnake(field.name)
const fieldNum = planFieldNumbers[field.name]
const prefix = field.protoType.startsWith("map<") ? "" : "optional "
lines.push(` ${prefix}${field.protoType} ${snakeName} = ${fieldNum};`)
}
lines.push("")
lines.push(" // Act mode configurations")
// Assign numbers to act mode fields starting at 200
const actFieldNumbers = assignFieldNumbersForSection(actModeFields, existingFieldNumbers, 200)
const sortedActFields = [...actModeFields].sort((a, b) => actFieldNumbers[a.name] - actFieldNumbers[b.name])
for (const field of sortedActFields) {
const snakeName = camelToSnake(field.name)
const fieldNum = actFieldNumbers[field.name]
const prefix = field.protoType.startsWith("map<") ? "" : "optional "
lines.push(` ${prefix}${field.protoType} ${snakeName} = ${fieldNum};`)
}
lines.push("}")
return lines.join("\n")
}
/**
* Assign field numbers for a section, preserving existing assignments
*/
function assignFieldNumbersForSection(fields, existingNumbers, startNumber) {
const result = {}
let nextNumber = startNumber
// Find the highest existing number in this section's range
for (const field of fields) {
if (existingNumbers[field.name] !== undefined) {
const num = existingNumbers[field.name]
if (num >= nextNumber) {
nextNumber = num + 1
}
}
}
// Preserve existing assignments
for (const field of fields) {
if (existingNumbers[field.name] !== undefined) {
result[field.name] = existingNumbers[field.name]
}
}
// Assign new numbers for new fields
for (const field of fields) {
if (result[field.name] === undefined) {
result[field.name] = nextNumber++
}
}
return result
}
/**
* Replace a message in the proto file content
*/
function replaceMessage(protoContent, messageName, newMessageContent) {
// Match the message definition including nested braces
const messageRegex = new RegExp(`message\\s+${messageName}\\s*\\{[^}]*(?:\\{[^}]*\\}[^}]*)*\\}`, "g")
if (messageRegex.test(protoContent)) {
return protoContent.replace(messageRegex, newMessageContent)
} else {
// Message doesn't exist, append before the first message or at end
console.warn(`Warning: ${messageName} message not found in proto file, appending`)
return protoContent + "\n\n" + newMessageContent
}
}
async function main() {
console.log("Generating proto definitions from TypeScript source...")
// Parse TypeScript source
const project = new Project({
tsConfigFilePath: "tsconfig.json",
})
const sourceFile = project.addSourceFileAtPath(STATE_KEYS_PATH)
// Parse definitions
const secretsKeys = parseSecretsKeys(sourceFile)
console.log(`Found ${secretsKeys.length} secret keys`)
const apiHandlerFields = parseFieldDefinitions(sourceFile, "API_HANDLER_SETTINGS_FIELDS")
const userSettingsFields = parseFieldDefinitions(sourceFile, "USER_SETTINGS_FIELDS")
const settingsFields = [...apiHandlerFields, ...userSettingsFields]
console.log(`Found ${settingsFields.length} settings fields`)
console.log(`Found ${apiHandlerFields.length} API handler fields`)
// ========================================
// Generate state.proto messages
// ========================================
console.log("\nGenerating state.proto messages...")
// Load existing field numbers from proto file
const existingFieldNumbers = await loadFieldNumbersFromProto()
// Assign field numbers (preserving existing, adding new ones)
const secretsFieldNumbers = assignFieldNumbers(
secretsKeys.map((k) => ({ name: k })),
existingFieldNumbers.Secrets,
1,
)
const settingsFieldNumbers = assignFieldNumbers(settingsFields, existingFieldNumbers.Settings, 1)
// Generate messages
const secretsMessage = generateSecretsMessage(secretsKeys, secretsFieldNumbers)
const settingsMessage = generateProtoMessage("Settings", settingsFields, settingsFieldNumbers)
// Read existing proto file
let protoContent = await fs.readFile(STATE_PROTO_PATH, "utf-8")
// Replace messages
protoContent = replaceMessage(protoContent, "Secrets", secretsMessage)
protoContent = replaceMessage(protoContent, "Settings", settingsMessage)
// Write updated proto file
await fs.writeFile(STATE_PROTO_PATH, protoContent)
console.log(`Updated ${STATE_PROTO_PATH}`)
// ========================================
// Generate models.proto messages
// ========================================
console.log("\nGenerating models.proto messages...")
// Load existing field numbers from models.proto
const existingModelsFieldNumbers = await loadFieldNumbersFromModelsProto()
// Assign field numbers for ModelsApiSecrets (preserving existing, adding new ones)
// Include extra fields like 'clineApiKey' that aren't in SECRETS_KEYS
const modelsApiSecretsFields = [
...MODELS_API_SECRETS_EXTRA_FIELDS.map((name) => ({ name })),
...secretsKeys.map((k) => ({ name: k })),
]
const modelsApiSecretsFieldNumbers = assignFieldNumbers(
modelsApiSecretsFields,
existingModelsFieldNumbers.ModelsApiSecrets,
1,
)
// Generate ModelsApiSecrets message
const modelsApiSecretsMessage = generateModelsApiSecretsMessage(secretsKeys, modelsApiSecretsFieldNumbers)
// Generate ModelsApiOptions message
const modelsApiOptionsMessage = generateModelsApiOptionsMessage(apiHandlerFields, existingModelsFieldNumbers.ModelsApiOptions)
// Read existing models.proto file
let modelsProtoContent = await fs.readFile(MODELS_PROTO_PATH, "utf-8")
// Replace messages
modelsProtoContent = replaceMessage(modelsProtoContent, "ModelsApiSecrets", modelsApiSecretsMessage)
modelsProtoContent = replaceMessage(modelsProtoContent, "ModelsApiOptions", modelsApiOptionsMessage)
// Write updated models.proto file
await fs.writeFile(MODELS_PROTO_PATH, modelsProtoContent)
console.log(`Updated ${MODELS_PROTO_PATH}`)
console.log("\nGeneration complete! Run 'npm run protos' to regenerate TypeScript from protos.")
}
main().catch((error) => {
console.error("Error:", error)
process.exit(1)
})
+3 -1
View File
@@ -25,6 +25,9 @@ import { LanguageModelChatSelector } from "vscode"
// SINGLE SOURCE OF TRUTH FOR STORAGE KEYS
//
// Property definitions with types, default values, and metadata
// NOTE: When adding a new field, the scripts/generate-state-proto.mjs will be
// executed automatically to regenerate the proto/cline/state.proto file with the
// new fields once the file is staged and committed.
// ============================================================================
/**
@@ -83,7 +86,6 @@ const GLOBAL_STATE_FIELDS = {
} satisfies FieldDefinitions
// Fields that map directly to ApiHandlerOptions in @shared/api.ts
// NOTE: Keep these in sync with ApiHandlerOptions interface
const API_HANDLER_SETTINGS_FIELDS = {
// Global configuration (not mode-specific)
liteLlmBaseUrl: { default: undefined as string | undefined },