@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.
- package/README.md +132 -0
- package/ciel-runtime-menu.py +56 -6
- package/ciel_runtime.py +9379 -35909
- package/ciel_runtime_support/advisor_client.py +193 -0
- package/ciel_runtime_support/advisor_policy.py +320 -0
- package/ciel_runtime_support/advisor_refinement.py +160 -0
- package/ciel_runtime_support/advisor_request_builder.py +261 -0
- package/ciel_runtime_support/agy_installer.py +169 -0
- package/ciel_runtime_support/agy_mcp_restore.py +182 -0
- package/ciel_runtime_support/anthropic_model_policy.py +186 -0
- package/ciel_runtime_support/anthropic_response_writer.py +255 -0
- package/ciel_runtime_support/anthropic_tool_turns.py +130 -0
- package/ciel_runtime_support/api_key_cooldown.py +159 -0
- package/ciel_runtime_support/architecture.py +488 -1
- package/ciel_runtime_support/architecture_budget.py +42 -0
- package/ciel_runtime_support/channel_backlog.py +90 -0
- package/ciel_runtime_support/channel_cli.py +119 -0
- package/ciel_runtime_support/channel_compact_injection.py +82 -0
- package/ciel_runtime_support/channel_compact_poll.py +67 -0
- package/ciel_runtime_support/channel_compact_request_repository.py +113 -0
- package/ciel_runtime_support/channel_config_service.py +281 -0
- package/ciel_runtime_support/channel_connection_lifecycle.py +180 -0
- package/ciel_runtime_support/channel_connection_registry.py +128 -0
- package/ciel_runtime_support/channel_connection_worker.py +284 -0
- package/ciel_runtime_support/channel_cursor_recovery.py +92 -0
- package/ciel_runtime_support/channel_cursor_repository.py +89 -0
- package/ciel_runtime_support/channel_cursor_service.py +178 -0
- package/ciel_runtime_support/channel_event_identity.py +212 -0
- package/ciel_runtime_support/channel_event_projection.py +315 -0
- package/ciel_runtime_support/channel_inflight.py +127 -0
- package/ciel_runtime_support/channel_injection.py +115 -0
- package/ciel_runtime_support/channel_launch_guard_repository.py +58 -0
- package/ciel_runtime_support/channel_launch_policy.py +180 -0
- package/ciel_runtime_support/channel_llm_context.py +156 -0
- package/ciel_runtime_support/channel_mcp_discovery.py +186 -0
- package/ciel_runtime_support/channel_mcp_http_controller.py +239 -0
- package/ciel_runtime_support/channel_mcp_ownership.py +148 -0
- package/ciel_runtime_support/channel_mcp_tools.py +240 -0
- package/ciel_runtime_support/channel_mcp_transport.py +394 -0
- package/ciel_runtime_support/channel_message_dedupe.py +65 -0
- package/ciel_runtime_support/channel_message_policy.py +256 -0
- package/ciel_runtime_support/channel_message_prompt.py +305 -0
- package/ciel_runtime_support/channel_message_repository.py +234 -0
- package/ciel_runtime_support/channel_notification_projection.py +217 -0
- package/ciel_runtime_support/channel_panel.py +162 -0
- package/ciel_runtime_support/channel_pending_injection.py +209 -0
- package/ciel_runtime_support/channel_pending_poll.py +109 -0
- package/ciel_runtime_support/channel_probe_cache.py +433 -0
- package/ciel_runtime_support/channel_probe_report.py +101 -0
- package/ciel_runtime_support/channel_runtime_environment.py +181 -0
- package/ciel_runtime_support/channel_session_lifecycle.py +114 -0
- package/ciel_runtime_support/channel_session_repository.py +90 -0
- package/ciel_runtime_support/channel_terminal_dispatch.py +115 -0
- package/ciel_runtime_support/channel_terminal_input.py +277 -0
- package/ciel_runtime_support/channel_terminal_proxy.py +447 -0
- package/ciel_runtime_support/channel_tool_context.py +166 -0
- package/ciel_runtime_support/channel_transcript.py +414 -0
- package/ciel_runtime_support/channel_transcript_repository.py +96 -0
- package/ciel_runtime_support/channel_wake_claim_repository.py +126 -0
- package/ciel_runtime_support/channel_wake_delivery_repository.py +88 -0
- package/ciel_runtime_support/chat_files.py +138 -0
- package/ciel_runtime_support/chat_http_controller.py +235 -0
- package/ciel_runtime_support/claude_environment.py +375 -0
- package/ciel_runtime_support/claude_router.py +247 -193
- package/ciel_runtime_support/cli_dispatch.py +792 -0
- package/ciel_runtime_support/cli_parser.py +165 -0
- package/ciel_runtime_support/cli_usage.py +100 -0
- package/ciel_runtime_support/codex_app_server.py +20 -5
- package/ciel_runtime_support/codex_channel_sse_launch.py +87 -0
- package/ciel_runtime_support/codex_cli.py +42 -6
- package/ciel_runtime_support/codex_config.py +323 -0
- package/ciel_runtime_support/codex_launch_configuration.py +240 -0
- package/ciel_runtime_support/codex_launch_policy.py +66 -0
- package/ciel_runtime_support/codex_mcp_integration.py +195 -0
- package/ciel_runtime_support/codex_mcp_restore.py +304 -0
- package/ciel_runtime_support/codex_model_catalog.py +133 -0
- package/ciel_runtime_support/codex_process_lifecycle.py +271 -0
- package/ciel_runtime_support/codex_router.py +147 -1
- package/ciel_runtime_support/codex_session_repository.py +115 -0
- package/ciel_runtime_support/codex_session_selection.py +114 -0
- package/ciel_runtime_support/command_asset_installer.py +103 -0
- package/ciel_runtime_support/compatibility_probe.py +295 -0
- package/ciel_runtime_support/compatibility_protocol.py +251 -0
- package/ciel_runtime_support/compatibility_runtime.py +166 -0
- package/ciel_runtime_support/compatibility_test.py +370 -0
- package/ciel_runtime_support/config_migrations.py +307 -0
- package/ciel_runtime_support/config_repository.py +175 -0
- package/ciel_runtime_support/config_value_codec.py +64 -0
- package/ciel_runtime_support/configuration_cli.py +374 -0
- package/ciel_runtime_support/context_compaction.py +280 -0
- package/ciel_runtime_support/context_setup.py +208 -0
- package/ciel_runtime_support/context_summary_policy.py +392 -0
- package/ciel_runtime_support/credential_cli.py +104 -0
- package/ciel_runtime_support/credential_management.py +261 -0
- package/ciel_runtime_support/credentials.py +269 -0
- package/ciel_runtime_support/executable_discovery.py +141 -0
- package/ciel_runtime_support/github_copilot_oauth.py +335 -0
- package/ciel_runtime_support/github_copilot_oauth_runtime.py +213 -0
- package/ciel_runtime_support/header_forwarding.py +73 -0
- package/ciel_runtime_support/headless_config.py +221 -0
- package/ciel_runtime_support/http_response.py +129 -0
- package/ciel_runtime_support/install_diagnostics.py +149 -0
- package/ciel_runtime_support/kimi_identity.py +123 -0
- package/ciel_runtime_support/launch_diagnostics.py +204 -0
- package/ciel_runtime_support/launch_state.py +127 -0
- package/ciel_runtime_support/live_api_key_controller.py +58 -0
- package/ciel_runtime_support/llm_config_http.py +148 -0
- package/ciel_runtime_support/llm_option_config.py +259 -0
- package/ciel_runtime_support/llm_presentation_data.py +447 -0
- package/ciel_runtime_support/llm_presets.py +773 -0
- package/ciel_runtime_support/lm_studio_runtime.py +401 -0
- package/ciel_runtime_support/managed_mcp_config.py +144 -0
- package/ciel_runtime_support/managed_mcp_discovery.py +151 -0
- package/ciel_runtime_support/managed_service_cleanup.py +89 -0
- package/ciel_runtime_support/mcp_config_reader.py +230 -0
- package/ciel_runtime_support/mcp_http_proxy.py +607 -0
- package/ciel_runtime_support/mcp_inventory.py +59 -0
- package/ciel_runtime_support/mcp_notification_wait_policy.py +159 -0
- package/ciel_runtime_support/mcp_probe_codec.py +136 -0
- package/ciel_runtime_support/mcp_probe_transport.py +328 -0
- package/ciel_runtime_support/mcp_proxy_codec.py +345 -0
- package/ciel_runtime_support/mcp_proxy_config.py +107 -0
- package/ciel_runtime_support/mcp_proxy_notifications.py +261 -0
- package/ciel_runtime_support/mcp_proxy_process.py +560 -0
- package/ciel_runtime_support/mcp_split_proxy_http.py +165 -0
- package/ciel_runtime_support/mcp_stdio_probe.py +240 -0
- package/ciel_runtime_support/mcp_transport.py +146 -0
- package/ciel_runtime_support/model_cache_lifecycle.py +78 -0
- package/ciel_runtime_support/model_catalog_projection.py +61 -0
- package/ciel_runtime_support/model_context_hints.py +109 -0
- package/ciel_runtime_support/model_panel.py +147 -0
- package/ciel_runtime_support/model_registry_repository.py +231 -0
- package/ciel_runtime_support/npm_runtime.py +191 -0
- package/ciel_runtime_support/ollama_catalog.py +462 -0
- package/ciel_runtime_support/ollama_catalog_cli.py +41 -0
- package/ciel_runtime_support/ollama_catalog_repository.py +75 -0
- package/ciel_runtime_support/ollama_context_sync.py +87 -0
- package/ciel_runtime_support/ollama_forwarding.py +449 -0
- package/ciel_runtime_support/openai_chat_passthrough.py +67 -0
- package/ciel_runtime_support/openai_chat_router.py +64 -0
- package/ciel_runtime_support/openai_forwarding.py +194 -0
- package/ciel_runtime_support/openai_responses_router.py +291 -0
- package/ciel_runtime_support/openai_responses_stream.py +135 -0
- package/ciel_runtime_support/output_budget.py +89 -0
- package/ciel_runtime_support/package_lifecycle.py +217 -0
- package/ciel_runtime_support/plan_artifact_controller.py +104 -0
- package/ciel_runtime_support/prelaunch.py +959 -0
- package/ciel_runtime_support/prelaunch_launch_preference.py +75 -0
- package/ciel_runtime_support/prelaunch_panel_projection.py +396 -0
- package/ciel_runtime_support/prelaunch_terminal.py +764 -0
- package/ciel_runtime_support/process_control.py +708 -0
- package/ciel_runtime_support/prompt_compaction.py +322 -0
- package/ciel_runtime_support/prompt_injection.py +176 -0
- package/ciel_runtime_support/protocols/__init__.py +24 -0
- package/ciel_runtime_support/protocols/anthropic_content.py +29 -0
- package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +336 -0
- package/ciel_runtime_support/protocols/chat_projection.py +315 -0
- package/ciel_runtime_support/protocols/conversation_policy.py +217 -0
- package/ciel_runtime_support/protocols/conversation_turn_policy.py +972 -0
- package/ciel_runtime_support/protocols/ollama_chat.py +111 -0
- package/ciel_runtime_support/protocols/ollama_response.py +231 -0
- package/ciel_runtime_support/protocols/openai_reasoning.py +75 -0
- package/ciel_runtime_support/protocols/openai_responses.py +271 -0
- package/ciel_runtime_support/protocols/pseudo_tool_history.py +248 -0
- package/ciel_runtime_support/protocols/tool_result_projection.py +112 -0
- package/ciel_runtime_support/provider_adapters.py +160 -0
- package/ciel_runtime_support/provider_catalog_sources.py +316 -0
- package/ciel_runtime_support/provider_choice.py +201 -0
- package/ciel_runtime_support/provider_compatibility.py +165 -0
- package/ciel_runtime_support/provider_config_mutations.py +361 -0
- package/ciel_runtime_support/provider_configuration_service.py +162 -0
- package/ciel_runtime_support/provider_context.py +308 -0
- package/ciel_runtime_support/provider_contract_projection.py +73 -0
- package/ciel_runtime_support/provider_descriptor.py +82 -0
- package/ciel_runtime_support/provider_endpoint_policy.py +130 -0
- package/ciel_runtime_support/provider_endpoint_probe.py +165 -0
- package/ciel_runtime_support/provider_launch_endpoint.py +109 -0
- package/ciel_runtime_support/provider_limits.py +457 -0
- package/ciel_runtime_support/provider_model_identity.py +139 -0
- package/ciel_runtime_support/provider_model_selection.py +431 -0
- package/ciel_runtime_support/provider_model_specs.py +142 -0
- package/ciel_runtime_support/provider_models.py +263 -0
- package/ciel_runtime_support/provider_network.py +176 -0
- package/ciel_runtime_support/provider_option_cli.py +238 -0
- package/ciel_runtime_support/provider_option_panel.py +275 -0
- package/ciel_runtime_support/provider_option_status.py +192 -0
- package/ciel_runtime_support/provider_policy.py +101 -0
- package/ciel_runtime_support/provider_query_policy.py +67 -0
- package/ciel_runtime_support/provider_readiness.py +112 -0
- package/ciel_runtime_support/provider_request_access.py +131 -0
- package/ciel_runtime_support/provider_request_builder.py +250 -0
- package/ciel_runtime_support/provider_responses_passthrough.py +81 -0
- package/ciel_runtime_support/provider_runtime_info.py +113 -0
- package/ciel_runtime_support/provider_runtime_modes.py +150 -0
- package/ciel_runtime_support/provider_sampling_policy.py +46 -0
- package/ciel_runtime_support/provider_status.py +145 -0
- package/ciel_runtime_support/provider_timeout_policy.py +184 -0
- package/ciel_runtime_support/provider_tool_policy.py +145 -0
- package/ciel_runtime_support/providers/__init__.py +65 -0
- package/ciel_runtime_support/providers/anthropic.py +160 -0
- package/ciel_runtime_support/providers/anthropic_catalog.py +119 -0
- package/ciel_runtime_support/providers/base.py +232 -0
- package/ciel_runtime_support/providers/catalog.py +326 -0
- package/ciel_runtime_support/providers/cloud.py +194 -0
- package/ciel_runtime_support/providers/constants.py +54 -0
- package/ciel_runtime_support/providers/deepseek.py +115 -0
- package/ciel_runtime_support/providers/fireworks.py +158 -0
- package/ciel_runtime_support/providers/github_copilot_oauth.py +199 -0
- package/ciel_runtime_support/providers/kimi.py +297 -0
- package/ciel_runtime_support/providers/lm_studio.py +76 -0
- package/ciel_runtime_support/providers/meta.py +257 -0
- package/ciel_runtime_support/providers/native.py +194 -0
- package/ciel_runtime_support/providers/nim.py +69 -0
- package/ciel_runtime_support/providers/nvidia.py +158 -0
- package/ciel_runtime_support/providers/nvidia_runtime.py +285 -0
- package/ciel_runtime_support/providers/ollama.py +181 -0
- package/ciel_runtime_support/providers/ollama_context.py +195 -0
- package/ciel_runtime_support/providers/ollama_runtime.py +213 -0
- package/ciel_runtime_support/providers/opencode.py +220 -0
- package/ciel_runtime_support/providers/opencode_go.py +37 -0
- package/ciel_runtime_support/providers/openrouter.py +57 -0
- package/ciel_runtime_support/providers/vllm.py +65 -0
- package/ciel_runtime_support/providers/zai.py +119 -0
- package/ciel_runtime_support/pseudo_tool_parser.py +115 -0
- package/ciel_runtime_support/rate_limit_policy.py +117 -0
- package/ciel_runtime_support/rate_limit_repository.py +154 -0
- package/ciel_runtime_support/registry.py +46 -0
- package/ciel_runtime_support/request_shortcuts.py +253 -0
- package/ciel_runtime_support/request_trace.py +323 -0
- package/ciel_runtime_support/response_collection.py +209 -0
- package/ciel_runtime_support/router_access.py +238 -0
- package/ciel_runtime_support/router_client_lifecycle.py +366 -0
- package/ciel_runtime_support/router_health_policy.py +101 -0
- package/ciel_runtime_support/router_http.py +513 -0
- package/ciel_runtime_support/router_process_lifecycle.py +401 -0
- package/ciel_runtime_support/router_rate_limit_service.py +285 -0
- package/ciel_runtime_support/router_server_runtime.py +103 -0
- package/ciel_runtime_support/router_shortcuts.py +201 -0
- package/ciel_runtime_support/routing_fallback.py +73 -0
- package/ciel_runtime_support/runtime_activity_repository.py +143 -0
- package/ciel_runtime_support/runtime_adapters.py +104 -0
- package/ciel_runtime_support/runtime_command_factory.py +73 -0
- package/ciel_runtime_support/runtime_compatibility.py +50 -0
- package/ciel_runtime_support/runtime_constants.py +178 -0
- package/ciel_runtime_support/runtime_launch.py +1602 -0
- package/ciel_runtime_support/runtime_llm_options.py +312 -0
- package/ciel_runtime_support/runtime_logging.py +161 -0
- package/ciel_runtime_support/runtime_paths.py +157 -0
- package/ciel_runtime_support/runtime_restart.py +84 -0
- package/ciel_runtime_support/runtime_upgrade.py +149 -0
- package/ciel_runtime_support/secure_json_repository.py +55 -0
- package/ciel_runtime_support/session_import.py +356 -0
- package/ciel_runtime_support/settings_repository.py +8 -0
- package/ciel_runtime_support/slash_command_assets.py +211 -0
- package/ciel_runtime_support/sse_stream.py +57 -0
- package/ciel_runtime_support/sse_trace.py +225 -0
- package/ciel_runtime_support/statusline_script.py +593 -0
- package/ciel_runtime_support/statusline_settings.py +53 -0
- package/ciel_runtime_support/stream_chunk_policy.py +18 -0
- package/ciel_runtime_support/streaming_anthropic.py +1955 -0
- package/ciel_runtime_support/synthetic_tool_policy.py +105 -0
- package/ciel_runtime_support/terminal_platform_io.py +127 -0
- package/ciel_runtime_support/timeout_profile.py +196 -0
- package/ciel_runtime_support/tool_dialects.py +85 -0
- package/ciel_runtime_support/tool_exposure_policy.py +63 -0
- package/ciel_runtime_support/tool_guard_hooks.py +218 -0
- package/ciel_runtime_support/tool_request_projection.py +96 -0
- package/ciel_runtime_support/tool_schema.py +483 -0
- package/ciel_runtime_support/tool_side_effect_dedupe.py +95 -0
- package/ciel_runtime_support/ui_text.py +266 -0
- package/ciel_runtime_support/upstream_error_policy.py +104 -0
- package/ciel_runtime_support/upstream_retry.py +419 -0
- package/ciel_runtime_support/upstream_stream_io.py +106 -0
- package/ciel_runtime_support/usage_events.py +96 -0
- package/ciel_runtime_support/visible_stream_filters.py +130 -0
- package/ciel_runtime_support/web_endpoints.py +447 -0
- package/ciel_runtime_support/web_ui.py +915 -0
- package/ciel_runtime_support/web_ui_controller.py +189 -0
- package/ciel_runtime_support/windows_console_input.py +137 -0
- package/ciel_runtime_support/windows_console_mode.py +112 -0
- package/docs/Architecture.md +54 -0
- package/docs/Configuration.md +17 -0
- package/docs/Module-Map.md +1093 -26
- package/docs/Providers.md +46 -1
- package/docs/adr/0001-runtime-bounded-contexts.md +295 -0
- package/npm-bin/run-ciel-runtime.js +22 -2
- package/package.json +9 -2
|
@@ -0,0 +1,263 @@
|
|
|
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 ModelCatalogStorage:
|
|
9
|
+
read_model_list_cache: Callable[..., Any]
|
|
10
|
+
write_model_list_cache: Callable[..., Any]
|
|
11
|
+
write_model_registry: Callable[..., Any]
|
|
12
|
+
router_log: Callable[..., Any]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class ModelCatalogHttp:
|
|
17
|
+
http_json: Callable[..., Any]
|
|
18
|
+
join_url: Callable[..., Any]
|
|
19
|
+
with_upstream_user_agent: Callable[..., Any]
|
|
20
|
+
lm_studio_api_base: Callable[..., Any]
|
|
21
|
+
nvidia_hosted_list_headers: Callable[..., Any]
|
|
22
|
+
nvidia_upstream_base_url: Callable[..., Any]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class ProviderCatalogSources:
|
|
27
|
+
ANTHROPIC_MODEL_DOCS_URLS: Any
|
|
28
|
+
fetch_anthropic_api_model_ids: Callable[..., Any]
|
|
29
|
+
fetch_anthropic_public_model_ids: Callable[..., Any]
|
|
30
|
+
fetch_fireworks_model_ids: Callable[..., Any]
|
|
31
|
+
fireworks_account_id: Callable[..., Any]
|
|
32
|
+
fireworks_management_base_url: Callable[..., Any]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class ModelCatalogResponseCodec:
|
|
37
|
+
model_ids_from_response: Callable[..., Any]
|
|
38
|
+
model_info_from_response: Callable[..., Any]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class ModelCatalogPolicy:
|
|
43
|
+
normalize_model_id: Callable[..., Any]
|
|
44
|
+
ollama_catalog_model_ids: Callable[..., Any]
|
|
45
|
+
provider_has_api_key: Callable[..., Any]
|
|
46
|
+
provider_model_catalog_policy: Callable[..., Any]
|
|
47
|
+
provider_model_paths: Callable[..., Any]
|
|
48
|
+
provider_model_list_headers: Callable[..., Any]
|
|
49
|
+
provider_upstream_request_base: Callable[..., Any]
|
|
50
|
+
sorted_model_ids: Callable[..., Any]
|
|
51
|
+
unique_model_ids: Callable[..., Any]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True, slots=True)
|
|
55
|
+
class ProviderModelServices:
|
|
56
|
+
storage: ModelCatalogStorage
|
|
57
|
+
http: ModelCatalogHttp
|
|
58
|
+
sources: ProviderCatalogSources
|
|
59
|
+
response_codec: ModelCatalogResponseCodec
|
|
60
|
+
policy: ModelCatalogPolicy
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def fetch_upstream_model_ids(provider: str, pcfg: dict[str, Any], force_refresh: bool = False,
|
|
64
|
+
*,
|
|
65
|
+
services: ProviderModelServices,
|
|
66
|
+
) -> list[str]:
|
|
67
|
+
ANTHROPIC_MODEL_DOCS_URLS = services.sources.ANTHROPIC_MODEL_DOCS_URLS
|
|
68
|
+
fetch_anthropic_api_model_ids = services.sources.fetch_anthropic_api_model_ids
|
|
69
|
+
fetch_anthropic_public_model_ids = services.sources.fetch_anthropic_public_model_ids
|
|
70
|
+
fetch_fireworks_model_ids = services.sources.fetch_fireworks_model_ids
|
|
71
|
+
fireworks_account_id = services.sources.fireworks_account_id
|
|
72
|
+
fireworks_management_base_url = services.sources.fireworks_management_base_url
|
|
73
|
+
http_json = services.http.http_json
|
|
74
|
+
join_url = services.http.join_url
|
|
75
|
+
lm_studio_api_base = services.http.lm_studio_api_base
|
|
76
|
+
model_ids_from_response = services.response_codec.model_ids_from_response
|
|
77
|
+
model_info_from_response = services.response_codec.model_info_from_response
|
|
78
|
+
normalize_model_id = services.policy.normalize_model_id
|
|
79
|
+
nvidia_hosted_list_headers = services.http.nvidia_hosted_list_headers
|
|
80
|
+
nvidia_upstream_base_url = services.http.nvidia_upstream_base_url
|
|
81
|
+
ollama_catalog_model_ids = services.policy.ollama_catalog_model_ids
|
|
82
|
+
provider_has_api_key = services.policy.provider_has_api_key
|
|
83
|
+
provider_model_catalog_policy = services.policy.provider_model_catalog_policy
|
|
84
|
+
provider_model_paths = services.policy.provider_model_paths
|
|
85
|
+
provider_model_list_headers = services.policy.provider_model_list_headers
|
|
86
|
+
provider_upstream_request_base = services.policy.provider_upstream_request_base
|
|
87
|
+
read_model_list_cache = services.storage.read_model_list_cache
|
|
88
|
+
router_log = services.storage.router_log
|
|
89
|
+
sorted_model_ids = services.policy.sorted_model_ids
|
|
90
|
+
unique_model_ids = services.policy.unique_model_ids
|
|
91
|
+
with_upstream_user_agent = services.http.with_upstream_user_agent
|
|
92
|
+
write_model_list_cache = services.storage.write_model_list_cache
|
|
93
|
+
write_model_registry = services.storage.write_model_registry
|
|
94
|
+
cached = None if force_refresh else read_model_list_cache(provider, pcfg)
|
|
95
|
+
if cached is not None:
|
|
96
|
+
return cached
|
|
97
|
+
catalog_policy = provider_model_catalog_policy(provider, pcfg)
|
|
98
|
+
if catalog_policy.kind == "configured":
|
|
99
|
+
ids = unique_model_ids(provider, [
|
|
100
|
+
*catalog_policy.fallback_models,
|
|
101
|
+
*(pcfg.get("custom_models", []) or []),
|
|
102
|
+
pcfg.get("current_model") or "",
|
|
103
|
+
])
|
|
104
|
+
sorted_ids = sorted_model_ids(ids)
|
|
105
|
+
if sorted_ids:
|
|
106
|
+
write_model_list_cache(provider, pcfg, sorted_ids)
|
|
107
|
+
return sorted_ids
|
|
108
|
+
if catalog_policy.kind == "anthropic":
|
|
109
|
+
ids: list[str] = []
|
|
110
|
+
source = ""
|
|
111
|
+
if provider_has_api_key(provider, pcfg):
|
|
112
|
+
ids, source = fetch_anthropic_api_model_ids(pcfg, provider_model_list_headers(provider, pcfg), timeout=6.0)
|
|
113
|
+
if not ids:
|
|
114
|
+
ids = fetch_anthropic_public_model_ids()
|
|
115
|
+
source = "anthropic-docs"
|
|
116
|
+
if not ids:
|
|
117
|
+
return []
|
|
118
|
+
for mid in pcfg.get("custom_models", []) or []:
|
|
119
|
+
mid = normalize_model_id(provider, mid)
|
|
120
|
+
if mid and mid not in ids:
|
|
121
|
+
ids.append(mid)
|
|
122
|
+
cur = normalize_model_id(provider, pcfg.get("current_model") or "")
|
|
123
|
+
if cur and cur not in ids:
|
|
124
|
+
ids.insert(0, cur)
|
|
125
|
+
sorted_ids = unique_model_ids(provider, ids)
|
|
126
|
+
write_model_list_cache(provider, pcfg, sorted_ids)
|
|
127
|
+
write_model_registry(provider, pcfg, sorted_ids, source, {"urls": list(ANTHROPIC_MODEL_DOCS_URLS) if source == "anthropic-docs" else []})
|
|
128
|
+
return sorted_ids
|
|
129
|
+
if catalog_policy.kind == "fireworks":
|
|
130
|
+
headers = provider_model_list_headers(provider, pcfg)
|
|
131
|
+
model_info: dict[str, dict[str, Any]] = {}
|
|
132
|
+
source = ""
|
|
133
|
+
try:
|
|
134
|
+
ids, model_info, source = fetch_fireworks_model_ids(pcfg, headers, timeout=8.0)
|
|
135
|
+
fetched = bool(ids)
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
router_log("DEBUG", f"fireworks model API fetch failed: {type(exc).__name__}: {exc}")
|
|
138
|
+
ids = []
|
|
139
|
+
fetched = False
|
|
140
|
+
if not fetched:
|
|
141
|
+
base = provider_upstream_request_base(provider, pcfg)
|
|
142
|
+
try:
|
|
143
|
+
data = http_json(join_url(base, "/v1/models"), headers=headers, timeout=6.0, provider=provider, pcfg=pcfg)
|
|
144
|
+
ids = [normalize_model_id(provider, mid) for mid in model_ids_from_response(data)]
|
|
145
|
+
model_info.update(model_info_from_response(provider, data))
|
|
146
|
+
fetched = bool(ids)
|
|
147
|
+
source = "fireworks:/v1/models"
|
|
148
|
+
except Exception as exc:
|
|
149
|
+
router_log("DEBUG", f"fireworks inference model list fetch failed: {type(exc).__name__}: {exc}")
|
|
150
|
+
if not fetched:
|
|
151
|
+
ids = unique_model_ids(provider, [
|
|
152
|
+
*(pcfg.get("custom_models", []) or []),
|
|
153
|
+
pcfg.get("current_model") or "",
|
|
154
|
+
])
|
|
155
|
+
sorted_ids = sorted_model_ids(ids)
|
|
156
|
+
if sorted_ids:
|
|
157
|
+
write_model_list_cache(provider, pcfg, sorted_ids)
|
|
158
|
+
return sorted_ids
|
|
159
|
+
for mid in pcfg.get("custom_models", []) or []:
|
|
160
|
+
mid = normalize_model_id(provider, mid)
|
|
161
|
+
if mid and mid not in ids:
|
|
162
|
+
ids.append(mid)
|
|
163
|
+
cur = normalize_model_id(provider, pcfg.get("current_model") or "")
|
|
164
|
+
if cur and cur not in ids:
|
|
165
|
+
ids.insert(0, cur)
|
|
166
|
+
sorted_ids = sorted_model_ids(unique_model_ids(provider, ids))
|
|
167
|
+
metadata = {
|
|
168
|
+
"model_info": model_info,
|
|
169
|
+
"source": source,
|
|
170
|
+
"account_id": fireworks_account_id(pcfg),
|
|
171
|
+
"model_api_base_url": fireworks_management_base_url(pcfg),
|
|
172
|
+
}
|
|
173
|
+
write_model_list_cache(provider, pcfg, sorted_ids, metadata)
|
|
174
|
+
return sorted_ids
|
|
175
|
+
if catalog_policy.kind == "nvidia":
|
|
176
|
+
base = (pcfg.get("base_url") or nvidia_upstream_base_url()).rstrip("/")
|
|
177
|
+
else:
|
|
178
|
+
base = provider_upstream_request_base(provider, pcfg)
|
|
179
|
+
ids: list[str] = []
|
|
180
|
+
model_info: dict[str, dict[str, Any]] = {}
|
|
181
|
+
fetched = False
|
|
182
|
+
try:
|
|
183
|
+
if catalog_policy.kind == "nvidia":
|
|
184
|
+
data = http_json(join_url(base, "/v1/models"), headers=nvidia_hosted_list_headers(), timeout=8.0, provider=provider, pcfg=pcfg)
|
|
185
|
+
ids = model_ids_from_response(data)
|
|
186
|
+
model_info.update(model_info_from_response(provider, data))
|
|
187
|
+
fetched = True
|
|
188
|
+
else:
|
|
189
|
+
headers = provider_model_list_headers(provider, pcfg)
|
|
190
|
+
for path in provider_model_paths(provider, pcfg):
|
|
191
|
+
try:
|
|
192
|
+
request_base = lm_studio_api_base(pcfg) if catalog_policy.kind == "lm_studio" and path.startswith("/api/") else base
|
|
193
|
+
timeout = 2.0 if catalog_policy.kind == "lm_studio" else (4.0 if catalog_policy.kind == "ollama" else 6.0)
|
|
194
|
+
data = http_json(join_url(request_base, path), headers=headers, timeout=timeout, provider=provider, pcfg=pcfg)
|
|
195
|
+
ids = [normalize_model_id(provider, mid) for mid in model_ids_from_response(data)]
|
|
196
|
+
model_info.update(model_info_from_response(provider, data))
|
|
197
|
+
fetched = True
|
|
198
|
+
if ids:
|
|
199
|
+
break
|
|
200
|
+
except Exception:
|
|
201
|
+
continue
|
|
202
|
+
if not fetched and catalog_policy.allow_public_without_auth:
|
|
203
|
+
# OpenCode publishes the model catalog at /v1/models. Keep the
|
|
204
|
+
# picker independent from key-specific auth/rate-limit failures.
|
|
205
|
+
try:
|
|
206
|
+
public_headers = with_upstream_user_agent({"content-type": "application/json"})
|
|
207
|
+
data = http_json(join_url(base, "/v1/models"), headers=public_headers, timeout=6.0, provider=provider, pcfg=pcfg)
|
|
208
|
+
ids = model_ids_from_response(data)
|
|
209
|
+
model_info.update(model_info_from_response(provider, data))
|
|
210
|
+
fetched = True
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
router_log("DEBUG", f"{provider} public model catalog fetch failed: {type(exc).__name__}: {exc}")
|
|
213
|
+
except Exception:
|
|
214
|
+
ids = []
|
|
215
|
+
if catalog_policy.use_bundled_catalog_fallback and not ids:
|
|
216
|
+
ids = ollama_catalog_model_ids(provider)
|
|
217
|
+
fetched = bool(ids)
|
|
218
|
+
if not fetched and catalog_policy.allow_configured_fallback:
|
|
219
|
+
ids = unique_model_ids(provider, [
|
|
220
|
+
*(pcfg.get("custom_models", []) or []),
|
|
221
|
+
pcfg.get("current_model") or "",
|
|
222
|
+
])
|
|
223
|
+
sorted_ids = sorted_model_ids(ids)
|
|
224
|
+
if sorted_ids:
|
|
225
|
+
write_model_list_cache(provider, pcfg, sorted_ids)
|
|
226
|
+
return sorted_ids
|
|
227
|
+
if not fetched and catalog_policy.fallback_models:
|
|
228
|
+
ids = list(catalog_policy.fallback_models)
|
|
229
|
+
fetched = True
|
|
230
|
+
if not fetched:
|
|
231
|
+
return []
|
|
232
|
+
for mid in catalog_policy.fallback_models:
|
|
233
|
+
mid = normalize_model_id(provider, mid)
|
|
234
|
+
if mid and mid not in ids:
|
|
235
|
+
ids.append(mid)
|
|
236
|
+
for mid in pcfg.get("custom_models", []) or []:
|
|
237
|
+
mid = normalize_model_id(provider, mid)
|
|
238
|
+
if mid and mid not in ids:
|
|
239
|
+
ids.append(mid)
|
|
240
|
+
cur = normalize_model_id(provider, pcfg.get("current_model") or "")
|
|
241
|
+
if cur and catalog_policy.kind != "nvidia" and cur.startswith(f"ciel-runtime-{provider}-"):
|
|
242
|
+
pass
|
|
243
|
+
elif cur and cur not in ids and not (catalog_policy.kind == "nvidia" and cur.startswith("claude-")):
|
|
244
|
+
ids.insert(0, cur)
|
|
245
|
+
if catalog_policy.kind == "nvidia" and cur and cur not in ids:
|
|
246
|
+
ids.insert(0, cur)
|
|
247
|
+
sorted_ids = unique_model_ids(provider, ids)
|
|
248
|
+
if catalog_policy.kind != "anthropic":
|
|
249
|
+
sorted_ids = sorted_model_ids(sorted_ids)
|
|
250
|
+
metadata = {"model_info": model_info} if model_info else None
|
|
251
|
+
write_model_list_cache(provider, pcfg, sorted_ids, metadata)
|
|
252
|
+
return sorted_ids
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
__all__ = [
|
|
256
|
+
"ModelCatalogHttp",
|
|
257
|
+
"ModelCatalogPolicy",
|
|
258
|
+
"ModelCatalogResponseCodec",
|
|
259
|
+
"ModelCatalogStorage",
|
|
260
|
+
"ProviderCatalogSources",
|
|
261
|
+
"ProviderModelServices",
|
|
262
|
+
"fetch_upstream_model_ids",
|
|
263
|
+
]
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Provider HTTP identity, IP-family selection, and connectivity policy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import os
|
|
7
|
+
import socket
|
|
8
|
+
import threading
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.request
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from typing import Any, Callable
|
|
13
|
+
|
|
14
|
+
from ciel_runtime_support.providers.constants import OPENCODE_PROVIDER_NAMES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
DEFAULT_UPSTREAM_USER_AGENT = "claude-cli"
|
|
18
|
+
IP_FAMILY_ALIASES = {
|
|
19
|
+
"": "auto",
|
|
20
|
+
"auto": "auto",
|
|
21
|
+
"default": "auto",
|
|
22
|
+
"system": "auto",
|
|
23
|
+
"any": "auto",
|
|
24
|
+
"4": "ipv4",
|
|
25
|
+
"v4": "ipv4",
|
|
26
|
+
"ipv4": "ipv4",
|
|
27
|
+
"inet": "ipv4",
|
|
28
|
+
"6": "ipv6",
|
|
29
|
+
"v6": "ipv6",
|
|
30
|
+
"ipv6": "ipv6",
|
|
31
|
+
"inet6": "ipv6",
|
|
32
|
+
"prefer4": "ipv4-preferred",
|
|
33
|
+
"prefer-v4": "ipv4-preferred",
|
|
34
|
+
"preferred4": "ipv4-preferred",
|
|
35
|
+
"ipv4-preferred": "ipv4-preferred",
|
|
36
|
+
"ipv4_preferred": "ipv4-preferred",
|
|
37
|
+
"prefer-ipv4": "ipv4-preferred",
|
|
38
|
+
"prefer6": "ipv6-preferred",
|
|
39
|
+
"prefer-v6": "ipv6-preferred",
|
|
40
|
+
"preferred6": "ipv6-preferred",
|
|
41
|
+
"ipv6-preferred": "ipv6-preferred",
|
|
42
|
+
"ipv6_preferred": "ipv6-preferred",
|
|
43
|
+
"prefer-ipv6": "ipv6-preferred",
|
|
44
|
+
}
|
|
45
|
+
IP_FAMILY_CHOICES = ("auto", "ipv4", "ipv6", "ipv4-preferred", "ipv6-preferred")
|
|
46
|
+
|
|
47
|
+
_IP_FAMILY_LOCK = threading.Lock()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def upstream_user_agent(environ: dict[str, str] | None = None) -> str:
|
|
51
|
+
values = os.environ if environ is None else environ
|
|
52
|
+
configured = str(values.get("CIEL_RUNTIME_UPSTREAM_USER_AGENT") or "").strip()
|
|
53
|
+
return configured or DEFAULT_UPSTREAM_USER_AGENT
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def with_upstream_user_agent(headers: dict[str, str] | None = None) -> dict[str, str]:
|
|
57
|
+
result = dict(headers or {})
|
|
58
|
+
if not any(str(key).lower() == "user-agent" for key in result):
|
|
59
|
+
result["user-agent"] = upstream_user_agent()
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def normalize_ip_family(value: Any, default: str = "auto") -> str:
|
|
64
|
+
text = str(value if value is not None else default).strip().lower().replace("_", "-")
|
|
65
|
+
if family := IP_FAMILY_ALIASES.get(text):
|
|
66
|
+
return family
|
|
67
|
+
raise SystemExit(f"ip_family must be one of: {', '.join(IP_FAMILY_CHOICES)}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def default_provider_ip_family(provider: str) -> str:
|
|
71
|
+
return "ipv6-preferred" if provider in OPENCODE_PROVIDER_NAMES else "auto"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def provider_ip_family(provider: str | None, config: dict[str, Any] | None) -> str:
|
|
75
|
+
if not provider or not isinstance(config, dict):
|
|
76
|
+
return "auto"
|
|
77
|
+
return normalize_ip_family(config.get("ip_family"), default_provider_ip_family(provider))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@contextlib.contextmanager
|
|
81
|
+
def socket_ip_family_policy(ip_family: str) -> Iterable[None]:
|
|
82
|
+
policy = normalize_ip_family(ip_family)
|
|
83
|
+
if policy == "auto":
|
|
84
|
+
yield
|
|
85
|
+
return
|
|
86
|
+
strict = policy in ("ipv4", "ipv6")
|
|
87
|
+
desired = socket.AF_INET6 if policy in ("ipv6", "ipv6-preferred") else socket.AF_INET
|
|
88
|
+
with _IP_FAMILY_LOCK:
|
|
89
|
+
original_getaddrinfo = socket.getaddrinfo
|
|
90
|
+
|
|
91
|
+
def filtered_getaddrinfo(
|
|
92
|
+
host: Any,
|
|
93
|
+
port: Any,
|
|
94
|
+
family: int = 0,
|
|
95
|
+
type: int = 0,
|
|
96
|
+
proto: int = 0,
|
|
97
|
+
flags: int = 0,
|
|
98
|
+
) -> Any:
|
|
99
|
+
infos = original_getaddrinfo(host, port, family, type, proto, flags)
|
|
100
|
+
matches = [info for info in infos if info and info[0] == desired]
|
|
101
|
+
if strict:
|
|
102
|
+
if not matches:
|
|
103
|
+
raise socket.gaierror(socket.EAI_NONAME, f"no {policy} address for {host}")
|
|
104
|
+
return matches
|
|
105
|
+
others = [info for info in infos if not info or info[0] != desired]
|
|
106
|
+
return sorted(matches, key=lambda info: (0 if info[0] == desired else 1, info[0])) + others
|
|
107
|
+
|
|
108
|
+
socket.getaddrinfo = filtered_getaddrinfo
|
|
109
|
+
try:
|
|
110
|
+
yield
|
|
111
|
+
finally:
|
|
112
|
+
socket.getaddrinfo = original_getaddrinfo
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def provider_urlopen(
|
|
116
|
+
request: urllib.request.Request,
|
|
117
|
+
timeout: float,
|
|
118
|
+
provider: str | None,
|
|
119
|
+
config: dict[str, Any] | None,
|
|
120
|
+
log: Callable[[str, str], None],
|
|
121
|
+
) -> Any:
|
|
122
|
+
policy = provider_ip_family(provider, config)
|
|
123
|
+
if policy != "auto":
|
|
124
|
+
log("DEBUG", f"upstream_ip_family provider={provider or ''} policy={policy}")
|
|
125
|
+
with socket_ip_family_policy(policy):
|
|
126
|
+
return urllib.request.urlopen(request, timeout=timeout)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def ip_family_connectivity(
|
|
130
|
+
host: str,
|
|
131
|
+
port: int,
|
|
132
|
+
family: int,
|
|
133
|
+
timeout: float = 1.5,
|
|
134
|
+
) -> tuple[bool, str]:
|
|
135
|
+
try:
|
|
136
|
+
infos = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP)
|
|
137
|
+
except Exception as error:
|
|
138
|
+
return False, f"dns:{type(error).__name__}"
|
|
139
|
+
if not infos:
|
|
140
|
+
return False, "dns:no-address"
|
|
141
|
+
last_error = ""
|
|
142
|
+
for address_family, socktype, protocol, _canonical_name, address in infos:
|
|
143
|
+
connection = socket.socket(address_family, socktype, protocol)
|
|
144
|
+
connection.settimeout(timeout)
|
|
145
|
+
try:
|
|
146
|
+
connection.connect(address)
|
|
147
|
+
return True, "connect:ok"
|
|
148
|
+
except Exception as error:
|
|
149
|
+
last_error = f"connect:{type(error).__name__}"
|
|
150
|
+
finally:
|
|
151
|
+
try:
|
|
152
|
+
connection.close()
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
return False, last_error or "connect:failed"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def provider_ip_family_probe_lines(
|
|
159
|
+
provider: str,
|
|
160
|
+
config: dict[str, Any],
|
|
161
|
+
default_base_url: Callable[[str], str | None],
|
|
162
|
+
) -> list[str]:
|
|
163
|
+
if provider not in OPENCODE_PROVIDER_NAMES:
|
|
164
|
+
return []
|
|
165
|
+
base = str(config.get("base_url") or default_base_url(provider) or "").strip()
|
|
166
|
+
parsed = urllib.parse.urlparse(base)
|
|
167
|
+
if not parsed.hostname:
|
|
168
|
+
return [f"IP family: {provider_ip_family(provider, config)}; probe unavailable (base URL host missing)"]
|
|
169
|
+
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
|
170
|
+
ipv4_ok, ipv4_reason = ip_family_connectivity(parsed.hostname, port, socket.AF_INET)
|
|
171
|
+
ipv6_ok, ipv6_reason = ip_family_connectivity(parsed.hostname, port, socket.AF_INET6)
|
|
172
|
+
return [
|
|
173
|
+
f"IP family: {provider_ip_family(provider, config)}",
|
|
174
|
+
f"IPv4 connectivity: {'OK' if ipv4_ok else 'FAIL'} ({ipv4_reason})",
|
|
175
|
+
f"IPv6 connectivity: {'OK' if ipv6_ok else 'FAIL'} ({ipv6_reason})",
|
|
176
|
+
]
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""CLI controller for provider and Ollama option configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Collection, Mapping, Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class ProviderOptionCliConfig:
|
|
12
|
+
load: Callable[[], dict[str, Any]]
|
|
13
|
+
save: Callable[[dict[str, Any]], None]
|
|
14
|
+
normalize_provider: Callable[[str], str]
|
|
15
|
+
clear_model_cache: Callable[[], None]
|
|
16
|
+
output: Callable[[str], None]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class OllamaOptionCommands:
|
|
21
|
+
apply: Callable[[dict[str, Any], str], None]
|
|
22
|
+
apply_timeout: Callable[[str, dict[str, Any]], list[str]]
|
|
23
|
+
context_status: Callable[[dict[str, Any]], str]
|
|
24
|
+
rate_usage: Callable[[str, dict[str, Any]], tuple[int, int | None]]
|
|
25
|
+
options_status: Callable[[dict[str, Any]], str]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class ProviderOptionCommands:
|
|
30
|
+
apply: Callable[[str, dict[str, Any], str], None]
|
|
31
|
+
cap_context: Callable[[str, dict[str, Any]], list[str]]
|
|
32
|
+
cap_output: Callable[[str, dict[str, Any]], list[str]]
|
|
33
|
+
apply_timeout: Callable[[str, dict[str, Any]], list[str]]
|
|
34
|
+
status: Callable[[str, dict[str, Any]], str]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_CONTEXT_KEYS = frozenset(("context_window", "context", "max_model_len"))
|
|
38
|
+
_OLLAMA_CONTEXT_KEYS = frozenset(
|
|
39
|
+
(
|
|
40
|
+
"num_ctx",
|
|
41
|
+
"ctx",
|
|
42
|
+
"num_ctx_min",
|
|
43
|
+
"ctx_min",
|
|
44
|
+
"min",
|
|
45
|
+
"num_ctx_max",
|
|
46
|
+
"ctx_max",
|
|
47
|
+
"max",
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
_TIMEOUT_KEYS = frozenset(
|
|
51
|
+
(
|
|
52
|
+
"timeout",
|
|
53
|
+
"timeout_ms",
|
|
54
|
+
"request_timeout",
|
|
55
|
+
"request_timeout_ms",
|
|
56
|
+
"stream_idle_timeout",
|
|
57
|
+
"stream_idle_timeout_ms",
|
|
58
|
+
"idle_timeout",
|
|
59
|
+
"idle_timeout_ms",
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
_COMMON_PROVIDER_NOTES = (
|
|
63
|
+
" max_output_tokens is passed to Claude Code as CLAUDE_CODE_MAX_OUTPUT_TOKENS.",
|
|
64
|
+
" context_window is a ciel-runtime/router cap; native mode still cannot raise the real server limit.",
|
|
65
|
+
" temperature/top_p/top_k are injected by ciel-runtime router mode when the provider supports them.",
|
|
66
|
+
)
|
|
67
|
+
_PROVIDER_EXAMPLES = (
|
|
68
|
+
" ciel-runtimectl provider-options deepseek max_output_tokens=8192 context_window=1048576",
|
|
69
|
+
" ciel-runtimectl provider-options opencode-go endpoint:custom-model=chat",
|
|
70
|
+
" ciel-runtimectl provider-options opencode ip_family=ipv6-preferred",
|
|
71
|
+
" ciel-runtimectl provider-options fireworks account_id=fireworks model_api_base_url=https://api.fireworks.ai",
|
|
72
|
+
" ciel-runtimectl provider-options nvidia-hosted max_output_tokens=4096 temperature=0.7 top_p=0.8 timeout=300000 rate_limit_rpm=40",
|
|
73
|
+
" ciel-runtimectl provider-options vllm max_output_tokens=4096 context_window=65536 timeout=300000",
|
|
74
|
+
" ciel-runtimectl provider-options self-hosted-nim native=true max_output_tokens=4096",
|
|
75
|
+
)
|
|
76
|
+
_OLLAMA_EXAMPLES = (
|
|
77
|
+
" ciel-runtimectl ollama-options num_ctx=auto min=32768 max=131072",
|
|
78
|
+
" ciel-runtimectl ollama-options num_ctx=65536 temperature=0.7 top_p=0.8 max_tokens=32768 timeout=300000",
|
|
79
|
+
" ciel-runtime --ca-ollama-option temperature=0.7 --ca-ollama-num-ctx 65536",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ProviderOptionCliController:
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
config: ProviderOptionCliConfig,
|
|
87
|
+
ollama: OllamaOptionCommands,
|
|
88
|
+
provider: ProviderOptionCommands,
|
|
89
|
+
*,
|
|
90
|
+
supported_providers: Collection[str],
|
|
91
|
+
ollama_providers: Collection[str],
|
|
92
|
+
provider_notes: Mapping[str, Sequence[str]],
|
|
93
|
+
unsupported_message: str,
|
|
94
|
+
) -> None:
|
|
95
|
+
self.config = config
|
|
96
|
+
self.ollama = ollama
|
|
97
|
+
self.provider = provider
|
|
98
|
+
self.supported_providers = frozenset(supported_providers)
|
|
99
|
+
self.ollama_providers = frozenset(ollama_providers)
|
|
100
|
+
self.provider_notes = provider_notes
|
|
101
|
+
self.unsupported_message = unsupported_message
|
|
102
|
+
|
|
103
|
+
def native(self, args: Any) -> None:
|
|
104
|
+
config = self.config.load()
|
|
105
|
+
provider_config = config["providers"]["ollama"]
|
|
106
|
+
raw = getattr(args, "value", None)
|
|
107
|
+
if raw:
|
|
108
|
+
value = raw.lower()
|
|
109
|
+
if value in ("on", "enable", "enabled", "true", "1"):
|
|
110
|
+
provider_config["native_compat"] = True
|
|
111
|
+
elif value in ("off", "disable", "disabled", "false", "0"):
|
|
112
|
+
provider_config["native_compat"] = False
|
|
113
|
+
else:
|
|
114
|
+
raise SystemExit("Use: ciel-runtime ollama-native on|off|status")
|
|
115
|
+
self.config.save(config)
|
|
116
|
+
state = "on" if provider_config.get("native_compat", True) else "off"
|
|
117
|
+
self._lines(
|
|
118
|
+
(
|
|
119
|
+
f"ollama_native_compat: {state}",
|
|
120
|
+
f"base_url: {provider_config.get('base_url')}",
|
|
121
|
+
f"model: {provider_config.get('current_model')}",
|
|
122
|
+
'launch_env: ANTHROPIC_BASE_URL=<ollama>, ANTHROPIC_AUTH_TOKEN=ollama, ANTHROPIC_API_KEY=""',
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def ollama_options(self, args: Any) -> None:
|
|
127
|
+
config = self.config.load()
|
|
128
|
+
values = list(getattr(args, "values", []) or [])
|
|
129
|
+
provider = self._selected_provider(
|
|
130
|
+
values,
|
|
131
|
+
str(config.get("current_provider", "ollama")),
|
|
132
|
+
self.ollama_providers,
|
|
133
|
+
"ollama",
|
|
134
|
+
)
|
|
135
|
+
provider_config = config["providers"][provider]
|
|
136
|
+
if values:
|
|
137
|
+
context_changed = self._contains_key(values, _OLLAMA_CONTEXT_KEYS)
|
|
138
|
+
explicit_timeout = self._contains_key(values, _TIMEOUT_KEYS)
|
|
139
|
+
for token in values:
|
|
140
|
+
self.ollama.apply(provider_config, token)
|
|
141
|
+
timeout_lines = (
|
|
142
|
+
self.ollama.apply_timeout(provider, provider_config)
|
|
143
|
+
if context_changed and not explicit_timeout
|
|
144
|
+
else []
|
|
145
|
+
)
|
|
146
|
+
self._persist(config)
|
|
147
|
+
self.config.output(f"Ollama options updated for {provider}.")
|
|
148
|
+
self._lines(timeout_lines)
|
|
149
|
+
self._ollama_status(provider, provider_config)
|
|
150
|
+
|
|
151
|
+
def provider_options(self, args: Any) -> None:
|
|
152
|
+
config = self.config.load()
|
|
153
|
+
values = list(getattr(args, "values", []) or [])
|
|
154
|
+
provider = self._selected_provider(
|
|
155
|
+
values,
|
|
156
|
+
str(config.get("current_provider", "vllm")),
|
|
157
|
+
self.supported_providers,
|
|
158
|
+
)
|
|
159
|
+
if provider not in self.supported_providers:
|
|
160
|
+
raise SystemExit(self.unsupported_message)
|
|
161
|
+
provider_config = config["providers"][provider]
|
|
162
|
+
if values:
|
|
163
|
+
context_changed = self._contains_key(values, _CONTEXT_KEYS)
|
|
164
|
+
explicit_timeout = self._contains_key(values, _TIMEOUT_KEYS)
|
|
165
|
+
for token in values:
|
|
166
|
+
self.provider.apply(provider, provider_config, token)
|
|
167
|
+
cap_lines = self.provider.cap_context(provider, provider_config)
|
|
168
|
+
cap_lines.extend(self.provider.cap_output(provider, provider_config))
|
|
169
|
+
timeout_lines = (
|
|
170
|
+
self.provider.apply_timeout(provider, provider_config)
|
|
171
|
+
if context_changed and not explicit_timeout
|
|
172
|
+
else []
|
|
173
|
+
)
|
|
174
|
+
self._persist(config)
|
|
175
|
+
self.config.output(f"Provider options updated for {provider}.")
|
|
176
|
+
self._lines((*cap_lines, *timeout_lines))
|
|
177
|
+
self.config.output(f"provider: {provider}")
|
|
178
|
+
self.config.output(
|
|
179
|
+
f"provider_options: {self.provider.status(provider, provider_config)}"
|
|
180
|
+
)
|
|
181
|
+
self.config.output("Notes:")
|
|
182
|
+
self._lines(_COMMON_PROVIDER_NOTES)
|
|
183
|
+
self._lines(self.provider_notes.get(provider, ()))
|
|
184
|
+
self.config.output("Examples:")
|
|
185
|
+
self._lines(_PROVIDER_EXAMPLES)
|
|
186
|
+
|
|
187
|
+
def _ollama_status(self, provider: str, config: dict[str, Any]) -> None:
|
|
188
|
+
self._lines(
|
|
189
|
+
(
|
|
190
|
+
f"provider: {provider}",
|
|
191
|
+
f"num_ctx: {self.ollama.context_status(config)}",
|
|
192
|
+
f"keep_alive: {config.get('keep_alive', 'default')}",
|
|
193
|
+
f"think: {bool(config.get('think', False))}",
|
|
194
|
+
f"request_timeout_ms: {config.get('request_timeout_ms', 'default')}",
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
used, limit = self.ollama.rate_usage(provider, config)
|
|
198
|
+
if limit is not None:
|
|
199
|
+
self.config.output(f"rate_limit_rpm: {limit}")
|
|
200
|
+
if bool(config.get("rate_limit_status", False)):
|
|
201
|
+
suffix = f"{used}/{limit}" if limit > 0 else f"{used}/min (unmanaged)"
|
|
202
|
+
self.config.output(f"rpm_used: {suffix}")
|
|
203
|
+
self.config.output(f"ollama_options: {self.ollama.options_status(config)}")
|
|
204
|
+
self.config.output("Examples:")
|
|
205
|
+
self._lines(_OLLAMA_EXAMPLES)
|
|
206
|
+
|
|
207
|
+
def _selected_provider(
|
|
208
|
+
self,
|
|
209
|
+
values: list[str],
|
|
210
|
+
current: str,
|
|
211
|
+
allowed: Collection[str],
|
|
212
|
+
fallback: str | None = None,
|
|
213
|
+
) -> str:
|
|
214
|
+
provider = current if current in allowed else fallback or current
|
|
215
|
+
if values:
|
|
216
|
+
try:
|
|
217
|
+
candidate = self.config.normalize_provider(values[0])
|
|
218
|
+
if candidate in allowed:
|
|
219
|
+
provider = candidate
|
|
220
|
+
values.pop(0)
|
|
221
|
+
except SystemExit:
|
|
222
|
+
pass
|
|
223
|
+
return provider
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _contains_key(values: Sequence[str], keys: Collection[str]) -> bool:
|
|
227
|
+
return any(
|
|
228
|
+
token.split("=", 1)[0].replace("unset:", "").strip() in keys
|
|
229
|
+
for token in values
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _persist(self, config: dict[str, Any]) -> None:
|
|
233
|
+
self.config.save(config)
|
|
234
|
+
self.config.clear_model_cache()
|
|
235
|
+
|
|
236
|
+
def _lines(self, lines: Sequence[str]) -> None:
|
|
237
|
+
for line in lines:
|
|
238
|
+
self.config.output(line)
|