glaip-sdk 0.0.3__py3-none-any.whl → 0.0.4__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.
- glaip_sdk/branding.py +145 -0
- glaip_sdk/cli/commands/agents.py +734 -116
- glaip_sdk/cli/commands/configure.py +17 -17
- glaip_sdk/cli/commands/init.py +32 -116
- glaip_sdk/cli/commands/mcps.py +13 -8
- glaip_sdk/cli/commands/tools.py +12 -9
- glaip_sdk/cli/main.py +14 -3
- glaip_sdk/cli/utils.py +136 -18
- glaip_sdk/client/agents.py +23 -2
- glaip_sdk/client/tools.py +183 -95
- glaip_sdk/models.py +1 -1
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/RECORD +15 -14
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.3.dist-info → glaip_sdk-0.0.4.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/utils.py
CHANGED
|
@@ -198,16 +198,6 @@ def get_client(ctx) -> Client:
|
|
|
198
198
|
)
|
|
199
199
|
|
|
200
200
|
|
|
201
|
-
# ----------------------------- Small helpers ----------------------------- #
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def safe_getattr(obj: Any, attr: str, default: Any = None) -> Any:
|
|
205
|
-
try:
|
|
206
|
-
return getattr(obj, attr)
|
|
207
|
-
except Exception:
|
|
208
|
-
return default
|
|
209
|
-
|
|
210
|
-
|
|
211
201
|
# ----------------------------- Secret masking ---------------------------- #
|
|
212
202
|
|
|
213
203
|
_DEFAULT_MASK_FIELDS = {
|
|
@@ -495,12 +485,12 @@ def output_result(
|
|
|
495
485
|
return
|
|
496
486
|
|
|
497
487
|
if success_message:
|
|
498
|
-
console.print(f"[green]✅ {success_message}[/green]")
|
|
488
|
+
console.print(Text(f"[green]✅ {success_message}[/green]"))
|
|
499
489
|
|
|
500
490
|
if panel_title:
|
|
501
491
|
console.print(Panel(Pretty(data), title=panel_title, border_style="blue"))
|
|
502
492
|
else:
|
|
503
|
-
console.print(f"[cyan]{title}:[/cyan]")
|
|
493
|
+
console.print(Text(f"[cyan]{title}:[/cyan]"))
|
|
504
494
|
console.print(Pretty(data))
|
|
505
495
|
|
|
506
496
|
|
|
@@ -576,7 +566,7 @@ def output_list(
|
|
|
576
566
|
return
|
|
577
567
|
|
|
578
568
|
if not items:
|
|
579
|
-
console.print(f"[yellow]No {title.lower()} found.[/yellow]")
|
|
569
|
+
console.print(Text(f"[yellow]No {title.lower()} found.[/yellow]"))
|
|
580
570
|
return
|
|
581
571
|
|
|
582
572
|
# Sort by name by default (unless disabled)
|
|
@@ -737,8 +727,12 @@ def build_renderer(
|
|
|
737
727
|
if save_path:
|
|
738
728
|
working_console = CapturingConsole(console, capture=True)
|
|
739
729
|
|
|
740
|
-
#
|
|
741
|
-
|
|
730
|
+
# Configure renderer based on verbose mode and explicit overrides
|
|
731
|
+
if live is None:
|
|
732
|
+
live_enabled = not verbose # Disable live mode in verbose (unless overridden)
|
|
733
|
+
else:
|
|
734
|
+
live_enabled = bool(live)
|
|
735
|
+
|
|
742
736
|
renderer_cfg = RendererConfig(
|
|
743
737
|
theme=theme,
|
|
744
738
|
style="debug" if verbose else "pretty",
|
|
@@ -761,8 +755,119 @@ def build_renderer(
|
|
|
761
755
|
return renderer, working_console
|
|
762
756
|
|
|
763
757
|
|
|
758
|
+
def _fuzzy_pick_for_resources(
|
|
759
|
+
resources: list[Any], resource_type: str, search_term: str
|
|
760
|
+
) -> Any | None:
|
|
761
|
+
"""
|
|
762
|
+
Fuzzy picker for resource objects, similar to _fuzzy_pick but without column dependencies.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
resources: List of resource objects to choose from
|
|
766
|
+
resource_type: Type of resource (e.g., "agent", "tool")
|
|
767
|
+
search_term: The search term that led to multiple matches
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
Selected resource object or None if cancelled/no selection
|
|
771
|
+
"""
|
|
772
|
+
if not (_HAS_PTK and console.is_terminal and os.isatty(1)):
|
|
773
|
+
return None
|
|
774
|
+
|
|
775
|
+
# Build display corpus and a reverse map
|
|
776
|
+
labels = []
|
|
777
|
+
by_label: dict[str, Any] = {}
|
|
778
|
+
for resource in resources:
|
|
779
|
+
name = getattr(resource, "name", "Unknown")
|
|
780
|
+
_id = getattr(resource, "id", "Unknown")
|
|
781
|
+
# Create a display label similar to _row_display
|
|
782
|
+
label_parts = []
|
|
783
|
+
if name and name != "Unknown":
|
|
784
|
+
label_parts.append(name)
|
|
785
|
+
label_parts.append(f"[{_id[:8]}...]") # Show first 8 chars of ID
|
|
786
|
+
label = " • ".join(label_parts)
|
|
787
|
+
|
|
788
|
+
# Ensure uniqueness
|
|
789
|
+
if label in by_label:
|
|
790
|
+
i = 2
|
|
791
|
+
base = label
|
|
792
|
+
while f"{base} #{i}" in by_label:
|
|
793
|
+
i += 1
|
|
794
|
+
label = f"{base} #{i}"
|
|
795
|
+
labels.append(label)
|
|
796
|
+
by_label[label] = resource
|
|
797
|
+
|
|
798
|
+
# Create fuzzy completer
|
|
799
|
+
class FuzzyCompleter:
|
|
800
|
+
def __init__(self, words: list[str]):
|
|
801
|
+
self.words = words
|
|
802
|
+
|
|
803
|
+
def get_completions(self, document, complete_event):
|
|
804
|
+
word = document.get_word_before_cursor()
|
|
805
|
+
if not word:
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
word_lower = word.lower()
|
|
809
|
+
for label in self.words:
|
|
810
|
+
label_lower = label.lower()
|
|
811
|
+
# Fuzzy match logic
|
|
812
|
+
if self._fuzzy_match(word_lower, label_lower):
|
|
813
|
+
yield Completion(label, start_position=-len(word))
|
|
814
|
+
|
|
815
|
+
def _fuzzy_match(self, search: str, target: str) -> bool:
|
|
816
|
+
if not search:
|
|
817
|
+
return True
|
|
818
|
+
|
|
819
|
+
search_idx = 0
|
|
820
|
+
for char in target:
|
|
821
|
+
if search_idx < len(search) and search[search_idx] == char:
|
|
822
|
+
search_idx += 1
|
|
823
|
+
if search_idx == len(search):
|
|
824
|
+
return True
|
|
825
|
+
return False
|
|
826
|
+
|
|
827
|
+
completer = FuzzyCompleter(labels)
|
|
828
|
+
|
|
829
|
+
try:
|
|
830
|
+
answer = prompt(
|
|
831
|
+
message=f"Find 🤖 {resource_type.title()}: ",
|
|
832
|
+
completer=completer,
|
|
833
|
+
complete_in_thread=True,
|
|
834
|
+
complete_while_typing=True,
|
|
835
|
+
)
|
|
836
|
+
except (KeyboardInterrupt, EOFError):
|
|
837
|
+
return None
|
|
838
|
+
|
|
839
|
+
if not answer:
|
|
840
|
+
return None
|
|
841
|
+
|
|
842
|
+
# Exact label match
|
|
843
|
+
if answer in by_label:
|
|
844
|
+
return by_label[answer]
|
|
845
|
+
|
|
846
|
+
# Fuzzy search fallback
|
|
847
|
+
best_match = None
|
|
848
|
+
best_score = -1
|
|
849
|
+
|
|
850
|
+
for label in labels:
|
|
851
|
+
score = _fuzzy_score(answer.lower(), label.lower())
|
|
852
|
+
if score > best_score:
|
|
853
|
+
best_score = score
|
|
854
|
+
best_match = label
|
|
855
|
+
|
|
856
|
+
if best_match and best_score > 0:
|
|
857
|
+
return by_label[best_match]
|
|
858
|
+
|
|
859
|
+
return None
|
|
860
|
+
|
|
861
|
+
|
|
764
862
|
def resolve_resource(
|
|
765
|
-
ctx,
|
|
863
|
+
ctx,
|
|
864
|
+
ref: str,
|
|
865
|
+
*,
|
|
866
|
+
get_by_id,
|
|
867
|
+
find_by_name,
|
|
868
|
+
label: str,
|
|
869
|
+
select: int | None = None,
|
|
870
|
+
interface_preference: str = "fuzzy",
|
|
766
871
|
):
|
|
767
872
|
"""Resolve resource reference (ID or name) with ambiguity handling.
|
|
768
873
|
|
|
@@ -773,6 +878,7 @@ def resolve_resource(
|
|
|
773
878
|
find_by_name: Function to find resources by name
|
|
774
879
|
label: Resource type label for error messages
|
|
775
880
|
select: Optional selection index for ambiguity resolution
|
|
881
|
+
interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
|
|
776
882
|
|
|
777
883
|
Returns:
|
|
778
884
|
Resolved resource object
|
|
@@ -795,7 +901,17 @@ def resolve_resource(
|
|
|
795
901
|
raise click.ClickException(f"--select must be 1..{len(matches)}")
|
|
796
902
|
return matches[idx]
|
|
797
903
|
|
|
798
|
-
|
|
904
|
+
# Choose interface based on preference
|
|
905
|
+
if interface_preference == "fuzzy":
|
|
906
|
+
# Use fuzzy picker for modern UX
|
|
907
|
+
picked = _fuzzy_pick_for_resources(matches, label.lower(), ref)
|
|
908
|
+
if picked:
|
|
909
|
+
return picked
|
|
910
|
+
# Fallback to original ambiguity handler if fuzzy picker fails
|
|
911
|
+
return handle_ambiguous_resource(ctx, label.lower(), ref, matches)
|
|
912
|
+
else:
|
|
913
|
+
# Use questionary interface for traditional up/down selection
|
|
914
|
+
return handle_ambiguous_resource(ctx, label.lower(), ref, matches)
|
|
799
915
|
|
|
800
916
|
|
|
801
917
|
def handle_ambiguous_resource(
|
|
@@ -825,7 +941,9 @@ def handle_ambiguous_resource(
|
|
|
825
941
|
|
|
826
942
|
# Fallback numeric prompt
|
|
827
943
|
console.print(
|
|
828
|
-
|
|
944
|
+
Text(
|
|
945
|
+
f"[yellow]Multiple {resource_type.replace('{', '{{').replace('}', '}}')}s found matching '{ref.replace('{', '{{').replace('}', '}}')}':[/yellow]"
|
|
946
|
+
)
|
|
829
947
|
)
|
|
830
948
|
table = Table(
|
|
831
949
|
title=f"Select {resource_type.replace('{', '{{').replace('}', '}}').title()}",
|
glaip_sdk/client/agents.py
CHANGED
|
@@ -31,6 +31,7 @@ from glaip_sdk.utils.client_utils import (
|
|
|
31
31
|
)
|
|
32
32
|
from glaip_sdk.utils.rendering.models import RunStats
|
|
33
33
|
from glaip_sdk.utils.rendering.renderer import RichStreamRenderer
|
|
34
|
+
from glaip_sdk.utils.rendering.renderer.config import RendererConfig
|
|
34
35
|
|
|
35
36
|
# Set up module-level logger
|
|
36
37
|
logger = logging.getLogger("glaip_sdk.agents")
|
|
@@ -254,8 +255,28 @@ class AgentClient(BaseClient):
|
|
|
254
255
|
if isinstance(renderer, RichStreamRenderer):
|
|
255
256
|
r = renderer
|
|
256
257
|
else:
|
|
257
|
-
#
|
|
258
|
-
|
|
258
|
+
# Check if verbose mode is requested
|
|
259
|
+
verbose = kwargs.get("verbose", False)
|
|
260
|
+
if verbose:
|
|
261
|
+
# Create a verbose renderer similar to CLI --verbose
|
|
262
|
+
verbose_config = RendererConfig(
|
|
263
|
+
theme="dark",
|
|
264
|
+
style="debug", # CLI uses "debug" style for verbose
|
|
265
|
+
live=False, # CLI disables live updates for verbose
|
|
266
|
+
show_delegate_tool_panels=True, # CLI always shows tool panels
|
|
267
|
+
append_finished_snapshots=False,
|
|
268
|
+
)
|
|
269
|
+
r = RichStreamRenderer(
|
|
270
|
+
console=_Console(),
|
|
271
|
+
cfg=verbose_config,
|
|
272
|
+
verbose=True,
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
# Default to a standard rich renderer with tool panels enabled
|
|
276
|
+
default_config = RendererConfig(
|
|
277
|
+
show_delegate_tool_panels=True, # Enable tool panels by default
|
|
278
|
+
)
|
|
279
|
+
r = RichStreamRenderer(console=_Console(), cfg=default_config)
|
|
259
280
|
|
|
260
281
|
# Try to set some meta early; refine as we receive events
|
|
261
282
|
meta = {
|
glaip_sdk/client/tools.py
CHANGED
|
@@ -49,67 +49,179 @@ class ToolClient(BaseClient):
|
|
|
49
49
|
tools = create_model_instances(data, Tool, self)
|
|
50
50
|
return find_by_name(tools, name, case_sensitive=False)
|
|
51
51
|
|
|
52
|
-
def
|
|
52
|
+
def _validate_and_read_file(self, file_path: str) -> str:
|
|
53
|
+
"""Validate file exists and read its content.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
file_path: Path to the file to read
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
str: File content
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
FileNotFoundError: If file doesn't exist
|
|
63
|
+
"""
|
|
64
|
+
if not os.path.exists(file_path):
|
|
65
|
+
raise FileNotFoundError(f"Tool file not found: {file_path}")
|
|
66
|
+
|
|
67
|
+
with open(file_path, encoding="utf-8") as f:
|
|
68
|
+
return f.read()
|
|
69
|
+
|
|
70
|
+
def _extract_name_from_file(self, file_path: str) -> str:
|
|
71
|
+
"""Extract tool name from file path.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
file_path: Path to the file
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
str: Extracted name (filename without extension)
|
|
78
|
+
"""
|
|
79
|
+
return os.path.splitext(os.path.basename(file_path))[0]
|
|
80
|
+
|
|
81
|
+
def _prepare_upload_data(
|
|
82
|
+
self, name: str, framework: str, description: str | None = None, **kwargs
|
|
83
|
+
) -> dict:
|
|
84
|
+
"""Prepare upload data dictionary.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
name: Tool name
|
|
88
|
+
framework: Tool framework
|
|
89
|
+
description: Optional description
|
|
90
|
+
**kwargs: Additional parameters
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
dict: Upload data dictionary
|
|
94
|
+
"""
|
|
95
|
+
data = {
|
|
96
|
+
"name": name,
|
|
97
|
+
"framework": framework,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if description:
|
|
101
|
+
data["description"] = description
|
|
102
|
+
|
|
103
|
+
# Handle tags if provided in kwargs
|
|
104
|
+
if "tags" in kwargs and kwargs["tags"]:
|
|
105
|
+
if isinstance(kwargs["tags"], list):
|
|
106
|
+
data["tags"] = ",".join(kwargs["tags"])
|
|
107
|
+
else:
|
|
108
|
+
data["tags"] = kwargs["tags"]
|
|
109
|
+
|
|
110
|
+
# Include any other kwargs in the upload data
|
|
111
|
+
for key, value in kwargs.items():
|
|
112
|
+
if key not in ["tags"]: # tags already handled above
|
|
113
|
+
data[key] = value
|
|
114
|
+
|
|
115
|
+
return data
|
|
116
|
+
|
|
117
|
+
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
118
|
+
"""Upload tool file to server.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
file_path: Path to temporary file to upload
|
|
122
|
+
upload_data: Dictionary with upload metadata
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Tool: Created tool object
|
|
126
|
+
"""
|
|
127
|
+
with open(file_path, "rb") as fb:
|
|
128
|
+
files = {
|
|
129
|
+
"file": (os.path.basename(file_path), fb, "application/octet-stream"),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
response = self._request(
|
|
133
|
+
"POST",
|
|
134
|
+
"/tools/upload",
|
|
135
|
+
files=files,
|
|
136
|
+
data=upload_data,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return Tool(**response)._set_client(self)
|
|
140
|
+
|
|
141
|
+
def _create_tool_from_file(
|
|
53
142
|
self,
|
|
143
|
+
file_path: str,
|
|
54
144
|
name: str | None = None,
|
|
55
|
-
tool_type: str = "custom",
|
|
56
145
|
description: str | None = None,
|
|
57
|
-
tool_script: str | None = None,
|
|
58
|
-
tool_file: str | None = None,
|
|
59
|
-
file_path: str | None = None,
|
|
60
|
-
code: str | None = None,
|
|
61
146
|
framework: str = "langchain",
|
|
62
147
|
**kwargs,
|
|
63
148
|
) -> Tool:
|
|
64
|
-
"""Create
|
|
149
|
+
"""Create tool from file content using upload endpoint.
|
|
65
150
|
|
|
66
151
|
Args:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
description:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
**kwargs: Additional tool parameters
|
|
152
|
+
file_path: Path to tool file
|
|
153
|
+
name: Optional tool name (auto-detected if not provided)
|
|
154
|
+
description: Optional tool description
|
|
155
|
+
framework: Tool framework
|
|
156
|
+
**kwargs: Additional parameters
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Tool: Created tool object
|
|
76
160
|
"""
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
tool_file = file_path
|
|
80
|
-
if code and not tool_script:
|
|
81
|
-
tool_script = code
|
|
161
|
+
# Read and validate file
|
|
162
|
+
file_content = self._validate_and_read_file(file_path)
|
|
82
163
|
|
|
83
|
-
# Auto-detect name
|
|
84
|
-
if not name
|
|
85
|
-
|
|
164
|
+
# Auto-detect name if not provided
|
|
165
|
+
if not name:
|
|
166
|
+
name = self._extract_name_from_file(file_path)
|
|
86
167
|
|
|
87
|
-
|
|
168
|
+
# Handle description - generate default if not provided or empty
|
|
169
|
+
if description is None or description == "":
|
|
170
|
+
# Generate default description based on tool_type if available
|
|
171
|
+
tool_type = kwargs.get("tool_type", "custom")
|
|
172
|
+
description = f"A {tool_type} tool"
|
|
88
173
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
174
|
+
# Create temporary file for upload
|
|
175
|
+
with tempfile.NamedTemporaryFile(
|
|
176
|
+
mode="w",
|
|
177
|
+
suffix=".py",
|
|
178
|
+
prefix=f"{name}_",
|
|
179
|
+
delete=False,
|
|
180
|
+
encoding="utf-8",
|
|
181
|
+
) as temp_file:
|
|
182
|
+
temp_file.write(file_content)
|
|
183
|
+
temp_file_path = temp_file.name
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Prepare upload data
|
|
187
|
+
upload_data = self._prepare_upload_data(
|
|
188
|
+
name=name, framework=framework, description=description, **kwargs
|
|
92
189
|
)
|
|
93
190
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
description = f"A {tool_type} tool"
|
|
191
|
+
# Upload file
|
|
192
|
+
return self._upload_tool_file(temp_file_path, upload_data)
|
|
97
193
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
194
|
+
finally:
|
|
195
|
+
# Clean up temporary file
|
|
196
|
+
try:
|
|
197
|
+
os.unlink(temp_file_path)
|
|
198
|
+
except OSError:
|
|
199
|
+
pass # Ignore cleanup errors
|
|
105
200
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
201
|
+
def create_tool(
|
|
202
|
+
self,
|
|
203
|
+
file_path: str,
|
|
204
|
+
name: str | None = None,
|
|
205
|
+
description: str | None = None,
|
|
206
|
+
framework: str = "langchain",
|
|
207
|
+
**kwargs,
|
|
208
|
+
) -> Tool:
|
|
209
|
+
"""Create a new tool from a file.
|
|
110
210
|
|
|
111
|
-
|
|
112
|
-
|
|
211
|
+
Args:
|
|
212
|
+
file_path: File path to tool script (required) - file content will be read and processed as plugin
|
|
213
|
+
name: Tool name (auto-detected from file if not provided)
|
|
214
|
+
description: Tool description (auto-generated if not provided)
|
|
215
|
+
framework: Tool framework (defaults to "langchain")
|
|
216
|
+
**kwargs: Additional tool parameters
|
|
217
|
+
"""
|
|
218
|
+
return self._create_tool_from_file(
|
|
219
|
+
file_path=file_path,
|
|
220
|
+
name=name,
|
|
221
|
+
description=description,
|
|
222
|
+
framework=framework,
|
|
223
|
+
**kwargs,
|
|
224
|
+
)
|
|
113
225
|
|
|
114
226
|
def create_tool_from_code(
|
|
115
227
|
self,
|
|
@@ -129,41 +241,35 @@ class ToolClient(BaseClient):
|
|
|
129
241
|
name: Name for the tool (used for temporary file naming)
|
|
130
242
|
code: Python code containing the tool plugin
|
|
131
243
|
framework: Tool framework (defaults to "langchain")
|
|
244
|
+
description: Optional tool description
|
|
245
|
+
tags: Optional list of tags
|
|
132
246
|
|
|
133
247
|
Returns:
|
|
134
248
|
Tool: The created tool object
|
|
135
249
|
"""
|
|
136
250
|
# Create a temporary file with the tool code
|
|
137
251
|
with tempfile.NamedTemporaryFile(
|
|
138
|
-
mode="w",
|
|
252
|
+
mode="w",
|
|
253
|
+
suffix=".py",
|
|
254
|
+
prefix=f"{name}_",
|
|
255
|
+
delete=False,
|
|
256
|
+
encoding="utf-8",
|
|
139
257
|
) as temp_file:
|
|
140
258
|
temp_file.write(code)
|
|
141
259
|
temp_file_path = temp_file.name
|
|
142
260
|
|
|
143
261
|
try:
|
|
144
|
-
# Prepare
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if description:
|
|
155
|
-
data["description"] = description
|
|
156
|
-
if tags:
|
|
157
|
-
# Backend might expect comma-separated or JSON; start with comma-separated
|
|
158
|
-
data["tags"] = ",".join(tags)
|
|
262
|
+
# Prepare upload data using shared helper
|
|
263
|
+
upload_data = self._prepare_upload_data(
|
|
264
|
+
name=name,
|
|
265
|
+
framework=framework,
|
|
266
|
+
description=description,
|
|
267
|
+
tags=tags if tags else None,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Upload file using shared helper
|
|
271
|
+
return self._upload_tool_file(temp_file_path, upload_data)
|
|
159
272
|
|
|
160
|
-
response = self._request(
|
|
161
|
-
"POST",
|
|
162
|
-
"/tools/upload",
|
|
163
|
-
files=files,
|
|
164
|
-
data=data,
|
|
165
|
-
)
|
|
166
|
-
return Tool(**response)._set_client(self)
|
|
167
273
|
finally:
|
|
168
274
|
# Clean up the temporary file
|
|
169
275
|
try:
|
|
@@ -180,24 +286,6 @@ class ToolClient(BaseClient):
|
|
|
180
286
|
"""Delete a tool."""
|
|
181
287
|
self._request("DELETE", f"/tools/{tool_id}")
|
|
182
288
|
|
|
183
|
-
def install_tool(self, tool_id: str) -> bool:
|
|
184
|
-
"""Install a tool."""
|
|
185
|
-
try:
|
|
186
|
-
self._request("POST", f"/tools/{tool_id}/install")
|
|
187
|
-
return True
|
|
188
|
-
except Exception as e:
|
|
189
|
-
logger.error(f"Failed to install tool {tool_id}: {e}")
|
|
190
|
-
return False
|
|
191
|
-
|
|
192
|
-
def uninstall_tool(self, tool_id: str) -> bool:
|
|
193
|
-
"""Uninstall a tool."""
|
|
194
|
-
try:
|
|
195
|
-
self._request("POST", f"/tools/{tool_id}/uninstall")
|
|
196
|
-
return True
|
|
197
|
-
except Exception as e:
|
|
198
|
-
logger.error(f"Failed to install tool {tool_id}: {e}")
|
|
199
|
-
return False
|
|
200
|
-
|
|
201
289
|
def get_tool_script(self, tool_id: str) -> str:
|
|
202
290
|
"""Get the tool script content.
|
|
203
291
|
|
|
@@ -232,29 +320,29 @@ class ToolClient(BaseClient):
|
|
|
232
320
|
FileNotFoundError: If the file doesn't exist
|
|
233
321
|
Exception: If the update fails
|
|
234
322
|
"""
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if not os.path.exists(file_path):
|
|
238
|
-
raise FileNotFoundError(f"Tool file not found: {file_path}")
|
|
323
|
+
# Validate file exists
|
|
324
|
+
self._validate_and_read_file(file_path)
|
|
239
325
|
|
|
240
326
|
try:
|
|
241
327
|
# Prepare multipart upload
|
|
242
|
-
filename = os.path.basename(file_path)
|
|
243
328
|
with open(file_path, "rb") as fb:
|
|
244
329
|
files = {
|
|
245
|
-
"file": (
|
|
330
|
+
"file": (
|
|
331
|
+
os.path.basename(file_path),
|
|
332
|
+
fb,
|
|
333
|
+
"application/octet-stream",
|
|
334
|
+
),
|
|
246
335
|
}
|
|
247
336
|
|
|
248
|
-
# Add any additional metadata
|
|
249
|
-
data = kwargs.copy()
|
|
250
|
-
|
|
251
337
|
response = self._request(
|
|
252
338
|
"PUT",
|
|
253
339
|
f"/tools/{tool_id}/upload",
|
|
254
340
|
files=files,
|
|
255
|
-
data=data
|
|
341
|
+
data=kwargs, # Pass kwargs directly as data
|
|
256
342
|
)
|
|
343
|
+
|
|
257
344
|
return Tool(**response)._set_client(self)
|
|
345
|
+
|
|
258
346
|
except Exception as e:
|
|
259
347
|
logger.error(f"Failed to update tool {tool_id} via file: {e}")
|
|
260
348
|
raise
|
glaip_sdk/models.py
CHANGED
|
@@ -128,7 +128,7 @@ class Tool(BaseModel):
|
|
|
128
128
|
raise RuntimeError(
|
|
129
129
|
"No client available. Use client.get_tool_by_id() to get a client-connected tool."
|
|
130
130
|
)
|
|
131
|
-
self._client.
|
|
131
|
+
self._client.delete_tool(self.id)
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
class MCP(BaseModel):
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
glaip_sdk/__init__.py,sha256=UGBsYHHSgSc1HGCTsA9bbz2ARJJ1g49cTieXEINHtAk,323
|
|
2
2
|
glaip_sdk/_version.py,sha256=Rb9YLDvK1DXCVFrjlLDbtucpwKh_PyCnmZ-ia9VX3Cc,1650
|
|
3
|
+
glaip_sdk/branding.py,sha256=Iwm9qsPoRNCiv7IS_pet_vno8ltdVR4UwyEHoYH7qp0,4891
|
|
3
4
|
glaip_sdk/exceptions.py,sha256=QTVtwxRHMN4e8gGn0icXphZvdugiRvcSrlMYylwGsDc,1993
|
|
4
|
-
glaip_sdk/models.py,sha256=
|
|
5
|
+
glaip_sdk/models.py,sha256=kkuH66cj5Oyrma3bLldPpheeXR4H9xZa_z32bpLd3DU,7229
|
|
5
6
|
glaip_sdk/cli/__init__.py,sha256=cPI-uOOBww_ESiH6NQBdJiTg8CUVNigFOU3kl0tgTuI,143
|
|
6
|
-
glaip_sdk/cli/main.py,sha256=
|
|
7
|
-
glaip_sdk/cli/utils.py,sha256=
|
|
7
|
+
glaip_sdk/cli/main.py,sha256=jiZmj9smpFYkdToJip_JMBNoBEIqOrt1cTaTQrNmN7w,10557
|
|
8
|
+
glaip_sdk/cli/utils.py,sha256=UBIrLIKLi4UZjwEa8J1AiOyGJe8o6eVhYBCwjx51CxQ,29182
|
|
8
9
|
glaip_sdk/cli/commands/__init__.py,sha256=WShiuYqHcbuexPboibDJ_Q2jOPIWp-TgsyUAO2-T20I,134
|
|
9
|
-
glaip_sdk/cli/commands/agents.py,sha256=
|
|
10
|
-
glaip_sdk/cli/commands/configure.py,sha256=
|
|
11
|
-
glaip_sdk/cli/commands/init.py,sha256=
|
|
12
|
-
glaip_sdk/cli/commands/mcps.py,sha256=
|
|
10
|
+
glaip_sdk/cli/commands/agents.py,sha256=yNrlOooe9vx_A_frycdJ9U4aibXAHV4aYwVYn54RbSY,38515
|
|
11
|
+
glaip_sdk/cli/commands/configure.py,sha256=SNaWPO0bgI6bztlKgoqGr0qh2KSSDztyubKhrw_HLEI,7285
|
|
12
|
+
glaip_sdk/cli/commands/init.py,sha256=W4z5TBlfSn9fmHpH1W92llgqfF0OvhgJSdny-P61Y_4,2934
|
|
13
|
+
glaip_sdk/cli/commands/mcps.py,sha256=4Ny54ApHYFCkJCXORD7zslVnrNdKs981adIbJNaYAMQ,12099
|
|
13
14
|
glaip_sdk/cli/commands/models.py,sha256=j8VqQ2edSEvD-sQXWm8ifUco9gX8J-OBib6OvzthpqU,1405
|
|
14
|
-
glaip_sdk/cli/commands/tools.py,sha256
|
|
15
|
+
glaip_sdk/cli/commands/tools.py,sha256=-xPMOKD-shI2ly9Tl-IwhKQR2kEekEs63bw7EEXNoAU,14508
|
|
15
16
|
glaip_sdk/client/__init__.py,sha256=jnsD3HCiOGiuL_4aqJiVnBsF8HNFS-s_aNLfg26gJqs,7530
|
|
16
|
-
glaip_sdk/client/agents.py,sha256=
|
|
17
|
+
glaip_sdk/client/agents.py,sha256=k3QthlIu82KeXV_W29LuFTGZVzyGL11ZY5OA4P4a2Ug,15041
|
|
17
18
|
glaip_sdk/client/base.py,sha256=3Ri5GVYrZ4cZ5lex1pyG0nvEmGxeXwAY194MxSH1_cY,9463
|
|
18
19
|
glaip_sdk/client/mcps.py,sha256=UEwgFbl4ogeozfJGcUbQQ7JGfwYliN9i5V6fTvkbOZ0,4311
|
|
19
|
-
glaip_sdk/client/tools.py,sha256=
|
|
20
|
+
glaip_sdk/client/tools.py,sha256=v5jxHPgoGHwB2PXzlm0yywvqKaYLcrm6dJz9aT1SxuM,10954
|
|
20
21
|
glaip_sdk/client/validators.py,sha256=3MtOt11IivEwQAgzmdRGWUBzP223ytECOLU_a77x7IU,7101
|
|
21
22
|
glaip_sdk/config/constants.py,sha256=Pm0tGYiJ9VdW9XXz0CG36l8-sAhnlFPTKbHKKleUgik,705
|
|
22
23
|
glaip_sdk/utils/__init__.py,sha256=5pOPBTjJ4hzvMNYjLnTut_ovwU8QJ39kARKx9-qr5Qs,5101
|
|
@@ -34,7 +35,7 @@ glaip_sdk/utils/rendering/renderer/debug.py,sha256=2XP6A4w3EIjVVY_M-BDPvHf05QQSz
|
|
|
34
35
|
glaip_sdk/utils/rendering/renderer/panels.py,sha256=iTDJoRBaa73pX9z9nTcb6aKhfXe1Mds0RzCuhwtvkdc,2994
|
|
35
36
|
glaip_sdk/utils/rendering/renderer/progress.py,sha256=i4HG_iNwtK4c3Gf2sviLCiOJ-5ydX4t-YE5dgtLOxNI,3438
|
|
36
37
|
glaip_sdk/utils/rendering/renderer/stream.py,sha256=ZKitYkt_7OirNeERu3cc_NybQ8mWUMoEstg9UL1S090,7323
|
|
37
|
-
glaip_sdk-0.0.
|
|
38
|
-
glaip_sdk-0.0.
|
|
39
|
-
glaip_sdk-0.0.
|
|
40
|
-
glaip_sdk-0.0.
|
|
38
|
+
glaip_sdk-0.0.4.dist-info/METADATA,sha256=H5uB6CuucGboUl_Z9LhzJHOIc7PS_a0pgHcqNX7-ANE,19501
|
|
39
|
+
glaip_sdk-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
40
|
+
glaip_sdk-0.0.4.dist-info/entry_points.txt,sha256=65vNPUggyYnVGhuw7RhNJ8Fp2jygTcX0yxJBcBY3iLU,48
|
|
41
|
+
glaip_sdk-0.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|