Skip to content

Commit cc8ffa2

Browse files
Darth-Hidiousclaude
andcommitted
fix: restore Python entry point as Rust trampoline + tool input validation
The Python prism entry point MUST stay — removing it breaks upgrades because pip/uv removes the executable. The bootstrap.py already delegates to the Rust binary via execvp when it's available. Flow: pip installs Python 'prism' → bootstrap.py finds Rust binary → execvp replaces process with Rust → user sees Rust CLI. If Rust binary missing: Python CLI runs with fallback commands that tell user to install the Rust binary. Also: added tool input validation (2.2) — validates required fields and types against tool input_schema before execution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b3245ff commit cc8ffa2

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

app/agent/core.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,32 @@
2626
MAX_TOOL_RESULT_CHARS = 30_000
2727

2828

29+
def _validate_tool_input(tool, args: dict) -> str | None:
30+
"""Validate tool args against the tool's input_schema. Returns error string or None."""
31+
schema = getattr(tool, 'input_schema', None)
32+
if not schema or not isinstance(schema, dict):
33+
return None
34+
props = schema.get('properties', {})
35+
required = schema.get('required', [])
36+
for field in required:
37+
if field not in args:
38+
return f"missing required field '{field}'"
39+
for key, val in args.items():
40+
if key.startswith('_'):
41+
continue # internal fields
42+
if key in props:
43+
expected_type = props[key].get('type', '')
44+
if expected_type == 'string' and not isinstance(val, str):
45+
return f"'{key}' must be a string, got {type(val).__name__}"
46+
if expected_type == 'integer' and not isinstance(val, int):
47+
return f"'{key}' must be an integer, got {type(val).__name__}"
48+
if expected_type == 'number' and not isinstance(val, (int, float)):
49+
return f"'{key}' must be a number, got {type(val).__name__}"
50+
if expected_type == 'array' and not isinstance(val, list):
51+
return f"'{key}' must be an array, got {type(val).__name__}"
52+
return None
53+
54+
2955
def _load_system_prompt(settings_path: str = "") -> str:
3056
if settings_path:
3157
p = Path(settings_path).expanduser()
@@ -143,6 +169,11 @@ def _execute_tool_with_hooks(self, tool_name: str, tool_args: dict, call_id: str
143169
else:
144170
return {"skipped": f"Tool {tool_name} requires approval but no callback set."}
145171

172+
# Step 3.5: Validate inputs against schema
173+
validation_error = _validate_tool_input(tool, effective_args)
174+
if validation_error:
175+
return {"error": f"Invalid input for {tool_name}: {validation_error}"}
176+
146177
# Step 4: Execute
147178
try:
148179
if tool_name == "show_scratchpad":

pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,8 @@ dev = [
9494
"flake8>=6.1.0",
9595
]
9696

97-
# No Python entry point — the Rust binary IS the CLI.
98-
# Python is invoked by the Rust binary via prism-python-bridge.
99-
# [project.scripts]
100-
# prism = "app.cli.bootstrap:main" # REMOVED — conflicts with Rust binary
97+
[project.scripts]
98+
prism = "app.cli.bootstrap:main"
10199

102100
[project.urls]
103101
Homepage = "https://github.com/Darth-Hidious/PRISM"

0 commit comments

Comments
 (0)