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/__init__.py +30 -0
- augment/acp/__init__.py +11 -0
- augment/acp/claude_code_client.py +365 -0
- augment/acp/client.py +640 -0
- augment/acp/test_client_e2e.py +472 -0
- augment/agent.py +1139 -0
- augment/exceptions.py +92 -0
- augment/function_tools.py +265 -0
- augment/listener.py +186 -0
- augment/listener_adapter.py +83 -0
- augment/prompt_formatter.py +343 -0
- augment_sdk-0.1.1.dist-info/METADATA +841 -0
- augment_sdk-0.1.1.dist-info/RECORD +17 -0
- augment_sdk-0.1.1.dist-info/WHEEL +5 -0
- augment_sdk-0.1.1.dist-info/entry_points.txt +2 -0
- augment_sdk-0.1.1.dist-info/licenses/LICENSE +22 -0
- augment_sdk-0.1.1.dist-info/top_level.txt +1 -0
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)
|