@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,275 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable
5
+
6
+ from .architecture import ProviderOptionPresentationPolicy
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class OptionPanelPolicy:
11
+ presentation: ProviderOptionPresentationPolicy
12
+ context_strategy: str
13
+ shows_workflows: bool
14
+ timeout_default: str
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class OptionPanelText:
19
+ compact_text: Callable[[Any, int], str]
20
+ ui_text: Callable[[str, str], str]
21
+ context_status: Callable[[str, dict[str, Any]], str]
22
+ applied_preset: Callable[[str, dict[str, Any]], str]
23
+ preset_text: Callable[[str, str], tuple[str, str]]
24
+ timeout_status: Callable[[dict[str, Any], str], str]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class OptionPanelRuntime:
29
+ router_debug_external: Callable[[], bool]
30
+ message_preview_chars: Callable[[], int]
31
+ direct_native: Callable[[str, dict[str, Any]], bool]
32
+ capability_string: Callable[[str, dict[str, Any], str], str]
33
+ current_model: Callable[[str, dict[str, Any]], str]
34
+ workflows_enabled: Callable[[str, dict[str, Any]], bool]
35
+ ultracode_enabled: Callable[[str, dict[str, Any]], bool]
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class OptionPanelProvider:
40
+ ollama_options: Callable[[dict[str, Any]], dict[str, Any]]
41
+ ollama_context_status: Callable[[dict[str, Any]], str]
42
+ ollama_think_status: Callable[[str, dict[str, Any]], str]
43
+ query_status: Callable[[str, dict[str, Any]], str]
44
+ tool_choice_status: Callable[[str, dict[str, Any]], str]
45
+ rate_limit_status: Callable[[str, dict[str, Any]], str]
46
+ rate_limit_rpm: Callable[[str, dict[str, Any]], str]
47
+ ip_family: Callable[[str, dict[str, Any]], str]
48
+ parse_bool: Callable[..., bool]
49
+ configured_rate_limit: Callable[[str, dict[str, Any]], int | None]
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class OptionPanelServices:
54
+ text: OptionPanelText
55
+ runtime: OptionPanelRuntime
56
+ provider: OptionPanelProvider
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class OptionValuePolicy:
61
+ context_strategy: str
62
+ native_default: bool
63
+
64
+
65
+ def build_option_panel_rows(
66
+ provider: str,
67
+ config: dict[str, Any],
68
+ policy: OptionPanelPolicy,
69
+ services: OptionPanelServices,
70
+ *,
71
+ language: str,
72
+ ) -> tuple[list[str], list[str]]:
73
+ text = services.text
74
+ runtime = services.runtime
75
+ rows: list[str] = []
76
+ values: list[str] = []
77
+
78
+ def add(label: str, key: str, value: Any) -> None:
79
+ rows.append(f"{label:<24} [{text.compact_text(value, 56)}]")
80
+ values.append(key)
81
+
82
+ add(text.ui_text("context_setup", language), "context_setup", text.context_status(provider, config))
83
+ preset = text.applied_preset(provider, config)
84
+ add(text.ui_text("apply_preset", language), "preset", text.preset_text(preset, language)[0])
85
+ add(text.ui_text("timeout_preset", language), "timeout_profile", text.timeout_status(config, language))
86
+ add("Router debug external", "router_debug_external_access", "on" if runtime.router_debug_external() else "off")
87
+ add("Event message preview", "router_debug_message_preview_chars", runtime.message_preview_chars())
88
+ if not runtime.direct_native(provider, config) and policy.shows_workflows:
89
+ model = runtime.current_model(provider, config)
90
+ capabilities = runtime.capability_string(provider, config, model)
91
+ add("Claude Code workflows", "workflows_enabled", "on" if runtime.workflows_enabled(provider, config) else "off")
92
+ add("Claude Code ultracode", "ultracode_enabled", "on" if runtime.ultracode_enabled(provider, config) else "off")
93
+ add("Claude Code capabilities", "claude_code_supported_capabilities", capabilities or "auto/none")
94
+ if policy.context_strategy == "ollama":
95
+ _add_ollama_controls(add, provider, config, services)
96
+ else:
97
+ _add_standard_controls(add, provider, config, policy, services)
98
+ rows.append(text.ui_text("back", language))
99
+ values.append("back")
100
+ return rows, values
101
+
102
+
103
+ def _add_ollama_controls(
104
+ add: Callable[[str, str, Any], None],
105
+ provider: str,
106
+ config: dict[str, Any],
107
+ services: OptionPanelServices,
108
+ ) -> None:
109
+ projection = services.provider
110
+ options = projection.ollama_options(config)
111
+ add("Context window", "num_ctx", projection.ollama_context_status(config))
112
+ add("Context min", "num_ctx_min", config.get("num_ctx_min", "default"))
113
+ add("Context max", "num_ctx_max", config.get("num_ctx_max", "default"))
114
+ add("Max output tokens", "num_predict", options.get("num_predict", "default"))
115
+ add("Query string", "force_query_string", projection.query_status(provider, config))
116
+ add("Tool choice", "supports_tool_choice", projection.tool_choice_status(provider, config))
117
+ add("Temperature", "temperature", options.get("temperature", "default"))
118
+ add("Top P", "top_p", options.get("top_p", "default"))
119
+ add("Top K", "top_k", options.get("top_k", "default"))
120
+ model = services.runtime.current_model(provider, config)
121
+ add("Think", "think", projection.ollama_think_status(model, config))
122
+ add("Keep alive", "keep_alive", config.get("keep_alive", "default"))
123
+ add("Timeout ms", "request_timeout_ms", config.get("request_timeout_ms", "default"))
124
+ _add_stream_controls(add, config)
125
+ add("RPM limiter", "rate_limit_enabled", projection.rate_limit_status(provider, config))
126
+ add("Rate limit RPM", "rate_limit_rpm", projection.rate_limit_rpm(provider, config))
127
+ add("Rate limit status", "rate_limit_status", "on" if bool(config.get("rate_limit_status", False)) else "off")
128
+ add("IP family", "ip_family", projection.ip_family(provider, config))
129
+
130
+
131
+ def _add_standard_controls(
132
+ add: Callable[[str, str, Any], None],
133
+ provider: str,
134
+ config: dict[str, Any],
135
+ policy: OptionPanelPolicy,
136
+ services: OptionPanelServices,
137
+ ) -> None:
138
+ presentation = policy.presentation
139
+ projection = services.provider
140
+ if policy.context_strategy == "standard":
141
+ add("Context window", "context_window", config.get("context_window", "default"))
142
+ add("Context reserve", "context_reserve_tokens", config.get("context_reserve_tokens", "default"))
143
+ add("Max output tokens", "max_output_tokens", config.get("max_output_tokens", "default"))
144
+ add("Query string", "force_query_string", projection.query_status(provider, config))
145
+ if presentation.show_tool_choice:
146
+ add("Tool choice", "supports_tool_choice", projection.tool_choice_status(provider, config))
147
+ if policy.context_strategy == "standard":
148
+ add("Timeout ms", "request_timeout_ms", config.get("request_timeout_ms", "default"))
149
+ if presentation.show_rate_limit_controls:
150
+ add("RPM limiter", "rate_limit_enabled", projection.rate_limit_status(provider, config))
151
+ add("Rate limit RPM", "rate_limit_rpm", projection.rate_limit_rpm(provider, config))
152
+ add("Rate limit status", "rate_limit_status", "on" if bool(config.get("rate_limit_status", False)) else "off")
153
+ if presentation.show_sampling_controls:
154
+ for label, key in (("Temperature", "temperature"), ("Top P", "top_p"), ("Top K", "top_k")):
155
+ add(label, key, config.get(key, "default"))
156
+ if presentation.show_native:
157
+ add("Native compatibility", "native_compat", bool(config.get("native_compat", True)))
158
+ if presentation.show_stream:
159
+ _add_stream_controls(add, config)
160
+ if presentation.show_ip_family_control:
161
+ add("IP family", "ip_family", projection.ip_family(provider, config))
162
+ elif presentation.show_route:
163
+ routed = projection.parse_bool(config.get("route_through_router"), default=False)
164
+ add("Route through router", "route_through_router", "on" if routed else "off")
165
+ add("Timeout ms", "request_timeout_ms", config.get("request_timeout_ms", policy.timeout_default))
166
+ if presentation.show_ip_family_control and routed:
167
+ add("IP family", "ip_family", projection.ip_family(provider, config))
168
+
169
+
170
+ def _add_stream_controls(
171
+ add: Callable[[str, str, Any], None], config: dict[str, Any]
172
+ ) -> None:
173
+ enabled = bool(config.get("stream_enabled", True))
174
+ add("Stream", "stream_enabled", "on" if enabled else "off")
175
+ if enabled:
176
+ add("Stream idle timeout ms", "stream_idle_timeout_ms", config.get("stream_idle_timeout_ms", "auto"))
177
+ add("Stream word chunking", "stream_word_chunking", "on" if bool(config.get("stream_word_chunking", False)) else "off")
178
+
179
+
180
+ def current_option_bool(
181
+ provider: str,
182
+ config: dict[str, Any],
183
+ key: str,
184
+ policy: OptionValuePolicy,
185
+ services: OptionPanelServices,
186
+ ) -> bool:
187
+ runtime = services.runtime
188
+ projection = services.provider
189
+ if key == "stream_enabled":
190
+ return bool(config.get("stream_enabled", True))
191
+ if key == "stream_word_chunking":
192
+ return bool(config.get("stream_word_chunking", False))
193
+ if key == "native_compat":
194
+ return bool(config.get("native_compat", policy.native_default))
195
+ if key == "think":
196
+ return bool(config.get("think", False))
197
+ if key == "rate_limit_enabled":
198
+ return bool(projection.configured_rate_limit(provider, config))
199
+ if key == "rate_limit_status":
200
+ return bool(config.get("rate_limit_status", False))
201
+ if key == "router_debug_external_access":
202
+ return runtime.router_debug_external()
203
+ if key == "route_through_router":
204
+ return projection.parse_bool(config.get("route_through_router"), default=False)
205
+ if key == "workflows_enabled":
206
+ return runtime.workflows_enabled(provider, config)
207
+ if key == "ultracode_enabled":
208
+ return runtime.ultracode_enabled(provider, config)
209
+ return bool(config.get(key, False))
210
+
211
+
212
+ def option_prompt_default(
213
+ provider: str,
214
+ config: dict[str, Any],
215
+ key: str,
216
+ policy: OptionValuePolicy,
217
+ services: OptionPanelServices,
218
+ ) -> str:
219
+ runtime = services.runtime
220
+ projection = services.provider
221
+ boolean_keys = {
222
+ "router_debug_external_access",
223
+ "route_through_router",
224
+ "stream_enabled",
225
+ "stream_word_chunking",
226
+ "rate_limit_status",
227
+ "rate_limit_enabled",
228
+ "workflows_enabled",
229
+ "ultracode_enabled",
230
+ }
231
+ if key in boolean_keys:
232
+ return "true" if current_option_bool(provider, config, key, policy, services) else "false"
233
+ if key == "router_debug_message_preview_chars":
234
+ return str(runtime.message_preview_chars())
235
+ if key == "claude_code_supported_capabilities":
236
+ model = runtime.current_model(provider, config)
237
+ return runtime.capability_string(provider, config, model)
238
+ if key == "force_query_string":
239
+ return str(config.get("force_query_string") or "")
240
+ if key == "supports_tool_choice":
241
+ value = config.get("supports_tool_choice")
242
+ return "auto" if value is None else ("true" if bool(value) else "false")
243
+ if key == "ip_family":
244
+ return projection.ip_family(provider, config)
245
+ if policy.context_strategy == "ollama":
246
+ options = projection.ollama_options(config)
247
+ if key == "num_ctx":
248
+ return str(config.get("num_ctx", "auto"))
249
+ if key in (
250
+ "num_ctx_min",
251
+ "num_ctx_max",
252
+ "keep_alive",
253
+ "think",
254
+ "request_timeout_ms",
255
+ "stream_idle_timeout_ms",
256
+ ):
257
+ return str(config.get(key, ""))
258
+ if key in options:
259
+ return str(options[key])
260
+ return ""
261
+ value = config.get(key)
262
+ return "" if value is None else str(value)
263
+
264
+
265
+ __all__ = [
266
+ "OptionPanelPolicy",
267
+ "OptionPanelProvider",
268
+ "OptionPanelRuntime",
269
+ "OptionPanelServices",
270
+ "OptionPanelText",
271
+ "OptionValuePolicy",
272
+ "build_option_panel_rows",
273
+ "current_option_bool",
274
+ "option_prompt_default",
275
+ ]
@@ -0,0 +1,192 @@
1
+ """Presentation projection for provider and LLM option status."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+
10
+ def format_context_tokens(value: int | None) -> str:
11
+ if not value:
12
+ return "unknown"
13
+ if value >= 1024 * 1024 and value % (1024 * 1024) == 0:
14
+ return f"{value // (1024 * 1024)}M"
15
+ if value >= 1024 and value % 1024 == 0:
16
+ return f"{value // 1024}K"
17
+ return f"{value:,}"
18
+
19
+
20
+ def format_parameter_count(
21
+ value: Any, positive_int: Callable[[Any], int | None]
22
+ ) -> str:
23
+ fixed = positive_int(value)
24
+ if not fixed:
25
+ return ""
26
+ for suffix, scale in (
27
+ ("T", 1_000_000_000_000),
28
+ ("B", 1_000_000_000),
29
+ ("M", 1_000_000),
30
+ ):
31
+ if fixed >= scale:
32
+ scaled = fixed / scale
33
+ text = f"{scaled:.1f}".rstrip("0").rstrip(".")
34
+ return f"{text}{suffix}"
35
+ return str(fixed)
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class ProviderContextStatusPorts:
40
+ capacity: Callable[[str, dict[str, Any]], int | None]
41
+ context_policy: Callable[..., Any]
42
+ ollama_num_ctx_status: Callable[[dict[str, Any]], str]
43
+ positive_int: Callable[[Any], int | None]
44
+
45
+
46
+ @dataclass(frozen=True, slots=True)
47
+ class ProviderContextStatusProjection:
48
+ ports: ProviderContextStatusPorts
49
+
50
+ def status(self, provider: str, config: dict[str, Any]) -> str:
51
+ capacity = self.ports.capacity(provider, config)
52
+ cap_text = format_context_tokens(capacity)
53
+ strategy = self.ports.context_policy(provider, config).settings_strategy
54
+ if strategy == "ollama":
55
+ return f"model max {cap_text}; {self.ports.ollama_num_ctx_status(config)}"
56
+ if strategy == "standard":
57
+ window = self.ports.positive_int(config.get("context_window"))
58
+ reserve = self.ports.positive_int(config.get("context_reserve_tokens"))
59
+ reserve_text = (
60
+ f"; reserve {format_context_tokens(reserve)}" if reserve else ""
61
+ )
62
+ return (
63
+ f"model max {cap_text}; window {format_context_tokens(window)}"
64
+ f"{reserve_text}"
65
+ )
66
+ if strategy == "managed":
67
+ return "managed by Claude Code"
68
+ return f"model max {cap_text}"
69
+
70
+
71
+ @dataclass(frozen=True, slots=True)
72
+ class ProviderOptionStatusPorts:
73
+ configured_adapter: Callable[..., Any]
74
+ contract_config: Callable[..., Any]
75
+ rate_usage: Callable[..., tuple[int, int | None]]
76
+ ollama_num_ctx: Callable[[dict[str, Any]], str]
77
+ ollama_options_status: Callable[[dict[str, Any]], str]
78
+ ip_family: Callable[..., str]
79
+ parse_bool: Callable[..., bool]
80
+ tool_choice_status: Callable[..., str]
81
+ ollama_extra_options: Callable[[dict[str, Any]], dict[str, Any]]
82
+ anthropic_routed: Callable[..., bool]
83
+
84
+
85
+ class ProviderOptionStatusProjection:
86
+ def __init__(
87
+ self,
88
+ sampling_options: tuple[str, ...],
89
+ ports: ProviderOptionStatusPorts,
90
+ ) -> None:
91
+ self.sampling_options = sampling_options
92
+ self.ports = ports
93
+
94
+ def sampling(self, config: dict[str, Any]) -> list[str]:
95
+ return [
96
+ f"{key}={config.get(key, 'default')}" for key in self.sampling_options
97
+ ]
98
+
99
+ def provider(self, provider: str, config: dict[str, Any]) -> str:
100
+ adapter = self.ports.configured_adapter(provider, config)
101
+ contract = self.ports.contract_config(provider, config)
102
+ presentation = adapter.option_presentation_policy(contract)
103
+ context_strategy = adapter.context_policy(contract).settings_strategy
104
+ timeout = config.get("request_timeout_ms", "default")
105
+ timeout_text = f"{timeout}ms" if timeout != "default" else "default"
106
+ parts = [
107
+ f"max_output_tokens={config.get('max_output_tokens', 'default')}",
108
+ f"timeout={timeout_text}",
109
+ ]
110
+ if config.get("stream_idle_timeout_ms") is not None:
111
+ parts.append(f"stream_idle_timeout={config['stream_idle_timeout_ms']}ms")
112
+ if presentation.show_rate_limit:
113
+ parts.extend(self._rate_limit(provider, config))
114
+ if context_strategy == "ollama":
115
+ parts.insert(0, f"num_ctx={self.ports.ollama_num_ctx(config)}")
116
+ parts.append(
117
+ f"ollama_options={self.ports.ollama_options_status(config)}"
118
+ )
119
+ if context_strategy == "standard":
120
+ parts.insert(0, f"context_window={config.get('context_window', 'default')}")
121
+ parts.insert(
122
+ 1, f"reserve={config.get('context_reserve_tokens', 'default')}"
123
+ )
124
+ if presentation.show_native:
125
+ parts.append(f"native={bool(config.get('native_compat', True))}")
126
+ if presentation.show_ip_family:
127
+ overrides = config.get("model_endpoints")
128
+ parts.append(f"ip_family={self.ports.ip_family(provider, config)}")
129
+ parts.append(
130
+ f"endpoint_overrides={len(overrides) if isinstance(overrides, dict) else 0}"
131
+ )
132
+ if presentation.show_route:
133
+ routed = self.ports.parse_bool(
134
+ config.get("route_through_router"), default=False
135
+ )
136
+ parts.append(f"routed={'on' if routed else 'off'}")
137
+ elif presentation.show_tool_choice:
138
+ parts.append(
139
+ f"tool_choice={self.ports.tool_choice_status(provider, config)}"
140
+ )
141
+ query = str(config.get("force_query_string") or "").strip()
142
+ if query:
143
+ parts.append(f"query={query}")
144
+ if presentation.show_sampling:
145
+ parts.extend(self.sampling(config))
146
+ if presentation.show_stream:
147
+ parts.append(
148
+ f"stream={'on' if bool(config.get('stream_enabled', True)) else 'off'}"
149
+ )
150
+ if bool(config.get("stream_word_chunking", False)):
151
+ parts.append("word_chunk=on")
152
+ return ", ".join(parts)
153
+
154
+ def _rate_limit(self, provider: str, config: dict[str, Any]) -> list[str]:
155
+ parts = [f"rate_limit_rpm={config.get('rate_limit_rpm', 0)}"]
156
+ if bool(config.get("rate_limit_status", False)):
157
+ used, limit = self.ports.rate_usage(provider, config)
158
+ if limit is not None:
159
+ suffix = f"{used}/{limit}" if limit > 0 else f"{used}/min(unmanaged)"
160
+ parts.append(f"rpm_used={suffix}")
161
+ return parts
162
+
163
+ def llm(self, provider: str, config: dict[str, Any]) -> str:
164
+ adapter = self.ports.configured_adapter(provider, config)
165
+ contract = self.ports.contract_config(provider, config)
166
+ presentation = adapter.option_presentation_policy(contract)
167
+ if adapter.context_policy(contract).settings_strategy == "ollama":
168
+ options = self.ports.ollama_extra_options(config)
169
+ pieces = [
170
+ f"ctx {self.ports.ollama_num_ctx(config)}",
171
+ f"keep {config.get('keep_alive', 'default')}",
172
+ f"think {bool(config.get('think', False))}",
173
+ f"timeout {config.get('request_timeout_ms', 'default')}ms",
174
+ ]
175
+ if config.get("stream_idle_timeout_ms") is not None:
176
+ pieces.append(
177
+ f"stream_idle_timeout={config['stream_idle_timeout_ms']}ms"
178
+ )
179
+ for key in ("num_predict", "temperature", "top_p", "top_k"):
180
+ if key in options:
181
+ pieces.append(f"{key}={options[key]}")
182
+ return "; ".join(pieces)
183
+ owns_model = adapter.configuration_policy(contract).runtime_owns_model
184
+ if presentation.show_route and not owns_model:
185
+ return (
186
+ f"max_output_tokens={config.get('max_output_tokens', 'Claude Code default')}, "
187
+ f"timeout={config.get('request_timeout_ms', 'Claude Code default')}ms, "
188
+ f"routed={'on' if self.ports.anthropic_routed(provider, config) else 'off'}"
189
+ )
190
+ if presentation.show_tool_choice or presentation.show_route:
191
+ return self.provider(provider, config)
192
+ return "provider defaults"
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable
5
+
6
+
7
+ @dataclass(frozen=True, slots=True)
8
+ class ProviderWireServices:
9
+ normalize_model_id: Callable[..., Any]
10
+ openai_chat_reasoning_passback_enabled_for_body: Callable[..., Any]
11
+ preserves_anthropic_thinking_contract: Callable[..., Any]
12
+ provider_supports_tool_choice: Callable[..., Any]
13
+ resolve_requested_model: Callable[..., Any]
14
+ select_provider_protocol: Callable[..., Any]
15
+
16
+
17
+ def resolve_provider_wire_profile(provider: str, pcfg: dict[str, Any], body: dict[str, Any] | None = None,
18
+ *,
19
+ services: ProviderWireServices,
20
+ ) -> dict[str, str]:
21
+ """Return the active provider/base-url wire profile for this request.
22
+
23
+ The same model id can travel through different protocol adapters depending
24
+ on provider and base URL. Keep this provider-scoped; model metadata only
25
+ supplies limits and hints.
26
+ """
27
+
28
+ normalize_model_id = services.normalize_model_id
29
+ openai_chat_reasoning_passback_enabled_for_body = services.openai_chat_reasoning_passback_enabled_for_body
30
+ preserves_anthropic_thinking_contract = services.preserves_anthropic_thinking_contract
31
+ provider_supports_tool_choice = services.provider_supports_tool_choice
32
+ resolve_requested_model = services.resolve_requested_model
33
+ select_provider_protocol = services.select_provider_protocol
34
+ request_model = str((body or {}).get("model") or pcfg.get("current_model") or "")
35
+ try:
36
+ model = resolve_requested_model(provider, pcfg, request_model)
37
+ except Exception:
38
+ model = normalize_model_id(provider, request_model)
39
+
40
+ selected_protocol = select_provider_protocol(provider, pcfg, "anthropic_messages", model)
41
+ upstream_format = str(selected_protocol).replace("_", "-")
42
+ endpoint_family = upstream_format
43
+
44
+ if preserves_anthropic_thinking_contract(provider, pcfg):
45
+ thinking_policy = "preserve"
46
+ elif (
47
+ upstream_format == "openai-chat"
48
+ and body is not None
49
+ and openai_chat_reasoning_passback_enabled_for_body(provider, pcfg, body)
50
+ ):
51
+ thinking_policy = "openai-reasoning-passback"
52
+ else:
53
+ thinking_policy = "strip"
54
+
55
+ return {
56
+ "provider": provider,
57
+ "model": model,
58
+ "endpoint_family": endpoint_family,
59
+ "upstream_format": upstream_format,
60
+ "thinking_policy": thinking_policy,
61
+ "tool_choice_policy": "forward" if provider_supports_tool_choice(provider, pcfg, body or {}) else "strip",
62
+ "metadata_policy": "strip-internal-upstream-only",
63
+ }
64
+
65
+
66
+ @dataclass(frozen=True, slots=True)
67
+ class ProviderRequestServices:
68
+ apply_provider_adapter_request_policy: Callable[..., Any]
69
+ normalize_anthropic_system_role_messages: Callable[..., Any]
70
+ normalize_anthropic_tool_turns_for_provider: Callable[..., Any]
71
+ normalize_thinking_for_non_anthropic_provider: Callable[..., Any]
72
+ normalize_tool_choice_for_provider: Callable[..., Any]
73
+ provider_wire_profile: Callable[..., Any]
74
+ sanitize_assistant_pseudo_tool_text_history: Callable[..., Any]
75
+
76
+
77
+ def normalize_provider_request(provider: str, pcfg: dict[str, Any], body: dict[str, Any],
78
+ *,
79
+ services: ProviderRequestServices,
80
+ ) -> dict[str, Any]:
81
+ """Normalize a Claude Code /v1/messages request for the active provider wire profile."""
82
+
83
+ apply_provider_adapter_request_policy = services.apply_provider_adapter_request_policy
84
+ normalize_anthropic_system_role_messages = services.normalize_anthropic_system_role_messages
85
+ normalize_anthropic_tool_turns_for_provider = services.normalize_anthropic_tool_turns_for_provider
86
+ normalize_thinking_for_non_anthropic_provider = services.normalize_thinking_for_non_anthropic_provider
87
+ normalize_tool_choice_for_provider = services.normalize_tool_choice_for_provider
88
+ provider_wire_profile = services.provider_wire_profile
89
+ sanitize_assistant_pseudo_tool_text_history = services.sanitize_assistant_pseudo_tool_text_history
90
+ profile = provider_wire_profile(provider, pcfg, body)
91
+ out = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
92
+ out = apply_provider_adapter_request_policy(provider, pcfg, out)
93
+ out = normalize_tool_choice_for_provider(provider, pcfg, out)
94
+ out = sanitize_assistant_pseudo_tool_text_history(out)
95
+ out = normalize_anthropic_tool_turns_for_provider(provider, pcfg, out)
96
+ if profile.get("upstream_format") == "anthropic-messages":
97
+ out = normalize_anthropic_system_role_messages(out)
98
+ return out
99
+
100
+
101
+ __all__ = ["ProviderRequestServices", "ProviderWireServices", "normalize_provider_request", "resolve_provider_wire_profile"]
@@ -0,0 +1,67 @@
1
+ """Provider-aware upstream query-string projection policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+ from urllib.parse import parse_qs, urlparse
8
+
9
+
10
+ ProviderConfig = dict[str, Any]
11
+
12
+
13
+ @dataclass(frozen=True, slots=True)
14
+ class ProviderQueryPolicy:
15
+ normalize_provider: Callable[[str], str]
16
+ propagates_inbound_beta: Callable[[str, ProviderConfig], bool]
17
+
18
+ @staticmethod
19
+ def inbound_has_beta(request_path: str) -> bool:
20
+ query = urlparse(request_path).query
21
+ return any(
22
+ value.strip().lower() in {"true", "1"}
23
+ for value in parse_qs(query).get("beta", ())
24
+ )
25
+
26
+ def upstream_query(
27
+ self,
28
+ config: ProviderConfig,
29
+ request_path: str,
30
+ provider: str | None = None,
31
+ ) -> str:
32
+ forced = self._forced_query(config)
33
+ if forced:
34
+ return forced
35
+ provider_name = str(
36
+ provider or config.get("provider") or ""
37
+ ).strip()
38
+ if not provider_name:
39
+ return ""
40
+ provider_key = self.normalize_provider(provider_name)
41
+ if (
42
+ self.propagates_inbound_beta(provider_key, config)
43
+ and self.inbound_has_beta(request_path)
44
+ ):
45
+ return "beta=true"
46
+ return ""
47
+
48
+ def status(self, provider: str, config: ProviderConfig) -> str:
49
+ forced = self._forced_query(config)
50
+ if forced:
51
+ return forced
52
+ provider_key = self.normalize_provider(str(provider))
53
+ if self.propagates_inbound_beta(provider_key, config):
54
+ return "auto (beta=true when routed)"
55
+ return "empty"
56
+
57
+ @staticmethod
58
+ def _forced_query(config: ProviderConfig) -> str:
59
+ return (
60
+ str(config.get("force_query_string") or "")
61
+ .strip()
62
+ .lstrip("?")
63
+ .strip()
64
+ )
65
+
66
+
67
+ __all__ = ["ProviderQueryPolicy"]