vamp-cli 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.
Files changed (57) hide show
  1. vamp_cli/__init__.py +0 -0
  2. vamp_cli/agent/__init__.py +0 -0
  3. vamp_cli/agent/context_sync.py +72 -0
  4. vamp_cli/agent/graph.py +159 -0
  5. vamp_cli/agent/instructions.py +18 -0
  6. vamp_cli/agent/nodes.py +375 -0
  7. vamp_cli/agent/roles.py +8 -0
  8. vamp_cli/agent/runtime/__init__.py +0 -0
  9. vamp_cli/agent/runtime/runtime.py +133 -0
  10. vamp_cli/agent/state.py +55 -0
  11. vamp_cli/agent/status.py +17 -0
  12. vamp_cli/config/__init__.py +0 -0
  13. vamp_cli/config/loader.py +152 -0
  14. vamp_cli/config/schema.py +82 -0
  15. vamp_cli/llm/__init__.py +0 -0
  16. vamp_cli/llm/adapter.py +52 -0
  17. vamp_cli/llm/adapter_registry.py +26 -0
  18. vamp_cli/llm/debug_callbacks.py +18 -0
  19. vamp_cli/llm/router.py +247 -0
  20. vamp_cli/llm/usage.py +170 -0
  21. vamp_cli/logging_setup/logging_setup.py +37 -0
  22. vamp_cli/main.py +1023 -0
  23. vamp_cli/persistence/__init__.py +29 -0
  24. vamp_cli/persistence/compaction.py +372 -0
  25. vamp_cli/persistence/emitter.py +371 -0
  26. vamp_cli/persistence/event_log.py +303 -0
  27. vamp_cli/persistence/fork.py +81 -0
  28. vamp_cli/persistence/loader.py +616 -0
  29. vamp_cli/persistence/transcript.py +160 -0
  30. vamp_cli/prompts/prompts.py +182 -0
  31. vamp_cli/sandbox/__init__.py +0 -0
  32. vamp_cli/sandbox/base.py +22 -0
  33. vamp_cli/sandbox/config.py +16 -0
  34. vamp_cli/sandbox/docker.py +293 -0
  35. vamp_cli/sandbox/manager.py +31 -0
  36. vamp_cli/sandbox/result.py +14 -0
  37. vamp_cli/sandbox/terminal.py +151 -0
  38. vamp_cli/tools/__init__.py +0 -0
  39. vamp_cli/tools/base.py +44 -0
  40. vamp_cli/tools/diagnostic.py +120 -0
  41. vamp_cli/tools/execute_cmd.py +16 -0
  42. vamp_cli/tools/files.py +652 -0
  43. vamp_cli/tools/registry.py +163 -0
  44. vamp_cli/tools/tools_classes.py +622 -0
  45. vamp_cli/utils/__init__.py +0 -0
  46. vamp_cli/utils/error_codes.py +7 -0
  47. vamp_cli/utils/ignore.py +36 -0
  48. vamp_cli/utils/sha_256.py +32 -0
  49. vamp_cli/utils/token_tracker.py +31 -0
  50. vamp_cli/workspace/__init__.py +0 -0
  51. vamp_cli/workspace/workspace.py +16 -0
  52. vamp_cli-0.1.0.dist-info/METADATA +317 -0
  53. vamp_cli-0.1.0.dist-info/RECORD +57 -0
  54. vamp_cli-0.1.0.dist-info/WHEEL +5 -0
  55. vamp_cli-0.1.0.dist-info/entry_points.txt +2 -0
  56. vamp_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  57. vamp_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class CommandResult:
6
+ command: str
7
+
8
+ stdout: str
9
+ stderr: str
10
+
11
+ exit_code: int
12
+
13
+ timed_out: bool = False
14
+ truncated: bool = False
@@ -0,0 +1,151 @@
1
+ import re
2
+ from typing import Any
3
+
4
+ from vamp_cli.sandbox.base import Sandbox
5
+ from vamp_cli.sandbox.result import CommandResult
6
+
7
+
8
+ ANSI_ESCAPE_RE = re.compile(
9
+ r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])"
10
+ )
11
+
12
+ def _strip_ansi(text: str) -> str:
13
+ return ANSI_ESCAPE_RE.sub("", text)
14
+
15
+
16
+ def _truncate_output(
17
+ text: str,
18
+ max_chars: int = 10000,
19
+ ) -> tuple[str, bool]:
20
+ if len(text) <= max_chars:
21
+ return text, False
22
+
23
+ half = max_chars // 2
24
+ omitted = len(text) - max_chars
25
+
26
+ output = (
27
+ f"{text[:half]}\n\n"
28
+ f"... [Output truncated: {omitted} characters omitted] ...\n\n"
29
+ f"{text[-half:]}"
30
+ )
31
+
32
+ return output, True
33
+
34
+
35
+ class Terminal:
36
+ """
37
+ Shell interface exposed to the agent.
38
+ """
39
+
40
+ def __init__(self, sandbox: Sandbox) -> None:
41
+ self.sandbox = sandbox
42
+
43
+ async def execute(
44
+ self,
45
+ command: str,
46
+ cwd: str = ".",
47
+ timeout: int = 60,
48
+ max_output_chars: int = 12000,
49
+ ) -> dict[str, Any]:
50
+ """
51
+ Execute a shell command inside the configured sandbox.
52
+
53
+ Args:
54
+ command:
55
+ Shell command to execute.
56
+
57
+ cwd:
58
+ Working directory relative to /workspace.
59
+
60
+ timeout:
61
+ Maximum execution time in seconds.
62
+
63
+ max_output_chars:
64
+ Maximum amount of combined stdout/stderr returned
65
+ to the model.
66
+
67
+ Returns:
68
+ Dictionary containing execution status and output.
69
+ """
70
+
71
+ if not command.strip():
72
+ return {
73
+ "success": False,
74
+ "command": command,
75
+ "exit_code": -1,
76
+ "error": "Command cannot be empty.",
77
+ }
78
+
79
+ if timeout <= 0:
80
+ return {
81
+ "success": False,
82
+ "command": command,
83
+ "exit_code": -1,
84
+ "error": "Timeout must be greater than zero.",
85
+ }
86
+
87
+ if max_output_chars <= 0:
88
+ return {
89
+ "success": False,
90
+ "command": command,
91
+ "exit_code": -1,
92
+ "error": "max_output_chars must be greater than zero.",
93
+ }
94
+
95
+ try:
96
+ result: CommandResult = await self.sandbox.execute(
97
+ command=command,
98
+ cwd=cwd,
99
+ timeout=timeout,
100
+ )
101
+
102
+ except ValueError as e:
103
+ return {
104
+ "success": False,
105
+ "command": command,
106
+ "exit_code": -1,
107
+ "error": str(e),
108
+ }
109
+
110
+ except Exception as e:
111
+ return {
112
+ "success": False,
113
+ "command": command,
114
+ "exit_code": -1,
115
+ "error": f"Failed to execute command: {str(e)}",
116
+ }
117
+
118
+ stdout = _strip_ansi(result.stdout or "")
119
+ stderr = _strip_ansi(result.stderr or "")
120
+
121
+ if stderr:
122
+ combined_output = (
123
+ stdout + "\n" + stderr
124
+ ).strip()
125
+ else:
126
+ combined_output = stdout.strip()
127
+
128
+ combined_output, truncated = _truncate_output(
129
+ combined_output,
130
+ max_output_chars,
131
+ )
132
+
133
+ if result.timed_out:
134
+ return {
135
+ "success": False,
136
+ "command": command,
137
+ "exit_code": 124,
138
+ "error": f"Command timed out after {timeout} seconds.",
139
+ "output": combined_output or "(No output)",
140
+ "timed_out": True,
141
+ "truncated": truncated,
142
+ }
143
+
144
+ return {
145
+ "success": result.exit_code == 0,
146
+ "command": command,
147
+ "exit_code": result.exit_code,
148
+ "output": combined_output or "(No output)",
149
+ "timed_out": False,
150
+ "truncated": truncated,
151
+ }
File without changes
vamp_cli/tools/base.py ADDED
@@ -0,0 +1,44 @@
1
+ from pydantic import BaseModel
2
+ from abc import ABC
3
+ from typing import Any
4
+ import asyncio
5
+
6
+
7
+
8
+ class InputSchema(BaseModel):
9
+ type: str
10
+ properties: dict[str, Any]
11
+ required: list[str]
12
+ additionalProperties: bool
13
+
14
+
15
+
16
+ class Tools(ABC):
17
+ name: str
18
+ description: str
19
+ input_schema: InputSchema
20
+ parallel_safe: bool = False
21
+
22
+ def schema(self) -> dict[str, Any]:
23
+ return {
24
+ "name": self.name,
25
+ "description": self.description,
26
+ "input_schema": self.input_schema,
27
+ }
28
+
29
+ def to_openai_tool(self) -> dict[str, Any]:
30
+ """OpenAI function-calling shape accepted by ChatLiteLLM.bind_tools()."""
31
+ return {
32
+ "type": "function",
33
+ "function": {
34
+ "name": self.name,
35
+ "description": self.description,
36
+ "parameters": self.input_schema.model_dump(),
37
+ },
38
+ }
39
+
40
+ def run(self, **kwargs: Any) -> str:
41
+ raise NotImplementedError
42
+
43
+ async def arun(self, **kwargs: Any) -> str:
44
+ return await asyncio.to_thread(self.run, **kwargs)
@@ -0,0 +1,120 @@
1
+ import ast
2
+ import json
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Any
6
+ from vamp_cli.workspace.workspace import Workspace
7
+ from vamp_cli.utils.error_codes import Error_codes
8
+
9
+
10
+ class DiagnosticTools:
11
+ def __init__(self, workspace: Workspace) -> None:
12
+ self.workspace = workspace
13
+
14
+ def check_file_syntax(self, file_path: str) -> dict[str, Any]:
15
+ """
16
+ syntax check for newly modified files
17
+ """
18
+ path = self.workspace.safe_path(file_path)
19
+
20
+ if path == Error_codes.PATH_SCOPE_ERROR:
21
+ return {
22
+ "parent": path,
23
+ "error": Error_codes.PATH_SCOPE_ERROR.value
24
+ }
25
+ path = Path(path)
26
+
27
+ if not path.is_file():
28
+ return {"valid": False, "error": f"File '{file_path}' does not exist."}
29
+
30
+ # Python AST Check
31
+ if path.suffix == ".py":
32
+ try:
33
+ code = path.read_text(encoding="utf-8", errors="replace")
34
+ ast.parse(code, filename=str(path))
35
+ return {"valid": True, "diagnostics": []}
36
+ except SyntaxError as e:
37
+ return {
38
+ "valid": False,
39
+ "diagnostics": [{
40
+ "file": file_path,
41
+ "line": e.lineno,
42
+ "column": e.offset,
43
+ "severity": "error",
44
+ "message": f"SyntaxError: {e.msg}",
45
+ "source": "python_ast"
46
+ }]
47
+ }
48
+
49
+ # JSON Parse Check
50
+ if path.suffix == ".json":
51
+ try:
52
+ json.loads(path.read_text(encoding="utf-8", errors="replace"))
53
+ return {"valid": True, "diagnostics": []}
54
+ except json.JSONDecodeError as e:
55
+ return {
56
+ "valid": False,
57
+ "diagnostics": [{
58
+ "file": file_path,
59
+ "line": e.lineno,
60
+ "column": e.colno,
61
+ "severity": "error",
62
+ "message": f"JSONDecodeError: {e.msg}",
63
+ "source": "json_parser"
64
+ }]
65
+ }
66
+
67
+ return {"valid": True, "diagnostics": []}
68
+
69
+ def get_workspace_diagnostics(self, target_path: str = ".") -> dict[str, Any]:
70
+ """
71
+ Runs project-level linters if available in the environment and returns
72
+ structured diagnostics for the LLM.
73
+ """
74
+ diagnostics = []
75
+ root = Path(self.workspace.root)
76
+
77
+ # Python: Fast Ruff check if installed
78
+ ruff_bin = subprocess.run(["which", "ruff"], capture_output=True, text=True).stdout.strip()
79
+ if ruff_bin:
80
+ cmd = ["ruff", "check", target_path, "--output-format=json"]
81
+ proc = subprocess.run(cmd, cwd=root, capture_output=True, text=True)
82
+ if proc.stdout.strip():
83
+ try:
84
+ ruff_errors = json.loads(proc.stdout)
85
+ for err in ruff_errors[:30]: # Cap output to avoid context overflow
86
+ diagnostics.append({
87
+ "file": err.get("filename"),
88
+ "line": err.get("location", {}).get("row"),
89
+ "column": err.get("location", {}).get("column"),
90
+ "severity": "error",
91
+ "code": err.get("code"),
92
+ "message": err.get("message"),
93
+ "source": "ruff"
94
+ })
95
+ except json.JSONDecodeError:
96
+ pass
97
+
98
+ # TypeScript / JavaScript: Check for tsc if tsconfig exists
99
+ if (root / "tsconfig.json").is_file():
100
+ npx_bin = subprocess.run(["which", "npx"], capture_output=True, text=True).stdout.strip()
101
+ if npx_bin:
102
+ proc = subprocess.run(
103
+ ["npx", "tsc", "--noEmit", "--pretty", "false"],
104
+ cwd=root,
105
+ capture_output=True,
106
+ text=True
107
+ )
108
+ for line in proc.stdout.splitlines()[:20]:
109
+ if "error TS" in line:
110
+ diagnostics.append({
111
+ "raw": line.strip(),
112
+ "severity": "error",
113
+ "source": "tsc"
114
+ })
115
+
116
+ return {
117
+ "has_errors": len(diagnostics) > 0,
118
+ "total_diagnostics": len(diagnostics),
119
+ "diagnostics": diagnostics
120
+ }
@@ -0,0 +1,16 @@
1
+ from vamp_cli.sandbox.terminal import Terminal
2
+
3
+ class ExecuteCommandTool:
4
+
5
+ def __init__(self, terminal: Terminal):
6
+ self.terminal = terminal
7
+
8
+ async def execute_command(
9
+ self,
10
+ command: str,
11
+ cwd: str = ".",
12
+ ):
13
+ return await self.terminal.execute(
14
+ command=command,
15
+ cwd=cwd,
16
+ )