@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,316 @@
|
|
|
1
|
+
"""Provider model-catalog response projection and remote source adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import html
|
|
6
|
+
import re
|
|
7
|
+
import urllib.parse
|
|
8
|
+
import urllib.request
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ciel_runtime_support.model_catalog_projection import (
|
|
14
|
+
ModelCatalogProjectionServices,
|
|
15
|
+
project_model_info,
|
|
16
|
+
)
|
|
17
|
+
from ciel_runtime_support.runtime_constants import (
|
|
18
|
+
ANTHROPIC_LIMITED_ACCESS_MODEL_IDS,
|
|
19
|
+
ANTHROPIC_MODEL_DOCS_URLS,
|
|
20
|
+
ANTHROPIC_PUBLIC_MODEL_DEFAULT_IDS,
|
|
21
|
+
ANTHROPIC_PUBLIC_MODEL_FALLBACK_IDS,
|
|
22
|
+
FIREWORKS_API_BASE_URL,
|
|
23
|
+
FIREWORKS_DEFAULT_ACCOUNT_ID,
|
|
24
|
+
FIREWORKS_INFERENCE_BASE_URL,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class ModelCatalogProjectionPorts:
|
|
30
|
+
normalize_model_id: Callable[[str, Any], str]
|
|
31
|
+
model_context: Callable[[dict[str, Any]], Any]
|
|
32
|
+
positive_int: Callable[[Any], int | None]
|
|
33
|
+
provider_metadata: Callable[[str], Callable[..., dict[str, Any]]]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class ProviderCatalogHttpPorts:
|
|
38
|
+
http_json: Callable[..., Any]
|
|
39
|
+
join_url: Callable[[str, str], str]
|
|
40
|
+
upstream_base: Callable[[str, dict[str, Any]], str]
|
|
41
|
+
request_headers: Callable[[], dict[str, str]]
|
|
42
|
+
urlopen: Callable[..., Any]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True)
|
|
46
|
+
class ProviderCatalogPolicyPorts:
|
|
47
|
+
unique_model_ids: Callable[[str, list[str]], list[str]]
|
|
48
|
+
log: Callable[[str, str], None]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class AnthropicCatalogPolicy:
|
|
53
|
+
docs_urls: tuple[str, ...]
|
|
54
|
+
default_ids: tuple[str, ...]
|
|
55
|
+
limited_ids: tuple[str, ...]
|
|
56
|
+
fallback_ids: tuple[str, ...]
|
|
57
|
+
public_id_pattern: re.Pattern[str]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True)
|
|
61
|
+
class FireworksCatalogPolicy:
|
|
62
|
+
default_account_id: str
|
|
63
|
+
api_base_url: str
|
|
64
|
+
inference_base_url: str
|
|
65
|
+
max_pages: int = 20
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def build_default_provider_catalog_source_service(
|
|
69
|
+
projection: ModelCatalogProjectionPorts,
|
|
70
|
+
http: ProviderCatalogHttpPorts,
|
|
71
|
+
policy: ProviderCatalogPolicyPorts,
|
|
72
|
+
) -> ProviderCatalogSourceService:
|
|
73
|
+
"""Compose catalog sources with the runtime's immutable provider defaults."""
|
|
74
|
+
return ProviderCatalogSourceService(
|
|
75
|
+
projection=projection,
|
|
76
|
+
http=http,
|
|
77
|
+
policy=policy,
|
|
78
|
+
anthropic=AnthropicCatalogPolicy(
|
|
79
|
+
docs_urls=ANTHROPIC_MODEL_DOCS_URLS,
|
|
80
|
+
default_ids=ANTHROPIC_PUBLIC_MODEL_DEFAULT_IDS,
|
|
81
|
+
limited_ids=ANTHROPIC_LIMITED_ACCESS_MODEL_IDS,
|
|
82
|
+
fallback_ids=ANTHROPIC_PUBLIC_MODEL_FALLBACK_IDS,
|
|
83
|
+
public_id_pattern=ANTHROPIC_PUBLIC_MODEL_ID_RE,
|
|
84
|
+
),
|
|
85
|
+
fireworks=FireworksCatalogPolicy(
|
|
86
|
+
default_account_id=FIREWORKS_DEFAULT_ACCOUNT_ID,
|
|
87
|
+
api_base_url=FIREWORKS_API_BASE_URL,
|
|
88
|
+
inference_base_url=FIREWORKS_INFERENCE_BASE_URL,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True, slots=True)
|
|
94
|
+
class ProviderCatalogSourceService:
|
|
95
|
+
projection: ModelCatalogProjectionPorts
|
|
96
|
+
http: ProviderCatalogHttpPorts
|
|
97
|
+
policy: ProviderCatalogPolicyPorts
|
|
98
|
+
anthropic: AnthropicCatalogPolicy
|
|
99
|
+
fireworks: FireworksCatalogPolicy
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def model_ids_from_response(data: Any) -> list[str]:
|
|
103
|
+
ids: list[str] = []
|
|
104
|
+
candidates: Any
|
|
105
|
+
if isinstance(data, dict):
|
|
106
|
+
candidates = data.get("data")
|
|
107
|
+
if candidates is None:
|
|
108
|
+
candidates = data.get("models")
|
|
109
|
+
if candidates is None:
|
|
110
|
+
candidates = data.get("model")
|
|
111
|
+
else:
|
|
112
|
+
candidates = data
|
|
113
|
+
if isinstance(candidates, str):
|
|
114
|
+
candidates = [candidates]
|
|
115
|
+
if not isinstance(candidates, list):
|
|
116
|
+
return ids
|
|
117
|
+
for item in candidates:
|
|
118
|
+
if isinstance(item, str):
|
|
119
|
+
model_id = item
|
|
120
|
+
elif isinstance(item, dict):
|
|
121
|
+
model_id = (
|
|
122
|
+
item.get("id")
|
|
123
|
+
or item.get("key")
|
|
124
|
+
or item.get("name")
|
|
125
|
+
or item.get("model")
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
model_id = None
|
|
129
|
+
if model_id and str(model_id).strip():
|
|
130
|
+
ids.append(str(model_id).strip())
|
|
131
|
+
return ids
|
|
132
|
+
|
|
133
|
+
def model_info_from_response(
|
|
134
|
+
self, provider: str, data: Any
|
|
135
|
+
) -> dict[str, dict[str, Any]]:
|
|
136
|
+
return project_model_info(
|
|
137
|
+
provider,
|
|
138
|
+
data,
|
|
139
|
+
ModelCatalogProjectionServices(
|
|
140
|
+
normalize_model_id=self.projection.normalize_model_id,
|
|
141
|
+
model_context=self.projection.model_context,
|
|
142
|
+
positive_int=self.projection.positive_int,
|
|
143
|
+
project_metadata=self.projection.provider_metadata(provider),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def fireworks_account_id(self, provider_config: dict[str, Any]) -> str:
|
|
148
|
+
configured = str(provider_config.get("account_id") or "").strip()
|
|
149
|
+
if configured:
|
|
150
|
+
return configured
|
|
151
|
+
values = (
|
|
152
|
+
provider_config.get("current_model"),
|
|
153
|
+
*(provider_config.get("custom_models", []) or []),
|
|
154
|
+
)
|
|
155
|
+
for value in values:
|
|
156
|
+
match = re.match(
|
|
157
|
+
r"^accounts/([^/]+)/models/[^/]+$", str(value or "")
|
|
158
|
+
)
|
|
159
|
+
if match:
|
|
160
|
+
return match.group(1)
|
|
161
|
+
return self.fireworks.default_account_id
|
|
162
|
+
|
|
163
|
+
def fireworks_management_base_url(
|
|
164
|
+
self, provider_config: dict[str, Any]
|
|
165
|
+
) -> str:
|
|
166
|
+
configured = str(
|
|
167
|
+
provider_config.get("model_api_base_url") or ""
|
|
168
|
+
).strip().rstrip("/")
|
|
169
|
+
base = str(
|
|
170
|
+
provider_config.get("base_url") or self.fireworks.inference_base_url
|
|
171
|
+
).strip().rstrip("/")
|
|
172
|
+
parsed = urllib.parse.urlparse(base)
|
|
173
|
+
if configured and (
|
|
174
|
+
configured != self.fireworks.api_base_url
|
|
175
|
+
or not (parsed.scheme and parsed.netloc)
|
|
176
|
+
or parsed.netloc.endswith("fireworks.ai")
|
|
177
|
+
):
|
|
178
|
+
return configured
|
|
179
|
+
if parsed.scheme and parsed.netloc:
|
|
180
|
+
return f"{parsed.scheme}://{parsed.netloc}"
|
|
181
|
+
return configured or self.fireworks.api_base_url
|
|
182
|
+
|
|
183
|
+
def fetch_fireworks_model_ids(
|
|
184
|
+
self,
|
|
185
|
+
provider_config: dict[str, Any],
|
|
186
|
+
headers: dict[str, str],
|
|
187
|
+
timeout: float = 8.0,
|
|
188
|
+
) -> tuple[list[str], dict[str, dict[str, Any]], str]:
|
|
189
|
+
account_id = self.fireworks_account_id(provider_config)
|
|
190
|
+
base = self.fireworks_management_base_url(provider_config)
|
|
191
|
+
models: list[str] = []
|
|
192
|
+
model_info: dict[str, dict[str, Any]] = {}
|
|
193
|
+
page_token = ""
|
|
194
|
+
source = f"fireworks:{account_id}"
|
|
195
|
+
for _ in range(self.fireworks.max_pages):
|
|
196
|
+
query = {"pageSize": "200"}
|
|
197
|
+
if page_token:
|
|
198
|
+
query["pageToken"] = page_token
|
|
199
|
+
account = urllib.parse.quote(account_id, safe="")
|
|
200
|
+
encoded_query = urllib.parse.urlencode(query)
|
|
201
|
+
path = f"/v1/accounts/{account}/models?{encoded_query}"
|
|
202
|
+
data = self.http.http_json(
|
|
203
|
+
self.http.join_url(base, path),
|
|
204
|
+
headers=headers,
|
|
205
|
+
timeout=timeout,
|
|
206
|
+
provider="fireworks",
|
|
207
|
+
pcfg=provider_config,
|
|
208
|
+
)
|
|
209
|
+
ids = [
|
|
210
|
+
self.projection.normalize_model_id("fireworks", model_id)
|
|
211
|
+
for model_id in self.model_ids_from_response(data)
|
|
212
|
+
]
|
|
213
|
+
for model_id in ids:
|
|
214
|
+
if model_id and model_id not in models:
|
|
215
|
+
models.append(model_id)
|
|
216
|
+
model_info.update(self.model_info_from_response("fireworks", data))
|
|
217
|
+
if not isinstance(data, dict):
|
|
218
|
+
break
|
|
219
|
+
page_token = str(data.get("nextPageToken") or "").strip()
|
|
220
|
+
if not page_token:
|
|
221
|
+
break
|
|
222
|
+
return models, model_info, source
|
|
223
|
+
|
|
224
|
+
def fetch_text_url(self, url: str, timeout: float = 8.0) -> str:
|
|
225
|
+
request = urllib.request.Request(url, headers=self.http.request_headers())
|
|
226
|
+
with self.http.urlopen(request, timeout=timeout) as response:
|
|
227
|
+
return response.read(5_000_000).decode("utf-8", errors="replace")
|
|
228
|
+
|
|
229
|
+
def anthropic_model_ids_from_docs_text(self, text: str) -> list[str]:
|
|
230
|
+
ids: list[str] = []
|
|
231
|
+
seen: set[str] = set()
|
|
232
|
+
for match in self.anthropic.public_id_pattern.finditer(html.unescape(text or "")):
|
|
233
|
+
model_id = match.group(0)
|
|
234
|
+
key = model_id.casefold()
|
|
235
|
+
if key in seen:
|
|
236
|
+
continue
|
|
237
|
+
seen.add(key)
|
|
238
|
+
ids.append(model_id)
|
|
239
|
+
return ids
|
|
240
|
+
|
|
241
|
+
def filter_anthropic_default_model_ids(self, ids: list[str]) -> list[str]:
|
|
242
|
+
allowed = set(self.anthropic.default_ids)
|
|
243
|
+
limited = set(self.anthropic.limited_ids)
|
|
244
|
+
out: list[str] = []
|
|
245
|
+
seen: set[str] = set()
|
|
246
|
+
for raw in ids:
|
|
247
|
+
model_id = self.projection.normalize_model_id("anthropic", raw)
|
|
248
|
+
key = model_id.casefold()
|
|
249
|
+
if not model_id or key in seen or model_id in limited or model_id not in allowed:
|
|
250
|
+
continue
|
|
251
|
+
out.append(model_id)
|
|
252
|
+
seen.add(key)
|
|
253
|
+
return out
|
|
254
|
+
|
|
255
|
+
def fetch_anthropic_public_model_ids(self, timeout: float = 8.0) -> list[str]:
|
|
256
|
+
ids: list[str] = []
|
|
257
|
+
errors: list[str] = []
|
|
258
|
+
for url in self.anthropic.docs_urls:
|
|
259
|
+
try:
|
|
260
|
+
text = self.fetch_text_url(url, timeout=timeout)
|
|
261
|
+
ids.extend(self.anthropic_model_ids_from_docs_text(text))
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
errors.append(f"{url}: {type(exc).__name__}: {exc}")
|
|
264
|
+
unique = self.policy.unique_model_ids("anthropic", ids)
|
|
265
|
+
out = self.filter_anthropic_default_model_ids(unique)
|
|
266
|
+
if out:
|
|
267
|
+
return out
|
|
268
|
+
if errors:
|
|
269
|
+
self.policy.log(
|
|
270
|
+
"WARN", "anthropic model docs fetch failed: " + " ; ".join(errors)
|
|
271
|
+
)
|
|
272
|
+
return list(self.anthropic.fallback_ids)
|
|
273
|
+
|
|
274
|
+
def fetch_anthropic_api_model_ids(
|
|
275
|
+
self,
|
|
276
|
+
provider_config: dict[str, Any],
|
|
277
|
+
headers: dict[str, str],
|
|
278
|
+
timeout: float = 6.0,
|
|
279
|
+
) -> tuple[list[str], str]:
|
|
280
|
+
base = self.http.upstream_base("anthropic", provider_config)
|
|
281
|
+
errors: list[str] = []
|
|
282
|
+
for path in ("/v1/models", "/models"):
|
|
283
|
+
try:
|
|
284
|
+
data = self.http.http_json(
|
|
285
|
+
self.http.join_url(base, path),
|
|
286
|
+
headers=headers,
|
|
287
|
+
timeout=timeout,
|
|
288
|
+
)
|
|
289
|
+
ids = self.policy.unique_model_ids(
|
|
290
|
+
"anthropic", self.model_ids_from_response(data)
|
|
291
|
+
)
|
|
292
|
+
if ids:
|
|
293
|
+
return ids, f"api:{path}"
|
|
294
|
+
except Exception as exc:
|
|
295
|
+
errors.append(f"{path}: {type(exc).__name__}: {exc}")
|
|
296
|
+
if errors:
|
|
297
|
+
self.policy.log(
|
|
298
|
+
"DEBUG", "anthropic model API fetch failed: " + " ; ".join(errors)
|
|
299
|
+
)
|
|
300
|
+
return [], ""
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
ANTHROPIC_PUBLIC_MODEL_ID_RE = re.compile(
|
|
304
|
+
r"(?<![A-Za-z0-9_.@:-])"
|
|
305
|
+
r"(?:"
|
|
306
|
+
r"claude-(?:fable|mythos)-\d+(?:-\d+)?(?:-\d{8})?|"
|
|
307
|
+
r"claude-mythos-preview|"
|
|
308
|
+
r"claude-(?:opus|sonnet|haiku)-\d+-\d+-\d{8}|"
|
|
309
|
+
r"claude-(?:opus|sonnet|haiku)-\d+-\d{8}|"
|
|
310
|
+
r"claude-(?:opus|sonnet|haiku)-\d+-\d+|"
|
|
311
|
+
r"claude-(?:opus|sonnet|haiku)-\d+(?:-\d+)?-latest|"
|
|
312
|
+
r"claude-\d+(?:-\d+){0,2}-(?:opus|sonnet|haiku)-(?:\d{8}|latest)|"
|
|
313
|
+
r"claude-(?:opus|sonnet|haiku)-\d+"
|
|
314
|
+
r")"
|
|
315
|
+
r"(?![A-Za-z0-9_.@:-])"
|
|
316
|
+
)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Provider/runtime choice normalization and selection strategies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from .architecture import ProviderAdapter, ProviderConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ANTHROPIC_NATIVE_PROVIDER_CHOICE = "anthropic:native"
|
|
12
|
+
ANTHROPIC_ROUTED_PROVIDER_CHOICE = "anthropic:routed"
|
|
13
|
+
AGY_NATIVE_PROVIDER_CHOICE = "agy:native"
|
|
14
|
+
AGY_ROUTED_PROVIDER_CHOICE = "agy:routed"
|
|
15
|
+
CODEX_NATIVE_PROVIDER_CHOICE = "codex:native"
|
|
16
|
+
CODEX_ROUTED_PROVIDER_CHOICE = "codex:routed"
|
|
17
|
+
KIMI_NATIVE_PROVIDER_CHOICE = "kimi:native"
|
|
18
|
+
KIMI_ROUTED_PROVIDER_CHOICE = "kimi:routed"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
CHOICE_ALIASES = {
|
|
22
|
+
"anthropic-native": ANTHROPIC_NATIVE_PROVIDER_CHOICE,
|
|
23
|
+
"claude-native": ANTHROPIC_NATIVE_PROVIDER_CHOICE,
|
|
24
|
+
"native": ANTHROPIC_NATIVE_PROVIDER_CHOICE,
|
|
25
|
+
"claude-code": ANTHROPIC_NATIVE_PROVIDER_CHOICE,
|
|
26
|
+
"anthropic-routed": ANTHROPIC_ROUTED_PROVIDER_CHOICE,
|
|
27
|
+
"anthropic-router": ANTHROPIC_ROUTED_PROVIDER_CHOICE,
|
|
28
|
+
"claude-routed": ANTHROPIC_ROUTED_PROVIDER_CHOICE,
|
|
29
|
+
"claude-router": ANTHROPIC_ROUTED_PROVIDER_CHOICE,
|
|
30
|
+
"agy": AGY_NATIVE_PROVIDER_CHOICE,
|
|
31
|
+
"agy-native": AGY_NATIVE_PROVIDER_CHOICE,
|
|
32
|
+
"native-agy": AGY_NATIVE_PROVIDER_CHOICE,
|
|
33
|
+
"antigravity": AGY_NATIVE_PROVIDER_CHOICE,
|
|
34
|
+
"google-antigravity": AGY_NATIVE_PROVIDER_CHOICE,
|
|
35
|
+
"agy-routed": AGY_ROUTED_PROVIDER_CHOICE,
|
|
36
|
+
"agy-router": AGY_ROUTED_PROVIDER_CHOICE,
|
|
37
|
+
"routed-agy": AGY_ROUTED_PROVIDER_CHOICE,
|
|
38
|
+
"antigravity-routed": AGY_ROUTED_PROVIDER_CHOICE,
|
|
39
|
+
"codex": CODEX_NATIVE_PROVIDER_CHOICE,
|
|
40
|
+
"codex-native": CODEX_NATIVE_PROVIDER_CHOICE,
|
|
41
|
+
"native-codex": CODEX_NATIVE_PROVIDER_CHOICE,
|
|
42
|
+
"codex-routed": CODEX_ROUTED_PROVIDER_CHOICE,
|
|
43
|
+
"codex-router": CODEX_ROUTED_PROVIDER_CHOICE,
|
|
44
|
+
"routed-codex": CODEX_ROUTED_PROVIDER_CHOICE,
|
|
45
|
+
"kimi-native": KIMI_NATIVE_PROVIDER_CHOICE,
|
|
46
|
+
"native-kimi": KIMI_NATIVE_PROVIDER_CHOICE,
|
|
47
|
+
"kimi-code": KIMI_NATIVE_PROVIDER_CHOICE,
|
|
48
|
+
"kimi-routed": KIMI_ROUTED_PROVIDER_CHOICE,
|
|
49
|
+
"kimi-router": KIMI_ROUTED_PROVIDER_CHOICE,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class ProviderChoiceStrategy:
|
|
55
|
+
provider: str
|
|
56
|
+
routed: bool
|
|
57
|
+
status_lines: tuple[str, ...]
|
|
58
|
+
missing_api_key_line: str = ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
CHOICE_STRATEGIES = {
|
|
62
|
+
ANTHROPIC_NATIVE_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
63
|
+
provider="anthropic",
|
|
64
|
+
routed=False,
|
|
65
|
+
status_lines=(
|
|
66
|
+
"Provider set to anthropic (Claude Native).",
|
|
67
|
+
"mode: anthropic-native",
|
|
68
|
+
"Claude Code OAuth/Max can be used directly, but ciel-runtime router features such as /advisor are unavailable.",
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
ANTHROPIC_ROUTED_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
72
|
+
provider="anthropic",
|
|
73
|
+
routed=True,
|
|
74
|
+
status_lines=(
|
|
75
|
+
"Provider set to anthropic (Anthropic routed).",
|
|
76
|
+
"mode: anthropic-routed",
|
|
77
|
+
),
|
|
78
|
+
missing_api_key_line="Anthropic routed mode will use Claude Code OAuth/API auth headers when available.",
|
|
79
|
+
),
|
|
80
|
+
AGY_NATIVE_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
81
|
+
provider="agy",
|
|
82
|
+
routed=False,
|
|
83
|
+
status_lines=(
|
|
84
|
+
"Provider set to agy (AGY).",
|
|
85
|
+
"mode: agy-native",
|
|
86
|
+
"AGY runs with its own native settings; Ciel Runtime router features are unavailable.",
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
AGY_ROUTED_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
90
|
+
provider="agy",
|
|
91
|
+
routed=True,
|
|
92
|
+
status_lines=(
|
|
93
|
+
"Provider set to agy (AGY routed).",
|
|
94
|
+
"mode: agy-routed",
|
|
95
|
+
"AGY uses native Google Antigravity auth/settings; Ciel Runtime adds channel/PTY wake support only.",
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
CODEX_NATIVE_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
99
|
+
provider="codex",
|
|
100
|
+
routed=False,
|
|
101
|
+
status_lines=(
|
|
102
|
+
"Provider set to codex (Codex Native).",
|
|
103
|
+
"mode: codex-native",
|
|
104
|
+
"Codex runs with its own native settings; ciel-runtime router features are unavailable.",
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
CODEX_ROUTED_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
108
|
+
provider="codex",
|
|
109
|
+
routed=True,
|
|
110
|
+
status_lines=(
|
|
111
|
+
"Provider set to codex (Codex routed).",
|
|
112
|
+
"mode: codex-routed",
|
|
113
|
+
"Codex uses its native OpenAI account/config, with base URL routed through ciel-runtime.",
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
KIMI_NATIVE_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
117
|
+
provider="kimi", routed=False,
|
|
118
|
+
status_lines=("Provider set to kimi (Kimi Native).", "mode: kimi-native", "Kimi Code uses its official OAuth/config directly."),
|
|
119
|
+
),
|
|
120
|
+
KIMI_ROUTED_PROVIDER_CHOICE: ProviderChoiceStrategy(
|
|
121
|
+
provider="kimi", routed=True,
|
|
122
|
+
status_lines=("Provider set to kimi (Kimi Routed).", "mode: kimi-routed", "Kimi Code requests are routed through ciel-runtime."),
|
|
123
|
+
missing_api_key_line="Kimi Routed requires Kimi OAuth login or a Kimi API key; open API key setup to authenticate.",
|
|
124
|
+
),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def normalize_provider_choice(name: str) -> str | None:
|
|
129
|
+
raw = str(name or "").strip().lower().replace("_", "-").replace(" ", "-")
|
|
130
|
+
if raw in CHOICE_STRATEGIES:
|
|
131
|
+
return raw
|
|
132
|
+
return CHOICE_ALIASES.get(raw.replace(":", "-"))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True, slots=True)
|
|
136
|
+
class ProviderChoicePorts:
|
|
137
|
+
load_config: Callable[[], dict[str, Any]]
|
|
138
|
+
save_config: Callable[[dict[str, Any]], None]
|
|
139
|
+
clear_model_cache: Callable[[], None]
|
|
140
|
+
provider_has_api_key: Callable[[str, dict[str, Any]], bool]
|
|
141
|
+
configured_adapter: Callable[[str, dict[str, Any]], ProviderAdapter]
|
|
142
|
+
contract_config: Callable[[str, dict[str, Any]], ProviderConfig]
|
|
143
|
+
provider_label: Callable[[str], str]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ProviderChoiceController:
|
|
147
|
+
def __init__(self, ports: ProviderChoicePorts) -> None:
|
|
148
|
+
self._ports = ports
|
|
149
|
+
|
|
150
|
+
def select(self, choice: str) -> list[str]:
|
|
151
|
+
normalized = normalize_provider_choice(choice) or choice
|
|
152
|
+
strategy = CHOICE_STRATEGIES.get(normalized)
|
|
153
|
+
if strategy is None:
|
|
154
|
+
return self.select_standard(normalized)
|
|
155
|
+
config = self._ports.load_config()
|
|
156
|
+
config["current_provider"] = strategy.provider
|
|
157
|
+
provider_config = config["providers"][strategy.provider]
|
|
158
|
+
provider_config["route_through_router"] = strategy.routed
|
|
159
|
+
self._ports.save_config(config)
|
|
160
|
+
self._ports.clear_model_cache()
|
|
161
|
+
lines = list(strategy.status_lines)
|
|
162
|
+
if (
|
|
163
|
+
strategy.missing_api_key_line
|
|
164
|
+
and not self._ports.provider_has_api_key(strategy.provider, provider_config)
|
|
165
|
+
):
|
|
166
|
+
lines.append(strategy.missing_api_key_line)
|
|
167
|
+
return lines
|
|
168
|
+
|
|
169
|
+
def select_standard(self, provider: str) -> list[str]:
|
|
170
|
+
"""Select a provider while delegating normalization to its adapter."""
|
|
171
|
+
|
|
172
|
+
config = self._ports.load_config()
|
|
173
|
+
config["current_provider"] = provider
|
|
174
|
+
provider_config = config["providers"][provider]
|
|
175
|
+
adapter = self._ports.configured_adapter(provider, provider_config)
|
|
176
|
+
contract = self._ports.contract_config(provider, provider_config)
|
|
177
|
+
updates = adapter.selection_config_updates(contract)
|
|
178
|
+
provider_config.update(updates)
|
|
179
|
+
self._ports.save_config(config)
|
|
180
|
+
self._ports.clear_model_cache()
|
|
181
|
+
selected_contract = self._ports.contract_config(provider, provider_config)
|
|
182
|
+
lines = [f"Provider set to {provider} ({self._ports.provider_label(provider)})."]
|
|
183
|
+
lines.extend(adapter.selection_status_lines(selected_contract))
|
|
184
|
+
lines.extend(adapter.selection_update_status_lines(selected_contract, updates))
|
|
185
|
+
return lines
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = [
|
|
189
|
+
"AGY_NATIVE_PROVIDER_CHOICE",
|
|
190
|
+
"AGY_ROUTED_PROVIDER_CHOICE",
|
|
191
|
+
"ANTHROPIC_NATIVE_PROVIDER_CHOICE",
|
|
192
|
+
"ANTHROPIC_ROUTED_PROVIDER_CHOICE",
|
|
193
|
+
"CODEX_NATIVE_PROVIDER_CHOICE",
|
|
194
|
+
"CODEX_ROUTED_PROVIDER_CHOICE",
|
|
195
|
+
"KIMI_NATIVE_PROVIDER_CHOICE",
|
|
196
|
+
"KIMI_ROUTED_PROVIDER_CHOICE",
|
|
197
|
+
"ProviderChoiceController",
|
|
198
|
+
"ProviderChoicePorts",
|
|
199
|
+
"ProviderChoiceStrategy",
|
|
200
|
+
"normalize_provider_choice",
|
|
201
|
+
]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Provider compatibility strategies kept separate from transport adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Callable, Mapping
|
|
7
|
+
|
|
8
|
+
from .architecture import ProviderConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
FailureDiagnosis = Callable[[int | None, str], str | None]
|
|
12
|
+
ToolUseBlocker = Callable[[str], str]
|
|
13
|
+
RuntimeMetadataProjection = Callable[[Mapping[str, object]], tuple[str, ...]]
|
|
14
|
+
AutoWebSearchPolicy = Callable[[ProviderConfig], bool]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _no_diagnosis(code: int | None, message: str) -> str | None:
|
|
18
|
+
del code, message
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _no_tool_use_blocker(model: str) -> str:
|
|
23
|
+
del model
|
|
24
|
+
return ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _no_runtime_metadata(info: Mapping[str, object]) -> tuple[str, ...]:
|
|
28
|
+
del info
|
|
29
|
+
return ()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _allow_auto_web_search(config: ProviderConfig) -> bool:
|
|
33
|
+
del config
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class ProviderCompatibilityPolicy:
|
|
39
|
+
advisor_transport: str = ""
|
|
40
|
+
runtime_model_info_strategy: str = ""
|
|
41
|
+
auto_web_search: AutoWebSearchPolicy = _allow_auto_web_search
|
|
42
|
+
requires_compat_prompt: bool = True
|
|
43
|
+
failure_diagnosis: FailureDiagnosis = _no_diagnosis
|
|
44
|
+
tool_use_blocker: ToolUseBlocker = _no_tool_use_blocker
|
|
45
|
+
runtime_metadata: RuntimeMetadataProjection = _no_runtime_metadata
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def exposes_runtime_info(self) -> bool:
|
|
49
|
+
return bool(self.runtime_model_info_strategy)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class ProviderCompatibilityRegistry:
|
|
54
|
+
policies: Mapping[str, ProviderCompatibilityPolicy] = field(default_factory=dict)
|
|
55
|
+
default: ProviderCompatibilityPolicy = ProviderCompatibilityPolicy()
|
|
56
|
+
|
|
57
|
+
def resolve(self, provider: str) -> ProviderCompatibilityPolicy:
|
|
58
|
+
return self.policies.get(provider, self.default)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _vllm_failure_diagnosis(code: int | None, message: str) -> str | None:
|
|
62
|
+
del code
|
|
63
|
+
lower = message.lower()
|
|
64
|
+
if "tool" not in lower and "parse" not in lower and "parser" not in lower:
|
|
65
|
+
return None
|
|
66
|
+
return (
|
|
67
|
+
"Diagnosis: vLLM tool calling depends on the server's model-specific --tool-call-parser and chat template. "
|
|
68
|
+
"For Qwen3-Coder models, current vLLM docs recommend --tool-call-parser qwen3_xml; Hermes is for Hermes-style models "
|
|
69
|
+
"and some older Qwen tool templates."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _nvidia_failure_diagnosis(code: int | None, message: str) -> str | None:
|
|
74
|
+
lower = message.lower()
|
|
75
|
+
if code == 404:
|
|
76
|
+
return (
|
|
77
|
+
"Diagnosis: NVIDIA API Catalog does not expose this request path/model for the current account. "
|
|
78
|
+
"Use the default router mode for nvidia-hosted, or pick another hosted model."
|
|
79
|
+
)
|
|
80
|
+
if code in (502, 503, 504):
|
|
81
|
+
return (
|
|
82
|
+
"Diagnosis: NVIDIA API Catalog or the hosted model backend returned a transient upstream error. "
|
|
83
|
+
"Retry the compatibility test, or choose another NVIDIA hosted model if it repeats."
|
|
84
|
+
)
|
|
85
|
+
if any(
|
|
86
|
+
marker in lower
|
|
87
|
+
for marker in (
|
|
88
|
+
"remotedisconnected",
|
|
89
|
+
"remote end closed connection",
|
|
90
|
+
"connection reset",
|
|
91
|
+
"gateway timeout",
|
|
92
|
+
)
|
|
93
|
+
):
|
|
94
|
+
return (
|
|
95
|
+
"Diagnosis: the NVIDIA hosted upstream closed the request without a complete response. "
|
|
96
|
+
"This is usually a transient API Catalog/backend issue rather than a local ciel-runtime configuration error. "
|
|
97
|
+
"Retry the test, or choose another hosted model if it repeats."
|
|
98
|
+
)
|
|
99
|
+
if "function" in lower and "not found" in lower:
|
|
100
|
+
return (
|
|
101
|
+
"Diagnosis: NVIDIA returned a missing function for this hosted model. The model is visible in /v1/models "
|
|
102
|
+
"but is not callable with the current account."
|
|
103
|
+
)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _zai_tool_use_blocker(model: str) -> str:
|
|
108
|
+
if str(model or "").strip().lower() != "glm-4.7-flash":
|
|
109
|
+
return ""
|
|
110
|
+
return (
|
|
111
|
+
"Z.AI GLM-4.7-Flash responds to text requests but direct Anthropic tool-use probes time out. "
|
|
112
|
+
"Claude Code requires tool calling for normal work; use glm-4.5-flash for a Flash/free model, "
|
|
113
|
+
"or use glm-4.7/glm-5.2 if your Z.AI account can access them."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _lm_studio_runtime_metadata(info: Mapping[str, object]) -> tuple[str, ...]:
|
|
118
|
+
lines: list[str] = []
|
|
119
|
+
if info.get("loaded_context_len"):
|
|
120
|
+
lines.append(f"Runtime loaded_context_length: {info['loaded_context_len']}")
|
|
121
|
+
if info.get("state"):
|
|
122
|
+
lines.append(f"Runtime model state: {info['state']}")
|
|
123
|
+
return tuple(lines)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _zai_auto_web_search(config: ProviderConfig) -> bool:
|
|
127
|
+
return not bool(config.options.get("managed_mcp", True))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
PROVIDER_COMPATIBILITY = ProviderCompatibilityRegistry(
|
|
131
|
+
policies={
|
|
132
|
+
"anthropic": ProviderCompatibilityPolicy(
|
|
133
|
+
advisor_transport="anthropic",
|
|
134
|
+
auto_web_search=lambda _config: False,
|
|
135
|
+
requires_compat_prompt=False,
|
|
136
|
+
),
|
|
137
|
+
"ollama": ProviderCompatibilityPolicy(advisor_transport="ollama"),
|
|
138
|
+
"ollama-cloud": ProviderCompatibilityPolicy(advisor_transport="ollama"),
|
|
139
|
+
"lm-studio": ProviderCompatibilityPolicy(
|
|
140
|
+
advisor_transport="openai-compatible",
|
|
141
|
+
runtime_model_info_strategy="lm_studio",
|
|
142
|
+
runtime_metadata=_lm_studio_runtime_metadata,
|
|
143
|
+
),
|
|
144
|
+
"vllm": ProviderCompatibilityPolicy(
|
|
145
|
+
runtime_model_info_strategy="openai",
|
|
146
|
+
failure_diagnosis=_vllm_failure_diagnosis,
|
|
147
|
+
),
|
|
148
|
+
"nvidia-hosted": ProviderCompatibilityPolicy(
|
|
149
|
+
advisor_transport="openai-compatible",
|
|
150
|
+
failure_diagnosis=_nvidia_failure_diagnosis,
|
|
151
|
+
),
|
|
152
|
+
"self-hosted-nim": ProviderCompatibilityPolicy(runtime_model_info_strategy="openai"),
|
|
153
|
+
"zai": ProviderCompatibilityPolicy(
|
|
154
|
+
auto_web_search=_zai_auto_web_search,
|
|
155
|
+
tool_use_blocker=_zai_tool_use_blocker,
|
|
156
|
+
),
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
__all__ = [
|
|
162
|
+
"PROVIDER_COMPATIBILITY",
|
|
163
|
+
"ProviderCompatibilityPolicy",
|
|
164
|
+
"ProviderCompatibilityRegistry",
|
|
165
|
+
]
|