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.
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/PKG-INFO +1 -1
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/pyproject.toml +1 -1
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/config/settings.py +6 -4
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/agent.py +14 -0
- deepy_cli-0.2.15/src/deepy/llm/provider.py +143 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/agents.py +63 -11
- deepy_cli-0.2.14/src/deepy/llm/provider.py +0 -82
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/README.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/cli.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/config/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/Search.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/apply_patch.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/edit_text.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/read_file.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/data/tools/write_file.md +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/input_suggestions.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/runner.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/mcp.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/session_cost.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/sessions/jsonl.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/builtin.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/search.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/app.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/commands.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/compat.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/diff.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/runner.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/screens.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/state.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/tui/widgets.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/message_view.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/prompt_input.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/slash_commands.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/status_footer.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/terminal.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/ui/welcome.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.14 → deepy_cli-0.2.15}/src/deepy/utils/notify.py +0 -0
|
@@ -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 = {"
|
|
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
|
|
248
|
+
if thinking is False or value in {"none", "disabled"}:
|
|
249
249
|
return "none"
|
|
250
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|