@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,84 @@
|
|
|
1
|
+
"""Restart the active Ciel Runtime installation after self-update."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, MutableMapping, Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def running_from_npm_package(path: Path, environ: MutableMapping[str, str]) -> bool:
|
|
11
|
+
if environ.get("CIEL_RUNTIME_NPM_MODE") is not None:
|
|
12
|
+
return True
|
|
13
|
+
normalized = str(path.resolve(strict=False)).replace("\\", "/")
|
|
14
|
+
return "/node_modules/@oneciel-ai/ciel-runtime/" in normalized
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def forced_upgrade_environment(environ: MutableMapping[str, str]) -> dict[str, str]:
|
|
18
|
+
result = dict(environ)
|
|
19
|
+
result.update(CI="1", NPM_CONFIG_YES="true", npm_config_yes="true")
|
|
20
|
+
result.setdefault("NPM_CONFIG_UPDATE_NOTIFIER", "false")
|
|
21
|
+
result.setdefault("npm_config_update_notifier", "false")
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class RuntimeRestartSettings:
|
|
27
|
+
argv: Sequence[str]
|
|
28
|
+
python_executable: str
|
|
29
|
+
environ: MutableMapping[str, str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class RuntimeRestartPorts:
|
|
34
|
+
current_package_root: Callable[[], Path | None]
|
|
35
|
+
global_package_root: Callable[[str], Path | None]
|
|
36
|
+
find_executable: Callable[[str], str | None]
|
|
37
|
+
execv: Callable[[str, list[str]], object]
|
|
38
|
+
call: Callable[..., int]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class RuntimeRestartService:
|
|
43
|
+
settings: RuntimeRestartSettings
|
|
44
|
+
ports: RuntimeRestartPorts
|
|
45
|
+
|
|
46
|
+
def user_args(self) -> list[str]:
|
|
47
|
+
arguments = list(self.settings.argv[1:])
|
|
48
|
+
return arguments[1:] if arguments and arguments[0] == "cli" else arguments
|
|
49
|
+
|
|
50
|
+
def restart(self, npm: str, package_root: Path | None = None) -> None:
|
|
51
|
+
self.settings.environ["CIEL_RUNTIME_SKIP_SELF_UPDATE"] = "1"
|
|
52
|
+
user_args = self.user_args()
|
|
53
|
+
root = (
|
|
54
|
+
package_root
|
|
55
|
+
or self.ports.current_package_root()
|
|
56
|
+
or self.ports.global_package_root(npm)
|
|
57
|
+
)
|
|
58
|
+
package_script = root / "ciel_runtime.py" if root else None
|
|
59
|
+
if package_script and package_script.exists():
|
|
60
|
+
self.ports.execv(
|
|
61
|
+
self.settings.python_executable,
|
|
62
|
+
[self.settings.python_executable, str(package_script), "cli", *user_args],
|
|
63
|
+
)
|
|
64
|
+
return
|
|
65
|
+
launcher = self.ports.find_executable("ciel-runtime")
|
|
66
|
+
if launcher:
|
|
67
|
+
raise SystemExit(
|
|
68
|
+
self.ports.call(
|
|
69
|
+
[launcher, *user_args], env=dict(self.settings.environ)
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
self.ports.execv(
|
|
73
|
+
self.settings.python_executable,
|
|
74
|
+
[self.settings.python_executable, *self.settings.argv],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__all__ = [
|
|
79
|
+
"RuntimeRestartPorts",
|
|
80
|
+
"RuntimeRestartService",
|
|
81
|
+
"RuntimeRestartSettings",
|
|
82
|
+
"forced_upgrade_environment",
|
|
83
|
+
"running_from_npm_package",
|
|
84
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Coordinated quiet upgrades for the runtime CLI toolchain."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class RuntimeUpgradeSettings:
|
|
12
|
+
runtime_version: str
|
|
13
|
+
environ: Mapping[str, str]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class RuntimeUpgradeNpmPorts:
|
|
18
|
+
find_executable: Callable[[str], str | None]
|
|
19
|
+
latest_version: Callable[[str, str], str]
|
|
20
|
+
version_newer: Callable[[str, str], bool]
|
|
21
|
+
current_package_root: Callable[[], Path | None]
|
|
22
|
+
package_prefix: Callable[[Path], Path | None]
|
|
23
|
+
current_prefix: Callable[[], Path | None]
|
|
24
|
+
global_install_command: Callable[[str, str, Path | None], list[str]]
|
|
25
|
+
runtime_install_command: Callable[[str | None, str, Path | None], list[str]]
|
|
26
|
+
run_command: Callable[[list[str], float], tuple[int, str]]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class RuntimeUpgradeToolPorts:
|
|
31
|
+
claude_version: Callable[[str], str]
|
|
32
|
+
codex_version: Callable[[str], str]
|
|
33
|
+
install_claude: Callable[[], str | None]
|
|
34
|
+
install_codex: Callable[[], str | None]
|
|
35
|
+
install_agy: Callable[[], str | None]
|
|
36
|
+
update_agy: Callable[[str, bool], str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, slots=True)
|
|
40
|
+
class RuntimeUpgradeService:
|
|
41
|
+
settings: RuntimeUpgradeSettings
|
|
42
|
+
npm: RuntimeUpgradeNpmPorts
|
|
43
|
+
tools: RuntimeUpgradeToolPorts
|
|
44
|
+
output: Callable[[str], None]
|
|
45
|
+
|
|
46
|
+
def ciel_runtime(self) -> int:
|
|
47
|
+
npm = self.npm.find_executable("npm")
|
|
48
|
+
if not npm:
|
|
49
|
+
self.output("Ciel Runtime update skipped: npm was not found.")
|
|
50
|
+
return 1
|
|
51
|
+
package_spec = "@oneciel-ai/ciel-runtime@latest"
|
|
52
|
+
latest = self.npm.latest_version(npm, package_spec)
|
|
53
|
+
if latest and not self.npm.version_newer(
|
|
54
|
+
latest, self.settings.runtime_version
|
|
55
|
+
):
|
|
56
|
+
self.output(
|
|
57
|
+
f"Ciel Runtime is up to date ({self.settings.runtime_version})."
|
|
58
|
+
)
|
|
59
|
+
return 0
|
|
60
|
+
self.output(f"Updating Ciel Runtime to {latest or 'latest'}...")
|
|
61
|
+
package_root = self.npm.current_package_root()
|
|
62
|
+
install_prefix = self.npm.package_prefix(package_root) if package_root else None
|
|
63
|
+
if install_prefix is not None:
|
|
64
|
+
self.output(f"Updating current Ciel Runtime install prefix: {install_prefix}")
|
|
65
|
+
return self._run_install(
|
|
66
|
+
self.npm.global_install_command(npm, package_spec, install_prefix),
|
|
67
|
+
"Ciel Runtime",
|
|
68
|
+
install_prefix,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def claude(self) -> int:
|
|
72
|
+
claude = self.npm.find_executable("claude")
|
|
73
|
+
if not claude:
|
|
74
|
+
return 0 if self.tools.install_claude() else 1
|
|
75
|
+
current = self.tools.claude_version(claude)
|
|
76
|
+
npm = self.npm.find_executable("npm")
|
|
77
|
+
package_spec = self.settings.environ.get(
|
|
78
|
+
"CIEL_RUNTIME_CLAUDE_CODE_PACKAGE", "@anthropic-ai/claude-code@latest"
|
|
79
|
+
)
|
|
80
|
+
latest = self.npm.latest_version(npm, package_spec) if npm else ""
|
|
81
|
+
if current and latest and not self.npm.version_newer(latest, current):
|
|
82
|
+
self.output(f"Claude Code is up to date ({current}).")
|
|
83
|
+
return 0
|
|
84
|
+
self.output(f"Updating Claude Code ({current or 'unknown'} -> {latest or 'latest'})...")
|
|
85
|
+
install_prefix = self.npm.current_prefix()
|
|
86
|
+
if install_prefix is not None:
|
|
87
|
+
self.output(f"Updating Claude Code in active npm prefix: {install_prefix}")
|
|
88
|
+
return self._run_install(
|
|
89
|
+
self.npm.runtime_install_command(npm, package_spec, install_prefix),
|
|
90
|
+
"Claude Code",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def codex(self) -> int:
|
|
94
|
+
codex = self.npm.find_executable("codex")
|
|
95
|
+
if not codex:
|
|
96
|
+
return 0 if self.tools.install_codex() else 1
|
|
97
|
+
current = self.tools.codex_version(codex)
|
|
98
|
+
npm = self.npm.find_executable("npm")
|
|
99
|
+
if not npm:
|
|
100
|
+
self.output("Codex update skipped: npm was not found.")
|
|
101
|
+
return 1
|
|
102
|
+
package_spec = self.settings.environ.get(
|
|
103
|
+
"CIEL_RUNTIME_CODEX_PACKAGE", "@openai/codex@latest"
|
|
104
|
+
)
|
|
105
|
+
latest = self.npm.latest_version(npm, package_spec)
|
|
106
|
+
if current and latest and not self.npm.version_newer(latest, current):
|
|
107
|
+
self.output(f"Codex is up to date ({current}).")
|
|
108
|
+
return 0
|
|
109
|
+
self.output(f"Updating Codex ({current or 'unknown'} -> {latest or 'latest'})...")
|
|
110
|
+
install_prefix = self.npm.current_prefix()
|
|
111
|
+
if install_prefix is not None:
|
|
112
|
+
self.output(f"Updating Codex in active npm prefix: {install_prefix}")
|
|
113
|
+
return self._run_install(
|
|
114
|
+
self.npm.runtime_install_command(npm, package_spec, install_prefix),
|
|
115
|
+
"Codex",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def agy(self) -> int:
|
|
119
|
+
agy = self.npm.find_executable("agy")
|
|
120
|
+
if not agy:
|
|
121
|
+
return 0 if self.tools.install_agy() else 1
|
|
122
|
+
return 0 if self.tools.update_agy(agy, True) else 1
|
|
123
|
+
|
|
124
|
+
def _run_install(
|
|
125
|
+
self,
|
|
126
|
+
command: list[str],
|
|
127
|
+
label: str,
|
|
128
|
+
install_prefix: Path | None = None,
|
|
129
|
+
) -> int:
|
|
130
|
+
return_code, output = self.npm.run_command(command, 300.0)
|
|
131
|
+
if output:
|
|
132
|
+
self.output(output)
|
|
133
|
+
if return_code != 0:
|
|
134
|
+
self.output(f"{label} update failed ({return_code}).")
|
|
135
|
+
if install_prefix is not None:
|
|
136
|
+
self.output(
|
|
137
|
+
f"Update targeted the active install prefix ({install_prefix}). "
|
|
138
|
+
"If this prefix is not writable, reinstall or update with the "
|
|
139
|
+
"permissions used for that prefix."
|
|
140
|
+
)
|
|
141
|
+
return return_code
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
__all__ = [
|
|
145
|
+
"RuntimeUpgradeNpmPorts",
|
|
146
|
+
"RuntimeUpgradeService",
|
|
147
|
+
"RuntimeUpgradeSettings",
|
|
148
|
+
"RuntimeUpgradeToolPorts",
|
|
149
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class SecureJsonEffects:
|
|
13
|
+
log: Callable[[str, str], None]
|
|
14
|
+
chmod: Callable[[Path, int], None] = os.chmod
|
|
15
|
+
process_id: Callable[[], int] = os.getpid
|
|
16
|
+
time_ns: Callable[[], int] = time.time_ns
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class SecureJsonRepository:
|
|
21
|
+
path: Path
|
|
22
|
+
effects: SecureJsonEffects
|
|
23
|
+
|
|
24
|
+
def load(self, purpose: str) -> dict[str, Any] | None:
|
|
25
|
+
if not self.path.exists():
|
|
26
|
+
return {}
|
|
27
|
+
try:
|
|
28
|
+
value = json.loads(self.path.read_text(encoding="utf-8"))
|
|
29
|
+
except (OSError, UnicodeError, json.JSONDecodeError, TypeError) as exc:
|
|
30
|
+
self.effects.log(
|
|
31
|
+
"WARN",
|
|
32
|
+
f"secure_json_read_failed purpose={purpose} path={self.path} "
|
|
33
|
+
f"error={type(exc).__name__}: {exc}",
|
|
34
|
+
)
|
|
35
|
+
return None
|
|
36
|
+
return value if isinstance(value, dict) else {}
|
|
37
|
+
|
|
38
|
+
def save(self, value: dict[str, Any], purpose: str) -> None:
|
|
39
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
temporary_path = self.path.with_name(
|
|
41
|
+
f"{self.path.name}.{self.effects.process_id()}.{self.effects.time_ns()}.tmp"
|
|
42
|
+
)
|
|
43
|
+
temporary_path.write_text(
|
|
44
|
+
json.dumps(value, indent=2, ensure_ascii=False) + "\n",
|
|
45
|
+
encoding="utf-8",
|
|
46
|
+
)
|
|
47
|
+
try:
|
|
48
|
+
self.effects.chmod(temporary_path, 0o600)
|
|
49
|
+
except OSError as exc:
|
|
50
|
+
self.effects.log(
|
|
51
|
+
"WARN",
|
|
52
|
+
f"secure_json_chmod_failed purpose={purpose} path={temporary_path} "
|
|
53
|
+
f"error={type(exc).__name__}: {exc}",
|
|
54
|
+
)
|
|
55
|
+
temporary_path.replace(self.path)
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""Cross-runtime transcript import repository and application service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
from collections.abc import Callable, Mapping
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ciel_runtime_support.channel_transcript import (
|
|
15
|
+
content_text,
|
|
16
|
+
is_assistant_message,
|
|
17
|
+
user_text,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def normalize_import_source(value: str) -> str:
|
|
22
|
+
normalized = re.sub(r"[^a-z0-9]+", "", str(value or "").strip().lower())
|
|
23
|
+
if normalized in {"codex", "openai", "codexcli"}:
|
|
24
|
+
return "codex"
|
|
25
|
+
if normalized in {"claude", "claudecode", "anthropic"}:
|
|
26
|
+
return "claude"
|
|
27
|
+
return ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class ImportSessionLimits:
|
|
32
|
+
max_bytes: int
|
|
33
|
+
max_chars: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ImportSessionRepository:
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
home: Path,
|
|
40
|
+
environment: Mapping[str, str],
|
|
41
|
+
limits: ImportSessionLimits,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.home = home
|
|
44
|
+
self.environment = environment
|
|
45
|
+
self.limits = limits
|
|
46
|
+
|
|
47
|
+
def latest(self, source: str) -> Path | None:
|
|
48
|
+
if source == "claude":
|
|
49
|
+
root, pattern = self.home / ".claude" / "projects", "*/*.jsonl"
|
|
50
|
+
elif source == "codex":
|
|
51
|
+
codex_home = Path(
|
|
52
|
+
self.environment.get("CODEX_HOME") or (self.home / ".codex")
|
|
53
|
+
).expanduser()
|
|
54
|
+
root, pattern = codex_home / "sessions", "**/*.jsonl"
|
|
55
|
+
else:
|
|
56
|
+
return None
|
|
57
|
+
return self._latest_under(root, pattern)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _latest_under(root: Path, pattern: str) -> Path | None:
|
|
61
|
+
latest: Path | None = None
|
|
62
|
+
latest_mtime = -1.0
|
|
63
|
+
try:
|
|
64
|
+
paths = root.glob(pattern)
|
|
65
|
+
except (OSError, ValueError):
|
|
66
|
+
return None
|
|
67
|
+
for path in paths:
|
|
68
|
+
try:
|
|
69
|
+
mtime = path.stat().st_mtime if path.is_file() else -1.0
|
|
70
|
+
except OSError:
|
|
71
|
+
continue
|
|
72
|
+
if mtime > latest_mtime:
|
|
73
|
+
latest = path
|
|
74
|
+
latest_mtime = mtime
|
|
75
|
+
return latest
|
|
76
|
+
|
|
77
|
+
def resolve(self, source: str, path_text: str) -> tuple[Path | None, str]:
|
|
78
|
+
raw = str(path_text or "").strip()
|
|
79
|
+
if not raw:
|
|
80
|
+
latest = self.latest(source)
|
|
81
|
+
if latest is None:
|
|
82
|
+
return None, (
|
|
83
|
+
f"No {source} transcript path was provided and no recent "
|
|
84
|
+
f"{source} transcript was found."
|
|
85
|
+
)
|
|
86
|
+
return latest, ""
|
|
87
|
+
if re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", raw):
|
|
88
|
+
return None, "ImportSession only reads local transcript files, not URLs."
|
|
89
|
+
path = Path(os.path.expandvars(raw)).expanduser()
|
|
90
|
+
if path.is_dir():
|
|
91
|
+
latest = self._latest_under(path, "**/*.jsonl")
|
|
92
|
+
if latest is None:
|
|
93
|
+
return None, f"No .jsonl transcript files found under {path}."
|
|
94
|
+
return latest, ""
|
|
95
|
+
if not path.exists():
|
|
96
|
+
return None, f"Transcript file not found: {path}"
|
|
97
|
+
if not path.is_file():
|
|
98
|
+
return None, f"Transcript path is not a regular file: {path}"
|
|
99
|
+
return path, ""
|
|
100
|
+
|
|
101
|
+
def read(self, source: str, path: Path) -> tuple[str, dict[str, Any]]:
|
|
102
|
+
size = path.stat().st_size
|
|
103
|
+
truncated_bytes = size > self.limits.max_bytes
|
|
104
|
+
with path.open("rb") as stream:
|
|
105
|
+
if truncated_bytes:
|
|
106
|
+
stream.seek(max(0, size - self.limits.max_bytes))
|
|
107
|
+
raw = stream.read(self.limits.max_bytes)
|
|
108
|
+
text = raw.decode("utf-8", errors="replace")
|
|
109
|
+
lines: list[str] = []
|
|
110
|
+
parsed_jsonl = False
|
|
111
|
+
for raw_line in text.splitlines():
|
|
112
|
+
try:
|
|
113
|
+
record = json.loads(raw_line.strip())
|
|
114
|
+
except (TypeError, ValueError):
|
|
115
|
+
continue
|
|
116
|
+
if not isinstance(record, dict):
|
|
117
|
+
continue
|
|
118
|
+
parsed_jsonl = True
|
|
119
|
+
line = import_record_line(record)
|
|
120
|
+
if line:
|
|
121
|
+
lines.append(line)
|
|
122
|
+
imported = "\n\n".join(lines).strip() if parsed_jsonl else text.strip()
|
|
123
|
+
truncated_chars = len(imported) > self.limits.max_chars
|
|
124
|
+
if truncated_chars:
|
|
125
|
+
imported = imported[-self.limits.max_chars :].lstrip()
|
|
126
|
+
return imported, {
|
|
127
|
+
"source": source,
|
|
128
|
+
"path": str(path),
|
|
129
|
+
"bytes": size,
|
|
130
|
+
"bytes_read": len(raw),
|
|
131
|
+
"byte_truncated": truncated_bytes,
|
|
132
|
+
"char_truncated": truncated_chars,
|
|
133
|
+
"jsonl": parsed_jsonl,
|
|
134
|
+
"records": len(lines),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def import_tool_text(record: dict[str, Any]) -> str:
|
|
139
|
+
record_type = str(record.get("type") or "")
|
|
140
|
+
payload = record.get("payload")
|
|
141
|
+
payload_object = payload if isinstance(payload, dict) else {}
|
|
142
|
+
payload_type = str(payload_object.get("type") or "")
|
|
143
|
+
call_types = {"function_call", "custom_tool_call", "local_shell_call"}
|
|
144
|
+
result_types = {
|
|
145
|
+
"function_call_output",
|
|
146
|
+
"custom_tool_call_output",
|
|
147
|
+
"local_shell_call_output",
|
|
148
|
+
}
|
|
149
|
+
if record_type == "response_item" and payload_type in call_types:
|
|
150
|
+
name = str(
|
|
151
|
+
payload_object.get("name")
|
|
152
|
+
or payload_object.get("tool_name")
|
|
153
|
+
or payload_type
|
|
154
|
+
).strip()
|
|
155
|
+
arguments = payload_object.get("arguments")
|
|
156
|
+
if not isinstance(arguments, str):
|
|
157
|
+
arguments = (
|
|
158
|
+
json.dumps(arguments, ensure_ascii=False, default=str)
|
|
159
|
+
if arguments is not None
|
|
160
|
+
else ""
|
|
161
|
+
)
|
|
162
|
+
return f"tool_call {name}: {arguments}".strip()
|
|
163
|
+
if record_type == "response_item" and payload_type in result_types:
|
|
164
|
+
text = content_text(
|
|
165
|
+
payload_object.get("output")
|
|
166
|
+
or payload_object.get("content")
|
|
167
|
+
or payload_object.get("message")
|
|
168
|
+
)
|
|
169
|
+
return f"tool_result: {text}".strip() if text else "tool_result"
|
|
170
|
+
message = record.get("message")
|
|
171
|
+
message_object = message if isinstance(message, dict) else {}
|
|
172
|
+
content = message_object.get("content")
|
|
173
|
+
if not isinstance(content, list):
|
|
174
|
+
return ""
|
|
175
|
+
parts: list[str] = []
|
|
176
|
+
for block in content:
|
|
177
|
+
if not isinstance(block, dict):
|
|
178
|
+
continue
|
|
179
|
+
if block.get("type") == "tool_use":
|
|
180
|
+
arguments = json.dumps(
|
|
181
|
+
block.get("input") or {}, ensure_ascii=False, default=str
|
|
182
|
+
)
|
|
183
|
+
parts.append(f"tool_call {block.get('name') or 'tool'}: {arguments}")
|
|
184
|
+
elif block.get("type") == "tool_result":
|
|
185
|
+
parts.append(f"tool_result: {content_text(block.get('content'))}".strip())
|
|
186
|
+
return "\n".join(part for part in parts if part)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def import_record_line(record: dict[str, Any]) -> str:
|
|
190
|
+
text = user_text(record).strip()
|
|
191
|
+
if text:
|
|
192
|
+
return "User: " + text
|
|
193
|
+
if is_assistant_message(record):
|
|
194
|
+
message = record.get("message")
|
|
195
|
+
message_object = message if isinstance(message, dict) else {}
|
|
196
|
+
payload = record.get("payload")
|
|
197
|
+
payload_object = payload if isinstance(payload, dict) else {}
|
|
198
|
+
text = content_text(message_object.get("content")).strip()
|
|
199
|
+
if not text:
|
|
200
|
+
text = content_text(
|
|
201
|
+
payload_object.get("content") or payload_object.get("message")
|
|
202
|
+
).strip()
|
|
203
|
+
if text:
|
|
204
|
+
return "Assistant: " + text
|
|
205
|
+
tool_text = import_tool_text(record).strip()
|
|
206
|
+
return "Tool: " + tool_text if tool_text else ""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ImportSessionService:
|
|
210
|
+
def __init__(
|
|
211
|
+
self,
|
|
212
|
+
repository: ImportSessionRepository,
|
|
213
|
+
parse_args: Callable[[dict[str, Any]], tuple[str, str]],
|
|
214
|
+
) -> None:
|
|
215
|
+
self.repository = repository
|
|
216
|
+
self.parse_args = parse_args
|
|
217
|
+
|
|
218
|
+
def response_text(self, client_runtime: str, body: dict[str, Any]) -> str:
|
|
219
|
+
raw_source, raw_path = self.parse_args(body)
|
|
220
|
+
source = normalize_import_source(raw_source)
|
|
221
|
+
if not source:
|
|
222
|
+
return (
|
|
223
|
+
"Usage: `/ImportSession Codex [transcript-path]` or "
|
|
224
|
+
"`/ImportSession Claude [transcript-path]`."
|
|
225
|
+
)
|
|
226
|
+
if client_runtime == "claude" and source == "claude":
|
|
227
|
+
return (
|
|
228
|
+
"Claude transcript import into a Claude session is blocked. "
|
|
229
|
+
"Use Claude Code's native resume/continue flow for Claude-to-Claude "
|
|
230
|
+
"sessions. Use ImportSession for cross-runtime import, for example "
|
|
231
|
+
"`/ImportSession Codex <path>`."
|
|
232
|
+
)
|
|
233
|
+
path, error = self.repository.resolve(source, raw_path)
|
|
234
|
+
if error or path is None:
|
|
235
|
+
return f"ImportSession failed: {error}"
|
|
236
|
+
try:
|
|
237
|
+
imported, metadata = self.repository.read(source, path)
|
|
238
|
+
except (OSError, UnicodeError, ValueError) as exc:
|
|
239
|
+
return (
|
|
240
|
+
f"ImportSession failed while reading {path}: "
|
|
241
|
+
f"{type(exc).__name__}: {exc}"
|
|
242
|
+
)
|
|
243
|
+
if not imported:
|
|
244
|
+
return f"ImportSession found no importable transcript content in {path}."
|
|
245
|
+
header = self._header(client_runtime, source, path, metadata)
|
|
246
|
+
return "\n".join(
|
|
247
|
+
[
|
|
248
|
+
*header,
|
|
249
|
+
"",
|
|
250
|
+
"Imported transcript context:",
|
|
251
|
+
f'<ciel-runtime-imported-session source="{source}" path="{path}">',
|
|
252
|
+
imported,
|
|
253
|
+
"</ciel-runtime-imported-session>",
|
|
254
|
+
]
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def _header(
|
|
258
|
+
self,
|
|
259
|
+
client_runtime: str,
|
|
260
|
+
source: str,
|
|
261
|
+
path: Path,
|
|
262
|
+
metadata: dict[str, Any],
|
|
263
|
+
) -> list[str]:
|
|
264
|
+
header = [
|
|
265
|
+
"Ciel Runtime ImportSession completed.",
|
|
266
|
+
f"- current client: {client_runtime}",
|
|
267
|
+
f"- source transcript format: {source}",
|
|
268
|
+
f"- transcript: {path}",
|
|
269
|
+
f"- parsed jsonl: {str(bool(metadata.get('jsonl'))).lower()}",
|
|
270
|
+
f"- imported records: {metadata.get('records')}",
|
|
271
|
+
]
|
|
272
|
+
if metadata.get("byte_truncated"):
|
|
273
|
+
header.append(
|
|
274
|
+
f"- warning: read tail only: {metadata.get('bytes_read'):,}/"
|
|
275
|
+
f"{metadata.get('bytes'):,} bytes"
|
|
276
|
+
)
|
|
277
|
+
if metadata.get("char_truncated"):
|
|
278
|
+
header.append(
|
|
279
|
+
f"- warning: trimmed to last {self.repository.limits.max_chars:,} chars"
|
|
280
|
+
)
|
|
281
|
+
return header
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass(frozen=True, slots=True)
|
|
285
|
+
class ImportSessionHttpPorts:
|
|
286
|
+
is_request: Callable[[dict[str, Any]], bool]
|
|
287
|
+
response_text: Callable[[str, dict[str, Any]], str]
|
|
288
|
+
load_config: Callable[[], dict[str, Any]]
|
|
289
|
+
current_alias: Callable[[dict[str, Any]], str]
|
|
290
|
+
current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
|
|
291
|
+
estimate_tokens: Callable[[Any], int]
|
|
292
|
+
write_openai: Callable[..., Any]
|
|
293
|
+
write_anthropic: Callable[..., Any]
|
|
294
|
+
publish_event: Callable[..., Any]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@dataclass(frozen=True, slots=True)
|
|
298
|
+
class ImportSessionHttpController:
|
|
299
|
+
ports: ImportSessionHttpPorts
|
|
300
|
+
|
|
301
|
+
def handle(
|
|
302
|
+
self,
|
|
303
|
+
handler: Any,
|
|
304
|
+
body: dict[str, Any],
|
|
305
|
+
*,
|
|
306
|
+
client_runtime: str,
|
|
307
|
+
response_format: str = "anthropic",
|
|
308
|
+
source_body: dict[str, Any] | None = None,
|
|
309
|
+
) -> bool:
|
|
310
|
+
if not self.ports.is_request(body):
|
|
311
|
+
return False
|
|
312
|
+
text = self.ports.response_text(client_runtime, body)
|
|
313
|
+
config = self.ports.load_config()
|
|
314
|
+
model = str(body.get("model") or self.ports.current_alias(config))
|
|
315
|
+
source = source_body or body
|
|
316
|
+
stream = bool(source.get("stream", True))
|
|
317
|
+
if response_format == "openai":
|
|
318
|
+
message = {
|
|
319
|
+
"id": f"msg_ciel_runtime_import_{uuid.uuid4().hex[:12]}",
|
|
320
|
+
"type": "message",
|
|
321
|
+
"role": "assistant",
|
|
322
|
+
"model": model,
|
|
323
|
+
"content": [{"type": "text", "text": text}],
|
|
324
|
+
"stop_reason": "end_turn",
|
|
325
|
+
"usage": {
|
|
326
|
+
"input_tokens": 1,
|
|
327
|
+
"output_tokens": max(1, self.ports.estimate_tokens({"text": text})),
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
self.ports.write_openai(
|
|
331
|
+
handler, message, source_body=source, stream=stream
|
|
332
|
+
)
|
|
333
|
+
else:
|
|
334
|
+
self.ports.write_anthropic(handler, model, text, stream)
|
|
335
|
+
provider = self.ports.current_provider(config)[0]
|
|
336
|
+
self.ports.publish_event(
|
|
337
|
+
level="info",
|
|
338
|
+
category="import_session.short_circuit",
|
|
339
|
+
message="ImportSession request handled locally",
|
|
340
|
+
provider=provider,
|
|
341
|
+
model=model,
|
|
342
|
+
data={"client_runtime": client_runtime},
|
|
343
|
+
)
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
__all__ = [
|
|
348
|
+
"ImportSessionHttpController",
|
|
349
|
+
"ImportSessionHttpPorts",
|
|
350
|
+
"ImportSessionLimits",
|
|
351
|
+
"ImportSessionRepository",
|
|
352
|
+
"ImportSessionService",
|
|
353
|
+
"import_record_line",
|
|
354
|
+
"import_tool_text",
|
|
355
|
+
"normalize_import_source",
|
|
356
|
+
]
|