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.
Files changed (52) hide show
  1. klaude_code/app/runtime.py +2 -15
  2. klaude_code/cli/list_model.py +27 -10
  3. klaude_code/cli/main.py +25 -9
  4. klaude_code/config/assets/builtin_config.yaml +25 -16
  5. klaude_code/config/config.py +144 -7
  6. klaude_code/config/select_model.py +38 -13
  7. klaude_code/config/sub_agent_model_helper.py +217 -0
  8. klaude_code/const.py +1 -1
  9. klaude_code/core/agent_profile.py +43 -5
  10. klaude_code/core/executor.py +75 -0
  11. klaude_code/core/manager/llm_clients_builder.py +17 -11
  12. klaude_code/core/prompts/prompt-nano-banana.md +1 -1
  13. klaude_code/core/tool/sub_agent_tool.py +2 -1
  14. klaude_code/llm/anthropic/client.py +7 -4
  15. klaude_code/llm/anthropic/input.py +54 -29
  16. klaude_code/llm/google/client.py +1 -1
  17. klaude_code/llm/google/input.py +23 -2
  18. klaude_code/llm/openai_compatible/input.py +22 -13
  19. klaude_code/llm/openrouter/input.py +37 -25
  20. klaude_code/llm/responses/input.py +96 -57
  21. klaude_code/protocol/commands.py +1 -2
  22. klaude_code/protocol/events/system.py +4 -0
  23. klaude_code/protocol/op.py +17 -0
  24. klaude_code/protocol/op_handler.py +5 -0
  25. klaude_code/protocol/sub_agent/AGENTS.md +28 -0
  26. klaude_code/protocol/sub_agent/__init__.py +10 -14
  27. klaude_code/protocol/sub_agent/image_gen.py +2 -1
  28. klaude_code/session/codec.py +2 -6
  29. klaude_code/session/session.py +9 -1
  30. klaude_code/skill/assets/create-plan/SKILL.md +3 -5
  31. klaude_code/tui/command/__init__.py +3 -6
  32. klaude_code/tui/command/model_cmd.py +6 -43
  33. klaude_code/tui/command/model_select.py +75 -15
  34. klaude_code/tui/command/sub_agent_model_cmd.py +190 -0
  35. klaude_code/tui/components/bash_syntax.py +1 -1
  36. klaude_code/tui/components/common.py +1 -1
  37. klaude_code/tui/components/developer.py +0 -5
  38. klaude_code/tui/components/metadata.py +1 -63
  39. klaude_code/tui/components/rich/cjk_wrap.py +3 -2
  40. klaude_code/tui/components/rich/status.py +49 -3
  41. klaude_code/tui/components/rich/theme.py +2 -0
  42. klaude_code/tui/components/sub_agent.py +25 -46
  43. klaude_code/tui/components/welcome.py +99 -0
  44. klaude_code/tui/input/prompt_toolkit.py +14 -1
  45. klaude_code/tui/renderer.py +2 -3
  46. klaude_code/tui/terminal/selector.py +5 -3
  47. {klaude_code-2.2.0.dist-info → klaude_code-2.3.0.dist-info}/METADATA +1 -1
  48. {klaude_code-2.2.0.dist-info → klaude_code-2.3.0.dist-info}/RECORD +50 -48
  49. klaude_code/tui/command/help_cmd.py +0 -51
  50. klaude_code/tui/command/release_notes_cmd.py +0 -85
  51. {klaude_code-2.2.0.dist-info → klaude_code-2.3.0.dist-info}/WHEEL +0 -0
  52. {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
- ) -> dict[str, object]:
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[dict[str, object]]) -> BetaMessageParam:
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[dict[str, object]] = []
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
- content.append({"type": "thinking", "thinking": current_thinking_content})
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
- "type": "thinking",
132
- "thinking": current_thinking_content or "",
133
- "signature": part.signature,
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
- "type": "tool_use",
146
- "id": part.call_id,
147
- "name": part.tool_name,
148
- "input": json.loads(part.arguments_json) if part.arguments_json else None,
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[dict[str, object]] = []
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
- return [{"type": "text", "text": "\n".join(parts), "cache_control": {"type": "ephemeral"}}]
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
- "input_schema": tool.parameters,
227
- "type": "custom",
228
- "name": tool.name,
229
- "description": tool.description,
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
  ]
@@ -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 or None,
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
  )
@@ -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=image_parts if (has_images and supports_multimodal_function_response) else None,
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=pending_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 [{"type": "image_url", "image_url": {"url": image.url}} for image in images]
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] = [{"role": "system", "content": system}] if system else []
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
- "type": "function",
74
- "function": {
75
- "name": tool.name,
76
- "description": tool.description,
77
- "parameters": tool.parameters,
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
- "role": "system",
103
- "content": [
104
- {
105
- "type": "text",
106
- "text": system,
107
- "cache_control": {"type": "ephemeral"},
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 (
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
- "role": "system",
125
- "content": [
126
- {
127
- "type": "text",
128
- "text": system_text,
129
- "cache_control": {"type": "ephemeral"},
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({"role": "system", "content": system_text})
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({"type": "input_image", "detail": "auto", "image_url": part.url})
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({"type": "input_image", "detail": "auto", "image_url": image.url})
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({"type": "input_image", "detail": "auto", "image_url": image.url})
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
- "type": "message",
78
- "role": "system",
79
- "content": [
80
- {
81
- "type": "input_text",
82
- "text": system_text,
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
- "type": "message",
91
- "role": "user",
92
- "id": msg.id,
93
- "content": _build_user_content_parts(msg, attachment),
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(*, _message_id: str = msg.id) -> None:
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
- "type": "message",
113
- "role": "assistant",
114
- "id": _message_id,
115
- "content": assistant_text_parts,
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({"type": "output_text", "text": part.text})
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
- "type": "function_call",
149
- "name": part.tool_name,
150
- "arguments": part.arguments_json,
151
- "call_id": part.call_id,
152
- "id": part.id,
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: responses.ResponseInputItemParam = {
163
- "type": "message",
164
- "role": "assistant",
165
- "content": [
166
- {
167
- "type": "output_text",
168
- "text": "<thinking>\n" + "\n".join(degraded_thinking_texts) + "\n</thinking>",
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
- "type": "function",
198
- "name": tool.name,
199
- "description": tool.description,
200
- "parameters": tool.parameters,
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
  ]