klaude-code 2.8.0__py3-none-any.whl → 2.9.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 (100) hide show
  1. klaude_code/app/runtime.py +2 -1
  2. klaude_code/auth/antigravity/oauth.py +0 -9
  3. klaude_code/auth/antigravity/token_manager.py +0 -18
  4. klaude_code/auth/base.py +53 -0
  5. klaude_code/auth/codex/exceptions.py +0 -4
  6. klaude_code/auth/codex/oauth.py +32 -28
  7. klaude_code/auth/codex/token_manager.py +0 -18
  8. klaude_code/cli/cost_cmd.py +128 -39
  9. klaude_code/cli/list_model.py +27 -10
  10. klaude_code/cli/main.py +15 -4
  11. klaude_code/config/assets/builtin_config.yaml +8 -24
  12. klaude_code/config/config.py +47 -25
  13. klaude_code/config/sub_agent_model_helper.py +18 -13
  14. klaude_code/config/thinking.py +0 -8
  15. klaude_code/const.py +2 -2
  16. klaude_code/core/agent_profile.py +11 -53
  17. klaude_code/core/compaction/compaction.py +4 -6
  18. klaude_code/core/compaction/overflow.py +0 -4
  19. klaude_code/core/executor.py +51 -5
  20. klaude_code/core/manager/llm_clients.py +9 -1
  21. klaude_code/core/prompts/prompt-claude-code.md +4 -4
  22. klaude_code/core/reminders.py +21 -23
  23. klaude_code/core/task.py +0 -4
  24. klaude_code/core/tool/__init__.py +3 -2
  25. klaude_code/core/tool/file/apply_patch.py +0 -27
  26. klaude_code/core/tool/file/edit_tool.py +1 -2
  27. klaude_code/core/tool/file/read_tool.md +3 -2
  28. klaude_code/core/tool/file/read_tool.py +15 -2
  29. klaude_code/core/tool/offload.py +0 -35
  30. klaude_code/core/tool/sub_agent/__init__.py +6 -0
  31. klaude_code/core/tool/sub_agent/image_gen.md +16 -0
  32. klaude_code/core/tool/sub_agent/image_gen.py +146 -0
  33. klaude_code/core/tool/sub_agent/task.md +20 -0
  34. klaude_code/core/tool/sub_agent/task.py +205 -0
  35. klaude_code/core/tool/tool_registry.py +0 -16
  36. klaude_code/core/turn.py +1 -1
  37. klaude_code/llm/anthropic/input.py +6 -5
  38. klaude_code/llm/antigravity/input.py +14 -7
  39. klaude_code/llm/codex/client.py +22 -0
  40. klaude_code/llm/codex/prompt_sync.py +237 -0
  41. klaude_code/llm/google/client.py +8 -6
  42. klaude_code/llm/google/input.py +20 -12
  43. klaude_code/llm/image.py +18 -11
  44. klaude_code/llm/input_common.py +14 -6
  45. klaude_code/llm/json_stable.py +37 -0
  46. klaude_code/llm/openai_compatible/input.py +0 -10
  47. klaude_code/llm/openai_compatible/stream.py +16 -1
  48. klaude_code/llm/registry.py +0 -5
  49. klaude_code/llm/responses/input.py +15 -5
  50. klaude_code/llm/usage.py +0 -8
  51. klaude_code/protocol/commands.py +1 -0
  52. klaude_code/protocol/events.py +2 -1
  53. klaude_code/protocol/message.py +2 -2
  54. klaude_code/protocol/model.py +20 -1
  55. klaude_code/protocol/op.py +27 -0
  56. klaude_code/protocol/op_handler.py +10 -0
  57. klaude_code/protocol/sub_agent/AGENTS.md +5 -5
  58. klaude_code/protocol/sub_agent/__init__.py +13 -34
  59. klaude_code/protocol/sub_agent/explore.py +7 -34
  60. klaude_code/protocol/sub_agent/image_gen.py +3 -74
  61. klaude_code/protocol/sub_agent/task.py +3 -47
  62. klaude_code/protocol/sub_agent/web.py +8 -52
  63. klaude_code/protocol/tools.py +2 -0
  64. klaude_code/session/export.py +308 -299
  65. klaude_code/session/session.py +58 -21
  66. klaude_code/session/store.py +0 -4
  67. klaude_code/session/templates/export_session.html +430 -134
  68. klaude_code/skill/assets/deslop/SKILL.md +9 -0
  69. klaude_code/skill/system_skills.py +0 -20
  70. klaude_code/tui/command/__init__.py +3 -0
  71. klaude_code/tui/command/continue_cmd.py +34 -0
  72. klaude_code/tui/command/fork_session_cmd.py +5 -2
  73. klaude_code/tui/command/resume_cmd.py +9 -2
  74. klaude_code/tui/command/sub_agent_model_cmd.py +85 -18
  75. klaude_code/tui/components/assistant.py +0 -26
  76. klaude_code/tui/components/command_output.py +3 -1
  77. klaude_code/tui/components/developer.py +3 -0
  78. klaude_code/tui/components/diffs.py +2 -208
  79. klaude_code/tui/components/errors.py +4 -0
  80. klaude_code/tui/components/mermaid_viewer.py +2 -2
  81. klaude_code/tui/components/rich/markdown.py +60 -63
  82. klaude_code/tui/components/rich/theme.py +2 -0
  83. klaude_code/tui/components/sub_agent.py +2 -46
  84. klaude_code/tui/components/thinking.py +0 -33
  85. klaude_code/tui/components/tools.py +43 -21
  86. klaude_code/tui/input/images.py +21 -18
  87. klaude_code/tui/input/key_bindings.py +2 -2
  88. klaude_code/tui/input/prompt_toolkit.py +49 -49
  89. klaude_code/tui/machine.py +15 -11
  90. klaude_code/tui/renderer.py +12 -20
  91. klaude_code/tui/runner.py +2 -1
  92. klaude_code/tui/terminal/image.py +6 -34
  93. klaude_code/ui/common.py +0 -70
  94. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/METADATA +3 -6
  95. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/RECORD +97 -92
  96. klaude_code/core/tool/sub_agent_tool.py +0 -126
  97. klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -108
  98. klaude_code/tui/components/rich/searchable_text.py +0 -68
  99. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/WHEEL +0 -0
  100. {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/entry_points.txt +0 -0
@@ -21,20 +21,6 @@ AT_FILE_PATTERN = re.compile(r'(?:(?<!\S)|(?<=\u2192))@("(?P<quoted>[^\"]+)"|(?P
21
21
  SKILL_PATTERN = re.compile(r"(?:^|\s)[$¥](?P<skill>\S+)")
22
22
 
23
23
 
24
- def get_last_new_user_input(session: Session) -> str | None:
25
- """Get last user input & developer message (CLAUDE.md) from conversation history. if there's a tool result after user input, return None"""
26
- result: list[str] = []
27
- for item in reversed(session.conversation_history):
28
- if isinstance(item, message.ToolResultMessage):
29
- return None
30
- if isinstance(item, message.UserMessage):
31
- result.append(message.join_text_parts(item.parts))
32
- break
33
- if isinstance(item, message.DeveloperMessage):
34
- result.append(message.join_text_parts(item.parts))
35
- return "\n\n".join(result)
36
-
37
-
38
24
  @dataclass
39
25
  class AtPatternSource:
40
26
  """Represents an @ pattern with its source file (if from a memory file)."""
@@ -115,6 +101,7 @@ async def _load_at_file_recursive(
115
101
  at_ops: list[model.AtFileOp],
116
102
  formatted_blocks: list[str],
117
103
  collected_images: list[message.ImageURLPart],
104
+ collected_image_paths: list[str],
118
105
  visited: set[str],
119
106
  base_dir: Path | None = None,
120
107
  mentioned_in: str | None = None,
@@ -150,6 +137,7 @@ Result of calling the {tools.READ} tool:
150
137
  at_ops.append(model.AtFileOp(operation="Read", path=path_str, mentioned_in=mentioned_in))
151
138
  if images:
152
139
  collected_images.extend(images)
140
+ collected_image_paths.append(path_str)
153
141
 
154
142
  # Recursively parse @ references from ReadTool output
155
143
  output = tool_result.output_text
@@ -163,6 +151,7 @@ Result of calling the {tools.READ} tool:
163
151
  at_ops,
164
152
  formatted_blocks,
165
153
  collected_images,
154
+ collected_image_paths,
166
155
  visited,
167
156
  base_dir=path.parent,
168
157
  mentioned_in=path_str,
@@ -193,6 +182,7 @@ async def at_file_reader_reminder(
193
182
  at_ops: list[model.AtFileOp] = []
194
183
  formatted_blocks: list[str] = []
195
184
  collected_images: list[message.ImageURLPart] = []
185
+ collected_image_paths: list[str] = []
196
186
  visited: set[str] = set()
197
187
 
198
188
  for source in at_pattern_sources:
@@ -202,6 +192,7 @@ async def at_file_reader_reminder(
202
192
  at_ops,
203
193
  formatted_blocks,
204
194
  collected_images,
195
+ collected_image_paths,
205
196
  visited,
206
197
  mentioned_in=source.mentioned_in,
207
198
  )
@@ -210,12 +201,15 @@ async def at_file_reader_reminder(
210
201
  return None
211
202
 
212
203
  at_files_str = "\n\n".join(formatted_blocks)
204
+ ui_items: list[model.DeveloperUIItem] = [model.AtFileOpsUIItem(ops=at_ops)]
205
+ if collected_image_paths:
206
+ ui_items.append(model.AtFileImagesUIItem(paths=collected_image_paths))
213
207
  return message.DeveloperMessage(
214
208
  parts=message.parts_from_text_and_images(
215
209
  f"""<system-reminder>{at_files_str}\n</system-reminder>""",
216
210
  collected_images or None,
217
211
  ),
218
- ui_extra=model.DeveloperUIExtra(items=[model.AtFileOpsUIItem(ops=at_ops)]),
212
+ ui_extra=model.DeveloperUIExtra(items=ui_items),
219
213
  )
220
214
 
221
215
 
@@ -410,25 +404,29 @@ class Memory(BaseModel):
410
404
  content: str
411
405
 
412
406
 
413
- def get_last_user_message_image_count(session: Session) -> int:
414
- """Get image count from the last user message in conversation history."""
407
+ def get_last_user_message_image_paths(session: Session) -> list[str]:
408
+ """Get image file paths from the last user message in conversation history."""
415
409
  for item in reversed(session.conversation_history):
416
410
  if isinstance(item, message.ToolResultMessage):
417
- return 0
411
+ return []
418
412
  if isinstance(item, message.UserMessage):
419
- return len([part for part in item.parts if isinstance(part, message.ImageURLPart)])
420
- return 0
413
+ paths: list[str] = []
414
+ for part in item.parts:
415
+ if isinstance(part, message.ImageFilePart):
416
+ paths.append(part.file_path)
417
+ return paths
418
+ return []
421
419
 
422
420
 
423
421
  async def image_reminder(session: Session) -> message.DeveloperMessage | None:
424
422
  """Remind agent about images attached by user in the last message."""
425
- image_count = get_last_user_message_image_count(session)
426
- if image_count == 0:
423
+ image_paths = get_last_user_message_image_paths(session)
424
+ if not image_paths:
427
425
  return None
428
426
 
429
427
  return message.DeveloperMessage(
430
428
  parts=[],
431
- ui_extra=model.DeveloperUIExtra(items=[model.UserImagesUIItem(count=image_count)]),
429
+ ui_extra=model.DeveloperUIExtra(items=[model.UserImagesUIItem(count=len(image_paths), paths=image_paths)]),
432
430
  )
433
431
 
434
432
 
klaude_code/core/task.py CHANGED
@@ -179,10 +179,6 @@ class TaskExecutor:
179
179
  self._started_at: float = 0.0
180
180
  self._metadata_accumulator: MetadataAccumulator | None = None
181
181
 
182
- @property
183
- def current_turn(self) -> TurnExecutor | None:
184
- return self._current_turn
185
-
186
182
  def get_partial_metadata(self) -> model.TaskMetadata | None:
187
183
  """Get the currently accumulated metadata without finalizing.
188
184
 
@@ -7,7 +7,7 @@ from .file.write_tool import WriteTool
7
7
  from .report_back_tool import ReportBackTool
8
8
  from .shell.bash_tool import BashTool
9
9
  from .shell.command_safety import SafetyCheckResult, is_safe_command
10
- from .sub_agent_tool import SubAgentTool
10
+ from .sub_agent import ImageGenTool, TaskTool
11
11
  from .todo.todo_write_tool import TodoWriteTool
12
12
  from .todo.update_plan_tool import UpdatePlanTool
13
13
  from .tool_abc import ToolABC
@@ -23,13 +23,14 @@ __all__ = [
23
23
  "DiffError",
24
24
  "EditTool",
25
25
  "FileTracker",
26
+ "ImageGenTool",
26
27
  "MermaidTool",
27
28
  "ReadTool",
28
29
  "ReportBackTool",
29
30
  "RunSubtask",
30
31
  "SafetyCheckResult",
31
32
  "SubAgentResumeClaims",
32
- "SubAgentTool",
33
+ "TaskTool",
33
34
  "TodoContext",
34
35
  "TodoWriteTool",
35
36
  "ToolABC",
@@ -26,33 +26,6 @@ class Commit(BaseModel):
26
26
  changes: dict[str, FileChange] = Field(default_factory=dict)
27
27
 
28
28
 
29
- def assemble_changes(orig: dict[str, str | None], dest: dict[str, str | None]) -> Commit:
30
- commit = Commit()
31
- for path in sorted(set(orig.keys()).union(dest.keys())):
32
- old_content = orig.get(path)
33
- new_content = dest.get(path)
34
- if old_content != new_content:
35
- if old_content is not None and new_content is not None:
36
- commit.changes[path] = FileChange(
37
- type=ActionType.UPDATE,
38
- old_content=old_content,
39
- new_content=new_content,
40
- )
41
- elif new_content:
42
- commit.changes[path] = FileChange(
43
- type=ActionType.ADD,
44
- new_content=new_content,
45
- )
46
- elif old_content:
47
- commit.changes[path] = FileChange(
48
- type=ActionType.DELETE,
49
- old_content=old_content,
50
- )
51
- else:
52
- raise AssertionError()
53
- return commit
54
-
55
-
56
29
  def _new_str_list() -> list[str]:
57
30
  # Returns a new list[str] for pydantic Field default_factory
58
31
  return []
@@ -114,10 +114,9 @@ class EditTool(ToolABC):
114
114
  file_tracker = context.file_tracker
115
115
  tracked_status: model.FileStatus | None = None
116
116
  if not file_exists(file_path):
117
- # We require reading before editing
118
117
  return message.ToolResultMessage(
119
118
  status="error",
120
- output_text=("File has not been read yet. Read it first before writing to it."),
119
+ output_text=("File does not exist. If you want to create a file, use the Write tool instead."),
121
120
  )
122
121
  tracked_status = file_tracker.get(file_path)
123
122
  if tracked_status is None:
@@ -4,10 +4,11 @@ When you need to read an image, use this tool.
4
4
 
5
5
  Usage:
6
6
  - The file_path parameter must be an absolute path, not a relative path
7
- - By default, it reads up to 2000 lines starting from the beginning of the file
7
+ - By default, it reads up to ${line_cap} lines starting from the beginning of the file
8
8
  - This tool allows you to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as you are a multimodal LLM.
9
9
  - You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
10
- - Any lines longer than 2000 characters will be truncated
10
+ - Any lines longer than ${char_limit_per_line} characters will be truncated
11
+ - Total output is capped at ${max_chars} characters
11
12
  - Results are returned using cat -n format, with line numbers starting at 1
12
13
  - This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
13
14
  - You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.
@@ -22,6 +22,7 @@ from klaude_code.core.tool.file._utils import file_exists, is_directory
22
22
  from klaude_code.core.tool.tool_abc import ToolABC, load_desc
23
23
  from klaude_code.core.tool.tool_registry import register
24
24
  from klaude_code.protocol import llm_param, message, model, tools
25
+ from klaude_code.protocol.model import ImageUIExtra
25
26
 
26
27
  _IMAGE_MIME_TYPES: dict[str, str] = {
27
28
  ".png": "image/png",
@@ -164,7 +165,14 @@ class ReadTool(ToolABC):
164
165
  return llm_param.ToolSchema(
165
166
  name=tools.READ,
166
167
  type="function",
167
- description=load_desc(Path(__file__).parent / "read_tool.md"),
168
+ description=load_desc(
169
+ Path(__file__).parent / "read_tool.md",
170
+ {
171
+ "line_cap": str(READ_GLOBAL_LINE_CAP),
172
+ "char_limit_per_line": str(READ_CHAR_LIMIT_PER_LINE),
173
+ "max_chars": str(READ_MAX_CHARS),
174
+ },
175
+ ),
168
176
  parameters={
169
177
  "type": "object",
170
178
  "properties": {
@@ -280,7 +288,12 @@ class ReadTool(ToolABC):
280
288
  size_kb = size_bytes / 1024.0 if size_bytes else 0.0
281
289
  output_text = f"[image] {Path(file_path).name} ({size_kb:.1f}KB)"
282
290
  image_part = message.ImageURLPart(url=data_url, id=None)
283
- return message.ToolResultMessage(status="success", output_text=output_text, parts=[image_part])
291
+ return message.ToolResultMessage(
292
+ status="success",
293
+ output_text=output_text,
294
+ parts=[image_part],
295
+ ui_extra=ImageUIExtra(file_path=file_path),
296
+ )
284
297
 
285
298
  offset = 1 if args.offset is None or args.offset < 1 else int(args.offset)
286
299
  limit = None if args.limit is None else int(args.limit)
@@ -68,13 +68,6 @@ class OffloadPolicy(Enum):
68
68
  ON_THRESHOLD = auto() # Offload only when exceeding size threshold
69
69
 
70
70
 
71
- class TruncationStyle(Enum):
72
- """How to truncate content that exceeds limits."""
73
-
74
- HEAD_ONLY = auto() # Keep head, discard tail (important content at top)
75
- HEAD_TAIL = auto() # Keep head and tail, discard middle (errors at end)
76
-
77
-
78
71
  @dataclass
79
72
  class OffloadResult:
80
73
  """Result of offload/truncation operation."""
@@ -94,18 +87,6 @@ class OffloadResult:
94
87
  class OffloadStrategy(ABC):
95
88
  """Base class for tool-specific offload strategies."""
96
89
 
97
- @property
98
- @abstractmethod
99
- def offload_policy(self) -> OffloadPolicy:
100
- """When to offload content to file."""
101
- ...
102
-
103
- @property
104
- @abstractmethod
105
- def truncation_style(self) -> TruncationStyle:
106
- """How to truncate content."""
107
- ...
108
-
109
90
  @abstractmethod
110
91
  def process(self, output: str, tool_call: ToolCallLike | None = None) -> OffloadResult:
111
92
  """Process tool output: truncate and optionally offload."""
@@ -126,14 +107,6 @@ class ReadToolStrategy(OffloadStrategy):
126
107
  This strategy is a pass-through since Read tool handles its own truncation.
127
108
  """
128
109
 
129
- @property
130
- def offload_policy(self) -> OffloadPolicy:
131
- return OffloadPolicy.NEVER
132
-
133
- @property
134
- def truncation_style(self) -> TruncationStyle:
135
- return TruncationStyle.HEAD_ONLY
136
-
137
110
  def process(self, output: str, tool_call: ToolCallLike | None = None) -> OffloadResult:
138
111
  return OffloadResult(output=output, was_truncated=False, original_length=len(output))
139
112
 
@@ -165,14 +138,6 @@ class HeadTailOffloadStrategy(OffloadStrategy):
165
138
  self.offload_dir = Path(offload_dir or TOOL_OUTPUT_TRUNCATION_DIR)
166
139
  self._policy = policy
167
140
 
168
- @property
169
- def offload_policy(self) -> OffloadPolicy:
170
- return self._policy
171
-
172
- @property
173
- def truncation_style(self) -> TruncationStyle:
174
- return TruncationStyle.HEAD_TAIL
175
-
176
141
  def _save_to_file(self, output: str, tool_call: ToolCallLike | None) -> str | None:
177
142
  """Save full output to file. Returns path or None on failure."""
178
143
  try:
@@ -0,0 +1,6 @@
1
+ """Sub-agent tool implementations."""
2
+
3
+ from .image_gen import ImageGenTool
4
+ from .task import TaskTool
5
+
6
+ __all__ = ["ImageGenTool", "TaskTool"]
@@ -0,0 +1,16 @@
1
+ Generate one or more images from a text prompt.
2
+
3
+ This tool invokes an Image Gen model to generate images. The generated image paths are automatically returned in the response.
4
+
5
+ Inputs:
6
+ - `prompt`: The main instruction describing the desired image.
7
+ - `image_paths` (optional): Local image file paths to use as references for editing or style guidance.
8
+ - `generation` (optional): Per-call image generation settings (aspect ratio / size).
9
+
10
+ Notes:
11
+ - Provide a short textual description of the generated image(s).
12
+ - Do NOT include base64 image data in text output.
13
+ - When providing multiple input images, describe each image's characteristics and purpose in the prompt, not just "image 1, image 2".
14
+
15
+ Multi-turn image editing:
16
+ - Use `resume` to continue editing a previously generated image. The agent preserves its full context including the generated image, so you don't need to pass `image_paths` again.
@@ -0,0 +1,146 @@
1
+ """Image generation tool implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, cast
8
+
9
+ from klaude_code.core.tool.context import ToolContext
10
+ from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy, ToolMetadata, load_desc
11
+ from klaude_code.core.tool.tool_registry import register
12
+ from klaude_code.protocol import llm_param, message, model, tools
13
+ from klaude_code.protocol.sub_agent import get_sub_agent_profile
14
+ from klaude_code.protocol.sub_agent.image_gen import build_image_gen_prompt
15
+ from klaude_code.session.session import Session
16
+
17
+ IMAGE_GEN_PARAMETERS: dict[str, Any] = {
18
+ "type": "object",
19
+ "properties": {
20
+ "resume": {
21
+ "type": "string",
22
+ "description": "Optional agent ID to resume from. If provided, the agent will continue from the previous execution transcript.",
23
+ },
24
+ "description": {
25
+ "type": "string",
26
+ "description": "A short (3-5 word) description of the request.",
27
+ },
28
+ "prompt": {
29
+ "type": "string",
30
+ "description": "Text prompt for image generation.",
31
+ },
32
+ "image_paths": {
33
+ "type": "array",
34
+ "items": {"type": "string"},
35
+ "description": "Optional local image file paths used as references.",
36
+ },
37
+ "generation": {
38
+ "type": "object",
39
+ "description": "Optional per-call image generation settings.",
40
+ "properties": {
41
+ "aspect_ratio": {
42
+ "type": "string",
43
+ "description": "Aspect ratio, e.g. '16:9', '1:1', '9:16'.",
44
+ },
45
+ "image_size": {
46
+ "type": "string",
47
+ "enum": ["1K", "2K", "4K"],
48
+ "description": "Output size for Nano Banana Pro (must use uppercase K).",
49
+ },
50
+ },
51
+ "additionalProperties": False,
52
+ },
53
+ },
54
+ "required": ["prompt"],
55
+ "additionalProperties": False,
56
+ }
57
+
58
+
59
+ @register(tools.IMAGE_GEN)
60
+ class ImageGenTool(ToolABC):
61
+ """Generate or edit images using the ImageGen sub-agent."""
62
+
63
+ @classmethod
64
+ def metadata(cls) -> ToolMetadata:
65
+ return ToolMetadata(concurrency_policy=ToolConcurrencyPolicy.CONCURRENT, has_side_effects=True)
66
+
67
+ @classmethod
68
+ def schema(cls) -> llm_param.ToolSchema:
69
+ return llm_param.ToolSchema(
70
+ name=tools.IMAGE_GEN,
71
+ type="function",
72
+ description=load_desc(Path(__file__).parent / "image_gen.md"),
73
+ parameters=IMAGE_GEN_PARAMETERS,
74
+ )
75
+
76
+ @classmethod
77
+ async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
78
+ try:
79
+ args = json.loads(arguments)
80
+ except json.JSONDecodeError as exc:
81
+ return message.ToolResultMessage(status="error", output_text=f"Invalid JSON arguments: {exc}")
82
+
83
+ if not isinstance(args, dict):
84
+ return message.ToolResultMessage(status="error", output_text="Invalid arguments: expected object")
85
+
86
+ typed_args = cast(dict[str, Any], args)
87
+
88
+ runner = context.run_subtask
89
+ if runner is None:
90
+ return message.ToolResultMessage(status="error", output_text="No subtask runner available in this context")
91
+
92
+ resume_raw = typed_args.get("resume")
93
+ resume_session_id: str | None = None
94
+ if isinstance(resume_raw, str) and resume_raw.strip():
95
+ try:
96
+ resume_session_id = Session.resolve_sub_agent_session_id(resume_raw)
97
+ except ValueError as exc:
98
+ return message.ToolResultMessage(status="error", output_text=str(exc))
99
+
100
+ claims = context.sub_agent_resume_claims
101
+ if claims is not None:
102
+ ok = await claims.claim(resume_session_id)
103
+ if not ok:
104
+ return message.ToolResultMessage(
105
+ status="error",
106
+ output_text=(
107
+ "Duplicate sub-agent resume in the same response: "
108
+ f"resume='{resume_raw.strip()}' (resolved='{resume_session_id[:7]}…'). "
109
+ "Merge into a single call or resume in a later turn."
110
+ ),
111
+ )
112
+
113
+ description = str(typed_args.get("description") or "")
114
+ prompt = build_image_gen_prompt(typed_args)
115
+ generation = typed_args.get("generation")
116
+ generation_dict: dict[str, Any] | None = (
117
+ cast(dict[str, Any], generation) if isinstance(generation, dict) else None
118
+ )
119
+
120
+ try:
121
+ profile = get_sub_agent_profile(tools.IMAGE_GEN)
122
+ except KeyError as exc:
123
+ return message.ToolResultMessage(status="error", output_text=str(exc))
124
+
125
+ try:
126
+ result = await runner(
127
+ model.SubAgentState(
128
+ sub_agent_type=profile.name,
129
+ sub_agent_desc=description,
130
+ sub_agent_prompt=prompt,
131
+ resume=resume_session_id,
132
+ output_schema=None,
133
+ generation=generation_dict,
134
+ ),
135
+ context.record_sub_agent_session_id,
136
+ context.register_sub_agent_metadata_getter,
137
+ )
138
+ except Exception as exc:
139
+ return message.ToolResultMessage(status="error", output_text=f"Failed to run subtask: {exc}")
140
+
141
+ return message.ToolResultMessage(
142
+ status="success" if not result.error else "error",
143
+ output_text=result.task_result,
144
+ ui_extra=model.SessionIdUIExtra(session_id=result.session_id),
145
+ task_metadata=result.task_metadata,
146
+ )
@@ -0,0 +1,20 @@
1
+ Launch a new agent to handle complex, multi-step tasks autonomously.
2
+
3
+ The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
4
+
5
+ When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
6
+
7
+ Available agent types and the tools they have access to:
8
+
9
+ ${types_section}
10
+
11
+ Usage notes:
12
+ - Always include a short description (3-5 words) summarizing what the agent will do
13
+ - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
14
+ - Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, etc.), since it is not aware of the user's intent
15
+ - Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
16
+ - Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. When resumed, the agent continues with its full previous context preserved. When NOT resuming, each invocation starts fresh and you should provide a detailed task description with all necessary context.
17
+ - When the agent is done, it will return a single message back to you along with its agent ID. You can use this ID to resume the agent later if needed for follow-up work.
18
+ - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
19
+ - If the user specifies that they want you to run agents "in parallel", you MUST send a single message with multiple Task tool use content blocks. For example, if you need to launch both a code-reviewer agent and a test-runner agent in parallel, send a single message with both tool calls.
20
+ - Agents can provide structured output by passing a JSON Schema in `output_schema`.