@oneciel-ai/ciel-runtime 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ciel_runtime.py +2555 -9635
- package/ciel_runtime_support/advisor_request_builder.py +8 -21
- package/ciel_runtime_support/anthropic_tool_turns.py +13 -8
- package/ciel_runtime_support/architecture.py +68 -0
- package/ciel_runtime_support/architecture_budget.py +1 -1
- package/ciel_runtime_support/channel_connection_context.py +233 -0
- package/ciel_runtime_support/channel_delivery_context.py +332 -0
- package/ciel_runtime_support/channel_mcp_context.py +313 -0
- package/ciel_runtime_support/channel_mcp_discovery.py +47 -0
- package/ciel_runtime_support/channel_mcp_transport.py +5 -1
- package/ciel_runtime_support/channel_message_context.py +212 -0
- package/ciel_runtime_support/channel_message_repository.py +14 -3
- package/ciel_runtime_support/channel_pending_injection.py +9 -0
- package/ciel_runtime_support/channel_probe_launch_context.py +213 -0
- package/ciel_runtime_support/channel_replay_policy.py +38 -0
- package/ciel_runtime_support/channel_runtime_environment.py +8 -0
- package/ciel_runtime_support/channel_session_context.py +236 -0
- package/ciel_runtime_support/channel_terminal_context.py +350 -0
- package/ciel_runtime_support/channel_wake_context.py +532 -0
- package/ciel_runtime_support/claude_environment.py +60 -0
- package/ciel_runtime_support/claude_launch_assembly.py +249 -0
- package/ciel_runtime_support/claude_router.py +62 -12
- package/ciel_runtime_support/cli_application_context.py +132 -0
- package/ciel_runtime_support/cli_assembly.py +50 -0
- package/ciel_runtime_support/codex_backend_context.py +363 -0
- package/ciel_runtime_support/codex_config.py +13 -1
- package/ciel_runtime_support/codex_launch_assembly.py +213 -0
- package/ciel_runtime_support/codex_launch_configuration.py +30 -1
- package/ciel_runtime_support/codex_mcp_integration.py +90 -8
- package/ciel_runtime_support/codex_model_catalog.py +4 -1
- package/ciel_runtime_support/codex_reasoning_rejects.py +225 -0
- package/ciel_runtime_support/codex_router.py +38 -8
- package/ciel_runtime_support/codex_turn_recovery.py +154 -0
- package/ciel_runtime_support/config_migrations.py +103 -0
- package/ciel_runtime_support/config_repository.py +30 -0
- package/ciel_runtime_support/configuration_cli.py +38 -0
- package/ciel_runtime_support/context_compaction.py +9 -4
- package/ciel_runtime_support/credential_management.py +12 -0
- package/ciel_runtime_support/credentials.py +12 -0
- package/ciel_runtime_support/github_copilot_oauth.py +2 -2
- package/ciel_runtime_support/hosted_formula_tools.py +216 -0
- package/ciel_runtime_support/kimi_runtime_context.py +208 -0
- package/ciel_runtime_support/llm_preset_context.py +338 -0
- package/ciel_runtime_support/managed_mcp_config.py +8 -4
- package/ciel_runtime_support/mcp_configuration_context.py +291 -0
- package/ciel_runtime_support/mcp_http_proxy.py +14 -8
- package/ciel_runtime_support/mcp_probe_transport.py +47 -15
- package/ciel_runtime_support/mcp_transport.py +14 -1
- package/ciel_runtime_support/native_context_recovery.py +72 -0
- package/ciel_runtime_support/ollama_catalog_context.py +213 -0
- package/ciel_runtime_support/ollama_stream_collection.py +103 -0
- package/ciel_runtime_support/ollama_thinking.py +6 -1
- package/ciel_runtime_support/ollama_wire_projection.py +157 -0
- package/ciel_runtime_support/openai_forwarding.py +32 -10
- package/ciel_runtime_support/openai_responses_router.py +12 -0
- package/ciel_runtime_support/package_lifecycle.py +39 -0
- package/ciel_runtime_support/prelaunch_assembly.py +37 -0
- package/ciel_runtime_support/prelaunch_panel_context.py +418 -0
- package/ciel_runtime_support/prelaunch_shell_context.py +394 -0
- package/ciel_runtime_support/prompt_compaction.py +144 -0
- package/ciel_runtime_support/prompt_injection.py +45 -0
- package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +1 -1
- package/ciel_runtime_support/protocols/chat_projection.py +85 -5
- package/ciel_runtime_support/protocols/conversation_turn_policy.py +43 -0
- package/ciel_runtime_support/protocols/ollama_chat.py +31 -0
- package/ciel_runtime_support/protocols/ollama_response.py +57 -5
- package/ciel_runtime_support/protocols/openai_reasoning.py +5 -2
- package/ciel_runtime_support/protocols/openai_responses.py +61 -15
- package/ciel_runtime_support/provider_adapters.py +26 -0
- package/ciel_runtime_support/provider_administration_context.py +207 -0
- package/ciel_runtime_support/provider_config_mutations.py +3 -0
- package/ciel_runtime_support/provider_model_catalog_context.py +137 -0
- package/ciel_runtime_support/provider_model_context.py +107 -0
- package/ciel_runtime_support/provider_model_metadata_context.py +197 -0
- package/ciel_runtime_support/provider_model_selection.py +10 -3
- package/ciel_runtime_support/provider_models.py +45 -2
- package/ciel_runtime_support/provider_option_cli.py +19 -0
- package/ciel_runtime_support/provider_policy.py +1 -1
- package/ciel_runtime_support/provider_readiness_context.py +189 -0
- package/ciel_runtime_support/provider_request_builder.py +64 -28
- package/ciel_runtime_support/provider_responses_passthrough.py +21 -2
- package/ciel_runtime_support/provider_timeout_policy.py +54 -0
- package/ciel_runtime_support/provider_tool_policy.py +9 -1
- package/ciel_runtime_support/providers/__init__.py +6 -0
- package/ciel_runtime_support/providers/alibaba.py +634 -0
- package/ciel_runtime_support/providers/catalog.py +24 -16
- package/ciel_runtime_support/providers/deepseek.py +73 -0
- package/ciel_runtime_support/providers/github_copilot_oauth.py +22 -1
- package/ciel_runtime_support/providers/kimi.py +69 -9
- package/ciel_runtime_support/providers/ollama.py +8 -0
- package/ciel_runtime_support/providers/ollama_context.py +21 -2
- package/ciel_runtime_support/providers/vllm.py +7 -1
- package/ciel_runtime_support/response_collection.py +68 -18
- package/ciel_runtime_support/response_collection_context.py +391 -0
- package/ciel_runtime_support/response_stream_context.py +555 -0
- package/ciel_runtime_support/responses_input_compatibility.py +121 -0
- package/ciel_runtime_support/responses_usage_observer.py +83 -0
- package/ciel_runtime_support/router_client_lifecycle.py +1 -0
- package/ciel_runtime_support/router_http.py +245 -3
- package/ciel_runtime_support/router_observability_context.py +251 -0
- package/ciel_runtime_support/router_process_context.py +200 -0
- package/ciel_runtime_support/router_process_lifecycle.py +2 -0
- package/ciel_runtime_support/router_request_assembly.py +399 -0
- package/ciel_runtime_support/router_request_context.py +215 -0
- package/ciel_runtime_support/router_server_context.py +84 -0
- package/ciel_runtime_support/runaway_output_guard.py +488 -0
- package/ciel_runtime_support/runtime_asset_assembly.py +147 -0
- package/ciel_runtime_support/runtime_asset_context.py +297 -0
- package/ciel_runtime_support/runtime_constants.py +16 -1
- package/ciel_runtime_support/runtime_launch.py +9 -5
- package/ciel_runtime_support/runtime_launch_context.py +130 -0
- package/ciel_runtime_support/runtime_maintenance_assembly.py +60 -0
- package/ciel_runtime_support/runtime_maintenance_context.py +309 -0
- package/ciel_runtime_support/runtime_maintenance_services.py +265 -0
- package/ciel_runtime_support/runtime_paths.py +60 -40
- package/ciel_runtime_support/runtime_primitives.py +78 -0
- package/ciel_runtime_support/speech_http_controller.py +335 -0
- package/ciel_runtime_support/sse_stream_collection.py +236 -0
- package/ciel_runtime_support/statusline_script.py +57 -8
- package/ciel_runtime_support/streaming_anthropic.py +361 -24
- package/ciel_runtime_support/tool_schema.py +40 -2
- package/ciel_runtime_support/tool_side_effect_dedupe.py +117 -12
- package/ciel_runtime_support/upstream_dump.py +68 -0
- package/ciel_runtime_support/upstream_retry_context.py +259 -0
- package/ciel_runtime_support/web_ui.py +248 -1
- package/ciel_runtime_support/workspace_router_selection.py +86 -0
- package/docs/COLAB_SPEECH.md +32 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +4 -1
- package/scripts/colab/__pycache__/bootstrap_moss_tts.cpython-311.pyc +0 -0
- package/scripts/colab/__pycache__/bootstrap_qwen_asr.cpython-311.pyc +0 -0
- package/scripts/colab/bootstrap_moss_tts.py +106 -0
- package/scripts/colab/bootstrap_qwen_asr.py +106 -0
- package/scripts/configure_speech_workers.py +37 -0
- package/scripts/deploy_colab_speech.ps1 +47 -0
|
@@ -34,6 +34,95 @@ def apply_config_migrations(cfg: dict[str, Any], *, policy: ConfigMigrationPolic
|
|
|
34
34
|
migrations = {}
|
|
35
35
|
cfg["migrations"] = migrations
|
|
36
36
|
|
|
37
|
+
marker = "alibaba_provider_catalogs_20260806"
|
|
38
|
+
if not migrations.get(marker):
|
|
39
|
+
providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
|
|
40
|
+
catalogs = {
|
|
41
|
+
"alicode": (
|
|
42
|
+
"qwen3.7-plus", "qwen3.6-plus", "kimi-k2.5", "glm-5",
|
|
43
|
+
"MiniMax-M2.5", "qwen3.5-plus", "qwen3-max-2026-01-23",
|
|
44
|
+
"qwen3-coder-next", "qwen3-coder-plus", "glm-4.7",
|
|
45
|
+
),
|
|
46
|
+
"alicode-intl": (
|
|
47
|
+
"qwen3.7-plus", "qwen3.6-plus", "kimi-k2.5", "glm-5",
|
|
48
|
+
"MiniMax-M2.5", "qwen3.5-plus", "qwen3-max-2026-01-23",
|
|
49
|
+
"qwen3-coder-next", "qwen3-coder-plus", "glm-4.7",
|
|
50
|
+
),
|
|
51
|
+
"alims-intl": (
|
|
52
|
+
"qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus",
|
|
53
|
+
"qwen3.6-flash", "qwen3.5-plus", "qwen3-coder-plus",
|
|
54
|
+
"qwen3-coder-flash", "deepseek-v4-pro", "deepseek-v4-flash",
|
|
55
|
+
"glm-5.2", "kimi-k2.7-code", "MiniMax-M2.5",
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
for provider_name, models in catalogs.items():
|
|
59
|
+
pcfg = providers.get(provider_name)
|
|
60
|
+
if not isinstance(pcfg, dict):
|
|
61
|
+
continue
|
|
62
|
+
custom = pcfg.get("custom_models")
|
|
63
|
+
if not isinstance(custom, list):
|
|
64
|
+
custom = []
|
|
65
|
+
pcfg["custom_models"] = custom
|
|
66
|
+
known = {
|
|
67
|
+
normalize_model_id(provider_name, str(model))
|
|
68
|
+
for model in custom
|
|
69
|
+
if str(model).strip()
|
|
70
|
+
}
|
|
71
|
+
for model in models:
|
|
72
|
+
if normalize_model_id(provider_name, model) not in known:
|
|
73
|
+
custom.append(model)
|
|
74
|
+
migrations[marker] = True
|
|
75
|
+
|
|
76
|
+
marker = "alibaba_token_plan_singapore_20260806"
|
|
77
|
+
if not migrations.get(marker):
|
|
78
|
+
providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
|
|
79
|
+
pcfg = providers.get("alitoken")
|
|
80
|
+
if isinstance(pcfg, dict):
|
|
81
|
+
custom = pcfg.get("custom_models")
|
|
82
|
+
if not isinstance(custom, list):
|
|
83
|
+
custom = []
|
|
84
|
+
pcfg["custom_models"] = custom
|
|
85
|
+
models = (
|
|
86
|
+
"qwen3.8-max", "qwen3.7-max", "qwen3.7-plus",
|
|
87
|
+
"qwen3.6-plus", "qwen3.6-flash", "deepseek-v4-pro",
|
|
88
|
+
"deepseek-v4-flash", "deepseek-v3.2", "kimi-k2.7-code",
|
|
89
|
+
"kimi-k2.6", "kimi-k2.5", "glm-5.2", "glm-5.1", "glm-5",
|
|
90
|
+
"MiniMax-M2.5",
|
|
91
|
+
)
|
|
92
|
+
known = {
|
|
93
|
+
normalize_model_id("alitoken", str(model))
|
|
94
|
+
for model in custom
|
|
95
|
+
if str(model).strip()
|
|
96
|
+
}
|
|
97
|
+
custom.extend(
|
|
98
|
+
model
|
|
99
|
+
for model in models
|
|
100
|
+
if normalize_model_id("alitoken", model) not in known
|
|
101
|
+
)
|
|
102
|
+
pcfg["region"] = "ap-southeast-1"
|
|
103
|
+
migrations[marker] = True
|
|
104
|
+
|
|
105
|
+
marker = "alibaba_native_anthropic_routes_20260806"
|
|
106
|
+
if not migrations.get(marker):
|
|
107
|
+
providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
|
|
108
|
+
for provider_name in (
|
|
109
|
+
"alicode", "alicode-intl", "alims-intl", "alitoken",
|
|
110
|
+
"alitoken-individual",
|
|
111
|
+
):
|
|
112
|
+
pcfg = providers.get(provider_name)
|
|
113
|
+
if isinstance(pcfg, dict):
|
|
114
|
+
pcfg["native_compat"] = True
|
|
115
|
+
migrations[marker] = True
|
|
116
|
+
|
|
117
|
+
marker = "alibaba_token_plan_individual_20260806"
|
|
118
|
+
if not migrations.get(marker):
|
|
119
|
+
providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
|
|
120
|
+
pcfg = providers.get("alitoken-individual")
|
|
121
|
+
if isinstance(pcfg, dict):
|
|
122
|
+
pcfg["region"] = "ap-southeast-1"
|
|
123
|
+
pcfg["native_compat"] = True
|
|
124
|
+
migrations[marker] = True
|
|
125
|
+
|
|
37
126
|
marker = "ollama_cloud_deepseek_v4_flash_0731_20260803"
|
|
38
127
|
if not migrations.get(marker):
|
|
39
128
|
pcfg = cfg.get("providers", {}).get("ollama-cloud", {})
|
|
@@ -329,4 +418,18 @@ def apply_config_migrations(cfg: dict[str, Any], *, policy: ConfigMigrationPolic
|
|
|
329
418
|
pcfg["model_profile"] = "kimi-k3-1m"
|
|
330
419
|
migrations[marker] = True
|
|
331
420
|
|
|
421
|
+
marker = "kimi_k3_official_max_effort_20260806"
|
|
422
|
+
if not migrations.get(marker):
|
|
423
|
+
providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
|
|
424
|
+
pcfg = providers.get("kimi")
|
|
425
|
+
if isinstance(pcfg, dict):
|
|
426
|
+
current = normalize_model_id("kimi", str(pcfg.get("current_model") or ""))
|
|
427
|
+
if (
|
|
428
|
+
current in {KIMI_K3_MODEL, f"{KIMI_K3_MODEL}[1m]"}
|
|
429
|
+
and pcfg.get("model_profile") == "kimi-k3-1m"
|
|
430
|
+
and str(pcfg.get("effort_level") or "").lower() == "high"
|
|
431
|
+
):
|
|
432
|
+
pcfg["effort_level"] = "max"
|
|
433
|
+
migrations[marker] = True
|
|
434
|
+
|
|
332
435
|
__all__ = ["ConfigMigrationPolicy", "apply_config_migrations"]
|
|
@@ -26,6 +26,36 @@ def build_default_config(provider_defaults: dict[str, Any]) -> dict[str, Any]:
|
|
|
26
26
|
"port": 0,
|
|
27
27
|
"tailscale_https": False,
|
|
28
28
|
},
|
|
29
|
+
"speech": {
|
|
30
|
+
"asr": {
|
|
31
|
+
"enabled": False,
|
|
32
|
+
"base_url": "http://ciel-asr:8000",
|
|
33
|
+
"endpoint": "/v1/audio/transcriptions",
|
|
34
|
+
"model": "Qwen/Qwen3-ASR-0.6B",
|
|
35
|
+
"language": "auto",
|
|
36
|
+
"api_key": "",
|
|
37
|
+
"timeout_seconds": 300,
|
|
38
|
+
},
|
|
39
|
+
"tts": {
|
|
40
|
+
"enabled": False,
|
|
41
|
+
"base_url": "http://ciel-tts:8091",
|
|
42
|
+
"endpoint": "/v1/audio/speech",
|
|
43
|
+
"voices_endpoint": "/v1/audio/voices",
|
|
44
|
+
"model": "OpenMOSS-Team/MOSS-TTS-Nano",
|
|
45
|
+
"voice": "default",
|
|
46
|
+
"language": "ko",
|
|
47
|
+
"response_format": "wav",
|
|
48
|
+
"speed": 1.0,
|
|
49
|
+
"auto_speak": False,
|
|
50
|
+
"api_key": "",
|
|
51
|
+
"timeout_seconds": 300,
|
|
52
|
+
},
|
|
53
|
+
"tailscale": {
|
|
54
|
+
"enabled": True,
|
|
55
|
+
"asr_hostname": "ciel-asr",
|
|
56
|
+
"tts_hostname": "ciel-tts",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
29
59
|
"claude_code": {
|
|
30
60
|
"compat_prompt_for_non_anthropic": True,
|
|
31
61
|
"channels": [],
|
|
@@ -364,6 +364,44 @@ class ConfigurationCliController:
|
|
|
364
364
|
return aliases.get(normalized, normalized)
|
|
365
365
|
|
|
366
366
|
|
|
367
|
+
@dataclass(frozen=True, slots=True)
|
|
368
|
+
class ConfigurationCliCompatibilityApi:
|
|
369
|
+
controller: Callable[[], ConfigurationCliController]
|
|
370
|
+
|
|
371
|
+
def provider(self, args: Any) -> None:
|
|
372
|
+
self.controller().provider_command(args.name)
|
|
373
|
+
|
|
374
|
+
def base_url(self, args: Any) -> None:
|
|
375
|
+
self.controller().base_url_command(args.provider, args.url)
|
|
376
|
+
|
|
377
|
+
def model(self, args: Any) -> None:
|
|
378
|
+
self.controller().model_command(args.value)
|
|
379
|
+
|
|
380
|
+
def advisor_model(self, args: Any) -> None:
|
|
381
|
+
self.controller().advisor_model_command(args.value)
|
|
382
|
+
|
|
383
|
+
def models(self, args: Any) -> None:
|
|
384
|
+
self.controller().models_command(args.provider)
|
|
385
|
+
|
|
386
|
+
def log_level(self, args: Any) -> None:
|
|
387
|
+
self.controller().log_level_command(getattr(args, "value", None))
|
|
388
|
+
|
|
389
|
+
def language(self, args: Any) -> None:
|
|
390
|
+
self.controller().language_command(args.value)
|
|
391
|
+
|
|
392
|
+
def web_search(self, args: Any) -> None:
|
|
393
|
+
self.controller().web_search_command(args.value)
|
|
394
|
+
|
|
395
|
+
def web_fetch(self, args: Any) -> None:
|
|
396
|
+
self.controller().web_fetch_command(args.value)
|
|
397
|
+
|
|
398
|
+
def portable_provider_menu(self) -> int:
|
|
399
|
+
return self.controller().portable_provider_menu()
|
|
400
|
+
|
|
401
|
+
def portable_language_menu(self) -> int:
|
|
402
|
+
return self.controller().portable_language_menu()
|
|
403
|
+
|
|
404
|
+
|
|
367
405
|
__all__ = [
|
|
368
406
|
"ConfigurationCliConfigPorts",
|
|
369
407
|
"ConfigurationCliController",
|
|
@@ -38,6 +38,7 @@ class ContextCompactionProjection:
|
|
|
38
38
|
build_fallback_summary: Callable[..., str]
|
|
39
39
|
build_reduce_prompt: Callable[..., str]
|
|
40
40
|
log: Callable[[str, str], None]
|
|
41
|
+
apply_ollama_optional: Callable[..., dict[str, Any]]
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
@dataclass(frozen=True)
|
|
@@ -69,11 +70,15 @@ def request_context_summary(
|
|
|
69
70
|
{"role": "user", "content": prompt},
|
|
70
71
|
],
|
|
71
72
|
"stream": False,
|
|
72
|
-
"think": False,
|
|
73
|
-
"options": {"num_predict": max_tokens},
|
|
74
73
|
}
|
|
75
|
-
|
|
76
|
-
request
|
|
74
|
+
request = services.projection.apply_ollama_optional(
|
|
75
|
+
request,
|
|
76
|
+
provider,
|
|
77
|
+
model,
|
|
78
|
+
provider_config,
|
|
79
|
+
{},
|
|
80
|
+
output_limit=max_tokens,
|
|
81
|
+
)
|
|
77
82
|
operation = "ollama_chat"
|
|
78
83
|
elif wire == "openai":
|
|
79
84
|
request = {
|
|
@@ -33,6 +33,7 @@ class CredentialPersistencePorts:
|
|
|
33
33
|
clear_requested: Callable[[Any], bool]
|
|
34
34
|
rotation_name: Callable[[str, dict[str, Any]], str]
|
|
35
35
|
error_text: Callable[[Any], bool] = lambda _value: False
|
|
36
|
+
plausible: Callable[[Any], bool] = lambda value: bool(str(value or "").strip())
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
@dataclass(frozen=True, slots=True)
|
|
@@ -125,6 +126,7 @@ class CredentialManagementService:
|
|
|
125
126
|
def store_one(self, provider: str, key: str) -> list[str]:
|
|
126
127
|
if self.persistence.clear_requested(key):
|
|
127
128
|
return self.clear(provider)
|
|
129
|
+
self._reject_invalid_keys([key])
|
|
128
130
|
config = self.persistence.load_config()
|
|
129
131
|
provider_config = config["providers"][provider]
|
|
130
132
|
if self.external.enabled(provider):
|
|
@@ -174,6 +176,7 @@ class CredentialManagementService:
|
|
|
174
176
|
return self.clear(provider)
|
|
175
177
|
if not parsed:
|
|
176
178
|
raise SystemExit("No API keys provided; unchanged.")
|
|
179
|
+
self._reject_invalid_keys(parsed)
|
|
177
180
|
config = self.persistence.load_config()
|
|
178
181
|
provider_config = config["providers"][provider]
|
|
179
182
|
provider_config["api_key"] = parsed[0]
|
|
@@ -215,6 +218,15 @@ class CredentialManagementService:
|
|
|
215
218
|
return self.store_one(provider, keys[0])
|
|
216
219
|
raise SystemExit("No API key provided; unchanged.")
|
|
217
220
|
|
|
221
|
+
def _reject_invalid_keys(self, keys: list[str]) -> None:
|
|
222
|
+
invalid = [key for key in keys if not self.persistence.plausible(key)]
|
|
223
|
+
if invalid:
|
|
224
|
+
raise SystemExit(
|
|
225
|
+
"Refusing to store an API key with an invalid token shape "
|
|
226
|
+
f"({len(invalid)} item(s)). Control characters and whitespace "
|
|
227
|
+
"are not valid inside API keys. Unchanged."
|
|
228
|
+
)
|
|
229
|
+
|
|
218
230
|
@staticmethod
|
|
219
231
|
def _snapshot_other_provider_keys(
|
|
220
232
|
providers: dict[str, Any], target_provider: str
|
|
@@ -58,6 +58,18 @@ def plausible_api_key(value: Any) -> bool:
|
|
|
58
58
|
if not _KEY_PLAUSIBLE_RE.match(text):
|
|
59
59
|
return False
|
|
60
60
|
return not looks_like_error_text(text)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def transportable_api_key(value: Any) -> bool:
|
|
64
|
+
"""Reject control/whitespace corruption without assuming provider key length."""
|
|
65
|
+
|
|
66
|
+
text = str(value or "")
|
|
67
|
+
return bool(
|
|
68
|
+
text
|
|
69
|
+
and text == text.strip()
|
|
70
|
+
and not looks_like_error_text(text)
|
|
71
|
+
and not any(character.isspace() or ord(character) < 32 or ord(character) == 127 for character in text)
|
|
72
|
+
)
|
|
61
73
|
SECRET_TEXT_PATTERNS = (
|
|
62
74
|
re.compile(r"ak_key_[A-Za-z0-9_-]+_secret_[A-Za-z0-9_-]+"),
|
|
63
75
|
re.compile(r"(AINET_API_KEY\s*=\s*)(\S+)", re.IGNORECASE),
|
|
@@ -20,8 +20,8 @@ GITHUB_USER_URL = "https://api.github.com/user"
|
|
|
20
20
|
GITHUB_COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token"
|
|
21
21
|
GITHUB_API_VERSION = "2022-11-28"
|
|
22
22
|
COPILOT_API_VERSION = "2025-04-01"
|
|
23
|
-
COPILOT_VSCODE_VERSION = "1.
|
|
24
|
-
COPILOT_CHAT_VERSION = "0.
|
|
23
|
+
COPILOT_VSCODE_VERSION = "1.128.0"
|
|
24
|
+
COPILOT_CHAT_VERSION = "0.43.0"
|
|
25
25
|
COPILOT_USER_AGENT = f"GitHubCopilotChat/{COPILOT_CHAT_VERSION}"
|
|
26
26
|
|
|
27
27
|
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Append-only orchestration for provider-hosted Formula tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
import threading
|
|
8
|
+
import urllib.parse
|
|
9
|
+
import urllib.request
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Callable, Mapping
|
|
12
|
+
|
|
13
|
+
from .architecture import HostedToolPolicy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class HostedFormulaState:
|
|
18
|
+
formula_by_function: Mapping[str, str]
|
|
19
|
+
headers: Mapping[str, str]
|
|
20
|
+
policy: HostedToolPolicy
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def enabled(self) -> bool:
|
|
24
|
+
return bool(self.formula_by_function)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HostedFormulaToolService:
|
|
28
|
+
"""Load Formula schemas, execute calls, and append results to chat history."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
policy_for: Callable[[str, dict[str, Any]], HostedToolPolicy],
|
|
33
|
+
log: Callable[[str, str], None],
|
|
34
|
+
*,
|
|
35
|
+
open_url: Callable[..., Any] = urllib.request.urlopen,
|
|
36
|
+
max_rounds: int = 8,
|
|
37
|
+
) -> None:
|
|
38
|
+
self._policy_for = policy_for
|
|
39
|
+
self._log = log
|
|
40
|
+
self._open_url = open_url
|
|
41
|
+
self._max_rounds = max(1, max_rounds)
|
|
42
|
+
self._cache: dict[tuple[str, str], tuple[dict[str, Any], ...]] = {}
|
|
43
|
+
self._unavailable: set[str] = set()
|
|
44
|
+
self._lock = threading.Lock()
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _auth_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
48
|
+
projected = {"content-type": "application/json"}
|
|
49
|
+
authorization = headers.get("authorization") or headers.get("Authorization")
|
|
50
|
+
if authorization:
|
|
51
|
+
projected["authorization"] = str(authorization)
|
|
52
|
+
return projected
|
|
53
|
+
|
|
54
|
+
def _json_request(
|
|
55
|
+
self,
|
|
56
|
+
url: str,
|
|
57
|
+
headers: Mapping[str, str],
|
|
58
|
+
*,
|
|
59
|
+
payload: Mapping[str, Any] | None = None,
|
|
60
|
+
timeout: float,
|
|
61
|
+
) -> dict[str, Any]:
|
|
62
|
+
data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
63
|
+
request = urllib.request.Request(
|
|
64
|
+
url,
|
|
65
|
+
data=data,
|
|
66
|
+
headers=dict(headers),
|
|
67
|
+
method="GET" if data is None else "POST",
|
|
68
|
+
)
|
|
69
|
+
with self._open_url(request, timeout=timeout) as response:
|
|
70
|
+
decoded = json.loads(response.read().decode("utf-8", errors="replace"))
|
|
71
|
+
if not isinstance(decoded, dict):
|
|
72
|
+
raise RuntimeError("hosted Formula API returned non-object JSON")
|
|
73
|
+
return decoded
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _formula_path(formula: str) -> str:
|
|
77
|
+
return urllib.parse.quote(formula, safe="/:")
|
|
78
|
+
|
|
79
|
+
def _tools_for_formula(
|
|
80
|
+
self,
|
|
81
|
+
policy: HostedToolPolicy,
|
|
82
|
+
formula: str,
|
|
83
|
+
headers: Mapping[str, str],
|
|
84
|
+
timeout: float,
|
|
85
|
+
) -> tuple[dict[str, Any], ...]:
|
|
86
|
+
cache_key = (policy.base_url, formula)
|
|
87
|
+
with self._lock:
|
|
88
|
+
cached = self._cache.get(cache_key)
|
|
89
|
+
if cached is not None:
|
|
90
|
+
return cached
|
|
91
|
+
payload = self._json_request(
|
|
92
|
+
f"{policy.base_url}/formulas/{self._formula_path(formula)}/tools",
|
|
93
|
+
headers,
|
|
94
|
+
timeout=timeout,
|
|
95
|
+
)
|
|
96
|
+
tools = tuple(copy.deepcopy(tool) for tool in payload.get("tools", ()) if isinstance(tool, dict))
|
|
97
|
+
with self._lock:
|
|
98
|
+
self._cache[cache_key] = tools
|
|
99
|
+
return tools
|
|
100
|
+
|
|
101
|
+
def prepare(
|
|
102
|
+
self,
|
|
103
|
+
provider: str,
|
|
104
|
+
config: dict[str, Any],
|
|
105
|
+
request_body: dict[str, Any],
|
|
106
|
+
upstream_headers: Mapping[str, str],
|
|
107
|
+
timeout: float,
|
|
108
|
+
) -> tuple[dict[str, Any], HostedFormulaState]:
|
|
109
|
+
policy = self._policy_for(provider, config)
|
|
110
|
+
auth_headers = self._auth_headers(upstream_headers)
|
|
111
|
+
empty = HostedFormulaState({}, auth_headers, policy)
|
|
112
|
+
if not policy.base_url or not policy.formulas or "authorization" not in auth_headers:
|
|
113
|
+
return request_body, empty
|
|
114
|
+
with self._lock:
|
|
115
|
+
if policy.base_url in self._unavailable:
|
|
116
|
+
return request_body, empty
|
|
117
|
+
timeout = min(max(1.0, timeout), 10.0)
|
|
118
|
+
existing = list(request_body.get("tools") or [])
|
|
119
|
+
used_names = {
|
|
120
|
+
str(tool.get("function", {}).get("name") or "")
|
|
121
|
+
for tool in existing
|
|
122
|
+
if isinstance(tool, dict) and isinstance(tool.get("function"), dict)
|
|
123
|
+
}
|
|
124
|
+
formula_by_function: dict[str, str] = {}
|
|
125
|
+
try:
|
|
126
|
+
for formula in policy.formulas:
|
|
127
|
+
for tool in self._tools_for_formula(policy, formula, auth_headers, timeout):
|
|
128
|
+
function = tool.get("function") if isinstance(tool.get("function"), dict) else {}
|
|
129
|
+
name = str(function.get("name") or "")
|
|
130
|
+
if not name or name in used_names:
|
|
131
|
+
continue
|
|
132
|
+
used_names.add(name)
|
|
133
|
+
formula_by_function[name] = formula
|
|
134
|
+
existing.append(tool)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
with self._lock:
|
|
137
|
+
self._unavailable.add(policy.base_url)
|
|
138
|
+
self._log("WARN", f"hosted Formula tools unavailable for {provider}: {type(exc).__name__}: {exc}")
|
|
139
|
+
return request_body, empty
|
|
140
|
+
if not formula_by_function:
|
|
141
|
+
return request_body, empty
|
|
142
|
+
prepared = {**request_body, "tools": existing, "stream": False}
|
|
143
|
+
return prepared, HostedFormulaState(formula_by_function, auth_headers, policy)
|
|
144
|
+
|
|
145
|
+
def _execute(
|
|
146
|
+
self,
|
|
147
|
+
state: HostedFormulaState,
|
|
148
|
+
function: Mapping[str, Any],
|
|
149
|
+
timeout: float,
|
|
150
|
+
) -> str:
|
|
151
|
+
name = str(function.get("name") or "")
|
|
152
|
+
formula = state.formula_by_function[name]
|
|
153
|
+
payload = self._json_request(
|
|
154
|
+
f"{state.policy.base_url}/formulas/{self._formula_path(formula)}/fibers",
|
|
155
|
+
state.headers,
|
|
156
|
+
payload={"function": {"name": name, "arguments": str(function.get("arguments") or "{}")}},
|
|
157
|
+
timeout=timeout,
|
|
158
|
+
)
|
|
159
|
+
context = payload.get("context") if isinstance(payload.get("context"), dict) else {}
|
|
160
|
+
output = context.get("output")
|
|
161
|
+
if output is None:
|
|
162
|
+
output = context.get("encrypted_output")
|
|
163
|
+
if isinstance(output, str):
|
|
164
|
+
return output
|
|
165
|
+
return json.dumps(output if output is not None else payload, ensure_ascii=False)
|
|
166
|
+
|
|
167
|
+
def resolve(
|
|
168
|
+
self,
|
|
169
|
+
state: HostedFormulaState,
|
|
170
|
+
request_body: dict[str, Any],
|
|
171
|
+
response: dict[str, Any],
|
|
172
|
+
post_chat: Callable[[dict[str, Any]], dict[str, Any]],
|
|
173
|
+
timeout: float,
|
|
174
|
+
) -> dict[str, Any]:
|
|
175
|
+
if not state.enabled:
|
|
176
|
+
return response
|
|
177
|
+
body = copy.deepcopy(request_body)
|
|
178
|
+
current = response
|
|
179
|
+
for _round in range(self._max_rounds):
|
|
180
|
+
choices = current.get("choices") if isinstance(current, dict) else None
|
|
181
|
+
choice = choices[0] if isinstance(choices, list) and choices and isinstance(choices[0], dict) else {}
|
|
182
|
+
message = choice.get("message") if isinstance(choice.get("message"), dict) else {}
|
|
183
|
+
calls = [call for call in message.get("tool_calls", ()) if isinstance(call, dict)]
|
|
184
|
+
if not calls:
|
|
185
|
+
return current
|
|
186
|
+
functions = [call.get("function") if isinstance(call.get("function"), dict) else {} for call in calls]
|
|
187
|
+
if any(str(function.get("name") or "") not in state.formula_by_function for function in functions):
|
|
188
|
+
return current
|
|
189
|
+
body.setdefault("messages", []).append(copy.deepcopy(message))
|
|
190
|
+
for call, function in zip(calls, functions):
|
|
191
|
+
body["messages"].append(
|
|
192
|
+
{
|
|
193
|
+
"role": "tool",
|
|
194
|
+
"tool_call_id": str(call.get("id") or "hosted_tool"),
|
|
195
|
+
"content": self._execute(state, function, timeout),
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
body["tool_choice"] = "auto"
|
|
199
|
+
current = post_chat(body)
|
|
200
|
+
raise RuntimeError(f"hosted Formula tool loop exceeded {self._max_rounds} rounds")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
__all__ = ["HostedFormulaState", "HostedFormulaToolService"]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
_SHARED_SERVICE: HostedFormulaToolService | None = None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def shared_service(
|
|
210
|
+
policy_for: Callable[[str, dict[str, Any]], HostedToolPolicy],
|
|
211
|
+
log: Callable[[str, str], None],
|
|
212
|
+
) -> HostedFormulaToolService:
|
|
213
|
+
global _SHARED_SERVICE
|
|
214
|
+
if _SHARED_SERVICE is None:
|
|
215
|
+
_SHARED_SERVICE = HostedFormulaToolService(policy_for, log)
|
|
216
|
+
return _SHARED_SERVICE
|