@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,130 @@
|
|
|
1
|
+
"""Stateful removal of provider-visible thinking and tool-call artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
VISIBLE_THINKING_MARKUP_TAG_RE = re.compile(r"</?think(?:ing)?\b[^>]*>", re.I)
|
|
10
|
+
VISIBLE_THINKING_MARKUP_PREFIXES = ("<think", "</think", "<thinking", "</thinking")
|
|
11
|
+
VISIBLE_TOOL_CALL_ARTIFACT_SUFFIX_RE = re.compile(
|
|
12
|
+
r"(?s)(?:^|(?:\r?\n){1,3})[ \t]*call[ \t]*(?:\r?\n)"
|
|
13
|
+
r"[ \t]*ignore[ \t]*(?:\r?\n)?[ \t]*$"
|
|
14
|
+
)
|
|
15
|
+
VISIBLE_TOOL_CALL_ARTIFACT_HOLD_CHARS = 96
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def visible_thinking_markup_partial_start(text: str) -> int:
|
|
19
|
+
less_than = text.rfind("<")
|
|
20
|
+
if less_than < 0 or less_than <= text.rfind(">"):
|
|
21
|
+
return -1
|
|
22
|
+
suffix = text[less_than:].lower()
|
|
23
|
+
if any(
|
|
24
|
+
prefix.startswith(suffix) or suffix.startswith(prefix)
|
|
25
|
+
for prefix in VISIBLE_THINKING_MARKUP_PREFIXES
|
|
26
|
+
):
|
|
27
|
+
return less_than
|
|
28
|
+
return -1
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VisibleThinkingMarkupFilter:
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self.in_thinking = False
|
|
34
|
+
self.pending = ""
|
|
35
|
+
|
|
36
|
+
def feed(self, text: Any) -> str:
|
|
37
|
+
raw = str(text or "")
|
|
38
|
+
if not raw:
|
|
39
|
+
return ""
|
|
40
|
+
value = self.pending + raw
|
|
41
|
+
self.pending = ""
|
|
42
|
+
partial_start = visible_thinking_markup_partial_start(value)
|
|
43
|
+
if partial_start >= 0:
|
|
44
|
+
self.pending = value[partial_start:]
|
|
45
|
+
value = value[:partial_start]
|
|
46
|
+
return self._strip_complete_tags(value)
|
|
47
|
+
|
|
48
|
+
def finish(self) -> str:
|
|
49
|
+
pending = self.pending
|
|
50
|
+
self.pending = ""
|
|
51
|
+
if (
|
|
52
|
+
not pending
|
|
53
|
+
or self.in_thinking
|
|
54
|
+
or visible_thinking_markup_partial_start(pending) == 0
|
|
55
|
+
):
|
|
56
|
+
self.in_thinking = False
|
|
57
|
+
return ""
|
|
58
|
+
return self._strip_complete_tags(pending)
|
|
59
|
+
|
|
60
|
+
def _strip_complete_tags(self, text: str) -> str:
|
|
61
|
+
output: list[str] = []
|
|
62
|
+
position = 0
|
|
63
|
+
for match in VISIBLE_THINKING_MARKUP_TAG_RE.finditer(text):
|
|
64
|
+
closing = match.group(0).lstrip().lower().startswith("</")
|
|
65
|
+
if self.in_thinking:
|
|
66
|
+
if closing:
|
|
67
|
+
self.in_thinking = False
|
|
68
|
+
position = match.end()
|
|
69
|
+
continue
|
|
70
|
+
output.append(text[position : match.start()])
|
|
71
|
+
position = match.end()
|
|
72
|
+
if not closing:
|
|
73
|
+
self.in_thinking = True
|
|
74
|
+
if not self.in_thinking:
|
|
75
|
+
output.append(text[position:])
|
|
76
|
+
return "".join(output)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def strip_visible_thinking_markup(text: Any) -> str:
|
|
80
|
+
raw = str(text or "")
|
|
81
|
+
if "<think" not in raw.lower() and "</think" not in raw.lower():
|
|
82
|
+
return raw
|
|
83
|
+
filter_state = VisibleThinkingMarkupFilter()
|
|
84
|
+
return (filter_state.feed(raw) + filter_state.finish()).lstrip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def strip_visible_tool_call_artifact_suffix(text: Any) -> str:
|
|
88
|
+
raw = str(text or "")
|
|
89
|
+
if "call" not in raw or "ignore" not in raw:
|
|
90
|
+
return raw
|
|
91
|
+
return VISIBLE_TOOL_CALL_ARTIFACT_SUFFIX_RE.sub("", raw)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class VisibleToolCallArtifactFilter:
|
|
95
|
+
def __init__(self, hold_chars: int = VISIBLE_TOOL_CALL_ARTIFACT_HOLD_CHARS) -> None:
|
|
96
|
+
self.hold_chars = max(16, int(hold_chars))
|
|
97
|
+
self.pending = ""
|
|
98
|
+
self.stripped = False
|
|
99
|
+
|
|
100
|
+
def feed(self, text: Any) -> str:
|
|
101
|
+
raw = str(text or "")
|
|
102
|
+
if not raw:
|
|
103
|
+
return ""
|
|
104
|
+
value = self.pending + raw
|
|
105
|
+
if len(value) <= self.hold_chars:
|
|
106
|
+
self.pending = value
|
|
107
|
+
return ""
|
|
108
|
+
emit_length = len(value) - self.hold_chars
|
|
109
|
+
self.pending = value[emit_length:]
|
|
110
|
+
return value[:emit_length]
|
|
111
|
+
|
|
112
|
+
def finish(self) -> str:
|
|
113
|
+
pending = self.pending
|
|
114
|
+
self.pending = ""
|
|
115
|
+
stripped = strip_visible_tool_call_artifact_suffix(pending)
|
|
116
|
+
self.stripped = self.stripped or stripped != pending
|
|
117
|
+
return stripped
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = [
|
|
121
|
+
"VISIBLE_THINKING_MARKUP_PREFIXES",
|
|
122
|
+
"VISIBLE_THINKING_MARKUP_TAG_RE",
|
|
123
|
+
"VISIBLE_TOOL_CALL_ARTIFACT_HOLD_CHARS",
|
|
124
|
+
"VISIBLE_TOOL_CALL_ARTIFACT_SUFFIX_RE",
|
|
125
|
+
"VisibleThinkingMarkupFilter",
|
|
126
|
+
"VisibleToolCallArtifactFilter",
|
|
127
|
+
"strip_visible_thinking_markup",
|
|
128
|
+
"strip_visible_tool_call_artifact_suffix",
|
|
129
|
+
"visible_thinking_markup_partial_start",
|
|
130
|
+
]
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""Startup web endpoint options and optional Tailscale Serve integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ipaddress
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import urllib.parse
|
|
11
|
+
from collections.abc import MutableMapping, Sequence
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_TRUE = {"1", "true", "yes", "on"}
|
|
18
|
+
_WILDCARD_HOSTS = {"0.0.0.0", "::", "[::]"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class TailscaleNode:
|
|
23
|
+
ipv4: str
|
|
24
|
+
dns_name: str
|
|
25
|
+
online: bool = True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class WebEndpointReport:
|
|
30
|
+
local_url: str
|
|
31
|
+
tailscale_ip_url: str = ""
|
|
32
|
+
tailscale_https_url: str = ""
|
|
33
|
+
tailscale_https_command: str = ""
|
|
34
|
+
|
|
35
|
+
def status_lines(self) -> list[str]:
|
|
36
|
+
lines = [f"web: {self.local_url}"]
|
|
37
|
+
if self.tailscale_ip_url:
|
|
38
|
+
lines.append(f"web_tailscale_http: {self.tailscale_ip_url}")
|
|
39
|
+
if self.tailscale_https_url:
|
|
40
|
+
lines.append(f"web_tailscale_https: {self.tailscale_https_url}")
|
|
41
|
+
elif self.tailscale_https_command:
|
|
42
|
+
lines.append(f"web_tailscale_https_recommended: {self.tailscale_https_command}")
|
|
43
|
+
return lines
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class WebBackendSettings:
|
|
48
|
+
enabled: bool = False
|
|
49
|
+
host: str = "127.0.0.1"
|
|
50
|
+
port: int = 0
|
|
51
|
+
tailscale_https: bool = False
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def client_host(self) -> str:
|
|
55
|
+
return "127.0.0.1" if self.host in _WILDCARD_HOSTS else self.host
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def web_backend_settings(config: dict[str, Any] | None) -> WebBackendSettings:
|
|
59
|
+
raw = config.get("web_backend") if isinstance(config, dict) else {}
|
|
60
|
+
values = raw if isinstance(raw, dict) else {}
|
|
61
|
+
host = str(values.get("host") or "127.0.0.1").strip()
|
|
62
|
+
if not host or any(character.isspace() for character in host):
|
|
63
|
+
host = "127.0.0.1"
|
|
64
|
+
try:
|
|
65
|
+
port = int(values.get("port") or 0)
|
|
66
|
+
except (TypeError, ValueError):
|
|
67
|
+
port = 0
|
|
68
|
+
if not 1 <= port <= 65535:
|
|
69
|
+
port = 0
|
|
70
|
+
tailscale_https = bool(values.get("tailscale_https", False))
|
|
71
|
+
enabled_value = values.get("enabled")
|
|
72
|
+
enabled = (
|
|
73
|
+
bool(enabled_value)
|
|
74
|
+
if enabled_value is not None
|
|
75
|
+
else bool(tailscale_https or host != "127.0.0.1" or port)
|
|
76
|
+
)
|
|
77
|
+
return WebBackendSettings(enabled, host, port, tailscale_https)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_saved_web_backend(path: os.PathLike[str] | str) -> WebBackendSettings:
|
|
81
|
+
try:
|
|
82
|
+
payload = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
83
|
+
except (OSError, ValueError, TypeError):
|
|
84
|
+
payload = {}
|
|
85
|
+
return web_backend_settings(payload if isinstance(payload, dict) else {})
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def web_backend_summary(config: dict[str, Any], effective_port: int) -> str:
|
|
89
|
+
settings = web_backend_settings(config)
|
|
90
|
+
port = settings.port or effective_port or int(config.get("_effective_web_port") or 0)
|
|
91
|
+
mode = " + Tailscale HTTPS" if settings.tailscale_https else ""
|
|
92
|
+
state = "on" if settings.enabled else "off"
|
|
93
|
+
return f"{state} · {settings.host}:{port or 'auto'}{mode}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def web_backend_panel_rows(
|
|
97
|
+
config: dict[str, Any], effective_port: int
|
|
98
|
+
) -> tuple[list[str], list[str]]:
|
|
99
|
+
settings = web_backend_settings(config)
|
|
100
|
+
port = settings.port or effective_port
|
|
101
|
+
node = discover_tailscale_node()
|
|
102
|
+
tailscale = "on" if settings.tailscale_https else "off"
|
|
103
|
+
if node and node.dns_name:
|
|
104
|
+
tailscale += f" · {node.dns_name}:{port}"
|
|
105
|
+
else:
|
|
106
|
+
tailscale += " · not detected"
|
|
107
|
+
return (
|
|
108
|
+
[
|
|
109
|
+
f"Web backend [{'on' if settings.enabled else 'off'}]",
|
|
110
|
+
f"Web bind address [{settings.host}]",
|
|
111
|
+
f"Web port [{port}]",
|
|
112
|
+
f"Tailscale HTTPS [{tailscale}]",
|
|
113
|
+
"Back",
|
|
114
|
+
],
|
|
115
|
+
["enabled", "host", "port", "tailscale", "back"],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def update_web_backend_config(
|
|
120
|
+
config: dict[str, Any], key: str, value: Any, effective_port: int
|
|
121
|
+
) -> list[str]:
|
|
122
|
+
current = web_backend_settings(config)
|
|
123
|
+
enabled = current.enabled
|
|
124
|
+
host = current.host
|
|
125
|
+
port = current.port
|
|
126
|
+
tailscale_https = current.tailscale_https
|
|
127
|
+
if key == "enabled":
|
|
128
|
+
enabled = bool(value)
|
|
129
|
+
elif key == "host":
|
|
130
|
+
host, embedded_port = _normalize_web_address(str(value))
|
|
131
|
+
enabled = True
|
|
132
|
+
if embedded_port is not None:
|
|
133
|
+
port = embedded_port
|
|
134
|
+
elif key == "port":
|
|
135
|
+
port = _valid_port(str(value), "web port")
|
|
136
|
+
enabled = True
|
|
137
|
+
elif key == "tailscale":
|
|
138
|
+
tailscale_https = bool(value)
|
|
139
|
+
enabled = True
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(f"unknown web backend setting: {key}")
|
|
142
|
+
config["web_backend"] = {
|
|
143
|
+
"enabled": enabled,
|
|
144
|
+
"host": host,
|
|
145
|
+
"port": port or effective_port,
|
|
146
|
+
"tailscale_https": tailscale_https,
|
|
147
|
+
}
|
|
148
|
+
external = host in _WILDCARD_HOSTS or not _is_loopback(host)
|
|
149
|
+
config["router_debug_external_access"] = external
|
|
150
|
+
config["router_debug_external_access_confirmed"] = external
|
|
151
|
+
scheme = "https" if tailscale_https else "http"
|
|
152
|
+
return [
|
|
153
|
+
f"Web backend: {'on' if enabled else 'off'} · {host}:{port or effective_port}.",
|
|
154
|
+
f"Tailscale HTTPS: {'on' if tailscale_https else 'off'}.",
|
|
155
|
+
f"Runtime is restarting so {scheme} endpoint settings apply now.",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def apply_startup_web_options(
|
|
160
|
+
argv: list[str],
|
|
161
|
+
environ: MutableMapping[str, str],
|
|
162
|
+
) -> list[str]:
|
|
163
|
+
"""Consume Ciel-owned web flags before runtime path constants are imported."""
|
|
164
|
+
output = [argv[0]] if argv else []
|
|
165
|
+
index = 1
|
|
166
|
+
passthrough = False
|
|
167
|
+
while index < len(argv):
|
|
168
|
+
argument = argv[index]
|
|
169
|
+
if passthrough:
|
|
170
|
+
output.append(argument)
|
|
171
|
+
index += 1
|
|
172
|
+
continue
|
|
173
|
+
if argument == "--":
|
|
174
|
+
passthrough = True
|
|
175
|
+
output.append(argument)
|
|
176
|
+
index += 1
|
|
177
|
+
continue
|
|
178
|
+
name, separator, inline_value = argument.partition("=")
|
|
179
|
+
if name in {"--ca-web-address", "--ca-web-host"}:
|
|
180
|
+
value, index = _option_value(argv, index, inline_value if separator else "", name)
|
|
181
|
+
host, embedded_port = _normalize_web_address(value)
|
|
182
|
+
_apply_web_host(host, environ)
|
|
183
|
+
environ["CIEL_RUNTIME_WEB_START_REQUESTED"] = "1"
|
|
184
|
+
if embedded_port is not None:
|
|
185
|
+
environ["CIEL_RUNTIME_ROUTER_PORT"] = str(embedded_port)
|
|
186
|
+
continue
|
|
187
|
+
if name == "--ca-web-port":
|
|
188
|
+
value, index = _option_value(argv, index, inline_value if separator else "", name)
|
|
189
|
+
environ["CIEL_RUNTIME_ROUTER_PORT"] = str(_valid_port(value, name))
|
|
190
|
+
environ["CIEL_RUNTIME_WEB_START_REQUESTED"] = "1"
|
|
191
|
+
continue
|
|
192
|
+
if name == "--ca-tailscale-https":
|
|
193
|
+
environ["CIEL_RUNTIME_TAILSCALE_HTTPS"] = "1"
|
|
194
|
+
environ["CIEL_RUNTIME_WEB_START_REQUESTED"] = "1"
|
|
195
|
+
if separator and inline_value:
|
|
196
|
+
environ["CIEL_RUNTIME_TAILSCALE_HTTPS_PORT"] = str(
|
|
197
|
+
_valid_port(inline_value, name)
|
|
198
|
+
)
|
|
199
|
+
index += 1
|
|
200
|
+
continue
|
|
201
|
+
output.append(argument)
|
|
202
|
+
index += 1
|
|
203
|
+
return output
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _option_value(
|
|
207
|
+
argv: Sequence[str], index: int, inline_value: str, option: str
|
|
208
|
+
) -> tuple[str, int]:
|
|
209
|
+
if inline_value:
|
|
210
|
+
return inline_value, index + 1
|
|
211
|
+
if index + 1 >= len(argv) or argv[index + 1] == "--":
|
|
212
|
+
raise SystemExit(f"{option} requires a value")
|
|
213
|
+
return argv[index + 1], index + 2
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _normalize_web_address(value: str) -> tuple[str, int | None]:
|
|
217
|
+
text = value.strip()
|
|
218
|
+
if not text:
|
|
219
|
+
raise SystemExit("--ca-web-address requires a host or URL")
|
|
220
|
+
parsed = urllib.parse.urlparse(text if "://" in text else f"//{text}")
|
|
221
|
+
if parsed.scheme and parsed.scheme not in {"http", "https"}:
|
|
222
|
+
raise SystemExit("--ca-web-address supports only http:// or https:// URLs")
|
|
223
|
+
if parsed.path not in {"", "/"} or parsed.query or parsed.fragment:
|
|
224
|
+
raise SystemExit("--ca-web-address must not include a path, query, or fragment")
|
|
225
|
+
try:
|
|
226
|
+
port = parsed.port
|
|
227
|
+
except ValueError as exc:
|
|
228
|
+
raise SystemExit(f"invalid web address port: {exc}") from exc
|
|
229
|
+
host = str(parsed.hostname or "").strip()
|
|
230
|
+
if not host or any(character.isspace() for character in host):
|
|
231
|
+
raise SystemExit("--ca-web-address contains an invalid host")
|
|
232
|
+
return host, port
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _valid_port(value: str, option: str) -> int:
|
|
236
|
+
try:
|
|
237
|
+
port = int(value)
|
|
238
|
+
except (TypeError, ValueError) as exc:
|
|
239
|
+
raise SystemExit(f"{option} requires a numeric port") from exc
|
|
240
|
+
if not 1 <= port <= 65535:
|
|
241
|
+
raise SystemExit(f"{option} port must be between 1 and 65535")
|
|
242
|
+
return port
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _apply_web_host(host: str, environ: MutableMapping[str, str]) -> None:
|
|
246
|
+
environ["CIEL_RUNTIME_ROUTER_BIND_HOST"] = host
|
|
247
|
+
environ["CIEL_RUNTIME_ROUTER_CLIENT_HOST"] = (
|
|
248
|
+
"127.0.0.1" if host in _WILDCARD_HOSTS else host
|
|
249
|
+
)
|
|
250
|
+
if host in _WILDCARD_HOSTS or not _is_loopback(host):
|
|
251
|
+
environ["CIEL_RUNTIME_ROUTER_DEBUG_EXTERNAL"] = "1"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _is_loopback(host: str) -> bool:
|
|
255
|
+
if host.lower() == "localhost":
|
|
256
|
+
return True
|
|
257
|
+
try:
|
|
258
|
+
return ipaddress.ip_address(host).is_loopback
|
|
259
|
+
except ValueError:
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def discover_tailscale_node(*, timeout: float = 2.0) -> TailscaleNode | None:
|
|
264
|
+
executable = shutil.which("tailscale")
|
|
265
|
+
if not executable:
|
|
266
|
+
return None
|
|
267
|
+
try:
|
|
268
|
+
completed = subprocess.run(
|
|
269
|
+
[executable, "status", "--json"],
|
|
270
|
+
capture_output=True,
|
|
271
|
+
text=True,
|
|
272
|
+
timeout=timeout,
|
|
273
|
+
check=False,
|
|
274
|
+
)
|
|
275
|
+
if completed.returncode != 0:
|
|
276
|
+
return None
|
|
277
|
+
payload = json.loads(completed.stdout)
|
|
278
|
+
except (OSError, subprocess.SubprocessError, json.JSONDecodeError):
|
|
279
|
+
return None
|
|
280
|
+
if str(payload.get("BackendState") or "").lower() != "running":
|
|
281
|
+
return None
|
|
282
|
+
self_node = payload.get("Self") if isinstance(payload.get("Self"), dict) else {}
|
|
283
|
+
addresses = self_node.get("TailscaleIPs") or payload.get("TailscaleIPs") or []
|
|
284
|
+
ipv4 = next((str(item) for item in addresses if _is_ipv4(item)), "")
|
|
285
|
+
dns_name = str(self_node.get("DNSName") or "").strip().rstrip(".")
|
|
286
|
+
if not ipv4 and not dns_name:
|
|
287
|
+
return None
|
|
288
|
+
return TailscaleNode(ipv4=ipv4, dns_name=dns_name, online=bool(self_node.get("Online", True)))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _is_ipv4(value: Any) -> bool:
|
|
292
|
+
try:
|
|
293
|
+
return ipaddress.ip_address(str(value)).version == 4
|
|
294
|
+
except ValueError:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def tailscale_https_url_for_target(
|
|
299
|
+
node: TailscaleNode,
|
|
300
|
+
router_port: int,
|
|
301
|
+
*,
|
|
302
|
+
timeout: float = 2.0,
|
|
303
|
+
) -> str:
|
|
304
|
+
if not node.dns_name:
|
|
305
|
+
return ""
|
|
306
|
+
executable = shutil.which("tailscale")
|
|
307
|
+
if not executable:
|
|
308
|
+
return ""
|
|
309
|
+
try:
|
|
310
|
+
completed = subprocess.run(
|
|
311
|
+
[executable, "serve", "status", "--json"],
|
|
312
|
+
capture_output=True,
|
|
313
|
+
text=True,
|
|
314
|
+
timeout=timeout,
|
|
315
|
+
check=False,
|
|
316
|
+
)
|
|
317
|
+
if completed.returncode != 0:
|
|
318
|
+
return ""
|
|
319
|
+
payload = json.loads(completed.stdout)
|
|
320
|
+
except (OSError, subprocess.SubprocessError, json.JSONDecodeError):
|
|
321
|
+
return ""
|
|
322
|
+
target = f"http://127.0.0.1:{router_port}"
|
|
323
|
+
web = payload.get("Web") if isinstance(payload.get("Web"), dict) else {}
|
|
324
|
+
tcp = payload.get("TCP") if isinstance(payload.get("TCP"), dict) else {}
|
|
325
|
+
for authority, config in web.items():
|
|
326
|
+
handlers = config.get("Handlers") if isinstance(config, dict) else {}
|
|
327
|
+
if not isinstance(handlers, dict):
|
|
328
|
+
continue
|
|
329
|
+
if not any(
|
|
330
|
+
isinstance(handler, dict) and str(handler.get("Proxy") or "").rstrip("/") == target
|
|
331
|
+
for handler in handlers.values()
|
|
332
|
+
):
|
|
333
|
+
continue
|
|
334
|
+
port_text = str(authority).rsplit(":", 1)[-1]
|
|
335
|
+
port_config = tcp.get(port_text) if isinstance(tcp, dict) else None
|
|
336
|
+
if not isinstance(port_config, dict) or not port_config.get("HTTPS"):
|
|
337
|
+
continue
|
|
338
|
+
port = int(port_text) if port_text.isdigit() else 443
|
|
339
|
+
suffix = "" if port == 443 else f":{port}"
|
|
340
|
+
return f"https://{node.dns_name}{suffix}"
|
|
341
|
+
return ""
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def build_web_endpoint_report(
|
|
345
|
+
client_host: str,
|
|
346
|
+
bind_host: str,
|
|
347
|
+
router_port: int,
|
|
348
|
+
) -> WebEndpointReport:
|
|
349
|
+
display_host = _url_host(client_host or "127.0.0.1")
|
|
350
|
+
local_url = f"http://{display_host}:{router_port}/"
|
|
351
|
+
node = discover_tailscale_node()
|
|
352
|
+
if node is None:
|
|
353
|
+
return WebEndpointReport(local_url=local_url)
|
|
354
|
+
exposed = bind_host in _WILDCARD_HOSTS or bind_host in {node.ipv4, node.dns_name}
|
|
355
|
+
tailscale_ip_url = f"http://{node.ipv4}:{router_port}/" if exposed and node.ipv4 else ""
|
|
356
|
+
https_url = tailscale_https_url_for_target(node, router_port)
|
|
357
|
+
command = ""
|
|
358
|
+
if not https_url and node.dns_name:
|
|
359
|
+
command = (
|
|
360
|
+
f"ciel-runtime --ca-web-port {router_port} --ca-tailscale-https={router_port}"
|
|
361
|
+
)
|
|
362
|
+
return WebEndpointReport(
|
|
363
|
+
local_url=local_url,
|
|
364
|
+
tailscale_ip_url=tailscale_ip_url,
|
|
365
|
+
tailscale_https_url=f"{https_url}/" if https_url else "",
|
|
366
|
+
tailscale_https_command=command,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def configure_tailscale_https(router_port: int, https_port: int | None = None) -> list[str]:
|
|
371
|
+
node = discover_tailscale_node()
|
|
372
|
+
if node is None or not node.dns_name:
|
|
373
|
+
return ["Tailscale HTTPS was requested, but no online MagicDNS node was detected."]
|
|
374
|
+
executable = shutil.which("tailscale")
|
|
375
|
+
if not executable:
|
|
376
|
+
return ["Tailscale HTTPS was requested, but the tailscale CLI was not found."]
|
|
377
|
+
public_port = https_port or router_port
|
|
378
|
+
command = [
|
|
379
|
+
executable,
|
|
380
|
+
"serve",
|
|
381
|
+
f"--https={public_port}",
|
|
382
|
+
"--bg",
|
|
383
|
+
"--yes",
|
|
384
|
+
f"http://127.0.0.1:{router_port}",
|
|
385
|
+
]
|
|
386
|
+
try:
|
|
387
|
+
completed = subprocess.run(
|
|
388
|
+
command,
|
|
389
|
+
capture_output=True,
|
|
390
|
+
text=True,
|
|
391
|
+
timeout=20.0,
|
|
392
|
+
check=False,
|
|
393
|
+
)
|
|
394
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
395
|
+
return [f"Tailscale HTTPS setup failed: {type(exc).__name__}: {exc}"]
|
|
396
|
+
if completed.returncode != 0:
|
|
397
|
+
detail = (completed.stderr or completed.stdout or "unknown error").strip()
|
|
398
|
+
return [f"Tailscale HTTPS setup failed: {detail}"]
|
|
399
|
+
suffix = "" if public_port == 443 else f":{public_port}"
|
|
400
|
+
return [
|
|
401
|
+
f"Tailscale HTTPS: https://{node.dns_name}{suffix}/",
|
|
402
|
+
f"Tailscale Serve target: http://127.0.0.1:{router_port}",
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def configure_requested_web_endpoints(
|
|
407
|
+
router_port: int,
|
|
408
|
+
client_host: str,
|
|
409
|
+
bind_host: str,
|
|
410
|
+
environ: MutableMapping[str, str] | None = None,
|
|
411
|
+
config: dict[str, Any] | None = None,
|
|
412
|
+
) -> list[str]:
|
|
413
|
+
environment = os.environ if environ is None else environ
|
|
414
|
+
lines: list[str] = []
|
|
415
|
+
saved = web_backend_settings(config)
|
|
416
|
+
requested = (
|
|
417
|
+
str(environment.get("CIEL_RUNTIME_TAILSCALE_HTTPS") or "").lower() in _TRUE
|
|
418
|
+
or (saved.enabled and saved.tailscale_https)
|
|
419
|
+
)
|
|
420
|
+
if requested:
|
|
421
|
+
configured_port = str(environment.get("CIEL_RUNTIME_TAILSCALE_HTTPS_PORT") or "").strip()
|
|
422
|
+
https_port = _valid_port(configured_port, "CIEL_RUNTIME_TAILSCALE_HTTPS_PORT") if configured_port else None
|
|
423
|
+
lines.extend(configure_tailscale_https(router_port, https_port))
|
|
424
|
+
lines.extend(build_web_endpoint_report(client_host, bind_host, router_port).status_lines())
|
|
425
|
+
return lines
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _url_host(host: str) -> str:
|
|
429
|
+
return f"[{host}]" if ":" in host and not host.startswith("[") else host
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
__all__ = [
|
|
433
|
+
"TailscaleNode",
|
|
434
|
+
"WebBackendSettings",
|
|
435
|
+
"WebEndpointReport",
|
|
436
|
+
"apply_startup_web_options",
|
|
437
|
+
"build_web_endpoint_report",
|
|
438
|
+
"configure_requested_web_endpoints",
|
|
439
|
+
"configure_tailscale_https",
|
|
440
|
+
"discover_tailscale_node",
|
|
441
|
+
"load_saved_web_backend",
|
|
442
|
+
"tailscale_https_url_for_target",
|
|
443
|
+
"update_web_backend_config",
|
|
444
|
+
"web_backend_panel_rows",
|
|
445
|
+
"web_backend_settings",
|
|
446
|
+
"web_backend_summary",
|
|
447
|
+
]
|