klaude-code 2.4.1__py3-none-any.whl → 2.5.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 (58) hide show
  1. klaude_code/app/runtime.py +2 -6
  2. klaude_code/cli/main.py +0 -1
  3. klaude_code/config/assets/builtin_config.yaml +7 -0
  4. klaude_code/const.py +7 -4
  5. klaude_code/core/agent.py +10 -1
  6. klaude_code/core/agent_profile.py +47 -35
  7. klaude_code/core/executor.py +6 -21
  8. klaude_code/core/manager/sub_agent_manager.py +17 -1
  9. klaude_code/core/prompts/prompt-sub-agent-web.md +4 -4
  10. klaude_code/core/task.py +65 -4
  11. klaude_code/core/tool/__init__.py +0 -5
  12. klaude_code/core/tool/context.py +12 -1
  13. klaude_code/core/tool/offload.py +311 -0
  14. klaude_code/core/tool/shell/bash_tool.md +1 -43
  15. klaude_code/core/tool/sub_agent_tool.py +1 -0
  16. klaude_code/core/tool/todo/todo_write_tool.md +0 -23
  17. klaude_code/core/tool/tool_runner.py +14 -9
  18. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  19. klaude_code/core/tool/web/web_fetch_tool.py +14 -39
  20. klaude_code/core/turn.py +128 -138
  21. klaude_code/llm/anthropic/client.py +176 -82
  22. klaude_code/llm/bedrock/client.py +8 -12
  23. klaude_code/llm/claude/client.py +11 -15
  24. klaude_code/llm/client.py +31 -4
  25. klaude_code/llm/codex/client.py +7 -11
  26. klaude_code/llm/google/client.py +150 -69
  27. klaude_code/llm/openai_compatible/client.py +10 -15
  28. klaude_code/llm/openai_compatible/stream.py +68 -6
  29. klaude_code/llm/openrouter/client.py +9 -15
  30. klaude_code/llm/partial_message.py +35 -0
  31. klaude_code/llm/responses/client.py +134 -68
  32. klaude_code/llm/usage.py +30 -0
  33. klaude_code/protocol/commands.py +0 -4
  34. klaude_code/protocol/events/metadata.py +1 -0
  35. klaude_code/protocol/events/streaming.py +1 -0
  36. klaude_code/protocol/events/system.py +0 -4
  37. klaude_code/protocol/model.py +2 -15
  38. klaude_code/protocol/sub_agent/explore.py +0 -10
  39. klaude_code/protocol/sub_agent/image_gen.py +0 -7
  40. klaude_code/protocol/sub_agent/task.py +0 -10
  41. klaude_code/protocol/sub_agent/web.py +4 -12
  42. klaude_code/session/templates/export_session.html +4 -4
  43. klaude_code/skill/manager.py +2 -1
  44. klaude_code/tui/components/metadata.py +41 -49
  45. klaude_code/tui/components/rich/markdown.py +1 -3
  46. klaude_code/tui/components/rich/theme.py +2 -2
  47. klaude_code/tui/components/sub_agent.py +9 -1
  48. klaude_code/tui/components/tools.py +0 -31
  49. klaude_code/tui/components/welcome.py +1 -32
  50. klaude_code/tui/input/prompt_toolkit.py +25 -9
  51. klaude_code/tui/machine.py +40 -8
  52. klaude_code/tui/renderer.py +1 -0
  53. {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/METADATA +2 -2
  54. {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/RECORD +56 -56
  55. klaude_code/core/prompts/prompt-nano-banana.md +0 -1
  56. klaude_code/core/tool/truncation.py +0 -203
  57. {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/WHEEL +0 -0
  58. {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/entry_points.txt +0 -0
@@ -9,11 +9,12 @@ from openai.types import responses
9
9
  from openai.types.responses.response_create_params import ResponseCreateParamsStreaming
10
10
 
11
11
  from klaude_code.const import LLM_HTTP_TIMEOUT_CONNECT, LLM_HTTP_TIMEOUT_READ, LLM_HTTP_TIMEOUT_TOTAL
12
- from klaude_code.llm.client import LLMClientABC
12
+ from klaude_code.llm.client import LLMClientABC, LLMStreamABC
13
13
  from klaude_code.llm.input_common import apply_config_defaults
14
+ from klaude_code.llm.partial_message import degrade_thinking_to_text
14
15
  from klaude_code.llm.registry import register
15
16
  from klaude_code.llm.responses.input import convert_history_to_input, convert_tool_schema
16
- from klaude_code.llm.usage import MetadataTracker, error_stream_items
17
+ from klaude_code.llm.usage import MetadataTracker, error_llm_stream
17
18
  from klaude_code.log import DebugType, log_debug
18
19
  from klaude_code.protocol import llm_param, message, model
19
20
 
@@ -56,46 +57,79 @@ def build_payload(param: llm_param.LLMCallParameter) -> ResponseCreateParamsStre
56
57
  return payload
57
58
 
58
59
 
59
- async def parse_responses_stream(
60
- stream: "AsyncStream[ResponseStreamEvent]",
61
- param: llm_param.LLMCallParameter,
62
- metadata_tracker: MetadataTracker,
63
- ) -> AsyncGenerator[message.LLMStreamItem]:
64
- """Parse OpenAI Responses API stream events into stream items."""
65
- response_id: str | None = None
66
- stage: Literal["waiting", "thinking", "assistant", "tool"] = "waiting"
67
-
68
- accumulated_thinking: list[str] = []
69
- accumulated_text: list[str] = []
70
- pending_signature: str | None = None
71
- assistant_parts: list[message.Part] = []
72
- stop_reason: model.StopReason | None = None
73
-
74
- def flush_thinking() -> None:
75
- nonlocal pending_signature
76
- if accumulated_thinking:
77
- assistant_parts.append(
60
+ class ResponsesStreamStateManager:
61
+ """Manages streaming state for Responses API and provides partial message access."""
62
+
63
+ def __init__(self, model_id: str) -> None:
64
+ self.model_id = model_id
65
+ self.response_id: str | None = None
66
+ self.stage: Literal["waiting", "thinking", "assistant", "tool"] = "waiting"
67
+ self.accumulated_thinking: list[str] = []
68
+ self.accumulated_text: list[str] = []
69
+ self.pending_signature: str | None = None
70
+ self.assistant_parts: list[message.Part] = []
71
+ self.stop_reason: model.StopReason | None = None
72
+
73
+ def flush_thinking(self) -> None:
74
+ """Flush accumulated thinking content into parts."""
75
+ if self.accumulated_thinking:
76
+ self.assistant_parts.append(
78
77
  message.ThinkingTextPart(
79
- text="".join(accumulated_thinking),
80
- model_id=str(param.model_id),
78
+ text="".join(self.accumulated_thinking),
79
+ model_id=self.model_id,
81
80
  )
82
81
  )
83
- accumulated_thinking.clear()
84
- if pending_signature:
85
- assistant_parts.append(
82
+ self.accumulated_thinking.clear()
83
+ if self.pending_signature:
84
+ self.assistant_parts.append(
86
85
  message.ThinkingSignaturePart(
87
- signature=pending_signature,
88
- model_id=str(param.model_id),
86
+ signature=self.pending_signature,
87
+ model_id=self.model_id,
89
88
  format="openai_reasoning",
90
89
  )
91
90
  )
92
- pending_signature = None
91
+ self.pending_signature = None
93
92
 
94
- def flush_text() -> None:
95
- if not accumulated_text:
93
+ def flush_text(self) -> None:
94
+ """Flush accumulated text content into parts."""
95
+ if not self.accumulated_text:
96
96
  return
97
- assistant_parts.append(message.TextPart(text="".join(accumulated_text)))
98
- accumulated_text.clear()
97
+ self.assistant_parts.append(message.TextPart(text="".join(self.accumulated_text)))
98
+ self.accumulated_text.clear()
99
+
100
+ def flush_all(self) -> list[message.Part]:
101
+ """Flush all accumulated content and return parts."""
102
+ self.flush_thinking()
103
+ self.flush_text()
104
+ return list(self.assistant_parts)
105
+
106
+ def get_partial_message(self) -> message.AssistantMessage | None:
107
+ """Build a partial AssistantMessage from accumulated state."""
108
+ parts = self.flush_all()
109
+ filtered_parts: list[message.Part] = []
110
+ for part in parts:
111
+ if isinstance(part, message.ToolCallPart):
112
+ continue
113
+ filtered_parts.append(part)
114
+
115
+ filtered_parts = degrade_thinking_to_text(filtered_parts)
116
+ if not filtered_parts:
117
+ return None
118
+ return message.AssistantMessage(
119
+ parts=filtered_parts,
120
+ response_id=self.response_id,
121
+ stop_reason="aborted",
122
+ )
123
+
124
+
125
+ async def parse_responses_stream(
126
+ stream: "AsyncStream[ResponseStreamEvent]",
127
+ *,
128
+ state: ResponsesStreamStateManager,
129
+ param: llm_param.LLMCallParameter,
130
+ metadata_tracker: MetadataTracker,
131
+ ) -> AsyncGenerator[message.LLMStreamItem]:
132
+ """Parse OpenAI Responses API stream events into stream items."""
99
133
 
100
134
  def map_stop_reason(status: str | None, reason: str | None) -> model.StopReason | None:
101
135
  if reason:
@@ -122,31 +156,31 @@ async def parse_responses_stream(
122
156
  )
123
157
  match event:
124
158
  case responses.ResponseCreatedEvent() as event:
125
- response_id = event.response.id
159
+ state.response_id = event.response.id
126
160
  case responses.ResponseReasoningSummaryTextDeltaEvent() as event:
127
161
  if event.delta:
128
162
  metadata_tracker.record_token()
129
- if stage == "assistant":
130
- flush_text()
131
- stage = "thinking"
132
- accumulated_thinking.append(event.delta)
133
- yield message.ThinkingTextDelta(content=event.delta, response_id=response_id)
163
+ if state.stage == "assistant":
164
+ state.flush_text()
165
+ state.stage = "thinking"
166
+ state.accumulated_thinking.append(event.delta)
167
+ yield message.ThinkingTextDelta(content=event.delta, response_id=state.response_id)
134
168
  case responses.ResponseReasoningSummaryTextDoneEvent() as event:
135
- if event.text and not accumulated_thinking:
136
- accumulated_thinking.append(event.text)
169
+ if event.text and not state.accumulated_thinking:
170
+ state.accumulated_thinking.append(event.text)
137
171
  case responses.ResponseTextDeltaEvent() as event:
138
172
  if event.delta:
139
173
  metadata_tracker.record_token()
140
- if stage == "thinking":
141
- flush_thinking()
142
- stage = "assistant"
143
- accumulated_text.append(event.delta)
144
- yield message.AssistantTextDelta(content=event.delta, response_id=response_id)
174
+ if state.stage == "thinking":
175
+ state.flush_thinking()
176
+ state.stage = "assistant"
177
+ state.accumulated_text.append(event.delta)
178
+ yield message.AssistantTextDelta(content=event.delta, response_id=state.response_id)
145
179
  case responses.ResponseOutputItemAddedEvent() as event:
146
180
  if isinstance(event.item, responses.ResponseFunctionToolCall):
147
181
  metadata_tracker.record_token()
148
182
  yield message.ToolCallStartDelta(
149
- response_id=response_id,
183
+ response_id=state.response_id,
150
184
  call_id=event.item.call_id,
151
185
  name=event.item.name,
152
186
  )
@@ -154,9 +188,9 @@ async def parse_responses_stream(
154
188
  match event.item:
155
189
  case responses.ResponseReasoningItem() as item:
156
190
  if item.encrypted_content:
157
- pending_signature = item.encrypted_content
191
+ state.pending_signature = item.encrypted_content
158
192
  case responses.ResponseOutputMessage() as item:
159
- if not accumulated_text:
193
+ if not state.accumulated_text:
160
194
  text_content = "\n".join(
161
195
  [
162
196
  part.text
@@ -165,13 +199,13 @@ async def parse_responses_stream(
165
199
  ]
166
200
  )
167
201
  if text_content:
168
- accumulated_text.append(text_content)
202
+ state.accumulated_text.append(text_content)
169
203
  case responses.ResponseFunctionToolCall() as item:
170
204
  metadata_tracker.record_token()
171
- flush_thinking()
172
- flush_text()
173
- stage = "tool"
174
- assistant_parts.append(
205
+ state.flush_thinking()
206
+ state.flush_text()
207
+ state.stage = "tool"
208
+ state.assistant_parts.append(
175
209
  message.ToolCallPart(
176
210
  call_id=item.call_id,
177
211
  id=item.id,
@@ -198,8 +232,8 @@ async def parse_responses_stream(
198
232
  )
199
233
  )
200
234
  metadata_tracker.set_model_name(str(param.model_id))
201
- metadata_tracker.set_response_id(response_id)
202
- stop_reason = map_stop_reason(event.response.status, error_reason)
235
+ metadata_tracker.set_response_id(state.response_id)
236
+ state.stop_reason = map_stop_reason(event.response.status, error_reason)
203
237
  if event.response.status != "completed":
204
238
  error_message = f"LLM response finished with status '{event.response.status}'"
205
239
  if error_reason:
@@ -221,18 +255,53 @@ async def parse_responses_stream(
221
255
  except (openai.OpenAIError, httpx.HTTPError) as e:
222
256
  yield message.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
223
257
 
224
- flush_thinking()
225
- flush_text()
226
- metadata_tracker.set_response_id(response_id)
258
+ parts = state.flush_all()
259
+ metadata_tracker.set_response_id(state.response_id)
227
260
  metadata = metadata_tracker.finalize()
228
261
  yield message.AssistantMessage(
229
- parts=assistant_parts,
230
- response_id=response_id,
262
+ parts=parts,
263
+ response_id=state.response_id,
231
264
  usage=metadata,
232
- stop_reason=stop_reason,
265
+ stop_reason=state.stop_reason,
233
266
  )
234
267
 
235
268
 
269
+ class ResponsesLLMStream(LLMStreamABC):
270
+ """LLMStream implementation for Responses API clients."""
271
+
272
+ def __init__(
273
+ self,
274
+ stream: "AsyncStream[ResponseStreamEvent]",
275
+ *,
276
+ param: llm_param.LLMCallParameter,
277
+ metadata_tracker: MetadataTracker,
278
+ ) -> None:
279
+ self._stream = stream
280
+ self._param = param
281
+ self._metadata_tracker = metadata_tracker
282
+ self._state = ResponsesStreamStateManager(str(param.model_id))
283
+ self._completed = False
284
+
285
+ def __aiter__(self) -> AsyncGenerator[message.LLMStreamItem]:
286
+ return self._iterate()
287
+
288
+ async def _iterate(self) -> AsyncGenerator[message.LLMStreamItem]:
289
+ async for item in parse_responses_stream(
290
+ self._stream,
291
+ state=self._state,
292
+ param=self._param,
293
+ metadata_tracker=self._metadata_tracker,
294
+ ):
295
+ if isinstance(item, message.AssistantMessage):
296
+ self._completed = True
297
+ yield item
298
+
299
+ def get_partial_message(self) -> message.AssistantMessage | None:
300
+ if self._completed:
301
+ return None
302
+ return self._state.get_partial_message()
303
+
304
+
236
305
  @register(llm_param.LLMClientProtocol.RESPONSES)
237
306
  class ResponsesClient(LLMClientABC):
238
307
  def __init__(self, config: llm_param.LLMConfigParameter):
@@ -264,7 +333,7 @@ class ResponsesClient(LLMClientABC):
264
333
  return cls(config)
265
334
 
266
335
  @override
267
- async def call(self, param: llm_param.LLMCallParameter) -> AsyncGenerator[message.LLMStreamItem]:
336
+ async def call(self, param: llm_param.LLMCallParameter) -> LLMStreamABC:
268
337
  param = apply_config_defaults(param, self.get_llm_config())
269
338
 
270
339
  metadata_tracker = MetadataTracker(cost_config=self.get_llm_config().cost)
@@ -283,9 +352,6 @@ class ResponsesClient(LLMClientABC):
283
352
  )
284
353
  except (openai.OpenAIError, httpx.HTTPError) as e:
285
354
  error_message = f"{e.__class__.__name__} {e!s}"
286
- for item in error_stream_items(metadata_tracker, error=error_message):
287
- yield item
288
- return
355
+ return error_llm_stream(metadata_tracker, error=error_message)
289
356
 
290
- async for item in parse_responses_stream(stream, param, metadata_tracker):
291
- yield item
357
+ return ResponsesLLMStream(stream, param=param, metadata_tracker=metadata_tracker)
klaude_code/llm/usage.py CHANGED
@@ -1,8 +1,10 @@
1
1
  import time
2
+ from collections.abc import AsyncGenerator
2
3
 
3
4
  import openai.types
4
5
 
5
6
  from klaude_code.const import THROUGHPUT_MIN_DURATION_SEC
7
+ from klaude_code.llm.client import LLMStreamABC
6
8
  from klaude_code.protocol import llm_param, message, model
7
9
 
8
10
 
@@ -114,6 +116,34 @@ def error_stream_items(
114
116
  ]
115
117
 
116
118
 
119
+ class ErrorLLMStream(LLMStreamABC):
120
+ """LLMStream implementation for error scenarios."""
121
+
122
+ def __init__(self, items: list[message.LLMStreamItem]) -> None:
123
+ self._items = list(items)
124
+
125
+ def __aiter__(self) -> AsyncGenerator[message.LLMStreamItem]:
126
+ return self._iterate()
127
+
128
+ async def _iterate(self) -> AsyncGenerator[message.LLMStreamItem]:
129
+ for item in self._items:
130
+ yield item
131
+
132
+ def get_partial_message(self) -> message.AssistantMessage | None:
133
+ return None
134
+
135
+
136
+ def error_llm_stream(
137
+ metadata_tracker: MetadataTracker,
138
+ *,
139
+ error: str,
140
+ response_id: str | None = None,
141
+ ) -> ErrorLLMStream:
142
+ """Create an LLMStream that yields error items."""
143
+ items = error_stream_items(metadata_tracker, error=error, response_id=response_id)
144
+ return ErrorLLMStream(items)
145
+
146
+
117
147
  def convert_usage(
118
148
  usage: openai.types.CompletionUsage,
119
149
  context_limit: int | None = None,
@@ -28,10 +28,6 @@ class CommandName(str, Enum):
28
28
  FORK_SESSION = "fork-session"
29
29
  RESUME = "resume"
30
30
  COPY = "copy"
31
- # PLAN and DOC are dynamically registered now, but kept here if needed for reference
32
- # or we can remove them if no code explicitly imports them.
33
- # PLAN = "plan"
34
- # DOC = "doc"
35
31
 
36
32
  def __str__(self) -> str:
37
33
  return self.value
@@ -13,3 +13,4 @@ class UsageEvent(ResponseEvent):
13
13
 
14
14
  class TaskMetadataEvent(Event):
15
15
  metadata: model.TaskMetadataItem
16
+ cancelled: bool = False
@@ -34,6 +34,7 @@ class AssistantImageDeltaEvent(ResponseEvent):
34
34
  class ToolCallStartEvent(ResponseEvent):
35
35
  tool_call_id: str
36
36
  tool_name: str
37
+ model_id: str | None = None
37
38
 
38
39
 
39
40
  class ResponseCompleteEvent(ResponseEvent):
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pydantic import Field
4
-
5
3
  from klaude_code.protocol import llm_param
6
4
  from klaude_code.protocol.events.chat import DeveloperMessageEvent, UserMessageEvent
7
5
  from klaude_code.protocol.events.lifecycle import TaskFinishEvent, TaskStartEvent, TurnStartEvent
@@ -16,8 +14,6 @@ class WelcomeEvent(Event):
16
14
  work_dir: str
17
15
  llm_config: llm_param.LLMConfigParameter
18
16
  show_klaude_code_info: bool = True
19
- show_sub_agent_models: bool = True
20
- sub_agent_models: dict[str, llm_param.LLMConfigParameter] = Field(default_factory=dict)
21
17
 
22
18
 
23
19
  class ErrorEvent(Event):
@@ -75,6 +75,7 @@ class TaskMetadata(BaseModel):
75
75
  usage: Usage | None = None
76
76
  model_name: str = ""
77
77
  provider: str | None = None
78
+ description: str | None = None
78
79
  task_duration_s: float | None = None
79
80
  turn_count: int = 0
80
81
 
@@ -216,13 +217,6 @@ class MermaidLinkUIExtra(BaseModel):
216
217
  line_count: int
217
218
 
218
219
 
219
- class TruncationUIExtra(BaseModel):
220
- type: Literal["truncation"] = "truncation"
221
- saved_file_path: str
222
- original_length: int
223
- truncated_length: int
224
-
225
-
226
220
  class MarkdownDocUIExtra(BaseModel):
227
221
  type: Literal["markdown_doc"] = "markdown_doc"
228
222
  file_path: str
@@ -237,13 +231,7 @@ class SessionStatusUIExtra(BaseModel):
237
231
 
238
232
 
239
233
  MultiUIExtraItem = (
240
- DiffUIExtra
241
- | TodoListUIExtra
242
- | SessionIdUIExtra
243
- | MermaidLinkUIExtra
244
- | TruncationUIExtra
245
- | MarkdownDocUIExtra
246
- | SessionStatusUIExtra
234
+ DiffUIExtra | TodoListUIExtra | SessionIdUIExtra | MermaidLinkUIExtra | MarkdownDocUIExtra | SessionStatusUIExtra
247
235
  )
248
236
 
249
237
 
@@ -263,7 +251,6 @@ ToolResultUIExtra = Annotated[
263
251
  | TodoListUIExtra
264
252
  | SessionIdUIExtra
265
253
  | MermaidLinkUIExtra
266
- | TruncationUIExtra
267
254
  | MarkdownDocUIExtra
268
255
  | SessionStatusUIExtra
269
256
  | MultiUIExtra,
@@ -8,16 +8,6 @@ Spin up a fast agent specialized for exploring codebases. Use this when you need
8
8
  search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?")\
9
9
  When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.
10
10
  Always spawn multiple search agents in parallel to maximise speed.
11
-
12
- Structured output:
13
- - Provide an `output_format` (JSON Schema) parameter for structured data back from the sub-agent
14
- - Example: `output_format={"type": "object", "properties": {"files": {"type": "array", "items": {"type": "string"}, "description": "List of file paths that match the search criteria, e.g. ['src/main.py', 'src/utils/helper.py']"}}, "required": ["files"]}`\
15
-
16
- - Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. When resumed, the agent
17
- continues with its full previous context preserved. When NOT resuming, each invocation starts fresh and you should provide a detailed
18
- task description with all necessary context.
19
- - 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
20
- later if needed for follow-up work.
21
11
  """
22
12
 
23
13
  EXPLORE_PARAMETERS = {
@@ -30,13 +30,6 @@ including the generated image, so you don't need to pass `image_paths` again.
30
30
  1. Call ImageGen with prompt="Generate a watercolor painting of a mountain lake" -> returns agent_id
31
31
  2. Call ImageGen with resume=agent_id, prompt="Add a wooden cabin on the shore" -> edits the previous image
32
32
  3. Call ImageGen with resume=agent_id, prompt="Change to sunset lighting" -> continues editing
33
-
34
- - Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. When resumed, the agent
35
- continues with its full previous context preserved. When NOT resuming, each invocation starts fresh and you should provide a detailed
36
- task description with all necessary context.
37
- - 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
38
- later if needed for follow-up work.
39
-
40
33
  """
41
34
 
42
35
 
@@ -20,16 +20,6 @@ Usage notes:
20
20
  - 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
21
21
  - 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.
22
22
  - 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.
23
-
24
- Structured output:
25
- - Provide an `output_format` (JSON Schema) parameter for structured data back from the agent
26
- - Example: `output_format={"type": "object", "properties": {"files": {"type": "array", "items": {"type": "string"}, "description": "List of file paths that match the search criteria, e.g. ['src/main.py', 'src/utils/helper.py']"}}, "required": ["files"]}`\
27
-
28
- - Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. When resumed, the agent
29
- continues with its full previous context preserved. When NOT resuming, each invocation starts fresh and you should provide a detailed
30
- task description with all necessary context.
31
- - 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
32
- later if needed for follow-up work.
33
23
  """
34
24
 
35
25
  TASK_PARAMETERS = {
@@ -11,28 +11,20 @@ Launch a sub-agent to search the web, fetch pages, and analyze content. Use this
11
11
  - Gathering comprehensive information from multiple web sources
12
12
 
13
13
  Capabilities:
14
- - Search the web to find relevant pages (no URL required)
15
- - Fetch and parse web pages (HTML-to-Markdown conversion)
16
- - Follow links across multiple pages autonomously
14
+ - Search the web to find relevant pages
15
+ - Fetch and parse web pages
16
+ - Follow links across multiple pages
17
17
  - Aggregate findings from multiple sources
18
18
 
19
19
  How to use:
20
20
  - Write a clear prompt describing what information you need - the agent will search and fetch as needed
21
21
  - Account for "Today's date" in <env>. For example, if <env> says "Today's date: 2025-07-01", and the user wants the latest docs, do not use 2024 in the search query. Use 2025.
22
22
  - Provide the url if you already know the target page
23
- - Use `output_format` (JSON Schema) to get structured data back from the agent
24
23
 
25
24
  What you receive:
26
25
  - The agent returns a text response summarizing its findings
27
- - With `output_format`, you receive structured JSON matching your schema
28
26
  - The response is the agent's analysis, not raw web content
29
- - Web content is saved to local files (paths included in Sources) - read them directly if you need full content\
30
-
31
- - Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. When resumed, the agent
32
- continues with its full previous context preserved. When NOT resuming, each invocation starts fresh and you should provide a detailed
33
- task description with all necessary context.
34
- - 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
35
- later if needed for follow-up work.
27
+ - Web content is saved to local files (paths included in Sources) - read them directly if you need full content
36
28
  """
37
29
 
38
30
  WEB_AGENT_PARAMETERS = {
@@ -9,11 +9,11 @@
9
9
  href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%230b5bd3%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><polyline points=%2216 18 22 12 16 6%22></polyline><polyline points=%228 6 2 12 8 18%22></polyline></svg>"
10
10
  />
11
11
  <link
12
- href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/400.css"
12
+ href="https://cdn.jsdelivr.net/npm/@fontsource/lilex/400.css"
13
13
  rel="stylesheet"
14
14
  />
15
15
  <link
16
- href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/700.css"
16
+ href="https://cdn.jsdelivr.net/npm/@fontsource/lilex/700.css"
17
17
  rel="stylesheet"
18
18
  />
19
19
  <link
@@ -57,8 +57,8 @@
57
57
  --error: #c62828;
58
58
  --fg-inline-code: #1f4fbf;
59
59
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
60
- --font-mono: "TX-02", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
61
- --font-markdown-mono: "TX-02", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
60
+ --font-mono: "Lilex", "TX-02", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
61
+ --font-markdown-mono: "Lilex", "TX-02", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
62
62
  --font-markdown: var(--font-sans);
63
63
  --font-weight-bold: 700;
64
64
  --font-size-xs: 12px;
@@ -84,7 +84,8 @@ def format_available_skills_for_system_prompt() -> str:
84
84
  if not skills_xml:
85
85
  return ""
86
86
 
87
- return f"""\
87
+ return f"""
88
+
88
89
  # Skills
89
90
 
90
91
  Skills are optional task-specific instructions stored as `SKILL.md` files.