klaude-code 2.2.0__py3-none-any.whl → 2.3.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 +27 -10
- klaude_code/cli/main.py +25 -9
- klaude_code/config/assets/builtin_config.yaml +25 -16
- klaude_code/config/config.py +144 -7
- klaude_code/config/select_model.py +38 -13
- klaude_code/config/sub_agent_model_helper.py +217 -0
- klaude_code/const.py +1 -1
- klaude_code/core/agent_profile.py +43 -5
- klaude_code/core/executor.py +75 -0
- klaude_code/core/manager/llm_clients_builder.py +17 -11
- klaude_code/core/prompts/prompt-nano-banana.md +1 -1
- klaude_code/core/tool/sub_agent_tool.py +2 -1
- klaude_code/llm/anthropic/client.py +7 -4
- klaude_code/llm/anthropic/input.py +54 -29
- klaude_code/llm/google/client.py +1 -1
- klaude_code/llm/google/input.py +23 -2
- klaude_code/llm/openai_compatible/input.py +22 -13
- klaude_code/llm/openrouter/input.py +37 -25
- klaude_code/llm/responses/input.py +96 -57
- klaude_code/protocol/commands.py +1 -2
- klaude_code/protocol/events/system.py +4 -0
- klaude_code/protocol/op.py +17 -0
- 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 +9 -1
- klaude_code/skill/assets/create-plan/SKILL.md +3 -5
- klaude_code/tui/command/__init__.py +3 -6
- klaude_code/tui/command/model_cmd.py +6 -43
- klaude_code/tui/command/model_select.py +75 -15
- klaude_code/tui/command/sub_agent_model_cmd.py +190 -0
- klaude_code/tui/components/bash_syntax.py +1 -1
- klaude_code/tui/components/common.py +1 -1
- klaude_code/tui/components/developer.py +0 -5
- 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 +14 -1
- klaude_code/tui/renderer.py +2 -3
- klaude_code/tui/terminal/selector.py +5 -3
- {klaude_code-2.2.0.dist-info → klaude_code-2.3.0.dist-info}/METADATA +1 -1
- {klaude_code-2.2.0.dist-info → klaude_code-2.3.0.dist-info}/RECORD +50 -48
- klaude_code/tui/command/help_cmd.py +0 -51
- klaude_code/tui/command/release_notes_cmd.py +0 -85
- {klaude_code-2.2.0.dist-info → klaude_code-2.3.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.2.0.dist-info → klaude_code-2.3.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/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
|
)
|
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
|
|
@@ -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
|
]
|
|
@@ -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,24 @@ 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 (
|
|
118
|
+
else (
|
|
119
|
+
[cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system})] if system else []
|
|
120
|
+
)
|
|
114
121
|
)
|
|
115
122
|
|
|
116
123
|
for msg, attachment in attach_developer_messages(history):
|
|
@@ -120,24 +127,29 @@ def convert_history_to_input(
|
|
|
120
127
|
if system_text:
|
|
121
128
|
if use_cache_control:
|
|
122
129
|
messages.append(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
cast(
|
|
131
|
+
chat.ChatCompletionMessageParam,
|
|
132
|
+
{
|
|
133
|
+
"role": "system",
|
|
134
|
+
"content": [
|
|
135
|
+
{
|
|
136
|
+
"type": "text",
|
|
137
|
+
"text": system_text,
|
|
138
|
+
"cache_control": {"type": "ephemeral"},
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
)
|
|
133
143
|
)
|
|
134
144
|
else:
|
|
135
|
-
messages.append(
|
|
145
|
+
messages.append(
|
|
146
|
+
cast(chat.ChatCompletionMessageParam, {"role": "system", "content": system_text})
|
|
147
|
+
)
|
|
136
148
|
case message.UserMessage():
|
|
137
149
|
parts = build_chat_content_parts(msg, attachment)
|
|
138
|
-
messages.append({"role": "user", "content": parts})
|
|
150
|
+
messages.append(cast(chat.ChatCompletionMessageParam, {"role": "user", "content": parts}))
|
|
139
151
|
case message.ToolResultMessage():
|
|
140
|
-
messages.append(build_tool_message(msg, attachment))
|
|
152
|
+
messages.append(cast(chat.ChatCompletionMessageParam, build_tool_message(msg, attachment)))
|
|
141
153
|
case message.AssistantMessage():
|
|
142
154
|
messages.append(_assistant_message_to_openrouter(msg, model_name))
|
|
143
155
|
case _:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# pyright: reportArgumentType=false
|
|
3
3
|
# pyright: reportAssignmentType=false
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, cast
|
|
6
6
|
|
|
7
7
|
from openai.types import responses
|
|
8
8
|
|
|
@@ -23,15 +23,25 @@ def _build_user_content_parts(
|
|
|
23
23
|
parts: list[responses.ResponseInputContentParam] = []
|
|
24
24
|
for part in user.parts:
|
|
25
25
|
if isinstance(part, message.TextPart):
|
|
26
|
-
parts.append({"type": "input_text", "text": part.text})
|
|
26
|
+
parts.append(cast(responses.ResponseInputContentParam, {"type": "input_text", "text": part.text}))
|
|
27
27
|
elif isinstance(part, message.ImageURLPart):
|
|
28
|
-
parts.append(
|
|
28
|
+
parts.append(
|
|
29
|
+
cast(
|
|
30
|
+
responses.ResponseInputContentParam,
|
|
31
|
+
{"type": "input_image", "detail": "auto", "image_url": part.url},
|
|
32
|
+
)
|
|
33
|
+
)
|
|
29
34
|
if attachment.text:
|
|
30
|
-
parts.append({"type": "input_text", "text": attachment.text})
|
|
35
|
+
parts.append(cast(responses.ResponseInputContentParam, {"type": "input_text", "text": attachment.text}))
|
|
31
36
|
for image in attachment.images:
|
|
32
|
-
parts.append(
|
|
37
|
+
parts.append(
|
|
38
|
+
cast(
|
|
39
|
+
responses.ResponseInputContentParam,
|
|
40
|
+
{"type": "input_image", "detail": "auto", "image_url": image.url},
|
|
41
|
+
)
|
|
42
|
+
)
|
|
33
43
|
if not parts:
|
|
34
|
-
parts.append({"type": "input_text", "text": ""})
|
|
44
|
+
parts.append(cast(responses.ResponseInputContentParam, {"type": "input_text", "text": ""}))
|
|
35
45
|
return parts
|
|
36
46
|
|
|
37
47
|
|
|
@@ -45,17 +55,22 @@ def _build_tool_result_item(
|
|
|
45
55
|
attachment.text,
|
|
46
56
|
)
|
|
47
57
|
if text_output:
|
|
48
|
-
content_parts.append({"type": "input_text", "text": text_output})
|
|
58
|
+
content_parts.append(cast(responses.ResponseInputContentParam, {"type": "input_text", "text": text_output}))
|
|
49
59
|
images = [part for part in tool.parts if isinstance(part, message.ImageURLPart)] + attachment.images
|
|
50
60
|
for image in images:
|
|
51
|
-
content_parts.append(
|
|
61
|
+
content_parts.append(
|
|
62
|
+
cast(
|
|
63
|
+
responses.ResponseInputContentParam,
|
|
64
|
+
{"type": "input_image", "detail": "auto", "image_url": image.url},
|
|
65
|
+
)
|
|
66
|
+
)
|
|
52
67
|
|
|
53
68
|
item: dict[str, Any] = {
|
|
54
69
|
"type": "function_call_output",
|
|
55
70
|
"call_id": tool.call_id,
|
|
56
71
|
"output": content_parts,
|
|
57
72
|
}
|
|
58
|
-
return item
|
|
73
|
+
return cast(responses.ResponseInputItemParam, item)
|
|
59
74
|
|
|
60
75
|
|
|
61
76
|
def convert_history_to_input(
|
|
@@ -73,25 +88,30 @@ def convert_history_to_input(
|
|
|
73
88
|
system_text = "\n".join(part.text for part in msg.parts)
|
|
74
89
|
if system_text:
|
|
75
90
|
items.append(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
cast(
|
|
92
|
+
responses.ResponseInputItemParam,
|
|
93
|
+
{
|
|
94
|
+
"type": "message",
|
|
95
|
+
"role": "system",
|
|
96
|
+
"content": [
|
|
97
|
+
cast(
|
|
98
|
+
responses.ResponseInputContentParam,
|
|
99
|
+
{"type": "input_text", "text": system_text},
|
|
100
|
+
)
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
)
|
|
86
104
|
)
|
|
87
105
|
case message.UserMessage():
|
|
88
106
|
items.append(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
107
|
+
cast(
|
|
108
|
+
responses.ResponseInputItemParam,
|
|
109
|
+
{
|
|
110
|
+
"type": "message",
|
|
111
|
+
"role": "user",
|
|
112
|
+
"content": _build_user_content_parts(msg, attachment),
|
|
113
|
+
},
|
|
114
|
+
)
|
|
95
115
|
)
|
|
96
116
|
case message.ToolResultMessage():
|
|
97
117
|
items.append(_build_tool_result_item(msg, attachment))
|
|
@@ -103,17 +123,19 @@ def convert_history_to_input(
|
|
|
103
123
|
native_thinking_ids = {id(part) for part in native_thinking_parts}
|
|
104
124
|
degraded_thinking_texts.extend(degraded_for_message)
|
|
105
125
|
|
|
106
|
-
def flush_text(
|
|
126
|
+
def flush_text() -> None:
|
|
107
127
|
nonlocal assistant_text_parts
|
|
108
128
|
if not assistant_text_parts:
|
|
109
129
|
return
|
|
110
130
|
items.append(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
cast(
|
|
132
|
+
responses.ResponseInputItemParam,
|
|
133
|
+
{
|
|
134
|
+
"type": "message",
|
|
135
|
+
"role": "assistant",
|
|
136
|
+
"content": assistant_text_parts,
|
|
137
|
+
},
|
|
138
|
+
)
|
|
117
139
|
)
|
|
118
140
|
assistant_text_parts = []
|
|
119
141
|
|
|
@@ -140,17 +162,25 @@ def convert_history_to_input(
|
|
|
140
162
|
|
|
141
163
|
emit_reasoning()
|
|
142
164
|
if isinstance(part, message.TextPart):
|
|
143
|
-
assistant_text_parts.append(
|
|
165
|
+
assistant_text_parts.append(
|
|
166
|
+
cast(
|
|
167
|
+
responses.ResponseInputContentParam,
|
|
168
|
+
{"type": "input_text", "text": part.text},
|
|
169
|
+
)
|
|
170
|
+
)
|
|
144
171
|
elif isinstance(part, message.ToolCallPart):
|
|
145
172
|
flush_text()
|
|
146
173
|
items.append(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
cast(
|
|
175
|
+
responses.ResponseInputItemParam,
|
|
176
|
+
{
|
|
177
|
+
"type": "function_call",
|
|
178
|
+
"name": part.tool_name,
|
|
179
|
+
"arguments": part.arguments_json,
|
|
180
|
+
"call_id": part.call_id,
|
|
181
|
+
"id": part.id,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
154
184
|
)
|
|
155
185
|
|
|
156
186
|
emit_reasoning()
|
|
@@ -159,16 +189,22 @@ def convert_history_to_input(
|
|
|
159
189
|
continue
|
|
160
190
|
|
|
161
191
|
if degraded_thinking_texts:
|
|
162
|
-
degraded_item
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
192
|
+
degraded_item = cast(
|
|
193
|
+
responses.ResponseInputItemParam,
|
|
194
|
+
{
|
|
195
|
+
"type": "message",
|
|
196
|
+
"role": "assistant",
|
|
197
|
+
"content": [
|
|
198
|
+
cast(
|
|
199
|
+
responses.ResponseInputContentParam,
|
|
200
|
+
{
|
|
201
|
+
"type": "input_text",
|
|
202
|
+
"text": "<thinking>\n" + "\n".join(degraded_thinking_texts) + "\n</thinking>",
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
)
|
|
172
208
|
items.insert(0, degraded_item)
|
|
173
209
|
|
|
174
210
|
return items
|
|
@@ -184,7 +220,7 @@ def convert_reasoning_inputs(text_content: str | None, signature: str | None) ->
|
|
|
184
220
|
]
|
|
185
221
|
if signature:
|
|
186
222
|
result["encrypted_content"] = signature
|
|
187
|
-
return result
|
|
223
|
+
return cast(responses.ResponseInputItemParam, result)
|
|
188
224
|
|
|
189
225
|
|
|
190
226
|
def convert_tool_schema(
|
|
@@ -193,11 +229,14 @@ def convert_tool_schema(
|
|
|
193
229
|
if tools is None:
|
|
194
230
|
return []
|
|
195
231
|
return [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
232
|
+
cast(
|
|
233
|
+
responses.ToolParam,
|
|
234
|
+
{
|
|
235
|
+
"type": "function",
|
|
236
|
+
"name": tool.name,
|
|
237
|
+
"description": tool.description,
|
|
238
|
+
"parameters": tool.parameters,
|
|
239
|
+
},
|
|
240
|
+
)
|
|
202
241
|
for tool in tools
|
|
203
242
|
]
|