mycode-coding-agent 0.1.0__py3-none-any.whl
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.
- mycode/__init__.py +0 -0
- mycode/adapters/__init__.py +21 -0
- mycode/adapters/jsonl.py +692 -0
- mycode/agent/__init__.py +25 -0
- mycode/agent/events.py +111 -0
- mycode/agent/outcome.py +103 -0
- mycode/agent/progress.py +373 -0
- mycode/agent/runner.py +1481 -0
- mycode/application/__init__.py +38 -0
- mycode/application/agent_session.py +367 -0
- mycode/application/events.py +59 -0
- mycode/application/runtime.py +211 -0
- mycode/application/sessions.py +180 -0
- mycode/cli.py +840 -0
- mycode/config.py +355 -0
- mycode/context/__init__.py +1 -0
- mycode/context/artifacts.py +672 -0
- mycode/context/budget.py +752 -0
- mycode/context/builder.py +112 -0
- mycode/context/compact.py +795 -0
- mycode/context/tool_result_format.py +199 -0
- mycode/context/tool_result_retention.py +261 -0
- mycode/conversation.py +78 -0
- mycode/error_handling.py +481 -0
- mycode/event_format.py +147 -0
- mycode/instructions.py +285 -0
- mycode/llm.py +771 -0
- mycode/mcp/__init__.py +41 -0
- mycode/mcp/client.py +44 -0
- mycode/mcp/config.py +207 -0
- mycode/mcp/errors.py +302 -0
- mycode/mcp/manager.py +339 -0
- mycode/mcp/models.py +20 -0
- mycode/mcp/result_adapter.py +58 -0
- mycode/mcp/tool_adapter.py +145 -0
- mycode/mcp/trust.py +313 -0
- mycode/memory.py +570 -0
- mycode/memory_context.py +245 -0
- mycode/messages.py +63 -0
- mycode/observability.py +28 -0
- mycode/permissions.py +262 -0
- mycode/persistence/__init__.py +1 -0
- mycode/persistence/filesystem.py +291 -0
- mycode/persistence/project_storage.py +208 -0
- mycode/persistence/session_lock.py +138 -0
- mycode/persistence/session_store.py +503 -0
- mycode/presentation/__init__.py +1 -0
- mycode/presentation/cli/__init__.py +14 -0
- mycode/presentation/cli/confirmer.py +116 -0
- mycode/presentation/cli/mcp_trust.py +61 -0
- mycode/presentation/cli/presenter.py +320 -0
- mycode/presentation/cli/session_menu.py +146 -0
- mycode/presentation/cli/subagent_observer.py +124 -0
- mycode/presentation/command_format.py +90 -0
- mycode/presentation/commands.py +95 -0
- mycode/presentation/tui/__init__.py +6 -0
- mycode/presentation/tui/app.py +1351 -0
- mycode/presentation/tui/interactions.py +253 -0
- mycode/presentation/tui/presenter.py +266 -0
- mycode/presentation/tui/screens.py +305 -0
- mycode/presentation/tui/widgets.py +214 -0
- mycode/project.py +22 -0
- mycode/prompts.py +181 -0
- mycode/reasoning.py +40 -0
- mycode/session.py +86 -0
- mycode/skills/__init__.py +27 -0
- mycode/skills/builtin/database-recovery/SKILL.md +138 -0
- mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
- mycode/skills/registry.py +295 -0
- mycode/skills/state.py +68 -0
- mycode/subagents/__init__.py +1 -0
- mycode/subagents/audit.py +212 -0
- mycode/subagents/concurrency.py +124 -0
- mycode/subagents/contracts.py +421 -0
- mycode/subagents/delegate.py +80 -0
- mycode/subagents/delegation.py +128 -0
- mycode/subagents/lifecycle.py +86 -0
- mycode/subagents/limits.py +7 -0
- mycode/subagents/observability.py +150 -0
- mycode/subagents/persistence.py +152 -0
- mycode/subagents/profiles.py +184 -0
- mycode/subagents/prompts.py +67 -0
- mycode/subagents/results.py +178 -0
- mycode/subagents/runtime.py +528 -0
- mycode/subagents/snapshots.py +211 -0
- mycode/subagents/tool_batch.py +260 -0
- mycode/tools/__init__.py +81 -0
- mycode/tools/base.py +222 -0
- mycode/tools/bounds.py +14 -0
- mycode/tools/command_executor.py +167 -0
- mycode/tools/command_output.py +166 -0
- mycode/tools/command_risk.py +596 -0
- mycode/tools/defaults.py +59 -0
- mycode/tools/edit_file.py +524 -0
- mycode/tools/file_mutation.py +30 -0
- mycode/tools/glob.py +247 -0
- mycode/tools/grep.py +324 -0
- mycode/tools/ignore.py +122 -0
- mycode/tools/inspect_changes.py +269 -0
- mycode/tools/load_skill.py +92 -0
- mycode/tools/memory.py +264 -0
- mycode/tools/path_permissions.py +78 -0
- mycode/tools/patterns.py +48 -0
- mycode/tools/permission_metadata.py +27 -0
- mycode/tools/process_tree.py +166 -0
- mycode/tools/read_file.py +242 -0
- mycode/tools/read_skill_resource.py +93 -0
- mycode/tools/registry.py +279 -0
- mycode/tools/run_command.py +237 -0
- mycode/tools/run_skill_script.py +206 -0
- mycode/tools/run_validation.py +107 -0
- mycode/tools/submit_result.py +93 -0
- mycode/tools/text.py +15 -0
- mycode/tools/validation_command.py +377 -0
- mycode/tools/workspace.py +33 -0
- mycode/tools/write_file.py +169 -0
- mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
- mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
- mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- mycode_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, field_validator
|
|
6
|
+
|
|
7
|
+
from mycode.permissions import PermissionChecker, PermissionDecision, PermissionRequest
|
|
8
|
+
from mycode.skills import ActiveSkillState, SkillPathError, SkillRegistry
|
|
9
|
+
from mycode.tools.base import PydanticTool, ToolArgs, ToolResult
|
|
10
|
+
from mycode.tools.command_executor import CommandExecutionArgs, execute_command
|
|
11
|
+
from mycode.tools.permission_metadata import with_permission_metadata
|
|
12
|
+
from mycode.tools.run_command import (
|
|
13
|
+
DEFAULT_MAX_OUTPUT_CHARS,
|
|
14
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
15
|
+
MAX_OUTPUT_CHARS,
|
|
16
|
+
MAX_TIMEOUT_SECONDS,
|
|
17
|
+
)
|
|
18
|
+
from mycode.tools.workspace import Workspace
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RunSkillScriptArgs(ToolArgs):
|
|
22
|
+
skill: str = Field(min_length=1)
|
|
23
|
+
script: str = Field(min_length=1)
|
|
24
|
+
args: list[str] = Field(default_factory=list)
|
|
25
|
+
timeout_seconds: float = Field(
|
|
26
|
+
default=DEFAULT_TIMEOUT_SECONDS, gt=0, le=MAX_TIMEOUT_SECONDS
|
|
27
|
+
)
|
|
28
|
+
max_output_chars: int = Field(
|
|
29
|
+
default=DEFAULT_MAX_OUTPUT_CHARS, ge=0, le=MAX_OUTPUT_CHARS
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
@field_validator("args")
|
|
33
|
+
@classmethod
|
|
34
|
+
def validate_args(cls, value: list[str]) -> list[str]:
|
|
35
|
+
if any(not isinstance(part, str) or part == "" for part in value):
|
|
36
|
+
raise ValueError("Script arguments must be non-empty strings.")
|
|
37
|
+
if any("\x00" in part for part in value):
|
|
38
|
+
raise ValueError("Script arguments must not contain null bytes.")
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RunSkillScriptTool(PydanticTool[RunSkillScriptArgs]):
|
|
43
|
+
name = "run_skill_script"
|
|
44
|
+
description = "在当前 workspace 中运行已激活 Skill 的受支持脚本。"
|
|
45
|
+
args_model = RunSkillScriptArgs
|
|
46
|
+
capability = "command"
|
|
47
|
+
risk = "high"
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
workspace: Workspace,
|
|
52
|
+
registry: SkillRegistry,
|
|
53
|
+
state: ActiveSkillState,
|
|
54
|
+
) -> None:
|
|
55
|
+
self.workspace = workspace
|
|
56
|
+
self.registry = registry
|
|
57
|
+
self.state = state
|
|
58
|
+
|
|
59
|
+
def build_permission_request(self, args: RunSkillScriptArgs) -> PermissionRequest:
|
|
60
|
+
logical_script = _logical_script_path(args.script)
|
|
61
|
+
return PermissionRequest(
|
|
62
|
+
tool_name=self.name,
|
|
63
|
+
capability=self.capability,
|
|
64
|
+
action=self.name,
|
|
65
|
+
target=f"{args.skill}:{logical_script}",
|
|
66
|
+
arguments=args.model_dump(),
|
|
67
|
+
description=(
|
|
68
|
+
f'Skill "{args.skill}" 请求在 workspace 中执行 {logical_script},'
|
|
69
|
+
f"参数为 {args.args!r}。"
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def check_permission(
|
|
74
|
+
self,
|
|
75
|
+
args: RunSkillScriptArgs,
|
|
76
|
+
permission_checker: PermissionChecker,
|
|
77
|
+
) -> tuple[PermissionRequest, PermissionDecision]:
|
|
78
|
+
request = self.build_permission_request(args)
|
|
79
|
+
skill = self.registry.get(args.skill)
|
|
80
|
+
metadata = {
|
|
81
|
+
"skill_name": args.skill,
|
|
82
|
+
"script": _logical_script_path(args.script),
|
|
83
|
+
"cwd": str(self.workspace.root),
|
|
84
|
+
"arguments": list(args.args),
|
|
85
|
+
}
|
|
86
|
+
if skill is None or not self.state.is_active(args.skill):
|
|
87
|
+
return request, PermissionDecision.deny(
|
|
88
|
+
reason="unsupported_operation",
|
|
89
|
+
message=f"Skill is not active: {args.skill}",
|
|
90
|
+
metadata=metadata,
|
|
91
|
+
)
|
|
92
|
+
metadata["skill_source"] = skill.source
|
|
93
|
+
try:
|
|
94
|
+
script_path = self.registry.resolve_script(skill, args.script)
|
|
95
|
+
except SkillPathError as error:
|
|
96
|
+
return request, PermissionDecision.deny(
|
|
97
|
+
reason="outside_workspace", message=str(error), metadata=metadata
|
|
98
|
+
)
|
|
99
|
+
if not script_path.exists():
|
|
100
|
+
return request, PermissionDecision.deny(
|
|
101
|
+
reason="unsupported_operation",
|
|
102
|
+
message=f"Skill script not found: {args.script}",
|
|
103
|
+
metadata=metadata,
|
|
104
|
+
)
|
|
105
|
+
if not script_path.is_file():
|
|
106
|
+
return request, PermissionDecision.deny(
|
|
107
|
+
reason="unsupported_operation",
|
|
108
|
+
message=f"Skill script is not a file: {args.script}",
|
|
109
|
+
metadata=metadata,
|
|
110
|
+
)
|
|
111
|
+
runtime = _runtime_for_script(script_path)
|
|
112
|
+
if runtime is None:
|
|
113
|
+
return request, PermissionDecision.deny(
|
|
114
|
+
reason="unsupported_operation",
|
|
115
|
+
message=f"Unsupported Skill script type: {script_path.suffix or '<none>'}",
|
|
116
|
+
metadata=metadata,
|
|
117
|
+
)
|
|
118
|
+
executable = runtime[0]
|
|
119
|
+
if Path(executable).is_absolute():
|
|
120
|
+
runtime_available = Path(executable).is_file()
|
|
121
|
+
else:
|
|
122
|
+
runtime_available = shutil.which(executable) is not None
|
|
123
|
+
if not runtime_available:
|
|
124
|
+
return request, PermissionDecision.deny(
|
|
125
|
+
reason="unsupported_operation",
|
|
126
|
+
message=f"Required Skill script runtime is unavailable: {executable}",
|
|
127
|
+
metadata=metadata,
|
|
128
|
+
)
|
|
129
|
+
decision = permission_checker.check(request, self.get_permission_profile())
|
|
130
|
+
decision = with_permission_metadata(decision, metadata)
|
|
131
|
+
if decision.status == "ask":
|
|
132
|
+
decision = PermissionDecision.ask(
|
|
133
|
+
reason=decision.reason,
|
|
134
|
+
message=(
|
|
135
|
+
f'Skill "{skill.name}" 请求执行 '
|
|
136
|
+
f"{_logical_script_path(args.script)}。"
|
|
137
|
+
),
|
|
138
|
+
metadata=decision.metadata,
|
|
139
|
+
)
|
|
140
|
+
return request, decision
|
|
141
|
+
|
|
142
|
+
def run_authorized(
|
|
143
|
+
self,
|
|
144
|
+
args: RunSkillScriptArgs,
|
|
145
|
+
decision: PermissionDecision,
|
|
146
|
+
) -> ToolResult:
|
|
147
|
+
try:
|
|
148
|
+
skill = self.registry.require(args.skill)
|
|
149
|
+
if not self.state.is_active(skill.name):
|
|
150
|
+
raise ValueError(f"Skill is not active: {skill.name}")
|
|
151
|
+
script_path = self.registry.resolve_script(skill, args.script)
|
|
152
|
+
if not script_path.exists() or not script_path.is_file():
|
|
153
|
+
raise ValueError(f"Skill script is unavailable: {args.script}")
|
|
154
|
+
runtime = _runtime_for_script(script_path)
|
|
155
|
+
if runtime is None:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"Unsupported Skill script type: {script_path.suffix or '<none>'}"
|
|
158
|
+
)
|
|
159
|
+
command = [*runtime, str(script_path), *args.args]
|
|
160
|
+
return execute_command(
|
|
161
|
+
args=CommandExecutionArgs(
|
|
162
|
+
command=command,
|
|
163
|
+
timeout_seconds=args.timeout_seconds,
|
|
164
|
+
max_output_chars=args.max_output_chars,
|
|
165
|
+
),
|
|
166
|
+
cwd=self.workspace.root,
|
|
167
|
+
permission_metadata=decision.metadata,
|
|
168
|
+
permission_status=decision.status,
|
|
169
|
+
)
|
|
170
|
+
except Exception as error:
|
|
171
|
+
return ToolResult.failure(
|
|
172
|
+
error=f"Tool execution failed: {error}",
|
|
173
|
+
metadata={
|
|
174
|
+
**decision.metadata,
|
|
175
|
+
"permission_status": decision.status,
|
|
176
|
+
"exception_type": type(error).__name__,
|
|
177
|
+
},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _run(self, args: RunSkillScriptArgs) -> ToolResult:
|
|
181
|
+
return ToolResult.failure(
|
|
182
|
+
error="run_skill_script must be run through ToolRegistry.run_tool().",
|
|
183
|
+
metadata={
|
|
184
|
+
"skill_name": args.skill,
|
|
185
|
+
"script": _logical_script_path(args.script),
|
|
186
|
+
"reason": "permission_required",
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _runtime_for_script(script: Path) -> list[str] | None:
|
|
192
|
+
suffix = script.suffix.lower()
|
|
193
|
+
if suffix == ".py":
|
|
194
|
+
return [sys.executable]
|
|
195
|
+
if suffix in {".js", ".mjs"}:
|
|
196
|
+
return ["node"]
|
|
197
|
+
if suffix == ".sh":
|
|
198
|
+
return ["bash"]
|
|
199
|
+
if suffix == ".ps1":
|
|
200
|
+
return ["pwsh", "-File"]
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _logical_script_path(script: str) -> str:
|
|
205
|
+
normalized = script.replace("\\", "/")
|
|
206
|
+
return normalized if normalized.startswith("scripts/") else f"scripts/{normalized}"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, field_validator
|
|
4
|
+
|
|
5
|
+
from mycode.permissions import PermissionChecker, PermissionDecision, PermissionRequest
|
|
6
|
+
from mycode.subagents.limits import (
|
|
7
|
+
MAX_VALIDATION_COMMAND_CHARS,
|
|
8
|
+
MAX_VALIDATION_COMMAND_PART_CHARS,
|
|
9
|
+
MAX_VALIDATION_COMMAND_PARTS,
|
|
10
|
+
)
|
|
11
|
+
from mycode.tools.base import ToolResult
|
|
12
|
+
from mycode.tools.permission_metadata import with_permission_metadata
|
|
13
|
+
from mycode.tools.run_command import RunCommandArgs, RunCommandTool
|
|
14
|
+
from mycode.tools.validation_command import analyze_validation_command
|
|
15
|
+
from mycode.tools.workspace import Workspace
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RunValidationArgs(RunCommandArgs):
|
|
19
|
+
command: list[str] = Field(
|
|
20
|
+
min_length=1,
|
|
21
|
+
max_length=MAX_VALIDATION_COMMAND_PARTS,
|
|
22
|
+
)
|
|
23
|
+
cwd: str = Field(default=".", min_length=1, max_length=500)
|
|
24
|
+
|
|
25
|
+
@field_validator("command")
|
|
26
|
+
@classmethod
|
|
27
|
+
def validation_command_must_be_bounded(cls, value: list[str]) -> list[str]:
|
|
28
|
+
if any(len(part) > MAX_VALIDATION_COMMAND_PART_CHARS for part in value):
|
|
29
|
+
raise ValueError(
|
|
30
|
+
"Validation command parts must not exceed "
|
|
31
|
+
f"{MAX_VALIDATION_COMMAND_PART_CHARS} characters."
|
|
32
|
+
)
|
|
33
|
+
if sum(len(part) for part in value) > MAX_VALIDATION_COMMAND_CHARS:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"Validation command must not exceed "
|
|
36
|
+
f"{MAX_VALIDATION_COMMAND_CHARS} total characters."
|
|
37
|
+
)
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RunValidationTool(RunCommandTool):
|
|
42
|
+
name = "run_validation"
|
|
43
|
+
description = (
|
|
44
|
+
"Run a non-interactive validation command inside the workspace using "
|
|
45
|
+
"the same safety and permission checks as run_command."
|
|
46
|
+
)
|
|
47
|
+
args_model = RunValidationArgs
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
workspace: Workspace,
|
|
52
|
+
*,
|
|
53
|
+
restrict_to_known_validators: bool = True,
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__(workspace)
|
|
56
|
+
self.restrict_to_known_validators = restrict_to_known_validators
|
|
57
|
+
|
|
58
|
+
def build_permission_request(self, args: RunCommandArgs) -> PermissionRequest:
|
|
59
|
+
return PermissionRequest(
|
|
60
|
+
tool_name=self.name,
|
|
61
|
+
capability=self.capability,
|
|
62
|
+
action=self.name,
|
|
63
|
+
target=subprocess.list2cmdline(args.command),
|
|
64
|
+
arguments=args.model_dump(),
|
|
65
|
+
description="Run a validation command.",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def check_permission(
|
|
69
|
+
self,
|
|
70
|
+
args: RunCommandArgs,
|
|
71
|
+
permission_checker: PermissionChecker,
|
|
72
|
+
) -> tuple[PermissionRequest, PermissionDecision]:
|
|
73
|
+
if not self.restrict_to_known_validators:
|
|
74
|
+
return super().check_permission(args, permission_checker)
|
|
75
|
+
|
|
76
|
+
request = self.build_permission_request(args)
|
|
77
|
+
validation = analyze_validation_command(args.command)
|
|
78
|
+
validation_metadata = {
|
|
79
|
+
"validation_allowed": validation.allowed,
|
|
80
|
+
"validation_classification": validation.classification,
|
|
81
|
+
"validation_category": validation.category,
|
|
82
|
+
"validation_reason": validation.reason,
|
|
83
|
+
}
|
|
84
|
+
if not validation.allowed:
|
|
85
|
+
return request, PermissionDecision.deny(
|
|
86
|
+
reason="unsupported_operation",
|
|
87
|
+
message=validation.reason,
|
|
88
|
+
metadata={
|
|
89
|
+
"tool_name": self.name,
|
|
90
|
+
"command": list(args.command),
|
|
91
|
+
"cwd": args.cwd,
|
|
92
|
+
**validation_metadata,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
request, decision = super().check_permission(args, permission_checker)
|
|
97
|
+
return request, with_permission_metadata(decision, validation_metadata)
|
|
98
|
+
|
|
99
|
+
def _run(self, args: RunCommandArgs) -> ToolResult:
|
|
100
|
+
return ToolResult.failure(
|
|
101
|
+
error="run_validation must be run through ToolRegistry.run_tool().",
|
|
102
|
+
metadata={
|
|
103
|
+
"command": args.command,
|
|
104
|
+
"cwd": args.cwd,
|
|
105
|
+
"reason": "permission_required",
|
|
106
|
+
},
|
|
107
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
from mycode.permissions import ToolRisk
|
|
5
|
+
from mycode.subagents.contracts import BoundedResultArgs, SubAgentRole
|
|
6
|
+
from mycode.tools.base import PydanticTool, ToolResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DEFAULT_MAX_SUBMITTED_RESULT_CHARS = 24000
|
|
10
|
+
|
|
11
|
+
ResultArgsT = TypeVar("ResultArgsT", bound=BoundedResultArgs)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SubmitResultTool(PydanticTool[ResultArgsT], Generic[ResultArgsT]):
|
|
15
|
+
name = "submit_result"
|
|
16
|
+
description = "Submit the role-specific structured result and finish the SubAgent run."
|
|
17
|
+
capability = "control"
|
|
18
|
+
risk: ToolRisk = "low"
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
role: SubAgentRole,
|
|
24
|
+
result_model: type[ResultArgsT],
|
|
25
|
+
max_result_chars: int = DEFAULT_MAX_SUBMITTED_RESULT_CHARS,
|
|
26
|
+
acceptance_validator: Callable[[ResultArgsT], str | None] | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
if max_result_chars < 1:
|
|
29
|
+
raise ValueError("max_result_chars must be at least 1.")
|
|
30
|
+
self.role = role
|
|
31
|
+
self.args_model = result_model
|
|
32
|
+
self.max_result_chars = max_result_chars
|
|
33
|
+
self.acceptance_validator = acceptance_validator
|
|
34
|
+
self.submitted_result: ResultArgsT | None = None
|
|
35
|
+
|
|
36
|
+
def _run(self, args: ResultArgsT) -> ToolResult:
|
|
37
|
+
if self.submitted_result is not None:
|
|
38
|
+
return ToolResult.failure(
|
|
39
|
+
error="A structured SubAgent result has already been submitted.",
|
|
40
|
+
metadata={"role": self.role, "reason": "result_already_submitted"},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
serialized = args.model_dump_json()
|
|
44
|
+
result_chars = len(serialized)
|
|
45
|
+
if result_chars > self.max_result_chars:
|
|
46
|
+
return ToolResult.failure(
|
|
47
|
+
error=(
|
|
48
|
+
"Structured SubAgent result exceeds the allowed size: "
|
|
49
|
+
f"{result_chars}/{self.max_result_chars} characters."
|
|
50
|
+
),
|
|
51
|
+
metadata={
|
|
52
|
+
"role": self.role,
|
|
53
|
+
"reason": "result_too_large",
|
|
54
|
+
"result_chars": result_chars,
|
|
55
|
+
"max_result_chars": self.max_result_chars,
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if self.acceptance_validator is not None:
|
|
60
|
+
try:
|
|
61
|
+
validation_error = self.acceptance_validator(args)
|
|
62
|
+
except Exception as error:
|
|
63
|
+
return ToolResult.failure(
|
|
64
|
+
error="Runtime result validation failed unexpectedly.",
|
|
65
|
+
metadata={
|
|
66
|
+
"role": self.role,
|
|
67
|
+
"reason": "result_runtime_validation_error",
|
|
68
|
+
"exception_type": type(error).__name__,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
if validation_error is not None:
|
|
72
|
+
return ToolResult.failure(
|
|
73
|
+
error=validation_error,
|
|
74
|
+
metadata={
|
|
75
|
+
"role": self.role,
|
|
76
|
+
"reason": "result_runtime_validation_failed",
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self.submitted_result = args
|
|
81
|
+
outcome = getattr(args, "status", None)
|
|
82
|
+
if outcome is None:
|
|
83
|
+
outcome = getattr(args, "recommendation", None)
|
|
84
|
+
return ToolResult.success(
|
|
85
|
+
content="Structured SubAgent result accepted.",
|
|
86
|
+
metadata={
|
|
87
|
+
"role": self.role,
|
|
88
|
+
"outcome": outcome,
|
|
89
|
+
"result_chars": result_chars,
|
|
90
|
+
"truncated": args.truncated,
|
|
91
|
+
"omitted_count": args.omitted_count,
|
|
92
|
+
},
|
|
93
|
+
)
|
mycode/tools/text.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
SUPPORTED_TEXT_ENCODINGS = ("utf-8", "gbk")
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def contains_nul_byte(raw_content: bytes) -> bool:
|
|
5
|
+
return b"\x00" in raw_content
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def decode_text(raw_content: bytes) -> tuple[str, str] | None:
|
|
9
|
+
for encoding in SUPPORTED_TEXT_ENCODINGS:
|
|
10
|
+
try:
|
|
11
|
+
return raw_content.decode(encoding), encoding
|
|
12
|
+
except UnicodeDecodeError:
|
|
13
|
+
continue
|
|
14
|
+
|
|
15
|
+
return None
|