@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,89 @@
|
|
|
1
|
+
"""Provider request output-token budgeting policy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class OpenAIContextBudgetPolicy:
|
|
12
|
+
"""Resolve context capacity for providers using OpenAI-compatible payloads."""
|
|
13
|
+
|
|
14
|
+
positive_int: Callable[[Any], int | None]
|
|
15
|
+
runtime_capacity: Callable[[str, dict[str, Any]], int]
|
|
16
|
+
nvidia_default: Callable[[str], int]
|
|
17
|
+
|
|
18
|
+
def context_limit(self, provider: str, config: dict[str, Any]) -> int:
|
|
19
|
+
configured = self.positive_int(config.get("context_window")) or self.positive_int(
|
|
20
|
+
config.get("max_model_len")
|
|
21
|
+
)
|
|
22
|
+
if provider in ("vllm", "self-hosted-nim"):
|
|
23
|
+
runtime = self.runtime_capacity(provider, config)
|
|
24
|
+
if configured and str(config.get("llm_preset") or "").strip():
|
|
25
|
+
return min(configured, runtime) if runtime else configured
|
|
26
|
+
if runtime:
|
|
27
|
+
return runtime
|
|
28
|
+
if configured:
|
|
29
|
+
return configured
|
|
30
|
+
if provider == "nvidia-hosted":
|
|
31
|
+
return self.nvidia_default(str(config.get("current_model") or ""))
|
|
32
|
+
return 65536
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class OutputBudgetPolicy:
|
|
37
|
+
positive_int: Callable[[Any], int | None]
|
|
38
|
+
estimate_tokens: Callable[[Any, dict[int, int] | None], int]
|
|
39
|
+
provider_options: Callable[[dict[str, Any]], dict[str, Any]]
|
|
40
|
+
|
|
41
|
+
def configured_tokens(
|
|
42
|
+
self,
|
|
43
|
+
config: dict[str, Any],
|
|
44
|
+
body: dict[str, Any],
|
|
45
|
+
option_key: str | None = None,
|
|
46
|
+
) -> int | None:
|
|
47
|
+
configured = self.positive_int(config.get("max_output_tokens"))
|
|
48
|
+
if option_key:
|
|
49
|
+
configured = (
|
|
50
|
+
self.positive_int(self.provider_options(config).get(option_key))
|
|
51
|
+
or configured
|
|
52
|
+
)
|
|
53
|
+
requested = self.positive_int(body.get("max_tokens"))
|
|
54
|
+
if configured and requested:
|
|
55
|
+
return min(configured, requested)
|
|
56
|
+
return configured or requested
|
|
57
|
+
|
|
58
|
+
def reserve_tokens(
|
|
59
|
+
self, config: dict[str, Any], context_limit: int | None
|
|
60
|
+
) -> int:
|
|
61
|
+
configured = self.positive_int(config.get("context_reserve_tokens"))
|
|
62
|
+
if configured:
|
|
63
|
+
return configured
|
|
64
|
+
if not context_limit:
|
|
65
|
+
return 1024
|
|
66
|
+
return max(1024, min(32768, int(context_limit) // 32))
|
|
67
|
+
|
|
68
|
+
def cap_tokens_for_context(
|
|
69
|
+
self,
|
|
70
|
+
config: dict[str, Any],
|
|
71
|
+
body: dict[str, Any],
|
|
72
|
+
payload: Any,
|
|
73
|
+
context_limit: int | None,
|
|
74
|
+
configured: int | None,
|
|
75
|
+
_token_cache: dict[int, int] | None = None,
|
|
76
|
+
) -> int | None:
|
|
77
|
+
del body
|
|
78
|
+
if not configured:
|
|
79
|
+
return None
|
|
80
|
+
if not context_limit:
|
|
81
|
+
return configured
|
|
82
|
+
reserve = self.reserve_tokens(config, context_limit)
|
|
83
|
+
available = context_limit - self.estimate_tokens(payload, _token_cache) - reserve
|
|
84
|
+
if available <= 0:
|
|
85
|
+
return min(configured, 256)
|
|
86
|
+
return max(1, min(configured, available))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = ["OpenAIContextBudgetPolicy", "OutputBudgetPolicy"]
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Reusable npm-backed runtime package installation and update lifecycle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import subprocess
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class NpmPackageLifecyclePorts:
|
|
14
|
+
find_executable: Callable[[str], str | None]
|
|
15
|
+
install_prefix: Callable[[], Path | None]
|
|
16
|
+
install_command: Callable[[str, str, Path | None], list[str]]
|
|
17
|
+
run_upgrade: Callable[..., tuple[int, str]]
|
|
18
|
+
add_prefix_bin: Callable[[Path | None], None]
|
|
19
|
+
latest_version: Callable[[str, str], str]
|
|
20
|
+
version_newer: Callable[[str, str], bool]
|
|
21
|
+
output: Callable[..., None] = print
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NpmPackageLifecycle:
|
|
25
|
+
def __init__(self, ports: NpmPackageLifecyclePorts) -> None:
|
|
26
|
+
self._ports = ports
|
|
27
|
+
|
|
28
|
+
def install_if_missing(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
executable_name: str,
|
|
32
|
+
label: str,
|
|
33
|
+
package_spec: str,
|
|
34
|
+
skip_env: str,
|
|
35
|
+
) -> str | None:
|
|
36
|
+
executable = self._ports.find_executable(executable_name)
|
|
37
|
+
if executable:
|
|
38
|
+
return executable
|
|
39
|
+
if os.environ.get(skip_env) == "1":
|
|
40
|
+
return None
|
|
41
|
+
npm = self._ports.find_executable("npm")
|
|
42
|
+
if not npm:
|
|
43
|
+
self._print(
|
|
44
|
+
f"{label} executable was not found, and npm is not available to install {package_spec}."
|
|
45
|
+
)
|
|
46
|
+
return None
|
|
47
|
+
prefix = self._ports.install_prefix()
|
|
48
|
+
command = self._ports.install_command(npm, package_spec, prefix)
|
|
49
|
+
self._print(f"{label} executable was not found; installing {package_spec}...")
|
|
50
|
+
if prefix is not None:
|
|
51
|
+
self._print(f"Installing {label} into active npm prefix: {prefix}")
|
|
52
|
+
return_code, output = self._ports.run_upgrade(command, timeout=300)
|
|
53
|
+
if output:
|
|
54
|
+
self._print(output)
|
|
55
|
+
if return_code != 0:
|
|
56
|
+
self._print(f"{label} install failed ({return_code}).")
|
|
57
|
+
if prefix is not None:
|
|
58
|
+
self._print(
|
|
59
|
+
f"Install targeted the active install prefix ({prefix}). "
|
|
60
|
+
"If this prefix is not writable, install with the permissions used for that prefix."
|
|
61
|
+
)
|
|
62
|
+
return None
|
|
63
|
+
self._ports.add_prefix_bin(prefix)
|
|
64
|
+
executable = self._ports.find_executable(executable_name)
|
|
65
|
+
if executable:
|
|
66
|
+
self._print(f"{label} installed: {executable}")
|
|
67
|
+
else:
|
|
68
|
+
self._print(
|
|
69
|
+
f"{label} install completed, but the {executable_name} executable is still not visible in PATH."
|
|
70
|
+
)
|
|
71
|
+
return executable
|
|
72
|
+
|
|
73
|
+
def update_check(
|
|
74
|
+
self,
|
|
75
|
+
executable: str,
|
|
76
|
+
*,
|
|
77
|
+
executable_name: str,
|
|
78
|
+
label: str,
|
|
79
|
+
package_spec: str,
|
|
80
|
+
skip_env: str,
|
|
81
|
+
current_version: Callable[[str], str],
|
|
82
|
+
enabled: bool = True,
|
|
83
|
+
) -> str:
|
|
84
|
+
if not enabled or os.environ.get(skip_env) == "1":
|
|
85
|
+
return executable
|
|
86
|
+
self._print(f"Checking {label} update before launch...")
|
|
87
|
+
current = current_version(executable)
|
|
88
|
+
if current:
|
|
89
|
+
self._print(f"Current {label} version: {current}")
|
|
90
|
+
npm = self._ports.find_executable("npm")
|
|
91
|
+
if not npm:
|
|
92
|
+
self._print(f"{label} update check skipped: npm was not found.")
|
|
93
|
+
return executable
|
|
94
|
+
latest = self._ports.latest_version(npm, package_spec)
|
|
95
|
+
if not latest:
|
|
96
|
+
self._print(f"{label} update check could not read the latest npm version; continuing.")
|
|
97
|
+
return executable
|
|
98
|
+
if current and not self._ports.version_newer(latest, current):
|
|
99
|
+
self._print(f"{label} is up to date ({current}).")
|
|
100
|
+
return executable
|
|
101
|
+
self._print(f"{label} update available: {current or 'unknown'} -> {latest}; upgrading automatically.")
|
|
102
|
+
prefix = self._ports.install_prefix()
|
|
103
|
+
if prefix is not None:
|
|
104
|
+
self._print(f"Updating {label} in active npm prefix: {prefix}")
|
|
105
|
+
return_code, output = self._ports.run_upgrade(
|
|
106
|
+
self._ports.install_command(npm, package_spec, prefix),
|
|
107
|
+
timeout=300,
|
|
108
|
+
)
|
|
109
|
+
if output:
|
|
110
|
+
self._print(output)
|
|
111
|
+
if return_code != 0:
|
|
112
|
+
self._print(f"{label} update failed ({return_code}); continuing with current version.")
|
|
113
|
+
return executable
|
|
114
|
+
self._ports.add_prefix_bin(prefix)
|
|
115
|
+
updated = self._ports.find_executable(executable_name) or executable
|
|
116
|
+
new_version = current_version(updated)
|
|
117
|
+
if new_version:
|
|
118
|
+
self._print(f"{label} version after update: {new_version}")
|
|
119
|
+
return updated
|
|
120
|
+
|
|
121
|
+
def _print(self, message: str) -> None:
|
|
122
|
+
self._ports.output(message, flush=True)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True, slots=True)
|
|
126
|
+
class SelfUpdatePorts:
|
|
127
|
+
running_from_package: Callable[[], bool]
|
|
128
|
+
find_executable: Callable[[str], str | None]
|
|
129
|
+
latest_version: Callable[[str, str], str]
|
|
130
|
+
version_newer: Callable[[str, str], bool]
|
|
131
|
+
package_root: Callable[[], Path | None]
|
|
132
|
+
prefix_from_root: Callable[[Path], Path | None]
|
|
133
|
+
install_command: Callable[[str, str, Path | None], list[str]]
|
|
134
|
+
forced_environment: Callable[[], dict[str, str]]
|
|
135
|
+
restart: Callable[..., None]
|
|
136
|
+
output: Callable[..., None] = print
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SelfUpdateLifecycle:
|
|
140
|
+
PACKAGE_SPEC = "@oneciel-ai/ciel-runtime@latest"
|
|
141
|
+
|
|
142
|
+
def __init__(self, current_version: str, ports: SelfUpdatePorts) -> None:
|
|
143
|
+
self._current_version = current_version
|
|
144
|
+
self._ports = ports
|
|
145
|
+
|
|
146
|
+
def run(self, enabled: bool = True) -> bool:
|
|
147
|
+
if not enabled or os.environ.get("CIEL_RUNTIME_SKIP_SELF_UPDATE") == "1":
|
|
148
|
+
return False
|
|
149
|
+
raw_check = os.environ.get("CIEL_RUNTIME_SELF_UPDATE_CHECK")
|
|
150
|
+
if raw_check is not None and raw_check.strip().lower() in {"0", "false", "no", "off"}:
|
|
151
|
+
return False
|
|
152
|
+
if not self._ports.running_from_package():
|
|
153
|
+
return False
|
|
154
|
+
npm = self._ports.find_executable("npm")
|
|
155
|
+
if not npm:
|
|
156
|
+
return False
|
|
157
|
+
latest = self._ports.latest_version(npm, self.PACKAGE_SPEC)
|
|
158
|
+
if not latest or not self._ports.version_newer(latest, self._current_version):
|
|
159
|
+
return False
|
|
160
|
+
self._print(
|
|
161
|
+
f"Ciel Runtime update available: {self._current_version} -> {latest}; upgrading automatically."
|
|
162
|
+
)
|
|
163
|
+
package_root = self._ports.package_root()
|
|
164
|
+
prefix = self._ports.prefix_from_root(package_root) if package_root else None
|
|
165
|
+
command = self._ports.install_command(npm, self.PACKAGE_SPEC, prefix)
|
|
166
|
+
if prefix is not None:
|
|
167
|
+
self._print(f"Updating current Ciel Runtime install prefix: {prefix}")
|
|
168
|
+
try:
|
|
169
|
+
update = subprocess.run(
|
|
170
|
+
command,
|
|
171
|
+
text=True,
|
|
172
|
+
input="y\n",
|
|
173
|
+
stdout=subprocess.PIPE,
|
|
174
|
+
stderr=subprocess.STDOUT,
|
|
175
|
+
env=self._ports.forced_environment(),
|
|
176
|
+
timeout=300,
|
|
177
|
+
)
|
|
178
|
+
except subprocess.TimeoutExpired:
|
|
179
|
+
self._print("Ciel Runtime update timed out; continuing with current version.")
|
|
180
|
+
return False
|
|
181
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
182
|
+
self._print(f"Ciel Runtime update failed ({type(exc).__name__}); continuing.")
|
|
183
|
+
return False
|
|
184
|
+
output = (update.stdout or "").strip()
|
|
185
|
+
if output:
|
|
186
|
+
self._print(output)
|
|
187
|
+
if update.returncode != 0:
|
|
188
|
+
self._print(
|
|
189
|
+
f"Ciel Runtime update exited with {update.returncode}; continuing with current version."
|
|
190
|
+
)
|
|
191
|
+
if prefix is not None:
|
|
192
|
+
self._print(
|
|
193
|
+
f"Update targeted the active install prefix ({prefix}). "
|
|
194
|
+
"If this prefix is not writable, reinstall or update with the permissions used for that prefix."
|
|
195
|
+
)
|
|
196
|
+
return False
|
|
197
|
+
self._print("Ciel Runtime updated. Restarting with the new version...")
|
|
198
|
+
try:
|
|
199
|
+
self._ports.restart(npm, package_root=package_root)
|
|
200
|
+
except SystemExit:
|
|
201
|
+
raise
|
|
202
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
203
|
+
self._print(
|
|
204
|
+
f"Restart failed ({type(exc).__name__}); continuing with the current process."
|
|
205
|
+
)
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
def _print(self, message: str) -> None:
|
|
209
|
+
self._ports.output(message, flush=True)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
__all__ = [
|
|
213
|
+
"NpmPackageLifecycle",
|
|
214
|
+
"NpmPackageLifecyclePorts",
|
|
215
|
+
"SelfUpdateLifecycle",
|
|
216
|
+
"SelfUpdatePorts",
|
|
217
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""HTTP controller and repository adapter for shared plan artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import urllib.parse
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from http.server import BaseHTTPRequestHandler
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class PlanArtifactServices:
|
|
15
|
+
directory: Path
|
|
16
|
+
router_base: str
|
|
17
|
+
safe_segment: Callable[[str, str], str]
|
|
18
|
+
write_json: Callable[..., None]
|
|
19
|
+
write_text: Callable[..., None]
|
|
20
|
+
announce: Callable[[dict[str, Any]], dict[str, Any]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class PlanArtifactController:
|
|
25
|
+
services: PlanArtifactServices
|
|
26
|
+
|
|
27
|
+
def get(self, handler: BaseHTTPRequestHandler, path: str) -> bool:
|
|
28
|
+
if path == "/ca/plan/artifacts":
|
|
29
|
+
self.services.directory.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
items = [
|
|
31
|
+
{
|
|
32
|
+
"name": item.name,
|
|
33
|
+
"bytes": item.stat().st_size,
|
|
34
|
+
"url": f"{self.services.router_base}/ca/plan/artifacts/{urllib.parse.quote(item.name)}",
|
|
35
|
+
}
|
|
36
|
+
for item in sorted(self.services.directory.glob("*"))
|
|
37
|
+
if item.is_file()
|
|
38
|
+
]
|
|
39
|
+
self.services.write_json(handler, {"ok": True, "artifacts": items})
|
|
40
|
+
return True
|
|
41
|
+
if not path.startswith("/ca/plan/artifacts/"):
|
|
42
|
+
return False
|
|
43
|
+
name = self.services.safe_segment(
|
|
44
|
+
urllib.parse.unquote(path[len("/ca/plan/artifacts/") :]), "plan.md"
|
|
45
|
+
)
|
|
46
|
+
target = self.services.directory / name
|
|
47
|
+
if not target.exists() or not target.is_file():
|
|
48
|
+
self.services.write_json(handler, {"ok": False, "error": "not_found"}, 404)
|
|
49
|
+
return True
|
|
50
|
+
content_type = (
|
|
51
|
+
"text/markdown; charset=utf-8"
|
|
52
|
+
if target.suffix.lower() in (".md", ".markdown")
|
|
53
|
+
else "text/plain; charset=utf-8"
|
|
54
|
+
)
|
|
55
|
+
self.services.write_text(
|
|
56
|
+
handler,
|
|
57
|
+
target.read_text(encoding="utf-8", errors="replace"),
|
|
58
|
+
content_type=content_type,
|
|
59
|
+
)
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
def post(
|
|
63
|
+
self,
|
|
64
|
+
handler: BaseHTTPRequestHandler,
|
|
65
|
+
path: str,
|
|
66
|
+
body: dict[str, Any],
|
|
67
|
+
) -> bool:
|
|
68
|
+
if path != "/ca/plan/artifacts":
|
|
69
|
+
return False
|
|
70
|
+
self.services.directory.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
title = str(body.get("title") or "plan")
|
|
72
|
+
content = str(body.get("content") or body.get("message") or "")
|
|
73
|
+
name = self.services.safe_segment(
|
|
74
|
+
str(body.get("name") or f"{int(time.time())}-{title}.md"), "plan.md"
|
|
75
|
+
)
|
|
76
|
+
if "." not in name:
|
|
77
|
+
name += ".md"
|
|
78
|
+
target = self.services.directory / name
|
|
79
|
+
target.write_text(content, encoding="utf-8")
|
|
80
|
+
latest = self.services.directory / "latest.md"
|
|
81
|
+
if target.name != latest.name:
|
|
82
|
+
latest.write_text(content, encoding="utf-8")
|
|
83
|
+
url = f"{self.services.router_base}/ca/plan/artifacts/{urllib.parse.quote(name)}"
|
|
84
|
+
if body.get("announce", True):
|
|
85
|
+
self.services.announce(
|
|
86
|
+
{
|
|
87
|
+
"channel": body.get("channel", "plan"),
|
|
88
|
+
"sender_id": body.get("sender_id", "plan"),
|
|
89
|
+
"recipients": body.get("recipients", "all"),
|
|
90
|
+
"kind": "plan",
|
|
91
|
+
"message": url,
|
|
92
|
+
"meta": {"title": title, "url": url, "name": name},
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
self.services.write_json(
|
|
96
|
+
handler,
|
|
97
|
+
{
|
|
98
|
+
"ok": True,
|
|
99
|
+
"name": name,
|
|
100
|
+
"url": url,
|
|
101
|
+
"latest_url": f"{self.services.router_base}/ca/plan/artifacts/latest.md",
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
return True
|