deepy-cli 0.2.4__tar.gz → 0.2.6__tar.gz
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.
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/PKG-INFO +24 -2
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/README.md +21 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/pyproject.toml +3 -2
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/cli.py +28 -11
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/runner.py +149 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/mcp.py +21 -2
- deepy_cli-0.2.6/src/deepy/tui/__init__.py +5 -0
- deepy_cli-0.2.6/src/deepy/tui/app.py +1728 -0
- deepy_cli-0.2.6/src/deepy/tui/commands.py +89 -0
- deepy_cli-0.2.6/src/deepy/tui/compat.py +7 -0
- deepy_cli-0.2.6/src/deepy/tui/diff.py +115 -0
- deepy_cli-0.2.6/src/deepy/tui/runner.py +34 -0
- deepy_cli-0.2.6/src/deepy/tui/screens.py +409 -0
- deepy_cli-0.2.6/src/deepy/tui/state.py +101 -0
- deepy_cli-0.2.6/src/deepy/tui/widgets.py +872 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/message_view.py +6 -2
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/slash_commands.py +5 -1
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/terminal.py +2 -2
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/config/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/config/settings.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/edit.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/modify.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/read.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/write.md +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/agent.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/provider.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/sessions/jsonl.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/agents.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/builtin.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/prompt_input.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/status_footer.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/welcome.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/notify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: deepy-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Deepy - Vibe coding for DeepSeek models in your terminal
|
|
5
5
|
Keywords: deepseek,coding-agent,terminal,cli,agents
|
|
6
6
|
Author: kirineko
|
|
@@ -18,7 +18,8 @@ Requires-Dist: orjson>=3.10,<4
|
|
|
18
18
|
Requires-Dist: pydantic>=2.12,<3
|
|
19
19
|
Requires-Dist: prompt-toolkit>=3.0,<4
|
|
20
20
|
Requires-Dist: pyyaml>=6.0,<7
|
|
21
|
-
Requires-Dist: rich>=
|
|
21
|
+
Requires-Dist: rich>=14.2,<15
|
|
22
|
+
Requires-Dist: textual>=8.2,<9
|
|
22
23
|
Requires-Dist: tiktoken>=0.9,<1
|
|
23
24
|
Requires-Dist: tomli-w>=1
|
|
24
25
|
Requires-Python: >=3.12
|
|
@@ -153,6 +154,27 @@ cd your-project
|
|
|
153
154
|
deepy
|
|
154
155
|
```
|
|
155
156
|
|
|
157
|
+
### Experimental Textual TUI
|
|
158
|
+
|
|
159
|
+
Deepy's stable default UI is still launched with `deepy`. Users who want to try
|
|
160
|
+
the new opt-in Textual interface can run:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
deepy tui
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This first iteration focuses on experience and TUI-native interactions: a
|
|
167
|
+
scrollable transcript, live thinking and assistant blocks, prompt suggestions
|
|
168
|
+
for slash commands and `@file` mentions, status/help surfaces, and a Deepy-owned
|
|
169
|
+
diff view. It is experimental and may change between releases.
|
|
170
|
+
|
|
171
|
+
Known limitations: the TUI does not add interactive shell/PTTY support yet, and
|
|
172
|
+
toad / textual-diff-view are only design references. Deepy does not copy their
|
|
173
|
+
AGPL source or depend on those packages.
|
|
174
|
+
|
|
175
|
+
Please report feedback through GitHub Issues and include your terminal, shell,
|
|
176
|
+
operating system, and the exact `deepy tui` workflow you tried.
|
|
177
|
+
|
|
156
178
|
## Installation Notes
|
|
157
179
|
|
|
158
180
|
Use this order for a fresh machine:
|
|
@@ -124,6 +124,27 @@ cd your-project
|
|
|
124
124
|
deepy
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### Experimental Textual TUI
|
|
128
|
+
|
|
129
|
+
Deepy's stable default UI is still launched with `deepy`. Users who want to try
|
|
130
|
+
the new opt-in Textual interface can run:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
deepy tui
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This first iteration focuses on experience and TUI-native interactions: a
|
|
137
|
+
scrollable transcript, live thinking and assistant blocks, prompt suggestions
|
|
138
|
+
for slash commands and `@file` mentions, status/help surfaces, and a Deepy-owned
|
|
139
|
+
diff view. It is experimental and may change between releases.
|
|
140
|
+
|
|
141
|
+
Known limitations: the TUI does not add interactive shell/PTTY support yet, and
|
|
142
|
+
toad / textual-diff-view are only design references. Deepy does not copy their
|
|
143
|
+
AGPL source or depend on those packages.
|
|
144
|
+
|
|
145
|
+
Please report feedback through GitHub Issues and include your terminal, shell,
|
|
146
|
+
operating system, and the exact `deepy tui` workflow you tried.
|
|
147
|
+
|
|
127
148
|
## Installation Notes
|
|
128
149
|
|
|
129
150
|
Use this order for a fresh machine:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deepy-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.6"
|
|
4
4
|
description = "Deepy - Vibe coding for DeepSeek models in your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -24,7 +24,8 @@ dependencies = [
|
|
|
24
24
|
"pydantic>=2.12,<3",
|
|
25
25
|
"prompt-toolkit>=3.0,<4",
|
|
26
26
|
"pyyaml>=6.0,<7",
|
|
27
|
-
"rich>=
|
|
27
|
+
"rich>=14.2,<15",
|
|
28
|
+
"textual>=8.2,<9",
|
|
28
29
|
"tiktoken>=0.9,<1",
|
|
29
30
|
"tomli-w>=1",
|
|
30
31
|
]
|
|
@@ -78,6 +78,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
78
78
|
run_parser.add_argument("--session", help="Resume an existing session id.")
|
|
79
79
|
run_parser.add_argument("--skill", action="append", default=[], help="Load a skill by name.")
|
|
80
80
|
|
|
81
|
+
subparsers.add_parser("tui", help="Start the experimental Textual TUI.")
|
|
82
|
+
|
|
81
83
|
sessions_parser = subparsers.add_parser("sessions", help="Inspect project sessions.")
|
|
82
84
|
sessions_sub = sessions_parser.add_subparsers(dest="sessions_command", required=True)
|
|
83
85
|
sessions_sub.add_parser("list", help="List sessions for the current project.")
|
|
@@ -410,6 +412,29 @@ def _cmd_status(args: argparse.Namespace) -> int:
|
|
|
410
412
|
return 0
|
|
411
413
|
|
|
412
414
|
|
|
415
|
+
def _ensure_interactive_settings(args: argparse.Namespace) -> Settings:
|
|
416
|
+
settings = load_settings(args.config)
|
|
417
|
+
if not settings.model.api_key:
|
|
418
|
+
print("Deepy needs a DeepSeek API key before starting interactive mode.")
|
|
419
|
+
setup_args = argparse.Namespace(config=args.config, force=True)
|
|
420
|
+
_cmd_config_setup(setup_args)
|
|
421
|
+
settings = load_settings(args.config)
|
|
422
|
+
if settings.path is not None and not settings.ui.theme_configured:
|
|
423
|
+
theme = _prompt_theme_value(default=settings.ui.theme)
|
|
424
|
+
update_config_theme(settings.path, theme)
|
|
425
|
+
settings = load_settings(args.config)
|
|
426
|
+
return settings
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _cmd_tui(args: argparse.Namespace) -> int:
|
|
430
|
+
if not sys.stdin.isatty():
|
|
431
|
+
print("experimental TUI requires a TTY; use `deepy` for the stable terminal UI.", file=sys.stderr)
|
|
432
|
+
return 1
|
|
433
|
+
from deepy.tui import run_tui
|
|
434
|
+
|
|
435
|
+
return run_tui(_ensure_interactive_settings(args), project_root=Path.cwd())
|
|
436
|
+
|
|
437
|
+
|
|
413
438
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
414
439
|
parser = _build_parser()
|
|
415
440
|
args = parser.parse_args(argv)
|
|
@@ -435,20 +460,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
435
460
|
return _cmd_skills(args)
|
|
436
461
|
if args.command == "status":
|
|
437
462
|
return _cmd_status(args)
|
|
463
|
+
if args.command == "tui":
|
|
464
|
+
return _cmd_tui(args)
|
|
438
465
|
|
|
439
466
|
if not sys.stdin.isatty():
|
|
440
467
|
parser.error("interactive mode requires a TTY; use `deepy doctor` or `deepy config show`.")
|
|
441
|
-
|
|
442
|
-
if not settings.model.api_key:
|
|
443
|
-
print("Deepy needs a DeepSeek API key before starting interactive mode.")
|
|
444
|
-
setup_args = argparse.Namespace(config=args.config, force=True)
|
|
445
|
-
_cmd_config_setup(setup_args)
|
|
446
|
-
settings = load_settings(args.config)
|
|
447
|
-
if settings.path is not None and not settings.ui.theme_configured:
|
|
448
|
-
theme = _prompt_theme_value(default=settings.ui.theme)
|
|
449
|
-
update_config_theme(settings.path, theme)
|
|
450
|
-
settings = load_settings(args.config)
|
|
451
|
-
return run_interactive(settings)
|
|
468
|
+
return run_interactive(_ensure_interactive_settings(args))
|
|
452
469
|
|
|
453
470
|
|
|
454
471
|
if __name__ == "__main__":
|
|
@@ -25,6 +25,11 @@ from .events import DeepyStreamEvent, normalize_stream_event
|
|
|
25
25
|
from .provider import ProviderBundle, build_provider_bundle
|
|
26
26
|
|
|
27
27
|
DEFAULT_MAX_TURNS = 100
|
|
28
|
+
INTERRUPTED_MARKER_TEXT = (
|
|
29
|
+
"Interrupted by user with Esc. This turn was stopped before completion. "
|
|
30
|
+
"Do not continue the interrupted request unless the user explicitly asks to continue."
|
|
31
|
+
)
|
|
32
|
+
INTERRUPTED_TOOL_OUTPUT_TEXT = "Tool execution interrupted by user with Esc."
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
@dataclass(frozen=True)
|
|
@@ -128,6 +133,7 @@ async def run_prompt_once(
|
|
|
128
133
|
pending_questions: list[dict[str, Any]] = []
|
|
129
134
|
usage = TokenUsage()
|
|
130
135
|
interrupt_task: asyncio.Task[bool] | None = None
|
|
136
|
+
session_baseline_count = len(await session.get_items())
|
|
131
137
|
try:
|
|
132
138
|
result = Runner.run_streamed(
|
|
133
139
|
agent,
|
|
@@ -272,6 +278,12 @@ async def run_prompt_once(
|
|
|
272
278
|
raise
|
|
273
279
|
|
|
274
280
|
interrupted = interrupted or await _finish_interrupt_task(interrupt_task)
|
|
281
|
+
if interrupted:
|
|
282
|
+
await _reconcile_interrupted_session_tail(
|
|
283
|
+
session,
|
|
284
|
+
baseline_count=session_baseline_count,
|
|
285
|
+
prompt=prompt,
|
|
286
|
+
)
|
|
275
287
|
|
|
276
288
|
final_output = getattr(result, "final_output", None)
|
|
277
289
|
output = final_output if isinstance(final_output, str) else "".join(chunks)
|
|
@@ -512,6 +524,143 @@ async def _finish_interrupt_task(task: asyncio.Task[bool] | None) -> bool:
|
|
|
512
524
|
return False
|
|
513
525
|
|
|
514
526
|
|
|
527
|
+
async def _reconcile_interrupted_session_tail(
|
|
528
|
+
session: DeepyJsonlSession,
|
|
529
|
+
*,
|
|
530
|
+
baseline_count: int,
|
|
531
|
+
prompt: str,
|
|
532
|
+
) -> None:
|
|
533
|
+
items = await session.get_items()
|
|
534
|
+
if baseline_count < 0 or baseline_count > len(items):
|
|
535
|
+
return
|
|
536
|
+
suffix = items[baseline_count:]
|
|
537
|
+
if not suffix:
|
|
538
|
+
return
|
|
539
|
+
|
|
540
|
+
if len(suffix) == 1 and _is_user_prompt_item(suffix[0], prompt):
|
|
541
|
+
await session.pop_item()
|
|
542
|
+
return
|
|
543
|
+
|
|
544
|
+
additions = _interrupted_tool_output_items(suffix)
|
|
545
|
+
if not _is_interrupt_marker_item(suffix[-1]):
|
|
546
|
+
additions.append(_interrupted_marker_item())
|
|
547
|
+
if additions:
|
|
548
|
+
await session.add_items(additions)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _is_user_prompt_item(item: dict[str, Any], prompt: str) -> bool:
|
|
552
|
+
if item.get("role") != "user":
|
|
553
|
+
return False
|
|
554
|
+
return _item_text_content(item) == prompt
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _item_text_content(item: dict[str, Any]) -> str:
|
|
558
|
+
content = item.get("content")
|
|
559
|
+
if isinstance(content, str):
|
|
560
|
+
return content
|
|
561
|
+
if not isinstance(content, list):
|
|
562
|
+
return ""
|
|
563
|
+
parts: list[str] = []
|
|
564
|
+
for part in content:
|
|
565
|
+
if isinstance(part, dict):
|
|
566
|
+
text = part.get("text")
|
|
567
|
+
if text is None:
|
|
568
|
+
text = part.get("input_text")
|
|
569
|
+
if isinstance(text, str):
|
|
570
|
+
parts.append(text)
|
|
571
|
+
return "".join(parts)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _interrupted_tool_output_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
575
|
+
output_call_ids = {
|
|
576
|
+
call_id
|
|
577
|
+
for item in items
|
|
578
|
+
if (call_id := _function_call_output_id(item))
|
|
579
|
+
}
|
|
580
|
+
additions: list[dict[str, Any]] = []
|
|
581
|
+
added_call_ids: set[str] = set()
|
|
582
|
+
for item in items:
|
|
583
|
+
for call_id, output_item in _missing_output_items_for_call(item, output_call_ids):
|
|
584
|
+
if call_id in added_call_ids:
|
|
585
|
+
continue
|
|
586
|
+
additions.append(output_item)
|
|
587
|
+
added_call_ids.add(call_id)
|
|
588
|
+
return additions
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _missing_output_items_for_call(
|
|
592
|
+
item: dict[str, Any],
|
|
593
|
+
output_call_ids: set[str],
|
|
594
|
+
) -> list[tuple[str, dict[str, Any]]]:
|
|
595
|
+
call_id = _function_call_id(item)
|
|
596
|
+
if call_id:
|
|
597
|
+
return (
|
|
598
|
+
[]
|
|
599
|
+
if call_id in output_call_ids
|
|
600
|
+
else [
|
|
601
|
+
(
|
|
602
|
+
call_id,
|
|
603
|
+
{
|
|
604
|
+
"type": "function_call_output",
|
|
605
|
+
"call_id": call_id,
|
|
606
|
+
"output": INTERRUPTED_TOOL_OUTPUT_TEXT,
|
|
607
|
+
},
|
|
608
|
+
)
|
|
609
|
+
]
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
missing: list[tuple[str, dict[str, Any]]] = []
|
|
613
|
+
if item.get("role") != "assistant":
|
|
614
|
+
return missing
|
|
615
|
+
tool_calls = item.get("tool_calls")
|
|
616
|
+
if not isinstance(tool_calls, list):
|
|
617
|
+
return missing
|
|
618
|
+
for tool_call in tool_calls:
|
|
619
|
+
if not isinstance(tool_call, dict):
|
|
620
|
+
continue
|
|
621
|
+
chat_call_id = tool_call.get("id")
|
|
622
|
+
if not isinstance(chat_call_id, str) or not chat_call_id or chat_call_id in output_call_ids:
|
|
623
|
+
continue
|
|
624
|
+
missing.append(
|
|
625
|
+
(
|
|
626
|
+
chat_call_id,
|
|
627
|
+
{
|
|
628
|
+
"role": "tool",
|
|
629
|
+
"tool_call_id": chat_call_id,
|
|
630
|
+
"content": INTERRUPTED_TOOL_OUTPUT_TEXT,
|
|
631
|
+
},
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
return missing
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _function_call_id(item: dict[str, Any]) -> str:
|
|
638
|
+
if item.get("type") != "function_call":
|
|
639
|
+
return ""
|
|
640
|
+
call_id = item.get("call_id")
|
|
641
|
+
if call_id is None:
|
|
642
|
+
call_id = item.get("id")
|
|
643
|
+
return call_id if isinstance(call_id, str) else ""
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _function_call_output_id(item: dict[str, Any]) -> str:
|
|
647
|
+
if item.get("type") == "function_call_output":
|
|
648
|
+
call_id = item.get("call_id")
|
|
649
|
+
return call_id if isinstance(call_id, str) else ""
|
|
650
|
+
if item.get("role") == "tool":
|
|
651
|
+
tool_call_id = item.get("tool_call_id")
|
|
652
|
+
return tool_call_id if isinstance(tool_call_id, str) else ""
|
|
653
|
+
return ""
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _interrupted_marker_item() -> dict[str, Any]:
|
|
657
|
+
return {"role": "assistant", "content": INTERRUPTED_MARKER_TEXT}
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _is_interrupt_marker_item(item: dict[str, Any]) -> bool:
|
|
661
|
+
return item.get("role") == "assistant" and item.get("content") == INTERRUPTED_MARKER_TEXT
|
|
662
|
+
|
|
663
|
+
|
|
515
664
|
def _pending_questions_from_tool_output(output: str) -> list[dict[str, Any]]:
|
|
516
665
|
if not output.strip():
|
|
517
666
|
return []
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Literal, Mapping
|
|
@@ -156,7 +157,6 @@ class DeepyMcpRuntime:
|
|
|
156
157
|
|
|
157
158
|
def _build_sdk_servers(self) -> list[Any]:
|
|
158
159
|
from agents.mcp import (
|
|
159
|
-
MCPServerStdio,
|
|
160
160
|
MCPServerStdioParams,
|
|
161
161
|
MCPServerStreamableHttp,
|
|
162
162
|
MCPServerStreamableHttpParams,
|
|
@@ -185,7 +185,7 @@ class DeepyMcpRuntime:
|
|
|
185
185
|
params["env"] = dict(definition.env)
|
|
186
186
|
if definition.cwd:
|
|
187
187
|
params["cwd"] = definition.cwd
|
|
188
|
-
server =
|
|
188
|
+
server = _quiet_stdio_server(
|
|
189
189
|
params=params,
|
|
190
190
|
name=definition.name,
|
|
191
191
|
cache_tools_list=self.settings.mcp.cache_tools_list,
|
|
@@ -419,6 +419,25 @@ def _status_from_definition(
|
|
|
419
419
|
)
|
|
420
420
|
|
|
421
421
|
|
|
422
|
+
def _quiet_stdio_server(**kwargs: Any) -> Any:
|
|
423
|
+
from agents.mcp import MCPServerStdio
|
|
424
|
+
|
|
425
|
+
class DeepyQuietMCPServerStdio(MCPServerStdio):
|
|
426
|
+
def create_streams(self) -> Any:
|
|
427
|
+
return _quiet_stdio_client(self.params)
|
|
428
|
+
|
|
429
|
+
return DeepyQuietMCPServerStdio(**kwargs)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@asynccontextmanager
|
|
433
|
+
async def _quiet_stdio_client(params: Any):
|
|
434
|
+
from mcp import stdio_client
|
|
435
|
+
|
|
436
|
+
with open(os.devnull, "w", encoding="utf-8") as errlog:
|
|
437
|
+
async with stdio_client(params, errlog=errlog) as streams:
|
|
438
|
+
yield streams
|
|
439
|
+
|
|
440
|
+
|
|
422
441
|
def _load_mcp_file(
|
|
423
442
|
path: Path,
|
|
424
443
|
source: str,
|