@oneciel-ai/ciel-runtime 0.2.2 → 0.2.4
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 +2555 -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/config_repository.py +30 -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 +245 -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 +84 -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/speech_http_controller.py +335 -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/web_ui.py +248 -1
- package/ciel_runtime_support/workspace_router_selection.py +86 -0
- package/docs/COLAB_SPEECH.md +32 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +4 -1
- package/scripts/colab/__pycache__/bootstrap_moss_tts.cpython-311.pyc +0 -0
- package/scripts/colab/__pycache__/bootstrap_qwen_asr.cpython-311.pyc +0 -0
- package/scripts/colab/bootstrap_moss_tts.py +106 -0
- package/scripts/colab/bootstrap_qwen_asr.py +106 -0
- package/scripts/configure_speech_workers.py +37 -0
- package/scripts/deploy_colab_speech.ps1 +47 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Kimi Code installation, OAuth identity, and launch 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
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class KimiIdentityPorts:
|
|
12
|
+
home: Path
|
|
13
|
+
code_home: Callable[[Path], Path]
|
|
14
|
+
token_record: Callable[[Path], dict[str, Any] | None]
|
|
15
|
+
access_token: Callable[[Path], str | None]
|
|
16
|
+
configured: Callable[[Path], bool]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class KimiProcessPorts:
|
|
21
|
+
find_executable: Callable[[str], str | None]
|
|
22
|
+
run: Callable[..., Any]
|
|
23
|
+
call: Callable[..., int]
|
|
24
|
+
print_line: Callable[..., None]
|
|
25
|
+
environment: dict[str, str]
|
|
26
|
+
augment_path: Callable[[dict[str, str]], str]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class KimiConfigurationPorts:
|
|
31
|
+
load: Callable[[], dict[str, Any]]
|
|
32
|
+
current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
|
|
33
|
+
provider_has_key: Callable[[str, dict[str, Any]], bool]
|
|
34
|
+
current_alias: Callable[[dict[str, Any]], str]
|
|
35
|
+
positive_int: Callable[[Any], int | None]
|
|
36
|
+
clear_api_key: Callable[[str], list[str]]
|
|
37
|
+
router_base: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class KimiLifecyclePorts:
|
|
42
|
+
install: Callable[[], str]
|
|
43
|
+
oauth_configured: Callable[[], bool]
|
|
44
|
+
oauth_login: Callable[[], int]
|
|
45
|
+
start_router: Callable[[], Any]
|
|
46
|
+
run_with_router: Callable[[Callable[[], int], bool], int]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True, slots=True)
|
|
50
|
+
class KimiRuntimeContext:
|
|
51
|
+
identity: KimiIdentityPorts
|
|
52
|
+
process: KimiProcessPorts
|
|
53
|
+
config: KimiConfigurationPorts
|
|
54
|
+
lifecycle: KimiLifecyclePorts
|
|
55
|
+
|
|
56
|
+
def code_home(self) -> Path:
|
|
57
|
+
return self.identity.code_home(self.identity.home)
|
|
58
|
+
|
|
59
|
+
def oauth_token_record(self) -> dict[str, Any] | None:
|
|
60
|
+
return self.identity.token_record(self.identity.home)
|
|
61
|
+
|
|
62
|
+
def oauth_access_token(self) -> str | None:
|
|
63
|
+
return self.identity.access_token(self.identity.home)
|
|
64
|
+
|
|
65
|
+
def oauth_configured(self) -> bool:
|
|
66
|
+
return self.identity.configured(self.identity.home)
|
|
67
|
+
|
|
68
|
+
def install_if_missing(self) -> str:
|
|
69
|
+
executable = self.process.find_executable("kimi")
|
|
70
|
+
if executable:
|
|
71
|
+
return executable
|
|
72
|
+
npm = self.process.find_executable("npm")
|
|
73
|
+
if not npm:
|
|
74
|
+
raise RuntimeError(
|
|
75
|
+
"Kimi Code CLI is missing; install @moonshot-ai/kimi-code "
|
|
76
|
+
"(Node.js 22.19+)."
|
|
77
|
+
)
|
|
78
|
+
self.process.print_line(
|
|
79
|
+
"Installing official Kimi Code CLI (@moonshot-ai/kimi-code)...",
|
|
80
|
+
flush=True,
|
|
81
|
+
)
|
|
82
|
+
result = self.process.run(
|
|
83
|
+
[npm, "install", "-g", "@moonshot-ai/kimi-code"], check=False
|
|
84
|
+
)
|
|
85
|
+
if result.returncode:
|
|
86
|
+
raise RuntimeError(
|
|
87
|
+
f"Kimi Code CLI installation failed (exit {result.returncode})."
|
|
88
|
+
)
|
|
89
|
+
executable = self.process.find_executable("kimi")
|
|
90
|
+
if not executable:
|
|
91
|
+
raise RuntimeError(
|
|
92
|
+
"Kimi Code CLI installed but 'kimi' is not available on PATH."
|
|
93
|
+
)
|
|
94
|
+
return executable
|
|
95
|
+
|
|
96
|
+
def oauth_login(self) -> int:
|
|
97
|
+
return self.process.call([self.lifecycle.install(), "login"])
|
|
98
|
+
|
|
99
|
+
def oauth_action(self, action: str) -> list[str]:
|
|
100
|
+
if action != "login":
|
|
101
|
+
return [f"Unsupported Kimi OAuth action: {action}"]
|
|
102
|
+
try:
|
|
103
|
+
code = self.lifecycle.oauth_login()
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
return [f"Kimi OAuth login failed: {type(exc).__name__}: {exc}"]
|
|
106
|
+
if code:
|
|
107
|
+
return [f"Kimi OAuth login exited with status {code}."]
|
|
108
|
+
if not self.lifecycle.oauth_configured():
|
|
109
|
+
return [
|
|
110
|
+
"Kimi OAuth login exited successfully, but no usable credential "
|
|
111
|
+
"was detected; the existing Kimi API key was not cleared."
|
|
112
|
+
]
|
|
113
|
+
messages = [
|
|
114
|
+
"Kimi OAuth login completed in the official Kimi Code credential store."
|
|
115
|
+
]
|
|
116
|
+
messages.extend(self.config.clear_api_key("kimi"))
|
|
117
|
+
return messages
|
|
118
|
+
|
|
119
|
+
def launch(self, passthrough: list[str]) -> int:
|
|
120
|
+
cfg = self.config.load()
|
|
121
|
+
provider, pcfg = self.config.current_provider(cfg)
|
|
122
|
+
if provider != "kimi":
|
|
123
|
+
self.process.print_line(
|
|
124
|
+
"Launch Kimi Code requires Kimi Native or Kimi Routed provider.",
|
|
125
|
+
flush=True,
|
|
126
|
+
)
|
|
127
|
+
return 2
|
|
128
|
+
executable = self.lifecycle.install()
|
|
129
|
+
routed = bool(pcfg.get("route_through_router"))
|
|
130
|
+
env = self.process.environment.copy()
|
|
131
|
+
env["PATH"] = self.process.augment_path(env)
|
|
132
|
+
if not routed:
|
|
133
|
+
if not self.lifecycle.oauth_configured():
|
|
134
|
+
self.process.print_line(
|
|
135
|
+
"Kimi Code OAuth login is required for first launch.", flush=True
|
|
136
|
+
)
|
|
137
|
+
if self.process.call([executable, "login"], env=env):
|
|
138
|
+
return 1
|
|
139
|
+
return self.process.call([executable, *passthrough], env=env)
|
|
140
|
+
if (
|
|
141
|
+
not self.config.provider_has_key(provider, pcfg)
|
|
142
|
+
and not self.lifecycle.oauth_configured()
|
|
143
|
+
):
|
|
144
|
+
self.process.print_line(
|
|
145
|
+
"Kimi Routed requires Kimi OAuth login or a Kimi API key.",
|
|
146
|
+
flush=True,
|
|
147
|
+
)
|
|
148
|
+
if self.process.call([executable, "login"], env=env):
|
|
149
|
+
return 1
|
|
150
|
+
manage_router = bool(self.lifecycle.start_router())
|
|
151
|
+
env.update(
|
|
152
|
+
{
|
|
153
|
+
"KIMI_MODEL_NAME": self.config.current_alias(cfg)
|
|
154
|
+
or str(pcfg.get("current_model") or "kimi-for-coding"),
|
|
155
|
+
"KIMI_MODEL_API_KEY": "ciel-runtime-router-local-key",
|
|
156
|
+
"KIMI_MODEL_PROVIDER_TYPE": "openai",
|
|
157
|
+
"KIMI_MODEL_BASE_URL": f"{self.config.router_base.rstrip('/')}/v1",
|
|
158
|
+
"KIMI_MODEL_MAX_CONTEXT_SIZE": str(
|
|
159
|
+
self.config.positive_int(pcfg.get("context_window")) or 262144
|
|
160
|
+
),
|
|
161
|
+
"KIMI_MODEL_THINKING_EFFORT": str(
|
|
162
|
+
pcfg.get("effort_level") or "high"
|
|
163
|
+
),
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
return self.lifecycle.run_with_router(
|
|
167
|
+
lambda: self.process.call([executable, *passthrough], env=env),
|
|
168
|
+
manage_router,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(frozen=True, slots=True)
|
|
173
|
+
class KimiRuntimeCompatibilityApi:
|
|
174
|
+
context: Callable[[], KimiRuntimeContext]
|
|
175
|
+
|
|
176
|
+
def code_home(self) -> Path:
|
|
177
|
+
return self.context().code_home()
|
|
178
|
+
|
|
179
|
+
def oauth_token_record(self) -> dict[str, Any] | None:
|
|
180
|
+
return self.context().oauth_token_record()
|
|
181
|
+
|
|
182
|
+
def oauth_access_token(self) -> str | None:
|
|
183
|
+
return self.context().oauth_access_token()
|
|
184
|
+
|
|
185
|
+
def oauth_configured(self) -> bool:
|
|
186
|
+
return self.context().oauth_configured()
|
|
187
|
+
|
|
188
|
+
def install_if_missing(self) -> str:
|
|
189
|
+
return self.context().install_if_missing()
|
|
190
|
+
|
|
191
|
+
def oauth_login(self) -> int:
|
|
192
|
+
return self.context().oauth_login()
|
|
193
|
+
|
|
194
|
+
def oauth_action(self, action: str) -> list[str]:
|
|
195
|
+
return self.context().oauth_action(action)
|
|
196
|
+
|
|
197
|
+
def launch(self, passthrough: list[str]) -> int:
|
|
198
|
+
return self.context().launch(passthrough)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = [
|
|
202
|
+
"KimiConfigurationPorts",
|
|
203
|
+
"KimiIdentityPorts",
|
|
204
|
+
"KimiLifecyclePorts",
|
|
205
|
+
"KimiProcessPorts",
|
|
206
|
+
"KimiRuntimeCompatibilityApi",
|
|
207
|
+
"KimiRuntimeContext",
|
|
208
|
+
]
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""LLM preset identity, recommendation, and presentation bounded context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Callable, Mapping
|
|
8
|
+
|
|
9
|
+
from .architecture import ProviderContextPolicy
|
|
10
|
+
from . import llm_presets
|
|
11
|
+
from .llm_presets import PresetIdentityPolicy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class LlmPresetCatalog:
|
|
16
|
+
presets: Mapping[str, tuple[str, str]]
|
|
17
|
+
preset_i18n: Mapping[str, Mapping[str, tuple[str, str]]]
|
|
18
|
+
family_i18n: Mapping[str, Mapping[str, str]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class LlmPresetQueries:
|
|
23
|
+
load_config: Callable[[], dict[str, Any]]
|
|
24
|
+
context_policy: Callable[[str, dict[str, Any]], ProviderContextPolicy]
|
|
25
|
+
context_capacity: Callable[[str, dict[str, Any]], int | None]
|
|
26
|
+
context_services: Callable[[str], Any]
|
|
27
|
+
ui_text: Callable[[str, str | None], str]
|
|
28
|
+
pad_cells: Callable[[str, int], str]
|
|
29
|
+
format_context: Callable[[int | None], str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class LlmPresetAlgorithms:
|
|
34
|
+
classify_family: Callable[..., str]
|
|
35
|
+
recommend: Callable[[str, int | None], str]
|
|
36
|
+
infer: Callable[..., str | None]
|
|
37
|
+
required_context: Callable[[str, ProviderContextPolicy], int | None]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class LlmPresetDefinitionPorts:
|
|
42
|
+
context_heavy_presets: set[str] | frozenset[str]
|
|
43
|
+
llm_presets: Mapping[str, tuple[str, str]]
|
|
44
|
+
preset_text: Callable[..., tuple[str, str]]
|
|
45
|
+
load_config: Callable[[], dict[str, Any]]
|
|
46
|
+
family_text: Callable[..., str]
|
|
47
|
+
model_family: Callable[[str, dict[str, Any]], str]
|
|
48
|
+
positive_int: Callable[[Any], int | None]
|
|
49
|
+
required_context: Callable[[str, str | None], int | None]
|
|
50
|
+
ui_text: Callable[[str, str | None], str]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class LlmPresetContextPolicyPorts:
|
|
55
|
+
lm_studio_guard: Callable[..., list[str]]
|
|
56
|
+
ollama_output_guard: Callable[..., list[str]]
|
|
57
|
+
recommended_timeout: Callable[..., list[str]]
|
|
58
|
+
cap_context: Callable[..., list[str]]
|
|
59
|
+
cap_output: Callable[..., list[str]]
|
|
60
|
+
ollama_context_status: Callable[..., str]
|
|
61
|
+
model_capacity: Callable[..., int | None]
|
|
62
|
+
sync_ollama_context: Callable[..., list[str]]
|
|
63
|
+
upstream_context_limit: Callable[..., int | None]
|
|
64
|
+
timeout_tokens: Callable[..., list[str]]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True, slots=True)
|
|
68
|
+
class LlmPresetMutationPorts:
|
|
69
|
+
apply_ollama_option: Callable[..., list[str]]
|
|
70
|
+
apply_provider_option: Callable[..., list[str]]
|
|
71
|
+
ollama_options: Callable[[dict[str, Any]], dict[str, Any]]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True, slots=True)
|
|
75
|
+
class LlmPresetContext:
|
|
76
|
+
catalog: LlmPresetCatalog
|
|
77
|
+
queries: LlmPresetQueries
|
|
78
|
+
algorithms: LlmPresetAlgorithms
|
|
79
|
+
definition: LlmPresetDefinitionPorts
|
|
80
|
+
policy: LlmPresetContextPolicyPorts
|
|
81
|
+
mutation: LlmPresetMutationPorts
|
|
82
|
+
|
|
83
|
+
def model_family(self, provider: str, config: dict[str, Any]) -> str:
|
|
84
|
+
return self.algorithms.classify_family(
|
|
85
|
+
config,
|
|
86
|
+
self.queries.context_policy(provider, config),
|
|
87
|
+
self.queries.context_capacity(provider, config),
|
|
88
|
+
self.queries.context_services(provider),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def recommended(self, provider: str, config: dict[str, Any]) -> str:
|
|
92
|
+
return self.algorithms.recommend(
|
|
93
|
+
self.model_family(provider, config),
|
|
94
|
+
self.queries.context_capacity(provider, config),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def slider_ids(self) -> list[str]:
|
|
98
|
+
return list(self.catalog.presets)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def command_name(preset_id: str) -> str:
|
|
102
|
+
normalized = re.sub(
|
|
103
|
+
r"[^a-z0-9]+", "-", str(preset_id or "").lower()
|
|
104
|
+
).strip("-")
|
|
105
|
+
return f"llm-{normalized}"
|
|
106
|
+
|
|
107
|
+
def slash_command(self, preset_id: str) -> str:
|
|
108
|
+
label, description = self.text(preset_id, "en")
|
|
109
|
+
return f"""---
|
|
110
|
+
description: Apply ciel-runtime live preset: {label}
|
|
111
|
+
argument-hint: [ignored]
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
CIEL_RUNTIME_LIVE_LLM_OPTIONS
|
|
115
|
+
|
|
116
|
+
Value: {preset_id}
|
|
117
|
+
|
|
118
|
+
Apply the ciel-runtime live LLM preset `{preset_id}` ({description}) to this routed session. The original options are captured before the first live preset change and can be restored with `/llm-restore`.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def resolve(self, value: str) -> str | None:
|
|
122
|
+
return PresetIdentityPolicy(self.catalog.presets, self.command_name).resolve(value)
|
|
123
|
+
|
|
124
|
+
def required_context(
|
|
125
|
+
self, preset_id: str, provider: str | None = None
|
|
126
|
+
) -> int | None:
|
|
127
|
+
selected = provider or "anthropic"
|
|
128
|
+
return self.algorithms.required_context(
|
|
129
|
+
preset_id, self.queries.context_policy(selected, {})
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def available(
|
|
133
|
+
self, provider: str, config: dict[str, Any], preset_id: str
|
|
134
|
+
) -> bool:
|
|
135
|
+
required = self.required_context(preset_id, provider)
|
|
136
|
+
if not required:
|
|
137
|
+
return True
|
|
138
|
+
capacity = self.queries.context_capacity(provider, config)
|
|
139
|
+
return not capacity or required <= capacity
|
|
140
|
+
|
|
141
|
+
def infer(self, provider: str, config: dict[str, Any]) -> str | None:
|
|
142
|
+
return self.algorithms.infer(
|
|
143
|
+
config,
|
|
144
|
+
self.queries.context_policy(provider, config),
|
|
145
|
+
self.queries.context_services(provider),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def applied(self, provider: str, config: dict[str, Any]) -> str:
|
|
149
|
+
preset_id = str(config.get("llm_preset") or "").strip()
|
|
150
|
+
if preset_id in self.catalog.presets:
|
|
151
|
+
return preset_id
|
|
152
|
+
inferred = self.infer(provider, config)
|
|
153
|
+
if inferred and self.available(provider, config, inferred):
|
|
154
|
+
return inferred
|
|
155
|
+
recommended = self.recommended(provider, config)
|
|
156
|
+
return recommended if self.available(provider, config, recommended) else "balanced"
|
|
157
|
+
|
|
158
|
+
def text(self, preset_id: str, lang: str | None = None) -> tuple[str, str]:
|
|
159
|
+
language = lang or str(self.queries.load_config().get("language") or "en")
|
|
160
|
+
return self.catalog.preset_i18n.get(language, {}).get(
|
|
161
|
+
preset_id, self.catalog.presets[preset_id]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def family_text(self, family: str, lang: str | None = None) -> str:
|
|
165
|
+
language = lang or str(self.queries.load_config().get("language") or "en")
|
|
166
|
+
return self.catalog.family_i18n.get(language, {}).get(family, family)
|
|
167
|
+
|
|
168
|
+
def panel_rows(
|
|
169
|
+
self,
|
|
170
|
+
provider: str,
|
|
171
|
+
config: dict[str, Any],
|
|
172
|
+
lang: str | None = None,
|
|
173
|
+
) -> tuple[list[str], list[str]]:
|
|
174
|
+
language = lang or str(self.queries.load_config().get("language") or "en")
|
|
175
|
+
recommended = self.recommended(provider, config)
|
|
176
|
+
applied = self.applied(provider, config)
|
|
177
|
+
family = self.model_family(provider, config)
|
|
178
|
+
recommended_label, _ = self.text(recommended, language)
|
|
179
|
+
rows = [
|
|
180
|
+
f"{self.queries.ui_text('model_family', language)}: "
|
|
181
|
+
f"{self.family_text(family, language)}; "
|
|
182
|
+
f"{self.queries.ui_text('recommended_preset_is', language)} "
|
|
183
|
+
f"{recommended_label}"
|
|
184
|
+
]
|
|
185
|
+
values = ["__info__"]
|
|
186
|
+
for preset_id in self.catalog.presets:
|
|
187
|
+
label, description = self.text(preset_id, language)
|
|
188
|
+
mark = "*" if preset_id == applied else " "
|
|
189
|
+
suffix = ""
|
|
190
|
+
required = self.required_context(preset_id, provider)
|
|
191
|
+
capacity = (
|
|
192
|
+
self.queries.context_capacity(provider, config) if required else None
|
|
193
|
+
)
|
|
194
|
+
if required and capacity and required > capacity:
|
|
195
|
+
suffix = (
|
|
196
|
+
f" (requires {self.queries.format_context(required)}; "
|
|
197
|
+
f"server {self.queries.format_context(capacity)})"
|
|
198
|
+
)
|
|
199
|
+
rows.append(
|
|
200
|
+
f"{mark} {self.queries.pad_cells(label, 24)} {description}{suffix}"
|
|
201
|
+
)
|
|
202
|
+
values.append(preset_id)
|
|
203
|
+
rows.append(self.queries.ui_text("back", language))
|
|
204
|
+
values.append("back")
|
|
205
|
+
return rows, values
|
|
206
|
+
|
|
207
|
+
def apply(
|
|
208
|
+
self,
|
|
209
|
+
provider: str,
|
|
210
|
+
config: dict[str, Any],
|
|
211
|
+
preset_id: str,
|
|
212
|
+
lang: str | None = None,
|
|
213
|
+
*,
|
|
214
|
+
sync_ollama_context: bool = True,
|
|
215
|
+
load_lm_studio: bool = False,
|
|
216
|
+
) -> list[str]:
|
|
217
|
+
return llm_presets.apply_preset_to_provider(
|
|
218
|
+
provider,
|
|
219
|
+
config,
|
|
220
|
+
preset_id,
|
|
221
|
+
lang,
|
|
222
|
+
sync_ollama_context=sync_ollama_context,
|
|
223
|
+
load_lm_studio=load_lm_studio,
|
|
224
|
+
services=llm_presets.PresetServices(
|
|
225
|
+
definition=llm_presets.PresetDefinition(
|
|
226
|
+
CONTEXT_HEAVY_PRESETS=self.definition.context_heavy_presets,
|
|
227
|
+
LLM_PRESETS=self.definition.llm_presets,
|
|
228
|
+
llm_preset_text=self.definition.preset_text,
|
|
229
|
+
load_config=self.definition.load_config,
|
|
230
|
+
model_family_text=self.definition.family_text,
|
|
231
|
+
model_option_family=self.definition.model_family,
|
|
232
|
+
positive_int=self.definition.positive_int,
|
|
233
|
+
required_context_for_preset=self.definition.required_context,
|
|
234
|
+
ui_text=self.definition.ui_text,
|
|
235
|
+
),
|
|
236
|
+
context_policy=llm_presets.PresetContextPolicy(
|
|
237
|
+
apply_lm_studio_loaded_context_guard=self.policy.lm_studio_guard,
|
|
238
|
+
apply_ollama_runtime_output_guard=self.policy.ollama_output_guard,
|
|
239
|
+
apply_recommended_timeout_for_model_context=self.policy.recommended_timeout,
|
|
240
|
+
cap_context_settings_to_model_capacity=self.policy.cap_context,
|
|
241
|
+
cap_output_settings_to_context_ratio=self.policy.cap_output,
|
|
242
|
+
ollama_num_ctx_status=self.policy.ollama_context_status,
|
|
243
|
+
provider_model_context_capacity=self.policy.model_capacity,
|
|
244
|
+
sync_ollama_library_context_limit=self.policy.sync_ollama_context,
|
|
245
|
+
upstream_model_context_limit=self.policy.upstream_context_limit,
|
|
246
|
+
with_preset_timeout_tokens=self.policy.timeout_tokens,
|
|
247
|
+
),
|
|
248
|
+
provider_mutation=llm_presets.PresetProviderMutation(
|
|
249
|
+
apply_ollama_option=self.mutation.apply_ollama_option,
|
|
250
|
+
apply_provider_option=self.mutation.apply_provider_option,
|
|
251
|
+
ollama_extra_options=self.mutation.ollama_options,
|
|
252
|
+
),
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass(frozen=True, slots=True)
|
|
258
|
+
class LlmPresetCompatibilityApi:
|
|
259
|
+
context: Callable[[], LlmPresetContext]
|
|
260
|
+
|
|
261
|
+
def model_family(self, provider: str, config: dict[str, Any]) -> str:
|
|
262
|
+
return self.context().model_family(provider, config)
|
|
263
|
+
|
|
264
|
+
def recommended(self, provider: str, config: dict[str, Any]) -> str:
|
|
265
|
+
return self.context().recommended(provider, config)
|
|
266
|
+
|
|
267
|
+
def slider_ids(self) -> list[str]:
|
|
268
|
+
return self.context().slider_ids()
|
|
269
|
+
|
|
270
|
+
def command_name(self, preset_id: str) -> str:
|
|
271
|
+
return self.context().command_name(preset_id)
|
|
272
|
+
|
|
273
|
+
def slash_command(self, preset_id: str) -> str:
|
|
274
|
+
return self.context().slash_command(preset_id)
|
|
275
|
+
|
|
276
|
+
def resolve(self, value: str) -> str | None:
|
|
277
|
+
return self.context().resolve(value)
|
|
278
|
+
|
|
279
|
+
def required_context(
|
|
280
|
+
self, preset_id: str, provider: str | None = None
|
|
281
|
+
) -> int | None:
|
|
282
|
+
return self.context().required_context(preset_id, provider)
|
|
283
|
+
|
|
284
|
+
def available(
|
|
285
|
+
self, provider: str, config: dict[str, Any], preset_id: str
|
|
286
|
+
) -> bool:
|
|
287
|
+
return self.context().available(provider, config, preset_id)
|
|
288
|
+
|
|
289
|
+
def applied(self, provider: str, config: dict[str, Any]) -> str:
|
|
290
|
+
return self.context().applied(provider, config)
|
|
291
|
+
|
|
292
|
+
def infer(self, provider: str, config: dict[str, Any]) -> str | None:
|
|
293
|
+
return self.context().infer(provider, config)
|
|
294
|
+
|
|
295
|
+
def text(self, preset_id: str, lang: str | None = None) -> tuple[str, str]:
|
|
296
|
+
return self.context().text(preset_id, lang)
|
|
297
|
+
|
|
298
|
+
def family_text(self, family: str, lang: str | None = None) -> str:
|
|
299
|
+
return self.context().family_text(family, lang)
|
|
300
|
+
|
|
301
|
+
def panel_rows(
|
|
302
|
+
self,
|
|
303
|
+
provider: str,
|
|
304
|
+
config: dict[str, Any],
|
|
305
|
+
lang: str | None = None,
|
|
306
|
+
) -> tuple[list[str], list[str]]:
|
|
307
|
+
return self.context().panel_rows(provider, config, lang)
|
|
308
|
+
|
|
309
|
+
def apply(
|
|
310
|
+
self,
|
|
311
|
+
provider: str,
|
|
312
|
+
config: dict[str, Any],
|
|
313
|
+
preset_id: str,
|
|
314
|
+
lang: str | None = None,
|
|
315
|
+
*,
|
|
316
|
+
sync_ollama_context: bool = True,
|
|
317
|
+
load_lm_studio: bool = False,
|
|
318
|
+
) -> list[str]:
|
|
319
|
+
return self.context().apply(
|
|
320
|
+
provider,
|
|
321
|
+
config,
|
|
322
|
+
preset_id,
|
|
323
|
+
lang,
|
|
324
|
+
sync_ollama_context=sync_ollama_context,
|
|
325
|
+
load_lm_studio=load_lm_studio,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
__all__ = [
|
|
330
|
+
"LlmPresetAlgorithms",
|
|
331
|
+
"LlmPresetCatalog",
|
|
332
|
+
"LlmPresetCompatibilityApi",
|
|
333
|
+
"LlmPresetContext",
|
|
334
|
+
"LlmPresetContextPolicyPorts",
|
|
335
|
+
"LlmPresetDefinitionPorts",
|
|
336
|
+
"LlmPresetMutationPorts",
|
|
337
|
+
"LlmPresetQueries",
|
|
338
|
+
]
|
|
@@ -119,19 +119,23 @@ class ManagedMcpConfigService:
|
|
|
119
119
|
return self.paths.channel
|
|
120
120
|
|
|
121
121
|
def _fetch_command(self, web: dict[str, Any]) -> tuple[str | None, list[Any]]:
|
|
122
|
-
|
|
122
|
+
package = str(web.get("fetch_package") or "mcp-server-fetch")
|
|
123
|
+
arguments: list[Any] = [package]
|
|
123
124
|
if web.get("fetch_user_agent"):
|
|
124
125
|
arguments.extend(["--user-agent", str(web["fetch_user_agent"])])
|
|
125
126
|
if web.get("fetch_ignore_robots_txt", False):
|
|
126
127
|
arguments.append("--ignore-robots-txt")
|
|
128
|
+
dependency_args = ["--with", "mcp<2"] if package == "mcp-server-fetch" else []
|
|
127
129
|
command = self.ports.find_executable("uvx")
|
|
128
130
|
if command:
|
|
129
|
-
return command, arguments
|
|
131
|
+
return command, [*dependency_args, *arguments]
|
|
130
132
|
uv = self.ports.find_executable("uv")
|
|
131
133
|
if uv:
|
|
132
|
-
return uv, ["tool", "run", *arguments]
|
|
134
|
+
return uv, ["tool", "run", *dependency_args, *arguments]
|
|
133
135
|
if importlib.util.find_spec("uv") is not None:
|
|
134
|
-
return sys.executable, [
|
|
136
|
+
return sys.executable, [
|
|
137
|
+
"-m", "uv", "tool", "run", *dependency_args, *arguments
|
|
138
|
+
]
|
|
135
139
|
pipx = self.ports.find_executable("pipx")
|
|
136
140
|
return (pipx, ["run", *arguments]) if pipx else (None, arguments)
|
|
137
141
|
|