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.
Files changed (95) hide show
  1. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/PKG-INFO +24 -2
  2. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/README.md +21 -0
  3. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/pyproject.toml +3 -2
  4. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/cli.py +28 -11
  6. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/runner.py +149 -0
  7. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/mcp.py +21 -2
  8. deepy_cli-0.2.6/src/deepy/tui/__init__.py +5 -0
  9. deepy_cli-0.2.6/src/deepy/tui/app.py +1728 -0
  10. deepy_cli-0.2.6/src/deepy/tui/commands.py +89 -0
  11. deepy_cli-0.2.6/src/deepy/tui/compat.py +7 -0
  12. deepy_cli-0.2.6/src/deepy/tui/diff.py +115 -0
  13. deepy_cli-0.2.6/src/deepy/tui/runner.py +34 -0
  14. deepy_cli-0.2.6/src/deepy/tui/screens.py +409 -0
  15. deepy_cli-0.2.6/src/deepy/tui/state.py +101 -0
  16. deepy_cli-0.2.6/src/deepy/tui/widgets.py +872 -0
  17. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/message_view.py +6 -2
  18. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/slash_commands.py +5 -1
  19. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/terminal.py +2 -2
  20. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/__main__.py +0 -0
  21. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/config/__init__.py +0 -0
  22. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/config/settings.py +0 -0
  23. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/__init__.py +0 -0
  24. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
  25. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
  26. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  27. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/WebFetch.md +0 -0
  28. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/WebSearch.md +0 -0
  29. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/__init__.py +0 -0
  30. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/edit.md +0 -0
  31. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/modify.md +0 -0
  32. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/read.md +0 -0
  33. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/shell.md +0 -0
  34. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/todo_write.md +0 -0
  35. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/data/tools/write.md +0 -0
  36. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/errors.py +0 -0
  37. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/__init__.py +0 -0
  38. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/agent.py +0 -0
  39. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/compaction.py +0 -0
  40. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/context.py +0 -0
  41. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/events.py +0 -0
  42. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/model_capabilities.py +0 -0
  43. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/provider.py +0 -0
  44. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/replay.py +0 -0
  45. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/llm/thinking.py +0 -0
  46. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/__init__.py +0 -0
  47. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/compact.py +0 -0
  48. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/init_agents.py +0 -0
  49. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/rules.py +0 -0
  50. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/runtime_context.py +0 -0
  51. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/system.py +0 -0
  52. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/prompts/tool_docs.py +0 -0
  53. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/sessions/__init__.py +0 -0
  54. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/sessions/jsonl.py +0 -0
  55. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/sessions/manager.py +0 -0
  56. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/skill_market.py +0 -0
  57. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/skills.py +0 -0
  58. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/status.py +0 -0
  59. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/todos.py +0 -0
  60. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/__init__.py +0 -0
  61. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/agents.py +0 -0
  62. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/builtin.py +0 -0
  63. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/file_state.py +0 -0
  64. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/result.py +0 -0
  65. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/shell_output.py +0 -0
  66. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/tools/shell_utils.py +0 -0
  67. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/types/__init__.py +0 -0
  68. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/types/sdk.py +0 -0
  69. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/types/tool_payloads.py +0 -0
  70. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/__init__.py +0 -0
  71. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/app.py +0 -0
  72. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/ask_user_question.py +0 -0
  73. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/exit_summary.py +0 -0
  74. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/file_mentions.py +0 -0
  75. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/loading_text.py +0 -0
  76. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/local_command.py +0 -0
  77. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/markdown.py +0 -0
  78. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/model_picker.py +0 -0
  79. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/prompt_buffer.py +0 -0
  80. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/prompt_input.py +0 -0
  81. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/session_list.py +0 -0
  82. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/session_picker.py +0 -0
  83. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/skill_picker.py +0 -0
  84. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/status_footer.py +0 -0
  85. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/styles.py +0 -0
  86. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/theme_picker.py +0 -0
  87. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/thinking_state.py +0 -0
  88. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/ui/welcome.py +0 -0
  89. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/update_check.py +0 -0
  90. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/usage.py +0 -0
  91. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/__init__.py +0 -0
  92. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/debug_logger.py +0 -0
  93. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/error_logger.py +0 -0
  94. {deepy_cli-0.2.4 → deepy_cli-0.2.6}/src/deepy/utils/json.py +0 -0
  95. {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.4
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>=13.9,<15
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.4"
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>=13.9,<15",
27
+ "rich>=14.2,<15",
28
+ "textual>=8.2,<9",
28
29
  "tiktoken>=0.9,<1",
29
30
  "tomli-w>=1",
30
31
  ]
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.6"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -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
- settings = load_settings(args.config)
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 = MCPServerStdio(
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,
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from .runner import run_tui
4
+
5
+ __all__ = ["run_tui"]