deepy-cli 0.2.14__tar.gz → 0.2.15__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 (100) hide show
  1. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/PKG-INFO +1 -1
  2. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/pyproject.toml +1 -1
  3. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/__init__.py +1 -1
  4. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/config/settings.py +6 -4
  5. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/agent.py +14 -0
  6. deepy_cli-0.2.15/src/deepy/llm/provider.py +143 -0
  7. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/agents.py +63 -11
  8. deepy_cli-0.2.14/src/deepy/llm/provider.py +0 -82
  9. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/README.md +0 -0
  10. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/__main__.py +0 -0
  11. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/cli.py +0 -0
  12. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/config/__init__.py +0 -0
  13. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/__init__.py +0 -0
  14. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
  15. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
  16. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  17. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/Search.md +0 -0
  18. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/WebFetch.md +0 -0
  19. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/WebSearch.md +0 -0
  20. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/__init__.py +0 -0
  21. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/apply_patch.md +0 -0
  22. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/edit_text.md +0 -0
  23. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/read_file.md +0 -0
  24. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/shell.md +0 -0
  25. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/todo_write.md +0 -0
  26. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/write_file.md +0 -0
  27. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/errors.py +0 -0
  28. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/input_suggestions.py +0 -0
  29. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/__init__.py +0 -0
  30. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/compaction.py +0 -0
  31. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/context.py +0 -0
  32. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/events.py +0 -0
  33. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/model_capabilities.py +0 -0
  34. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/replay.py +0 -0
  35. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/runner.py +0 -0
  36. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/thinking.py +0 -0
  37. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/mcp.py +0 -0
  38. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/__init__.py +0 -0
  39. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/compact.py +0 -0
  40. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/init_agents.py +0 -0
  41. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/rules.py +0 -0
  42. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/runtime_context.py +0 -0
  43. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/system.py +0 -0
  44. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/tool_docs.py +0 -0
  45. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/session_cost.py +0 -0
  46. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/sessions/__init__.py +0 -0
  47. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/sessions/jsonl.py +0 -0
  48. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/sessions/manager.py +0 -0
  49. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/skill_market.py +0 -0
  50. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/skills.py +0 -0
  51. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/status.py +0 -0
  52. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/todos.py +0 -0
  53. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/__init__.py +0 -0
  54. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/builtin.py +0 -0
  55. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/file_state.py +0 -0
  56. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/result.py +0 -0
  57. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/search.py +0 -0
  58. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/shell_output.py +0 -0
  59. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/shell_utils.py +0 -0
  60. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/__init__.py +0 -0
  61. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/app.py +0 -0
  62. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/commands.py +0 -0
  63. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/compat.py +0 -0
  64. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/diff.py +0 -0
  65. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/runner.py +0 -0
  66. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/screens.py +0 -0
  67. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/state.py +0 -0
  68. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/widgets.py +0 -0
  69. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/types/__init__.py +0 -0
  70. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/types/sdk.py +0 -0
  71. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/types/tool_payloads.py +0 -0
  72. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/__init__.py +0 -0
  73. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/app.py +0 -0
  74. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/ask_user_question.py +0 -0
  75. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/exit_summary.py +0 -0
  76. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/file_mentions.py +0 -0
  77. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/loading_text.py +0 -0
  78. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/local_command.py +0 -0
  79. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/markdown.py +0 -0
  80. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/message_view.py +0 -0
  81. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/model_picker.py +0 -0
  82. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/prompt_buffer.py +0 -0
  83. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/prompt_input.py +0 -0
  84. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/session_list.py +0 -0
  85. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/session_picker.py +0 -0
  86. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/skill_picker.py +0 -0
  87. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/slash_commands.py +0 -0
  88. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/status_footer.py +0 -0
  89. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/styles.py +0 -0
  90. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/terminal.py +0 -0
  91. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/theme_picker.py +0 -0
  92. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/thinking_state.py +0 -0
  93. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/welcome.py +0 -0
  94. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/update_check.py +0 -0
  95. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/usage.py +0 -0
  96. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/__init__.py +0 -0
  97. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/debug_logger.py +0 -0
  98. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/error_logger.py +0 -0
  99. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/json.py +0 -0
  100. {deepy_cli-0.2.14 → deepy_cli-0.2.15}/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.14
3
+ Version: 0.2.15
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.2.14"
3
+ version = "0.2.15"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.2.14"
3
+ __version__ = "0.2.15"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -27,7 +27,7 @@ DEFAULT_PROVIDER = "deepseek"
27
27
  DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
28
28
  DEFAULT_XIAOMI_BASE_URL = "https://api.xiaomimimo.com/v1"
29
29
  DEEPSEEK_REASONING_EFFORTS = {"high", "max"}
30
- SWITCH_ONLY_REASONING_EFFORTS = {"high", "none"}
30
+ SWITCH_ONLY_REASONING_EFFORTS = {"enabled", "none"}
31
31
  OPENROUTER_REASONING_MODES = (
32
32
  "enabled",
33
33
  "disabled",
@@ -245,9 +245,11 @@ def normalize_reasoning_effort(
245
245
  return value
246
246
  return provider_info.default_thinking_mode
247
247
  if provider_info.thinking_modes == SWITCH_ONLY_THINKING_MODES:
248
- if thinking is False or value == "none":
248
+ if thinking is False or value in {"none", "disabled"}:
249
249
  return "none"
250
- return "high"
250
+ if thinking is True or value == "enabled":
251
+ return "enabled"
252
+ return provider_info.default_thinking_mode
251
253
  if value in DEEPSEEK_REASONING_EFFORTS:
252
254
  return value
253
255
  return provider_info.default_thinking_mode
@@ -271,7 +273,7 @@ def reasoning_effort_for_mode(mode: str, provider: str) -> str:
271
273
  return mode
272
274
  return provider_info_for(provider).default_thinking_mode
273
275
  if provider_info_for(provider).thinking_modes == SWITCH_ONLY_THINKING_MODES:
274
- return "none" if mode == "disabled" else "high"
276
+ return "none" if mode == "disabled" else "enabled"
275
277
  return mode if mode in DEEPSEEK_REASONING_EFFORTS else "max"
276
278
 
277
279
 
@@ -40,8 +40,22 @@ def build_deepy_agent(
40
40
  model_settings=provider.model_settings,
41
41
  tools=build_function_tools(
42
42
  runtime,
43
+ mimo_schema_compatibility=uses_mimo_tool_schema_compatibility(
44
+ settings.model.provider,
45
+ settings.model.name,
46
+ ),
43
47
  preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
44
48
  ),
45
49
  mcp_servers=list(mcp_servers or []),
46
50
  mcp_config={"include_server_in_tool_names": True},
47
51
  )
52
+
53
+
54
+ def uses_mimo_tool_schema_compatibility(provider: str, model: str) -> bool:
55
+ normalized_provider = provider.strip().lower()
56
+ normalized_model = model.strip().lower()
57
+ if normalized_provider == "xiaomi":
58
+ return normalized_model in {"mimo-v2.5", "mimo-v2.5-pro"}
59
+ if normalized_provider == "openrouter":
60
+ return normalized_model in {"xiaomi/mimo-v2.5", "xiaomi/mimo-v2.5-pro"}
61
+ return False
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+ from urllib.parse import urlparse
6
+
7
+ from agents import Model, ModelSettings
8
+ from agents import OpenAIChatCompletionsModel
9
+
10
+ from deepy.config import Settings
11
+
12
+ from .replay import (
13
+ sanitize_chat_completion_stream_event,
14
+ sanitize_model_input_for_chat_completions,
15
+ sanitize_model_response_output,
16
+ )
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ProviderBundle:
21
+ client: object
22
+ model: Model
23
+ model_settings: ModelSettings
24
+
25
+
26
+ class DeepyOpenAIChatCompletionsModel(OpenAIChatCompletionsModel):
27
+ async def get_response(self, *args: Any, **kwargs: Any) -> Any:
28
+ response = await super().get_response(*args, **kwargs)
29
+ response.output = sanitize_model_response_output(response.output)
30
+ return response
31
+
32
+ async def stream_response(self, *args: Any, **kwargs: Any) -> Any:
33
+ async for event in super().stream_response(*args, **kwargs):
34
+ sanitized = sanitize_chat_completion_stream_event(event)
35
+ if sanitized is not None:
36
+ yield sanitized
37
+
38
+ async def _fetch_response(
39
+ self,
40
+ system_instructions: str | None,
41
+ input: Any,
42
+ *args: Any,
43
+ **kwargs: Any,
44
+ ) -> Any:
45
+ response = await super()._fetch_response(
46
+ system_instructions,
47
+ sanitize_model_input_for_chat_completions(input),
48
+ *args,
49
+ **kwargs,
50
+ )
51
+ preserve_openrouter_reasoning_content_alias(
52
+ response,
53
+ str(getattr(self._get_client(), "base_url", "") or ""),
54
+ )
55
+ return response
56
+
57
+
58
+ def preserve_openrouter_reasoning_content_alias(response: Any, base_url: str) -> None:
59
+ if not _is_openrouter_base_url(base_url):
60
+ return
61
+
62
+ choices = getattr(response, "choices", None)
63
+ if not isinstance(choices, list):
64
+ return
65
+
66
+ for choice in choices:
67
+ message = getattr(choice, "message", None)
68
+ if message is None or not getattr(message, "tool_calls", None):
69
+ continue
70
+ reasoning = getattr(message, "reasoning", None)
71
+ if not isinstance(reasoning, str) or not reasoning.strip():
72
+ continue
73
+ existing = getattr(message, "reasoning_content", None)
74
+ if isinstance(existing, str) and existing:
75
+ continue
76
+ setattr(message, "reasoning_content", reasoning)
77
+
78
+
79
+ def should_replay_chat_completion_reasoning_content(context: object) -> bool:
80
+ model = str(getattr(context, "model", "")).lower()
81
+ base_url = str(getattr(context, "base_url", "") or "").rstrip("/").lower()
82
+ if "deepseek" in model:
83
+ return _reasoning_origin_matches(context, "deepseek")
84
+ if _is_direct_xiaomi_mimo(model, base_url):
85
+ return _reasoning_origin_matches(context, "mimo")
86
+ if _is_openrouter_base_url(base_url):
87
+ return _reasoning_origin_matches_model(context, model)
88
+ return False
89
+
90
+
91
+ def should_replay_deepseek_reasoning_content(context: object) -> bool:
92
+ return should_replay_chat_completion_reasoning_content(context)
93
+
94
+
95
+ def _reasoning_origin_matches(context: object, model_fragment: str) -> bool:
96
+ reasoning = getattr(context, "reasoning", None)
97
+ origin_model = getattr(reasoning, "origin_model", None)
98
+ provider_data = getattr(reasoning, "provider_data", {}) or {}
99
+ return (
100
+ isinstance(origin_model, str)
101
+ and model_fragment in origin_model.lower()
102
+ ) or provider_data == {}
103
+
104
+
105
+ def _reasoning_origin_matches_model(context: object, model: str) -> bool:
106
+ reasoning = getattr(context, "reasoning", None)
107
+ origin_model = getattr(reasoning, "origin_model", None)
108
+ provider_data = getattr(reasoning, "provider_data", {}) or {}
109
+ return (
110
+ isinstance(origin_model, str)
111
+ and origin_model.strip().lower() == model
112
+ ) or provider_data == {}
113
+
114
+
115
+ def _is_direct_xiaomi_mimo(model: str, base_url: str) -> bool:
116
+ if "xiaomimimo.com" not in base_url:
117
+ return False
118
+ return model in {"mimo-v2.5", "mimo-v2.5-pro"}
119
+
120
+
121
+ def _is_openrouter_base_url(base_url: str) -> bool:
122
+ parsed = urlparse(base_url)
123
+ host = (parsed.hostname or base_url).rstrip("/").lower()
124
+ return host == "openrouter.ai" or host.endswith(".openrouter.ai")
125
+
126
+
127
+ def build_provider_bundle(settings: Settings) -> ProviderBundle:
128
+ from agents import set_tracing_disabled
129
+ from openai import AsyncOpenAI
130
+
131
+ from .thinking import build_model_settings
132
+
133
+ if not settings.model.api_key:
134
+ raise ValueError(f"Model API key is missing in {settings.path or 'Deepy config'}.")
135
+
136
+ set_tracing_disabled(disabled=True)
137
+ client = AsyncOpenAI(base_url=settings.model.base_url, api_key=settings.model.api_key)
138
+ model = DeepyOpenAIChatCompletionsModel(
139
+ model=settings.model.name,
140
+ openai_client=client,
141
+ should_replay_reasoning_content=should_replay_chat_completion_reasoning_content,
142
+ )
143
+ return ProviderBundle(client=client, model=model, model_settings=build_model_settings(settings))
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ from copy import deepcopy
4
5
  from typing import TYPE_CHECKING, Any
5
6
 
6
7
  from deepy.utils import json as json_utils
@@ -15,10 +16,17 @@ if TYPE_CHECKING:
15
16
  def build_function_tools(
16
17
  runtime: ToolRuntime,
17
18
  *,
19
+ mimo_schema_compatibility: bool = False,
18
20
  preferred_mcp_web_search_tools: list[str] | None = None,
19
21
  ) -> list[Tool]:
20
22
  from agents.tool import FunctionTool
21
23
 
24
+ def make_function_tool(**kwargs: Any) -> FunctionTool:
25
+ tool = FunctionTool(**kwargs)
26
+ if mimo_schema_compatibility:
27
+ tool.params_json_schema = make_mimo_compatible_tool_schema(tool.params_json_schema)
28
+ return tool
29
+
22
30
  async def invoke_shell(_context: object, raw_input: str) -> str:
23
31
  args, error = _tool_args(raw_input, "shell")
24
32
  if error is not None:
@@ -131,7 +139,7 @@ def build_function_tools(
131
139
  )
132
140
 
133
141
  return [
134
- FunctionTool(
142
+ make_function_tool(
135
143
  name="shell",
136
144
  description=(
137
145
  "Execute commands in the current runtime shell. Match the runtime context's "
@@ -141,7 +149,7 @@ def build_function_tools(
141
149
  on_invoke_tool=invoke_shell,
142
150
  strict_json_schema=False,
143
151
  ),
144
- FunctionTool(
152
+ make_function_tool(
145
153
  name="AskUserQuestion",
146
154
  description=(
147
155
  "当用户意图、范围、偏好、实现路线、高影响取舍或必要批准会明显影响结果时,"
@@ -154,7 +162,7 @@ def build_function_tools(
154
162
  on_invoke_tool=invoke_ask_user_question,
155
163
  strict_json_schema=False,
156
164
  ),
157
- FunctionTool(
165
+ make_function_tool(
158
166
  name="Search",
159
167
  description=(
160
168
  "Search local project files without shell grep or rg. Prefer this for repository "
@@ -165,7 +173,7 @@ def build_function_tools(
165
173
  on_invoke_tool=invoke_search,
166
174
  strict_json_schema=True,
167
175
  ),
168
- FunctionTool(
176
+ make_function_tool(
169
177
  name="read_file",
170
178
  description=(
171
179
  "Read a file or directory and record managed text snapshots for later edits. "
@@ -175,7 +183,7 @@ def build_function_tools(
175
183
  on_invoke_tool=invoke_read_file,
176
184
  strict_json_schema=True,
177
185
  ),
178
- FunctionTool(
186
+ make_function_tool(
179
187
  name="edit_text",
180
188
  description=(
181
189
  "Preferred tool for small single-file exact/string edits. Use file_path "
@@ -186,7 +194,7 @@ def build_function_tools(
186
194
  on_invoke_tool=invoke_edit_text,
187
195
  strict_json_schema=True,
188
196
  ),
189
- FunctionTool(
197
+ make_function_tool(
190
198
  name="write_file",
191
199
  description=(
192
200
  "Create a new text file or explicitly replace a whole file. Existing-file "
@@ -196,7 +204,7 @@ def build_function_tools(
196
204
  on_invoke_tool=invoke_write_file,
197
205
  strict_json_schema=True,
198
206
  ),
199
- FunctionTool(
207
+ make_function_tool(
200
208
  name="apply_patch",
201
209
  description=(
202
210
  "Batch structured file operations. Best for multiple edits in one file, "
@@ -208,21 +216,21 @@ def build_function_tools(
208
216
  on_invoke_tool=invoke_apply_patch,
209
217
  strict_json_schema=True,
210
218
  ),
211
- FunctionTool(
219
+ make_function_tool(
212
220
  name="WebSearch",
213
221
  description=web_search_description,
214
222
  params_json_schema=WEB_SEARCH_SCHEMA,
215
223
  on_invoke_tool=invoke_web_search,
216
224
  strict_json_schema=False,
217
225
  ),
218
- FunctionTool(
226
+ make_function_tool(
219
227
  name="WebFetch",
220
228
  description="Fetch and extract readable content from a complete http or https URL.",
221
229
  params_json_schema=WEB_FETCH_SCHEMA,
222
230
  on_invoke_tool=invoke_web_fetch,
223
231
  strict_json_schema=False,
224
232
  ),
225
- FunctionTool(
233
+ make_function_tool(
226
234
  name="load_skill",
227
235
  description=(
228
236
  "Load the complete instructions for an available Agent Skill by name. Use this "
@@ -233,7 +241,7 @@ def build_function_tools(
233
241
  on_invoke_tool=invoke_load_skill,
234
242
  strict_json_schema=False,
235
243
  ),
236
- FunctionTool(
244
+ make_function_tool(
237
245
  name="todo_write",
238
246
  description=(
239
247
  "Create, replace, read, or clear the session todo list for complex multi-step "
@@ -247,6 +255,50 @@ def build_function_tools(
247
255
  ]
248
256
 
249
257
 
258
+ def make_mimo_compatible_tool_schema(schema: dict[str, Any]) -> dict[str, Any]:
259
+ compatible = deepcopy(schema)
260
+ _remove_nullable_required_fields(compatible)
261
+ return compatible
262
+
263
+
264
+ def _remove_nullable_required_fields(value: Any) -> None:
265
+ if isinstance(value, list):
266
+ for item in value:
267
+ _remove_nullable_required_fields(item)
268
+ return
269
+ if not isinstance(value, dict):
270
+ return
271
+
272
+ properties = value.get("properties")
273
+ required = value.get("required")
274
+ if isinstance(properties, dict) and isinstance(required, list):
275
+ value["required"] = [
276
+ field
277
+ for field in required
278
+ if not (
279
+ isinstance(field, str)
280
+ and isinstance(properties.get(field), dict)
281
+ and _schema_type_allows_null(properties[field])
282
+ )
283
+ ]
284
+
285
+ if _schema_type_allows_null(value):
286
+ schema_type = value["type"]
287
+ non_null_types = [item for item in schema_type if item != "null"]
288
+ if len(non_null_types) == 1:
289
+ value["type"] = non_null_types[0]
290
+ elif non_null_types:
291
+ value["type"] = non_null_types
292
+
293
+ for item in value.values():
294
+ _remove_nullable_required_fields(item)
295
+
296
+
297
+ def _schema_type_allows_null(schema: dict[str, Any]) -> bool:
298
+ schema_type = schema.get("type")
299
+ return isinstance(schema_type, list) and "null" in schema_type
300
+
301
+
250
302
  def _tool_args(raw_input: str, tool_name: str) -> tuple[dict[str, Any], str | None]:
251
303
  try:
252
304
  parsed = json_utils.loads(raw_input or "{}")
@@ -1,82 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import Any
5
-
6
- from agents import Model, ModelSettings
7
- from agents import OpenAIChatCompletionsModel
8
-
9
- from deepy.config import Settings
10
-
11
- from .replay import (
12
- sanitize_chat_completion_stream_event,
13
- sanitize_model_input_for_chat_completions,
14
- sanitize_model_response_output,
15
- )
16
-
17
-
18
- @dataclass(frozen=True)
19
- class ProviderBundle:
20
- client: object
21
- model: Model
22
- model_settings: ModelSettings
23
-
24
-
25
- class DeepyOpenAIChatCompletionsModel(OpenAIChatCompletionsModel):
26
- async def get_response(self, *args: Any, **kwargs: Any) -> Any:
27
- response = await super().get_response(*args, **kwargs)
28
- response.output = sanitize_model_response_output(response.output)
29
- return response
30
-
31
- async def stream_response(self, *args: Any, **kwargs: Any) -> Any:
32
- async for event in super().stream_response(*args, **kwargs):
33
- sanitized = sanitize_chat_completion_stream_event(event)
34
- if sanitized is not None:
35
- yield sanitized
36
-
37
- async def _fetch_response(
38
- self,
39
- system_instructions: str | None,
40
- input: Any,
41
- *args: Any,
42
- **kwargs: Any,
43
- ) -> Any:
44
- return await super()._fetch_response(
45
- system_instructions,
46
- sanitize_model_input_for_chat_completions(input),
47
- *args,
48
- **kwargs,
49
- )
50
-
51
-
52
- def should_replay_deepseek_reasoning_content(context: object) -> bool:
53
- model = str(getattr(context, "model", "")).lower()
54
- if "deepseek" not in model:
55
- return False
56
-
57
- reasoning = getattr(context, "reasoning", None)
58
- origin_model = getattr(reasoning, "origin_model", None)
59
- provider_data = getattr(reasoning, "provider_data", {}) or {}
60
- return (
61
- isinstance(origin_model, str)
62
- and "deepseek" in origin_model.lower()
63
- ) or provider_data == {}
64
-
65
-
66
- def build_provider_bundle(settings: Settings) -> ProviderBundle:
67
- from agents import set_tracing_disabled
68
- from openai import AsyncOpenAI
69
-
70
- from .thinking import build_model_settings
71
-
72
- if not settings.model.api_key:
73
- raise ValueError(f"Model API key is missing in {settings.path or 'Deepy config'}.")
74
-
75
- set_tracing_disabled(disabled=True)
76
- client = AsyncOpenAI(base_url=settings.model.base_url, api_key=settings.model.api_key)
77
- model = DeepyOpenAIChatCompletionsModel(
78
- model=settings.model.name,
79
- openai_client=client,
80
- should_replay_reasoning_content=should_replay_deepseek_reasoning_content,
81
- )
82
- return ProviderBundle(client=client, model=model, model_settings=build_model_settings(settings))
File without changes
File without changes
File without changes