devcopilot 0.2.0__py3-none-any.whl

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 (189) hide show
  1. api/__init__.py +17 -0
  2. api/admin_config.py +1303 -0
  3. api/admin_routes.py +287 -0
  4. api/admin_static/admin.css +459 -0
  5. api/admin_static/admin.js +497 -0
  6. api/admin_static/index.html +77 -0
  7. api/admin_urls.py +34 -0
  8. api/app.py +194 -0
  9. api/command_utils.py +164 -0
  10. api/dependencies.py +144 -0
  11. api/detection.py +152 -0
  12. api/gateway_model_ids.py +54 -0
  13. api/model_catalog.py +133 -0
  14. api/model_router.py +125 -0
  15. api/models/__init__.py +45 -0
  16. api/models/anthropic.py +234 -0
  17. api/models/openai_responses.py +28 -0
  18. api/models/responses.py +60 -0
  19. api/optimization_handlers.py +154 -0
  20. api/request_pipeline.py +424 -0
  21. api/routes.py +156 -0
  22. api/runtime.py +334 -0
  23. api/validation_log.py +48 -0
  24. api/web_server_tools.py +22 -0
  25. api/web_tools/__init__.py +17 -0
  26. api/web_tools/constants.py +15 -0
  27. api/web_tools/egress.py +99 -0
  28. api/web_tools/outbound.py +278 -0
  29. api/web_tools/parsers.py +104 -0
  30. api/web_tools/request.py +87 -0
  31. api/web_tools/streaming.py +206 -0
  32. cli/__init__.py +5 -0
  33. cli/claude_env.py +12 -0
  34. cli/entrypoints.py +166 -0
  35. cli/env.example +209 -0
  36. cli/launchers/__init__.py +1 -0
  37. cli/launchers/claude.py +84 -0
  38. cli/launchers/codex.py +204 -0
  39. cli/launchers/codex_model_catalog.py +186 -0
  40. cli/launchers/common.py +93 -0
  41. cli/managed/__init__.py +6 -0
  42. cli/managed/claude.py +215 -0
  43. cli/managed/manager.py +157 -0
  44. cli/managed/session.py +260 -0
  45. cli/process_registry.py +78 -0
  46. config/__init__.py +5 -0
  47. config/constants.py +13 -0
  48. config/logging_config.py +159 -0
  49. config/nim.py +118 -0
  50. config/paths.py +91 -0
  51. config/provider_catalog.py +259 -0
  52. config/provider_ids.py +7 -0
  53. config/settings.py +538 -0
  54. core/__init__.py +1 -0
  55. core/anthropic/__init__.py +46 -0
  56. core/anthropic/content.py +31 -0
  57. core/anthropic/conversion.py +587 -0
  58. core/anthropic/emitted_sse_tracker.py +346 -0
  59. core/anthropic/errors.py +70 -0
  60. core/anthropic/native_messages_request.py +280 -0
  61. core/anthropic/native_sse_block_policy.py +313 -0
  62. core/anthropic/provider_stream_error.py +34 -0
  63. core/anthropic/server_tool_sse.py +14 -0
  64. core/anthropic/sse.py +440 -0
  65. core/anthropic/stream_contracts.py +205 -0
  66. core/anthropic/stream_recovery.py +346 -0
  67. core/anthropic/stream_recovery_session.py +133 -0
  68. core/anthropic/thinking.py +140 -0
  69. core/anthropic/tokens.py +117 -0
  70. core/anthropic/tools.py +212 -0
  71. core/anthropic/utils.py +9 -0
  72. core/openai_responses/__init__.py +5 -0
  73. core/openai_responses/adapter.py +31 -0
  74. core/openai_responses/anthropic_sse.py +59 -0
  75. core/openai_responses/errors.py +22 -0
  76. core/openai_responses/events.py +19 -0
  77. core/openai_responses/ids.py +21 -0
  78. core/openai_responses/input.py +258 -0
  79. core/openai_responses/items.py +37 -0
  80. core/openai_responses/reasoning.py +52 -0
  81. core/openai_responses/stream.py +25 -0
  82. core/openai_responses/stream_state.py +654 -0
  83. core/openai_responses/tools.py +374 -0
  84. core/openai_responses/usage.py +37 -0
  85. core/rate_limit.py +60 -0
  86. core/trace.py +216 -0
  87. devcopilot-0.2.0.dist-info/METADATA +687 -0
  88. devcopilot-0.2.0.dist-info/RECORD +189 -0
  89. devcopilot-0.2.0.dist-info/WHEEL +4 -0
  90. devcopilot-0.2.0.dist-info/entry_points.txt +6 -0
  91. devcopilot-0.2.0.dist-info/licenses/LICENSE +21 -0
  92. messaging/__init__.py +26 -0
  93. messaging/cli_event_constants.py +67 -0
  94. messaging/command_context.py +66 -0
  95. messaging/command_dispatcher.py +37 -0
  96. messaging/commands.py +275 -0
  97. messaging/event_parser.py +181 -0
  98. messaging/limiter.py +300 -0
  99. messaging/models.py +36 -0
  100. messaging/node_event_pipeline.py +127 -0
  101. messaging/node_runner.py +342 -0
  102. messaging/platforms/__init__.py +15 -0
  103. messaging/platforms/base.py +228 -0
  104. messaging/platforms/discord.py +567 -0
  105. messaging/platforms/factory.py +103 -0
  106. messaging/platforms/outbox.py +144 -0
  107. messaging/platforms/telegram.py +688 -0
  108. messaging/platforms/voice_flow.py +295 -0
  109. messaging/rendering/__init__.py +3 -0
  110. messaging/rendering/discord_markdown.py +318 -0
  111. messaging/rendering/markdown_tables.py +49 -0
  112. messaging/rendering/profiles.py +55 -0
  113. messaging/rendering/telegram_markdown.py +327 -0
  114. messaging/safe_diagnostics.py +17 -0
  115. messaging/session.py +334 -0
  116. messaging/transcript.py +581 -0
  117. messaging/transcription.py +164 -0
  118. messaging/trees/__init__.py +15 -0
  119. messaging/trees/data.py +482 -0
  120. messaging/trees/manager.py +433 -0
  121. messaging/trees/processor.py +179 -0
  122. messaging/trees/repository.py +177 -0
  123. messaging/turn_intake.py +235 -0
  124. messaging/ui_updates.py +101 -0
  125. messaging/voice.py +76 -0
  126. messaging/workflow.py +200 -0
  127. providers/__init__.py +31 -0
  128. providers/base.py +152 -0
  129. providers/cerebras/__init__.py +7 -0
  130. providers/cerebras/client.py +31 -0
  131. providers/cerebras/request.py +55 -0
  132. providers/codestral/__init__.py +7 -0
  133. providers/codestral/client.py +34 -0
  134. providers/deepseek/__init__.py +11 -0
  135. providers/deepseek/client.py +51 -0
  136. providers/deepseek/request.py +475 -0
  137. providers/defaults.py +41 -0
  138. providers/error_mapping.py +309 -0
  139. providers/exceptions.py +113 -0
  140. providers/fireworks/__init__.py +5 -0
  141. providers/fireworks/client.py +45 -0
  142. providers/fireworks/request.py +48 -0
  143. providers/gemini/__init__.py +7 -0
  144. providers/gemini/client.py +49 -0
  145. providers/gemini/request.py +199 -0
  146. providers/groq/__init__.py +7 -0
  147. providers/groq/client.py +31 -0
  148. providers/groq/request.py +83 -0
  149. providers/kimi/__init__.py +10 -0
  150. providers/kimi/client.py +53 -0
  151. providers/kimi/request.py +42 -0
  152. providers/llamacpp/__init__.py +3 -0
  153. providers/llamacpp/client.py +16 -0
  154. providers/lmstudio/__init__.py +5 -0
  155. providers/lmstudio/client.py +16 -0
  156. providers/mistral/__init__.py +7 -0
  157. providers/mistral/client.py +31 -0
  158. providers/mistral/request.py +37 -0
  159. providers/model_listing.py +133 -0
  160. providers/nvidia_nim/__init__.py +7 -0
  161. providers/nvidia_nim/client.py +91 -0
  162. providers/nvidia_nim/request.py +430 -0
  163. providers/nvidia_nim/voice.py +95 -0
  164. providers/ollama/__init__.py +7 -0
  165. providers/ollama/client.py +39 -0
  166. providers/open_router/__init__.py +7 -0
  167. providers/open_router/client.py +124 -0
  168. providers/open_router/request.py +42 -0
  169. providers/opencode/__init__.py +11 -0
  170. providers/opencode/client.py +31 -0
  171. providers/opencode/request.py +35 -0
  172. providers/rate_limit.py +300 -0
  173. providers/registry.py +527 -0
  174. providers/transports/__init__.py +1 -0
  175. providers/transports/anthropic_messages/__init__.py +5 -0
  176. providers/transports/anthropic_messages/http.py +118 -0
  177. providers/transports/anthropic_messages/recovery.py +206 -0
  178. providers/transports/anthropic_messages/stream.py +295 -0
  179. providers/transports/anthropic_messages/transport.py +236 -0
  180. providers/transports/openai_chat/__init__.py +5 -0
  181. providers/transports/openai_chat/recovery.py +217 -0
  182. providers/transports/openai_chat/stream.py +384 -0
  183. providers/transports/openai_chat/tool_calls.py +293 -0
  184. providers/transports/openai_chat/transport.py +156 -0
  185. providers/wafer/__init__.py +10 -0
  186. providers/wafer/client.py +50 -0
  187. providers/zai/__init__.py +10 -0
  188. providers/zai/client.py +46 -0
  189. providers/zai/request.py +42 -0
config/paths.py ADDED
@@ -0,0 +1,91 @@
1
+ """Shared filesystem paths for DevCopilot configuration."""
2
+
3
+ from pathlib import Path
4
+ import shutil
5
+
6
+ DEVCOPILOT_CONFIG_DIRNAME = ".dc"
7
+ FCC_CONFIG_DIRNAME = ".fcc"
8
+
9
+ ENV_FILENAME = ".env"
10
+
11
+ LEGACY_REPO_DIRNAME = "free-claude-code"
12
+ LEGACY_XDG_CONFIG_DIRNAME = ".config"
13
+
14
+ CLAUDE_WORKSPACE_DIRNAME = "agent_workspace"
15
+ LOGS_DIRNAME = "logs"
16
+ SERVER_LOG_FILENAME = "server.log"
17
+ CODEX_MODEL_CATALOG_FILENAME = "codex-model-catalog.json"
18
+
19
+
20
+ def repo_env_path() -> Path:
21
+ """Local development .env inside the repository."""
22
+ return Path.cwd() / ENV_FILENAME
23
+
24
+
25
+ def config_dir_path() -> Path:
26
+ """
27
+ Priority:
28
+
29
+ 1. repo/.env
30
+ 2. ~/.dc
31
+ 3. migrate from ~/.fcc
32
+ """
33
+
34
+ home = Path.home()
35
+
36
+ dc_dir = home / DEVCOPILOT_CONFIG_DIRNAME
37
+ fcc_dir = home / FCC_CONFIG_DIRNAME
38
+
39
+ # Development mode
40
+ if repo_env_path().exists():
41
+ return Path.cwd()
42
+
43
+ # Existing DevCopilot config
44
+ if dc_dir.exists():
45
+ return dc_dir
46
+
47
+ # Migrate legacy FCC config
48
+ if fcc_dir.exists():
49
+ dc_dir.mkdir(parents=True, exist_ok=True)
50
+
51
+ fcc_env = fcc_dir / ENV_FILENAME
52
+ dc_env = dc_dir / ENV_FILENAME
53
+
54
+ if fcc_env.exists() and not dc_env.exists():
55
+ shutil.copy2(fcc_env, dc_env)
56
+
57
+ return dc_dir
58
+
59
+ # First startup
60
+ dc_dir.mkdir(parents=True, exist_ok=True)
61
+ return dc_dir
62
+
63
+
64
+ def managed_env_path() -> Path:
65
+ """Return active env path."""
66
+
67
+ if repo_env_path().exists():
68
+ return repo_env_path()
69
+
70
+ return config_dir_path() / ENV_FILENAME
71
+
72
+
73
+ def legacy_env_paths() -> tuple[Path, ...]:
74
+ home = Path.home()
75
+
76
+ return (
77
+ home / LEGACY_REPO_DIRNAME / ENV_FILENAME,
78
+ home / LEGACY_XDG_CONFIG_DIRNAME / LEGACY_REPO_DIRNAME / ENV_FILENAME,
79
+ )
80
+
81
+
82
+ def default_claude_workspace_path() -> Path:
83
+ return config_dir_path() / CLAUDE_WORKSPACE_DIRNAME
84
+
85
+
86
+ def server_log_path() -> Path:
87
+ return config_dir_path() / LOGS_DIRNAME / SERVER_LOG_FILENAME
88
+
89
+
90
+ def codex_model_catalog_path() -> Path:
91
+ return config_dir_path() / CODEX_MODEL_CATALOG_FILENAME
@@ -0,0 +1,259 @@
1
+ """Neutral provider catalog: IDs, credentials, defaults, proxy and capability metadata.
2
+
3
+ Adapter factories live in :mod:`providers.registry`; this module stays free of
4
+ provider implementation imports (see contract tests).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Literal
11
+
12
+ TransportType = Literal["openai_chat", "anthropic_messages"]
13
+
14
+ # Default upstream base URLs (also re-exported via :mod:`providers.defaults`)
15
+ NVIDIA_NIM_DEFAULT_BASE = "https://integrate.api.nvidia.com/v1"
16
+ # Moonshot Kimi Anthropic-compatible Messages API (POST …/messages).
17
+ KIMI_DEFAULT_BASE = "https://api.moonshot.ai/anthropic/v1"
18
+ WAFER_DEFAULT_BASE = "https://pass.wafer.ai/v1"
19
+ # DeepSeek Anthropic-compatible Messages API (not OpenAI ``/v1`` chat completions).
20
+ DEEPSEEK_ANTHROPIC_DEFAULT_BASE = "https://api.deepseek.com/anthropic"
21
+ # Historical export name: DeepSeek upstream is the native Anthropic path above.
22
+ DEEPSEEK_DEFAULT_BASE = DEEPSEEK_ANTHROPIC_DEFAULT_BASE
23
+ FIREWORKS_DEFAULT_BASE = "https://api.fireworks.ai/inference/v1"
24
+ OPENROUTER_DEFAULT_BASE = "https://openrouter.ai/api/v1"
25
+ MISTRAL_DEFAULT_BASE = "https://api.mistral.ai/v1"
26
+ # Codestral IDE/personal endpoint (distinct from La Plateforme ``api.mistral.ai`` keys).
27
+ CODESTRAL_DEFAULT_BASE = "https://codestral.mistral.ai/v1"
28
+ LMSTUDIO_DEFAULT_BASE = "http://localhost:1234/v1"
29
+ LLAMACPP_DEFAULT_BASE = "http://localhost:8080/v1"
30
+ OLLAMA_DEFAULT_BASE = "http://localhost:11434"
31
+ OPENCODE_DEFAULT_BASE = "https://opencode.ai/zen/v1"
32
+ OPENCODE_GO_DEFAULT_BASE = "https://opencode.ai/zen/go/v1"
33
+ # Z.ai Anthropic-compatible Messages API (not OpenAI Coding Plan chat completions).
34
+ ZAI_DEFAULT_BASE = "https://api.z.ai/api/anthropic/v1"
35
+ # Google AI Studio Gemini API OpenAI-compat layer (not Vertex AI).
36
+ GEMINI_DEFAULT_BASE = "https://generativelanguage.googleapis.com/v1beta/openai/"
37
+ GROQ_DEFAULT_BASE = "https://api.groq.com/openai/v1"
38
+ CEREBRAS_DEFAULT_BASE = "https://api.cerebras.ai/v1"
39
+
40
+
41
+ @dataclass(frozen=True, slots=True)
42
+ class ProviderDescriptor:
43
+ """Metadata for building :class:`~providers.base.ProviderConfig` and factory wiring."""
44
+
45
+ provider_id: str
46
+ transport_type: TransportType
47
+ capabilities: tuple[str, ...]
48
+ credential_env: str | None = None
49
+ credential_url: str | None = None
50
+ credential_attr: str | None = None
51
+ static_credential: str | None = None
52
+ default_base_url: str | None = None
53
+ base_url_attr: str | None = None
54
+ proxy_attr: str | None = None
55
+
56
+
57
+ PROVIDER_CATALOG: dict[str, ProviderDescriptor] = {
58
+ "nvidia_nim": ProviderDescriptor(
59
+ provider_id="nvidia_nim",
60
+ transport_type="openai_chat",
61
+ credential_env="NVIDIA_NIM_API_KEY",
62
+ credential_url="https://build.nvidia.com/settings/api-keys",
63
+ credential_attr="nvidia_nim_api_key",
64
+ default_base_url=NVIDIA_NIM_DEFAULT_BASE,
65
+ proxy_attr="nvidia_nim_proxy",
66
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
67
+ ),
68
+ "open_router": ProviderDescriptor(
69
+ provider_id="open_router",
70
+ transport_type="anthropic_messages",
71
+ credential_env="OPENROUTER_API_KEY",
72
+ credential_url="https://openrouter.ai/keys",
73
+ credential_attr="open_router_api_key",
74
+ default_base_url=OPENROUTER_DEFAULT_BASE,
75
+ proxy_attr="open_router_proxy",
76
+ capabilities=("chat", "streaming", "tools", "thinking", "native_anthropic"),
77
+ ),
78
+ "gemini": ProviderDescriptor(
79
+ provider_id="gemini",
80
+ transport_type="openai_chat",
81
+ credential_env="GEMINI_API_KEY",
82
+ credential_url="https://aistudio.google.com/apikey",
83
+ credential_attr="gemini_api_key",
84
+ default_base_url=GEMINI_DEFAULT_BASE,
85
+ proxy_attr="gemini_proxy",
86
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
87
+ ),
88
+ "deepseek": ProviderDescriptor(
89
+ provider_id="deepseek",
90
+ transport_type="anthropic_messages",
91
+ credential_env="DEEPSEEK_API_KEY",
92
+ credential_url="https://platform.deepseek.com/api_keys",
93
+ credential_attr="deepseek_api_key",
94
+ default_base_url=DEEPSEEK_ANTHROPIC_DEFAULT_BASE,
95
+ capabilities=("chat", "streaming", "tools", "thinking", "native_anthropic"),
96
+ ),
97
+ "mistral": ProviderDescriptor(
98
+ provider_id="mistral",
99
+ transport_type="openai_chat",
100
+ credential_env="MISTRAL_API_KEY",
101
+ credential_url="https://console.mistral.ai/",
102
+ credential_attr="mistral_api_key",
103
+ default_base_url=MISTRAL_DEFAULT_BASE,
104
+ proxy_attr="mistral_proxy",
105
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
106
+ ),
107
+ "mistral_codestral": ProviderDescriptor(
108
+ provider_id="mistral_codestral",
109
+ transport_type="openai_chat",
110
+ credential_env="CODESTRAL_API_KEY",
111
+ credential_url="https://console.mistral.ai/",
112
+ credential_attr="codestral_api_key",
113
+ default_base_url=CODESTRAL_DEFAULT_BASE,
114
+ proxy_attr="codestral_proxy",
115
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
116
+ ),
117
+ "opencode": ProviderDescriptor(
118
+ provider_id="opencode",
119
+ transport_type="openai_chat",
120
+ credential_env="OPENCODE_API_KEY",
121
+ credential_url="https://opencode.ai/auth",
122
+ credential_attr="opencode_api_key",
123
+ default_base_url=OPENCODE_DEFAULT_BASE,
124
+ proxy_attr="opencode_proxy",
125
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
126
+ ),
127
+ "opencode_go": ProviderDescriptor(
128
+ provider_id="opencode_go",
129
+ transport_type="openai_chat",
130
+ credential_env="OPENCODE_API_KEY",
131
+ credential_url="https://opencode.ai/auth",
132
+ credential_attr="opencode_api_key",
133
+ default_base_url=OPENCODE_GO_DEFAULT_BASE,
134
+ proxy_attr="opencode_go_proxy",
135
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
136
+ ),
137
+ "wafer": ProviderDescriptor(
138
+ provider_id="wafer",
139
+ transport_type="anthropic_messages",
140
+ credential_env="WAFER_API_KEY",
141
+ credential_url="https://www.wafer.ai/pass",
142
+ credential_attr="wafer_api_key",
143
+ default_base_url=WAFER_DEFAULT_BASE,
144
+ proxy_attr="wafer_proxy",
145
+ capabilities=("chat", "streaming", "tools", "thinking", "native_anthropic"),
146
+ ),
147
+ "kimi": ProviderDescriptor(
148
+ provider_id="kimi",
149
+ transport_type="anthropic_messages",
150
+ credential_env="KIMI_API_KEY",
151
+ credential_url="https://platform.moonshot.cn/console/api-keys",
152
+ credential_attr="kimi_api_key",
153
+ default_base_url=KIMI_DEFAULT_BASE,
154
+ proxy_attr="kimi_proxy",
155
+ capabilities=(
156
+ "chat",
157
+ "streaming",
158
+ "tools",
159
+ "thinking",
160
+ "native_anthropic",
161
+ ),
162
+ ),
163
+ "cerebras": ProviderDescriptor(
164
+ provider_id="cerebras",
165
+ transport_type="openai_chat",
166
+ credential_env="CEREBRAS_API_KEY",
167
+ credential_url="https://cloud.cerebras.ai",
168
+ credential_attr="cerebras_api_key",
169
+ default_base_url=CEREBRAS_DEFAULT_BASE,
170
+ proxy_attr="cerebras_proxy",
171
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
172
+ ),
173
+ "groq": ProviderDescriptor(
174
+ provider_id="groq",
175
+ transport_type="openai_chat",
176
+ credential_env="GROQ_API_KEY",
177
+ credential_url="https://console.groq.com/keys",
178
+ credential_attr="groq_api_key",
179
+ default_base_url=GROQ_DEFAULT_BASE,
180
+ proxy_attr="groq_proxy",
181
+ capabilities=("chat", "streaming", "tools", "thinking", "rate_limit"),
182
+ ),
183
+ "fireworks": ProviderDescriptor(
184
+ provider_id="fireworks",
185
+ transport_type="anthropic_messages",
186
+ credential_env="FIREWORKS_API_KEY",
187
+ credential_url="https://fireworks.ai/account/api-keys",
188
+ credential_attr="fireworks_api_key",
189
+ default_base_url=FIREWORKS_DEFAULT_BASE,
190
+ proxy_attr="fireworks_proxy",
191
+ capabilities=(
192
+ "chat",
193
+ "streaming",
194
+ "tools",
195
+ "thinking",
196
+ "native_anthropic",
197
+ "rate_limit",
198
+ ),
199
+ ),
200
+ "zai": ProviderDescriptor(
201
+ provider_id="zai",
202
+ transport_type="anthropic_messages",
203
+ credential_env="ZAI_API_KEY",
204
+ credential_attr="zai_api_key",
205
+ default_base_url=ZAI_DEFAULT_BASE,
206
+ proxy_attr="zai_proxy",
207
+ capabilities=(
208
+ "chat",
209
+ "streaming",
210
+ "tools",
211
+ "thinking",
212
+ "native_anthropic",
213
+ "rate_limit",
214
+ ),
215
+ ),
216
+ "lmstudio": ProviderDescriptor(
217
+ provider_id="lmstudio",
218
+ transport_type="anthropic_messages",
219
+ static_credential="lm-studio",
220
+ default_base_url=LMSTUDIO_DEFAULT_BASE,
221
+ base_url_attr="lm_studio_base_url",
222
+ proxy_attr="lmstudio_proxy",
223
+ capabilities=("chat", "streaming", "tools", "native_anthropic", "local"),
224
+ ),
225
+ "llamacpp": ProviderDescriptor(
226
+ provider_id="llamacpp",
227
+ transport_type="anthropic_messages",
228
+ static_credential="llamacpp",
229
+ default_base_url=LLAMACPP_DEFAULT_BASE,
230
+ base_url_attr="llamacpp_base_url",
231
+ proxy_attr="llamacpp_proxy",
232
+ capabilities=("chat", "streaming", "tools", "native_anthropic", "local"),
233
+ ),
234
+ "ollama": ProviderDescriptor(
235
+ provider_id="ollama",
236
+ transport_type="anthropic_messages",
237
+ static_credential="ollama",
238
+ default_base_url=OLLAMA_DEFAULT_BASE,
239
+ base_url_attr="ollama_base_url",
240
+ capabilities=(
241
+ "chat",
242
+ "streaming",
243
+ "tools",
244
+ "thinking",
245
+ "native_anthropic",
246
+ "local",
247
+ ),
248
+ ),
249
+ }
250
+
251
+ # Key order:
252
+ # NVIDIA NIM first (README default), DeepSeek fourth, Wafer ninth / Kimi tenth; then cerebras /
253
+ # groq / fireworks overlap; remainder and locals last per project plan (
254
+ # github.com/cheahjs/free-llm-api-resources Free Providers TOC as rough guide beyond fixed slots).
255
+ # ``SUPPORTED_PROVIDER_IDS`` inherits this insertion order for UI and error-message listing.
256
+ SUPPORTED_PROVIDER_IDS: tuple[str, ...] = tuple(PROVIDER_CATALOG.keys())
257
+
258
+ if len(set(SUPPORTED_PROVIDER_IDS)) != len(SUPPORTED_PROVIDER_IDS):
259
+ raise AssertionError("Duplicate provider ids in PROVIDER_CATALOG key order")
config/provider_ids.py ADDED
@@ -0,0 +1,7 @@
1
+ """Canonical provider id tuple (re-exported from the provider catalog)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .provider_catalog import SUPPORTED_PROVIDER_IDS
6
+
7
+ __all__ = ("SUPPORTED_PROVIDER_IDS",)