@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,312 @@
|
|
|
1
|
+
"""Runtime LLM option snapshot, restore, slider, and status controller."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class RuntimeLlmSettings:
|
|
13
|
+
option_keys: frozenset[str]
|
|
14
|
+
original_key: str
|
|
15
|
+
slider_labels: Mapping[str, str]
|
|
16
|
+
ollama_providers: frozenset[str] = frozenset(("ollama", "ollama-cloud"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class RuntimeLlmConfigPorts:
|
|
21
|
+
load: Callable[[], dict[str, Any]]
|
|
22
|
+
save: Callable[[dict[str, Any]], None]
|
|
23
|
+
clear_model_cache: Callable[[], None]
|
|
24
|
+
deep_copy: Callable[[Any], Any]
|
|
25
|
+
current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
|
|
26
|
+
normalize_preset: Callable[[str], str]
|
|
27
|
+
resolve_preset: Callable[[str], str | None]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class RuntimeLlmPresentationPorts:
|
|
32
|
+
applied_preset: Callable[[str, dict[str, Any]], str]
|
|
33
|
+
slider_presets: Callable[[], Sequence[str]]
|
|
34
|
+
preset_text: Callable[[str, str], tuple[str, str]]
|
|
35
|
+
provider_label: Callable[[str, dict[str, Any]], str]
|
|
36
|
+
context_status: Callable[[str, dict[str, Any]], str]
|
|
37
|
+
timeout_status: Callable[[dict[str, Any], str], str]
|
|
38
|
+
ollama_options: Callable[[dict[str, Any]], dict[str, Any]]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class RuntimeLlmMutationPorts:
|
|
43
|
+
apply_preset: Callable[..., list[str]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RuntimeLlmOptionsController:
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
settings: RuntimeLlmSettings,
|
|
50
|
+
config: RuntimeLlmConfigPorts,
|
|
51
|
+
presentation: RuntimeLlmPresentationPorts,
|
|
52
|
+
mutation: RuntimeLlmMutationPorts,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.settings = settings
|
|
55
|
+
self.config = config
|
|
56
|
+
self.presentation = presentation
|
|
57
|
+
self.mutation = mutation
|
|
58
|
+
|
|
59
|
+
def snapshot(self, provider: str, provider_config: dict[str, Any]) -> dict[str, Any]:
|
|
60
|
+
values = {
|
|
61
|
+
key: self.config.deep_copy(provider_config[key])
|
|
62
|
+
for key in sorted(self.settings.option_keys)
|
|
63
|
+
if key in provider_config
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
"version": 1,
|
|
67
|
+
"captured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
68
|
+
"provider": provider,
|
|
69
|
+
"model": str(provider_config.get("current_model") or ""),
|
|
70
|
+
"values": values,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def ensure_snapshot(self, provider: str, provider_config: dict[str, Any]) -> bool:
|
|
74
|
+
existing = provider_config.get(self.settings.original_key)
|
|
75
|
+
if isinstance(existing, dict) and isinstance(existing.get("values"), dict):
|
|
76
|
+
return False
|
|
77
|
+
provider_config[self.settings.original_key] = self.snapshot(
|
|
78
|
+
provider,
|
|
79
|
+
provider_config,
|
|
80
|
+
)
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
def restore(self, provider: str) -> list[str]:
|
|
84
|
+
config = self.config.load()
|
|
85
|
+
provider_config = config["providers"][provider]
|
|
86
|
+
snapshot = provider_config.get(self.settings.original_key)
|
|
87
|
+
if not isinstance(snapshot, dict) or not isinstance(snapshot.get("values"), dict):
|
|
88
|
+
return ["No captured live LLM options to restore."]
|
|
89
|
+
values = self.config.deep_copy(snapshot.get("values") or {})
|
|
90
|
+
for key in self.settings.option_keys:
|
|
91
|
+
provider_config.pop(key, None)
|
|
92
|
+
provider_config.update(values)
|
|
93
|
+
provider_config.pop(self.settings.original_key, None)
|
|
94
|
+
self._persist(config)
|
|
95
|
+
return [
|
|
96
|
+
"Restored live LLM options to the values captured before the first runtime preset change.",
|
|
97
|
+
f"Captured provider/model: {snapshot.get('provider') or provider} / "
|
|
98
|
+
f"{snapshot.get('model') or 'unknown'}",
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def apply_preset(self, provider: str, preset_id: str) -> list[str]:
|
|
102
|
+
config = self.config.load()
|
|
103
|
+
provider_config = config["providers"][provider]
|
|
104
|
+
captured = self.ensure_snapshot(provider, provider_config)
|
|
105
|
+
lines = self.mutation.apply_preset(
|
|
106
|
+
provider,
|
|
107
|
+
provider_config,
|
|
108
|
+
preset_id,
|
|
109
|
+
config.get("language", "en"),
|
|
110
|
+
)
|
|
111
|
+
if captured:
|
|
112
|
+
lines.insert(0, "Captured current live LLM options for /llm-restore.")
|
|
113
|
+
self._persist(config)
|
|
114
|
+
return lines
|
|
115
|
+
|
|
116
|
+
def slider_line(self, provider: str, provider_config: dict[str, Any]) -> str:
|
|
117
|
+
current = self.presentation.applied_preset(provider, provider_config)
|
|
118
|
+
parts = []
|
|
119
|
+
for preset_id in self.presentation.slider_presets():
|
|
120
|
+
label = self.settings.slider_labels.get(preset_id, preset_id)
|
|
121
|
+
parts.append(f"[{label}]" if preset_id == current else label)
|
|
122
|
+
return "< " + " | ".join(parts) + " >"
|
|
123
|
+
|
|
124
|
+
def apply_slider_delta(self, provider: str, delta: int) -> list[str]:
|
|
125
|
+
config = self.config.load()
|
|
126
|
+
provider_config = config["providers"][provider]
|
|
127
|
+
presets = list(self.presentation.slider_presets())
|
|
128
|
+
current = self.presentation.applied_preset(provider, provider_config)
|
|
129
|
+
try:
|
|
130
|
+
current_index = presets.index(current)
|
|
131
|
+
except ValueError:
|
|
132
|
+
current_index = 0
|
|
133
|
+
next_index = max(0, min(len(presets) - 1, current_index + delta))
|
|
134
|
+
next_preset = presets[next_index]
|
|
135
|
+
language = config.get("language", "en")
|
|
136
|
+
if next_index == current_index:
|
|
137
|
+
label = self.presentation.preset_text(next_preset, language)[0]
|
|
138
|
+
return [
|
|
139
|
+
f"Live LLM preset remains at {label}.",
|
|
140
|
+
f"Slider: {self.slider_line(provider, provider_config)}",
|
|
141
|
+
]
|
|
142
|
+
captured = self.ensure_snapshot(provider, provider_config)
|
|
143
|
+
lines = self.mutation.apply_preset(
|
|
144
|
+
provider,
|
|
145
|
+
provider_config,
|
|
146
|
+
next_preset,
|
|
147
|
+
language,
|
|
148
|
+
)
|
|
149
|
+
if captured:
|
|
150
|
+
lines.insert(0, "Captured current live LLM options for /llm-restore.")
|
|
151
|
+
self._persist(config)
|
|
152
|
+
label = self.presentation.preset_text(next_preset, language)[0]
|
|
153
|
+
return [f"Live LLM preset moved to {label}."] + lines + [
|
|
154
|
+
f"Slider: {self.slider_line(provider, provider_config)}"
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
def status_lines(
|
|
158
|
+
self,
|
|
159
|
+
provider: str,
|
|
160
|
+
provider_config: dict[str, Any],
|
|
161
|
+
) -> list[str]:
|
|
162
|
+
language = self.config.load().get("language", "en")
|
|
163
|
+
applied = self.presentation.applied_preset(provider, provider_config)
|
|
164
|
+
lines = [
|
|
165
|
+
f"Provider: {self.presentation.provider_label(provider, provider_config)}",
|
|
166
|
+
f"Model: {provider_config.get('current_model') or 'unknown'}",
|
|
167
|
+
f"Preset: {applied} ({self.presentation.preset_text(applied, language)[0]})",
|
|
168
|
+
f"Slider: {self.slider_line(provider, provider_config)}",
|
|
169
|
+
f"Context: {self.presentation.context_status(provider, provider_config)}",
|
|
170
|
+
f"Timeout: {self.presentation.timeout_status(provider_config, language)}",
|
|
171
|
+
]
|
|
172
|
+
if provider in self.settings.ollama_providers:
|
|
173
|
+
options = self.presentation.ollama_options(provider_config)
|
|
174
|
+
lines.append(f"Output tokens: {options.get('num_predict', 'default')}")
|
|
175
|
+
else:
|
|
176
|
+
lines.append(
|
|
177
|
+
f"Output tokens: {provider_config.get('max_output_tokens', 'default')}"
|
|
178
|
+
)
|
|
179
|
+
restore_available = isinstance(
|
|
180
|
+
provider_config.get(self.settings.original_key),
|
|
181
|
+
dict,
|
|
182
|
+
)
|
|
183
|
+
lines.append(f"Restore available: {'yes' if restore_available else 'no'}")
|
|
184
|
+
return lines
|
|
185
|
+
|
|
186
|
+
def preset_list_lines(
|
|
187
|
+
self,
|
|
188
|
+
provider: str,
|
|
189
|
+
provider_config: dict[str, Any],
|
|
190
|
+
) -> list[str]:
|
|
191
|
+
language = self.config.load().get("language", "en")
|
|
192
|
+
applied = self.presentation.applied_preset(provider, provider_config)
|
|
193
|
+
lines = self.status_lines(provider, provider_config)
|
|
194
|
+
lines.extend(
|
|
195
|
+
(
|
|
196
|
+
"",
|
|
197
|
+
"Use `/llm left` or `/llm right` to move one step, or `/llm <preset-id>` to jump directly.",
|
|
198
|
+
"Preset ids:",
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
for preset_id in self.presentation.slider_presets():
|
|
202
|
+
label, description = self.presentation.preset_text(preset_id, language)
|
|
203
|
+
mark = "*" if preset_id == applied else " "
|
|
204
|
+
lines.append(f"{mark} {preset_id} — {label}: {description}")
|
|
205
|
+
lines.extend(
|
|
206
|
+
(
|
|
207
|
+
" /llm-restore restore captured original options",
|
|
208
|
+
" /llm <left|right|preset-id|status|list|restore>",
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
return lines
|
|
212
|
+
|
|
213
|
+
def handle_action(
|
|
214
|
+
self,
|
|
215
|
+
action: str = "status",
|
|
216
|
+
preset: str = "",
|
|
217
|
+
) -> tuple[list[str], bool]:
|
|
218
|
+
config = self.config.load()
|
|
219
|
+
provider, provider_config = self.config.current_provider(config)
|
|
220
|
+
raw_action = str(action or "").strip()
|
|
221
|
+
raw_preset = str(preset or "").strip()
|
|
222
|
+
if not raw_action and raw_preset:
|
|
223
|
+
raw_action = "apply"
|
|
224
|
+
value = raw_preset or raw_action
|
|
225
|
+
normalized = self.config.normalize_preset(value)
|
|
226
|
+
if normalized in {"", "status", "state", "show", "current", "now"}:
|
|
227
|
+
return self.status_lines(provider, provider_config), False
|
|
228
|
+
if normalized in {
|
|
229
|
+
"list", "presets", "preset", "help", "options", "menu", "select"
|
|
230
|
+
}:
|
|
231
|
+
return self.preset_list_lines(provider, provider_config), False
|
|
232
|
+
if normalized in {
|
|
233
|
+
"left", "prev", "previous", "backward", "back", "decrease", "minus"
|
|
234
|
+
}:
|
|
235
|
+
return self._slider_action(provider, -1)
|
|
236
|
+
if normalized in {"right", "next", "forward", "increase", "plus"}:
|
|
237
|
+
return self._slider_action(provider, 1)
|
|
238
|
+
if normalized in {"restore", "original", "reset", "revert", "undo"}:
|
|
239
|
+
had_snapshot = isinstance(
|
|
240
|
+
provider_config.get(self.settings.original_key), dict
|
|
241
|
+
)
|
|
242
|
+
lines = self.restore(provider)
|
|
243
|
+
config_after = self.config.load()
|
|
244
|
+
_provider_after, provider_config_after = self.config.current_provider(
|
|
245
|
+
config_after
|
|
246
|
+
)
|
|
247
|
+
return lines + [""] + self.status_lines(
|
|
248
|
+
provider, provider_config_after
|
|
249
|
+
), had_snapshot
|
|
250
|
+
preset_id = self.config.resolve_preset(value)
|
|
251
|
+
if not preset_id:
|
|
252
|
+
return [
|
|
253
|
+
f"Unknown live LLM preset/action: {value or raw_action or raw_preset}",
|
|
254
|
+
"Use `/llm-options list` to see available presets, or `/llm-restore` to revert.",
|
|
255
|
+
], False
|
|
256
|
+
lines = self.apply_preset(provider, preset_id)
|
|
257
|
+
return self._updated_lines(provider, lines), True
|
|
258
|
+
|
|
259
|
+
def _slider_action(self, provider: str, delta: int) -> tuple[list[str], bool]:
|
|
260
|
+
return self._updated_lines(
|
|
261
|
+
provider,
|
|
262
|
+
self.apply_slider_delta(provider, delta),
|
|
263
|
+
), True
|
|
264
|
+
|
|
265
|
+
def _updated_lines(self, provider: str, lines: list[str]) -> list[str]:
|
|
266
|
+
config_after = self.config.load()
|
|
267
|
+
_provider_after, provider_config_after = self.config.current_provider(
|
|
268
|
+
config_after
|
|
269
|
+
)
|
|
270
|
+
return (
|
|
271
|
+
lines
|
|
272
|
+
+ ["", "Updated live LLM options. The next model request uses these settings."]
|
|
273
|
+
+ self.status_lines(provider, provider_config_after)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def _persist(self, config: dict[str, Any]) -> None:
|
|
277
|
+
self.config.save(config)
|
|
278
|
+
self.config.clear_model_cache()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@dataclass(frozen=True, slots=True)
|
|
282
|
+
class RuntimeLlmOptionsApi:
|
|
283
|
+
"""Explicit public adapter for a late-bound runtime options controller."""
|
|
284
|
+
|
|
285
|
+
controller_factory: Callable[[], RuntimeLlmOptionsController]
|
|
286
|
+
|
|
287
|
+
def handle_live_llm_options_action(self, action: str = "status", preset: str = "") -> tuple[list[str], bool]:
|
|
288
|
+
return self.controller_factory().handle_action(action, preset)
|
|
289
|
+
|
|
290
|
+
def snapshot_from_provider(self, provider: str, pcfg: dict[str, Any]) -> dict[str, Any]:
|
|
291
|
+
return self.controller_factory().snapshot(provider, pcfg)
|
|
292
|
+
|
|
293
|
+
def ensure_original_snapshot(self, provider: str, pcfg: dict[str, Any]) -> bool:
|
|
294
|
+
return self.controller_factory().ensure_snapshot(provider, pcfg)
|
|
295
|
+
|
|
296
|
+
def restore_original_options(self, provider: str) -> list[str]:
|
|
297
|
+
return self.controller_factory().restore(provider)
|
|
298
|
+
|
|
299
|
+
def apply_preset_config(self, provider: str, preset_id: str) -> list[str]:
|
|
300
|
+
return self.controller_factory().apply_preset(provider, preset_id)
|
|
301
|
+
|
|
302
|
+
def slider_line(self, provider: str, pcfg: dict[str, Any]) -> str:
|
|
303
|
+
return self.controller_factory().slider_line(provider, pcfg)
|
|
304
|
+
|
|
305
|
+
def apply_slider_delta_config(self, provider: str, delta: int) -> list[str]:
|
|
306
|
+
return self.controller_factory().apply_slider_delta(provider, delta)
|
|
307
|
+
|
|
308
|
+
def status_lines(self, provider: str, pcfg: dict[str, Any]) -> list[str]:
|
|
309
|
+
return self.controller_factory().status_lines(provider, pcfg)
|
|
310
|
+
|
|
311
|
+
def preset_list_lines(self, provider: str, pcfg: dict[str, Any]) -> list[str]:
|
|
312
|
+
return self.controller_factory().preset_list_lines(provider, pcfg)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Runtime log-level repository and rotating file logger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
|
|
14
|
+
LOG_LEVEL_NAMES = {value: name for name, value in LOG_LEVELS.items()}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def normalize_log_level(value: str) -> str | None:
|
|
18
|
+
raw = str(value or "").strip()
|
|
19
|
+
if not raw:
|
|
20
|
+
raise ValueError("log level is empty")
|
|
21
|
+
upper = {
|
|
22
|
+
"OFF": "SILENT",
|
|
23
|
+
"NONE": "SILENT",
|
|
24
|
+
"QUIET": "SILENT",
|
|
25
|
+
"WARNING": "WARN",
|
|
26
|
+
"WARNINGS": "WARN",
|
|
27
|
+
}.get(raw.upper(), raw.upper())
|
|
28
|
+
if upper in ("DEFAULT", "RESET", "UNSET", "AUTO"):
|
|
29
|
+
return None
|
|
30
|
+
if upper in LOG_LEVELS:
|
|
31
|
+
return upper
|
|
32
|
+
if upper.isdigit():
|
|
33
|
+
return LOG_LEVEL_NAMES[max(0, min(5, int(upper)))]
|
|
34
|
+
raise ValueError(f"unknown log level: {value}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class LogLevelRepository:
|
|
39
|
+
config_dir: Path
|
|
40
|
+
path: Path
|
|
41
|
+
cache: dict[str, Any]
|
|
42
|
+
default_level: int
|
|
43
|
+
environ: dict[str, str]
|
|
44
|
+
|
|
45
|
+
def current(self) -> int:
|
|
46
|
+
now = time.time()
|
|
47
|
+
if self.cache["value"] is not None and now - float(self.cache["checked_at"]) < 1.0:
|
|
48
|
+
return int(self.cache["value"])
|
|
49
|
+
level = self._file_level(now)
|
|
50
|
+
if level is None:
|
|
51
|
+
value = self.environ.get("CIEL_RUNTIME_LOG_LEVEL", "").strip().upper()
|
|
52
|
+
if value in LOG_LEVELS:
|
|
53
|
+
level = LOG_LEVELS[value]
|
|
54
|
+
elif value.isdigit():
|
|
55
|
+
level = max(0, min(5, int(value)))
|
|
56
|
+
if level is None:
|
|
57
|
+
level = self.default_level
|
|
58
|
+
self.cache.update(value=level, checked_at=now)
|
|
59
|
+
return level
|
|
60
|
+
|
|
61
|
+
def _file_level(self, now: float) -> int | None:
|
|
62
|
+
try:
|
|
63
|
+
if not self.path.exists():
|
|
64
|
+
return None
|
|
65
|
+
modified = self.path.stat().st_mtime
|
|
66
|
+
if self.cache["value"] is not None and modified == self.cache["file_mtime"]:
|
|
67
|
+
self.cache["checked_at"] = now
|
|
68
|
+
return int(self.cache["value"])
|
|
69
|
+
text = self.path.read_text(encoding="utf-8").strip().upper()
|
|
70
|
+
self.cache["file_mtime"] = modified
|
|
71
|
+
if text in LOG_LEVELS:
|
|
72
|
+
return LOG_LEVELS[text]
|
|
73
|
+
if text.isdigit():
|
|
74
|
+
return max(0, min(5, int(text)))
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def reset_cache(self) -> None:
|
|
80
|
+
self.cache.update(value=None, checked_at=0.0, file_mtime=0.0)
|
|
81
|
+
|
|
82
|
+
def name(self, value: int | None = None) -> str:
|
|
83
|
+
effective = self.current() if value is None else value
|
|
84
|
+
return str(LOG_LEVEL_NAMES.get(int(effective), effective))
|
|
85
|
+
|
|
86
|
+
def source(self) -> str:
|
|
87
|
+
if self.path.exists():
|
|
88
|
+
return "file"
|
|
89
|
+
if self.environ.get("CIEL_RUNTIME_LOG_LEVEL", "").strip():
|
|
90
|
+
return "env"
|
|
91
|
+
return "default"
|
|
92
|
+
|
|
93
|
+
def status(self) -> str:
|
|
94
|
+
return f"{self.name()} ({self.source()})"
|
|
95
|
+
|
|
96
|
+
def set(self, value: str) -> list[str]:
|
|
97
|
+
try:
|
|
98
|
+
level = normalize_log_level(value)
|
|
99
|
+
except ValueError as error:
|
|
100
|
+
return [f"{error}. Known levels: {', '.join(LOG_LEVELS)}, DEFAULT."]
|
|
101
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
if level is None:
|
|
103
|
+
try:
|
|
104
|
+
self.path.unlink()
|
|
105
|
+
except FileNotFoundError:
|
|
106
|
+
pass
|
|
107
|
+
self.reset_cache()
|
|
108
|
+
return [f"Log level reset to {self.status()}."]
|
|
109
|
+
self.path.write_text(level + "\n", encoding="utf-8")
|
|
110
|
+
try:
|
|
111
|
+
os.chmod(self.path, 0o600)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
self.reset_cache()
|
|
115
|
+
return [f"Log level set to {level}."]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True, slots=True)
|
|
119
|
+
class LogLevelApi:
|
|
120
|
+
"""Explicit compatibility adapter for a late-bound log repository."""
|
|
121
|
+
|
|
122
|
+
repository_factory: Callable[[], LogLevelRepository]
|
|
123
|
+
|
|
124
|
+
def current(self) -> int:
|
|
125
|
+
return self.repository_factory().current()
|
|
126
|
+
|
|
127
|
+
def reset_cache(self) -> None:
|
|
128
|
+
self.repository_factory().reset_cache()
|
|
129
|
+
|
|
130
|
+
def name(self, value: int | None = None) -> str:
|
|
131
|
+
return self.repository_factory().name(value)
|
|
132
|
+
|
|
133
|
+
def source(self) -> str:
|
|
134
|
+
return self.repository_factory().source()
|
|
135
|
+
|
|
136
|
+
def status(self) -> str:
|
|
137
|
+
return self.repository_factory().status()
|
|
138
|
+
|
|
139
|
+
def set(self, value: str) -> list[str]:
|
|
140
|
+
return self.repository_factory().set(value)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True, slots=True)
|
|
144
|
+
class RouterFileLogger:
|
|
145
|
+
config_dir: Path
|
|
146
|
+
path: Path
|
|
147
|
+
max_bytes: int
|
|
148
|
+
levels: LogLevelRepository
|
|
149
|
+
|
|
150
|
+
def write(self, level: str, message: str) -> None:
|
|
151
|
+
threshold = LOG_LEVELS.get(level, 0)
|
|
152
|
+
if threshold <= 0 or threshold > self.levels.current():
|
|
153
|
+
return
|
|
154
|
+
try:
|
|
155
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
if self.path.exists() and self.path.stat().st_size > self.max_bytes:
|
|
157
|
+
self.path.replace(self.path.with_suffix(".log.1"))
|
|
158
|
+
with self.path.open("a", encoding="utf-8") as handle:
|
|
159
|
+
handle.write(f"{time.strftime('%Y-%m-%dT%H:%M:%S')} [{level}] {message}\n")
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Platform-aware filesystem and local router endpoint configuration."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import getpass
|
|
5
|
+
import hashlib
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path, PureWindowsPath
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .web_endpoints import apply_startup_web_options, load_saved_web_backend
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Web startup flags affect the process-wide router endpoint constants below.
|
|
15
|
+
sys.argv[:] = apply_startup_web_options(sys.argv, os.environ)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
HOME = Path.home()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def platform_path(value: str | os.PathLike[str]) -> Any:
|
|
22
|
+
if os.name == "nt" and sys.platform != "win32":
|
|
23
|
+
return PureWindowsPath(value)
|
|
24
|
+
return Path(value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def windows_appdata_root() -> Path:
|
|
28
|
+
for env_name in ("APPDATA", "LOCALAPPDATA"):
|
|
29
|
+
raw = os.environ.get(env_name)
|
|
30
|
+
if raw:
|
|
31
|
+
return platform_path(raw)
|
|
32
|
+
return HOME / "AppData" / "Roaming"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def windows_local_appdata_root() -> Path:
|
|
36
|
+
raw = os.environ.get("LOCALAPPDATA")
|
|
37
|
+
if raw:
|
|
38
|
+
return platform_path(raw)
|
|
39
|
+
return HOME / "AppData" / "Local"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def platform_config_dir(app_name: str) -> Path:
|
|
43
|
+
if os.name == "nt":
|
|
44
|
+
return windows_appdata_root() / app_name
|
|
45
|
+
return HOME / ".config" / app_name
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ciel_runtime_user_bin_dir() -> Path:
|
|
49
|
+
if os.name == "nt":
|
|
50
|
+
return windows_local_appdata_root() / "ciel-runtime" / "bin"
|
|
51
|
+
return HOME / ".local" / "bin"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def agy_user_bin_dir() -> Path:
|
|
55
|
+
if os.name == "nt":
|
|
56
|
+
return windows_local_appdata_root() / "agy" / "bin"
|
|
57
|
+
return HOME / ".local" / "bin"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def path_with_ciel_runtime_user_dirs(env: dict[str, str]) -> str:
|
|
61
|
+
dirs = [ciel_runtime_user_bin_dir(), agy_user_bin_dir()]
|
|
62
|
+
if os.name == "nt":
|
|
63
|
+
appdata = env.get("APPDATA") or os.environ.get("APPDATA")
|
|
64
|
+
if appdata:
|
|
65
|
+
dirs.append(platform_path(appdata) / "npm")
|
|
66
|
+
local_appdata = env.get("LOCALAPPDATA") or os.environ.get("LOCALAPPDATA")
|
|
67
|
+
if local_appdata:
|
|
68
|
+
dirs.append(platform_path(local_appdata) / "Programs" / "nodejs")
|
|
69
|
+
existing = env.get("PATH", "")
|
|
70
|
+
prefix = os.pathsep.join(str(path) for path in dirs if str(path))
|
|
71
|
+
return prefix + (os.pathsep + existing if existing else "")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def default_router_port(saved_port: int = 0) -> int:
|
|
75
|
+
configured = str(os.environ.get("CIEL_RUNTIME_ROUTER_PORT") or "").strip()
|
|
76
|
+
if configured:
|
|
77
|
+
try:
|
|
78
|
+
port = int(configured)
|
|
79
|
+
if 1 <= port <= 65535:
|
|
80
|
+
return port
|
|
81
|
+
except ValueError:
|
|
82
|
+
pass
|
|
83
|
+
if 1 <= saved_port <= 65535:
|
|
84
|
+
return saved_port
|
|
85
|
+
base = 8799
|
|
86
|
+
getuid = getattr(os, "getuid", None)
|
|
87
|
+
if callable(getuid):
|
|
88
|
+
try:
|
|
89
|
+
return base + (int(getuid()) % 1000)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
seed = f"{getpass.getuser()}|{HOME}"
|
|
93
|
+
digest = hashlib.sha256(seed.encode("utf-8", errors="replace")).hexdigest()
|
|
94
|
+
return base + (int(digest[:8], 16) % 1000)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
CONFIG_DIR = Path(os.environ.get("CIEL_RUNTIME_CONFIG_DIR") or platform_config_dir("ciel-runtime"))
|
|
98
|
+
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
99
|
+
LOG_PATH = CONFIG_DIR / "router.log"
|
|
100
|
+
LOG_LEVEL_PATH = CONFIG_DIR / "log-level"
|
|
101
|
+
REQUEST_DUMP_PATH = CONFIG_DIR / "requests.jsonl"
|
|
102
|
+
RESPONSE_DUMP_PATH = CONFIG_DIR / "responses.jsonl"
|
|
103
|
+
USAGE_EVENTS_PATH = CONFIG_DIR / "usage-events.jsonl"
|
|
104
|
+
SSE_TRACE_PATH = CONFIG_DIR / "router-sse-trace.jsonl"
|
|
105
|
+
SSE_LAST_PATH = CONFIG_DIR / "router-last-sse.json"
|
|
106
|
+
TOOL_CALL_LOG_PATH = CONFIG_DIR / "tool-calls.jsonl"
|
|
107
|
+
RATE_LIMIT_STATE_PATH = CONFIG_DIR / "rate-limit-state.json"
|
|
108
|
+
ROUTER_ACTIVITY_PATH = CONFIG_DIR / "router-activity.json"
|
|
109
|
+
CONTEXT_COMPACT_ACTIVITY_PATH = CONFIG_DIR / "context-compact-activity.json"
|
|
110
|
+
CONTEXT_USAGE_PATH = CONFIG_DIR / "context-usage.json"
|
|
111
|
+
OLLAMA_MODEL_CATALOG_PATH = CONFIG_DIR / "ollama-model-catalog.json"
|
|
112
|
+
CHAT_MESSAGES_PATH = CONFIG_DIR / "chat-messages.jsonl"
|
|
113
|
+
CHAT_FILES_DIR = CONFIG_DIR / "chat-files"
|
|
114
|
+
MENU_KEY_DEBUG_PATH = CONFIG_DIR / "ca-key-debug.log"
|
|
115
|
+
PLAN_ARTIFACTS_DIR = CONFIG_DIR / "plan-artifacts"
|
|
116
|
+
PID_PATH = CONFIG_DIR / "router.pid"
|
|
117
|
+
ROUTER_EXTERNAL_TOKEN_PATH = CONFIG_DIR / "router-external-token"
|
|
118
|
+
ROUTER_CLIENTS_DIR = CONFIG_DIR / "router-clients"
|
|
119
|
+
MODEL_LIST_CACHE_PATH = CONFIG_DIR / "model-list-cache.json"
|
|
120
|
+
MODEL_REGISTRY_PATH = CONFIG_DIR / "model-registry.json"
|
|
121
|
+
LAUNCH_STATE_PATH = CONFIG_DIR / "launch-state.json"
|
|
122
|
+
WEB_TOOLS_MCP_CONFIG = CONFIG_DIR / "web-tools-mcp.json"
|
|
123
|
+
DUCKDUCKGO_MCP_CONFIG = CONFIG_DIR / "duckduckgo-mcp.json"
|
|
124
|
+
ZAI_MCP_CONFIG = CONFIG_DIR / "zai-mcp.json"
|
|
125
|
+
CHANNEL_MCP_CONFIG = CONFIG_DIR / "channel-mcp.json"
|
|
126
|
+
NATIVE_MCP_CONFIG = CONFIG_DIR / "native-mcp.json"
|
|
127
|
+
CODEX_MCP_CONFIG = CONFIG_DIR / "codex-mcp.json"
|
|
128
|
+
CODEX_PROCESS_DIR = CONFIG_DIR / "codex-processes"
|
|
129
|
+
CODEX_PROMPTS_DIR_NAME = "prompts"
|
|
130
|
+
CHANNEL_MCP_CURSOR_PATH = CONFIG_DIR / "channel-mcp-cursor.json"
|
|
131
|
+
CHANNEL_LLM_CURSOR_PATH = CONFIG_DIR / "channel-llm-cursor.json"
|
|
132
|
+
CHANNEL_LLM_CLEAR_FLOOR_PATH = CONFIG_DIR / "channel-llm-clear-floor.json"
|
|
133
|
+
CHANNEL_LLM_LAUNCH_GUARD_PATH = CONFIG_DIR / "channel-llm-launch-guard.json"
|
|
134
|
+
CHANNEL_COMPACT_REQUEST_PATH = CONFIG_DIR / "channel-compact-request.json"
|
|
135
|
+
CHANNEL_STDIN_WAKE_CLAIMS_PATH = CONFIG_DIR / "channel-stdin-wake-claims.json"
|
|
136
|
+
CHANNEL_PROBE_CACHE_PATH = CONFIG_DIR / "channel-probe-cache.json"
|
|
137
|
+
MCP_PROXY_CONFIG = CONFIG_DIR / "mcp-proxy.json"
|
|
138
|
+
_SAVED_WEB_BACKEND = load_saved_web_backend(CONFIG_PATH)
|
|
139
|
+
ROUTER_HOST = (
|
|
140
|
+
os.environ.get("CIEL_RUNTIME_ROUTER_CLIENT_HOST", "").strip()
|
|
141
|
+
or _SAVED_WEB_BACKEND.client_host
|
|
142
|
+
)
|
|
143
|
+
ROUTER_PORT = default_router_port(_SAVED_WEB_BACKEND.port)
|
|
144
|
+
ROUTER_BASE = f"http://{ROUTER_HOST}:{ROUTER_PORT}"
|
|
145
|
+
CLAUDE_GATEWAY_CACHE = HOME / ".claude" / "cache" / "gateway-models.json"
|
|
146
|
+
CLAUDE_SETTINGS_PATH = HOME / ".claude" / "settings.json"
|
|
147
|
+
CLAUDE_COMMANDS_DIR = HOME / ".claude" / "commands"
|
|
148
|
+
CIEL_RUNTIME_STATUSLINE_PATH = ciel_runtime_user_bin_dir() / "ciel-runtime-statusline.py"
|
|
149
|
+
NCP_ENV = platform_config_dir("nvd-claude-proxy") / ".env"
|
|
150
|
+
NCP_LOG = platform_config_dir("nvd-claude-proxy") / "proxy.log"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
__all__ = [name for name in globals() if name.isupper() or name in {
|
|
154
|
+
"agy_user_bin_dir", "ciel_runtime_user_bin_dir", "default_router_port",
|
|
155
|
+
"path_with_ciel_runtime_user_dirs", "platform_config_dir", "platform_path",
|
|
156
|
+
"windows_appdata_root", "windows_local_appdata_root",
|
|
157
|
+
}]
|