gofannon 0.25.16__py3-none-any.whl → 0.25.18__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.
- gofannon/base/__init__.py +2 -0
- gofannon/base/llamastack.py +266 -0
- gofannon/file/__init__.py +0 -0
- gofannon/file/list_directory.py +82 -0
- gofannon/file/read_file.py +56 -0
- gofannon/file/write_file.py +46 -0
- gofannon/github/clone_repo.py +64 -0
- gofannon/github/create_issue.py +3 -6
- gofannon/github/list_issues.py +110 -0
- {gofannon-0.25.16.dist-info → gofannon-0.25.18.dist-info}/METADATA +1 -1
- {gofannon-0.25.16.dist-info → gofannon-0.25.18.dist-info}/RECORD +13 -6
- {gofannon-0.25.16.dist-info → gofannon-0.25.18.dist-info}/LICENSE +0 -0
- {gofannon-0.25.16.dist-info → gofannon-0.25.18.dist-info}/WHEEL +0 -0
gofannon/base/__init__.py
CHANGED
@@ -15,6 +15,7 @@ from .langchain import LangchainMixin
|
|
15
15
|
from .bedrock import BedrockMixin
|
16
16
|
from .langflow import LangflowMixin
|
17
17
|
from .mcp import MCPMixin
|
18
|
+
from .llamastack import LlamaStackMixin
|
18
19
|
|
19
20
|
|
20
21
|
@dataclass
|
@@ -72,6 +73,7 @@ class BaseTool(SmolAgentsMixin,
|
|
72
73
|
BedrockMixin,
|
73
74
|
LangflowMixin,
|
74
75
|
MCPMixin,
|
76
|
+
LlamaStackMixin,
|
75
77
|
ABC):
|
76
78
|
def __init__(self, **kwargs):
|
77
79
|
self.logger = logging.getLogger(
|
@@ -0,0 +1,266 @@
|
|
1
|
+
# gofannon/base/llamastack.py
|
2
|
+
|
3
|
+
import inspect
|
4
|
+
import re
|
5
|
+
from textwrap import dedent
|
6
|
+
from typing import Callable, Any
|
7
|
+
|
8
|
+
# Type mapping from Gofannon definition to Python types (primarily for potential future use,
|
9
|
+
# as Llama Stack example doesn't heavily rely on type hints in the generated function)
|
10
|
+
GOFANNON_TO_PYTHON_TYPE_MAP = {
|
11
|
+
"string": str,
|
12
|
+
"number": float,
|
13
|
+
"integer": int,
|
14
|
+
"boolean": bool,
|
15
|
+
"object": dict,
|
16
|
+
"array": list,
|
17
|
+
}
|
18
|
+
|
19
|
+
# Type mapping from Python types/common hints to Gofannon definition types
|
20
|
+
PYTHON_TO_GOFANNON_TYPE_MAP = {
|
21
|
+
str: "string",
|
22
|
+
float: "number",
|
23
|
+
int: "integer",
|
24
|
+
bool: "boolean",
|
25
|
+
dict: "object",
|
26
|
+
list: "array",
|
27
|
+
Any: "string", # Default fallback
|
28
|
+
type(None): "string" # Default fallback for untyped
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
class LlamaStackMixin:
|
33
|
+
"""
|
34
|
+
Mixin for converting Gofannon tools to and from the Llama Stack custom tool format.
|
35
|
+
|
36
|
+
Llama Stack expects custom tools as Python functions with specific docstrings.
|
37
|
+
See: https://github.com/meta-llama/llama-stack/blob/main/docs/tools.md#adding-custom-tools
|
38
|
+
"""
|
39
|
+
|
40
|
+
def _parse_llamastack_docstring(self, docstring: str) -> tuple[str, dict[str, str], list[str]]:
|
41
|
+
"""
|
42
|
+
Parses a Llama Stack style docstring to extract description and parameters.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
docstring: The docstring content.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
A tuple containing:
|
49
|
+
- description (str): The main tool description.
|
50
|
+
- params (dict): A dictionary mapping parameter names to their descriptions.
|
51
|
+
- required (list): A list of required parameter names (inferred if not explicitly optional).
|
52
|
+
NOTE: Llama Stack's doc example doesn't show optional syntax, so we assume all
|
53
|
+
documented params are required unless function signature has defaults.
|
54
|
+
"""
|
55
|
+
if not docstring:
|
56
|
+
return "No description provided.", {}, []
|
57
|
+
|
58
|
+
lines = docstring.strip().split('\n')
|
59
|
+
description_lines = []
|
60
|
+
params = {}
|
61
|
+
param_section_started = False
|
62
|
+
|
63
|
+
# Simple regex to find ':param <name>:' or ':param <name> (<type>):'
|
64
|
+
param_regex = re.compile(r":param\s+([\w_]+)(?:\s*\([^)]+\))?:\s*(.*)")
|
65
|
+
|
66
|
+
for line in lines:
|
67
|
+
line = line.strip()
|
68
|
+
if not line: # Skip empty lines between description and params
|
69
|
+
if description_lines and not param_section_started:
|
70
|
+
param_section_started = True # Assume blank line separates desc from params
|
71
|
+
continue
|
72
|
+
|
73
|
+
match = param_regex.match(line)
|
74
|
+
if match:
|
75
|
+
param_section_started = True
|
76
|
+
param_name = match.group(1)
|
77
|
+
param_desc = match.group(2).strip()
|
78
|
+
params[param_name] = param_desc
|
79
|
+
elif not param_section_started:
|
80
|
+
description_lines.append(line)
|
81
|
+
|
82
|
+
description = " ".join(description_lines).strip()
|
83
|
+
# Llama Stack example doesn't specify required/optional in docstring.
|
84
|
+
# We'll infer from function signature later.
|
85
|
+
# For now, return an empty required list based purely on docstring parsing.
|
86
|
+
required = list(params.keys()) # Assume all documented params are required initially
|
87
|
+
|
88
|
+
return description, params, required
|
89
|
+
|
90
|
+
def import_from_llamastack(self, llamastack_tool_func: Callable):
|
91
|
+
"""
|
92
|
+
Adapts a Llama Stack custom tool function to a Gofannon tool.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
llamastack_tool_func: The Python function representing the Llama Stack tool.
|
96
|
+
"""
|
97
|
+
if not callable(llamastack_tool_func):
|
98
|
+
raise ValueError("Input must be a callable function.")
|
99
|
+
|
100
|
+
self.name = llamastack_tool_func.__name__
|
101
|
+
docstring = inspect.getdoc(llamastack_tool_func) or ""
|
102
|
+
|
103
|
+
# Parse docstring for description and param descriptions
|
104
|
+
description, param_descriptions, _ = self._parse_llamastack_docstring(docstring)
|
105
|
+
self.description = description # Set top-level description
|
106
|
+
|
107
|
+
# Use inspect.signature to get parameter names, types, and defaults
|
108
|
+
try:
|
109
|
+
sig = inspect.signature(llamastack_tool_func)
|
110
|
+
parameters_properties = {}
|
111
|
+
required_params = []
|
112
|
+
|
113
|
+
for name, param in sig.parameters.items():
|
114
|
+
param_type_hint = param.annotation
|
115
|
+
gofannon_type = PYTHON_TO_GOFANNON_TYPE_MAP.get(param_type_hint, "string") # Default to string if unknown
|
116
|
+
|
117
|
+
parameters_properties[name] = {
|
118
|
+
"type": gofannon_type,
|
119
|
+
"description": param_descriptions.get(name, f"Parameter '{name}'") # Use parsed desc or default
|
120
|
+
}
|
121
|
+
|
122
|
+
# If the parameter has no default value, it's required
|
123
|
+
if param.default is inspect.Parameter.empty:
|
124
|
+
required_params.append(name)
|
125
|
+
|
126
|
+
self.definition = {
|
127
|
+
"function": {
|
128
|
+
"name": self.name,
|
129
|
+
"description": self.description,
|
130
|
+
"parameters": {
|
131
|
+
"type": "object",
|
132
|
+
"properties": parameters_properties,
|
133
|
+
"required": required_params,
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
except Exception as e:
|
139
|
+
# Fallback if signature inspection fails (e.g., built-in function)
|
140
|
+
# Create a basic definition based only on name/docstring
|
141
|
+
self.logger.warning(f"Could not inspect signature for {self.name}: {e}. Creating basic definition.")
|
142
|
+
self.definition = {
|
143
|
+
"function": {
|
144
|
+
"name": self.name,
|
145
|
+
"description": self.description,
|
146
|
+
"parameters": { # Assume no parameters if signature fails
|
147
|
+
"type": "object",
|
148
|
+
"properties": {},
|
149
|
+
"required": []
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
|
155
|
+
# The core function is the Llama Stack function itself
|
156
|
+
self.fn = llamastack_tool_func
|
157
|
+
|
158
|
+
self.logger.info(f"Imported Llama Stack tool: {self.name}")
|
159
|
+
|
160
|
+
|
161
|
+
def export_to_llamastack(self) -> Callable:
|
162
|
+
"""
|
163
|
+
Convert the Gofannon tool to a Llama Stack compatible function.
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
A callable function with a Llama Stack-style docstring.
|
167
|
+
"""
|
168
|
+
gofannon_def = self.definition.get("function", {})
|
169
|
+
tool_name = gofannon_def.get("name", getattr(self, "name", "gofannon_exported_tool"))
|
170
|
+
tool_description = gofannon_def.get("description", getattr(self, "description", "No description provided."))
|
171
|
+
parameters = gofannon_def.get("parameters", {})
|
172
|
+
param_properties = parameters.get("properties", {})
|
173
|
+
required_params = parameters.get("required", [])
|
174
|
+
|
175
|
+
# Construct the docstring
|
176
|
+
docstring_lines = [tool_description, ""]
|
177
|
+
param_lines = []
|
178
|
+
arg_names = []
|
179
|
+
|
180
|
+
for param_name, param_def in param_properties.items():
|
181
|
+
param_desc = param_def.get("description", "")
|
182
|
+
# Llama Stack example doesn't show type in docstring, just param name and description
|
183
|
+
# param_type = param_def.get("type", "string") # Could potentially add :type if needed
|
184
|
+
docstring_line = f":param {param_name}: {param_desc}"
|
185
|
+
# NOTE: Llama Stack example doesn't explicitly mark required/optional in docstring
|
186
|
+
# We could add (required) or (optional) if desired, but sticking to example format.
|
187
|
+
# if param_name in required_params:
|
188
|
+
# docstring_line += " (required)"
|
189
|
+
param_lines.append(docstring_line)
|
190
|
+
arg_names.append(param_name)
|
191
|
+
|
192
|
+
if param_lines:
|
193
|
+
docstring_lines.extend(param_lines)
|
194
|
+
docstring_lines.append("") # Add blank line after params if any
|
195
|
+
|
196
|
+
# Add a basic return description (Gofannon definition doesn't store this explicitly)
|
197
|
+
docstring_lines.append(":return: The result of executing the tool.")
|
198
|
+
|
199
|
+
final_docstring = "\n".join(docstring_lines)
|
200
|
+
|
201
|
+
# Create the wrapper function dynamically
|
202
|
+
original_fn = self.fn
|
203
|
+
args_string = ", ".join(arg_names)
|
204
|
+
|
205
|
+
# We need to define the function in a scope where original_fn is accessible
|
206
|
+
# Using exec can be risky, but is one way to dynamically create a function
|
207
|
+
# with a specific signature and docstring. A safer alternative might involve
|
208
|
+
# function factories or functools.wraps if the signature complexity allows.
|
209
|
+
|
210
|
+
# Let's try a closure approach which is generally safer:
|
211
|
+
def make_wrapper(original_func, doc, name, signature_args):
|
212
|
+
# Construct the function signature string dynamically
|
213
|
+
sig_str = f"({', '.join(signature_args)})"
|
214
|
+
# Use eval to create the function with the correct signature
|
215
|
+
# This is still somewhat risky, ensure signature_args are sanitized if needed.
|
216
|
+
# Define the wrapper within this factory function's scope
|
217
|
+
def wrapper(*args, **kwargs):
|
218
|
+
# Map positional args to keywords if necessary, or rely on kwargs
|
219
|
+
call_kwargs = {}
|
220
|
+
if args:
|
221
|
+
for i, arg_val in enumerate(args):
|
222
|
+
if i < len(signature_args):
|
223
|
+
call_kwargs[signature_args[i]] = arg_val
|
224
|
+
else:
|
225
|
+
# Handle extra positional args if necessary, maybe raise error?
|
226
|
+
pass
|
227
|
+
call_kwargs.update(kwargs)
|
228
|
+
return original_func(**call_kwargs)
|
229
|
+
|
230
|
+
wrapper.__doc__ = doc
|
231
|
+
wrapper.__name__ = name
|
232
|
+
# Try to mimic signature for inspection tools (might not be perfect)
|
233
|
+
try:
|
234
|
+
# Build parameter list for inspect.Signature
|
235
|
+
params = [inspect.Parameter(arg, inspect.Parameter.POSITIONAL_OR_KEYWORD) for arg in signature_args]
|
236
|
+
wrapper.__signature__ = inspect.Signature(parameters=params)
|
237
|
+
except Exception as e:
|
238
|
+
self.logger.warning(f"Could not set __signature__ for {name}: {e}")
|
239
|
+
|
240
|
+
return wrapper
|
241
|
+
|
242
|
+
# Generate the arguments list for the signature
|
243
|
+
signature_args_list = list(param_properties.keys())
|
244
|
+
exported_function = make_wrapper(original_fn, final_docstring, tool_name, signature_args_list)
|
245
|
+
|
246
|
+
|
247
|
+
# Alternative using exec (use with caution):
|
248
|
+
# func_code = f"""
|
249
|
+
# def {tool_name}({args_string}):
|
250
|
+
# '''{final_docstring}'''
|
251
|
+
# # Prepare kwargs for the original function
|
252
|
+
# kwargs_for_original = {{}}
|
253
|
+
# """
|
254
|
+
# for arg_name in arg_names:
|
255
|
+
# func_code += f" kwargs_for_original['{arg_name}'] = {arg_name}\n"
|
256
|
+
#
|
257
|
+
# func_code += f"""
|
258
|
+
# return original_fn(**kwargs_for_original)
|
259
|
+
# """
|
260
|
+
# local_scope = {'original_fn': original_fn}
|
261
|
+
# exec(dedent(func_code), local_scope)
|
262
|
+
# exported_function = local_scope[tool_name]
|
263
|
+
|
264
|
+
self.logger.info(f"Exported Gofannon tool '{self.name}' to Llama Stack format as function '{tool_name}'")
|
265
|
+
return exported_function
|
266
|
+
|
File without changes
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from..base import BaseTool
|
4
|
+
from ..config import FunctionRegistry
|
5
|
+
import logging
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
@FunctionRegistry.register
|
10
|
+
class ListDirectory(BaseTool):
|
11
|
+
def __init__(self, name="list_directory"):
|
12
|
+
super().__init__()
|
13
|
+
self.name = name
|
14
|
+
|
15
|
+
@property
|
16
|
+
def definition(self):
|
17
|
+
return {
|
18
|
+
"type": "function",
|
19
|
+
"function": {
|
20
|
+
"name": self.name,
|
21
|
+
"description": "List the contents of a directory recursively in a tree-like format",
|
22
|
+
"parameters": {
|
23
|
+
"type": "object",
|
24
|
+
"properties": {
|
25
|
+
"directory_path": {
|
26
|
+
"type": "string",
|
27
|
+
"description": "The path of the directory to list"
|
28
|
+
},
|
29
|
+
"max_depth": {
|
30
|
+
"type": "integer",
|
31
|
+
"description": "Maximum depth to recurse into (default: 5)",
|
32
|
+
"default": 5
|
33
|
+
}
|
34
|
+
},
|
35
|
+
"required": ["directory_path"]
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
def _build_tree(self, path, prefix="", depth=0, max_depth=5):
|
41
|
+
if depth > max_depth:
|
42
|
+
return ""
|
43
|
+
|
44
|
+
try:
|
45
|
+
entries = os.listdir(path)
|
46
|
+
except PermissionError:
|
47
|
+
return f"{prefix}[Permission Denied]\n"
|
48
|
+
except FileNotFoundError:
|
49
|
+
return f"{prefix}[Directory Not Found]\n"
|
50
|
+
|
51
|
+
tree = ""
|
52
|
+
entries.sort()
|
53
|
+
length = len(entries)
|
54
|
+
|
55
|
+
for i, entry in enumerate(entries):
|
56
|
+
full_path = os.path.join(path, entry)
|
57
|
+
is_last = i == length - 1
|
58
|
+
|
59
|
+
if os.path.isdir(full_path):
|
60
|
+
tree += f"{prefix}{'└── ' if is_last else '├── '}{entry}/\n"
|
61
|
+
tree += self._build_tree(
|
62
|
+
full_path,
|
63
|
+
prefix + (" " if is_last else "│ "),
|
64
|
+
depth + 1,
|
65
|
+
max_depth
|
66
|
+
)
|
67
|
+
else:
|
68
|
+
tree += f"{prefix}{'└── ' if is_last else '├── '}{entry}\n"
|
69
|
+
|
70
|
+
return tree
|
71
|
+
|
72
|
+
def fn(self, directory_path, max_depth=5):
|
73
|
+
logger.debug(f"Listing directory: {directory_path}")
|
74
|
+
|
75
|
+
if not os.path.exists(directory_path):
|
76
|
+
return f"Error: Directory '{directory_path}' does not exist"
|
77
|
+
|
78
|
+
if not os.path.isdir(directory_path):
|
79
|
+
return f"Error: '{directory_path}' is not a directory"
|
80
|
+
|
81
|
+
tree = self._build_tree(directory_path, max_depth=max_depth)
|
82
|
+
return f"{directory_path}/\n{tree}"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from..base import BaseTool
|
4
|
+
from ..config import FunctionRegistry
|
5
|
+
import logging
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
@FunctionRegistry.register
|
10
|
+
class ReadFile(BaseTool):
|
11
|
+
"""
|
12
|
+
A tool for reading the contents of a file from the local filesystem.
|
13
|
+
|
14
|
+
This class provides a function that takes a file path as input and returns
|
15
|
+
the contents of that file as a string.
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
file_path (str): The path to the file that should be read.
|
19
|
+
"""
|
20
|
+
def __init__(self, name="read_file"):
|
21
|
+
super().__init__()
|
22
|
+
self.name = name
|
23
|
+
|
24
|
+
@property
|
25
|
+
def definition(self):
|
26
|
+
return {
|
27
|
+
"type": "function",
|
28
|
+
"function": {
|
29
|
+
"name": self.name,
|
30
|
+
"description": "Read the contents of a specified file.",
|
31
|
+
"parameters": {
|
32
|
+
"type": "object",
|
33
|
+
"properties": {
|
34
|
+
"file_path": {
|
35
|
+
"type": "string",
|
36
|
+
"description": "The path to the file to be read."
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"required": ["file_path"]
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
def fn(self, file_path):
|
45
|
+
logger.debug(f"Reading file: {file_path}")
|
46
|
+
try:
|
47
|
+
if not os.path.exists(file_path):
|
48
|
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
49
|
+
|
50
|
+
with open(file_path, 'r') as file:
|
51
|
+
content = file.read()
|
52
|
+
|
53
|
+
return content
|
54
|
+
except Exception as e:
|
55
|
+
logger.error(f"Error reading file: {e}")
|
56
|
+
return f"Error reading file: {e}"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from..base import BaseTool
|
2
|
+
from ..config import FunctionRegistry
|
3
|
+
import logging
|
4
|
+
|
5
|
+
logger = logging.getLogger(__name__)
|
6
|
+
|
7
|
+
@FunctionRegistry.register
|
8
|
+
class WriteFile(BaseTool):
|
9
|
+
def __init__(self, name="write_file"):
|
10
|
+
super().__init__()
|
11
|
+
self.name = name
|
12
|
+
|
13
|
+
@property
|
14
|
+
def definition(self):
|
15
|
+
return {
|
16
|
+
"type": "function",
|
17
|
+
"function": {
|
18
|
+
"name": self.name,
|
19
|
+
"description": "Write the contents of a sting to a specified file.",
|
20
|
+
"parameters": {
|
21
|
+
"type": "object",
|
22
|
+
"properties": {
|
23
|
+
"file_path": {
|
24
|
+
"type": "string",
|
25
|
+
"description": "The path to the file to be written."
|
26
|
+
},
|
27
|
+
"content": {
|
28
|
+
"type": "string",
|
29
|
+
"description": "The content to be written to the file."
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"required": ["file_path", "content"]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
def fn(self, file_path, content):
|
38
|
+
logger.debug(f"Writing file: {file_path}")
|
39
|
+
try:
|
40
|
+
with open(file_path, 'w') as file:
|
41
|
+
file.write(content)
|
42
|
+
|
43
|
+
return f"File {file_path} written successfully."
|
44
|
+
except Exception as e:
|
45
|
+
logger.error(f"Error writing file: {e}")
|
46
|
+
return f"Error writing file: {e}"
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import git
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
from..base import BaseTool
|
5
|
+
from ..config import FunctionRegistry
|
6
|
+
import logging
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
@FunctionRegistry.register
|
11
|
+
class CloneRepo(BaseTool):
|
12
|
+
"""
|
13
|
+
Clone a GitHub repository to a specified local directory.
|
14
|
+
This tool takes a GitHub repository URL and a target local directory,
|
15
|
+
then clones the repository into that directory using GitPython.
|
16
|
+
Returns a success message if the operation completes successfully,
|
17
|
+
or an error message if it fails.
|
18
|
+
"""
|
19
|
+
def __init__(self, name="clone_github_repo"):
|
20
|
+
super().__init__()
|
21
|
+
self.name = name
|
22
|
+
|
23
|
+
@property
|
24
|
+
def definition(self):
|
25
|
+
return {
|
26
|
+
"type": "function",
|
27
|
+
"function": {
|
28
|
+
"name": self.name,
|
29
|
+
"description": "Clone a GitHub repository to a specified local directory.",
|
30
|
+
"parameters": {
|
31
|
+
"type": "object",
|
32
|
+
"properties": {
|
33
|
+
"repo_url": {
|
34
|
+
"type": "string",
|
35
|
+
"description": "The URL of the GitHub repository to clone."
|
36
|
+
},
|
37
|
+
"local_dir": {
|
38
|
+
"type": "string",
|
39
|
+
"description": "The local directory where the repository should be cloned."
|
40
|
+
}
|
41
|
+
},
|
42
|
+
"required": ["repo_url", "local_dir"]
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
def fn(self, repo_url, local_dir):
|
48
|
+
logger.debug(f"Cloning repository {repo_url} to {local_dir}")
|
49
|
+
|
50
|
+
# Ensure the local directory exists
|
51
|
+
local_dir_path = Path(local_dir)
|
52
|
+
if not local_dir_path.exists():
|
53
|
+
local_dir_path.mkdir(parents=True, exist_ok=True)
|
54
|
+
|
55
|
+
try:
|
56
|
+
# Clone the repository
|
57
|
+
repo = git.Repo.clone_from(repo_url, local_dir_path)
|
58
|
+
return f"Repository cloned successfully to {local_dir}"
|
59
|
+
except git.exc.GitCommandError as e:
|
60
|
+
logger.error(f"Error cloning repository: {e}")
|
61
|
+
return f"Error cloning repository: {e}"
|
62
|
+
except Exception as e:
|
63
|
+
logger.error(f"Unexpected error: {e}")
|
64
|
+
return f"Unexpected error: {e}"
|
gofannon/github/create_issue.py
CHANGED
@@ -39,11 +39,8 @@ class CreateIssue(BaseTool):
|
|
39
39
|
"description": "The body of the issue"
|
40
40
|
},
|
41
41
|
"labels": {
|
42
|
-
"type": "
|
43
|
-
"
|
44
|
-
"type": "string"
|
45
|
-
},
|
46
|
-
"description": "An array of labels for the issue"
|
42
|
+
"type": "string",
|
43
|
+
"description": "A comma separated list of labels to apply to the issue"
|
47
44
|
}
|
48
45
|
},
|
49
46
|
"required": ["repo_url", "title", "body"]
|
@@ -70,7 +67,7 @@ class CreateIssue(BaseTool):
|
|
70
67
|
}
|
71
68
|
|
72
69
|
if labels:
|
73
|
-
payload["labels"] = labels
|
70
|
+
payload["labels"] = labels.split(',')
|
74
71
|
|
75
72
|
response = post(api_url, headers=headers, json=payload)
|
76
73
|
response.raise_for_status()
|
@@ -0,0 +1,110 @@
|
|
1
|
+
from..base import BaseTool
|
2
|
+
from requests import get
|
3
|
+
import json
|
4
|
+
from ..config import FunctionRegistry
|
5
|
+
import logging
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
@FunctionRegistry.register
|
11
|
+
class ListIssues(BaseTool):
|
12
|
+
def __init__(self, api_key=None, name="list_issues"):
|
13
|
+
super().__init__()
|
14
|
+
self.api_key = api_key
|
15
|
+
self.name = name
|
16
|
+
self.API_SERVICE = 'github'
|
17
|
+
|
18
|
+
@property
|
19
|
+
def definition(self):
|
20
|
+
return {
|
21
|
+
"type": "function",
|
22
|
+
"function": {
|
23
|
+
"name": self.name,
|
24
|
+
"description": "List all issues in a GitHub repository",
|
25
|
+
"parameters": {
|
26
|
+
"type": "object",
|
27
|
+
"properties": {
|
28
|
+
"repo_url": {
|
29
|
+
"type": "string",
|
30
|
+
"description": "The URL of the repository, e.g. https://github.com/The-AI-Alliance/gofannon"
|
31
|
+
},
|
32
|
+
"state": {
|
33
|
+
"type": "string",
|
34
|
+
"enum": ["open", "closed", "all"],
|
35
|
+
"description": "Filter issues by state (default: open)",
|
36
|
+
"default": "open"
|
37
|
+
},
|
38
|
+
"labels": {
|
39
|
+
"type": "string",
|
40
|
+
"description": "Comma-separated list of label names to filter by"
|
41
|
+
},
|
42
|
+
"sort": {
|
43
|
+
"type": "string",
|
44
|
+
"enum": ["created", "updated", "comments"],
|
45
|
+
"description": "What to sort results by (default: created)",
|
46
|
+
"default": "created"
|
47
|
+
},
|
48
|
+
"direction": {
|
49
|
+
"type": "string",
|
50
|
+
"enum": ["asc", "desc"],
|
51
|
+
"description": "Sort direction (default: desc)",
|
52
|
+
"default": "desc"
|
53
|
+
},
|
54
|
+
"since": {
|
55
|
+
"type": "string",
|
56
|
+
"description": "Only show issues updated after this date (ISO 8601 format)"
|
57
|
+
}
|
58
|
+
},
|
59
|
+
"required": ["repo_url"]
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
def fn(self, repo_url, state="open", labels=None, sort="created", direction="desc", since=None):
|
65
|
+
logger.debug(f"Listing issues for repo {repo_url} with state={state}")
|
66
|
+
# Extracting the owner and repo name from the URL
|
67
|
+
repo_parts = repo_url.rstrip('/').split('/')
|
68
|
+
owner = repo_parts[-2]
|
69
|
+
repo = repo_parts[-1]
|
70
|
+
|
71
|
+
api_url = f"https://api.github.com/repos/{owner}/{repo}/issues"
|
72
|
+
headers = {
|
73
|
+
'Authorization': f'token {self.api_key}',
|
74
|
+
'Accept': 'application/vnd.github.v3+json'
|
75
|
+
}
|
76
|
+
params = {
|
77
|
+
'state': state,
|
78
|
+
'sort': sort,
|
79
|
+
'direction': direction
|
80
|
+
}
|
81
|
+
|
82
|
+
if labels:
|
83
|
+
params['labels'] = labels
|
84
|
+
if since:
|
85
|
+
params['since'] = since
|
86
|
+
|
87
|
+
response = get(api_url, headers=headers, params=params)
|
88
|
+
response.raise_for_status()
|
89
|
+
|
90
|
+
issues = response.json()
|
91
|
+
|
92
|
+
# Format the response
|
93
|
+
formatted_issues = []
|
94
|
+
for issue in issues:
|
95
|
+
# Skip pull requests (GitHub API returns PRs as issues too)
|
96
|
+
if 'pull_request' in issue:
|
97
|
+
continue
|
98
|
+
|
99
|
+
formatted_issues.append({
|
100
|
+
"number": issue['number'],
|
101
|
+
"title": issue['title'],
|
102
|
+
"state": issue['state'],
|
103
|
+
"created_at": issue['created_at'],
|
104
|
+
"updated_at": issue['updated_at'],
|
105
|
+
"html_url": issue['html_url'],
|
106
|
+
"labels": [label['name'] for label in issue['labels']],
|
107
|
+
"user": issue['user']['login']
|
108
|
+
})
|
109
|
+
|
110
|
+
return formatted_issues
|
@@ -2,10 +2,11 @@ gofannon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
gofannon/arxiv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
gofannon/arxiv/get_article.py,sha256=SRGTXFXdXdXTIOLZKWUTXxZEYEqZFWFJEV2nTsU5qqU,1167
|
4
4
|
gofannon/arxiv/search.py,sha256=37Zx1y2vAX1xYIKaAxzBGXE3qPHUZdAD2XR0H1Acs-4,4225
|
5
|
-
gofannon/base/__init__.py,sha256=
|
5
|
+
gofannon/base/__init__.py,sha256=wnTkASBszxkZiWugQOgvSdAghFFBUwxzRzz7wTMxg3c,3757
|
6
6
|
gofannon/base/bedrock.py,sha256=Z2c36R8jaIusgpmegbYVz2eR7lDBc0IhTtwiqUGOcT4,25646
|
7
7
|
gofannon/base/langchain.py,sha256=25z9opy7E7qWP-DSn6oYAqKDg8i21az-kAKrsYLfyiQ,2704
|
8
8
|
gofannon/base/langflow.py,sha256=0WfNJ9MnndyLJ-yUAStIuXZpCzOPItsGKgtxdNifmLM,3833
|
9
|
+
gofannon/base/llamastack.py,sha256=M1820wdKgNBDca12fjO5PMWyhHu7Inj0qSioKqmFj8o,11251
|
9
10
|
gofannon/base/mcp.py,sha256=2e7f1FiD_M7wk4gOd7YNt4zLBgdLQTnWWYL0Kf5CzTY,253
|
10
11
|
gofannon/base/smol_agents.py,sha256=p2YU5BrscavGk5X9R7HSJmtVR5OpOsQwCd9hwC9eBMk,2229
|
11
12
|
gofannon/basic_math/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
@@ -15,11 +16,17 @@ gofannon/basic_math/exponents.py,sha256=w4qDlFZ9M1lf6X-tjG-ndpECfCOS7Qtc_VLICw0o
|
|
15
16
|
gofannon/basic_math/multiplication.py,sha256=PJ5sKWMCVlBaTeZ_j3gwYOEQXAhN-qIXhnrNcyhWGKM,1168
|
16
17
|
gofannon/basic_math/subtraction.py,sha256=gM1_N1mZ3qAXC6qnkzfijKXiOx27Gg93-CaB_ifMbOQ,1164
|
17
18
|
gofannon/config.py,sha256=KPVtjBnpwfM39EQg-_xiqL1UFxcuQehrQIUS6HHBP6k,1784
|
19
|
+
gofannon/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
gofannon/file/list_directory.py,sha256=Jw0F50_hdtyDSFBmcy9e8F4KajtsPyT8Gsm_eyjMUuQ,2679
|
21
|
+
gofannon/file/read_file.py,sha256=kYf-4aGCHLj-D-XQs0YBEnbB1MbIccgcWbiLnVXidp4,1669
|
22
|
+
gofannon/file/write_file.py,sha256=fQ5P4cqZKdoq8Bk7nys2Esau0Q5WvbeY8srGvfx36NQ,1495
|
18
23
|
gofannon/github/__init__.py,sha256=VFw4sJIt4Zc0-__eYnksN8Ku9qMhbPpHJEkXMWUiD30,4
|
24
|
+
gofannon/github/clone_repo.py,sha256=UNXh1JaZkzK94REJUfQrBAhF66ncFWkzcZj34Si34cI,2287
|
19
25
|
gofannon/github/commit_file.py,sha256=jdQGQHbrZx4521XgTbx5N0Ss8fDyl7hvp9sjDW15v9U,2573
|
20
26
|
gofannon/github/commit_files.py,sha256=OZclhhSejRB1CYmd7IGYvdJZEWBzpaRRKK5S8NQxALU,4554
|
21
|
-
gofannon/github/create_issue.py,sha256=
|
27
|
+
gofannon/github/create_issue.py,sha256=oS4Q-3urpd-qTJ9K4cCCrwMl_QumlVFsM5q-M1_pc_I,2475
|
22
28
|
gofannon/github/get_repo_contents.py,sha256=9k6M2BqGlNsSGVjyfW7nxZpk1TFuhyPoZvURkv1PEyo,3637
|
29
|
+
gofannon/github/list_issues.py,sha256=F1YkRrmtequaGAPo-dIanyi75SQ-Gq5UWMf6cmhOLcY,4018
|
23
30
|
gofannon/github/pr_review_tool.py,sha256=srBbfgqBWy-J4wdAM0kLJfQr8LJ6uaA4vkDErqhyMxI,4336
|
24
31
|
gofannon/github/read_issue.py,sha256=JrnBAlZxknhHm3aLC0uB9u0bSvoQNfK3OKmxYlr8jgQ,2308
|
25
32
|
gofannon/github/search.py,sha256=yuX_dZ6f8ogUnskIRMUgF8wuN7JepqRtTDFrbmbwrrs,2183
|
@@ -42,7 +49,7 @@ gofannon/reasoning/sequential_cot.py,sha256=m9c8GnyTtmI-JntCuhkoFfULAabVOxsYgTRU
|
|
42
49
|
gofannon/reasoning/tree_of_thought.py,sha256=TRhRJQNsFVauCLw4TOvQCDcX1nGmp_wSg9H67GJn1hs,10574
|
43
50
|
gofannon/wikipedia/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
51
|
gofannon/wikipedia/wikipedia_lookup.py,sha256=J6wKPbSivCF7cccaiRaJW1o0VqNhQAGfrh5U1ULLesg,2869
|
45
|
-
gofannon-0.25.
|
46
|
-
gofannon-0.25.
|
47
|
-
gofannon-0.25.
|
48
|
-
gofannon-0.25.
|
52
|
+
gofannon-0.25.18.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
53
|
+
gofannon-0.25.18.dist-info/METADATA,sha256=fEPx-nCBs3eCopXGFC-fo7UODM7aHKBSt5KE2X2i5Qk,5415
|
54
|
+
gofannon-0.25.18.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
55
|
+
gofannon-0.25.18.dist-info/RECORD,,
|
File without changes
|
File without changes
|