augment-sdk 0.1.1__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.
augment/exceptions.py ADDED
@@ -0,0 +1,92 @@
1
+ """Custom exceptions for the Augment SDK"""
2
+
3
+
4
+ class AugmentError(Exception):
5
+ """Base exception for all Augment SDK errors"""
6
+
7
+ def __init__(self, message: str, status_code: int = None):
8
+ self.message = message
9
+ self.status_code = status_code
10
+ super().__init__(self.message)
11
+
12
+
13
+ class AuthenticationError(AugmentError):
14
+ """Raised when authentication fails"""
15
+
16
+ def __init__(self, message: str = "Authentication failed"):
17
+ super().__init__(message, status_code=401)
18
+
19
+
20
+ class RateLimitError(AugmentError):
21
+ """Raised when rate limit is exceeded"""
22
+
23
+ def __init__(self, message: str = "Rate limit exceeded", retry_after: int = None):
24
+ super().__init__(message, status_code=429)
25
+ self.retry_after = retry_after
26
+
27
+
28
+ class NotFoundError(AugmentError):
29
+ """Raised when a resource is not found"""
30
+
31
+ def __init__(self, message: str = "Resource not found"):
32
+ super().__init__(message, status_code=404)
33
+
34
+
35
+ class ValidationError(AugmentError):
36
+ """Raised when request validation fails"""
37
+
38
+ def __init__(self, message: str = "Validation error"):
39
+ super().__init__(message, status_code=400)
40
+
41
+
42
+ class AugmentCLIError(AugmentError):
43
+ """Raised when the auggie CLI command fails"""
44
+
45
+ def __init__(self, message: str, return_code: int = None, stderr: str = None):
46
+ super().__init__(message)
47
+ self.return_code = return_code
48
+ self.stderr = stderr
49
+
50
+
51
+ class AugmentNotFoundError(AugmentError):
52
+ """Raised when auggie CLI is not found or accessible"""
53
+
54
+ def __init__(self, message: str = "auggie CLI not found"):
55
+ super().__init__(message)
56
+
57
+
58
+ class AugmentWorkspaceError(AugmentError):
59
+ """Raised when workspace path is invalid or inaccessible"""
60
+
61
+ def __init__(self, message: str):
62
+ super().__init__(message)
63
+
64
+
65
+ class AugmentParseError(AugmentError):
66
+ """Raised when parsing agent response fails"""
67
+
68
+ def __init__(self, message: str):
69
+ super().__init__(message)
70
+
71
+
72
+ class AugmentJSONError(AugmentError):
73
+ """Raised when JSON parsing fails"""
74
+
75
+ def __init__(self, message: str):
76
+ super().__init__(message)
77
+
78
+
79
+ class AugmentVerificationError(AugmentError):
80
+ """Raised when success criteria verification fails after max rounds"""
81
+
82
+ def __init__(
83
+ self,
84
+ message: str,
85
+ unmet_criteria: list = None,
86
+ issues: list = None,
87
+ rounds_attempted: int = None,
88
+ ):
89
+ super().__init__(message)
90
+ self.unmet_criteria = unmet_criteria or []
91
+ self.issues = issues or []
92
+ self.rounds_attempted = rounds_attempted
@@ -0,0 +1,265 @@
1
+ """
2
+ Utilities for converting Python functions to tool schemas for agent function calling.
3
+ """
4
+
5
+ import inspect
6
+ import json
7
+ from typing import Any, Callable, Dict, List, Optional, Union, get_args, get_origin
8
+
9
+
10
+ def python_type_to_json_schema_type(py_type: Any) -> Dict[str, Any]:
11
+ """
12
+ Convert a Python type annotation to a JSON schema type.
13
+
14
+ Args:
15
+ py_type: Python type annotation
16
+
17
+ Returns:
18
+ JSON schema type definition
19
+ """
20
+ # Handle None/NoneType
21
+ if py_type is None or py_type is type(None):
22
+ return {"type": "null"}
23
+
24
+ # Get origin for generic types (List, Dict, Optional, etc.)
25
+ origin = get_origin(py_type)
26
+ args = get_args(py_type)
27
+
28
+ # Handle Optional[T] (Union[T, None])
29
+ if origin is Union:
30
+ # Filter out None types
31
+ non_none_args = [arg for arg in args if arg is not type(None)]
32
+ if len(non_none_args) == 1:
33
+ # This is Optional[T]
34
+ return python_type_to_json_schema_type(non_none_args[0])
35
+ else:
36
+ # Multiple union types - use anyOf
37
+ return {"anyOf": [python_type_to_json_schema_type(arg) for arg in args]}
38
+
39
+ # Handle List[T]
40
+ if origin is list or py_type is list:
41
+ if args:
42
+ return {"type": "array", "items": python_type_to_json_schema_type(args[0])}
43
+ return {"type": "array"}
44
+
45
+ # Handle Dict[K, V]
46
+ if origin is dict or py_type is dict:
47
+ schema = {"type": "object"}
48
+ if args and len(args) == 2:
49
+ # Dict[str, T] - we can specify value type
50
+ schema["additionalProperties"] = python_type_to_json_schema_type(args[1])
51
+ return schema
52
+
53
+ # Handle basic types
54
+ type_mapping = {
55
+ str: {"type": "string"},
56
+ int: {"type": "integer"},
57
+ float: {"type": "number"},
58
+ bool: {"type": "boolean"},
59
+ Any: {}, # No type constraint
60
+ }
61
+
62
+ if py_type in type_mapping:
63
+ return type_mapping[py_type]
64
+
65
+ # Default to object for unknown types
66
+ return {"type": "object"}
67
+
68
+
69
+ def extract_param_description(
70
+ docstring: Optional[str], param_name: str
71
+ ) -> Optional[str]:
72
+ """
73
+ Extract parameter description from docstring.
74
+
75
+ Supports Google-style and NumPy-style docstrings.
76
+
77
+ Args:
78
+ docstring: Function docstring
79
+ param_name: Parameter name to find
80
+
81
+ Returns:
82
+ Parameter description if found, None otherwise
83
+ """
84
+ if not docstring:
85
+ return None
86
+
87
+ lines = docstring.split("\n")
88
+ in_args_section = False
89
+
90
+ for i, line in enumerate(lines):
91
+ stripped = line.strip()
92
+
93
+ # Check if we're entering Args/Parameters section
94
+ if stripped.lower() in ["args:", "arguments:", "parameters:", "params:"]:
95
+ in_args_section = True
96
+ continue
97
+
98
+ # Check if we're leaving Args section (new section starts)
99
+ if (
100
+ in_args_section
101
+ and stripped.endswith(":")
102
+ and not stripped.startswith(param_name)
103
+ ):
104
+ in_args_section = False
105
+ continue
106
+
107
+ # Look for parameter in Args section
108
+ if in_args_section:
109
+ # Google style: "param_name: description" or "param_name (type): description"
110
+ if stripped.startswith(f"{param_name}:") or stripped.startswith(
111
+ f"{param_name} ("
112
+ ):
113
+ # Extract description after colon
114
+ colon_idx = stripped.find(":", stripped.find(param_name))
115
+ if colon_idx != -1:
116
+ desc = stripped[colon_idx + 1 :].strip()
117
+ # Check if description continues on next lines
118
+ j = i + 1
119
+ while j < len(lines):
120
+ next_line = lines[j].strip()
121
+ # Stop if we hit another parameter or empty line
122
+ if not next_line or ":" in next_line:
123
+ break
124
+ desc += " " + next_line
125
+ j += 1
126
+ return desc
127
+
128
+ return None
129
+
130
+
131
+ def function_to_schema(func: Callable) -> Dict[str, Any]:
132
+ """
133
+ Convert a Python function to a JSON schema for agent function calling.
134
+
135
+ The function should have:
136
+ - Type hints for parameters
137
+ - A docstring with description and parameter descriptions
138
+
139
+ Args:
140
+ func: Python function to convert
141
+
142
+ Returns:
143
+ JSON schema dictionary with name, description, and parameters
144
+
145
+ Example:
146
+ >>> def get_weather(location: str, unit: str = "celsius") -> dict:
147
+ ... '''Get weather for a location.
148
+ ...
149
+ ... Args:
150
+ ... location: City name or coordinates
151
+ ... unit: Temperature unit (celsius or fahrenheit)
152
+ ... '''
153
+ ... pass
154
+ >>> schema = function_to_schema(get_weather)
155
+ >>> schema['name']
156
+ 'get_weather'
157
+ """
158
+ sig = inspect.signature(func)
159
+ doc = inspect.getdoc(func)
160
+
161
+ # Extract function description (first line/paragraph of docstring)
162
+ description = ""
163
+ if doc:
164
+ # Get everything before Args/Parameters section
165
+ lines = doc.split("\n")
166
+ desc_lines = []
167
+ for line in lines:
168
+ stripped = line.strip()
169
+ if stripped.lower() in [
170
+ "args:",
171
+ "arguments:",
172
+ "parameters:",
173
+ "params:",
174
+ "returns:",
175
+ "return:",
176
+ ]:
177
+ break
178
+ desc_lines.append(line)
179
+ description = "\n".join(desc_lines).strip()
180
+
181
+ if not description:
182
+ description = f"Call the {func.__name__} function"
183
+
184
+ # Build parameters schema
185
+ properties = {}
186
+ required = []
187
+
188
+ for param_name, param in sig.parameters.items():
189
+ # Skip self, cls, *args, **kwargs
190
+ if param_name in ["self", "cls"] or param.kind in [
191
+ inspect.Parameter.VAR_POSITIONAL,
192
+ inspect.Parameter.VAR_KEYWORD,
193
+ ]:
194
+ continue
195
+
196
+ # Get type annotation
197
+ param_type = (
198
+ param.annotation if param.annotation != inspect.Parameter.empty else Any
199
+ )
200
+ param_schema = python_type_to_json_schema_type(param_type)
201
+
202
+ # Get parameter description from docstring
203
+ param_desc = extract_param_description(doc, param_name)
204
+ if param_desc:
205
+ param_schema["description"] = param_desc
206
+
207
+ properties[param_name] = param_schema
208
+
209
+ # Mark as required if no default value
210
+ if param.default == inspect.Parameter.empty:
211
+ required.append(param_name)
212
+
213
+ schema = {
214
+ "name": func.__name__,
215
+ "description": description,
216
+ "parameters": {
217
+ "type": "object",
218
+ "properties": properties,
219
+ },
220
+ }
221
+
222
+ if required:
223
+ schema["parameters"]["required"] = required
224
+
225
+ return schema
226
+
227
+
228
+ def format_function_schemas_for_prompt(functions: List[Callable]) -> str:
229
+ """
230
+ Format function schemas as a string for inclusion in agent prompt.
231
+
232
+ Args:
233
+ functions: List of Python functions
234
+
235
+ Returns:
236
+ Formatted string describing available functions
237
+ """
238
+ if not functions:
239
+ return ""
240
+
241
+ schemas = [function_to_schema(func) for func in functions]
242
+
243
+ prompt = "You have access to the following functions that you can call:\n\n"
244
+
245
+ for schema in schemas:
246
+ prompt += f"Function: {schema['name']}\n"
247
+ prompt += f"Description: {schema['description']}\n"
248
+ prompt += f"Parameters: {json.dumps(schema['parameters'], indent=2)}\n\n"
249
+
250
+ prompt += """To call a function, include in your response:
251
+ <function-call>
252
+ {
253
+ "name": "function_name",
254
+ "arguments": {
255
+ "param1": "value1",
256
+ "param2": "value2"
257
+ }
258
+ }
259
+ </function-call>
260
+
261
+ You can call multiple functions by including multiple <function-call> blocks.
262
+ After calling functions, you will receive the results and can continue your response.
263
+ """
264
+
265
+ return prompt
augment/listener.py ADDED
@@ -0,0 +1,186 @@
1
+ """
2
+ Agent listener interface for receiving notifications about agent activity.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Optional
7
+
8
+
9
+ class AgentListener(ABC):
10
+ """
11
+ Interface for listening to agent events.
12
+
13
+ Implement this interface to receive notifications about what the agent is doing
14
+ during execution. This is useful for logging, debugging, or providing user feedback.
15
+
16
+ All methods are optional - you only need to implement the ones you care about.
17
+ """
18
+
19
+ def on_agent_message(self, message: str) -> None:
20
+ """
21
+ Called when the agent sends a complete message.
22
+
23
+ This is called with the agent's textual response, which may include
24
+ reasoning, explanations, or other context.
25
+
26
+ Args:
27
+ message: The complete message from the agent
28
+ """
29
+ pass
30
+
31
+ def on_tool_call(
32
+ self,
33
+ tool_call_id: str,
34
+ title: str,
35
+ kind: Optional[str] = None,
36
+ status: Optional[str] = None,
37
+ ) -> None:
38
+ """
39
+ Called when the agent makes a tool call.
40
+
41
+ Tools are things like:
42
+ - "view" - reading a file
43
+ - "str-replace-editor" - editing a file
44
+ - "launch-process" - running a command
45
+ - "save-file" - creating a new file
46
+ - "codebase-retrieval" - searching the codebase
47
+
48
+ Args:
49
+ tool_call_id: Unique identifier for this tool call
50
+ title: Human-readable description of what the tool is doing
51
+ kind: Category of tool (read, edit, delete, execute, etc.)
52
+ status: Current status (pending, in_progress, completed, failed)
53
+ """
54
+ pass
55
+
56
+ def on_tool_response(
57
+ self,
58
+ tool_call_id: str,
59
+ status: Optional[str] = None,
60
+ content: Optional[Any] = None,
61
+ ) -> None:
62
+ """
63
+ Called when a tool responds with results.
64
+
65
+ Args:
66
+ tool_call_id: Unique identifier for this tool call
67
+ status: Response status (completed, failed, etc.)
68
+ content: Response content/results from the tool
69
+ """
70
+ pass
71
+
72
+ def on_agent_thought(self, text: str) -> None:
73
+ """
74
+ Called when the agent shares its internal reasoning.
75
+
76
+ This provides insight into how the agent is thinking about the problem.
77
+
78
+ Args:
79
+ text: The thought content from the agent
80
+ """
81
+ pass
82
+
83
+ def on_function_call(
84
+ self,
85
+ function_name: str,
86
+ arguments: dict,
87
+ ) -> None:
88
+ """
89
+ Called when the agent calls a user-provided function.
90
+
91
+ Args:
92
+ function_name: Name of the function being called
93
+ arguments: Arguments passed to the function
94
+ """
95
+ pass
96
+
97
+ def on_function_result(
98
+ self,
99
+ function_name: str,
100
+ result: Any,
101
+ error: Optional[str] = None,
102
+ ) -> None:
103
+ """
104
+ Called when a user-provided function returns a result or error.
105
+
106
+ Args:
107
+ function_name: Name of the function that was called
108
+ result: The return value from the function (None if error)
109
+ error: Error message if the function raised an exception
110
+ """
111
+ pass
112
+
113
+
114
+ class LoggingAgentListener(AgentListener):
115
+ """
116
+ Simple logging implementation of AgentListener.
117
+
118
+ Prints all events to stdout with timestamps and formatting.
119
+ Useful for debugging and understanding what the agent is doing.
120
+ """
121
+
122
+ def __init__(self, verbose: bool = False):
123
+ """
124
+ Initialize the logging listener.
125
+
126
+ Args:
127
+ verbose: If True, logs all events. If False, only logs major events.
128
+ """
129
+ self.verbose = verbose
130
+
131
+ def on_agent_message(self, message: str) -> None:
132
+ """Log agent messages."""
133
+ print(f"\nšŸ’¬ Agent: {message[:200]}{'...' if len(message) > 200 else ''}")
134
+
135
+ def on_tool_call(
136
+ self,
137
+ tool_call_id: str,
138
+ title: str,
139
+ kind: Optional[str] = None,
140
+ status: Optional[str] = None,
141
+ ) -> None:
142
+ """Log tool calls."""
143
+ if self.verbose or status in ["pending", None]:
144
+ icon = "šŸ”§" if kind != "read" else "šŸ“–"
145
+ print(f"{icon} Tool: {title} ({kind or 'unknown'})")
146
+
147
+ def on_tool_response(
148
+ self,
149
+ tool_call_id: str,
150
+ status: Optional[str] = None,
151
+ content: Optional[Any] = None,
152
+ ) -> None:
153
+ """Log tool responses."""
154
+ if self.verbose:
155
+ icon = "āœ…" if status == "completed" else "āŒ"
156
+ print(f" {icon} Tool response: {status}")
157
+
158
+ def on_agent_thought(self, text: str) -> None:
159
+ """Log agent thoughts."""
160
+ if self.verbose:
161
+ print(f"šŸ’­ Thinking: {text[:100]}{'...' if len(text) > 100 else ''}")
162
+
163
+ def on_function_call(
164
+ self,
165
+ function_name: str,
166
+ arguments: dict,
167
+ ) -> None:
168
+ """Log function calls."""
169
+ print(
170
+ f"šŸ“ž Calling function: {function_name}({', '.join(f'{k}={v}' for k, v in arguments.items())})"
171
+ )
172
+
173
+ def on_function_result(
174
+ self,
175
+ function_name: str,
176
+ result: Any,
177
+ error: Optional[str] = None,
178
+ ) -> None:
179
+ """Log function results."""
180
+ if error:
181
+ print(f" āŒ Function {function_name} failed: {error}")
182
+ else:
183
+ result_str = str(result)
184
+ if len(result_str) > 100:
185
+ result_str = result_str[:100] + "..."
186
+ print(f" āœ… Function {function_name} returned: {result_str}")
@@ -0,0 +1,83 @@
1
+ """
2
+ Adapter to bridge ACP AgentEventListener to Agent AgentListener.
3
+ """
4
+
5
+ from typing import Any, Optional
6
+
7
+ from .acp import AgentEventListener
8
+ from .listener import AgentListener
9
+
10
+
11
+ class AgentListenerAdapter(AgentEventListener):
12
+ """
13
+ Adapter that converts ACP events to AgentListener events.
14
+
15
+ This class implements AgentEventListener (the ACP interface) and forwards
16
+ relevant events to an AgentListener (the user-facing interface).
17
+ """
18
+
19
+ def __init__(self, agent_listener: Optional[AgentListener] = None):
20
+ """
21
+ Initialize the adapter.
22
+
23
+ Args:
24
+ agent_listener: The user's AgentListener to forward events to
25
+ """
26
+ self.agent_listener = agent_listener
27
+
28
+ def on_agent_message_chunk(self, text: str) -> None:
29
+ """
30
+ Called when the agent sends a message chunk (streaming).
31
+
32
+ We don't forward chunks to AgentListener since it doesn't support streaming.
33
+ We'll accumulate them and send the complete message in on_agent_message.
34
+ """
35
+ # AgentListener doesn't support streaming, so we ignore chunks
36
+ pass
37
+
38
+ def on_tool_call(
39
+ self,
40
+ tool_call_id: str,
41
+ title: str,
42
+ kind: Optional[str] = None,
43
+ status: Optional[str] = None,
44
+ ) -> None:
45
+ """
46
+ Called when the agent makes a tool call.
47
+
48
+ Forward to AgentListener if present.
49
+ """
50
+ if self.agent_listener:
51
+ self.agent_listener.on_tool_call(tool_call_id, title, kind, status)
52
+
53
+ def on_tool_response(
54
+ self,
55
+ tool_call_id: str,
56
+ status: Optional[str] = None,
57
+ content: Optional[Any] = None,
58
+ ) -> None:
59
+ """
60
+ Called when a tool responds with results.
61
+
62
+ Forward to AgentListener if present.
63
+ """
64
+ if self.agent_listener:
65
+ self.agent_listener.on_tool_response(tool_call_id, status, content)
66
+
67
+ def on_agent_thought(self, text: str) -> None:
68
+ """
69
+ Called when the agent shares its internal reasoning.
70
+
71
+ Forward to AgentListener if present.
72
+ """
73
+ if self.agent_listener:
74
+ self.agent_listener.on_agent_thought(text)
75
+
76
+ def on_agent_message(self, message: str) -> None:
77
+ """
78
+ Called when the agent finishes sending a complete message.
79
+
80
+ Forward to AgentListener if present.
81
+ """
82
+ if self.agent_listener:
83
+ self.agent_listener.on_agent_message(message)