bone-agent 1.4.0 → 2.0.0

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 (126) hide show
  1. package/bin/bone.js +39 -0
  2. package/package.json +25 -39
  3. package/LICENSE +0 -21
  4. package/README.md +0 -201
  5. package/bin/npm-wrapper.js +0 -235
  6. package/bin/rg +0 -0
  7. package/bin/rg.exe +0 -0
  8. package/config.yaml.example +0 -144
  9. package/prompts/main/ask_questions.md +0 -31
  10. package/prompts/main/batch_independent_calls.md +0 -5
  11. package/prompts/main/casual_interactions.md +0 -11
  12. package/prompts/main/code_references.md +0 -8
  13. package/prompts/main/communication_style.md +0 -12
  14. package/prompts/main/context_reliability.md +0 -12
  15. package/prompts/main/conversational_tool_calling.md +0 -15
  16. package/prompts/main/dream.md +0 -50
  17. package/prompts/main/editing_pattern.md +0 -13
  18. package/prompts/main/error_handling.md +0 -6
  19. package/prompts/main/exploration_pattern.md +0 -21
  20. package/prompts/main/intro.md +0 -1
  21. package/prompts/main/obsidian.md +0 -16
  22. package/prompts/main/obsidian_project.md +0 -79
  23. package/prompts/main/professional_objectivity.md +0 -3
  24. package/prompts/main/skills.md +0 -3
  25. package/prompts/main/targeted_searching.md +0 -10
  26. package/prompts/main/task_lists_pattern.md +0 -8
  27. package/prompts/main/temp_folder.md +0 -9
  28. package/prompts/main/think_before_acting.md +0 -10
  29. package/prompts/main/tone_and_style.md +0 -4
  30. package/prompts/main/tool_preferences.md +0 -24
  31. package/prompts/main/trust_subagent_context.md +0 -21
  32. package/prompts/main/when_to_use_sub_agent.md +0 -7
  33. package/prompts/micro/ask_questions.md +0 -1
  34. package/prompts/micro/batch_independent_calls.md +0 -1
  35. package/prompts/micro/casual_interactions.md +0 -1
  36. package/prompts/micro/code_references.md +0 -1
  37. package/prompts/micro/communication_style.md +0 -1
  38. package/prompts/micro/context_reliability.md +0 -1
  39. package/prompts/micro/conversational_tool_calling.md +0 -1
  40. package/prompts/micro/editing_pattern.md +0 -1
  41. package/prompts/micro/error_handling.md +0 -1
  42. package/prompts/micro/exploration_pattern.md +0 -1
  43. package/prompts/micro/intro.md +0 -1
  44. package/prompts/micro/obsidian.md +0 -4
  45. package/prompts/micro/obsidian_project.md +0 -5
  46. package/prompts/micro/professional_objectivity.md +0 -1
  47. package/prompts/micro/skills.md +0 -1
  48. package/prompts/micro/targeted_searching.md +0 -1
  49. package/prompts/micro/task_lists_pattern.md +0 -1
  50. package/prompts/micro/temp_folder.md +0 -1
  51. package/prompts/micro/think_before_acting.md +0 -5
  52. package/prompts/micro/tone_and_style.md +0 -1
  53. package/prompts/micro/tool_preferences.md +0 -1
  54. package/prompts/micro/trust_subagent_context.md +0 -1
  55. package/prompts/micro/when_to_use_sub_agent.md +0 -1
  56. package/requirements.txt +0 -9
  57. package/src/__init__.py +0 -11
  58. package/src/core/__init__.py +0 -1
  59. package/src/core/agentic.py +0 -1085
  60. package/src/core/chat_manager.py +0 -1577
  61. package/src/core/config_manager.py +0 -260
  62. package/src/core/cron.py +0 -578
  63. package/src/core/cron_allowlist.py +0 -118
  64. package/src/core/memory.py +0 -145
  65. package/src/core/metadata.py +0 -75
  66. package/src/core/retry.py +0 -71
  67. package/src/core/skills.py +0 -463
  68. package/src/core/sub_agent.py +0 -376
  69. package/src/core/tool_approval.py +0 -220
  70. package/src/core/tool_feedback.py +0 -789
  71. package/src/exceptions.py +0 -79
  72. package/src/llm/__init__.py +0 -1
  73. package/src/llm/client.py +0 -176
  74. package/src/llm/codex_provider.py +0 -350
  75. package/src/llm/config.py +0 -536
  76. package/src/llm/prompts.py +0 -494
  77. package/src/llm/providers.py +0 -438
  78. package/src/llm/streaming.py +0 -163
  79. package/src/llm/token_tracker.py +0 -399
  80. package/src/tools/__init__.py +0 -151
  81. package/src/tools/constants.py +0 -59
  82. package/src/tools/create_file.py +0 -136
  83. package/src/tools/directory.py +0 -389
  84. package/src/tools/edit.py +0 -549
  85. package/src/tools/file_reader.py +0 -322
  86. package/src/tools/helpers/__init__.py +0 -99
  87. package/src/tools/helpers/base.py +0 -599
  88. package/src/tools/helpers/converters.py +0 -44
  89. package/src/tools/helpers/file_helpers.py +0 -189
  90. package/src/tools/helpers/formatters.py +0 -411
  91. package/src/tools/helpers/loader.py +0 -145
  92. package/src/tools/helpers/parallel_executor.py +0 -231
  93. package/src/tools/helpers/path_resolver.py +0 -283
  94. package/src/tools/helpers/plugin_manifest.py +0 -185
  95. package/src/tools/obsidian.py +0 -96
  96. package/src/tools/review_sub_agent.py +0 -190
  97. package/src/tools/rg_search.py +0 -477
  98. package/src/tools/search_plugins.py +0 -177
  99. package/src/tools/select_option.py +0 -600
  100. package/src/tools/shell.py +0 -302
  101. package/src/tools/sub_agent.py +0 -139
  102. package/src/tools/task_list.py +0 -269
  103. package/src/tools/web_search.py +0 -61
  104. package/src/ui/__init__.py +0 -1
  105. package/src/ui/banner.py +0 -87
  106. package/src/ui/commands.py +0 -3131
  107. package/src/ui/displays.py +0 -239
  108. package/src/ui/loader.py +0 -284
  109. package/src/ui/main.py +0 -643
  110. package/src/ui/prompt_utils.py +0 -113
  111. package/src/ui/setting_selector.py +0 -590
  112. package/src/ui/setup_wizard.py +0 -294
  113. package/src/ui/sub_agent_panel.py +0 -234
  114. package/src/ui/tool_confirmation.py +0 -226
  115. package/src/utils/__init__.py +0 -1
  116. package/src/utils/citation_parser.py +0 -199
  117. package/src/utils/editor.py +0 -207
  118. package/src/utils/gitignore_filter.py +0 -149
  119. package/src/utils/logger.py +0 -254
  120. package/src/utils/paths.py +0 -30
  121. package/src/utils/result_parsers.py +0 -108
  122. package/src/utils/safe_commands.py +0 -243
  123. package/src/utils/settings.py +0 -195
  124. package/src/utils/user_message_logger.py +0 -120
  125. package/src/utils/validation.py +0 -201
  126. package/src/utils/web_search.py +0 -173
package/src/llm/config.py DELETED
@@ -1,536 +0,0 @@
1
- import os
2
- import platform
3
- from pathlib import Path
4
- import yaml
5
-
6
- # Provider selection - loaded from config (see after PROVIDER_REGISTRY definition)
7
-
8
- def resolve_config_path() -> Path:
9
- """Resolve config.yaml path.
10
-
11
- Resolution order:
12
- 1. BONE_CONFIG_PATH — explicit user config path (set by npm wrapper)
13
- 2. ~/.bone/config.yaml — user home dotfile (dev and npm installs)
14
- """
15
- _user_cfg = os.environ.get('BONE_CONFIG_PATH')
16
- if _user_cfg:
17
- return Path(_user_cfg).resolve()
18
- return Path.home() / '.bone' / 'config.yaml'
19
-
20
- # Module-level config path (single source of truth)
21
- CONFIG_PATH = resolve_config_path()
22
-
23
- # Environment variable names for API keys (env vars take precedence over config file)
24
- ENV_API_KEYS = {
25
- 'ANTHROPIC_API_KEY': os.environ.get('ANTHROPIC_API_KEY'),
26
- 'OPENAI_API_KEY': os.environ.get('OPENAI_API_KEY'),
27
- 'GLM_PLAN_API_KEY': os.environ.get('GLM_PLAN_API_KEY'),
28
- 'GLM_API_KEY': os.environ.get('GLM_API_KEY'),
29
- 'GEMINI_API_KEY': os.environ.get('GEMINI_API_KEY'),
30
- 'OPENROUTER_API_KEY': os.environ.get('OPENROUTER_API_KEY'),
31
- 'KIMI_API_KEY': os.environ.get('KIMI_API_KEY'),
32
- 'MINIMAX_PLAN_API_KEY': os.environ.get('MINIMAX_PLAN_API_KEY'),
33
- 'MINIMAX_API_KEY': os.environ.get('MINIMAX_API_KEY'),
34
- 'BONE_PROXY_API_KEY': os.environ.get('BONE_PROXY_API_KEY'),
35
- 'CODEX_PLAN_API_KEY': os.environ.get('CODEX_PLAN_API_KEY'),
36
- }
37
-
38
- # Detect platform for llama.cpp paths
39
- _IS_WINDOWS = platform.system() == "Windows"
40
- _IS_LINUX = platform.system() == "Linux"
41
-
42
- # Set llama.cpp paths based on platform
43
- if _IS_WINDOWS:
44
- _LLAMA_SERVER_NAME = "llama-server.exe"
45
- _LLAMA_BUILD_DIR = "build"
46
- elif _IS_LINUX:
47
- _LLAMA_SERVER_NAME = "llama-server"
48
- _LLAMA_BUILD_DIR = "build-linux"
49
- else:
50
- # Fallback for macOS or other platforms
51
- _LLAMA_SERVER_NAME = "llama-server"
52
- _LLAMA_BUILD_DIR = "build"
53
-
54
- def _load_config():
55
- """Load config from YAML file, with environment variable overrides for API keys.
56
-
57
- Environment variables take precedence over values in config.yaml.
58
- """
59
- config_path = resolve_config_path()
60
- if not config_path.exists():
61
- return {}
62
- try:
63
- config = yaml.safe_load(config_path.read_text(encoding="utf-8-sig")) or {}
64
- except yaml.YAMLError:
65
- config = {}
66
-
67
- # Override API keys from environment variables (env vars take precedence)
68
- for key, env_value in ENV_API_KEYS.items():
69
- if env_value: # Only override if env var is set and non-empty
70
- config[key] = env_value
71
-
72
- return config
73
-
74
-
75
- _CONFIG = _load_config()
76
-
77
-
78
- def _get_codex_token() -> str:
79
- """Read access token from Codex CLI's cached auth (~/.codex/auth.json).
80
-
81
- Returns the access_token if available, empty string otherwise.
82
- Codex CLI stores OAuth tokens here after `codex login`.
83
- """
84
- try:
85
- auth_path = Path.home() / ".codex" / "auth.json"
86
- if not auth_path.exists():
87
- return ""
88
- import json
89
- data = json.loads(auth_path.read_text(encoding="utf-8"))
90
- return data.get("tokens", {}).get("access_token", "")
91
- except Exception:
92
- return ""
93
-
94
-
95
- # Cache for provider registry (built once at module load)
96
- _provider_registry_cache = None
97
- _cached_provider = None
98
-
99
-
100
- def get_model_cost(model_name: str, config: dict | None = None) -> tuple[float, float]:
101
- """Get model-specific (cost_in, cost_out) per 1M tokens from MODEL_PRICES.
102
-
103
- Shared utility — used by both the provider registry builder and config_manager.
104
- Returns (0.0, 0.0) for unknown models.
105
-
106
- Args:
107
- model_name: Model name to look up.
108
- config: Optional config dict (defaults to _CONFIG). Pass a fresh config
109
- after runtime edits to get up-to-date pricing.
110
- """
111
- model_prices = (config if config is not None else _CONFIG).get("MODEL_PRICES", {})
112
- if model_name in model_prices:
113
- mc = model_prices[model_name]
114
- return float(mc.get("cost_in", 0.0)), float(mc.get("cost_out", 0.0))
115
- return 0.0, 0.0
116
-
117
-
118
- def _model_cost(model_config_key):
119
- """Return {cost_in, cost_out} dict for use in provider registry via **spread.
120
-
121
- Reads from module-level _CONFIG (not an injected config). Safe because
122
- reload_config() invalidates the provider registry cache, forcing a rebuild.
123
- """
124
- ci, co = get_model_cost(_CONFIG.get(model_config_key, ""))
125
- return {"cost_in": ci, "cost_out": co}
126
-
127
-
128
- def _get_provider_registry():
129
- """Build PROVIDER_REGISTRY from current config (cached)."""
130
- global _provider_registry_cache
131
- if _provider_registry_cache is not None:
132
- return _provider_registry_cache
133
-
134
- _provider_registry_cache = {
135
- "local": {
136
- "type": "local",
137
- "api_key": None,
138
- "model": _CONFIG.get("LOCAL_MODEL_PATH", ""),
139
- "api_model": "model",
140
- "api_base": "http://127.0.0.1:8080",
141
- "endpoint": "/v1/chat/completions",
142
- "error_prefix": "local server",
143
- "config_keys": {
144
- "LOCAL_MODEL_PATH": "",
145
- "LOCAL_SERVER_PATH": str(
146
- Path(__file__).resolve().parents[2] /
147
- f"llama.cpp/{_LLAMA_BUILD_DIR}/bin/{_LLAMA_SERVER_NAME}"
148
- ),
149
- },
150
- "extra": {
151
- "host": "127.0.0.1",
152
- "port": 8080,
153
- },
154
- "default_temperature": 0.1,
155
- "default_top_p": 0.9,
156
- "allow_top_p": False,
157
- "allow_temperature": False,
158
- "cost_in": 0.0,
159
- "cost_out": 0.0
160
- },
161
- "openrouter": {
162
- "type": "api",
163
- "api_key": _CONFIG.get("OPENROUTER_API_KEY", ""),
164
- "model": _CONFIG.get("OPENROUTER_MODEL", ""),
165
- "api_base": _CONFIG.get("OPENROUTER_API_BASE", "https://openrouter.ai/api/v1"),
166
- "endpoint": "/chat/completions",
167
- "error_prefix": "OpenRouter",
168
- "headers_extra": {
169
- "HTTP-Referer": "http://localhost:8080",
170
- "X-Title": "Chat App"
171
- },
172
- "config_keys": {
173
- "OPENROUTER_API_KEY": "",
174
- "OPENROUTER_MODEL": "",
175
- "OPENROUTER_API_BASE": "https://openrouter.ai/api/v1",
176
- },
177
- "default_temperature": 0.1,
178
- "default_top_p": 0.9,
179
- "allow_top_p": True,
180
- "allow_temperature": True,
181
- **_model_cost("OPENROUTER_MODEL"),
182
- },
183
- "glm_plan": {
184
- "type": "api",
185
- "api_key": _CONFIG.get("GLM_PLAN_API_KEY", ""),
186
- "model": _CONFIG.get("GLM_PLAN_MODEL", ""),
187
- "api_base": _CONFIG.get("GLM_PLAN_API_BASE", "https://open.bigmodel.cn/api/paas/v4"),
188
- "endpoint": "/chat/completions",
189
- "error_prefix": "GLM",
190
- "config_keys": {
191
- "GLM_PLAN_API_KEY": "",
192
- "GLM_PLAN_MODEL": "",
193
- "GLM_PLAN_API_BASE": "https://open.bigmodel.cn/api/paas/v4",
194
- },
195
- "default_temperature": 0.1,
196
- "default_top_p": 0.9,
197
- "allow_top_p": True,
198
- "allow_temperature": True,
199
- **_model_cost("GLM_PLAN_MODEL"),
200
- },
201
- "glm": {
202
- "type": "api",
203
- "api_key": _CONFIG.get("GLM_API_KEY", ""),
204
- "model": _CONFIG.get("GLM_MODEL", ""),
205
- "api_base": _CONFIG.get("GLM_API_BASE", "https://open.bigmodel.cn/api/paas/v4"),
206
- "endpoint": "/chat/completions",
207
- "error_prefix": "GLM",
208
- "config_keys": {
209
- "GLM_API_KEY": "",
210
- "GLM_MODEL": "",
211
- "GLM_API_BASE": "https://open.bigmodel.cn/api/paas/v4",
212
- },
213
- "default_temperature": 0.1,
214
- "default_top_p": 0.9,
215
- "allow_top_p": True,
216
- "allow_temperature": True,
217
- **_model_cost("GLM_MODEL"),
218
- },
219
- "openai": {
220
- "type": "api",
221
- "api_key": _CONFIG.get("OPENAI_API_KEY", ""),
222
- "model": _CONFIG.get("OPENAI_MODEL", ""),
223
- "api_base": _CONFIG.get("OPENAI_API_BASE", "https://api.openai.com/v1"),
224
- "endpoint": "/chat/completions",
225
- "error_prefix": "OpenAI",
226
- "config_keys": {
227
- "OPENAI_API_KEY": "",
228
- "OPENAI_MODEL": "",
229
- "OPENAI_API_BASE": "https://api.openai.com/v1",
230
- },
231
- "default_temperature": 0.1,
232
- "default_top_p": 0.9,
233
- "allow_top_p": False,
234
- "allow_temperature": False,
235
- **_model_cost("OPENAI_MODEL"),
236
- },
237
- "gemini": {
238
- "type": "api",
239
- "api_key": _CONFIG.get("GEMINI_API_KEY", ""),
240
- "model": _CONFIG.get("GEMINI_MODEL", ""),
241
- "api_base": _CONFIG.get("GEMINI_API_BASE", "https://generativelanguage.googleapis.com/v1beta"),
242
- "endpoint": "/chat/completions",
243
- "error_prefix": "Gemini",
244
- "config_keys": {
245
- "GEMINI_API_KEY": "",
246
- "GEMINI_MODEL": "",
247
- "GEMINI_API_BASE": "https://generativelanguage.googleapis.com/v1beta",
248
- },
249
- "default_temperature": 0.1,
250
- "default_top_p": 0.9,
251
- "allow_top_p": True,
252
- "allow_temperature": True,
253
- **_model_cost("GEMINI_MODEL"),
254
- },
255
- "minimax_plan": {
256
- "type": "api",
257
- "api_key": _CONFIG.get("MINIMAX_PLAN_API_KEY", ""),
258
- "model": _CONFIG.get("MINIMAX_PLAN_MODEL", ""),
259
- "api_base": _CONFIG.get("MINIMAX_PLAN_API_BASE", "https://api.minimax.io/anthropic/v1"),
260
- "endpoint": "/messages",
261
- "error_prefix": "MiniMax",
262
- "config_keys": {
263
- "MINIMAX_PLAN_API_KEY": "",
264
- "MINIMAX_PLAN_MODEL": "",
265
- "MINIMAX_PLAN_API_BASE": "https://api.minimax.io/anthropic/v1",
266
- },
267
- "default_temperature": 0.1,
268
- "default_top_p": 0.9,
269
- "allow_top_p": False,
270
- "allow_temperature": True,
271
- "max_tokens": 4096,
272
- **_model_cost("MINIMAX_PLAN_MODEL"),
273
- },
274
- "minimax": {
275
- "type": "api",
276
- "api_key": _CONFIG.get("MINIMAX_API_KEY", ""),
277
- "model": _CONFIG.get("MINIMAX_MODEL", ""),
278
- "api_base": _CONFIG.get("MINIMAX_API_BASE", "https://api.minimax.io/anthropic/v1"),
279
- "endpoint": "/messages",
280
- "error_prefix": "MiniMax",
281
- "config_keys": {
282
- "MINIMAX_API_KEY": "",
283
- "MINIMAX_MODEL": "",
284
- "MINIMAX_API_BASE": "https://api.minimax.io/anthropic/v1",
285
- },
286
- "default_temperature": 0.1,
287
- "default_top_p": 0.9,
288
- "allow_top_p": False,
289
- "allow_temperature": True,
290
- "max_tokens": 4096,
291
- **_model_cost("MINIMAX_MODEL"),
292
- },
293
- "anthropic": {
294
- "type": "api",
295
- "api_key": _CONFIG.get("ANTHROPIC_API_KEY", ""),
296
- "model": _CONFIG.get("ANTHROPIC_MODEL", ""),
297
- "api_base": _CONFIG.get("ANTHROPIC_API_BASE", "https://api.anthropic.com/v1"),
298
- "endpoint": "/messages",
299
- "error_prefix": "Anthropic",
300
- "headers_extra": {
301
- "anthropic-version": "2023-06-01"
302
- },
303
- "config_keys": {
304
- "ANTHROPIC_API_KEY": "",
305
- "ANTHROPIC_MODEL": "",
306
- "ANTHROPIC_API_BASE": "https://api.anthropic.com/v1",
307
- },
308
- "default_temperature": 0.1,
309
- "default_top_p": 0.9,
310
- "allow_top_p": False,
311
- "allow_temperature": True,
312
- "max_tokens": 4096,
313
- **_model_cost("ANTHROPIC_MODEL"),
314
- },
315
- "kimi": {
316
- "type": "api",
317
- "api_key": _CONFIG.get("KIMI_API_KEY", ""),
318
- "model": _CONFIG.get("KIMI_MODEL", ""),
319
- "api_base": _CONFIG.get("KIMI_API_BASE", "https://api.moonshot.cn/v1"),
320
- "endpoint": "/chat/completions",
321
- "error_prefix": "Kimi",
322
- "config_keys": {
323
- "KIMI_API_KEY": "",
324
- "KIMI_MODEL": "",
325
- "KIMI_API_BASE": "https://api.moonshot.cn/v1",
326
- },
327
- "default_temperature": 0.1,
328
- "default_top_p": 0.9,
329
- "allow_top_p": True,
330
- "allow_temperature": True,
331
- **_model_cost("KIMI_MODEL"),
332
- },
333
- "codex": {
334
- "type": "api",
335
- "api_key": _CONFIG.get("CODEX_PLAN_API_KEY", "") or _get_codex_token(),
336
- "model": _CONFIG.get("CODEX_PLAN_MODEL", "gpt-5.4-mini"),
337
- "api_base": _CONFIG.get("CODEX_PLAN_API_BASE", "https://chatgpt.com/backend-api/codex"),
338
- "endpoint": "/responses",
339
- "error_prefix": "Codex",
340
- "config_keys": {
341
- "CODEX_PLAN_API_KEY": "",
342
- "CODEX_PLAN_MODEL": "",
343
- "CODEX_PLAN_API_BASE": "https://chatgpt.com/backend-api/codex",
344
- },
345
- "allow_temperature": False,
346
- "allow_top_p": False,
347
- **_model_cost("CODEX_PLAN_MODEL"),
348
- },
349
- "bone": {
350
- "type": "api",
351
- "api_key": _CONFIG.get("BONE_PROXY_API_KEY", ""),
352
- "model": _CONFIG.get("BONE_PROXY_MODEL", "openai/gpt-4o-mini"),
353
- "api_base": _CONFIG.get("BONE_PROXY_API_BASE", "https://api.vmcode.dev"),
354
- "endpoint": "/v1/chat/completions",
355
- "error_prefix": "bone-agent Proxy",
356
- "config_keys": {
357
- "BONE_PROXY_API_KEY": "",
358
- "BONE_PROXY_MODEL": "openai/gpt-4o-mini",
359
- "BONE_PROXY_API_BASE": "https://api.vmcode.dev",
360
- },
361
- "default_temperature": 0.1,
362
- "default_top_p": 0.9,
363
- "allow_top_p": True,
364
- "allow_temperature": True,
365
- **_model_cost("BONE_PROXY_MODEL"),
366
- },
367
- }
368
- return _provider_registry_cache
369
-
370
-
371
- def _get_provider():
372
- """Get the current provider from config (cached)."""
373
- global _cached_provider
374
- if _cached_provider is not None:
375
- return _cached_provider
376
-
377
- last_provider = _CONFIG.get("LAST_PROVIDER")
378
- if last_provider and last_provider in _provider_registry_cache:
379
- _cached_provider = last_provider
380
- return _cached_provider
381
- _cached_provider = "glm_plan"
382
- return _cached_provider
383
-
384
-
385
- def reload_config():
386
- """Reload config from disk and invalidate caches.
387
-
388
- Reloads both the config.yaml file and environment variables.
389
-
390
- Note: This is a manual operation - call after config changes.
391
- """
392
- global _CONFIG, _provider_registry_cache, _cached_provider, PROVIDER_REGISTRY, LLM_PROVIDER, STATUS_BAR_SETTINGS, MEMORY_SETTINGS
393
- _CONFIG = _load_config()
394
- _provider_registry_cache = None
395
- _cached_provider = None
396
- # Rebuild module-level variables
397
- PROVIDER_REGISTRY = _get_provider_registry()
398
- LLM_PROVIDER = _get_provider()
399
- # Rebuild status bar settings
400
- STATUS_BAR_SETTINGS = _build_status_bar_settings()
401
- # Rebuild memory settings
402
- MEMORY_SETTINGS = _build_memory_settings()
403
-
404
-
405
- def _build_status_bar_settings():
406
- """Build STATUS_BAR_SETTINGS dict from current _CONFIG."""
407
- sbs = _CONFIG.get("STATUS_BAR_SETTINGS", {})
408
- return {
409
- "show_curr_tokens": sbs.get("show_curr_tokens", True),
410
- "show_in_tokens": sbs.get("show_in_tokens", True),
411
- "show_out_tokens": sbs.get("show_out_tokens", True),
412
- "show_total_tokens": sbs.get("show_total_tokens", True),
413
- "show_cost": sbs.get("show_cost", True),
414
- }
415
-
416
-
417
- def _build_memory_settings():
418
- """Build MEMORY_SETTINGS dict from current _CONFIG."""
419
- ms = _CONFIG.get("MEMORY_SETTINGS", {})
420
- return {
421
- "enabled": ms.get("enabled", True),
422
- }
423
-
424
-
425
- def update_memory_settings(settings_dict):
426
- """Update MEMORY_SETTINGS at runtime.
427
-
428
- Returns:
429
- Updated MEMORY_SETTINGS dict
430
- """
431
- global MEMORY_SETTINGS
432
- MEMORY_SETTINGS.update(settings_dict)
433
- return MEMORY_SETTINGS
434
-
435
-
436
- def update_status_bar_settings(settings_dict):
437
- """Update STATUS_BAR_SETTINGS at runtime.
438
-
439
- Args:
440
- settings_dict: Dict of settings to update (e.g., {"show_cost": False})
441
-
442
- Returns:
443
- Updated STATUS_BAR_SETTINGS dict
444
- """
445
- global STATUS_BAR_SETTINGS
446
- STATUS_BAR_SETTINGS.update(settings_dict)
447
- return STATUS_BAR_SETTINGS
448
-
449
-
450
- def get_providers():
451
- """Get list of available providers.
452
-
453
- Returns:
454
- list: List of provider names from PROVIDER_REGISTRY.
455
- """
456
- return list(PROVIDER_REGISTRY.keys())
457
-
458
-
459
- def get_provider_display_name(provider: str) -> str:
460
- """Return the user-facing provider name for a provider key."""
461
- display_names = {
462
- "codex": "Codex",
463
- }
464
- return display_names.get(provider, provider.replace("_", " ").title())
465
-
466
-
467
- # ============================================================================
468
- # PROVIDER REGISTRY - Centralized provider configuration
469
- # ============================================================================
470
-
471
- # Build provider registry and export as module-level constants (loaded once)
472
- PROVIDER_REGISTRY = _get_provider_registry()
473
- LLM_PROVIDER = _get_provider()
474
-
475
-
476
- __all__ = [
477
- "CONFIG_PATH",
478
- "PROVIDER_REGISTRY",
479
- "get_providers",
480
- "get_provider_display_name",
481
- "get_model_cost",
482
- "LLM_PROVIDER",
483
- "TOOLS_ENABLED",
484
- "TOOLS_REQUIRE_CONFIRMATION",
485
- "WEB_SEARCH_REQUIRE_CONFIRMATION",
486
- "APPROVE_MODES",
487
- "APPROVE_MODE_LABELS",
488
-
489
- "get_provider_config",
490
- "generate_config_template",
491
- "reload_config",
492
- "STATUS_BAR_SETTINGS",
493
- "update_status_bar_settings",
494
- "MEMORY_SETTINGS",
495
- "update_memory_settings",
496
- ]
497
-
498
-
499
- def generate_config_template():
500
- """Generate default template for config.json from provider registry."""
501
- template = {}
502
- for provider, config in PROVIDER_REGISTRY.items():
503
- if "config_keys" in config:
504
- template.update(config["config_keys"])
505
- return template
506
-
507
- # Tooling configuration
508
- TOOLS_ENABLED = True
509
- TOOLS_REQUIRE_CONFIRMATION = False
510
- WEB_SEARCH_REQUIRE_CONFIRMATION = False
511
-
512
- # Status bar configuration
513
- STATUS_BAR_SETTINGS = _build_status_bar_settings()
514
-
515
- # Memory configuration
516
- MEMORY_SETTINGS = _build_memory_settings()
517
-
518
- # Tool approval modes
519
- APPROVE_MODES = ("safe", "accept_edits", "danger")
520
- CYCLEABLE_APPROVE_MODES = ("safe", "accept_edits")
521
- APPROVE_MODE_LABELS = {
522
- "safe": "Safe",
523
- "accept_edits": "Accept Edits",
524
- "danger": "Danger",
525
- }
526
-
527
- def get_provider_config(provider: str):
528
- """Retrieve the configuration dictionary for a given provider.
529
-
530
- Args:
531
- provider (str): Provider name (e.g., 'local', 'openrouter', 'glm', 'openai').
532
-
533
- Returns:
534
- dict: Provider config from the PROVIDER_REGISTRY.
535
- """
536
- return PROVIDER_REGISTRY.get(provider, {})