shotgun-sh 0.2.3.dev2__py3-none-any.whl → 0.2.11.dev5__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.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +664 -75
- shotgun/agents/common.py +76 -70
- shotgun/agents/config/constants.py +0 -6
- shotgun/agents/config/manager.py +78 -36
- shotgun/agents/config/models.py +41 -1
- shotgun/agents/config/provider.py +70 -15
- shotgun/agents/context_analyzer/__init__.py +28 -0
- shotgun/agents/context_analyzer/analyzer.py +471 -0
- shotgun/agents/context_analyzer/constants.py +9 -0
- shotgun/agents/context_analyzer/formatter.py +115 -0
- shotgun/agents/context_analyzer/models.py +212 -0
- shotgun/agents/conversation_history.py +125 -2
- shotgun/agents/conversation_manager.py +57 -19
- shotgun/agents/export.py +6 -7
- shotgun/agents/history/compaction.py +9 -4
- shotgun/agents/history/context_extraction.py +93 -6
- shotgun/agents/history/history_processors.py +14 -2
- shotgun/agents/history/token_counting/anthropic.py +49 -11
- shotgun/agents/history/token_counting/base.py +14 -3
- shotgun/agents/history/token_counting/openai.py +8 -0
- shotgun/agents/history/token_counting/sentencepiece_counter.py +8 -0
- shotgun/agents/history/token_counting/tokenizer_cache.py +3 -1
- shotgun/agents/history/token_counting/utils.py +0 -3
- shotgun/agents/models.py +50 -2
- shotgun/agents/plan.py +6 -7
- shotgun/agents/research.py +7 -8
- shotgun/agents/specify.py +6 -7
- shotgun/agents/tasks.py +6 -7
- shotgun/agents/tools/__init__.py +0 -2
- shotgun/agents/tools/codebase/codebase_shell.py +6 -0
- shotgun/agents/tools/codebase/directory_lister.py +6 -0
- shotgun/agents/tools/codebase/file_read.py +11 -2
- shotgun/agents/tools/codebase/query_graph.py +6 -0
- shotgun/agents/tools/codebase/retrieve_code.py +6 -0
- shotgun/agents/tools/file_management.py +82 -16
- shotgun/agents/tools/registry.py +217 -0
- shotgun/agents/tools/web_search/__init__.py +30 -18
- shotgun/agents/tools/web_search/anthropic.py +26 -5
- shotgun/agents/tools/web_search/gemini.py +23 -11
- shotgun/agents/tools/web_search/openai.py +22 -13
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +16 -11
- shotgun/api_endpoints.py +7 -3
- shotgun/build_constants.py +1 -1
- shotgun/cli/clear.py +53 -0
- shotgun/cli/compact.py +186 -0
- shotgun/cli/config.py +8 -5
- shotgun/cli/context.py +111 -0
- shotgun/cli/export.py +1 -1
- shotgun/cli/feedback.py +4 -2
- shotgun/cli/models.py +1 -0
- shotgun/cli/plan.py +1 -1
- shotgun/cli/research.py +1 -1
- shotgun/cli/specify.py +1 -1
- shotgun/cli/tasks.py +1 -1
- shotgun/cli/update.py +16 -2
- shotgun/codebase/core/change_detector.py +5 -3
- shotgun/codebase/core/code_retrieval.py +4 -2
- shotgun/codebase/core/ingestor.py +10 -8
- shotgun/codebase/core/manager.py +13 -4
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/llm_proxy/__init__.py +5 -2
- shotgun/llm_proxy/clients.py +12 -7
- shotgun/logging_config.py +18 -27
- shotgun/main.py +73 -11
- shotgun/posthog_telemetry.py +23 -7
- shotgun/prompts/agents/export.j2 +18 -1
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +5 -1
- shotgun/prompts/agents/partials/interactive_mode.j2 +24 -7
- shotgun/prompts/agents/plan.j2 +1 -1
- shotgun/prompts/agents/research.j2 +1 -1
- shotgun/prompts/agents/specify.j2 +270 -3
- shotgun/prompts/agents/state/system_state.j2 +4 -0
- shotgun/prompts/agents/tasks.j2 +1 -1
- shotgun/prompts/loader.py +2 -2
- shotgun/prompts/tools/web_search.j2 +14 -0
- shotgun/sentry_telemetry.py +7 -16
- shotgun/settings.py +238 -0
- shotgun/telemetry.py +18 -33
- shotgun/tui/app.py +243 -43
- shotgun/tui/commands/__init__.py +1 -1
- shotgun/tui/components/context_indicator.py +179 -0
- shotgun/tui/components/mode_indicator.py +70 -0
- shotgun/tui/components/status_bar.py +48 -0
- shotgun/tui/containers.py +91 -0
- shotgun/tui/dependencies.py +39 -0
- shotgun/tui/protocols.py +45 -0
- shotgun/tui/screens/chat/__init__.py +5 -0
- shotgun/tui/screens/chat/chat.tcss +54 -0
- shotgun/tui/screens/chat/chat_screen.py +1202 -0
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +64 -0
- shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
- shotgun/tui/screens/chat/help_text.py +40 -0
- shotgun/tui/screens/chat/prompt_history.py +48 -0
- shotgun/tui/screens/chat.tcss +11 -0
- shotgun/tui/screens/chat_screen/command_providers.py +78 -2
- shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
- shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
- shotgun/tui/screens/chat_screen/history/chat_history.py +116 -0
- shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
- shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
- shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
- shotgun/tui/screens/confirmation_dialog.py +151 -0
- shotgun/tui/screens/feedback.py +4 -4
- shotgun/tui/screens/github_issue.py +102 -0
- shotgun/tui/screens/model_picker.py +49 -24
- shotgun/tui/screens/onboarding.py +431 -0
- shotgun/tui/screens/pipx_migration.py +153 -0
- shotgun/tui/screens/provider_config.py +50 -27
- shotgun/tui/screens/shotgun_auth.py +2 -2
- shotgun/tui/screens/welcome.py +32 -10
- shotgun/tui/services/__init__.py +5 -0
- shotgun/tui/services/conversation_service.py +184 -0
- shotgun/tui/state/__init__.py +7 -0
- shotgun/tui/state/processing_state.py +185 -0
- shotgun/tui/utils/mode_progress.py +14 -7
- shotgun/tui/widgets/__init__.py +5 -0
- shotgun/tui/widgets/widget_coordinator.py +262 -0
- shotgun/utils/datetime_utils.py +77 -0
- shotgun/utils/file_system_utils.py +22 -2
- shotgun/utils/marketing.py +110 -0
- shotgun/utils/update_checker.py +69 -14
- shotgun_sh-0.2.11.dev5.dist-info/METADATA +130 -0
- shotgun_sh-0.2.11.dev5.dist-info/RECORD +193 -0
- {shotgun_sh-0.2.3.dev2.dist-info → shotgun_sh-0.2.11.dev5.dist-info}/entry_points.txt +1 -0
- {shotgun_sh-0.2.3.dev2.dist-info → shotgun_sh-0.2.11.dev5.dist-info}/licenses/LICENSE +1 -1
- shotgun/agents/tools/user_interaction.py +0 -37
- shotgun/tui/screens/chat.py +0 -804
- shotgun/tui/screens/chat_screen/history.py +0 -352
- shotgun_sh-0.2.3.dev2.dist-info/METADATA +0 -467
- shotgun_sh-0.2.3.dev2.dist-info/RECORD +0 -154
- {shotgun_sh-0.2.3.dev2.dist-info → shotgun_sh-0.2.11.dev5.dist-info}/WHEEL +0 -0
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from collections.abc import Generator, Sequence
|
|
3
|
-
|
|
4
|
-
from pydantic_ai.messages import (
|
|
5
|
-
BuiltinToolCallPart,
|
|
6
|
-
BuiltinToolReturnPart,
|
|
7
|
-
ModelMessage,
|
|
8
|
-
ModelRequest,
|
|
9
|
-
ModelRequestPart,
|
|
10
|
-
ModelResponse,
|
|
11
|
-
TextPart,
|
|
12
|
-
ThinkingPart,
|
|
13
|
-
ToolCallPart,
|
|
14
|
-
ToolReturnPart,
|
|
15
|
-
UserPromptPart,
|
|
16
|
-
)
|
|
17
|
-
from textual.app import ComposeResult
|
|
18
|
-
from textual.reactive import reactive
|
|
19
|
-
from textual.widget import Widget
|
|
20
|
-
from textual.widgets import Markdown
|
|
21
|
-
|
|
22
|
-
from shotgun.agents.models import UserAnswer
|
|
23
|
-
from shotgun.tui.components.vertical_tail import VerticalTail
|
|
24
|
-
from shotgun.tui.screens.chat_screen.hint_message import HintMessage, HintMessageWidget
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class PartialResponseWidget(Widget): # TODO: doesn't work lol
|
|
28
|
-
DEFAULT_CSS = """
|
|
29
|
-
PartialResponseWidget {
|
|
30
|
-
height: auto;
|
|
31
|
-
}
|
|
32
|
-
Markdown, AgentResponseWidget, UserQuestionWidget {
|
|
33
|
-
height: auto;
|
|
34
|
-
}
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
item: reactive[ModelMessage | None] = reactive(None, recompose=True)
|
|
38
|
-
|
|
39
|
-
def __init__(self, item: ModelMessage | None) -> None:
|
|
40
|
-
super().__init__()
|
|
41
|
-
self.item = item
|
|
42
|
-
|
|
43
|
-
def compose(self) -> ComposeResult:
|
|
44
|
-
if self.item is None:
|
|
45
|
-
pass
|
|
46
|
-
elif self.item.kind == "response":
|
|
47
|
-
yield AgentResponseWidget(self.item)
|
|
48
|
-
elif self.item.kind == "request":
|
|
49
|
-
yield UserQuestionWidget(self.item)
|
|
50
|
-
|
|
51
|
-
def watch_item(self, item: ModelMessage | None) -> None:
|
|
52
|
-
if item is None:
|
|
53
|
-
self.display = False
|
|
54
|
-
else:
|
|
55
|
-
self.display = True
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ChatHistory(Widget):
|
|
59
|
-
DEFAULT_CSS = """
|
|
60
|
-
VerticalTail {
|
|
61
|
-
align: left bottom;
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
VerticalTail > * {
|
|
65
|
-
height: auto;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
Horizontal {
|
|
69
|
-
height: auto;
|
|
70
|
-
background: $secondary-muted;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
Markdown {
|
|
74
|
-
height: auto;
|
|
75
|
-
}
|
|
76
|
-
"""
|
|
77
|
-
partial_response: reactive[ModelMessage | None] = reactive(None)
|
|
78
|
-
|
|
79
|
-
def __init__(self) -> None:
|
|
80
|
-
super().__init__()
|
|
81
|
-
self.items: Sequence[ModelMessage | HintMessage] = []
|
|
82
|
-
self.vertical_tail: VerticalTail | None = None
|
|
83
|
-
self.partial_response = None
|
|
84
|
-
self._rendered_count = 0 # Track how many messages have been mounted
|
|
85
|
-
|
|
86
|
-
def compose(self) -> ComposeResult:
|
|
87
|
-
self.vertical_tail = VerticalTail()
|
|
88
|
-
|
|
89
|
-
filtered = list(self.filtered_items())
|
|
90
|
-
with self.vertical_tail:
|
|
91
|
-
for item in filtered:
|
|
92
|
-
if isinstance(item, ModelRequest):
|
|
93
|
-
yield UserQuestionWidget(item)
|
|
94
|
-
elif isinstance(item, HintMessage):
|
|
95
|
-
yield HintMessageWidget(item)
|
|
96
|
-
elif isinstance(item, ModelResponse):
|
|
97
|
-
yield AgentResponseWidget(item)
|
|
98
|
-
yield PartialResponseWidget(self.partial_response).data_bind(
|
|
99
|
-
item=ChatHistory.partial_response
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
# Track how many messages were rendered during initial compose
|
|
103
|
-
self._rendered_count = len(filtered)
|
|
104
|
-
|
|
105
|
-
def filtered_items(self) -> Generator[ModelMessage | HintMessage, None, None]:
|
|
106
|
-
for idx, next_item in enumerate(self.items):
|
|
107
|
-
prev_item = self.items[idx - 1] if idx > 0 else None
|
|
108
|
-
|
|
109
|
-
if isinstance(prev_item, ModelRequest) and isinstance(
|
|
110
|
-
next_item, ModelResponse
|
|
111
|
-
):
|
|
112
|
-
ask_user_tool_response_part = next(
|
|
113
|
-
(
|
|
114
|
-
part
|
|
115
|
-
for part in prev_item.parts
|
|
116
|
-
if isinstance(part, ToolReturnPart)
|
|
117
|
-
and part.tool_name == "ask_user"
|
|
118
|
-
),
|
|
119
|
-
None,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
ask_user_part = next(
|
|
123
|
-
(
|
|
124
|
-
part
|
|
125
|
-
for part in next_item.parts
|
|
126
|
-
if isinstance(part, ToolCallPart)
|
|
127
|
-
and part.tool_name == "ask_user"
|
|
128
|
-
),
|
|
129
|
-
None,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
if not ask_user_part or not ask_user_tool_response_part:
|
|
133
|
-
yield next_item
|
|
134
|
-
continue
|
|
135
|
-
if (
|
|
136
|
-
ask_user_tool_response_part.tool_call_id
|
|
137
|
-
== ask_user_part.tool_call_id
|
|
138
|
-
):
|
|
139
|
-
continue # don't emit tool call that happens after tool response
|
|
140
|
-
|
|
141
|
-
yield next_item
|
|
142
|
-
|
|
143
|
-
def update_messages(self, messages: list[ModelMessage | HintMessage]) -> None:
|
|
144
|
-
"""Update the displayed messages using incremental mounting."""
|
|
145
|
-
if not self.vertical_tail:
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
self.items = messages
|
|
149
|
-
filtered = list(self.filtered_items())
|
|
150
|
-
|
|
151
|
-
# Only mount new messages that haven't been rendered yet
|
|
152
|
-
if len(filtered) > self._rendered_count:
|
|
153
|
-
new_messages = filtered[self._rendered_count :]
|
|
154
|
-
for item in new_messages:
|
|
155
|
-
widget: Widget
|
|
156
|
-
if isinstance(item, ModelRequest):
|
|
157
|
-
widget = UserQuestionWidget(item)
|
|
158
|
-
elif isinstance(item, HintMessage):
|
|
159
|
-
widget = HintMessageWidget(item)
|
|
160
|
-
elif isinstance(item, ModelResponse):
|
|
161
|
-
widget = AgentResponseWidget(item)
|
|
162
|
-
else:
|
|
163
|
-
continue
|
|
164
|
-
|
|
165
|
-
# Mount before the PartialResponseWidget
|
|
166
|
-
self.vertical_tail.mount(widget, before=self.vertical_tail.children[-1])
|
|
167
|
-
|
|
168
|
-
self._rendered_count = len(filtered)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class UserQuestionWidget(Widget):
|
|
172
|
-
def __init__(self, item: ModelRequest | None) -> None:
|
|
173
|
-
super().__init__()
|
|
174
|
-
self.item = item
|
|
175
|
-
|
|
176
|
-
def compose(self) -> ComposeResult:
|
|
177
|
-
self.display = self.item is not None
|
|
178
|
-
if self.item is None:
|
|
179
|
-
yield Markdown(markdown="")
|
|
180
|
-
else:
|
|
181
|
-
prompt = self.format_prompt_parts(self.item.parts)
|
|
182
|
-
yield Markdown(markdown=prompt)
|
|
183
|
-
|
|
184
|
-
def format_prompt_parts(self, parts: Sequence[ModelRequestPart]) -> str:
|
|
185
|
-
acc = ""
|
|
186
|
-
for part in parts:
|
|
187
|
-
if isinstance(part, UserPromptPart):
|
|
188
|
-
acc += (
|
|
189
|
-
f"**>** {part.content if isinstance(part.content, str) else ''}\n\n"
|
|
190
|
-
)
|
|
191
|
-
elif isinstance(part, ToolReturnPart):
|
|
192
|
-
if part.tool_name == "ask_user":
|
|
193
|
-
acc += f"**>** {part.content.answer if isinstance(part.content, UserAnswer) else part.content['answer']}\n\n"
|
|
194
|
-
else:
|
|
195
|
-
# acc += " ∟ finished\n\n" # let's not show anything yet
|
|
196
|
-
pass
|
|
197
|
-
elif isinstance(part, UserPromptPart):
|
|
198
|
-
acc += f"**>** {part.content}\n\n"
|
|
199
|
-
return acc
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
class AgentResponseWidget(Widget):
|
|
203
|
-
def __init__(self, item: ModelResponse | None) -> None:
|
|
204
|
-
super().__init__()
|
|
205
|
-
self.item = item
|
|
206
|
-
|
|
207
|
-
def compose(self) -> ComposeResult:
|
|
208
|
-
self.display = self.item is not None
|
|
209
|
-
if self.item is None:
|
|
210
|
-
yield Markdown(markdown="")
|
|
211
|
-
else:
|
|
212
|
-
yield Markdown(markdown=self.compute_output())
|
|
213
|
-
|
|
214
|
-
def compute_output(self) -> str:
|
|
215
|
-
acc = ""
|
|
216
|
-
if self.item is None:
|
|
217
|
-
return ""
|
|
218
|
-
for idx, part in enumerate(self.item.parts):
|
|
219
|
-
if isinstance(part, TextPart):
|
|
220
|
-
# Only show the circle prefix if there's actual content
|
|
221
|
-
if part.content and part.content.strip():
|
|
222
|
-
acc += f"**⏺** {part.content}\n\n"
|
|
223
|
-
elif isinstance(part, ToolCallPart):
|
|
224
|
-
parts_str = self._format_tool_call_part(part)
|
|
225
|
-
acc += parts_str + "\n\n"
|
|
226
|
-
elif isinstance(part, BuiltinToolCallPart):
|
|
227
|
-
acc += f"{part.tool_name}({part.args})\n\n"
|
|
228
|
-
elif isinstance(part, BuiltinToolReturnPart):
|
|
229
|
-
acc += f"builtin tool ({part.tool_name}) return: {part.content}\n\n"
|
|
230
|
-
elif isinstance(part, ThinkingPart):
|
|
231
|
-
if (
|
|
232
|
-
idx == len(self.item.parts) - 1
|
|
233
|
-
): # show the thinking part only if it's the last part
|
|
234
|
-
acc += (
|
|
235
|
-
f"thinking: {part.content}\n\n"
|
|
236
|
-
if part.content
|
|
237
|
-
else "Thinking..."
|
|
238
|
-
)
|
|
239
|
-
else:
|
|
240
|
-
continue
|
|
241
|
-
return acc.strip()
|
|
242
|
-
|
|
243
|
-
def _truncate(self, text: str, max_length: int = 100) -> str:
|
|
244
|
-
"""Truncate text to max_length characters, adding ellipsis if needed."""
|
|
245
|
-
if len(text) <= max_length:
|
|
246
|
-
return text
|
|
247
|
-
return text[: max_length - 3] + "..."
|
|
248
|
-
|
|
249
|
-
def _parse_args(self, args: dict[str, object] | str | None) -> dict[str, object]:
|
|
250
|
-
"""Parse tool call arguments, handling both dict and JSON string formats."""
|
|
251
|
-
if args is None:
|
|
252
|
-
return {}
|
|
253
|
-
if isinstance(args, str):
|
|
254
|
-
try:
|
|
255
|
-
return json.loads(args) if args.strip() else {}
|
|
256
|
-
except json.JSONDecodeError:
|
|
257
|
-
return {}
|
|
258
|
-
return args if isinstance(args, dict) else {}
|
|
259
|
-
|
|
260
|
-
def _format_tool_call_part(self, part: ToolCallPart) -> str:
|
|
261
|
-
if part.tool_name == "ask_user":
|
|
262
|
-
return self._format_ask_user_part(part)
|
|
263
|
-
|
|
264
|
-
# Parse args once (handles both JSON string and dict)
|
|
265
|
-
args = self._parse_args(part.args)
|
|
266
|
-
|
|
267
|
-
# Codebase tools - show friendly names
|
|
268
|
-
if part.tool_name == "query_graph":
|
|
269
|
-
if "query" in args:
|
|
270
|
-
query = self._truncate(str(args["query"]))
|
|
271
|
-
return f'Querying code: "{query}"'
|
|
272
|
-
return "Querying code"
|
|
273
|
-
|
|
274
|
-
if part.tool_name == "retrieve_code":
|
|
275
|
-
if "qualified_name" in args:
|
|
276
|
-
return f'Retrieving code: "{args["qualified_name"]}"'
|
|
277
|
-
return "Retrieving code"
|
|
278
|
-
|
|
279
|
-
if part.tool_name == "file_read":
|
|
280
|
-
if "file_path" in args:
|
|
281
|
-
return f'Reading file: "{args["file_path"]}"'
|
|
282
|
-
return "Reading file"
|
|
283
|
-
|
|
284
|
-
if part.tool_name == "directory_lister":
|
|
285
|
-
if "directory" in args:
|
|
286
|
-
return f'Listing directory: "{args["directory"]}"'
|
|
287
|
-
return "Listing directory"
|
|
288
|
-
|
|
289
|
-
if part.tool_name == "codebase_shell":
|
|
290
|
-
command = args.get("command", "")
|
|
291
|
-
cmd_args = args.get("args", [])
|
|
292
|
-
# Handle cmd_args as list of strings
|
|
293
|
-
if isinstance(cmd_args, list):
|
|
294
|
-
args_str = " ".join(str(arg) for arg in cmd_args)
|
|
295
|
-
else:
|
|
296
|
-
args_str = ""
|
|
297
|
-
full_cmd = f"{command} {args_str}".strip()
|
|
298
|
-
if full_cmd:
|
|
299
|
-
return f'Running shell: "{self._truncate(full_cmd)}"'
|
|
300
|
-
return "Running shell"
|
|
301
|
-
|
|
302
|
-
# File management tools
|
|
303
|
-
if part.tool_name == "read_file":
|
|
304
|
-
if "filename" in args:
|
|
305
|
-
return f'Reading file: "{args["filename"]}"'
|
|
306
|
-
return "Reading file"
|
|
307
|
-
|
|
308
|
-
# Web search tools
|
|
309
|
-
if part.tool_name in [
|
|
310
|
-
"openai_web_search_tool",
|
|
311
|
-
"anthropic_web_search_tool",
|
|
312
|
-
"gemini_web_search_tool",
|
|
313
|
-
]:
|
|
314
|
-
if "query" in args:
|
|
315
|
-
query = self._truncate(str(args["query"]))
|
|
316
|
-
return f'Searching web: "{query}"'
|
|
317
|
-
return "Searching web"
|
|
318
|
-
|
|
319
|
-
# write_file
|
|
320
|
-
if part.tool_name == "write_file" or part.tool_name == "append_file":
|
|
321
|
-
if "filename" in args:
|
|
322
|
-
return f"{part.tool_name}({args['filename']})"
|
|
323
|
-
return f"{part.tool_name}()"
|
|
324
|
-
|
|
325
|
-
if part.tool_name == "write_artifact_section":
|
|
326
|
-
if "section_title" in args:
|
|
327
|
-
return f"{part.tool_name}({args['section_title']})"
|
|
328
|
-
return f"{part.tool_name}()"
|
|
329
|
-
|
|
330
|
-
if part.tool_name == "create_artifact":
|
|
331
|
-
if "name" in args:
|
|
332
|
-
return f"{part.tool_name}({args['name']})"
|
|
333
|
-
return f"▪ {part.tool_name}()"
|
|
334
|
-
|
|
335
|
-
return f"{part.tool_name}({part.args})"
|
|
336
|
-
|
|
337
|
-
def _format_ask_user_part(
|
|
338
|
-
self,
|
|
339
|
-
part: ToolCallPart,
|
|
340
|
-
) -> str:
|
|
341
|
-
if isinstance(part.args, str):
|
|
342
|
-
try:
|
|
343
|
-
_args = json.loads(part.args) if part.args.strip() else {}
|
|
344
|
-
except json.JSONDecodeError:
|
|
345
|
-
_args = {}
|
|
346
|
-
else:
|
|
347
|
-
_args = part.args
|
|
348
|
-
|
|
349
|
-
if isinstance(_args, dict) and "question" in _args:
|
|
350
|
-
return f"{_args['question']}"
|
|
351
|
-
else:
|
|
352
|
-
return "❓ "
|