deepy-cli 0.2.0__tar.gz → 0.2.1__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.0 → deepy_cli-0.2.1}/PKG-INFO +3 -3
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/README.md +2 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/pyproject.toml +2 -9
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/cli.py +3 -3
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/agent.py +5 -1
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/context.py +8 -8
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/provider.py +3 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/replay.py +2 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/runner.py +2 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/mcp.py +8 -3
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/skills.py +1 -1
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/agents.py +5 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/builtin.py +44 -42
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/result.py +1 -1
- deepy_cli-0.2.1/src/deepy/types/__init__.py +2 -0
- deepy_cli-0.2.1/src/deepy/types/sdk.py +12 -0
- deepy_cli-0.2.1/src/deepy/types/tool_payloads.py +15 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/exit_summary.py +2 -1
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/local_command.py +9 -3
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/message_view.py +27 -15
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/prompt_input.py +7 -6
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/skill_picker.py +150 -1
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/terminal.py +76 -23
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/welcome.py +2 -1
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/utils/json.py +5 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/utils/notify.py +2 -2
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/config/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/config/settings.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/edit.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/modify.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/read.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/data/tools/write.md +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/sessions/jsonl.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/slash_commands.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.0 → deepy_cli-0.2.1}/src/deepy/utils/error_logger.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.1
|
|
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
|
|
@@ -342,7 +342,7 @@ A concise `AGENTS.md` works best:
|
|
|
342
342
|
## Commands
|
|
343
343
|
- Test: `uv run pytest`
|
|
344
344
|
- Lint: `uv run ruff check`
|
|
345
|
-
- Type check: `uv run
|
|
345
|
+
- Type check: `uv run ty check src`
|
|
346
346
|
|
|
347
347
|
## Architecture
|
|
348
348
|
- Keep CLI entry points thin; put reusable behavior under `src/`.
|
|
@@ -369,7 +369,7 @@ create or refresh the project root `AGENTS.md`.
|
|
|
369
369
|
uv sync --group dev
|
|
370
370
|
uv run pytest
|
|
371
371
|
uv run ruff check
|
|
372
|
-
uv run
|
|
372
|
+
uv run ty check src
|
|
373
373
|
uv build
|
|
374
374
|
```
|
|
375
375
|
|
|
@@ -313,7 +313,7 @@ A concise `AGENTS.md` works best:
|
|
|
313
313
|
## Commands
|
|
314
314
|
- Test: `uv run pytest`
|
|
315
315
|
- Lint: `uv run ruff check`
|
|
316
|
-
- Type check: `uv run
|
|
316
|
+
- Type check: `uv run ty check src`
|
|
317
317
|
|
|
318
318
|
## Architecture
|
|
319
319
|
- Keep CLI entry points thin; put reusable behavior under `src/`.
|
|
@@ -340,7 +340,7 @@ create or refresh the project root `AGENTS.md`.
|
|
|
340
340
|
uv sync --group dev
|
|
341
341
|
uv run pytest
|
|
342
342
|
uv run ruff check
|
|
343
|
-
uv run
|
|
343
|
+
uv run ty check src
|
|
344
344
|
uv build
|
|
345
345
|
```
|
|
346
346
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deepy-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "Deepy - Vibe coding for DeepSeek models in your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -39,10 +39,10 @@ Issues = "https://github.com/kirineko/deepy/issues"
|
|
|
39
39
|
|
|
40
40
|
[dependency-groups]
|
|
41
41
|
dev = [
|
|
42
|
-
"pyright>=1.1.407",
|
|
43
42
|
"pytest>=8.0",
|
|
44
43
|
"pytest-asyncio>=0.24",
|
|
45
44
|
"ruff>=0.14",
|
|
45
|
+
"ty>=0.0.32",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
[tool.pytest.ini_options]
|
|
@@ -54,13 +54,6 @@ target-version = "py312"
|
|
|
54
54
|
src = ["src", "tests"]
|
|
55
55
|
exclude = ["dist", "reference", "spec"]
|
|
56
56
|
|
|
57
|
-
[tool.pyright]
|
|
58
|
-
pythonVersion = "3.12"
|
|
59
|
-
typeCheckingMode = "off"
|
|
60
|
-
include = ["src", "tests"]
|
|
61
|
-
exclude = ["dist", "reference", "spec"]
|
|
62
|
-
reportMissingTypeStubs = false
|
|
63
|
-
|
|
64
57
|
[tool.uv.build-backend]
|
|
65
58
|
module-name = "deepy"
|
|
66
59
|
|
|
@@ -4,7 +4,7 @@ import argparse
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Sequence
|
|
7
|
+
from typing import Any, Sequence
|
|
8
8
|
|
|
9
9
|
import tomli_w
|
|
10
10
|
|
|
@@ -198,7 +198,7 @@ def _cmd_config_theme(args: argparse.Namespace) -> int:
|
|
|
198
198
|
return 0
|
|
199
199
|
|
|
200
200
|
|
|
201
|
-
def _doctor(args: argparse.Namespace) -> tuple[int, dict[str,
|
|
201
|
+
def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, Any]]:
|
|
202
202
|
settings = load_settings(args.config)
|
|
203
203
|
checks: list[dict[str, object]] = []
|
|
204
204
|
|
|
@@ -251,7 +251,7 @@ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, object]]:
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
|
|
254
|
-
async def _doctor_live(settings: Settings) -> dict[str,
|
|
254
|
+
async def _doctor_live(settings: Settings) -> dict[str, Any]:
|
|
255
255
|
from agents import Agent, RunConfig, Runner
|
|
256
256
|
|
|
257
257
|
provider = build_provider_bundle(settings)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from deepy.config import Settings
|
|
6
7
|
from deepy.prompts import build_system_prompt
|
|
@@ -10,6 +11,9 @@ from deepy.tools.agents import build_function_tools
|
|
|
10
11
|
|
|
11
12
|
from .provider import ProviderBundle, build_provider_bundle
|
|
12
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from agents.mcp import MCPServer
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
def build_deepy_agent(
|
|
15
19
|
settings: Settings,
|
|
@@ -18,7 +22,7 @@ def build_deepy_agent(
|
|
|
18
22
|
project_root: Path,
|
|
19
23
|
provider: ProviderBundle | None = None,
|
|
20
24
|
loaded_skills: list[SkillInfo] | None = None,
|
|
21
|
-
mcp_servers: list[
|
|
25
|
+
mcp_servers: list[MCPServer] | None = None,
|
|
22
26
|
preferred_mcp_web_search_tools: list[str] | None = None,
|
|
23
27
|
):
|
|
24
28
|
from agents import Agent
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Callable
|
|
4
3
|
from math import ceil
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
7
6
|
from deepy.config import Settings
|
|
7
|
+
from deepy.types.sdk import SessionInputCallback
|
|
8
8
|
from deepy.utils import json as json_utils
|
|
9
9
|
|
|
10
|
+
tiktoken: Any | None
|
|
10
11
|
try:
|
|
11
|
-
import tiktoken
|
|
12
|
+
import tiktoken as _tiktoken
|
|
12
13
|
except Exception: # pragma: no cover - optional dependency fallback.
|
|
13
|
-
tiktoken = None
|
|
14
|
+
tiktoken = None
|
|
15
|
+
else:
|
|
16
|
+
tiktoken = _tiktoken
|
|
14
17
|
|
|
15
18
|
_ENCODING = None
|
|
16
19
|
|
|
@@ -38,11 +41,8 @@ def estimate_tokens_for_items(items: list[dict[str, Any]]) -> int:
|
|
|
38
41
|
return sum(estimate_tokens_for_item(item) for item in items)
|
|
39
42
|
|
|
40
43
|
|
|
41
|
-
def build_session_input_callback(settings: Settings) ->
|
|
42
|
-
|
|
43
|
-
list[dict[str, Any]],
|
|
44
|
-
]:
|
|
45
|
-
def callback(history: list[dict[str, Any]], new_input: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
44
|
+
def build_session_input_callback(settings: Settings) -> SessionInputCallback:
|
|
45
|
+
def callback(history: list[Any], new_input: list[Any]) -> list[Any]:
|
|
46
46
|
return [*history, *new_input]
|
|
47
47
|
|
|
48
48
|
return callback
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
+
from agents import Model, ModelSettings
|
|
6
7
|
from agents import OpenAIChatCompletionsModel
|
|
7
8
|
|
|
8
9
|
from deepy.config import Settings
|
|
@@ -17,8 +18,8 @@ from .replay import (
|
|
|
17
18
|
@dataclass(frozen=True)
|
|
18
19
|
class ProviderBundle:
|
|
19
20
|
client: object
|
|
20
|
-
model:
|
|
21
|
-
model_settings:
|
|
21
|
+
model: Model
|
|
22
|
+
model_settings: ModelSettings
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class DeepyOpenAIChatCompletionsModel(OpenAIChatCompletionsModel):
|
|
@@ -105,9 +105,9 @@ def sanitize_chat_completion_stream_event(event: Any) -> Any | None:
|
|
|
105
105
|
if getattr(event, "type", None) == "response.completed":
|
|
106
106
|
response = getattr(event, "response", None)
|
|
107
107
|
output = getattr(response, "output", None)
|
|
108
|
-
if isinstance(output, list):
|
|
108
|
+
if response is not None and isinstance(output, list):
|
|
109
109
|
try:
|
|
110
|
-
response.output = sanitize_model_response_output(output)
|
|
110
|
+
cast(Any, response).output = sanitize_model_response_output(output)
|
|
111
111
|
except Exception:
|
|
112
112
|
pass
|
|
113
113
|
return event
|
|
@@ -130,7 +130,7 @@ async def run_prompt_once(
|
|
|
130
130
|
input=prompt,
|
|
131
131
|
max_turns=max_turns,
|
|
132
132
|
run_config=run_config,
|
|
133
|
-
session=session,
|
|
133
|
+
session=session, # ty: ignore[invalid-argument-type] - DeepyJsonlSession matches the SDK Session protocol at runtime.
|
|
134
134
|
)
|
|
135
135
|
if should_interrupt is not None:
|
|
136
136
|
interrupt_task = asyncio.create_task(
|
|
@@ -349,7 +349,7 @@ def _max_turns_output(chunks: list[str], *, max_turns: int) -> str:
|
|
|
349
349
|
|
|
350
350
|
def format_deepseek_api_error(error: Any) -> str:
|
|
351
351
|
status_code = _safe_int(getattr(error, "status_code", None))
|
|
352
|
-
status = DEEPSEEK_ERROR_CODES.get(status_code)
|
|
352
|
+
status = DEEPSEEK_ERROR_CODES.get(status_code) if status_code is not None else None
|
|
353
353
|
title = f"DeepSeek API error {status_code}" if status_code is not None else "DeepSeek API error"
|
|
354
354
|
if status is not None:
|
|
355
355
|
title = f"{title}: {status.title}"
|
|
@@ -155,7 +155,12 @@ class DeepyMcpRuntime:
|
|
|
155
155
|
await self._manager.cleanup_all()
|
|
156
156
|
|
|
157
157
|
def _build_sdk_servers(self) -> list[Any]:
|
|
158
|
-
from agents.mcp import
|
|
158
|
+
from agents.mcp import (
|
|
159
|
+
MCPServerStdio,
|
|
160
|
+
MCPServerStdioParams,
|
|
161
|
+
MCPServerStreamableHttp,
|
|
162
|
+
MCPServerStreamableHttpParams,
|
|
163
|
+
)
|
|
159
164
|
|
|
160
165
|
servers: list[Any] = []
|
|
161
166
|
for definition in self.definitions:
|
|
@@ -173,7 +178,7 @@ class DeepyMcpRuntime:
|
|
|
173
178
|
)
|
|
174
179
|
continue
|
|
175
180
|
if definition.transport == "stdio":
|
|
176
|
-
params:
|
|
181
|
+
params: MCPServerStdioParams = {"command": definition.command or ""}
|
|
177
182
|
if definition.args:
|
|
178
183
|
params["args"] = list(definition.args)
|
|
179
184
|
if definition.env:
|
|
@@ -189,7 +194,7 @@ class DeepyMcpRuntime:
|
|
|
189
194
|
),
|
|
190
195
|
)
|
|
191
196
|
else:
|
|
192
|
-
params = {"url": definition.url or ""}
|
|
197
|
+
params: MCPServerStreamableHttpParams = {"url": definition.url or ""}
|
|
193
198
|
if definition.headers:
|
|
194
199
|
params["headers"] = dict(definition.headers)
|
|
195
200
|
server = MCPServerStreamableHttp(
|
|
@@ -116,7 +116,7 @@ def _format_skills(skills: Iterable[SkillInfo], *, include_paths: bool) -> str:
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def _builtin_skills_root() -> Path:
|
|
119
|
-
return Path(resources.files("deepy.data").joinpath("skills"))
|
|
119
|
+
return Path(str(resources.files("deepy.data").joinpath("skills")))
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def _discover_skills_root(root: Path, *, scope: str) -> list[SkillInfo]:
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
5
|
from deepy.utils import json as json_utils
|
|
6
6
|
|
|
7
7
|
from .builtin import ToolRuntime
|
|
8
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from agents import Tool
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
def build_function_tools(
|
|
11
14
|
runtime: ToolRuntime,
|
|
12
15
|
*,
|
|
13
16
|
preferred_mcp_web_search_tools: list[str] | None = None,
|
|
14
|
-
) -> list[
|
|
17
|
+
) -> list[Tool]:
|
|
15
18
|
from agents.tool import FunctionTool
|
|
16
19
|
|
|
17
20
|
async def invoke_shell(_context: object, raw_input: str) -> str:
|
|
@@ -22,6 +22,7 @@ from html.parser import HTMLParser
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
24
|
from deepy.config import DEFAULT_WEB_SEARCH_SEARXNG_URL, Settings, mask_secret
|
|
25
|
+
from deepy.types.tool_payloads import AskUserOption, AskUserQuestion
|
|
25
26
|
from deepy.utils import json as json_utils
|
|
26
27
|
|
|
27
28
|
from .file_state import FileSnippet, FileState
|
|
@@ -728,7 +729,7 @@ def _decide_search_language_with_llm(query: str, settings: Settings) -> dict[str
|
|
|
728
729
|
)
|
|
729
730
|
parsed = _parse_json_response(_web_search_chat(settings, prompt))
|
|
730
731
|
dominant_language = parsed.get("dominant_language")
|
|
731
|
-
if dominant_language not in {"en", "zh"}:
|
|
732
|
+
if not isinstance(dominant_language, str) or dominant_language not in {"en", "zh"}:
|
|
732
733
|
raise ValueError(f"Unexpected dominant language: {dominant_language}")
|
|
733
734
|
reason = parsed.get("reason")
|
|
734
735
|
return {
|
|
@@ -1249,7 +1250,7 @@ class ToolRuntime:
|
|
|
1249
1250
|
)
|
|
1250
1251
|
self.file_state.mark_read(target, full=False)
|
|
1251
1252
|
snippet_metadata = _snippet_metadata(snippet)
|
|
1252
|
-
metadata = {
|
|
1253
|
+
metadata: dict[str, object] = {
|
|
1253
1254
|
"path": str(target),
|
|
1254
1255
|
"kind": "file",
|
|
1255
1256
|
"startLine": start + 1,
|
|
@@ -1322,7 +1323,7 @@ class ToolRuntime:
|
|
|
1322
1323
|
encoding = (
|
|
1323
1324
|
existing_metadata.encoding
|
|
1324
1325
|
if existing_metadata is not None
|
|
1325
|
-
else _default_new_text_encoding(
|
|
1326
|
+
else _default_new_text_encoding()
|
|
1326
1327
|
)
|
|
1327
1328
|
line_endings = _detect_line_endings(old_content or text_content)
|
|
1328
1329
|
normalized_content = _normalize_line_endings(text_content, line_endings)
|
|
@@ -1453,7 +1454,7 @@ class ToolRuntime:
|
|
|
1453
1454
|
_write_text_with_encoding(target, updated, text_metadata.encoding)
|
|
1454
1455
|
self.file_state.mark_written(target)
|
|
1455
1456
|
diff = _unified_diff(text, updated, path=str(target))
|
|
1456
|
-
metadata = {
|
|
1457
|
+
metadata: dict[str, object] = {
|
|
1457
1458
|
"path": str(target),
|
|
1458
1459
|
"file_path": str(target),
|
|
1459
1460
|
"occurrences": occurrences if replace_all else 1,
|
|
@@ -1542,7 +1543,6 @@ class ToolRuntime:
|
|
|
1542
1543
|
self.cwd = final_cwd
|
|
1543
1544
|
returncode = exit_code if exit_code is not None else process.returncode
|
|
1544
1545
|
output, output_truncated = _truncate_output(stdout + (stderr or ""))
|
|
1545
|
-
result = ToolResult.ok_result if returncode == 0 else ToolResult.error_result
|
|
1546
1546
|
metadata = _shell_metadata(
|
|
1547
1547
|
self.cwd,
|
|
1548
1548
|
process_id,
|
|
@@ -1558,12 +1558,12 @@ class ToolRuntime:
|
|
|
1558
1558
|
}
|
|
1559
1559
|
)
|
|
1560
1560
|
if returncode == 0:
|
|
1561
|
-
return
|
|
1561
|
+
return ToolResult.ok_result(
|
|
1562
1562
|
name,
|
|
1563
1563
|
output,
|
|
1564
1564
|
metadata=metadata,
|
|
1565
1565
|
).to_json()
|
|
1566
|
-
return
|
|
1566
|
+
return ToolResult.error_result(
|
|
1567
1567
|
name,
|
|
1568
1568
|
f"Command exited with code {returncode}.",
|
|
1569
1569
|
output=output,
|
|
@@ -1916,17 +1916,10 @@ def _python_text_encoding(encoding: str) -> str:
|
|
|
1916
1916
|
return "utf8"
|
|
1917
1917
|
|
|
1918
1918
|
|
|
1919
|
-
def _default_new_text_encoding(
|
|
1920
|
-
resolved_platform = platform_name or sys.platform
|
|
1921
|
-
if resolved_platform.startswith("win") and _contains_non_ascii(content):
|
|
1922
|
-
return "utf8-sig"
|
|
1919
|
+
def _default_new_text_encoding() -> str:
|
|
1923
1920
|
return "utf8"
|
|
1924
1921
|
|
|
1925
1922
|
|
|
1926
|
-
def _contains_non_ascii(text: str) -> bool:
|
|
1927
|
-
return any(ord(char) > 0x7F for char in text)
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
1923
|
def _write_text_with_encoding(path: Path, content: str, encoding: str) -> None:
|
|
1931
1924
|
path.write_bytes(content.encode(_python_text_encoding(encoding)))
|
|
1932
1925
|
|
|
@@ -1974,24 +1967,24 @@ def _format_notebook(path: Path) -> tuple[str, str | None]:
|
|
|
1974
1967
|
cells = parsed.get("cells")
|
|
1975
1968
|
lines: list[str] = []
|
|
1976
1969
|
if isinstance(cells, list):
|
|
1977
|
-
for index,
|
|
1978
|
-
|
|
1970
|
+
for index, raw_cell in enumerate(cells):
|
|
1971
|
+
cell = _string_key_dict(raw_cell)
|
|
1972
|
+
if cell is None:
|
|
1979
1973
|
continue
|
|
1980
|
-
|
|
1974
|
+
raw_cell_type = cell.get("cell_type")
|
|
1975
|
+
cell_type = raw_cell_type if isinstance(raw_cell_type, str) else "unknown"
|
|
1981
1976
|
lines.append(f"# Cell {index + 1} ({cell_type})")
|
|
1982
1977
|
lines.extend(_normalize_notebook_field(cell.get("source")))
|
|
1983
1978
|
|
|
1984
1979
|
outputs = cell.get("outputs")
|
|
1985
1980
|
if not isinstance(outputs, list):
|
|
1986
1981
|
continue
|
|
1987
|
-
for output_index,
|
|
1988
|
-
|
|
1982
|
+
for output_index, raw_output in enumerate(outputs):
|
|
1983
|
+
output = _string_key_dict(raw_output)
|
|
1984
|
+
if output is None:
|
|
1989
1985
|
continue
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
if isinstance(output.get("output_type"), str)
|
|
1993
|
-
else "output"
|
|
1994
|
-
)
|
|
1986
|
+
raw_output_type = output.get("output_type")
|
|
1987
|
+
output_type = raw_output_type if isinstance(raw_output_type, str) else "output"
|
|
1995
1988
|
lines.append(f"# Output {output_index + 1} ({output_type})")
|
|
1996
1989
|
lines.extend(_format_notebook_output(output))
|
|
1997
1990
|
|
|
@@ -2011,12 +2004,13 @@ def _normalize_notebook_field(value: object) -> list[str]:
|
|
|
2011
2004
|
def _format_notebook_output(output: dict[str, object]) -> list[str]:
|
|
2012
2005
|
lines = _normalize_notebook_field(output.get("text"))
|
|
2013
2006
|
data = output.get("data")
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2007
|
+
data_dict = _string_key_dict(data)
|
|
2008
|
+
if data_dict is not None:
|
|
2009
|
+
lines.extend(_normalize_notebook_field(data_dict.get("text/plain")))
|
|
2010
|
+
image_png = data_dict.get("image/png")
|
|
2017
2011
|
if isinstance(image_png, str):
|
|
2018
2012
|
lines.append(f"[image/png {len(image_png)} chars]")
|
|
2019
|
-
image_jpeg =
|
|
2013
|
+
image_jpeg = data_dict.get("image/jpeg")
|
|
2020
2014
|
if isinstance(image_jpeg, str):
|
|
2021
2015
|
lines.append(f"[image/jpeg {len(image_jpeg)} chars]")
|
|
2022
2016
|
traceback = output.get("traceback")
|
|
@@ -2025,6 +2019,14 @@ def _format_notebook_output(output: dict[str, object]) -> list[str]:
|
|
|
2025
2019
|
return lines or ["[output omitted]"]
|
|
2026
2020
|
|
|
2027
2021
|
|
|
2022
|
+
def _string_key_dict(value: object) -> dict[str, object] | None:
|
|
2023
|
+
if not isinstance(value, dict):
|
|
2024
|
+
return None
|
|
2025
|
+
if not all(isinstance(key, str) for key in value):
|
|
2026
|
+
return None
|
|
2027
|
+
return {key: item for key, item in value.items() if isinstance(key, str)}
|
|
2028
|
+
|
|
2029
|
+
|
|
2028
2030
|
@dataclass(frozen=True)
|
|
2029
2031
|
class PageRange:
|
|
2030
2032
|
start: int
|
|
@@ -2360,7 +2362,7 @@ def _now_iso() -> str:
|
|
|
2360
2362
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
2361
2363
|
|
|
2362
2364
|
|
|
2363
|
-
def _terminate_process(process: subprocess.Popen[
|
|
2365
|
+
def _terminate_process(process: subprocess.Popen[bytes]) -> None:
|
|
2364
2366
|
try:
|
|
2365
2367
|
if os.name != "nt":
|
|
2366
2368
|
os.killpg(process.pid, signal.SIGKILL)
|
|
@@ -2483,13 +2485,14 @@ def _gitignore_pattern_matches(pattern: str, relative_path: str, is_dir: bool) -
|
|
|
2483
2485
|
return any(fnmatch(part, normalized_pattern) for part in parts)
|
|
2484
2486
|
|
|
2485
2487
|
|
|
2486
|
-
def _parse_ask_user_questions(value: object) -> tuple[list[
|
|
2488
|
+
def _parse_ask_user_questions(value: object) -> tuple[list[AskUserQuestion], str | None]:
|
|
2487
2489
|
if not isinstance(value, list) or not value:
|
|
2488
2490
|
return [], '"questions" must be a non-empty array.'
|
|
2489
2491
|
|
|
2490
|
-
questions: list[
|
|
2491
|
-
for index,
|
|
2492
|
-
|
|
2492
|
+
questions: list[AskUserQuestion] = []
|
|
2493
|
+
for index, raw_item in enumerate(value):
|
|
2494
|
+
item = _string_key_dict(raw_item)
|
|
2495
|
+
if item is None:
|
|
2493
2496
|
return [], f"Question at index {index} must be an object."
|
|
2494
2497
|
|
|
2495
2498
|
question = _trimmed_string(item.get("question"))
|
|
@@ -2500,9 +2503,10 @@ def _parse_ask_user_questions(value: object) -> tuple[list[dict[str, object]], s
|
|
|
2500
2503
|
if not isinstance(raw_options, list) or not raw_options:
|
|
2501
2504
|
return [], f'Question at index {index} must include a non-empty "options" array.'
|
|
2502
2505
|
|
|
2503
|
-
options: list[
|
|
2504
|
-
for option_index,
|
|
2505
|
-
|
|
2506
|
+
options: list[AskUserOption] = []
|
|
2507
|
+
for option_index, raw_option in enumerate(raw_options):
|
|
2508
|
+
option = _string_key_dict(raw_option)
|
|
2509
|
+
if option is None:
|
|
2506
2510
|
return [], f"Option {option_index} for question {index} must be an object."
|
|
2507
2511
|
|
|
2508
2512
|
label = _trimmed_string(option.get("label"))
|
|
@@ -2512,13 +2516,13 @@ def _parse_ask_user_questions(value: object) -> tuple[list[dict[str, object]], s
|
|
|
2512
2516
|
f'Option {option_index} for question {index} is missing a non-empty "label" string.',
|
|
2513
2517
|
)
|
|
2514
2518
|
|
|
2515
|
-
parsed_option = {"label": label}
|
|
2519
|
+
parsed_option: AskUserOption = {"label": label}
|
|
2516
2520
|
description = _trimmed_string(option.get("description"))
|
|
2517
2521
|
if description:
|
|
2518
2522
|
parsed_option["description"] = description
|
|
2519
2523
|
options.append(parsed_option)
|
|
2520
2524
|
|
|
2521
|
-
parsed_question:
|
|
2525
|
+
parsed_question: AskUserQuestion = {
|
|
2522
2526
|
"question": question,
|
|
2523
2527
|
"options": options,
|
|
2524
2528
|
}
|
|
@@ -2530,15 +2534,13 @@ def _parse_ask_user_questions(value: object) -> tuple[list[dict[str, object]], s
|
|
|
2530
2534
|
return questions, None
|
|
2531
2535
|
|
|
2532
2536
|
|
|
2533
|
-
def _build_question_summary(questions: list[
|
|
2537
|
+
def _build_question_summary(questions: list[AskUserQuestion]) -> str:
|
|
2534
2538
|
lines = ["Waiting for user input."]
|
|
2535
2539
|
for index, item in enumerate(questions):
|
|
2536
2540
|
lines.append("")
|
|
2537
2541
|
lines.append(f"{index + 1}. {item['question']}")
|
|
2538
2542
|
lines.append(f" Mode: {'multi-select' if item.get('multiSelect') else 'single-select'}")
|
|
2539
2543
|
for option in item["options"]:
|
|
2540
|
-
if not isinstance(option, dict):
|
|
2541
|
-
continue
|
|
2542
2544
|
lines.append(f" - {option['label']}")
|
|
2543
2545
|
if option.get("description"):
|
|
2544
2546
|
lines.append(f" {option['description']}")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AskUserOption(TypedDict):
|
|
7
|
+
label: str
|
|
8
|
+
description: NotRequired[str]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AskUserQuestion(TypedDict):
|
|
12
|
+
question: str
|
|
13
|
+
options: list[AskUserOption]
|
|
14
|
+
multiSelect: NotRequired[bool]
|
|
15
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from collections.abc import Sequence
|
|
4
5
|
from typing import Any, Mapping
|
|
5
6
|
|
|
6
7
|
|
|
@@ -55,7 +56,7 @@ def extract_usage_fields(usage: Any) -> UsageFields:
|
|
|
55
56
|
def build_exit_summary_text(
|
|
56
57
|
*,
|
|
57
58
|
session: Any | None = None,
|
|
58
|
-
messages:
|
|
59
|
+
messages: Sequence[Mapping[str, Any]] | None = None,
|
|
59
60
|
model: str | None = None,
|
|
60
61
|
) -> str:
|
|
61
62
|
usage = extract_usage_fields(_get_usage(session))
|
|
@@ -22,10 +22,13 @@ from deepy.tools.shell_output import decode_shell_output_bytes
|
|
|
22
22
|
from deepy.tools.shell_utils import RuntimeEnvironment, detect_runtime_environment
|
|
23
23
|
from deepy.utils import json as json_utils
|
|
24
24
|
|
|
25
|
+
pty: Any | None
|
|
25
26
|
try:
|
|
26
|
-
import pty
|
|
27
|
+
import pty as _pty
|
|
27
28
|
except ImportError: # pragma: no cover - exercised on Windows.
|
|
28
|
-
pty = None
|
|
29
|
+
pty = None
|
|
30
|
+
else:
|
|
31
|
+
pty = _pty
|
|
29
32
|
|
|
30
33
|
DEFAULT_LOCAL_COMMAND_TIMEOUT_MS = 120_000
|
|
31
34
|
DEFAULT_DISPLAY_OUTPUT_LIMIT = 30_000
|
|
@@ -389,9 +392,12 @@ def _read_pipe_output(
|
|
|
389
392
|
stream = process.stdout
|
|
390
393
|
if stream is None:
|
|
391
394
|
return
|
|
395
|
+
read1 = getattr(stream, "read1", None)
|
|
396
|
+
if not callable(read1):
|
|
397
|
+
return
|
|
392
398
|
while True:
|
|
393
399
|
try:
|
|
394
|
-
chunk =
|
|
400
|
+
chunk = read1(4096)
|
|
395
401
|
except Exception:
|
|
396
402
|
return
|
|
397
403
|
if not chunk:
|