fast-agent-mcp 0.3.16__py3-none-any.whl → 0.3.17__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 fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/agents/mcp_agent.py +1 -1
- fast_agent/cli/constants.py +2 -0
- fast_agent/cli/main.py +1 -1
- fast_agent/interfaces.py +4 -0
- fast_agent/llm/model_database.py +4 -1
- fast_agent/llm/model_factory.py +4 -2
- fast_agent/llm/model_info.py +19 -43
- fast_agent/llm/provider/google/llm_google_native.py +238 -7
- fast_agent/llm/provider/openai/llm_openai.py +229 -32
- fast_agent/skills/registry.py +17 -9
- fast_agent/tools/shell_runtime.py +4 -4
- fast_agent/ui/console_display.py +43 -1259
- fast_agent/ui/enhanced_prompt.py +26 -12
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +103 -45
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/streaming.py +638 -0
- fast_agent/ui/tool_display.py +417 -0
- {fast_agent_mcp-0.3.16.dist-info → fast_agent_mcp-0.3.17.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.3.16.dist-info → fast_agent_mcp-0.3.17.dist-info}/RECORD +23 -19
- {fast_agent_mcp-0.3.16.dist-info → fast_agent_mcp-0.3.17.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.16.dist-info → fast_agent_mcp-0.3.17.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.16.dist-info → fast_agent_mcp-0.3.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -115,6 +115,96 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
115
115
|
"Please check that your API key is valid and not expired.",
|
|
116
116
|
) from e
|
|
117
117
|
|
|
118
|
+
def _streams_tool_arguments(self) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Determine whether the current provider streams tool call arguments incrementally.
|
|
121
|
+
|
|
122
|
+
Official OpenAI and Azure OpenAI endpoints stream arguments. Most third-party
|
|
123
|
+
OpenAI-compatible gateways (e.g. OpenRouter, Moonshot) deliver the full arguments
|
|
124
|
+
once, so we should treat them as non-streaming to restore the legacy \"Calling Tool\"
|
|
125
|
+
display experience.
|
|
126
|
+
"""
|
|
127
|
+
if self.provider == Provider.AZURE:
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
if self.provider == Provider.OPENAI:
|
|
131
|
+
base_url = self._base_url()
|
|
132
|
+
if not base_url:
|
|
133
|
+
return True
|
|
134
|
+
lowered = base_url.lower()
|
|
135
|
+
return "api.openai" in lowered or "openai.azure" in lowered or "azure.com" in lowered
|
|
136
|
+
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def _emit_tool_notification_fallback(
|
|
140
|
+
self,
|
|
141
|
+
tool_calls: Any,
|
|
142
|
+
notified_indices: set[int],
|
|
143
|
+
*,
|
|
144
|
+
streams_arguments: bool,
|
|
145
|
+
model: str,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Emit start/stop notifications when streaming metadata was missing."""
|
|
148
|
+
if not tool_calls:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
for index, tool_call in enumerate(tool_calls):
|
|
152
|
+
if index in notified_indices:
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
tool_name = None
|
|
156
|
+
tool_use_id = None
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
tool_use_id = getattr(tool_call, "id", None)
|
|
160
|
+
function = getattr(tool_call, "function", None)
|
|
161
|
+
if function:
|
|
162
|
+
tool_name = getattr(function, "name", None)
|
|
163
|
+
except Exception:
|
|
164
|
+
tool_use_id = None
|
|
165
|
+
tool_name = None
|
|
166
|
+
|
|
167
|
+
if not tool_name:
|
|
168
|
+
tool_name = "tool"
|
|
169
|
+
if not tool_use_id:
|
|
170
|
+
tool_use_id = f"tool-{index}"
|
|
171
|
+
|
|
172
|
+
payload = {
|
|
173
|
+
"tool_name": tool_name,
|
|
174
|
+
"tool_use_id": tool_use_id,
|
|
175
|
+
"index": index,
|
|
176
|
+
"streams_arguments": streams_arguments,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
self._notify_tool_stream_listeners("start", payload)
|
|
180
|
+
self.logger.info(
|
|
181
|
+
"Model emitted fallback tool notification",
|
|
182
|
+
data={
|
|
183
|
+
"progress_action": ProgressAction.CALLING_TOOL,
|
|
184
|
+
"agent_name": self.name,
|
|
185
|
+
"model": model,
|
|
186
|
+
"tool_name": tool_name,
|
|
187
|
+
"tool_use_id": tool_use_id,
|
|
188
|
+
"tool_event": "start",
|
|
189
|
+
"streams_arguments": streams_arguments,
|
|
190
|
+
"fallback": True,
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
self._notify_tool_stream_listeners("stop", payload)
|
|
194
|
+
self.logger.info(
|
|
195
|
+
"Model emitted fallback tool notification",
|
|
196
|
+
data={
|
|
197
|
+
"progress_action": ProgressAction.CALLING_TOOL,
|
|
198
|
+
"agent_name": self.name,
|
|
199
|
+
"model": model,
|
|
200
|
+
"tool_name": tool_name,
|
|
201
|
+
"tool_use_id": tool_use_id,
|
|
202
|
+
"tool_event": "stop",
|
|
203
|
+
"streams_arguments": streams_arguments,
|
|
204
|
+
"fallback": True,
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
|
|
118
208
|
async def _process_stream(self, stream, model: str):
|
|
119
209
|
"""Process the streaming response and display real-time token usage."""
|
|
120
210
|
# Track estimated output tokens by counting text chunks
|
|
@@ -123,20 +213,25 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
123
213
|
# For non-OpenAI providers (like Ollama), ChatCompletionStreamState might not work correctly
|
|
124
214
|
# Fall back to manual accumulation if needed
|
|
125
215
|
# TODO -- consider this and whether to subclass instead
|
|
126
|
-
if self.provider in [
|
|
216
|
+
if self.provider in [
|
|
217
|
+
Provider.GENERIC,
|
|
218
|
+
Provider.OPENROUTER,
|
|
219
|
+
Provider.GOOGLE_OAI,
|
|
220
|
+
]:
|
|
127
221
|
return await self._process_stream_manual(stream, model)
|
|
128
222
|
|
|
129
223
|
# Use ChatCompletionStreamState helper for accumulation (OpenAI only)
|
|
130
224
|
state = ChatCompletionStreamState()
|
|
131
225
|
|
|
132
226
|
# Track tool call state for stream events
|
|
133
|
-
tool_call_started = {}
|
|
227
|
+
tool_call_started: dict[int, dict[str, Any]] = {}
|
|
228
|
+
streams_arguments = self._streams_tool_arguments()
|
|
229
|
+
notified_tool_indices: set[int] = set()
|
|
134
230
|
|
|
135
231
|
# Process the stream chunks
|
|
136
232
|
async for chunk in stream:
|
|
137
233
|
# Handle chunk accumulation
|
|
138
234
|
state.handle_chunk(chunk)
|
|
139
|
-
|
|
140
235
|
# Process streaming events for tool calls
|
|
141
236
|
if chunk.choices:
|
|
142
237
|
choice = chunk.choices[0]
|
|
@@ -148,15 +243,32 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
148
243
|
index = tool_call.index
|
|
149
244
|
|
|
150
245
|
# Fire "start" event on first chunk for this tool call
|
|
151
|
-
if index
|
|
152
|
-
|
|
246
|
+
if index is None:
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
existing_info = tool_call_started.get(index)
|
|
250
|
+
tool_use_id = tool_call.id or (
|
|
251
|
+
existing_info.get("tool_use_id") if existing_info else None
|
|
252
|
+
)
|
|
253
|
+
function_name = (
|
|
254
|
+
tool_call.function.name
|
|
255
|
+
if tool_call.function and tool_call.function.name
|
|
256
|
+
else (existing_info.get("tool_name") if existing_info else None)
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if existing_info is None and tool_use_id and function_name:
|
|
260
|
+
tool_call_started[index] = {
|
|
261
|
+
"tool_name": function_name,
|
|
262
|
+
"tool_use_id": tool_use_id,
|
|
263
|
+
"streams_arguments": streams_arguments,
|
|
264
|
+
}
|
|
153
265
|
self._notify_tool_stream_listeners(
|
|
154
266
|
"start",
|
|
155
267
|
{
|
|
156
|
-
"tool_name":
|
|
157
|
-
"tool_use_id":
|
|
268
|
+
"tool_name": function_name,
|
|
269
|
+
"tool_use_id": tool_use_id,
|
|
158
270
|
"index": index,
|
|
159
|
-
"streams_arguments":
|
|
271
|
+
"streams_arguments": streams_arguments,
|
|
160
272
|
},
|
|
161
273
|
)
|
|
162
274
|
self.logger.info(
|
|
@@ -165,22 +277,37 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
165
277
|
"progress_action": ProgressAction.CALLING_TOOL,
|
|
166
278
|
"agent_name": self.name,
|
|
167
279
|
"model": model,
|
|
168
|
-
"tool_name":
|
|
169
|
-
"tool_use_id":
|
|
280
|
+
"tool_name": function_name,
|
|
281
|
+
"tool_use_id": tool_use_id,
|
|
170
282
|
"tool_event": "start",
|
|
283
|
+
"streams_arguments": streams_arguments,
|
|
171
284
|
},
|
|
172
285
|
)
|
|
286
|
+
notified_tool_indices.add(index)
|
|
287
|
+
elif existing_info:
|
|
288
|
+
if tool_use_id:
|
|
289
|
+
existing_info["tool_use_id"] = tool_use_id
|
|
290
|
+
if function_name:
|
|
291
|
+
existing_info["tool_name"] = function_name
|
|
173
292
|
|
|
174
293
|
# Fire "delta" event for argument chunks
|
|
175
294
|
if tool_call.function and tool_call.function.arguments:
|
|
295
|
+
info = tool_call_started.setdefault(
|
|
296
|
+
index,
|
|
297
|
+
{
|
|
298
|
+
"tool_name": function_name,
|
|
299
|
+
"tool_use_id": tool_use_id,
|
|
300
|
+
"streams_arguments": streams_arguments,
|
|
301
|
+
},
|
|
302
|
+
)
|
|
176
303
|
self._notify_tool_stream_listeners(
|
|
177
304
|
"delta",
|
|
178
305
|
{
|
|
179
|
-
"tool_name":
|
|
180
|
-
"tool_use_id":
|
|
306
|
+
"tool_name": info.get("tool_name"),
|
|
307
|
+
"tool_use_id": info.get("tool_use_id"),
|
|
181
308
|
"index": index,
|
|
182
309
|
"chunk": tool_call.function.arguments,
|
|
183
|
-
"streams_arguments":
|
|
310
|
+
"streams_arguments": info.get("streams_arguments", False),
|
|
184
311
|
},
|
|
185
312
|
)
|
|
186
313
|
|
|
@@ -188,23 +315,27 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
188
315
|
if delta.content:
|
|
189
316
|
content = delta.content
|
|
190
317
|
# Use base class method for token estimation and progress emission
|
|
191
|
-
estimated_tokens = self._update_streaming_progress(
|
|
318
|
+
estimated_tokens = self._update_streaming_progress(
|
|
319
|
+
content, model, estimated_tokens
|
|
320
|
+
)
|
|
192
321
|
self._notify_tool_stream_listeners(
|
|
193
322
|
"text",
|
|
194
323
|
{
|
|
195
324
|
"chunk": content,
|
|
196
|
-
"streams_arguments":
|
|
325
|
+
"streams_arguments": streams_arguments,
|
|
197
326
|
},
|
|
198
327
|
)
|
|
199
328
|
|
|
200
329
|
# Fire "stop" event when tool calls complete
|
|
201
330
|
if choice.finish_reason == "tool_calls":
|
|
202
|
-
for index in tool_call_started.
|
|
331
|
+
for index, info in list(tool_call_started.items()):
|
|
203
332
|
self._notify_tool_stream_listeners(
|
|
204
333
|
"stop",
|
|
205
334
|
{
|
|
335
|
+
"tool_name": info.get("tool_name"),
|
|
336
|
+
"tool_use_id": info.get("tool_use_id"),
|
|
206
337
|
"index": index,
|
|
207
|
-
"streams_arguments":
|
|
338
|
+
"streams_arguments": info.get("streams_arguments", False),
|
|
208
339
|
},
|
|
209
340
|
)
|
|
210
341
|
self.logger.info(
|
|
@@ -213,9 +344,14 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
213
344
|
"progress_action": ProgressAction.CALLING_TOOL,
|
|
214
345
|
"agent_name": self.name,
|
|
215
346
|
"model": model,
|
|
347
|
+
"tool_name": info.get("tool_name"),
|
|
348
|
+
"tool_use_id": info.get("tool_use_id"),
|
|
216
349
|
"tool_event": "stop",
|
|
350
|
+
"streams_arguments": info.get("streams_arguments", False),
|
|
217
351
|
},
|
|
218
352
|
)
|
|
353
|
+
notified_tool_indices.add(index)
|
|
354
|
+
tool_call_started.clear()
|
|
219
355
|
|
|
220
356
|
# Check if we hit the length limit to avoid LengthFinishReasonError
|
|
221
357
|
current_snapshot = state.current_completion_snapshot
|
|
@@ -244,12 +380,24 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
244
380
|
f"Streaming complete - Model: {model}, Input tokens: {final_completion.usage.prompt_tokens}, Output tokens: {final_completion.usage.completion_tokens}"
|
|
245
381
|
)
|
|
246
382
|
|
|
383
|
+
final_message = None
|
|
384
|
+
if hasattr(final_completion, "choices") and final_completion.choices:
|
|
385
|
+
final_message = getattr(final_completion.choices[0], "message", None)
|
|
386
|
+
tool_calls = getattr(final_message, "tool_calls", None) if final_message else None
|
|
387
|
+
self._emit_tool_notification_fallback(
|
|
388
|
+
tool_calls,
|
|
389
|
+
notified_tool_indices,
|
|
390
|
+
streams_arguments=streams_arguments,
|
|
391
|
+
model=model,
|
|
392
|
+
)
|
|
393
|
+
|
|
247
394
|
return final_completion
|
|
248
395
|
|
|
249
396
|
# TODO - as per other comment this needs to go in another class. There are a number of "special" cases dealt with
|
|
250
397
|
# here to deal with OpenRouter idiosyncrasies between e.g. Anthropic and Gemini models.
|
|
251
398
|
async def _process_stream_manual(self, stream, model: str):
|
|
252
399
|
"""Manual stream processing for providers like Ollama that may not work with ChatCompletionStreamState."""
|
|
400
|
+
|
|
253
401
|
from openai.types.chat import ChatCompletionMessageToolCall
|
|
254
402
|
|
|
255
403
|
# Track estimated output tokens by counting text chunks
|
|
@@ -264,7 +412,9 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
264
412
|
usage_data = None
|
|
265
413
|
|
|
266
414
|
# Track tool call state for stream events
|
|
267
|
-
tool_call_started = {}
|
|
415
|
+
tool_call_started: dict[int, dict[str, Any]] = {}
|
|
416
|
+
streams_arguments = self._streams_tool_arguments()
|
|
417
|
+
notified_tool_indices: set[int] = set()
|
|
268
418
|
|
|
269
419
|
# Process the stream chunks manually
|
|
270
420
|
async for chunk in stream:
|
|
@@ -279,16 +429,30 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
279
429
|
if tool_call.index is not None:
|
|
280
430
|
index = tool_call.index
|
|
281
431
|
|
|
432
|
+
existing_info = tool_call_started.get(index)
|
|
433
|
+
tool_use_id = tool_call.id or (
|
|
434
|
+
existing_info.get("tool_use_id") if existing_info else None
|
|
435
|
+
)
|
|
436
|
+
function_name = (
|
|
437
|
+
tool_call.function.name
|
|
438
|
+
if tool_call.function and tool_call.function.name
|
|
439
|
+
else (existing_info.get("tool_name") if existing_info else None)
|
|
440
|
+
)
|
|
441
|
+
|
|
282
442
|
# Fire "start" event on first chunk for this tool call
|
|
283
|
-
if index not in tool_call_started and
|
|
284
|
-
tool_call_started[index] =
|
|
443
|
+
if index not in tool_call_started and tool_use_id and function_name:
|
|
444
|
+
tool_call_started[index] = {
|
|
445
|
+
"tool_name": function_name,
|
|
446
|
+
"tool_use_id": tool_use_id,
|
|
447
|
+
"streams_arguments": streams_arguments,
|
|
448
|
+
}
|
|
285
449
|
self._notify_tool_stream_listeners(
|
|
286
450
|
"start",
|
|
287
451
|
{
|
|
288
|
-
"tool_name":
|
|
289
|
-
"tool_use_id":
|
|
452
|
+
"tool_name": function_name,
|
|
453
|
+
"tool_use_id": tool_use_id,
|
|
290
454
|
"index": index,
|
|
291
|
-
"streams_arguments":
|
|
455
|
+
"streams_arguments": streams_arguments,
|
|
292
456
|
},
|
|
293
457
|
)
|
|
294
458
|
self.logger.info(
|
|
@@ -297,22 +461,37 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
297
461
|
"progress_action": ProgressAction.CALLING_TOOL,
|
|
298
462
|
"agent_name": self.name,
|
|
299
463
|
"model": model,
|
|
300
|
-
"tool_name":
|
|
301
|
-
"tool_use_id":
|
|
464
|
+
"tool_name": function_name,
|
|
465
|
+
"tool_use_id": tool_use_id,
|
|
302
466
|
"tool_event": "start",
|
|
467
|
+
"streams_arguments": streams_arguments,
|
|
303
468
|
},
|
|
304
469
|
)
|
|
470
|
+
notified_tool_indices.add(index)
|
|
471
|
+
elif existing_info:
|
|
472
|
+
if tool_use_id:
|
|
473
|
+
existing_info["tool_use_id"] = tool_use_id
|
|
474
|
+
if function_name:
|
|
475
|
+
existing_info["tool_name"] = function_name
|
|
305
476
|
|
|
306
477
|
# Fire "delta" event for argument chunks
|
|
307
478
|
if tool_call.function and tool_call.function.arguments:
|
|
479
|
+
info = tool_call_started.setdefault(
|
|
480
|
+
index,
|
|
481
|
+
{
|
|
482
|
+
"tool_name": function_name,
|
|
483
|
+
"tool_use_id": tool_use_id,
|
|
484
|
+
"streams_arguments": streams_arguments,
|
|
485
|
+
},
|
|
486
|
+
)
|
|
308
487
|
self._notify_tool_stream_listeners(
|
|
309
488
|
"delta",
|
|
310
489
|
{
|
|
311
|
-
"tool_name":
|
|
312
|
-
"tool_use_id":
|
|
490
|
+
"tool_name": info.get("tool_name"),
|
|
491
|
+
"tool_use_id": info.get("tool_use_id"),
|
|
313
492
|
"index": index,
|
|
314
493
|
"chunk": tool_call.function.arguments,
|
|
315
|
-
"streams_arguments":
|
|
494
|
+
"streams_arguments": info.get("streams_arguments", False),
|
|
316
495
|
},
|
|
317
496
|
)
|
|
318
497
|
|
|
@@ -321,23 +500,27 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
321
500
|
content = delta.content
|
|
322
501
|
accumulated_content += content
|
|
323
502
|
# Use base class method for token estimation and progress emission
|
|
324
|
-
estimated_tokens = self._update_streaming_progress(
|
|
503
|
+
estimated_tokens = self._update_streaming_progress(
|
|
504
|
+
content, model, estimated_tokens
|
|
505
|
+
)
|
|
325
506
|
self._notify_tool_stream_listeners(
|
|
326
507
|
"text",
|
|
327
508
|
{
|
|
328
509
|
"chunk": content,
|
|
329
|
-
"streams_arguments":
|
|
510
|
+
"streams_arguments": streams_arguments,
|
|
330
511
|
},
|
|
331
512
|
)
|
|
332
513
|
|
|
333
514
|
# Fire "stop" event when tool calls complete
|
|
334
515
|
if choice.finish_reason == "tool_calls":
|
|
335
|
-
for index in tool_call_started.
|
|
516
|
+
for index, info in list(tool_call_started.items()):
|
|
336
517
|
self._notify_tool_stream_listeners(
|
|
337
518
|
"stop",
|
|
338
519
|
{
|
|
520
|
+
"tool_name": info.get("tool_name"),
|
|
521
|
+
"tool_use_id": info.get("tool_use_id"),
|
|
339
522
|
"index": index,
|
|
340
|
-
"streams_arguments":
|
|
523
|
+
"streams_arguments": info.get("streams_arguments", False),
|
|
341
524
|
},
|
|
342
525
|
)
|
|
343
526
|
self.logger.info(
|
|
@@ -346,9 +529,14 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
346
529
|
"progress_action": ProgressAction.CALLING_TOOL,
|
|
347
530
|
"agent_name": self.name,
|
|
348
531
|
"model": model,
|
|
532
|
+
"tool_name": info.get("tool_name"),
|
|
533
|
+
"tool_use_id": info.get("tool_use_id"),
|
|
349
534
|
"tool_event": "stop",
|
|
535
|
+
"streams_arguments": info.get("streams_arguments", False),
|
|
350
536
|
},
|
|
351
537
|
)
|
|
538
|
+
notified_tool_indices.add(index)
|
|
539
|
+
tool_call_started.clear()
|
|
352
540
|
|
|
353
541
|
# Extract other fields from the chunk
|
|
354
542
|
if chunk.choices:
|
|
@@ -449,6 +637,15 @@ class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage])
|
|
|
449
637
|
f"Streaming complete - Model: {model}, Input tokens: {getattr(usage_data, 'prompt_tokens', 0)}, Output tokens: {actual_tokens}"
|
|
450
638
|
)
|
|
451
639
|
|
|
640
|
+
final_message = final_completion.choices[0].message if final_completion.choices else None
|
|
641
|
+
tool_calls = getattr(final_message, "tool_calls", None) if final_message else None
|
|
642
|
+
self._emit_tool_notification_fallback(
|
|
643
|
+
tool_calls,
|
|
644
|
+
notified_tool_indices,
|
|
645
|
+
streams_arguments=streams_arguments,
|
|
646
|
+
model=model,
|
|
647
|
+
)
|
|
648
|
+
|
|
452
649
|
return final_completion
|
|
453
650
|
|
|
454
651
|
async def _openai_completion(
|
fast_agent/skills/registry.py
CHANGED
|
@@ -106,7 +106,6 @@ class SkillRegistry:
|
|
|
106
106
|
errors: List[dict[str, str]] | None = None,
|
|
107
107
|
) -> List[SkillManifest]:
|
|
108
108
|
manifests: List[SkillManifest] = []
|
|
109
|
-
cwd = Path.cwd()
|
|
110
109
|
for entry in sorted(directory.iterdir()):
|
|
111
110
|
if not entry.is_dir():
|
|
112
111
|
continue
|
|
@@ -115,13 +114,22 @@ class SkillRegistry:
|
|
|
115
114
|
continue
|
|
116
115
|
manifest, error = cls._parse_manifest(manifest_path)
|
|
117
116
|
if manifest:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
# Compute relative path from skills directory (not cwd)
|
|
118
|
+
# Old behavior: try both cwd and directory
|
|
119
|
+
# relative_path: Path | None = None
|
|
120
|
+
# for base in (cwd, directory):
|
|
121
|
+
# try:
|
|
122
|
+
# relative_path = manifest_path.relative_to(base)
|
|
123
|
+
# break
|
|
124
|
+
# except ValueError:
|
|
125
|
+
# continue
|
|
126
|
+
|
|
127
|
+
# New behavior: always relative to skills directory
|
|
128
|
+
try:
|
|
129
|
+
relative_path = manifest_path.relative_to(directory)
|
|
130
|
+
except ValueError:
|
|
131
|
+
relative_path = None
|
|
132
|
+
|
|
125
133
|
manifest = replace(manifest, relative_path=relative_path)
|
|
126
134
|
manifests.append(manifest)
|
|
127
135
|
elif errors is not None:
|
|
@@ -175,7 +183,7 @@ def format_skills_for_prompt(manifests: Sequence[SkillManifest]) -> str:
|
|
|
175
183
|
preamble = (
|
|
176
184
|
"Skills provide specialized capabilities and domain knowledge. Use a Skill if it seems in any way "
|
|
177
185
|
"relevant to the Users task, intent or would increase effectiveness. \n"
|
|
178
|
-
"The 'execute' tool gives you shell access to the current working directory (agent workspace) "
|
|
186
|
+
"The 'execute' tool gives you direct shell access to the current working directory (agent workspace) "
|
|
179
187
|
"and outputted files are visible to the User.\n"
|
|
180
188
|
"To use a Skill you must first read the SKILL.md file (use 'execute' tool).\n "
|
|
181
189
|
"Only use skills listed in <available_skills> below.\n\n"
|
|
@@ -42,13 +42,13 @@ class ShellRuntime:
|
|
|
42
42
|
|
|
43
43
|
self._tool = Tool(
|
|
44
44
|
name="execute",
|
|
45
|
-
description=f"Run a shell command
|
|
45
|
+
description=f"Run a shell command directly in {shell_name}.",
|
|
46
46
|
inputSchema={
|
|
47
47
|
"type": "object",
|
|
48
48
|
"properties": {
|
|
49
49
|
"command": {
|
|
50
50
|
"type": "string",
|
|
51
|
-
"description": "
|
|
51
|
+
"description": "Command string only - no shell executable prefix (correct: 'pwd', incorrect: 'bash -c pwd').",
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"required": ["command"],
|
|
@@ -71,8 +71,8 @@ class ShellRuntime:
|
|
|
71
71
|
def working_directory(self) -> Path:
|
|
72
72
|
"""Return the working directory used for shell execution."""
|
|
73
73
|
# TODO -- reinstate when we provide duplication/isolation of skill workspaces
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if self._skills_directory and self._skills_directory.exists():
|
|
75
|
+
return self._skills_directory
|
|
76
76
|
return Path.cwd()
|
|
77
77
|
|
|
78
78
|
def runtime_info(self) -> Dict[str, str | None]:
|