@oneciel-ai/ciel-runtime 0.2.2 → 0.2.3
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/ciel_runtime.py +2553 -9635
- package/ciel_runtime_support/advisor_request_builder.py +8 -21
- package/ciel_runtime_support/anthropic_tool_turns.py +13 -8
- package/ciel_runtime_support/architecture.py +68 -0
- package/ciel_runtime_support/architecture_budget.py +1 -1
- package/ciel_runtime_support/channel_connection_context.py +233 -0
- package/ciel_runtime_support/channel_delivery_context.py +332 -0
- package/ciel_runtime_support/channel_mcp_context.py +313 -0
- package/ciel_runtime_support/channel_mcp_discovery.py +47 -0
- package/ciel_runtime_support/channel_mcp_transport.py +5 -1
- package/ciel_runtime_support/channel_message_context.py +212 -0
- package/ciel_runtime_support/channel_message_repository.py +14 -3
- package/ciel_runtime_support/channel_pending_injection.py +9 -0
- package/ciel_runtime_support/channel_probe_launch_context.py +213 -0
- package/ciel_runtime_support/channel_replay_policy.py +38 -0
- package/ciel_runtime_support/channel_runtime_environment.py +8 -0
- package/ciel_runtime_support/channel_session_context.py +236 -0
- package/ciel_runtime_support/channel_terminal_context.py +350 -0
- package/ciel_runtime_support/channel_wake_context.py +532 -0
- package/ciel_runtime_support/claude_environment.py +60 -0
- package/ciel_runtime_support/claude_launch_assembly.py +249 -0
- package/ciel_runtime_support/claude_router.py +62 -12
- package/ciel_runtime_support/cli_application_context.py +132 -0
- package/ciel_runtime_support/cli_assembly.py +50 -0
- package/ciel_runtime_support/codex_backend_context.py +363 -0
- package/ciel_runtime_support/codex_config.py +13 -1
- package/ciel_runtime_support/codex_launch_assembly.py +213 -0
- package/ciel_runtime_support/codex_launch_configuration.py +30 -1
- package/ciel_runtime_support/codex_mcp_integration.py +90 -8
- package/ciel_runtime_support/codex_model_catalog.py +4 -1
- package/ciel_runtime_support/codex_reasoning_rejects.py +225 -0
- package/ciel_runtime_support/codex_router.py +38 -8
- package/ciel_runtime_support/codex_turn_recovery.py +154 -0
- package/ciel_runtime_support/config_migrations.py +103 -0
- package/ciel_runtime_support/configuration_cli.py +38 -0
- package/ciel_runtime_support/context_compaction.py +9 -4
- package/ciel_runtime_support/credential_management.py +12 -0
- package/ciel_runtime_support/credentials.py +12 -0
- package/ciel_runtime_support/github_copilot_oauth.py +2 -2
- package/ciel_runtime_support/hosted_formula_tools.py +216 -0
- package/ciel_runtime_support/kimi_runtime_context.py +208 -0
- package/ciel_runtime_support/llm_preset_context.py +338 -0
- package/ciel_runtime_support/managed_mcp_config.py +8 -4
- package/ciel_runtime_support/mcp_configuration_context.py +291 -0
- package/ciel_runtime_support/mcp_http_proxy.py +14 -8
- package/ciel_runtime_support/mcp_probe_transport.py +47 -15
- package/ciel_runtime_support/mcp_transport.py +14 -1
- package/ciel_runtime_support/native_context_recovery.py +72 -0
- package/ciel_runtime_support/ollama_catalog_context.py +213 -0
- package/ciel_runtime_support/ollama_stream_collection.py +103 -0
- package/ciel_runtime_support/ollama_thinking.py +6 -1
- package/ciel_runtime_support/ollama_wire_projection.py +157 -0
- package/ciel_runtime_support/openai_forwarding.py +32 -10
- package/ciel_runtime_support/openai_responses_router.py +12 -0
- package/ciel_runtime_support/package_lifecycle.py +39 -0
- package/ciel_runtime_support/prelaunch_assembly.py +37 -0
- package/ciel_runtime_support/prelaunch_panel_context.py +418 -0
- package/ciel_runtime_support/prelaunch_shell_context.py +394 -0
- package/ciel_runtime_support/prompt_compaction.py +144 -0
- package/ciel_runtime_support/prompt_injection.py +45 -0
- package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +1 -1
- package/ciel_runtime_support/protocols/chat_projection.py +85 -5
- package/ciel_runtime_support/protocols/conversation_turn_policy.py +43 -0
- package/ciel_runtime_support/protocols/ollama_chat.py +31 -0
- package/ciel_runtime_support/protocols/ollama_response.py +57 -5
- package/ciel_runtime_support/protocols/openai_reasoning.py +5 -2
- package/ciel_runtime_support/protocols/openai_responses.py +61 -15
- package/ciel_runtime_support/provider_adapters.py +26 -0
- package/ciel_runtime_support/provider_administration_context.py +207 -0
- package/ciel_runtime_support/provider_config_mutations.py +3 -0
- package/ciel_runtime_support/provider_model_catalog_context.py +137 -0
- package/ciel_runtime_support/provider_model_context.py +107 -0
- package/ciel_runtime_support/provider_model_metadata_context.py +197 -0
- package/ciel_runtime_support/provider_model_selection.py +10 -3
- package/ciel_runtime_support/provider_models.py +45 -2
- package/ciel_runtime_support/provider_option_cli.py +19 -0
- package/ciel_runtime_support/provider_policy.py +1 -1
- package/ciel_runtime_support/provider_readiness_context.py +189 -0
- package/ciel_runtime_support/provider_request_builder.py +64 -28
- package/ciel_runtime_support/provider_responses_passthrough.py +21 -2
- package/ciel_runtime_support/provider_timeout_policy.py +54 -0
- package/ciel_runtime_support/provider_tool_policy.py +9 -1
- package/ciel_runtime_support/providers/__init__.py +6 -0
- package/ciel_runtime_support/providers/alibaba.py +634 -0
- package/ciel_runtime_support/providers/catalog.py +24 -16
- package/ciel_runtime_support/providers/deepseek.py +73 -0
- package/ciel_runtime_support/providers/github_copilot_oauth.py +22 -1
- package/ciel_runtime_support/providers/kimi.py +69 -9
- package/ciel_runtime_support/providers/ollama.py +8 -0
- package/ciel_runtime_support/providers/ollama_context.py +21 -2
- package/ciel_runtime_support/providers/vllm.py +7 -1
- package/ciel_runtime_support/response_collection.py +68 -18
- package/ciel_runtime_support/response_collection_context.py +391 -0
- package/ciel_runtime_support/response_stream_context.py +555 -0
- package/ciel_runtime_support/responses_input_compatibility.py +121 -0
- package/ciel_runtime_support/responses_usage_observer.py +83 -0
- package/ciel_runtime_support/router_client_lifecycle.py +1 -0
- package/ciel_runtime_support/router_http.py +239 -3
- package/ciel_runtime_support/router_observability_context.py +251 -0
- package/ciel_runtime_support/router_process_context.py +200 -0
- package/ciel_runtime_support/router_process_lifecycle.py +2 -0
- package/ciel_runtime_support/router_request_assembly.py +399 -0
- package/ciel_runtime_support/router_request_context.py +215 -0
- package/ciel_runtime_support/router_server_context.py +82 -0
- package/ciel_runtime_support/runaway_output_guard.py +488 -0
- package/ciel_runtime_support/runtime_asset_assembly.py +147 -0
- package/ciel_runtime_support/runtime_asset_context.py +297 -0
- package/ciel_runtime_support/runtime_constants.py +16 -1
- package/ciel_runtime_support/runtime_launch.py +9 -5
- package/ciel_runtime_support/runtime_launch_context.py +130 -0
- package/ciel_runtime_support/runtime_maintenance_assembly.py +60 -0
- package/ciel_runtime_support/runtime_maintenance_context.py +309 -0
- package/ciel_runtime_support/runtime_maintenance_services.py +265 -0
- package/ciel_runtime_support/runtime_paths.py +60 -40
- package/ciel_runtime_support/runtime_primitives.py +78 -0
- package/ciel_runtime_support/sse_stream_collection.py +236 -0
- package/ciel_runtime_support/statusline_script.py +57 -8
- package/ciel_runtime_support/streaming_anthropic.py +361 -24
- package/ciel_runtime_support/tool_schema.py +40 -2
- package/ciel_runtime_support/tool_side_effect_dedupe.py +117 -12
- package/ciel_runtime_support/upstream_dump.py +68 -0
- package/ciel_runtime_support/upstream_retry_context.py +259 -0
- package/ciel_runtime_support/workspace_router_selection.py +86 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Provider configuration and credential administration use cases."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Callable, Protocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CredentialRepository(Protocol):
|
|
11
|
+
def store(self, key: str) -> None: ...
|
|
12
|
+
def clear(self) -> None: ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OAuthRuntime(Protocol):
|
|
16
|
+
def token(self) -> str: ...
|
|
17
|
+
def action(self, action: str) -> list[str]: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ProviderAdministrationInfrastructure:
|
|
22
|
+
nvidia_credentials: Callable[[], CredentialRepository]
|
|
23
|
+
copilot_oauth: Callable[[], OAuthRuntime]
|
|
24
|
+
output: Callable[..., Any]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class ProviderAdministrationSelection:
|
|
29
|
+
provider_choice: Callable[[], Any]
|
|
30
|
+
provider_endpoint: Callable[[], Any]
|
|
31
|
+
model_selection: Callable[[], Any]
|
|
32
|
+
advisor_model_selection: Callable[[], Any]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class ProviderAdministrationCredentials:
|
|
37
|
+
management: Callable[[], Any]
|
|
38
|
+
cli: Callable[[], Any]
|
|
39
|
+
configured_keys: Callable[[str, dict[str, Any]], list[str]]
|
|
40
|
+
mask: Callable[[str], str]
|
|
41
|
+
fingerprint: Callable[[str], str]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class ProviderAdministrationPresentation:
|
|
46
|
+
status: Callable[[], Any]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True, slots=True)
|
|
50
|
+
class ProviderAdministrationContext:
|
|
51
|
+
infrastructure: ProviderAdministrationInfrastructure
|
|
52
|
+
selection: ProviderAdministrationSelection
|
|
53
|
+
credentials: ProviderAdministrationCredentials
|
|
54
|
+
presentation: ProviderAdministrationPresentation
|
|
55
|
+
|
|
56
|
+
def store_nvidia_api_key(self, key: str) -> None:
|
|
57
|
+
self.infrastructure.nvidia_credentials().store(key)
|
|
58
|
+
|
|
59
|
+
def clear_nvidia_api_key(self) -> None:
|
|
60
|
+
self.infrastructure.nvidia_credentials().clear()
|
|
61
|
+
|
|
62
|
+
def github_copilot_oauth_token(self) -> str:
|
|
63
|
+
return self.infrastructure.copilot_oauth().token()
|
|
64
|
+
|
|
65
|
+
def run_copilot_oauth_action(self, action: str) -> list[str]:
|
|
66
|
+
return self.infrastructure.copilot_oauth().action(action)
|
|
67
|
+
|
|
68
|
+
def cmd_copilot_oauth(self, args: argparse.Namespace) -> None:
|
|
69
|
+
for line in self.run_copilot_oauth_action(args.action):
|
|
70
|
+
self.infrastructure.output(line, flush=True)
|
|
71
|
+
|
|
72
|
+
def set_provider_config(self, provider: str) -> list[str]:
|
|
73
|
+
return self.selection.provider_choice().select_standard(provider)
|
|
74
|
+
|
|
75
|
+
def set_provider_choice_config(self, choice: str) -> list[str]:
|
|
76
|
+
return self.selection.provider_choice().select(choice)
|
|
77
|
+
|
|
78
|
+
def set_base_url_config(self, provider: str, url: str) -> list[str]:
|
|
79
|
+
return self.selection.provider_endpoint().set_base_url(provider, url)
|
|
80
|
+
|
|
81
|
+
def set_model_config(self, value: str) -> list[str]:
|
|
82
|
+
return self.selection.model_selection().select(value)
|
|
83
|
+
|
|
84
|
+
def set_advisor_model_config(self, value: str) -> list[str]:
|
|
85
|
+
return self.selection.advisor_model_selection().select(value)
|
|
86
|
+
|
|
87
|
+
def store_api_key_config(self, provider: str, key: str) -> list[str]:
|
|
88
|
+
return self.credentials.management().store_one(provider, key)
|
|
89
|
+
|
|
90
|
+
def clear_api_key_config(self, provider: str) -> list[str]:
|
|
91
|
+
return self.credentials.management().clear(provider)
|
|
92
|
+
|
|
93
|
+
def store_api_keys_config(
|
|
94
|
+
self, provider: str, keys: list[str]
|
|
95
|
+
) -> list[str]:
|
|
96
|
+
return self.credentials.management().store_many(provider, keys)
|
|
97
|
+
|
|
98
|
+
def stored_api_key_mask(self, provider: str, pcfg: dict[str, Any]) -> str:
|
|
99
|
+
keys = self.credentials.configured_keys(provider, pcfg)
|
|
100
|
+
if not keys:
|
|
101
|
+
return "not set"
|
|
102
|
+
primary = (
|
|
103
|
+
f"{self.credentials.mask(keys[0])}; "
|
|
104
|
+
f"fp {self.credentials.fingerprint(keys[0])}"
|
|
105
|
+
)
|
|
106
|
+
if len(keys) == 1:
|
|
107
|
+
return primary
|
|
108
|
+
return f"{len(keys)} keys (round-robin; primary {primary})"
|
|
109
|
+
|
|
110
|
+
def store_api_key_input_config(
|
|
111
|
+
self, provider: str, raw_value: str
|
|
112
|
+
) -> list[str]:
|
|
113
|
+
return self.credentials.management().store_input(provider, raw_value)
|
|
114
|
+
|
|
115
|
+
def cmd_set_api_key(self, args: argparse.Namespace) -> None:
|
|
116
|
+
self.credentials.cli().set_one(args)
|
|
117
|
+
|
|
118
|
+
def cmd_set_api_keys(self, args: argparse.Namespace) -> None:
|
|
119
|
+
self.credentials.cli().set_many(args)
|
|
120
|
+
|
|
121
|
+
def cmd_api_key(self, args: argparse.Namespace) -> None:
|
|
122
|
+
self.credentials.cli().manage(args)
|
|
123
|
+
|
|
124
|
+
def status_lines(self) -> list[str]:
|
|
125
|
+
return self.presentation.status().lines()
|
|
126
|
+
|
|
127
|
+
def cmd_status(self, _: argparse.Namespace) -> None:
|
|
128
|
+
self.infrastructure.output("\n".join(self.status_lines()))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class ProviderAdministrationCompatibilityApi:
|
|
133
|
+
context: Callable[[], ProviderAdministrationContext]
|
|
134
|
+
|
|
135
|
+
def store_nvidia_api_key(self, key: str) -> None:
|
|
136
|
+
self.context().store_nvidia_api_key(key)
|
|
137
|
+
|
|
138
|
+
def clear_nvidia_api_key(self) -> None:
|
|
139
|
+
self.context().clear_nvidia_api_key()
|
|
140
|
+
|
|
141
|
+
def github_copilot_oauth_token(self) -> str:
|
|
142
|
+
return self.context().github_copilot_oauth_token()
|
|
143
|
+
|
|
144
|
+
def run_copilot_oauth_action(self, action: str) -> list[str]:
|
|
145
|
+
return self.context().run_copilot_oauth_action(action)
|
|
146
|
+
|
|
147
|
+
def cmd_copilot_oauth(self, args: argparse.Namespace) -> None:
|
|
148
|
+
self.context().cmd_copilot_oauth(args)
|
|
149
|
+
|
|
150
|
+
def set_provider_config(self, provider: str) -> list[str]:
|
|
151
|
+
return self.context().set_provider_config(provider)
|
|
152
|
+
|
|
153
|
+
def set_provider_choice_config(self, choice: str) -> list[str]:
|
|
154
|
+
return self.context().set_provider_choice_config(choice)
|
|
155
|
+
|
|
156
|
+
def set_base_url_config(self, provider: str, url: str) -> list[str]:
|
|
157
|
+
return self.context().set_base_url_config(provider, url)
|
|
158
|
+
|
|
159
|
+
def set_model_config(self, value: str) -> list[str]:
|
|
160
|
+
return self.context().set_model_config(value)
|
|
161
|
+
|
|
162
|
+
def set_advisor_model_config(self, value: str) -> list[str]:
|
|
163
|
+
return self.context().set_advisor_model_config(value)
|
|
164
|
+
|
|
165
|
+
def store_api_key_config(self, provider: str, key: str) -> list[str]:
|
|
166
|
+
return self.context().store_api_key_config(provider, key)
|
|
167
|
+
|
|
168
|
+
def clear_api_key_config(self, provider: str) -> list[str]:
|
|
169
|
+
return self.context().clear_api_key_config(provider)
|
|
170
|
+
|
|
171
|
+
def store_api_keys_config(
|
|
172
|
+
self, provider: str, keys: list[str]
|
|
173
|
+
) -> list[str]:
|
|
174
|
+
return self.context().store_api_keys_config(provider, keys)
|
|
175
|
+
|
|
176
|
+
def stored_api_key_mask(self, provider: str, pcfg: dict[str, Any]) -> str:
|
|
177
|
+
return self.context().stored_api_key_mask(provider, pcfg)
|
|
178
|
+
|
|
179
|
+
def store_api_key_input_config(
|
|
180
|
+
self, provider: str, raw_value: str
|
|
181
|
+
) -> list[str]:
|
|
182
|
+
return self.context().store_api_key_input_config(provider, raw_value)
|
|
183
|
+
|
|
184
|
+
def cmd_set_api_key(self, args: argparse.Namespace) -> None:
|
|
185
|
+
self.context().cmd_set_api_key(args)
|
|
186
|
+
|
|
187
|
+
def cmd_set_api_keys(self, args: argparse.Namespace) -> None:
|
|
188
|
+
self.context().cmd_set_api_keys(args)
|
|
189
|
+
|
|
190
|
+
def cmd_api_key(self, args: argparse.Namespace) -> None:
|
|
191
|
+
self.context().cmd_api_key(args)
|
|
192
|
+
|
|
193
|
+
def status_lines(self) -> list[str]:
|
|
194
|
+
return self.context().status_lines()
|
|
195
|
+
|
|
196
|
+
def cmd_status(self, args: argparse.Namespace) -> None:
|
|
197
|
+
self.context().cmd_status(args)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
__all__ = [
|
|
201
|
+
"ProviderAdministrationCompatibilityApi",
|
|
202
|
+
"ProviderAdministrationContext",
|
|
203
|
+
"ProviderAdministrationCredentials",
|
|
204
|
+
"ProviderAdministrationInfrastructure",
|
|
205
|
+
"ProviderAdministrationPresentation",
|
|
206
|
+
"ProviderAdministrationSelection",
|
|
207
|
+
]
|
|
@@ -64,6 +64,7 @@ def apply_ollama_option(
|
|
|
64
64
|
_set_ollama_option_explicit(pcfg, "num_predict", False)
|
|
65
65
|
elif key in ("keep_alive", "keepalive"):
|
|
66
66
|
pcfg.pop("keep_alive", None)
|
|
67
|
+
pcfg.pop("keep_alive_explicit", None)
|
|
67
68
|
elif key == "think":
|
|
68
69
|
pcfg["think"] = False
|
|
69
70
|
pcfg.pop("think_explicit", None)
|
|
@@ -120,8 +121,10 @@ def apply_ollama_option(
|
|
|
120
121
|
if key in ("keep_alive", "keepalive"):
|
|
121
122
|
if value is None:
|
|
122
123
|
pcfg.pop("keep_alive", None)
|
|
124
|
+
pcfg.pop("keep_alive_explicit", None)
|
|
123
125
|
else:
|
|
124
126
|
pcfg["keep_alive"] = str(value)
|
|
127
|
+
pcfg["keep_alive_explicit"] = True
|
|
125
128
|
return
|
|
126
129
|
if key in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms"):
|
|
127
130
|
fixed = positive_int(value)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Provider model registry and cache lifecycle bounded context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from .model_cache_lifecycle import (
|
|
10
|
+
ModelCacheLifecyclePorts,
|
|
11
|
+
ModelCacheLifecycleService,
|
|
12
|
+
)
|
|
13
|
+
from .model_registry_repository import (
|
|
14
|
+
ModelRegistryPaths,
|
|
15
|
+
ModelRegistryPolicy,
|
|
16
|
+
ModelRegistryRepository,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ProviderModelRegistryConfig:
|
|
22
|
+
config_dir: Path
|
|
23
|
+
registry_path: Path
|
|
24
|
+
list_cache_path: Path
|
|
25
|
+
gateway_cache_path: Path
|
|
26
|
+
ttl_seconds: float
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class ProviderModelRegistryPorts:
|
|
31
|
+
cache_key: Callable[..., str]
|
|
32
|
+
unique_ids: Callable[..., list[str]]
|
|
33
|
+
normalize_id: Callable[..., str]
|
|
34
|
+
positive_int: Callable[..., int]
|
|
35
|
+
recommendations: Callable[..., dict[str, Any]]
|
|
36
|
+
log: Callable[[str, str], None]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, slots=True)
|
|
40
|
+
class ProviderModelCachePorts:
|
|
41
|
+
invalidate_config: Callable[[], None]
|
|
42
|
+
upstream_model_ids: Callable[..., list[str]]
|
|
43
|
+
catalog_model_ids: Callable[..., list[str]]
|
|
44
|
+
sorted_model_ids: Callable[..., list[str]]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True, slots=True)
|
|
48
|
+
class ProviderModelCatalogCompatibilityPorts:
|
|
49
|
+
read_list_cache: Callable[..., list[str] | None]
|
|
50
|
+
read_registry_models: Callable[..., list[str] | None]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class ProviderModelCatalogContext:
|
|
55
|
+
config: ProviderModelRegistryConfig
|
|
56
|
+
registry: ProviderModelRegistryPorts
|
|
57
|
+
cache: ProviderModelCachePorts
|
|
58
|
+
compatibility: ProviderModelCatalogCompatibilityPorts
|
|
59
|
+
|
|
60
|
+
def registry_repository(self) -> ModelRegistryRepository:
|
|
61
|
+
return ModelRegistryRepository(
|
|
62
|
+
paths=ModelRegistryPaths(
|
|
63
|
+
self.config.config_dir,
|
|
64
|
+
self.config.registry_path,
|
|
65
|
+
self.config.list_cache_path,
|
|
66
|
+
),
|
|
67
|
+
policy=ModelRegistryPolicy(
|
|
68
|
+
cache_key=self.registry.cache_key,
|
|
69
|
+
unique_ids=self.registry.unique_ids,
|
|
70
|
+
normalize_id=self.registry.normalize_id,
|
|
71
|
+
positive_int=self.registry.positive_int,
|
|
72
|
+
recommendations=self.registry.recommendations,
|
|
73
|
+
log=self.registry.log,
|
|
74
|
+
),
|
|
75
|
+
ttl_seconds=self.config.ttl_seconds,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def lifecycle_service(self) -> ModelCacheLifecycleService:
|
|
79
|
+
return ModelCacheLifecycleService(
|
|
80
|
+
ModelCacheLifecyclePorts(
|
|
81
|
+
invalidate_config=self.cache.invalidate_config,
|
|
82
|
+
artifact_paths=lambda: (
|
|
83
|
+
self.config.gateway_cache_path,
|
|
84
|
+
self.config.list_cache_path,
|
|
85
|
+
self.config.registry_path,
|
|
86
|
+
),
|
|
87
|
+
read_list_cache=self.compatibility.read_list_cache,
|
|
88
|
+
read_registry_models=self.compatibility.read_registry_models,
|
|
89
|
+
upstream_model_ids=self.cache.upstream_model_ids,
|
|
90
|
+
catalog_model_ids=self.cache.catalog_model_ids,
|
|
91
|
+
normalize_model_id=self.registry.normalize_id,
|
|
92
|
+
unique_model_ids=self.registry.unique_ids,
|
|
93
|
+
sorted_model_ids=self.cache.sorted_model_ids,
|
|
94
|
+
log=self.registry.log,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def clear(self) -> None:
|
|
99
|
+
self.lifecycle_service().clear()
|
|
100
|
+
|
|
101
|
+
def cached_or_configured_ids(
|
|
102
|
+
self, provider: str, pcfg: dict[str, Any]
|
|
103
|
+
) -> list[str]:
|
|
104
|
+
return self.lifecycle_service().cached_or_configured_ids(provider, pcfg)
|
|
105
|
+
|
|
106
|
+
def ensure_for_launch(self, provider: str, pcfg: dict[str, Any]) -> None:
|
|
107
|
+
self.lifecycle_service().ensure_for_launch(provider, pcfg)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True, slots=True)
|
|
111
|
+
class ProviderModelCatalogCompatibilityApi:
|
|
112
|
+
context: Callable[[], ProviderModelCatalogContext]
|
|
113
|
+
|
|
114
|
+
def registry_repository(self) -> ModelRegistryRepository:
|
|
115
|
+
return self.context().registry_repository()
|
|
116
|
+
|
|
117
|
+
def lifecycle_service(self) -> ModelCacheLifecycleService:
|
|
118
|
+
return self.context().lifecycle_service()
|
|
119
|
+
|
|
120
|
+
def clear(self) -> None:
|
|
121
|
+
self.context().clear()
|
|
122
|
+
|
|
123
|
+
def cached_or_configured_ids(self, *args: Any, **kwargs: Any) -> list[str]:
|
|
124
|
+
return self.context().cached_or_configured_ids(*args, **kwargs)
|
|
125
|
+
|
|
126
|
+
def ensure_for_launch(self, *args: Any, **kwargs: Any) -> None:
|
|
127
|
+
self.context().ensure_for_launch(*args, **kwargs)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
__all__ = [
|
|
131
|
+
"ProviderModelCachePorts",
|
|
132
|
+
"ProviderModelCatalogCompatibilityPorts",
|
|
133
|
+
"ProviderModelCatalogCompatibilityApi",
|
|
134
|
+
"ProviderModelCatalogContext",
|
|
135
|
+
"ProviderModelRegistryConfig",
|
|
136
|
+
"ProviderModelRegistryPorts",
|
|
137
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Provider model context-capacity and output-budget bounded context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from .architecture import ProviderContextPolicy
|
|
9
|
+
from .provider_context import ProviderContextServices
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class ProviderModelContextQueries:
|
|
14
|
+
context_policy: Callable[[str, dict[str, Any]], ProviderContextPolicy]
|
|
15
|
+
context_limit: Callable[[str, dict[str, Any]], int | None]
|
|
16
|
+
positive_int: Callable[[Any], int | None]
|
|
17
|
+
format_context: Callable[[int | None], str]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ProviderModelContextAlgorithms:
|
|
22
|
+
resolve_capacity: Callable[..., int | None]
|
|
23
|
+
apply_capacity_cap: Callable[..., list[str]]
|
|
24
|
+
resolve_small_output_cap: Callable[..., int | None]
|
|
25
|
+
apply_output_token_cap: Callable[..., int | None]
|
|
26
|
+
apply_output_context_cap: Callable[..., list[str]]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class ProviderModelContext:
|
|
31
|
+
services: ProviderContextServices
|
|
32
|
+
queries: ProviderModelContextQueries
|
|
33
|
+
algorithms: ProviderModelContextAlgorithms
|
|
34
|
+
|
|
35
|
+
def capacity(self, provider: str, config: dict[str, Any]) -> int | None:
|
|
36
|
+
return self.algorithms.resolve_capacity(
|
|
37
|
+
provider,
|
|
38
|
+
config,
|
|
39
|
+
self.queries.context_policy(provider, config),
|
|
40
|
+
self.services,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def cap_context(self, provider: str, config: dict[str, Any]) -> list[str]:
|
|
44
|
+
return self.algorithms.apply_capacity_cap(
|
|
45
|
+
config,
|
|
46
|
+
self.capacity(provider, config),
|
|
47
|
+
self.queries.context_policy(provider, config),
|
|
48
|
+
positive_int=self.queries.positive_int,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def small_output_cap(self, context_window: int | None) -> int | None:
|
|
52
|
+
return self.algorithms.resolve_small_output_cap(
|
|
53
|
+
context_window, positive_int=self.queries.positive_int
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def cap_output_tokens(
|
|
57
|
+
self, provider: str, config: dict[str, Any], configured: int | None
|
|
58
|
+
) -> int | None:
|
|
59
|
+
return self.algorithms.apply_output_token_cap(
|
|
60
|
+
configured,
|
|
61
|
+
self.queries.context_policy(provider, config),
|
|
62
|
+
self.queries.context_limit(provider, config),
|
|
63
|
+
positive_int=self.queries.positive_int,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def cap_output_settings(
|
|
67
|
+
self, provider: str, config: dict[str, Any]
|
|
68
|
+
) -> list[str]:
|
|
69
|
+
return self.algorithms.apply_output_context_cap(
|
|
70
|
+
config,
|
|
71
|
+
self.queries.context_policy(provider, config),
|
|
72
|
+
self.queries.context_limit(provider, config),
|
|
73
|
+
positive_int=self.queries.positive_int,
|
|
74
|
+
format_context=self.queries.format_context,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class ProviderModelContextCompatibilityApi:
|
|
80
|
+
context: Callable[[], ProviderModelContext]
|
|
81
|
+
|
|
82
|
+
def capacity(self, provider: str, config: dict[str, Any]) -> int | None:
|
|
83
|
+
return self.context().capacity(provider, config)
|
|
84
|
+
|
|
85
|
+
def cap_context(self, provider: str, config: dict[str, Any]) -> list[str]:
|
|
86
|
+
return self.context().cap_context(provider, config)
|
|
87
|
+
|
|
88
|
+
def small_output_cap(self, context_window: int | None) -> int | None:
|
|
89
|
+
return self.context().small_output_cap(context_window)
|
|
90
|
+
|
|
91
|
+
def cap_output_tokens(
|
|
92
|
+
self, provider: str, config: dict[str, Any], configured: int | None
|
|
93
|
+
) -> int | None:
|
|
94
|
+
return self.context().cap_output_tokens(provider, config, configured)
|
|
95
|
+
|
|
96
|
+
def cap_output_settings(
|
|
97
|
+
self, provider: str, config: dict[str, Any]
|
|
98
|
+
) -> list[str]:
|
|
99
|
+
return self.context().cap_output_settings(provider, config)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
"ProviderModelContext",
|
|
104
|
+
"ProviderModelContextAlgorithms",
|
|
105
|
+
"ProviderModelContextCompatibilityApi",
|
|
106
|
+
"ProviderModelContextQueries",
|
|
107
|
+
]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Provider model cache identity, capability, and catalog header policy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Mapping
|
|
9
|
+
|
|
10
|
+
from . import anthropic_model_policy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class ModelCapabilityPorts:
|
|
15
|
+
normalize_capabilities: Callable[[Any], list[str]]
|
|
16
|
+
current_model: Callable[[str, dict[str, Any]], str]
|
|
17
|
+
strip_context_suffix: Callable[[str], str]
|
|
18
|
+
is_kimi_k3: Callable[[str], bool]
|
|
19
|
+
parse_bool: Callable[..., bool]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class ModelRegistryRecommendationPorts:
|
|
24
|
+
unique_model_ids: Callable[[list[str]], list[str]]
|
|
25
|
+
preset_timeout_ms: Callable[[str], int]
|
|
26
|
+
timeout_idle_ms: Callable[[str], int]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class ModelCatalogHeaderPorts:
|
|
31
|
+
api_key_count: Callable[[str, dict[str, Any]], int]
|
|
32
|
+
read_env_file: Callable[[Path], dict[str, str]]
|
|
33
|
+
environment: Mapping[str, str]
|
|
34
|
+
user_agent_headers: Callable[..., dict[str, str]]
|
|
35
|
+
primary_api_key: Callable[[str, dict[str, Any]], str | None]
|
|
36
|
+
meaningful_key: Callable[[str | None], bool]
|
|
37
|
+
configured_adapter: Callable[..., Any]
|
|
38
|
+
contract_config: Callable[..., Any]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class ProviderModelMetadataContext:
|
|
43
|
+
capabilities: ModelCapabilityPorts
|
|
44
|
+
recommendations: ModelRegistryRecommendationPorts
|
|
45
|
+
headers: ModelCatalogHeaderPorts
|
|
46
|
+
nvidia_env: Path
|
|
47
|
+
|
|
48
|
+
def cache_key(self, provider: str, pcfg: dict[str, Any]) -> str:
|
|
49
|
+
api_state = (
|
|
50
|
+
"key" if self.headers.api_key_count(provider, pcfg) else "nokey"
|
|
51
|
+
)
|
|
52
|
+
return json.dumps(
|
|
53
|
+
{
|
|
54
|
+
"provider": provider,
|
|
55
|
+
"base_url": pcfg.get("base_url", ""),
|
|
56
|
+
"model_api_base_url": pcfg.get("model_api_base_url", ""),
|
|
57
|
+
"account_id": pcfg.get("account_id", ""),
|
|
58
|
+
"api": api_state,
|
|
59
|
+
"custom": pcfg.get("custom_models", []),
|
|
60
|
+
"schema": 7,
|
|
61
|
+
},
|
|
62
|
+
sort_keys=True,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def infer_claude_capabilities(self, model_id: str) -> list[str]:
|
|
66
|
+
return anthropic_model_policy.infer_capabilities(
|
|
67
|
+
model_id, self.capabilities.strip_context_suffix
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def claude_capabilities(
|
|
71
|
+
self,
|
|
72
|
+
provider: str,
|
|
73
|
+
pcfg: dict[str, Any],
|
|
74
|
+
model_id: str | None = None,
|
|
75
|
+
) -> list[str]:
|
|
76
|
+
caps = self.capabilities.normalize_capabilities(
|
|
77
|
+
pcfg.get("claude_code_supported_capabilities")
|
|
78
|
+
)
|
|
79
|
+
model = model_id or self.capabilities.current_model(provider, pcfg)
|
|
80
|
+
if not caps:
|
|
81
|
+
caps = self.infer_claude_capabilities(model)
|
|
82
|
+
if (
|
|
83
|
+
provider == "kimi"
|
|
84
|
+
and self.capabilities.is_kimi_k3(model)
|
|
85
|
+
and "max_effort" not in caps
|
|
86
|
+
):
|
|
87
|
+
caps.append("max_effort")
|
|
88
|
+
return caps
|
|
89
|
+
|
|
90
|
+
def claude_capability_string(
|
|
91
|
+
self,
|
|
92
|
+
provider: str,
|
|
93
|
+
pcfg: dict[str, Any],
|
|
94
|
+
model_id: str | None = None,
|
|
95
|
+
) -> str:
|
|
96
|
+
return ",".join(self.claude_capabilities(provider, pcfg, model_id))
|
|
97
|
+
|
|
98
|
+
def workflows_enabled(self, provider: str, pcfg: dict[str, Any]) -> bool:
|
|
99
|
+
del provider
|
|
100
|
+
ultracode = (
|
|
101
|
+
pcfg.get("ultracode_enabled")
|
|
102
|
+
if "ultracode_enabled" in pcfg
|
|
103
|
+
else pcfg.get("ultracode")
|
|
104
|
+
)
|
|
105
|
+
if self.capabilities.parse_bool(ultracode, False):
|
|
106
|
+
return True
|
|
107
|
+
value = (
|
|
108
|
+
pcfg.get("workflows_enabled")
|
|
109
|
+
if "workflows_enabled" in pcfg
|
|
110
|
+
else pcfg.get("workflows")
|
|
111
|
+
)
|
|
112
|
+
return self.capabilities.parse_bool(value, False)
|
|
113
|
+
|
|
114
|
+
def ultracode_enabled(self, provider: str, pcfg: dict[str, Any]) -> bool:
|
|
115
|
+
del provider
|
|
116
|
+
value = (
|
|
117
|
+
pcfg.get("ultracode_enabled")
|
|
118
|
+
if "ultracode_enabled" in pcfg
|
|
119
|
+
else pcfg.get("ultracode")
|
|
120
|
+
)
|
|
121
|
+
return self.capabilities.parse_bool(value, False)
|
|
122
|
+
|
|
123
|
+
def registry_recommendations(
|
|
124
|
+
self, provider: str, models: list[str]
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
|
+
return anthropic_model_policy.AnthropicModelRecommendations(
|
|
127
|
+
self.recommendations.unique_model_ids,
|
|
128
|
+
self.recommendations.preset_timeout_ms,
|
|
129
|
+
self.recommendations.timeout_idle_ms,
|
|
130
|
+
).build(provider, models)
|
|
131
|
+
|
|
132
|
+
def nvidia_list_headers(self) -> dict[str, str]:
|
|
133
|
+
headers = {"content-type": "application/json"}
|
|
134
|
+
key = self.headers.read_env_file(self.nvidia_env).get(
|
|
135
|
+
"NVIDIA_API_KEY"
|
|
136
|
+
) or self.headers.environment.get("NVIDIA_API_KEY")
|
|
137
|
+
if key:
|
|
138
|
+
headers["authorization"] = f"Bearer {key}"
|
|
139
|
+
headers["x-api-key"] = key
|
|
140
|
+
return headers
|
|
141
|
+
|
|
142
|
+
def provider_list_headers(
|
|
143
|
+
self, provider: str, pcfg: dict[str, Any]
|
|
144
|
+
) -> dict[str, str]:
|
|
145
|
+
headers = self.headers.user_agent_headers(
|
|
146
|
+
{"content-type": "application/json"}
|
|
147
|
+
)
|
|
148
|
+
key = self.headers.primary_api_key(provider, pcfg)
|
|
149
|
+
meaningful = str(key) if self.headers.meaningful_key(key) else None
|
|
150
|
+
adapter = self.headers.configured_adapter(provider, pcfg)
|
|
151
|
+
headers.update(
|
|
152
|
+
adapter.build_model_headers(
|
|
153
|
+
self.headers.contract_config(provider, pcfg), meaningful
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return headers
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass(frozen=True, slots=True)
|
|
160
|
+
class ProviderModelMetadataCompatibilityApi:
|
|
161
|
+
context: Callable[[], ProviderModelMetadataContext]
|
|
162
|
+
|
|
163
|
+
def cache_key(self, *args: Any, **kwargs: Any) -> str:
|
|
164
|
+
return self.context().cache_key(*args, **kwargs)
|
|
165
|
+
|
|
166
|
+
def infer_claude_capabilities(self, *args: Any, **kwargs: Any) -> list[str]:
|
|
167
|
+
return self.context().infer_claude_capabilities(*args, **kwargs)
|
|
168
|
+
|
|
169
|
+
def claude_capabilities(self, *args: Any, **kwargs: Any) -> list[str]:
|
|
170
|
+
return self.context().claude_capabilities(*args, **kwargs)
|
|
171
|
+
|
|
172
|
+
def claude_capability_string(self, *args: Any, **kwargs: Any) -> str:
|
|
173
|
+
return self.context().claude_capability_string(*args, **kwargs)
|
|
174
|
+
|
|
175
|
+
def workflows_enabled(self, *args: Any, **kwargs: Any) -> bool:
|
|
176
|
+
return self.context().workflows_enabled(*args, **kwargs)
|
|
177
|
+
|
|
178
|
+
def ultracode_enabled(self, *args: Any, **kwargs: Any) -> bool:
|
|
179
|
+
return self.context().ultracode_enabled(*args, **kwargs)
|
|
180
|
+
|
|
181
|
+
def registry_recommendations(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
182
|
+
return self.context().registry_recommendations(*args, **kwargs)
|
|
183
|
+
|
|
184
|
+
def nvidia_list_headers(self) -> dict[str, str]:
|
|
185
|
+
return self.context().nvidia_list_headers()
|
|
186
|
+
|
|
187
|
+
def provider_list_headers(self, *args: Any, **kwargs: Any) -> dict[str, str]:
|
|
188
|
+
return self.context().provider_list_headers(*args, **kwargs)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
__all__ = [
|
|
192
|
+
"ModelCapabilityPorts",
|
|
193
|
+
"ModelCatalogHeaderPorts",
|
|
194
|
+
"ModelRegistryRecommendationPorts",
|
|
195
|
+
"ProviderModelMetadataCompatibilityApi",
|
|
196
|
+
"ProviderModelMetadataContext",
|
|
197
|
+
]
|
|
@@ -220,13 +220,20 @@ class ProviderModelSelection:
|
|
|
220
220
|
force_refresh: bool = False,
|
|
221
221
|
) -> tuple[bool, list[str]]:
|
|
222
222
|
adapter = self.selection.adapter(provider, config)
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
contract = self.selection.contract(provider, config)
|
|
224
|
+
catalog_policy = adapter.model_catalog_policy(contract)
|
|
225
|
+
if (
|
|
226
|
+
not adapter.requires_catalog_model_selection(contract)
|
|
227
|
+
and not catalog_policy.authoritative_upstream_catalog
|
|
225
228
|
):
|
|
226
229
|
return True, []
|
|
227
230
|
current = self.identity.normalize(provider, str(config.get("current_model") or ""))
|
|
228
231
|
placeholders = self.selection.placeholders(provider)
|
|
229
|
-
if
|
|
232
|
+
if (
|
|
233
|
+
current
|
|
234
|
+
and current not in placeholders
|
|
235
|
+
and not catalog_policy.authoritative_upstream_catalog
|
|
236
|
+
):
|
|
230
237
|
return True, []
|
|
231
238
|
try:
|
|
232
239
|
ids = self.selection.unique_ids(
|