@oneciel-ai/ciel-runtime 0.2.2 → 0.2.3
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 +2553 -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/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 +239 -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 +82 -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/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/workspace_router_selection.py +86 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ from ..architecture import (
|
|
|
16
16
|
ProviderRequestPolicy,
|
|
17
17
|
)
|
|
18
18
|
from ..runtime_constants import DEFAULT_REQUEST_TIMEOUT_MS
|
|
19
|
+
from .alibaba import ALIBABA_CODING_PLAN_MODELS
|
|
19
20
|
from .base import OpenAICompatibleProviderAdapter, provider_configuration
|
|
20
21
|
|
|
21
22
|
|
|
@@ -30,8 +31,8 @@ class CompatibleProviderSpec:
|
|
|
30
31
|
aliases: tuple[str, ...] = ()
|
|
31
32
|
chat_path: str = "/v1/chat/completions"
|
|
32
33
|
models_path: str = "/v1/models"
|
|
34
|
+
anthropic_url: str = ""
|
|
33
35
|
authorization_header: str = "Authorization"
|
|
34
|
-
include_x_api_key: bool = False
|
|
35
36
|
requires_api_key: bool = True
|
|
36
37
|
|
|
37
38
|
|
|
@@ -52,7 +53,7 @@ class CatalogOpenAIProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
52
53
|
configuration_defaults_value=provider_configuration(
|
|
53
54
|
default_model,
|
|
54
55
|
custom_models=spec.models,
|
|
55
|
-
native_compat=
|
|
56
|
+
native_compat=bool(spec.anthropic_url),
|
|
56
57
|
context_window=131072,
|
|
57
58
|
max_output_tokens=8192,
|
|
58
59
|
context_reserve_tokens=4096,
|
|
@@ -61,7 +62,7 @@ class CatalogOpenAIProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
61
62
|
stream_word_chunking=False,
|
|
62
63
|
),
|
|
63
64
|
authorization_header=spec.authorization_header,
|
|
64
|
-
include_x_api_key=
|
|
65
|
+
include_x_api_key=False,
|
|
65
66
|
require_api_key=spec.requires_api_key,
|
|
66
67
|
api_key_display_name_value=spec.label,
|
|
67
68
|
api_key_launch_error_value=(
|
|
@@ -97,16 +98,25 @@ class CatalogOpenAIProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
97
98
|
config: ProviderConfig,
|
|
98
99
|
model: str | None = None,
|
|
99
100
|
) -> bool:
|
|
100
|
-
del
|
|
101
|
-
return
|
|
101
|
+
del model
|
|
102
|
+
return bool(self.spec.anthropic_url) and bool(
|
|
103
|
+
config.options.get("native_compat", True)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def anthropic_base_url(self, config: ProviderConfig) -> str:
|
|
107
|
+
del config
|
|
108
|
+
return self.spec.anthropic_url or self.default_base_url()
|
|
102
109
|
|
|
103
110
|
def supported_protocols(
|
|
104
111
|
self,
|
|
105
112
|
config: ProviderConfig,
|
|
106
113
|
model: str | None = None,
|
|
107
114
|
) -> frozenset[MessageProtocol]:
|
|
108
|
-
del
|
|
109
|
-
|
|
115
|
+
del model
|
|
116
|
+
protocols: set[MessageProtocol] = {"openai_chat"}
|
|
117
|
+
if self.router_native_anthropic_enabled(config):
|
|
118
|
+
protocols.add("anthropic_messages")
|
|
119
|
+
return frozenset(protocols)
|
|
110
120
|
|
|
111
121
|
def select_protocol(
|
|
112
122
|
self,
|
|
@@ -114,7 +124,9 @@ class CatalogOpenAIProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
114
124
|
config: ProviderConfig,
|
|
115
125
|
model: str | None = None,
|
|
116
126
|
) -> MessageProtocol:
|
|
117
|
-
del
|
|
127
|
+
del model
|
|
128
|
+
if operation == "anthropic_messages" and self.router_native_anthropic_enabled(config):
|
|
129
|
+
return "anthropic_messages"
|
|
118
130
|
return "openai_chat"
|
|
119
131
|
|
|
120
132
|
|
|
@@ -130,19 +142,15 @@ COMPATIBLE_PROVIDER_SPECS: tuple[CompatibleProviderSpec, ...] = (
|
|
|
130
142
|
CompatibleProviderSpec(
|
|
131
143
|
"alicode-intl", "Alibaba Coding International",
|
|
132
144
|
"https://coding-intl.dashscope.aliyuncs.com/v1",
|
|
133
|
-
|
|
145
|
+
ALIBABA_CODING_PLAN_MODELS,
|
|
134
146
|
("alibaba-coding-intl",),
|
|
147
|
+
anthropic_url="https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
|
135
148
|
),
|
|
136
149
|
CompatibleProviderSpec(
|
|
137
150
|
"alicode", "Alibaba Coding", "https://coding.dashscope.aliyuncs.com/v1",
|
|
138
|
-
|
|
151
|
+
ALIBABA_CODING_PLAN_MODELS,
|
|
139
152
|
("alibaba-coding",),
|
|
140
|
-
|
|
141
|
-
CompatibleProviderSpec(
|
|
142
|
-
"alims-intl", "Alibaba Model Studio International",
|
|
143
|
-
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
|
144
|
-
("qwen3.5-plus", "kimi-k2.5", "glm-5", "qwen3-coder-next"),
|
|
145
|
-
("dashscope-intl",),
|
|
153
|
+
anthropic_url="https://coding.dashscope.aliyuncs.com/apps/anthropic",
|
|
146
154
|
),
|
|
147
155
|
CompatibleProviderSpec(
|
|
148
156
|
"blackbox", "Blackbox AI", "https://api.blackbox.ai/v1",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""DeepSeek provider adapter."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Mapping
|
|
4
5
|
|
|
5
6
|
from ..architecture import (
|
|
6
7
|
ProviderCapabilities,
|
|
@@ -24,6 +25,12 @@ class DeepSeekProviderAdapter(HttpBearerProviderAdapter):
|
|
|
24
25
|
"deepseek-v4-pro[1m]",
|
|
25
26
|
custom_models=("deepseek-v4-pro[1m]", "deepseek-v4-flash"),
|
|
26
27
|
native_compat=True,
|
|
28
|
+
claude_code_supported_capabilities=[
|
|
29
|
+
"effort",
|
|
30
|
+
"max_effort",
|
|
31
|
+
"thinking",
|
|
32
|
+
"interleaved_thinking",
|
|
33
|
+
],
|
|
27
34
|
context_window=1048576,
|
|
28
35
|
max_output_tokens=8192,
|
|
29
36
|
context_reserve_tokens=8192,
|
|
@@ -111,5 +118,71 @@ class DeepSeekProviderAdapter(HttpBearerProviderAdapter):
|
|
|
111
118
|
not in str(model or config.model or "").split("[", 1)[0].lower()
|
|
112
119
|
)
|
|
113
120
|
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _normalized_effort(value: Any) -> str:
|
|
123
|
+
effort = str(value or "").strip().lower()
|
|
124
|
+
if effort in {"max", "xhigh"}:
|
|
125
|
+
return "max"
|
|
126
|
+
return "high"
|
|
127
|
+
|
|
128
|
+
def normalize_request_options(
|
|
129
|
+
self, config: ProviderConfig, request: Mapping[str, Any]
|
|
130
|
+
) -> Mapping[str, Any]:
|
|
131
|
+
"""Honor DeepSeek's native thinking contract on Anthropic requests."""
|
|
132
|
+
|
|
133
|
+
del config
|
|
134
|
+
normalized = dict(request)
|
|
135
|
+
thinking = request.get("thinking")
|
|
136
|
+
thinking_disabled = isinstance(thinking, Mapping) and str(
|
|
137
|
+
thinking.get("type") or ""
|
|
138
|
+
).strip().lower() in {"disabled", "none", "off", "false"}
|
|
139
|
+
if not thinking_disabled:
|
|
140
|
+
for key in (
|
|
141
|
+
"temperature",
|
|
142
|
+
"top_p",
|
|
143
|
+
"top_k",
|
|
144
|
+
"presence_penalty",
|
|
145
|
+
"frequency_penalty",
|
|
146
|
+
):
|
|
147
|
+
normalized.pop(key, None)
|
|
148
|
+
output_config = request.get("output_config")
|
|
149
|
+
if thinking_disabled:
|
|
150
|
+
normalized.pop("output_config", None)
|
|
151
|
+
output_config = None
|
|
152
|
+
elif not isinstance(output_config, Mapping) and isinstance(thinking, Mapping):
|
|
153
|
+
if thinking.get("effort") is not None:
|
|
154
|
+
output_config = {"effort": thinking.get("effort")}
|
|
155
|
+
if isinstance(output_config, Mapping) and output_config.get("effort") is not None:
|
|
156
|
+
normalized["output_config"] = {
|
|
157
|
+
**output_config,
|
|
158
|
+
"effort": self._normalized_effort(output_config.get("effort")),
|
|
159
|
+
}
|
|
160
|
+
if isinstance(thinking, Mapping) and "effort" in thinking:
|
|
161
|
+
normalized["thinking"] = {
|
|
162
|
+
key: value for key, value in thinking.items() if key != "effort"
|
|
163
|
+
}
|
|
164
|
+
return normalized
|
|
165
|
+
|
|
166
|
+
def openai_reasoning_passback_enabled(
|
|
167
|
+
self, config: ProviderConfig, model: str | None = None
|
|
168
|
+
) -> bool:
|
|
169
|
+
del config
|
|
170
|
+
normalized = str(model or "").split("[", 1)[0].lower()
|
|
171
|
+
return normalized.startswith(("deepseek-v4-", "deepseek-reasoner"))
|
|
172
|
+
|
|
173
|
+
def openai_reasoning_effort(
|
|
174
|
+
self, config: ProviderConfig, model: str, request: Mapping[str, Any]
|
|
175
|
+
) -> str | None:
|
|
176
|
+
del model
|
|
177
|
+
metadata = request.get("metadata")
|
|
178
|
+
hinted = metadata.get("ciel_runtime_reasoning_effort") if isinstance(metadata, Mapping) else None
|
|
179
|
+
return self._normalized_effort(hinted or config.options.get("effort_level") or "high")
|
|
180
|
+
|
|
181
|
+
def allows_sampling_overrides(self, config: ProviderConfig) -> bool:
|
|
182
|
+
# Thinking is enabled by default. Do not inject configured sampling
|
|
183
|
+
# values that DeepSeek documents as ineffective in that mode.
|
|
184
|
+
del config
|
|
185
|
+
return False
|
|
186
|
+
|
|
114
187
|
|
|
115
188
|
__all__ = ["DeepSeekProviderAdapter"]
|
|
@@ -26,12 +26,32 @@ from .base import HttpBearerProviderAdapter, provider_configuration
|
|
|
26
26
|
|
|
27
27
|
GITHUB_COPILOT_BASE_URL = "https://api.githubcopilot.com"
|
|
28
28
|
GITHUB_COPILOT_MODELS = (
|
|
29
|
+
"gpt-5.6-sol",
|
|
30
|
+
"gpt-5.6-terra",
|
|
31
|
+
"gpt-5.6-luna",
|
|
32
|
+
"gpt-5.5",
|
|
29
33
|
"gpt-5.4",
|
|
34
|
+
"gpt-5.4-mini",
|
|
35
|
+
"gpt-5.4-nano",
|
|
30
36
|
"gpt-5.3-codex",
|
|
31
|
-
"gpt-5
|
|
37
|
+
"gpt-5-mini",
|
|
38
|
+
"claude-fable-5",
|
|
39
|
+
"claude-sonnet-5",
|
|
40
|
+
"claude-opus-5",
|
|
41
|
+
"claude-opus-4.8",
|
|
42
|
+
"claude-opus-4.7",
|
|
32
43
|
"claude-sonnet-4.6",
|
|
33
44
|
"claude-opus-4.6",
|
|
45
|
+
"claude-sonnet-4.5",
|
|
46
|
+
"claude-opus-4.5",
|
|
47
|
+
"claude-haiku-4.5",
|
|
48
|
+
"gemini-3.6-flash",
|
|
49
|
+
"gemini-3.5-flash",
|
|
34
50
|
"gemini-3.1-pro-preview",
|
|
51
|
+
"kimi-k2.7-code",
|
|
52
|
+
"mai-code-1-flash",
|
|
53
|
+
"raptor-mini",
|
|
54
|
+
"grok-4.5",
|
|
35
55
|
)
|
|
36
56
|
|
|
37
57
|
|
|
@@ -78,6 +98,7 @@ class GitHubCopilotOAuthProviderAdapter(HttpBearerProviderAdapter):
|
|
|
78
98
|
kind="openai",
|
|
79
99
|
fallback_models=GITHUB_COPILOT_MODELS,
|
|
80
100
|
allow_configured_fallback=True,
|
|
101
|
+
authoritative_upstream_catalog=True,
|
|
81
102
|
)
|
|
82
103
|
)
|
|
83
104
|
|
|
@@ -6,6 +6,7 @@ from typing import Any, Mapping
|
|
|
6
6
|
|
|
7
7
|
from ..architecture import (
|
|
8
8
|
MessageProtocol,
|
|
9
|
+
HostedToolPolicy,
|
|
9
10
|
ProviderCapabilities,
|
|
10
11
|
ProviderConfig,
|
|
11
12
|
ProviderContextPolicy,
|
|
@@ -40,7 +41,9 @@ class KimiProviderAdapter(HttpBearerProviderAdapter):
|
|
|
40
41
|
request_timeout_ms=600000,
|
|
41
42
|
stream_enabled=True,
|
|
42
43
|
stream_word_chunking=False,
|
|
43
|
-
effort_level="
|
|
44
|
+
effort_level="max",
|
|
45
|
+
official_tools_enabled=True,
|
|
46
|
+
official_tools=["web-search", "fetch"],
|
|
44
47
|
haiku_model="kimi-for-coding",
|
|
45
48
|
subagent_model="kimi-for-coding",
|
|
46
49
|
)
|
|
@@ -155,10 +158,10 @@ class KimiProviderAdapter(HttpBearerProviderAdapter):
|
|
|
155
158
|
{
|
|
156
159
|
"context_window": context,
|
|
157
160
|
"max_model_len": context,
|
|
158
|
-
"effort_level": "
|
|
161
|
+
"effort_level": "max",
|
|
159
162
|
"model_profile": "kimi-k3-1m",
|
|
160
163
|
},
|
|
161
|
-
"Kimi K3 profile applied: 1M context and
|
|
164
|
+
"Kimi K3 profile applied: 1M context and max reasoning effort. "
|
|
162
165
|
"Start a new session after changing model, context, or reasoning effort.",
|
|
163
166
|
)
|
|
164
167
|
|
|
@@ -218,7 +221,15 @@ class KimiProviderAdapter(HttpBearerProviderAdapter):
|
|
|
218
221
|
config: ProviderConfig,
|
|
219
222
|
model: str | None = None,
|
|
220
223
|
) -> MessageProtocol:
|
|
221
|
-
|
|
224
|
+
normalized_model = self.normalize_model_id(
|
|
225
|
+
str(model or config.model or "")
|
|
226
|
+
)
|
|
227
|
+
if (
|
|
228
|
+
operation == "anthropic_messages"
|
|
229
|
+
and normalized_model in {"k3", "k3[1m]"}
|
|
230
|
+
and self.hosted_tool_policy(config).formulas
|
|
231
|
+
):
|
|
232
|
+
return "openai_chat"
|
|
222
233
|
return (
|
|
223
234
|
"openai_chat"
|
|
224
235
|
if operation in {"openai_chat", "openai_responses"}
|
|
@@ -266,20 +277,69 @@ class KimiProviderAdapter(HttpBearerProviderAdapter):
|
|
|
266
277
|
requested = config.options.get("effort_level")
|
|
267
278
|
return self._reasoning_effort(requested)
|
|
268
279
|
|
|
280
|
+
def openai_reasoning_passback_enabled(
|
|
281
|
+
self, config: ProviderConfig, model: str | None = None
|
|
282
|
+
) -> bool:
|
|
283
|
+
del config
|
|
284
|
+
normalized = self.normalize_model_id(str(model or ""))
|
|
285
|
+
return normalized in {
|
|
286
|
+
"k3",
|
|
287
|
+
"k3[1m]",
|
|
288
|
+
"kimi-for-coding",
|
|
289
|
+
"kimi-for-coding-highspeed",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
def hosted_tool_policy(self, config: ProviderConfig) -> HostedToolPolicy:
|
|
293
|
+
if config.options.get("official_tools_enabled", True) is False:
|
|
294
|
+
return HostedToolPolicy()
|
|
295
|
+
configured = config.options.get("official_tools", ("web-search", "fetch"))
|
|
296
|
+
if configured is False or configured is None:
|
|
297
|
+
return HostedToolPolicy()
|
|
298
|
+
if isinstance(configured, str):
|
|
299
|
+
names = tuple(part.strip() for part in configured.split(",") if part.strip())
|
|
300
|
+
elif isinstance(configured, (list, tuple)):
|
|
301
|
+
names = tuple(str(part).strip() for part in configured if str(part).strip())
|
|
302
|
+
else:
|
|
303
|
+
names = ("web-search", "fetch") if configured is True else ()
|
|
304
|
+
formulas = tuple(
|
|
305
|
+
name if "/" in name else f"moonshot/{name}:latest" for name in names
|
|
306
|
+
)
|
|
307
|
+
base_url = str(
|
|
308
|
+
config.options.get("official_tools_base_url")
|
|
309
|
+
or "https://api.moonshot.ai/v1"
|
|
310
|
+
).rstrip("/")
|
|
311
|
+
return HostedToolPolicy(base_url=base_url, formulas=formulas)
|
|
312
|
+
|
|
313
|
+
def should_omit_openai_tool_choice(
|
|
314
|
+
self,
|
|
315
|
+
config: ProviderConfig,
|
|
316
|
+
model: str | None,
|
|
317
|
+
request: Mapping[str, Any],
|
|
318
|
+
) -> bool:
|
|
319
|
+
if not self.openai_reasoning_passback_enabled(config, model):
|
|
320
|
+
return False
|
|
321
|
+
choice = request.get("tool_choice")
|
|
322
|
+
return bool(
|
|
323
|
+
isinstance(choice, Mapping)
|
|
324
|
+
and str(choice.get("type") or "").strip().lower() == "tool"
|
|
325
|
+
)
|
|
326
|
+
|
|
269
327
|
def allows_sampling_overrides(self, config: ProviderConfig) -> bool:
|
|
270
328
|
del config
|
|
271
329
|
return False
|
|
272
330
|
|
|
273
331
|
@staticmethod
|
|
274
332
|
def _reasoning_effort(value: Any) -> str:
|
|
275
|
-
effort = str(value or "
|
|
276
|
-
if effort
|
|
333
|
+
effort = str(value or "max").strip().lower()
|
|
334
|
+
if effort in {"xhigh", "ultra", "max"}:
|
|
277
335
|
return "max"
|
|
278
336
|
if effort in {"minimum", "light"}:
|
|
279
337
|
return "low"
|
|
280
|
-
if effort
|
|
338
|
+
if effort == "medium":
|
|
339
|
+
return "high"
|
|
340
|
+
if effort in {"low", "high"}:
|
|
281
341
|
return effort
|
|
282
|
-
return "
|
|
342
|
+
return "max"
|
|
283
343
|
|
|
284
344
|
def normalize_tool_choice(
|
|
285
345
|
self, config: ProviderConfig, model: str, tool_choice: Any
|
|
@@ -289,7 +349,7 @@ class KimiProviderAdapter(HttpBearerProviderAdapter):
|
|
|
289
349
|
tool_choice, Mapping
|
|
290
350
|
):
|
|
291
351
|
return tool_choice
|
|
292
|
-
if str(tool_choice.get("type") or "").strip().lower()
|
|
352
|
+
if str(tool_choice.get("type") or "").strip().lower() == "tool":
|
|
293
353
|
return {"type": "auto"}
|
|
294
354
|
return tool_choice
|
|
295
355
|
|
|
@@ -84,6 +84,14 @@ class OllamaProviderAdapter(HttpBearerProviderAdapter):
|
|
|
84
84
|
) -> bool | str | None:
|
|
85
85
|
return OllamaThinkingPolicy().value(config.options, model, request)
|
|
86
86
|
|
|
87
|
+
def reasoning_passback_enabled(
|
|
88
|
+
self, config: ProviderConfig, model: str | None = None
|
|
89
|
+
) -> bool:
|
|
90
|
+
"""Keep native Ollama thinking blocks so multi-turn tool history remains valid."""
|
|
91
|
+
|
|
92
|
+
del config, model
|
|
93
|
+
return True
|
|
94
|
+
|
|
87
95
|
def option_presentation_policy(
|
|
88
96
|
self, config: ProviderConfig
|
|
89
97
|
) -> ProviderOptionPresentationPolicy:
|
|
@@ -179,10 +179,29 @@ class OllamaRequestContextPolicy:
|
|
|
179
179
|
)
|
|
180
180
|
options = dict(self.extra_options(retry_config))
|
|
181
181
|
configured_num_predict = self.positive_int(options.get("num_predict"))
|
|
182
|
-
|
|
182
|
+
output_is_explicit = bool(config.get("output_tokens_explicit")) or (
|
|
183
|
+
"num_predict"
|
|
184
|
+
in {
|
|
185
|
+
str(item)
|
|
186
|
+
for item in config.get("ollama_explicit_options") or []
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
if configured_num_predict and output_is_explicit:
|
|
183
190
|
options["num_predict"] = min(configured_num_predict, output_cap)
|
|
191
|
+
else:
|
|
192
|
+
options.pop("num_predict", None)
|
|
184
193
|
retry_config["ollama_options"] = options
|
|
185
|
-
|
|
194
|
+
transient = {
|
|
195
|
+
str(item)
|
|
196
|
+
for item in retry_config.get("ollama_transient_options") or []
|
|
197
|
+
if str(item) != "num_predict"
|
|
198
|
+
}
|
|
199
|
+
if output_is_explicit:
|
|
200
|
+
transient.add("num_predict")
|
|
201
|
+
if transient:
|
|
202
|
+
retry_config["ollama_transient_options"] = sorted(transient)
|
|
203
|
+
else:
|
|
204
|
+
retry_config.pop("ollama_transient_options", None)
|
|
186
205
|
return retry_config
|
|
187
206
|
|
|
188
207
|
def context_limit_for_budget(self, config: dict[str, Any]) -> int:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""vLLM provider adapter."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field, replace
|
|
4
|
+
from typing import Literal
|
|
4
5
|
|
|
5
6
|
from ..architecture import (
|
|
6
7
|
ProviderCapabilities,
|
|
@@ -39,11 +40,16 @@ class VllmProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
39
40
|
upstream_protocol="openai_chat", supports_tool_choice=False, local=True
|
|
40
41
|
)
|
|
41
42
|
)
|
|
42
|
-
|
|
43
43
|
def requires_catalog_model_selection(self, config: ProviderConfig) -> bool:
|
|
44
44
|
del config
|
|
45
45
|
return True
|
|
46
46
|
|
|
47
|
+
def anthropic_system_role_strategy(
|
|
48
|
+
self, config: ProviderConfig
|
|
49
|
+
) -> Literal["hoist_top_level"]:
|
|
50
|
+
del config
|
|
51
|
+
return "hoist_top_level"
|
|
52
|
+
|
|
47
53
|
def placeholder_model_ids(self) -> frozenset[str]:
|
|
48
54
|
return super().placeholder_model_ids() | {"my-model"}
|
|
49
55
|
|
|
@@ -15,6 +15,10 @@ class ChatCollectionStrategy:
|
|
|
15
15
|
request_timeout_seconds: Callable[..., float]
|
|
16
16
|
normalize_upstream_model: Callable[..., str]
|
|
17
17
|
skip_rate_limit_during_compatibility_test: bool = False
|
|
18
|
+
# When set, the upstream is read as a stream and assembled here instead of
|
|
19
|
+
# being fetched with one blocking POST. Same result, except a runaway can be
|
|
20
|
+
# cut off while it is still being generated rather than after.
|
|
21
|
+
stream_collect: Callable[..., dict[str, Any]] | None = None
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
@dataclass(frozen=True, slots=True)
|
|
@@ -48,6 +52,7 @@ class ResponseCollectionServices:
|
|
|
48
52
|
rate_limit: ResponseCollectionRateLimit
|
|
49
53
|
projection: ResponseCollectionProjection
|
|
50
54
|
post_json_with_retry: Callable[..., Any]
|
|
55
|
+
hosted_tools: Any
|
|
51
56
|
|
|
52
57
|
|
|
53
58
|
@dataclass(frozen=True, slots=True)
|
|
@@ -112,25 +117,60 @@ def collect_chat_message_for_responses(
|
|
|
112
117
|
model = strategy.normalize_upstream_model(provider, pcfg, model)
|
|
113
118
|
original_body = body
|
|
114
119
|
upstream_body = request.body_with_advisor_tool(body, pcfg) if request.advisor_provider_supported(provider) else body
|
|
115
|
-
|
|
120
|
+
streaming = strategy.stream_collect is not None
|
|
121
|
+
req_body = strategy.build_request(provider, model, upstream_body, pcfg, stream=streaming)
|
|
116
122
|
url = request.provider_endpoint(provider, pcfg, strategy.operation)
|
|
123
|
+
timeout = strategy.request_timeout_seconds(pcfg)
|
|
124
|
+
headers = request.provider_headers(provider, pcfg, handler.headers, strategy.operation)
|
|
125
|
+
req_body, hosted_state = services.hosted_tools.prepare(
|
|
126
|
+
provider, pcfg, req_body, headers, timeout
|
|
127
|
+
)
|
|
117
128
|
compatibility_test = str(handler.headers.get(services.compatibility_test_header) or "").strip().lower() in ("1", "true", "yes", "on")
|
|
118
129
|
if compatibility_test and strategy.skip_rate_limit_during_compatibility_test:
|
|
119
130
|
waited, rpm_used, rpm_limit = 0.0, 0, rate_limit.effective_rpm(provider, pcfg, model)
|
|
120
131
|
else:
|
|
121
132
|
waited, rpm_used, rpm_limit = rate_limit.apply(provider, pcfg, model)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
if streaming:
|
|
134
|
+
data = strategy.stream_collect(
|
|
135
|
+
url,
|
|
136
|
+
req_body,
|
|
137
|
+
headers,
|
|
138
|
+
timeout,
|
|
139
|
+
provider,
|
|
140
|
+
pcfg,
|
|
141
|
+
model,
|
|
142
|
+
retry_rate_limits=not compatibility_test,
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
data = services.post_json_with_retry(
|
|
146
|
+
url,
|
|
147
|
+
req_body,
|
|
148
|
+
headers,
|
|
149
|
+
timeout,
|
|
150
|
+
provider,
|
|
151
|
+
pcfg,
|
|
152
|
+
model,
|
|
153
|
+
None,
|
|
154
|
+
retry_rate_limits=not compatibility_test,
|
|
155
|
+
)
|
|
156
|
+
# Hosted-tool follow-ups are plain request/response, so they keep using the
|
|
157
|
+
# blocking POST regardless of how the first turn was read.
|
|
158
|
+
data = services.hosted_tools.resolve(
|
|
159
|
+
hosted_state,
|
|
160
|
+
{**req_body, "stream": False} if streaming else req_body,
|
|
161
|
+
data,
|
|
162
|
+
lambda next_body: services.post_json_with_retry(
|
|
163
|
+
url,
|
|
164
|
+
next_body,
|
|
165
|
+
headers,
|
|
166
|
+
timeout,
|
|
167
|
+
provider,
|
|
168
|
+
pcfg,
|
|
169
|
+
model,
|
|
170
|
+
None,
|
|
171
|
+
retry_rate_limits=not compatibility_test,
|
|
127
172
|
),
|
|
128
|
-
|
|
129
|
-
provider,
|
|
130
|
-
pcfg,
|
|
131
|
-
model,
|
|
132
|
-
None,
|
|
133
|
-
retry_rate_limits=not compatibility_test,
|
|
173
|
+
timeout,
|
|
134
174
|
)
|
|
135
175
|
message = strategy.decode_response(data, model, source_body=original_body)
|
|
136
176
|
message = projection.refine_with_advisor(provider, pcfg, original_body, message, model)
|
|
@@ -146,14 +186,20 @@ def collect_anthropic_message_for_responses(
|
|
|
146
186
|
body: dict[str, Any],
|
|
147
187
|
*,
|
|
148
188
|
services: AnthropicCollectionServices,
|
|
189
|
+
stream_collect: Callable[..., dict[str, Any]] | None = None,
|
|
149
190
|
) -> dict[str, Any]:
|
|
150
|
-
"""Collect one native Anthropic message for a Responses API projection.
|
|
191
|
+
"""Collect one native Anthropic message for a Responses API projection.
|
|
192
|
+
|
|
193
|
+
``stream_collect`` reads the upstream as SSE and assembles the same message
|
|
194
|
+
object, which lets the repetition guard cut a loop off mid-generation
|
|
195
|
+
instead of paying for all of it and trimming afterwards.
|
|
196
|
+
"""
|
|
151
197
|
|
|
152
198
|
request = services.request
|
|
153
199
|
transport = services.transport
|
|
154
200
|
projection = services.projection
|
|
155
201
|
body = request.normalize_thinking(provider, pcfg, body)
|
|
156
|
-
body = request.normalize_system_roles(body)
|
|
202
|
+
body = request.normalize_system_roles(provider, pcfg, body)
|
|
157
203
|
body = request.cap_body(provider, pcfg, body)
|
|
158
204
|
body = request.apply_options(provider, pcfg, body)
|
|
159
205
|
body = request.rehydrate_thinking(provider, pcfg, body)
|
|
@@ -162,7 +208,8 @@ def collect_anthropic_message_for_responses(
|
|
|
162
208
|
body["model"] = upstream_model
|
|
163
209
|
body = request.resolve_tool_models(provider, pcfg, body)
|
|
164
210
|
body = request.normalize_model_options(provider, pcfg, body, upstream_model)
|
|
165
|
-
|
|
211
|
+
streaming = stream_collect is not None
|
|
212
|
+
upstream_body = request.strip_internal_metadata({**body, "stream": streaming})
|
|
166
213
|
if transport.native_compat_enabled(provider, pcfg):
|
|
167
214
|
base = transport.native_base_url(provider, pcfg)
|
|
168
215
|
else:
|
|
@@ -186,11 +233,14 @@ def collect_anthropic_message_for_responses(
|
|
|
186
233
|
provider,
|
|
187
234
|
pcfg,
|
|
188
235
|
upstream_model,
|
|
189
|
-
stream=
|
|
236
|
+
stream=streaming,
|
|
190
237
|
)
|
|
191
238
|
try:
|
|
192
|
-
|
|
193
|
-
|
|
239
|
+
if streaming:
|
|
240
|
+
payload = stream_collect(upstream_response, provider, upstream_model)
|
|
241
|
+
else:
|
|
242
|
+
raw_response = upstream_response.read()
|
|
243
|
+
payload = json.loads(raw_response.decode("utf-8", errors="replace"))
|
|
194
244
|
if not isinstance(payload, dict):
|
|
195
245
|
raise RuntimeError("upstream returned non-object JSON")
|
|
196
246
|
payload = projection.normalize_response_thinking(provider, pcfg, payload, upstream_model)
|