klaude-code 2.2.0__py3-none-any.whl → 2.4.0__py3-none-any.whl
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.
- klaude_code/app/runtime.py +2 -15
- klaude_code/cli/list_model.py +30 -13
- klaude_code/cli/main.py +26 -10
- klaude_code/config/assets/builtin_config.yaml +177 -310
- klaude_code/config/config.py +158 -21
- klaude_code/config/{select_model.py → model_matcher.py} +41 -16
- klaude_code/config/sub_agent_model_helper.py +217 -0
- klaude_code/config/thinking.py +2 -2
- klaude_code/const.py +1 -1
- klaude_code/core/agent_profile.py +43 -5
- klaude_code/core/executor.py +129 -47
- klaude_code/core/manager/llm_clients_builder.py +17 -11
- klaude_code/core/prompts/prompt-nano-banana.md +1 -1
- klaude_code/core/tool/file/diff_builder.py +25 -18
- klaude_code/core/tool/sub_agent_tool.py +2 -1
- klaude_code/llm/anthropic/client.py +12 -9
- klaude_code/llm/anthropic/input.py +54 -29
- klaude_code/llm/client.py +1 -1
- klaude_code/llm/codex/client.py +2 -2
- klaude_code/llm/google/client.py +7 -7
- klaude_code/llm/google/input.py +23 -2
- klaude_code/llm/input_common.py +2 -2
- klaude_code/llm/openai_compatible/client.py +3 -3
- klaude_code/llm/openai_compatible/input.py +22 -13
- klaude_code/llm/openai_compatible/stream.py +1 -1
- klaude_code/llm/openrouter/client.py +4 -4
- klaude_code/llm/openrouter/input.py +35 -25
- klaude_code/llm/responses/client.py +5 -5
- klaude_code/llm/responses/input.py +96 -57
- klaude_code/protocol/commands.py +1 -2
- klaude_code/protocol/events/__init__.py +7 -1
- klaude_code/protocol/events/chat.py +10 -0
- klaude_code/protocol/events/system.py +4 -0
- klaude_code/protocol/llm_param.py +1 -1
- klaude_code/protocol/model.py +0 -26
- klaude_code/protocol/op.py +17 -5
- klaude_code/protocol/op_handler.py +5 -0
- klaude_code/protocol/sub_agent/AGENTS.md +28 -0
- klaude_code/protocol/sub_agent/__init__.py +10 -14
- klaude_code/protocol/sub_agent/image_gen.py +2 -1
- klaude_code/session/codec.py +2 -6
- klaude_code/session/session.py +13 -3
- klaude_code/skill/assets/create-plan/SKILL.md +3 -5
- klaude_code/tui/command/__init__.py +3 -6
- klaude_code/tui/command/clear_cmd.py +0 -1
- klaude_code/tui/command/command_abc.py +6 -4
- klaude_code/tui/command/copy_cmd.py +10 -10
- klaude_code/tui/command/debug_cmd.py +11 -10
- klaude_code/tui/command/export_online_cmd.py +18 -23
- klaude_code/tui/command/fork_session_cmd.py +39 -43
- klaude_code/tui/command/model_cmd.py +10 -49
- klaude_code/tui/command/model_picker.py +142 -0
- klaude_code/tui/command/refresh_cmd.py +0 -1
- klaude_code/tui/command/registry.py +15 -21
- klaude_code/tui/command/resume_cmd.py +10 -16
- klaude_code/tui/command/status_cmd.py +8 -12
- klaude_code/tui/command/sub_agent_model_cmd.py +185 -0
- klaude_code/tui/command/terminal_setup_cmd.py +8 -11
- klaude_code/tui/command/thinking_cmd.py +4 -6
- klaude_code/tui/commands.py +5 -0
- klaude_code/tui/components/bash_syntax.py +1 -1
- klaude_code/tui/components/command_output.py +96 -0
- klaude_code/tui/components/common.py +1 -1
- klaude_code/tui/components/developer.py +3 -115
- klaude_code/tui/components/metadata.py +1 -63
- klaude_code/tui/components/rich/cjk_wrap.py +3 -2
- klaude_code/tui/components/rich/status.py +49 -3
- klaude_code/tui/components/rich/theme.py +2 -0
- klaude_code/tui/components/sub_agent.py +25 -46
- klaude_code/tui/components/welcome.py +99 -0
- klaude_code/tui/input/prompt_toolkit.py +19 -8
- klaude_code/tui/machine.py +5 -0
- klaude_code/tui/renderer.py +7 -8
- klaude_code/tui/runner.py +0 -6
- klaude_code/tui/terminal/selector.py +8 -6
- {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/METADATA +21 -74
- {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/RECORD +79 -76
- klaude_code/tui/command/help_cmd.py +0 -51
- klaude_code/tui/command/model_select.py +0 -84
- klaude_code/tui/command/release_notes_cmd.py +0 -85
- {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -8,10 +8,13 @@ import json
|
|
|
8
8
|
from typing import Literal, cast
|
|
9
9
|
|
|
10
10
|
from anthropic.types.beta.beta_base64_image_source_param import BetaBase64ImageSourceParam
|
|
11
|
+
from anthropic.types.beta.beta_content_block_param import BetaContentBlockParam
|
|
11
12
|
from anthropic.types.beta.beta_image_block_param import BetaImageBlockParam
|
|
12
13
|
from anthropic.types.beta.beta_message_param import BetaMessageParam
|
|
13
14
|
from anthropic.types.beta.beta_text_block_param import BetaTextBlockParam
|
|
14
15
|
from anthropic.types.beta.beta_tool_param import BetaToolParam
|
|
16
|
+
from anthropic.types.beta.beta_tool_result_block_param import BetaToolResultBlockParam
|
|
17
|
+
from anthropic.types.beta.beta_tool_use_block_param import BetaToolUseBlockParam
|
|
15
18
|
from anthropic.types.beta.beta_url_image_source_param import BetaURLImageSourceParam
|
|
16
19
|
|
|
17
20
|
from klaude_code.const import EMPTY_TOOL_OUTPUT_MESSAGE
|
|
@@ -60,29 +63,29 @@ def _user_message_to_message(
|
|
|
60
63
|
blocks: list[BetaTextBlockParam | BetaImageBlockParam] = []
|
|
61
64
|
for part in msg.parts:
|
|
62
65
|
if isinstance(part, message.TextPart):
|
|
63
|
-
blocks.append({"type": "text", "text": part.text})
|
|
66
|
+
blocks.append(cast(BetaTextBlockParam, {"type": "text", "text": part.text}))
|
|
64
67
|
elif isinstance(part, message.ImageURLPart):
|
|
65
68
|
blocks.append(_image_part_to_block(part))
|
|
66
69
|
if attachment.text:
|
|
67
|
-
blocks.append({"type": "text", "text": attachment.text})
|
|
70
|
+
blocks.append(cast(BetaTextBlockParam, {"type": "text", "text": attachment.text}))
|
|
68
71
|
for image in attachment.images:
|
|
69
72
|
blocks.append(_image_part_to_block(image))
|
|
70
73
|
if not blocks:
|
|
71
|
-
blocks.append({"type": "text", "text": ""})
|
|
74
|
+
blocks.append(cast(BetaTextBlockParam, {"type": "text", "text": ""}))
|
|
72
75
|
return {"role": "user", "content": blocks}
|
|
73
76
|
|
|
74
77
|
|
|
75
78
|
def _tool_message_to_block(
|
|
76
79
|
msg: message.ToolResultMessage,
|
|
77
80
|
attachment: DeveloperAttachment,
|
|
78
|
-
) ->
|
|
81
|
+
) -> BetaToolResultBlockParam:
|
|
79
82
|
"""Convert a single tool result message to a tool_result block."""
|
|
80
83
|
tool_content: list[BetaTextBlockParam | BetaImageBlockParam] = []
|
|
81
84
|
merged_text = merge_reminder_text(
|
|
82
85
|
msg.output_text or EMPTY_TOOL_OUTPUT_MESSAGE,
|
|
83
86
|
attachment.text,
|
|
84
87
|
)
|
|
85
|
-
tool_content.append({"type": "text", "text": merged_text})
|
|
88
|
+
tool_content.append(cast(BetaTextBlockParam, {"type": "text", "text": merged_text}))
|
|
86
89
|
for image in [part for part in msg.parts if isinstance(part, message.ImageURLPart)]:
|
|
87
90
|
tool_content.append(_image_part_to_block(image))
|
|
88
91
|
for image in attachment.images:
|
|
@@ -95,7 +98,7 @@ def _tool_message_to_block(
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
|
|
98
|
-
def _tool_blocks_to_message(blocks: list[
|
|
101
|
+
def _tool_blocks_to_message(blocks: list[BetaToolResultBlockParam]) -> BetaMessageParam:
|
|
99
102
|
"""Convert one or more tool_result blocks to a single user message."""
|
|
100
103
|
return {
|
|
101
104
|
"role": "user",
|
|
@@ -104,7 +107,7 @@ def _tool_blocks_to_message(blocks: list[dict[str, object]]) -> BetaMessageParam
|
|
|
104
107
|
|
|
105
108
|
|
|
106
109
|
def _assistant_message_to_message(msg: message.AssistantMessage, model_name: str | None) -> BetaMessageParam:
|
|
107
|
-
content: list[
|
|
110
|
+
content: list[BetaContentBlockParam] = []
|
|
108
111
|
current_thinking_content: str | None = None
|
|
109
112
|
native_thinking_parts, degraded_thinking_texts = split_thinking_parts(msg, model_name)
|
|
110
113
|
native_thinking_ids = {id(part) for part in native_thinking_parts}
|
|
@@ -113,7 +116,7 @@ def _assistant_message_to_message(msg: message.AssistantMessage, model_name: str
|
|
|
113
116
|
nonlocal current_thinking_content
|
|
114
117
|
if current_thinking_content is None:
|
|
115
118
|
return
|
|
116
|
-
|
|
119
|
+
degraded_thinking_texts.append(current_thinking_content)
|
|
117
120
|
current_thinking_content = None
|
|
118
121
|
|
|
119
122
|
for part in msg.parts:
|
|
@@ -127,33 +130,47 @@ def _assistant_message_to_message(msg: message.AssistantMessage, model_name: str
|
|
|
127
130
|
continue
|
|
128
131
|
if part.signature:
|
|
129
132
|
content.append(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
cast(
|
|
134
|
+
BetaContentBlockParam,
|
|
135
|
+
{
|
|
136
|
+
"type": "thinking",
|
|
137
|
+
"thinking": current_thinking_content or "",
|
|
138
|
+
"signature": part.signature,
|
|
139
|
+
},
|
|
140
|
+
)
|
|
135
141
|
)
|
|
136
142
|
current_thinking_content = None
|
|
137
143
|
continue
|
|
138
144
|
|
|
139
145
|
_flush_thinking()
|
|
140
146
|
if isinstance(part, message.TextPart):
|
|
141
|
-
content.append({"type": "text", "text": part.text})
|
|
147
|
+
content.append(cast(BetaTextBlockParam, {"type": "text", "text": part.text}))
|
|
142
148
|
elif isinstance(part, message.ToolCallPart):
|
|
149
|
+
tool_input: dict[str, object] = {}
|
|
150
|
+
if part.arguments_json:
|
|
151
|
+
try:
|
|
152
|
+
parsed = json.loads(part.arguments_json)
|
|
153
|
+
except json.JSONDecodeError:
|
|
154
|
+
parsed = {"_raw": part.arguments_json}
|
|
155
|
+
tool_input = cast(dict[str, object], parsed) if isinstance(parsed, dict) else {"_value": parsed}
|
|
156
|
+
|
|
143
157
|
content.append(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
cast(
|
|
159
|
+
BetaToolUseBlockParam,
|
|
160
|
+
{
|
|
161
|
+
"type": "tool_use",
|
|
162
|
+
"id": part.call_id,
|
|
163
|
+
"name": part.tool_name,
|
|
164
|
+
"input": tool_input,
|
|
165
|
+
},
|
|
166
|
+
)
|
|
150
167
|
)
|
|
151
168
|
|
|
152
169
|
_flush_thinking()
|
|
153
170
|
|
|
154
171
|
if degraded_thinking_texts:
|
|
155
172
|
degraded_text = "<thinking>\n" + "\n".join(degraded_thinking_texts) + "\n</thinking>"
|
|
156
|
-
content.insert(0, {"type": "text", "text": degraded_text})
|
|
173
|
+
content.insert(0, cast(BetaTextBlockParam, {"type": "text", "text": degraded_text}))
|
|
157
174
|
|
|
158
175
|
return {"role": "assistant", "content": content}
|
|
159
176
|
|
|
@@ -174,7 +191,7 @@ def convert_history_to_input(
|
|
|
174
191
|
) -> list[BetaMessageParam]:
|
|
175
192
|
"""Convert a list of messages to beta message params."""
|
|
176
193
|
messages: list[BetaMessageParam] = []
|
|
177
|
-
pending_tool_blocks: list[
|
|
194
|
+
pending_tool_blocks: list[BetaToolResultBlockParam] = []
|
|
178
195
|
|
|
179
196
|
def flush_tool_blocks() -> None:
|
|
180
197
|
nonlocal pending_tool_blocks
|
|
@@ -213,7 +230,12 @@ def convert_system_to_input(
|
|
|
213
230
|
parts.append("\n".join(part.text for part in msg.parts))
|
|
214
231
|
if not parts:
|
|
215
232
|
return []
|
|
216
|
-
|
|
233
|
+
block: BetaTextBlockParam = {
|
|
234
|
+
"type": "text",
|
|
235
|
+
"text": "\n".join(parts),
|
|
236
|
+
"cache_control": {"type": "ephemeral"},
|
|
237
|
+
}
|
|
238
|
+
return [block]
|
|
217
239
|
|
|
218
240
|
|
|
219
241
|
def convert_tool_schema(
|
|
@@ -222,11 +244,14 @@ def convert_tool_schema(
|
|
|
222
244
|
if tools is None:
|
|
223
245
|
return []
|
|
224
246
|
return [
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
247
|
+
cast(
|
|
248
|
+
BetaToolParam,
|
|
249
|
+
{
|
|
250
|
+
"input_schema": tool.parameters,
|
|
251
|
+
"type": "custom",
|
|
252
|
+
"name": tool.name,
|
|
253
|
+
"description": tool.description,
|
|
254
|
+
},
|
|
255
|
+
)
|
|
231
256
|
for tool in tools
|
|
232
257
|
]
|
klaude_code/llm/client.py
CHANGED
klaude_code/llm/codex/client.py
CHANGED
|
@@ -31,13 +31,13 @@ from klaude_code.protocol import llm_param, message
|
|
|
31
31
|
|
|
32
32
|
def build_payload(param: llm_param.LLMCallParameter) -> ResponseCreateParamsStreaming:
|
|
33
33
|
"""Build Codex API request parameters."""
|
|
34
|
-
inputs = convert_history_to_input(param.input, param.
|
|
34
|
+
inputs = convert_history_to_input(param.input, param.model_id)
|
|
35
35
|
tools = convert_tool_schema(param.tools)
|
|
36
36
|
|
|
37
37
|
session_id = param.session_id or ""
|
|
38
38
|
|
|
39
39
|
payload: ResponseCreateParamsStreaming = {
|
|
40
|
-
"model": str(param.
|
|
40
|
+
"model": str(param.model_id),
|
|
41
41
|
"tool_choice": "auto",
|
|
42
42
|
"parallel_tool_calls": True,
|
|
43
43
|
"include": [
|
klaude_code/llm/google/client.py
CHANGED
|
@@ -54,7 +54,7 @@ def _build_config(param: llm_param.LLMCallParameter) -> GenerateContentConfig:
|
|
|
54
54
|
system_instruction=param.system,
|
|
55
55
|
temperature=param.temperature,
|
|
56
56
|
max_output_tokens=param.max_tokens,
|
|
57
|
-
tools=tool_list
|
|
57
|
+
tools=cast(Any, tool_list) if tool_list else None,
|
|
58
58
|
tool_config=tool_config,
|
|
59
59
|
thinking_config=thinking_config,
|
|
60
60
|
)
|
|
@@ -163,7 +163,7 @@ async def parse_google_stream(
|
|
|
163
163
|
assistant_parts.append(
|
|
164
164
|
message.ThinkingTextPart(
|
|
165
165
|
text="".join(accumulated_thoughts),
|
|
166
|
-
model_id=str(param.
|
|
166
|
+
model_id=str(param.model_id),
|
|
167
167
|
)
|
|
168
168
|
)
|
|
169
169
|
accumulated_thoughts.clear()
|
|
@@ -171,7 +171,7 @@ async def parse_google_stream(
|
|
|
171
171
|
assistant_parts.append(
|
|
172
172
|
message.ThinkingSignaturePart(
|
|
173
173
|
signature=thought_signature,
|
|
174
|
-
model_id=str(param.
|
|
174
|
+
model_id=str(param.model_id),
|
|
175
175
|
format="google_thought_signature",
|
|
176
176
|
)
|
|
177
177
|
)
|
|
@@ -301,7 +301,7 @@ async def parse_google_stream(
|
|
|
301
301
|
usage = _usage_from_metadata(last_usage_metadata, context_limit=param.context_limit, max_tokens=param.max_tokens)
|
|
302
302
|
if usage is not None:
|
|
303
303
|
metadata_tracker.set_usage(usage)
|
|
304
|
-
metadata_tracker.set_model_name(str(param.
|
|
304
|
+
metadata_tracker.set_model_name(str(param.model_id))
|
|
305
305
|
metadata_tracker.set_response_id(response_id)
|
|
306
306
|
metadata = metadata_tracker.finalize()
|
|
307
307
|
yield message.AssistantMessage(
|
|
@@ -336,13 +336,13 @@ class GoogleClient(LLMClientABC):
|
|
|
336
336
|
param = apply_config_defaults(param, self.get_llm_config())
|
|
337
337
|
metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
|
|
338
338
|
|
|
339
|
-
contents = convert_history_to_contents(param.input, model_name=str(param.
|
|
339
|
+
contents = convert_history_to_contents(param.input, model_name=str(param.model_id))
|
|
340
340
|
config = _build_config(param)
|
|
341
341
|
|
|
342
342
|
log_debug(
|
|
343
343
|
json.dumps(
|
|
344
344
|
{
|
|
345
|
-
"model": str(param.
|
|
345
|
+
"model": str(param.model_id),
|
|
346
346
|
"contents": [c.model_dump(exclude_none=True) for c in contents],
|
|
347
347
|
"config": config.model_dump(exclude_none=True),
|
|
348
348
|
},
|
|
@@ -354,7 +354,7 @@ class GoogleClient(LLMClientABC):
|
|
|
354
354
|
|
|
355
355
|
try:
|
|
356
356
|
stream = await self.client.aio.models.generate_content_stream(
|
|
357
|
-
model=str(param.
|
|
357
|
+
model=str(param.model_id),
|
|
358
358
|
contents=cast(Any, contents),
|
|
359
359
|
config=config,
|
|
360
360
|
)
|
klaude_code/llm/google/input.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# pyright: reportAttributeAccessIssue=false
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
+
from base64 import b64decode
|
|
8
|
+
from binascii import Error as BinasciiError
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
9
11
|
from google.genai import types
|
|
@@ -32,6 +34,14 @@ def _image_part_to_part(image: message.ImageURLPart) -> types.Part:
|
|
|
32
34
|
return types.Part(file_data=types.FileData(file_uri=url))
|
|
33
35
|
|
|
34
36
|
|
|
37
|
+
def _image_part_to_function_response_part(image: message.ImageURLPart) -> types.FunctionResponsePart:
|
|
38
|
+
url = image.url
|
|
39
|
+
if url.startswith("data:"):
|
|
40
|
+
media_type, _, decoded = parse_data_url(url)
|
|
41
|
+
return types.FunctionResponsePart.from_bytes(data=decoded, mime_type=media_type)
|
|
42
|
+
return types.FunctionResponsePart.from_uri(file_uri=url)
|
|
43
|
+
|
|
44
|
+
|
|
35
45
|
def _user_message_to_content(msg: message.UserMessage, attachment: DeveloperAttachment) -> types.Content:
|
|
36
46
|
parts: list[types.Part] = []
|
|
37
47
|
for part in msg.parts:
|
|
@@ -65,9 +75,12 @@ def _tool_messages_to_contents(
|
|
|
65
75
|
|
|
66
76
|
images = [part for part in msg.parts if isinstance(part, message.ImageURLPart)] + attachment.images
|
|
67
77
|
image_parts: list[types.Part] = []
|
|
78
|
+
function_response_parts: list[types.FunctionResponsePart] = []
|
|
79
|
+
|
|
68
80
|
for image in images:
|
|
69
81
|
try:
|
|
70
82
|
image_parts.append(_image_part_to_part(image))
|
|
83
|
+
function_response_parts.append(_image_part_to_function_response_part(image))
|
|
71
84
|
except ValueError:
|
|
72
85
|
continue
|
|
73
86
|
|
|
@@ -79,7 +92,7 @@ def _tool_messages_to_contents(
|
|
|
79
92
|
id=msg.call_id,
|
|
80
93
|
name=msg.tool_name,
|
|
81
94
|
response=response_payload,
|
|
82
|
-
parts=
|
|
95
|
+
parts=function_response_parts if (has_images and supports_multimodal_function_response) else None,
|
|
83
96
|
)
|
|
84
97
|
response_parts.append(types.Part(function_response=function_response))
|
|
85
98
|
|
|
@@ -106,11 +119,19 @@ def _assistant_message_to_content(msg: message.AssistantMessage, model_name: str
|
|
|
106
119
|
nonlocal pending_thought_text, pending_thought_signature
|
|
107
120
|
if pending_thought_text is None and pending_thought_signature is None:
|
|
108
121
|
return
|
|
122
|
+
|
|
123
|
+
signature_bytes: bytes | None = None
|
|
124
|
+
if pending_thought_signature:
|
|
125
|
+
try:
|
|
126
|
+
signature_bytes = b64decode(pending_thought_signature)
|
|
127
|
+
except (BinasciiError, ValueError):
|
|
128
|
+
signature_bytes = None
|
|
129
|
+
|
|
109
130
|
parts.append(
|
|
110
131
|
types.Part(
|
|
111
132
|
text=pending_thought_text or "",
|
|
112
133
|
thought=True,
|
|
113
|
-
thought_signature=
|
|
134
|
+
thought_signature=signature_bytes,
|
|
114
135
|
)
|
|
115
136
|
)
|
|
116
137
|
pending_thought_text = None
|
klaude_code/llm/input_common.py
CHANGED
|
@@ -165,8 +165,8 @@ def split_thinking_parts(
|
|
|
165
165
|
|
|
166
166
|
def apply_config_defaults(param: "LLMCallParameter", config: "LLMConfigParameter") -> "LLMCallParameter":
|
|
167
167
|
"""Apply config defaults to LLM call parameters."""
|
|
168
|
-
if param.
|
|
169
|
-
param.
|
|
168
|
+
if param.model_id is None:
|
|
169
|
+
param.model_id = config.model_id
|
|
170
170
|
if param.temperature is None:
|
|
171
171
|
param.temperature = config.temperature
|
|
172
172
|
if param.max_tokens is None:
|
|
@@ -19,7 +19,7 @@ from klaude_code.protocol import llm_param, message
|
|
|
19
19
|
|
|
20
20
|
def build_payload(param: llm_param.LLMCallParameter) -> tuple[CompletionCreateParamsStreaming, dict[str, object]]:
|
|
21
21
|
"""Build OpenAI API request parameters."""
|
|
22
|
-
messages = convert_history_to_input(param.input, param.system, param.
|
|
22
|
+
messages = convert_history_to_input(param.input, param.system, param.model_id)
|
|
23
23
|
tools = convert_tool_schema(param.tools)
|
|
24
24
|
|
|
25
25
|
extra_body: dict[str, object] = {}
|
|
@@ -31,7 +31,7 @@ def build_payload(param: llm_param.LLMCallParameter) -> tuple[CompletionCreatePa
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
payload: CompletionCreateParamsStreaming = {
|
|
34
|
-
"model": str(param.
|
|
34
|
+
"model": str(param.model_id),
|
|
35
35
|
"tool_choice": "auto",
|
|
36
36
|
"parallel_tool_calls": True,
|
|
37
37
|
"stream": True,
|
|
@@ -108,7 +108,7 @@ class OpenAICompatibleClient(LLMClientABC):
|
|
|
108
108
|
return
|
|
109
109
|
|
|
110
110
|
reasoning_handler = DefaultReasoningHandler(
|
|
111
|
-
param_model=str(param.
|
|
111
|
+
param_model=str(param.model_id),
|
|
112
112
|
response_id=None,
|
|
113
113
|
)
|
|
114
114
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
# pyright: reportUnknownMemberType=false
|
|
4
4
|
# pyright: reportAttributeAccessIssue=false
|
|
5
5
|
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
6
8
|
from openai.types import chat
|
|
7
9
|
from openai.types.chat import ChatCompletionContentPartParam
|
|
8
10
|
|
|
@@ -25,14 +27,16 @@ def _assistant_message_to_openai(msg: message.AssistantMessage) -> chat.ChatComp
|
|
|
25
27
|
assistant_message["content"] = text_content
|
|
26
28
|
|
|
27
29
|
assistant_message.update(build_assistant_common_fields(msg, image_to_data_url=assistant_image_to_data_url))
|
|
28
|
-
return assistant_message
|
|
30
|
+
return cast(chat.ChatCompletionMessageParam, assistant_message)
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
def build_user_content_parts(
|
|
32
34
|
images: list[message.ImageURLPart],
|
|
33
35
|
) -> list[ChatCompletionContentPartParam]:
|
|
34
36
|
"""Build content parts for images only. Used by OpenRouter."""
|
|
35
|
-
return [
|
|
37
|
+
return [
|
|
38
|
+
cast(ChatCompletionContentPartParam, {"type": "image_url", "image_url": {"url": image.url}}) for image in images
|
|
39
|
+
]
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
def convert_history_to_input(
|
|
@@ -42,19 +46,21 @@ def convert_history_to_input(
|
|
|
42
46
|
) -> list[chat.ChatCompletionMessageParam]:
|
|
43
47
|
"""Convert a list of messages to chat completion params."""
|
|
44
48
|
del model_name
|
|
45
|
-
messages: list[chat.ChatCompletionMessageParam] =
|
|
49
|
+
messages: list[chat.ChatCompletionMessageParam] = (
|
|
50
|
+
[cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system})] if system else []
|
|
51
|
+
)
|
|
46
52
|
|
|
47
53
|
for msg, attachment in attach_developer_messages(history):
|
|
48
54
|
match msg:
|
|
49
55
|
case message.SystemMessage():
|
|
50
56
|
system_text = "\n".join(part.text for part in msg.parts)
|
|
51
57
|
if system_text:
|
|
52
|
-
messages.append({"role": "system", "content": system_text})
|
|
58
|
+
messages.append(cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system_text}))
|
|
53
59
|
case message.UserMessage():
|
|
54
60
|
parts = build_chat_content_parts(msg, attachment)
|
|
55
|
-
messages.append({"role": "user", "content": parts})
|
|
61
|
+
messages.append(cast(chat.ChatCompletionMessageParam, {"role": "user", "content": parts}))
|
|
56
62
|
case message.ToolResultMessage():
|
|
57
|
-
messages.append(build_tool_message(msg, attachment))
|
|
63
|
+
messages.append(cast(chat.ChatCompletionMessageParam, build_tool_message(msg, attachment)))
|
|
58
64
|
case message.AssistantMessage():
|
|
59
65
|
messages.append(_assistant_message_to_openai(msg))
|
|
60
66
|
case _:
|
|
@@ -69,13 +75,16 @@ def convert_tool_schema(
|
|
|
69
75
|
if tools is None:
|
|
70
76
|
return []
|
|
71
77
|
return [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
+
cast(
|
|
79
|
+
chat.ChatCompletionToolParam,
|
|
80
|
+
{
|
|
81
|
+
"type": "function",
|
|
82
|
+
"function": {
|
|
83
|
+
"name": tool.name,
|
|
84
|
+
"description": tool.description,
|
|
85
|
+
"parameters": tool.parameters,
|
|
86
|
+
},
|
|
78
87
|
},
|
|
79
|
-
|
|
88
|
+
)
|
|
80
89
|
for tool in tools
|
|
81
90
|
]
|
|
@@ -30,7 +30,7 @@ def build_payload(
|
|
|
30
30
|
param: llm_param.LLMCallParameter,
|
|
31
31
|
) -> tuple[CompletionCreateParamsStreaming, dict[str, object], dict[str, str]]:
|
|
32
32
|
"""Build OpenRouter API request parameters."""
|
|
33
|
-
messages = convert_history_to_input(param.input, param.system, param.
|
|
33
|
+
messages = convert_history_to_input(param.input, param.system, param.model_id)
|
|
34
34
|
tools = convert_tool_schema(param.tools)
|
|
35
35
|
|
|
36
36
|
extra_body: dict[str, object] = {
|
|
@@ -66,13 +66,13 @@ def build_payload(
|
|
|
66
66
|
if param.provider_routing:
|
|
67
67
|
extra_body["provider"] = param.provider_routing.model_dump(exclude_none=True)
|
|
68
68
|
|
|
69
|
-
if is_claude_model(param.
|
|
69
|
+
if is_claude_model(param.model_id):
|
|
70
70
|
extra_headers["x-anthropic-beta"] = (
|
|
71
71
|
f"{ANTHROPIC_BETA_FINE_GRAINED_TOOL_STREAMING},{ANTHROPIC_BETA_INTERLEAVED_THINKING}"
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
payload: CompletionCreateParamsStreaming = {
|
|
75
|
-
"model": str(param.
|
|
75
|
+
"model": str(param.model_id),
|
|
76
76
|
"tool_choice": "auto",
|
|
77
77
|
"parallel_tool_calls": True,
|
|
78
78
|
"stream": True,
|
|
@@ -133,7 +133,7 @@ class OpenRouterClient(LLMClientABC):
|
|
|
133
133
|
return
|
|
134
134
|
|
|
135
135
|
reasoning_handler = ReasoningStreamHandler(
|
|
136
|
-
param_model=str(param.
|
|
136
|
+
param_model=str(param.model_id),
|
|
137
137
|
response_id=None,
|
|
138
138
|
)
|
|
139
139
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
# pyright: reportUnnecessaryIsInstance=false
|
|
7
7
|
# pyright: reportGeneralTypeIssues=false
|
|
8
8
|
|
|
9
|
+
from typing import cast
|
|
10
|
+
|
|
9
11
|
from openai.types import chat
|
|
10
12
|
|
|
11
13
|
from klaude_code.llm.image import assistant_image_to_data_url
|
|
@@ -71,7 +73,7 @@ def _assistant_message_to_openrouter(
|
|
|
71
73
|
if content_parts:
|
|
72
74
|
assistant_message["content"] = "\n".join(content_parts)
|
|
73
75
|
|
|
74
|
-
return assistant_message
|
|
76
|
+
return cast(chat.ChatCompletionMessageParam, assistant_message)
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
def _add_cache_control(messages: list[chat.ChatCompletionMessageParam], use_cache_control: bool) -> None:
|
|
@@ -98,19 +100,22 @@ def convert_history_to_input(
|
|
|
98
100
|
|
|
99
101
|
messages: list[chat.ChatCompletionMessageParam] = (
|
|
100
102
|
[
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
cast(
|
|
104
|
+
chat.ChatCompletionMessageParam,
|
|
105
|
+
{
|
|
106
|
+
"role": "system",
|
|
107
|
+
"content": [
|
|
108
|
+
{
|
|
109
|
+
"type": "text",
|
|
110
|
+
"text": system,
|
|
111
|
+
"cache_control": {"type": "ephemeral"},
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
)
|
|
111
116
|
]
|
|
112
117
|
if system and use_cache_control
|
|
113
|
-
else ([{"role": "system", "content": system}] if system else [])
|
|
118
|
+
else ([cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system})] if system else [])
|
|
114
119
|
)
|
|
115
120
|
|
|
116
121
|
for msg, attachment in attach_developer_messages(history):
|
|
@@ -120,24 +125,29 @@ def convert_history_to_input(
|
|
|
120
125
|
if system_text:
|
|
121
126
|
if use_cache_control:
|
|
122
127
|
messages.append(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
cast(
|
|
129
|
+
chat.ChatCompletionMessageParam,
|
|
130
|
+
{
|
|
131
|
+
"role": "system",
|
|
132
|
+
"content": [
|
|
133
|
+
{
|
|
134
|
+
"type": "text",
|
|
135
|
+
"text": system_text,
|
|
136
|
+
"cache_control": {"type": "ephemeral"},
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
)
|
|
133
141
|
)
|
|
134
142
|
else:
|
|
135
|
-
messages.append(
|
|
143
|
+
messages.append(
|
|
144
|
+
cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system_text})
|
|
145
|
+
)
|
|
136
146
|
case message.UserMessage():
|
|
137
147
|
parts = build_chat_content_parts(msg, attachment)
|
|
138
|
-
messages.append({"role": "user", "content": parts})
|
|
148
|
+
messages.append(cast(chat.ChatCompletionMessageParam, {"role": "user", "content": parts}))
|
|
139
149
|
case message.ToolResultMessage():
|
|
140
|
-
messages.append(build_tool_message(msg, attachment))
|
|
150
|
+
messages.append(cast(chat.ChatCompletionMessageParam, build_tool_message(msg, attachment)))
|
|
141
151
|
case message.AssistantMessage():
|
|
142
152
|
messages.append(_assistant_message_to_openrouter(msg, model_name))
|
|
143
153
|
case _:
|
|
@@ -24,11 +24,11 @@ if TYPE_CHECKING:
|
|
|
24
24
|
|
|
25
25
|
def build_payload(param: llm_param.LLMCallParameter) -> ResponseCreateParamsStreaming:
|
|
26
26
|
"""Build OpenAI Responses API request parameters."""
|
|
27
|
-
inputs = convert_history_to_input(param.input, param.
|
|
27
|
+
inputs = convert_history_to_input(param.input, param.model_id)
|
|
28
28
|
tools = convert_tool_schema(param.tools)
|
|
29
29
|
|
|
30
30
|
payload: ResponseCreateParamsStreaming = {
|
|
31
|
-
"model": str(param.
|
|
31
|
+
"model": str(param.model_id),
|
|
32
32
|
"tool_choice": "auto",
|
|
33
33
|
"parallel_tool_calls": True,
|
|
34
34
|
"include": [
|
|
@@ -77,7 +77,7 @@ async def parse_responses_stream(
|
|
|
77
77
|
assistant_parts.append(
|
|
78
78
|
message.ThinkingTextPart(
|
|
79
79
|
text="".join(accumulated_thinking),
|
|
80
|
-
model_id=str(param.
|
|
80
|
+
model_id=str(param.model_id),
|
|
81
81
|
)
|
|
82
82
|
)
|
|
83
83
|
accumulated_thinking.clear()
|
|
@@ -85,7 +85,7 @@ async def parse_responses_stream(
|
|
|
85
85
|
assistant_parts.append(
|
|
86
86
|
message.ThinkingSignaturePart(
|
|
87
87
|
signature=pending_signature,
|
|
88
|
-
model_id=str(param.
|
|
88
|
+
model_id=str(param.model_id),
|
|
89
89
|
format="openai_reasoning",
|
|
90
90
|
)
|
|
91
91
|
)
|
|
@@ -197,7 +197,7 @@ async def parse_responses_stream(
|
|
|
197
197
|
max_tokens=param.max_tokens,
|
|
198
198
|
)
|
|
199
199
|
)
|
|
200
|
-
metadata_tracker.set_model_name(str(param.
|
|
200
|
+
metadata_tracker.set_model_name(str(param.model_id))
|
|
201
201
|
metadata_tracker.set_response_id(response_id)
|
|
202
202
|
stop_reason = map_stop_reason(event.response.status, error_reason)
|
|
203
203
|
if event.response.status != "completed":
|