Replace repository with DuckLM runtime

This commit is contained in:
2026-05-20 01:00:28 +08:00
parent ddc285b8f4
commit 4a84ada770
190 changed files with 7060 additions and 13602 deletions
+1
View File
@@ -0,0 +1 @@
+18
View File
@@ -0,0 +1,18 @@
from typing import Any, Protocol
from pydantic import BaseModel, Field
class ToolResult(BaseModel):
ok: bool
output: str | None = None
error: str | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
class Tool(Protocol):
name: str
risk_level: str
async def run(self, args: dict[str, Any]) -> ToolResult:
...
+36
View File
@@ -0,0 +1,36 @@
from pathlib import Path
from typing import Any
from duck_core.tools.base import ToolResult
from duck_core.tools.paths import WorkspacePathError, resolve_workspace_path
class FileReadTool:
name = "file_read"
risk_level = "low"
def __init__(self, workspace: str, max_bytes: int = 1_000_000):
self.workspace = workspace
self.max_bytes = max_bytes
async def run(self, args: dict[str, Any]) -> ToolResult:
raw_path = str(args.get("path", ""))
try:
path = resolve_workspace_path(self.workspace, raw_path)
except WorkspacePathError as exc:
return ToolResult(ok=False, error=str(exc))
if self._requires_approval(path):
return ToolResult(ok=False, error=f"Reading {raw_path} requires explicit approval")
if not path.is_file():
return ToolResult(ok=False, error=f"File not found: {raw_path}")
if path.stat().st_size > self.max_bytes:
return ToolResult(ok=False, error=f"File exceeds max size: {self.max_bytes}")
return ToolResult(
ok=True,
output=path.read_text(errors="replace"),
metadata={"path": str(path), "bytes_read": path.stat().st_size},
)
def _requires_approval(self, path: Path) -> bool:
parts = set(path.parts)
return path.name == ".env" or ".ssh" in parts or str(path) == "/etc/shadow"
+40
View File
@@ -0,0 +1,40 @@
from typing import Any
from duck_core.tools.base import ToolResult
from duck_core.tools.paths import WorkspacePathError, resolve_workspace_path
class FileWriteTool:
name = "file_write"
risk_level = "medium"
def __init__(self, workspace: str):
self.workspace = workspace
async def run(self, args: dict[str, Any]) -> ToolResult:
raw_path = str(args.get("path", ""))
content = str(args.get("content", ""))
overwrite = bool(args.get("overwrite", False))
try:
path = resolve_workspace_path(self.workspace, raw_path)
except WorkspacePathError as exc:
return ToolResult(ok=False, error=str(exc))
if path.exists() and not overwrite:
return ToolResult(
ok=False,
error="Refusing to overwrite existing file without overwrite=true or approval",
metadata={"path": str(path)},
)
path.parent.mkdir(parents=True, exist_ok=True)
existed = path.exists()
path.write_text(content)
return ToolResult(
ok=True,
output=f"Wrote {raw_path}",
metadata={
"path": str(path),
"bytes_written": len(content.encode()),
"created": not existed,
"updated": existed,
},
)
+31
View File
@@ -0,0 +1,31 @@
from typing import Any
from duck_core.tools.base import Tool, ToolResult
from duck_core.tools.file_read import FileReadTool
from duck_core.tools.file_write import FileWriteTool
from duck_core.tools.shell_exec_safe import ShellExecSafeTool
class ToolGateway:
def __init__(self, tools: list[Tool]):
self.tools = {tool.name: tool for tool in tools}
@classmethod
def default(cls, workspace: str) -> "ToolGateway":
return cls(
[
FileReadTool(workspace),
FileWriteTool(workspace),
ShellExecSafeTool(workspace),
]
)
async def run_action(self, action: dict[str, Any]) -> ToolResult:
tool_name = str(action.get("tool", ""))
tool = self.tools.get(tool_name)
if tool is None:
return ToolResult(ok=False, error=f"Unknown tool: {tool_name}")
args = action.get("args") or {}
if not isinstance(args, dict):
return ToolResult(ok=False, error="Tool args must be an object")
return await tool.run(args)
+13
View File
@@ -0,0 +1,13 @@
from pathlib import Path
class WorkspacePathError(ValueError):
pass
def resolve_workspace_path(workspace: str, relative_path: str) -> Path:
root = Path(workspace).resolve()
path = (root / relative_path).resolve()
if root != path and root not in path.parents:
raise WorkspacePathError(f"Path escapes workspace: {relative_path}")
return path
+95
View File
@@ -0,0 +1,95 @@
import shlex
import subprocess
from typing import Any
from duck_core.tools.base import ToolResult
ALLOWLIST = {
"pwd",
"ls",
"cat",
"head",
"tail",
"grep",
"find",
"pytest",
"python -m pytest",
"python3 -m pytest",
"git status",
"git diff",
"git log",
}
BLOCKLIST = {
"rm",
"sudo",
"su",
"dd",
"mkfs",
"mount",
"umount",
"shutdown",
"reboot",
"poweroff",
"systemctl",
"service",
"apt install",
"apt remove",
"pacman -S",
"pacman -R",
"pip install",
"npm install -g",
"chmod -R",
"chown -R",
"curl | sh",
"wget | sh",
}
class ShellExecSafeTool:
name = "shell_exec_safe"
risk_level = "medium"
def __init__(self, workspace: str, timeout_seconds: int = 30):
self.workspace = workspace
self.timeout_seconds = timeout_seconds
async def run(self, args: dict[str, Any]) -> ToolResult:
command = str(args.get("command", "")).strip()
allowed, reason = self._is_allowed(command)
if not allowed:
return ToolResult(ok=False, error=reason, metadata={"requires_approval": True})
try:
completed = subprocess.run(
command,
cwd=self.workspace,
shell=True,
text=True,
capture_output=True,
timeout=self.timeout_seconds,
check=False,
)
except subprocess.SubprocessError as exc:
return ToolResult(ok=False, error=str(exc))
return ToolResult(
ok=completed.returncode == 0,
output=completed.stdout,
error=completed.stderr if completed.returncode else None,
metadata={"returncode": completed.returncode, "command": command},
)
def _is_allowed(self, command: str) -> tuple[bool, str | None]:
if not command:
return False, "Empty command"
lowered = command.lower()
for blocked in BLOCKLIST:
if lowered.startswith(blocked.lower()) or blocked.lower() in lowered:
return False, f"Command is blocked: {blocked}"
parts = shlex.split(command)
prefix1 = parts[0] if parts else ""
prefix2 = " ".join(parts[:2])
prefix3 = " ".join(parts[:3])
if prefix1 in ALLOWLIST or prefix2 in ALLOWLIST or prefix3 in ALLOWLIST:
return True, None
return False, "Command is outside allowlist and requires approval"