@oneciel-ai/ciel-runtime 0.1.1 → 0.2.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 (287) hide show
  1. package/README.md +132 -0
  2. package/ciel-runtime-menu.py +56 -6
  3. package/ciel_runtime.py +9379 -35909
  4. package/ciel_runtime_support/advisor_client.py +193 -0
  5. package/ciel_runtime_support/advisor_policy.py +320 -0
  6. package/ciel_runtime_support/advisor_refinement.py +160 -0
  7. package/ciel_runtime_support/advisor_request_builder.py +261 -0
  8. package/ciel_runtime_support/agy_installer.py +169 -0
  9. package/ciel_runtime_support/agy_mcp_restore.py +182 -0
  10. package/ciel_runtime_support/anthropic_model_policy.py +186 -0
  11. package/ciel_runtime_support/anthropic_response_writer.py +255 -0
  12. package/ciel_runtime_support/anthropic_tool_turns.py +130 -0
  13. package/ciel_runtime_support/api_key_cooldown.py +159 -0
  14. package/ciel_runtime_support/architecture.py +488 -1
  15. package/ciel_runtime_support/architecture_budget.py +42 -0
  16. package/ciel_runtime_support/channel_backlog.py +90 -0
  17. package/ciel_runtime_support/channel_cli.py +119 -0
  18. package/ciel_runtime_support/channel_compact_injection.py +82 -0
  19. package/ciel_runtime_support/channel_compact_poll.py +67 -0
  20. package/ciel_runtime_support/channel_compact_request_repository.py +113 -0
  21. package/ciel_runtime_support/channel_config_service.py +281 -0
  22. package/ciel_runtime_support/channel_connection_lifecycle.py +180 -0
  23. package/ciel_runtime_support/channel_connection_registry.py +128 -0
  24. package/ciel_runtime_support/channel_connection_worker.py +284 -0
  25. package/ciel_runtime_support/channel_cursor_recovery.py +92 -0
  26. package/ciel_runtime_support/channel_cursor_repository.py +89 -0
  27. package/ciel_runtime_support/channel_cursor_service.py +178 -0
  28. package/ciel_runtime_support/channel_event_identity.py +212 -0
  29. package/ciel_runtime_support/channel_event_projection.py +315 -0
  30. package/ciel_runtime_support/channel_inflight.py +127 -0
  31. package/ciel_runtime_support/channel_injection.py +115 -0
  32. package/ciel_runtime_support/channel_launch_guard_repository.py +58 -0
  33. package/ciel_runtime_support/channel_launch_policy.py +180 -0
  34. package/ciel_runtime_support/channel_llm_context.py +156 -0
  35. package/ciel_runtime_support/channel_mcp_discovery.py +186 -0
  36. package/ciel_runtime_support/channel_mcp_http_controller.py +239 -0
  37. package/ciel_runtime_support/channel_mcp_ownership.py +148 -0
  38. package/ciel_runtime_support/channel_mcp_tools.py +240 -0
  39. package/ciel_runtime_support/channel_mcp_transport.py +394 -0
  40. package/ciel_runtime_support/channel_message_dedupe.py +65 -0
  41. package/ciel_runtime_support/channel_message_policy.py +256 -0
  42. package/ciel_runtime_support/channel_message_prompt.py +305 -0
  43. package/ciel_runtime_support/channel_message_repository.py +234 -0
  44. package/ciel_runtime_support/channel_notification_projection.py +217 -0
  45. package/ciel_runtime_support/channel_panel.py +162 -0
  46. package/ciel_runtime_support/channel_pending_injection.py +209 -0
  47. package/ciel_runtime_support/channel_pending_poll.py +109 -0
  48. package/ciel_runtime_support/channel_probe_cache.py +433 -0
  49. package/ciel_runtime_support/channel_probe_report.py +101 -0
  50. package/ciel_runtime_support/channel_runtime_environment.py +181 -0
  51. package/ciel_runtime_support/channel_session_lifecycle.py +114 -0
  52. package/ciel_runtime_support/channel_session_repository.py +90 -0
  53. package/ciel_runtime_support/channel_terminal_dispatch.py +115 -0
  54. package/ciel_runtime_support/channel_terminal_input.py +277 -0
  55. package/ciel_runtime_support/channel_terminal_proxy.py +447 -0
  56. package/ciel_runtime_support/channel_tool_context.py +166 -0
  57. package/ciel_runtime_support/channel_transcript.py +414 -0
  58. package/ciel_runtime_support/channel_transcript_repository.py +96 -0
  59. package/ciel_runtime_support/channel_wake_claim_repository.py +126 -0
  60. package/ciel_runtime_support/channel_wake_delivery_repository.py +88 -0
  61. package/ciel_runtime_support/chat_files.py +138 -0
  62. package/ciel_runtime_support/chat_http_controller.py +235 -0
  63. package/ciel_runtime_support/claude_environment.py +375 -0
  64. package/ciel_runtime_support/claude_router.py +247 -193
  65. package/ciel_runtime_support/cli_dispatch.py +792 -0
  66. package/ciel_runtime_support/cli_parser.py +165 -0
  67. package/ciel_runtime_support/cli_usage.py +100 -0
  68. package/ciel_runtime_support/codex_app_server.py +20 -5
  69. package/ciel_runtime_support/codex_channel_sse_launch.py +87 -0
  70. package/ciel_runtime_support/codex_cli.py +42 -6
  71. package/ciel_runtime_support/codex_config.py +323 -0
  72. package/ciel_runtime_support/codex_launch_configuration.py +240 -0
  73. package/ciel_runtime_support/codex_launch_policy.py +66 -0
  74. package/ciel_runtime_support/codex_mcp_integration.py +195 -0
  75. package/ciel_runtime_support/codex_mcp_restore.py +304 -0
  76. package/ciel_runtime_support/codex_model_catalog.py +133 -0
  77. package/ciel_runtime_support/codex_process_lifecycle.py +271 -0
  78. package/ciel_runtime_support/codex_router.py +147 -1
  79. package/ciel_runtime_support/codex_session_repository.py +115 -0
  80. package/ciel_runtime_support/codex_session_selection.py +114 -0
  81. package/ciel_runtime_support/command_asset_installer.py +103 -0
  82. package/ciel_runtime_support/compatibility_probe.py +295 -0
  83. package/ciel_runtime_support/compatibility_protocol.py +251 -0
  84. package/ciel_runtime_support/compatibility_runtime.py +166 -0
  85. package/ciel_runtime_support/compatibility_test.py +370 -0
  86. package/ciel_runtime_support/config_migrations.py +307 -0
  87. package/ciel_runtime_support/config_repository.py +175 -0
  88. package/ciel_runtime_support/config_value_codec.py +64 -0
  89. package/ciel_runtime_support/configuration_cli.py +374 -0
  90. package/ciel_runtime_support/context_compaction.py +280 -0
  91. package/ciel_runtime_support/context_setup.py +208 -0
  92. package/ciel_runtime_support/context_summary_policy.py +392 -0
  93. package/ciel_runtime_support/credential_cli.py +104 -0
  94. package/ciel_runtime_support/credential_management.py +261 -0
  95. package/ciel_runtime_support/credentials.py +269 -0
  96. package/ciel_runtime_support/executable_discovery.py +141 -0
  97. package/ciel_runtime_support/github_copilot_oauth.py +335 -0
  98. package/ciel_runtime_support/github_copilot_oauth_runtime.py +213 -0
  99. package/ciel_runtime_support/header_forwarding.py +73 -0
  100. package/ciel_runtime_support/headless_config.py +221 -0
  101. package/ciel_runtime_support/http_response.py +129 -0
  102. package/ciel_runtime_support/install_diagnostics.py +149 -0
  103. package/ciel_runtime_support/kimi_identity.py +123 -0
  104. package/ciel_runtime_support/launch_diagnostics.py +204 -0
  105. package/ciel_runtime_support/launch_state.py +127 -0
  106. package/ciel_runtime_support/live_api_key_controller.py +58 -0
  107. package/ciel_runtime_support/llm_config_http.py +148 -0
  108. package/ciel_runtime_support/llm_option_config.py +259 -0
  109. package/ciel_runtime_support/llm_presentation_data.py +447 -0
  110. package/ciel_runtime_support/llm_presets.py +773 -0
  111. package/ciel_runtime_support/lm_studio_runtime.py +401 -0
  112. package/ciel_runtime_support/managed_mcp_config.py +144 -0
  113. package/ciel_runtime_support/managed_mcp_discovery.py +151 -0
  114. package/ciel_runtime_support/managed_service_cleanup.py +89 -0
  115. package/ciel_runtime_support/mcp_config_reader.py +230 -0
  116. package/ciel_runtime_support/mcp_http_proxy.py +607 -0
  117. package/ciel_runtime_support/mcp_inventory.py +59 -0
  118. package/ciel_runtime_support/mcp_notification_wait_policy.py +159 -0
  119. package/ciel_runtime_support/mcp_probe_codec.py +136 -0
  120. package/ciel_runtime_support/mcp_probe_transport.py +328 -0
  121. package/ciel_runtime_support/mcp_proxy_codec.py +345 -0
  122. package/ciel_runtime_support/mcp_proxy_config.py +107 -0
  123. package/ciel_runtime_support/mcp_proxy_notifications.py +261 -0
  124. package/ciel_runtime_support/mcp_proxy_process.py +560 -0
  125. package/ciel_runtime_support/mcp_split_proxy_http.py +165 -0
  126. package/ciel_runtime_support/mcp_stdio_probe.py +240 -0
  127. package/ciel_runtime_support/mcp_transport.py +146 -0
  128. package/ciel_runtime_support/model_cache_lifecycle.py +78 -0
  129. package/ciel_runtime_support/model_catalog_projection.py +61 -0
  130. package/ciel_runtime_support/model_context_hints.py +109 -0
  131. package/ciel_runtime_support/model_panel.py +147 -0
  132. package/ciel_runtime_support/model_registry_repository.py +231 -0
  133. package/ciel_runtime_support/npm_runtime.py +191 -0
  134. package/ciel_runtime_support/ollama_catalog.py +462 -0
  135. package/ciel_runtime_support/ollama_catalog_cli.py +41 -0
  136. package/ciel_runtime_support/ollama_catalog_repository.py +75 -0
  137. package/ciel_runtime_support/ollama_context_sync.py +87 -0
  138. package/ciel_runtime_support/ollama_forwarding.py +449 -0
  139. package/ciel_runtime_support/openai_chat_passthrough.py +67 -0
  140. package/ciel_runtime_support/openai_chat_router.py +64 -0
  141. package/ciel_runtime_support/openai_forwarding.py +194 -0
  142. package/ciel_runtime_support/openai_responses_router.py +291 -0
  143. package/ciel_runtime_support/openai_responses_stream.py +135 -0
  144. package/ciel_runtime_support/output_budget.py +89 -0
  145. package/ciel_runtime_support/package_lifecycle.py +217 -0
  146. package/ciel_runtime_support/plan_artifact_controller.py +104 -0
  147. package/ciel_runtime_support/prelaunch.py +959 -0
  148. package/ciel_runtime_support/prelaunch_launch_preference.py +75 -0
  149. package/ciel_runtime_support/prelaunch_panel_projection.py +396 -0
  150. package/ciel_runtime_support/prelaunch_terminal.py +764 -0
  151. package/ciel_runtime_support/process_control.py +708 -0
  152. package/ciel_runtime_support/prompt_compaction.py +322 -0
  153. package/ciel_runtime_support/prompt_injection.py +176 -0
  154. package/ciel_runtime_support/protocols/__init__.py +24 -0
  155. package/ciel_runtime_support/protocols/anthropic_content.py +29 -0
  156. package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +336 -0
  157. package/ciel_runtime_support/protocols/chat_projection.py +315 -0
  158. package/ciel_runtime_support/protocols/conversation_policy.py +217 -0
  159. package/ciel_runtime_support/protocols/conversation_turn_policy.py +972 -0
  160. package/ciel_runtime_support/protocols/ollama_chat.py +111 -0
  161. package/ciel_runtime_support/protocols/ollama_response.py +231 -0
  162. package/ciel_runtime_support/protocols/openai_reasoning.py +75 -0
  163. package/ciel_runtime_support/protocols/openai_responses.py +271 -0
  164. package/ciel_runtime_support/protocols/pseudo_tool_history.py +248 -0
  165. package/ciel_runtime_support/protocols/tool_result_projection.py +112 -0
  166. package/ciel_runtime_support/provider_adapters.py +160 -0
  167. package/ciel_runtime_support/provider_catalog_sources.py +316 -0
  168. package/ciel_runtime_support/provider_choice.py +201 -0
  169. package/ciel_runtime_support/provider_compatibility.py +165 -0
  170. package/ciel_runtime_support/provider_config_mutations.py +361 -0
  171. package/ciel_runtime_support/provider_configuration_service.py +162 -0
  172. package/ciel_runtime_support/provider_context.py +308 -0
  173. package/ciel_runtime_support/provider_contract_projection.py +73 -0
  174. package/ciel_runtime_support/provider_descriptor.py +82 -0
  175. package/ciel_runtime_support/provider_endpoint_policy.py +130 -0
  176. package/ciel_runtime_support/provider_endpoint_probe.py +165 -0
  177. package/ciel_runtime_support/provider_launch_endpoint.py +109 -0
  178. package/ciel_runtime_support/provider_limits.py +457 -0
  179. package/ciel_runtime_support/provider_model_identity.py +139 -0
  180. package/ciel_runtime_support/provider_model_selection.py +431 -0
  181. package/ciel_runtime_support/provider_model_specs.py +142 -0
  182. package/ciel_runtime_support/provider_models.py +263 -0
  183. package/ciel_runtime_support/provider_network.py +176 -0
  184. package/ciel_runtime_support/provider_option_cli.py +238 -0
  185. package/ciel_runtime_support/provider_option_panel.py +275 -0
  186. package/ciel_runtime_support/provider_option_status.py +192 -0
  187. package/ciel_runtime_support/provider_policy.py +101 -0
  188. package/ciel_runtime_support/provider_query_policy.py +67 -0
  189. package/ciel_runtime_support/provider_readiness.py +112 -0
  190. package/ciel_runtime_support/provider_request_access.py +131 -0
  191. package/ciel_runtime_support/provider_request_builder.py +250 -0
  192. package/ciel_runtime_support/provider_responses_passthrough.py +81 -0
  193. package/ciel_runtime_support/provider_runtime_info.py +113 -0
  194. package/ciel_runtime_support/provider_runtime_modes.py +150 -0
  195. package/ciel_runtime_support/provider_sampling_policy.py +46 -0
  196. package/ciel_runtime_support/provider_status.py +145 -0
  197. package/ciel_runtime_support/provider_timeout_policy.py +184 -0
  198. package/ciel_runtime_support/provider_tool_policy.py +145 -0
  199. package/ciel_runtime_support/providers/__init__.py +65 -0
  200. package/ciel_runtime_support/providers/anthropic.py +160 -0
  201. package/ciel_runtime_support/providers/anthropic_catalog.py +119 -0
  202. package/ciel_runtime_support/providers/base.py +232 -0
  203. package/ciel_runtime_support/providers/catalog.py +326 -0
  204. package/ciel_runtime_support/providers/cloud.py +194 -0
  205. package/ciel_runtime_support/providers/constants.py +54 -0
  206. package/ciel_runtime_support/providers/deepseek.py +115 -0
  207. package/ciel_runtime_support/providers/fireworks.py +158 -0
  208. package/ciel_runtime_support/providers/github_copilot_oauth.py +199 -0
  209. package/ciel_runtime_support/providers/kimi.py +297 -0
  210. package/ciel_runtime_support/providers/lm_studio.py +76 -0
  211. package/ciel_runtime_support/providers/meta.py +257 -0
  212. package/ciel_runtime_support/providers/native.py +194 -0
  213. package/ciel_runtime_support/providers/nim.py +69 -0
  214. package/ciel_runtime_support/providers/nvidia.py +158 -0
  215. package/ciel_runtime_support/providers/nvidia_runtime.py +285 -0
  216. package/ciel_runtime_support/providers/ollama.py +181 -0
  217. package/ciel_runtime_support/providers/ollama_context.py +195 -0
  218. package/ciel_runtime_support/providers/ollama_runtime.py +213 -0
  219. package/ciel_runtime_support/providers/opencode.py +220 -0
  220. package/ciel_runtime_support/providers/opencode_go.py +37 -0
  221. package/ciel_runtime_support/providers/openrouter.py +57 -0
  222. package/ciel_runtime_support/providers/vllm.py +65 -0
  223. package/ciel_runtime_support/providers/zai.py +119 -0
  224. package/ciel_runtime_support/pseudo_tool_parser.py +115 -0
  225. package/ciel_runtime_support/rate_limit_policy.py +117 -0
  226. package/ciel_runtime_support/rate_limit_repository.py +154 -0
  227. package/ciel_runtime_support/registry.py +46 -0
  228. package/ciel_runtime_support/request_shortcuts.py +253 -0
  229. package/ciel_runtime_support/request_trace.py +323 -0
  230. package/ciel_runtime_support/response_collection.py +209 -0
  231. package/ciel_runtime_support/router_access.py +238 -0
  232. package/ciel_runtime_support/router_client_lifecycle.py +366 -0
  233. package/ciel_runtime_support/router_health_policy.py +101 -0
  234. package/ciel_runtime_support/router_http.py +513 -0
  235. package/ciel_runtime_support/router_process_lifecycle.py +401 -0
  236. package/ciel_runtime_support/router_rate_limit_service.py +285 -0
  237. package/ciel_runtime_support/router_server_runtime.py +103 -0
  238. package/ciel_runtime_support/router_shortcuts.py +201 -0
  239. package/ciel_runtime_support/routing_fallback.py +73 -0
  240. package/ciel_runtime_support/runtime_activity_repository.py +143 -0
  241. package/ciel_runtime_support/runtime_adapters.py +104 -0
  242. package/ciel_runtime_support/runtime_command_factory.py +73 -0
  243. package/ciel_runtime_support/runtime_compatibility.py +50 -0
  244. package/ciel_runtime_support/runtime_constants.py +178 -0
  245. package/ciel_runtime_support/runtime_launch.py +1602 -0
  246. package/ciel_runtime_support/runtime_llm_options.py +312 -0
  247. package/ciel_runtime_support/runtime_logging.py +161 -0
  248. package/ciel_runtime_support/runtime_paths.py +157 -0
  249. package/ciel_runtime_support/runtime_restart.py +84 -0
  250. package/ciel_runtime_support/runtime_upgrade.py +149 -0
  251. package/ciel_runtime_support/secure_json_repository.py +55 -0
  252. package/ciel_runtime_support/session_import.py +356 -0
  253. package/ciel_runtime_support/settings_repository.py +8 -0
  254. package/ciel_runtime_support/slash_command_assets.py +211 -0
  255. package/ciel_runtime_support/sse_stream.py +57 -0
  256. package/ciel_runtime_support/sse_trace.py +225 -0
  257. package/ciel_runtime_support/statusline_script.py +593 -0
  258. package/ciel_runtime_support/statusline_settings.py +53 -0
  259. package/ciel_runtime_support/stream_chunk_policy.py +18 -0
  260. package/ciel_runtime_support/streaming_anthropic.py +1955 -0
  261. package/ciel_runtime_support/synthetic_tool_policy.py +105 -0
  262. package/ciel_runtime_support/terminal_platform_io.py +127 -0
  263. package/ciel_runtime_support/timeout_profile.py +196 -0
  264. package/ciel_runtime_support/tool_dialects.py +85 -0
  265. package/ciel_runtime_support/tool_exposure_policy.py +63 -0
  266. package/ciel_runtime_support/tool_guard_hooks.py +218 -0
  267. package/ciel_runtime_support/tool_request_projection.py +96 -0
  268. package/ciel_runtime_support/tool_schema.py +483 -0
  269. package/ciel_runtime_support/tool_side_effect_dedupe.py +95 -0
  270. package/ciel_runtime_support/ui_text.py +266 -0
  271. package/ciel_runtime_support/upstream_error_policy.py +104 -0
  272. package/ciel_runtime_support/upstream_retry.py +419 -0
  273. package/ciel_runtime_support/upstream_stream_io.py +106 -0
  274. package/ciel_runtime_support/usage_events.py +96 -0
  275. package/ciel_runtime_support/visible_stream_filters.py +130 -0
  276. package/ciel_runtime_support/web_endpoints.py +447 -0
  277. package/ciel_runtime_support/web_ui.py +915 -0
  278. package/ciel_runtime_support/web_ui_controller.py +189 -0
  279. package/ciel_runtime_support/windows_console_input.py +137 -0
  280. package/ciel_runtime_support/windows_console_mode.py +112 -0
  281. package/docs/Architecture.md +54 -0
  282. package/docs/Configuration.md +17 -0
  283. package/docs/Module-Map.md +1093 -26
  284. package/docs/Providers.md +46 -1
  285. package/docs/adr/0001-runtime-bounded-contexts.md +295 -0
  286. package/npm-bin/run-ciel-runtime.js +22 -2
  287. package/package.json +9 -2
@@ -0,0 +1,158 @@
1
+ """NVIDIA hosted provider adapter."""
2
+
3
+ from dataclasses import dataclass, field, replace
4
+ from typing import Any, Mapping
5
+ from urllib.parse import urlparse
6
+
7
+ from ..architecture import (
8
+ ProviderCapabilities,
9
+ ProviderConfigurationPolicy,
10
+ ProviderConfig,
11
+ ProviderContextPolicy,
12
+ ProviderModelCatalogPolicy,
13
+ ProviderOptionPresentationPolicy,
14
+ ProviderRequestPolicy,
15
+ ProviderStatusPolicy,
16
+ )
17
+ from .base import (
18
+ OpenAICompatibleProviderAdapter,
19
+ configuration_policy,
20
+ provider_configuration,
21
+ )
22
+ from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
23
+
24
+
25
+ def hosted_context_default(model_id: str) -> int:
26
+ model = str(model_id or "").casefold()
27
+ if "kimi-k2.6" in model or "kimi_k2.6" in model:
28
+ return 262144
29
+ if "deepseek" in model:
30
+ return 131072
31
+ return 65536
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class NvidiaHostedProviderAdapter(OpenAICompatibleProviderAdapter):
36
+ name: str = "nvidia-hosted"
37
+ base_url: str = PROVIDER_DEFAULT_BASE_URLS["nvidia-hosted"]
38
+ configuration_defaults_value: dict = field(
39
+ default_factory=lambda: provider_configuration(
40
+ "qwen/qwen3-coder-480b-a35b-instruct",
41
+ api_key="not-used",
42
+ native_compat=False,
43
+ rate_limit_rpm=0,
44
+ rate_limit_status=False,
45
+ context_window=65536,
46
+ max_output_tokens=4096,
47
+ temperature=0.7,
48
+ top_p=0.8,
49
+ request_timeout_ms=DEFAULT_REQUEST_TIMEOUT_MS,
50
+ stream_enabled=True,
51
+ stream_word_chunking=False,
52
+ )
53
+ )
54
+ api_key_display_name_value: str = "NVIDIA"
55
+ api_key_launch_error_value: str = (
56
+ "Launch blocked: NVIDIA hosted requires an NVIDIA API key."
57
+ )
58
+ capabilities_value: ProviderCapabilities = field(
59
+ default_factory=lambda: ProviderCapabilities(
60
+ upstream_protocol="openai_chat", requires_api_key=True
61
+ )
62
+ )
63
+ model_catalog_policy_value: ProviderModelCatalogPolicy = field(
64
+ default_factory=lambda: ProviderModelCatalogPolicy(kind="nvidia")
65
+ )
66
+ request_policy_value: ProviderRequestPolicy = field(
67
+ default_factory=lambda: ProviderRequestPolicy(
68
+ chat_path="/v1/chat/completions",
69
+ models_path="/v1/models",
70
+ model_alias_strategy="ncp",
71
+ stream_required=True,
72
+ managed_service="nvidia_proxy",
73
+ )
74
+ )
75
+
76
+ def normalize_base_url(self, value: str) -> str:
77
+ text = str(value or "").strip()
78
+ parsed = urlparse(text)
79
+ if (
80
+ not text
81
+ or text.startswith("nv" + "api-")
82
+ or not text.startswith(("http://", "https://"))
83
+ or (parsed.hostname or "") in {"127.0.0.1", "localhost"}
84
+ ):
85
+ return PROVIDER_DEFAULT_BASE_URLS["nvidia-hosted"].rstrip("/")
86
+ return text.rstrip("/")
87
+
88
+ def selection_config_updates(self, config: ProviderConfig) -> Mapping[str, Any]:
89
+ normalized = self.normalize_base_url(config.base_url)
90
+ if normalized == str(config.base_url or "").strip().rstrip("/"):
91
+ return {}
92
+ return {"base_url": normalized}
93
+
94
+ def selection_update_status_lines(
95
+ self, config: ProviderConfig, updates: Mapping[str, Any]
96
+ ) -> tuple[str, ...]:
97
+ del config
98
+ if "base_url" not in updates:
99
+ return ()
100
+ return (f"Base URL set to {updates['base_url']} for NVIDIA hosted.",)
101
+
102
+ def configuration_policy(
103
+ self, config: ProviderConfig
104
+ ) -> ProviderConfigurationPolicy:
105
+ del config
106
+ return configuration_policy(
107
+ native_compat_error=(
108
+ "nvidia-hosted does not expose Anthropic /v1/messages; use router mode. "
109
+ "Use self-hosted-nim or vLLM for native /v1/messages."
110
+ ),
111
+ status_fields=(
112
+ "context_window",
113
+ "max_output_tokens",
114
+ "request_timeout_ms",
115
+ "stream_idle_timeout_ms",
116
+ ),
117
+ )
118
+
119
+ def status_policy(self, config: ProviderConfig) -> ProviderStatusPolicy:
120
+ del config
121
+ return ProviderStatusPolicy(kind="nvidia", label="NVIDIA hosted")
122
+
123
+ def preserves_claude_model_alias(self, model_id: str) -> bool:
124
+ return str(model_id).startswith("claude-")
125
+
126
+ def display_model_name(self, model_id: str, provider_label: str) -> str:
127
+ if str(model_id).startswith("claude-nvidia-"):
128
+ cleaned = str(model_id)[len("claude-") :]
129
+ return cleaned.replace("/", " ").replace("-", " ").replace("_", " ").title()
130
+ return super().display_model_name(model_id, provider_label)
131
+
132
+ def context_policy(self, config: ProviderConfig) -> ProviderContextPolicy:
133
+ del config
134
+ return ProviderContextPolicy(
135
+ capacity_strategy="nvidia",
136
+ settings_strategy="standard",
137
+ hosted_timeout=True,
138
+ preset_context_profile="nvidia",
139
+ status_capacity_strategy="openai_budget",
140
+ )
141
+
142
+ def router_native_anthropic_enabled(
143
+ self, config: ProviderConfig, model: str | None = None
144
+ ) -> bool:
145
+ del config, model
146
+ return False
147
+
148
+ def option_presentation_policy(
149
+ self, config: ProviderConfig
150
+ ) -> ProviderOptionPresentationPolicy:
151
+ return replace(
152
+ super().option_presentation_policy(config),
153
+ show_rate_limit=True,
154
+ show_native=False,
155
+ )
156
+
157
+
158
+ __all__ = ["NvidiaHostedProviderAdapter", "hosted_context_default"]
@@ -0,0 +1,285 @@
1
+ """NVIDIA hosted provider runtime and nvd-claude-proxy lifecycle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import importlib
7
+ import importlib.util
8
+ import os
9
+ from pathlib import Path
10
+ import subprocess
11
+ import sys
12
+ import time
13
+ from typing import Any, Callable, Protocol
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class NvidiaProxyRuntimeConfig:
18
+ env_path: Path
19
+ log_path: Path
20
+ package_name: str
21
+ upstream_base_url: str = "https://integrate.api.nvidia.com/v1"
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class NvidiaProxyRuntimePorts:
26
+ load_config: Callable[..., dict[str, Any]]
27
+ read_env_file: Callable[[Path], dict[str, str]]
28
+ is_url_up: Callable[[str], bool]
29
+ find_executable: Callable[[str], str | None]
30
+ positive_int: Callable[[Any], int | None]
31
+ http_json: Callable[..., Any]
32
+ join_url: Callable[[str, str], str]
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class NvidiaProxyRuntime:
37
+ config: NvidiaProxyRuntimeConfig
38
+ ports: NvidiaProxyRuntimePorts
39
+
40
+ def upstream_base_url(self) -> str:
41
+ return self.config.upstream_base_url
42
+
43
+ def proxy_base_url(self) -> str:
44
+ environment = self.ports.read_env_file(self.config.env_path)
45
+ host = environment.get("PROXY_HOST") or "127.0.0.1"
46
+ port = environment.get("PROXY_PORT") or "8788"
47
+ return f"http://{host}:{port}"
48
+
49
+ def api_key(self) -> str:
50
+ return (
51
+ self.ports.read_env_file(self.config.env_path).get("NVIDIA_API_KEY")
52
+ or os.environ.get("NVIDIA_API_KEY")
53
+ or os.environ.get("NV_API_KEY")
54
+ or ""
55
+ ).strip()
56
+
57
+ def install_proxy(self) -> str | None:
58
+ if os.environ.get("CIEL_RUNTIME_AUTO_INSTALL_NCP", "1").lower() in ("0", "false", "no"):
59
+ return None
60
+ self.config.log_path.parent.mkdir(parents=True, exist_ok=True)
61
+ with self.config.log_path.open("ab", buffering=0) as log:
62
+ log.write(b"\n[ciel-runtime] installing nvd-claude-proxy with pip\n")
63
+ process = subprocess.run(
64
+ [sys.executable, "-m", "pip", "install", "--user", "--upgrade", self.config.package_name],
65
+ stdout=log,
66
+ stderr=log,
67
+ timeout=240,
68
+ )
69
+ if process.returncode != 0:
70
+ return None
71
+ importlib.invalidate_caches()
72
+ return self.ports.find_executable("ncp")
73
+
74
+ @staticmethod
75
+ def module_available() -> bool:
76
+ return importlib.util.find_spec("nvd_claude_proxy") is not None
77
+
78
+ def proxy_executable(self) -> str | None:
79
+ return self.ports.find_executable("nvd-claude-proxy") or self.ports.find_executable("ncp")
80
+
81
+ def ensure(self) -> None:
82
+ provider = self.ports.load_config()["providers"]["nvidia-hosted"]
83
+ upstream = provider.get("base_url") or self.upstream_base_url()
84
+ environment = os.environ.copy()
85
+ environment.update(self.ports.read_env_file(self.config.env_path))
86
+ environment["NVIDIA_BASE_URL"] = upstream.rstrip("/")
87
+ environment.setdefault("PROXY_HOST", "127.0.0.1")
88
+ environment.setdefault("PROXY_PORT", "8788")
89
+ environment.setdefault("STORAGE_ENGINE", "sqlite")
90
+ timeout_ms = self.ports.positive_int(provider.get("request_timeout_ms"))
91
+ if timeout_ms:
92
+ environment["REQUEST_TIMEOUT_SECONDS"] = str(max(1, timeout_ms / 1000))
93
+ base_url = f"http://{environment['PROXY_HOST']}:{environment['PROXY_PORT']}"
94
+ if self.ports.is_url_up(f"{base_url}/v1/models"):
95
+ return
96
+ self.config.log_path.parent.mkdir(parents=True, exist_ok=True)
97
+ executable = self.proxy_executable()
98
+ if not (executable or self.module_available()):
99
+ self.install_proxy()
100
+ executable = self.proxy_executable()
101
+ if not (executable or self.module_available()):
102
+ raise RuntimeError(
103
+ "nvd-claude-proxy was not found. Install it with: "
104
+ "python -m pip install --user nvd-claude-proxy"
105
+ )
106
+ creation_flags = getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
107
+ with self.config.log_path.open("ab", buffering=0) as log:
108
+ if executable:
109
+ command = [executable]
110
+ log.write(f"\n[ciel-runtime] starting nvd-claude-proxy executable: {executable}\n".encode())
111
+ else:
112
+ command = [sys.executable, "-m", "nvd_claude_proxy.main"]
113
+ log.write(b"\n[ciel-runtime] starting nvd-claude-proxy module\n")
114
+ subprocess.Popen(
115
+ command,
116
+ stdout=log,
117
+ stderr=log,
118
+ env=environment,
119
+ cwd=str(self.config.env_path.parent),
120
+ creationflags=creation_flags,
121
+ )
122
+ deadline = time.time() + 45
123
+ while time.time() < deadline:
124
+ if self.ports.is_url_up(f"{base_url}/v1/models"):
125
+ return
126
+ time.sleep(0.5)
127
+ raise RuntimeError("nvd-claude-proxy did not become ready")
128
+
129
+ def model_id(self, model_id: str) -> str:
130
+ if model_id.startswith("claude-") and not model_id.startswith("ciel-runtime-"):
131
+ return model_id
132
+ try:
133
+ data = self.ports.http_json(
134
+ self.ports.join_url(self.proxy_base_url(), "/v1/models"),
135
+ timeout=3.0,
136
+ )
137
+ except Exception:
138
+ return model_id
139
+ items = data.get("data") if isinstance(data, dict) else None
140
+ if not isinstance(items, list):
141
+ return model_id
142
+ for item in items:
143
+ if not isinstance(item, dict):
144
+ continue
145
+ proxy_id = str(item.get("id") or "").strip()
146
+ upstream_id = str(item.get("nvidia_id") or "").strip()
147
+ if proxy_id == model_id:
148
+ return proxy_id
149
+ if upstream_id and upstream_id == model_id and proxy_id:
150
+ return proxy_id
151
+ return model_id
152
+
153
+
154
+ class NvidiaPortTerminator(Protocol):
155
+ def __call__(self, port: int, label: str, quiet: bool = False) -> bool: ...
156
+
157
+
158
+ class MatchingProcessTerminator(Protocol):
159
+ def __call__(
160
+ self,
161
+ needles: list[str],
162
+ label: str,
163
+ quiet: bool = False,
164
+ ) -> bool: ...
165
+
166
+
167
+ class ProcessRunner(Protocol):
168
+ def __call__(self, command: list[str], **kwargs: Any) -> Any: ...
169
+
170
+
171
+ @dataclass(frozen=True, slots=True)
172
+ class NvidiaProxyStopPorts:
173
+ read_env_file: Callable[[Path], dict[str, str]]
174
+ positive_int: Callable[[Any], int | None]
175
+ terminate_windows_port: NvidiaPortTerminator
176
+ find_executable: Callable[[str], str | None]
177
+ terminate_matching_processes: MatchingProcessTerminator
178
+ run: ProcessRunner
179
+ log: Callable[[str, str], None]
180
+ output: Callable[[str], None]
181
+
182
+
183
+ @dataclass(frozen=True, slots=True)
184
+ class NvidiaProxyStopper:
185
+ env_path: Path
186
+ ports: NvidiaProxyStopPorts
187
+
188
+ def stop(self, quiet: bool = False, *, platform_name: str = os.name) -> bool:
189
+ label = "Nvidia NCP proxy"
190
+ if platform_name == "nt":
191
+ port = (
192
+ self.ports.positive_int(
193
+ self.ports.read_env_file(self.env_path).get("PROXY_PORT")
194
+ )
195
+ or 8788
196
+ )
197
+ stopped = self.ports.terminate_windows_port(port, label, quiet=True)
198
+ if stopped and not quiet:
199
+ self.ports.output(
200
+ "Stopped existing Nvidia NCP proxy session if one was running."
201
+ )
202
+ return stopped
203
+
204
+ executable = self.ports.find_executable("ncp")
205
+ if not executable:
206
+ return self.ports.terminate_matching_processes(
207
+ ["nvd_claude_proxy"],
208
+ label,
209
+ quiet=quiet,
210
+ )
211
+
212
+ stopped = False
213
+ try:
214
+ self.ports.run(
215
+ [executable, "kill"],
216
+ stdout=subprocess.DEVNULL,
217
+ stderr=subprocess.DEVNULL,
218
+ timeout=10,
219
+ )
220
+ stopped = True
221
+ except (OSError, subprocess.SubprocessError) as exc:
222
+ self.ports.log(
223
+ "WARN",
224
+ f"ncp_proxy_kill_failed executable={executable} "
225
+ f"error={type(exc).__name__}: {exc}",
226
+ )
227
+ for needles in (
228
+ ["nvd-claude-proxy"],
229
+ ["ncp", "proxy"],
230
+ ["nvd_claude_proxy"],
231
+ ):
232
+ stopped = (
233
+ self.ports.terminate_matching_processes(
234
+ needles,
235
+ label,
236
+ quiet=True,
237
+ )
238
+ or stopped
239
+ )
240
+ if stopped and not quiet:
241
+ self.ports.output(
242
+ "Stopped existing Nvidia NCP proxy session if one was running."
243
+ )
244
+ return stopped
245
+
246
+
247
+ @dataclass(frozen=True, slots=True)
248
+ class NvidiaRuntimeApi:
249
+ """Explicit compatibility API for late-bound NVIDIA proxy runtimes."""
250
+
251
+ runtime_factory: Callable[[], NvidiaProxyRuntime]
252
+
253
+ def upstream_base_url(self) -> str:
254
+ return self.runtime_factory().upstream_base_url()
255
+
256
+ def proxy_base_url(self) -> str:
257
+ return self.runtime_factory().proxy_base_url()
258
+
259
+ def api_key(self) -> str:
260
+ return self.runtime_factory().api_key()
261
+
262
+ def install_proxy(self) -> str | None:
263
+ return self.runtime_factory().install_proxy()
264
+
265
+ def module_available(self) -> bool:
266
+ return self.runtime_factory().module_available()
267
+
268
+ def proxy_executable(self) -> str | None:
269
+ return self.runtime_factory().proxy_executable()
270
+
271
+ def ensure(self) -> None:
272
+ self.runtime_factory().ensure()
273
+
274
+ def model_id(self, model_id: str) -> str:
275
+ return self.runtime_factory().model_id(model_id)
276
+
277
+
278
+ __all__ = [
279
+ "NvidiaProxyRuntime",
280
+ "NvidiaProxyRuntimeConfig",
281
+ "NvidiaProxyRuntimePorts",
282
+ "NvidiaProxyStopper",
283
+ "NvidiaProxyStopPorts",
284
+ "NvidiaRuntimeApi",
285
+ ]
@@ -0,0 +1,181 @@
1
+ """Ollama local and cloud provider adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field, replace
6
+
7
+ from ..architecture import (
8
+ ProviderCapabilities,
9
+ ProviderConfigurationPolicy,
10
+ ProviderConfig,
11
+ ProviderContextPolicy,
12
+ ProviderModelCatalogPolicy,
13
+ ProviderOptionPresentationPolicy,
14
+ ProviderRequestPolicy,
15
+ ProviderStatusPolicy,
16
+ )
17
+ from .base import HttpBearerProviderAdapter, provider_configuration
18
+ from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class OllamaProviderAdapter(HttpBearerProviderAdapter):
23
+ name: str = "ollama"
24
+ base_url: str = PROVIDER_DEFAULT_BASE_URLS["ollama"]
25
+ configuration_defaults_value: dict = field(
26
+ default_factory=lambda: provider_configuration(
27
+ "qwen3-coder",
28
+ api_key="ollama",
29
+ custom_models=("qwen3-coder",),
30
+ native_compat=True,
31
+ rate_limit_rpm=0,
32
+ rate_limit_status=False,
33
+ num_ctx="auto",
34
+ num_ctx_min=32768,
35
+ num_ctx_max=131072,
36
+ keep_alive="5m",
37
+ think=False,
38
+ request_timeout_ms=DEFAULT_REQUEST_TIMEOUT_MS,
39
+ stream_enabled=True,
40
+ stream_word_chunking=False,
41
+ ollama_options={
42
+ "temperature": 0.7,
43
+ "top_p": 0.8,
44
+ "top_k": 40,
45
+ "num_predict": 4096,
46
+ },
47
+ )
48
+ )
49
+ send_placeholder_key: bool = True
50
+ api_key_display_name_value: str = "Ollama"
51
+ capabilities_value: ProviderCapabilities = field(
52
+ default_factory=lambda: ProviderCapabilities(
53
+ upstream_protocol="ollama_chat",
54
+ supports_thinking=True,
55
+ local=True,
56
+ )
57
+ )
58
+ request_policy_value: ProviderRequestPolicy = field(
59
+ default_factory=lambda: ProviderRequestPolicy(
60
+ chat_path="/api/chat",
61
+ models_path="/api/tags",
62
+ model_info_path="/api/show",
63
+ default_timeout_seconds=300.0,
64
+ probe_strategy="ollama",
65
+ )
66
+ )
67
+ model_catalog_policy_value: ProviderModelCatalogPolicy = field(
68
+ default_factory=lambda: ProviderModelCatalogPolicy(kind="ollama")
69
+ )
70
+
71
+ def context_policy(self, config: ProviderConfig) -> ProviderContextPolicy:
72
+ del config
73
+ return ProviderContextPolicy(
74
+ capacity_strategy="ollama",
75
+ settings_strategy="ollama",
76
+ uses_catalog_timeout=True,
77
+ preset_context_profile="ollama",
78
+ status_capacity_strategy="ollama_budget",
79
+ )
80
+
81
+ def launch_model_strategy(self, config: ProviderConfig) -> str:
82
+ del config
83
+ return "ollama_unslug"
84
+
85
+ def option_presentation_policy(
86
+ self, config: ProviderConfig
87
+ ) -> ProviderOptionPresentationPolicy:
88
+ del config
89
+ return ProviderOptionPresentationPolicy(
90
+ show_rate_limit=True,
91
+ show_tool_choice=True,
92
+ show_stream=True,
93
+ show_rate_limit_controls=True,
94
+ show_sampling_controls=True,
95
+ show_ip_family_control=True,
96
+ )
97
+
98
+ def model_paths(self, config: ProviderConfig) -> tuple[str, ...]:
99
+ del config
100
+ return ("/api/tags", "/v1/models")
101
+
102
+ def configuration_policy(
103
+ self, config: ProviderConfig
104
+ ) -> ProviderConfigurationPolicy:
105
+ del config
106
+ return ProviderConfigurationPolicy(
107
+ mutation_strategy="ollama", uses_ollama_status=True
108
+ )
109
+
110
+ def status_policy(self, config: ProviderConfig) -> ProviderStatusPolicy:
111
+ return replace(
112
+ super().status_policy(config),
113
+ unreachable_hint="Start Ollama or set a reachable Base URL before launching Claude Code.",
114
+ )
115
+
116
+
117
+ @dataclass(frozen=True)
118
+ class OllamaCloudProviderAdapter(OllamaProviderAdapter):
119
+ name: str = "ollama-cloud"
120
+ base_url: str = PROVIDER_DEFAULT_BASE_URLS["ollama-cloud"]
121
+ configuration_defaults_value: dict = field(
122
+ default_factory=lambda: provider_configuration(
123
+ "glm-5.1",
124
+ custom_models=("glm-5.1",),
125
+ rate_limit_rpm=0,
126
+ rate_limit_status=False,
127
+ num_ctx="auto",
128
+ num_ctx_min=32768,
129
+ num_ctx_max=131072,
130
+ keep_alive="5m",
131
+ think=True,
132
+ request_timeout_ms=DEFAULT_REQUEST_TIMEOUT_MS,
133
+ stream_enabled=True,
134
+ stream_word_chunking=False,
135
+ ollama_options={
136
+ "temperature": 0.7,
137
+ "top_p": 0.8,
138
+ "top_k": 40,
139
+ "num_predict": 4096,
140
+ },
141
+ )
142
+ )
143
+ capabilities_value: ProviderCapabilities = field(
144
+ default_factory=lambda: ProviderCapabilities(
145
+ upstream_protocol="ollama_chat",
146
+ supports_thinking=True,
147
+ requires_api_key=True,
148
+ )
149
+ )
150
+ api_key_display_name_value: str = "Ollama Cloud"
151
+ api_key_launch_error_value: str = (
152
+ "Launch blocked: Ollama Cloud requires an API key."
153
+ )
154
+ model_catalog_policy_value: ProviderModelCatalogPolicy = field(
155
+ default_factory=lambda: ProviderModelCatalogPolicy(
156
+ kind="ollama", use_bundled_catalog_fallback=True
157
+ )
158
+ )
159
+
160
+ def normalize_model_id(self, model_id: str) -> str:
161
+ normalized = super().normalize_model_id(model_id)
162
+ return normalized[:-6] if normalized.endswith(":cloud") else normalized
163
+
164
+ def launch_model_strategy(self, config: ProviderConfig) -> str:
165
+ del config
166
+ return "alias"
167
+
168
+ def context_policy(self, config: ProviderConfig) -> ProviderContextPolicy:
169
+ del config
170
+ return ProviderContextPolicy(
171
+ capacity_strategy="ollama",
172
+ settings_strategy="ollama",
173
+ hosted_timeout=True,
174
+ timeout_weight=1.2,
175
+ uses_catalog_timeout=True,
176
+ preset_context_profile="ollama",
177
+ status_capacity_strategy="ollama_budget",
178
+ )
179
+
180
+
181
+ __all__ = ["OllamaCloudProviderAdapter", "OllamaProviderAdapter"]