glaip-sdk 0.0.2__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/__init__.py +2 -2
- glaip_sdk/_version.py +51 -0
- glaip_sdk/branding.py +145 -0
- glaip_sdk/cli/commands/agents.py +876 -166
- glaip_sdk/cli/commands/configure.py +46 -104
- glaip_sdk/cli/commands/init.py +43 -118
- glaip_sdk/cli/commands/mcps.py +86 -161
- glaip_sdk/cli/commands/tools.py +196 -57
- glaip_sdk/cli/main.py +43 -29
- glaip_sdk/cli/utils.py +258 -27
- glaip_sdk/client/__init__.py +54 -2
- glaip_sdk/client/agents.py +196 -237
- glaip_sdk/client/base.py +62 -2
- glaip_sdk/client/mcps.py +63 -20
- glaip_sdk/client/tools.py +236 -81
- glaip_sdk/config/constants.py +10 -3
- glaip_sdk/exceptions.py +13 -0
- glaip_sdk/models.py +21 -5
- glaip_sdk/utils/__init__.py +116 -18
- glaip_sdk/utils/client_utils.py +284 -0
- glaip_sdk/utils/rendering/__init__.py +1 -0
- glaip_sdk/utils/rendering/formatting.py +211 -0
- glaip_sdk/utils/rendering/models.py +53 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +38 -0
- glaip_sdk/utils/rendering/renderer/base.py +827 -0
- glaip_sdk/utils/rendering/renderer/config.py +33 -0
- glaip_sdk/utils/rendering/renderer/console.py +54 -0
- glaip_sdk/utils/rendering/renderer/debug.py +82 -0
- glaip_sdk/utils/rendering/renderer/panels.py +123 -0
- glaip_sdk/utils/rendering/renderer/progress.py +118 -0
- glaip_sdk/utils/rendering/renderer/stream.py +198 -0
- glaip_sdk/utils/rendering/steps.py +168 -0
- glaip_sdk/utils/run_renderer.py +22 -1086
- {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/METADATA +8 -36
- glaip_sdk-0.0.4.dist-info/RECORD +41 -0
- glaip_sdk/cli/config.py +0 -592
- glaip_sdk/utils.py +0 -167
- glaip_sdk-0.0.2.dist-info/RECORD +0 -28
- {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.4.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/utils.py
CHANGED
|
@@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
19
19
|
import click
|
|
20
20
|
from rich import box
|
|
21
21
|
from rich.console import Console, Group
|
|
22
|
+
from rich.markdown import Markdown
|
|
22
23
|
from rich.panel import Panel
|
|
23
24
|
from rich.pretty import Pretty
|
|
24
25
|
from rich.table import Table
|
|
@@ -41,6 +42,15 @@ except Exception:
|
|
|
41
42
|
if TYPE_CHECKING:
|
|
42
43
|
from glaip_sdk import Client
|
|
43
44
|
|
|
45
|
+
from glaip_sdk import Client
|
|
46
|
+
from glaip_sdk.cli.commands.configure import load_config
|
|
47
|
+
from glaip_sdk.utils import is_uuid
|
|
48
|
+
from glaip_sdk.utils.rendering.renderer import (
|
|
49
|
+
CapturingConsole,
|
|
50
|
+
RendererConfig,
|
|
51
|
+
RichStreamRenderer,
|
|
52
|
+
)
|
|
53
|
+
|
|
44
54
|
console = Console()
|
|
45
55
|
|
|
46
56
|
|
|
@@ -160,9 +170,6 @@ def _get_view(ctx) -> str:
|
|
|
160
170
|
|
|
161
171
|
def get_client(ctx) -> Client:
|
|
162
172
|
"""Get configured client from context, env, and config file (ctx > env > file)."""
|
|
163
|
-
from glaip_sdk import Client
|
|
164
|
-
from glaip_sdk.cli.commands.configure import load_config
|
|
165
|
-
|
|
166
173
|
file_config = load_config() or {}
|
|
167
174
|
context_config = (ctx.obj or {}) if ctx else {}
|
|
168
175
|
|
|
@@ -191,16 +198,6 @@ def get_client(ctx) -> Client:
|
|
|
191
198
|
)
|
|
192
199
|
|
|
193
200
|
|
|
194
|
-
# ----------------------------- Small helpers ----------------------------- #
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def safe_getattr(obj: Any, attr: str, default: Any = None) -> Any:
|
|
198
|
-
try:
|
|
199
|
-
return getattr(obj, attr)
|
|
200
|
-
except Exception:
|
|
201
|
-
return default
|
|
202
|
-
|
|
203
|
-
|
|
204
201
|
# ----------------------------- Secret masking ---------------------------- #
|
|
205
202
|
|
|
206
203
|
_DEFAULT_MASK_FIELDS = {
|
|
@@ -481,8 +478,6 @@ def output_result(
|
|
|
481
478
|
|
|
482
479
|
if fmt == "md":
|
|
483
480
|
try:
|
|
484
|
-
from rich.markdown import Markdown
|
|
485
|
-
|
|
486
481
|
console.print(Markdown(str(data)))
|
|
487
482
|
except ImportError:
|
|
488
483
|
# Fallback to plain if markdown not available
|
|
@@ -490,12 +485,12 @@ def output_result(
|
|
|
490
485
|
return
|
|
491
486
|
|
|
492
487
|
if success_message:
|
|
493
|
-
console.print(f"[green]✅ {success_message}[/green]")
|
|
488
|
+
console.print(Text(f"[green]✅ {success_message}[/green]"))
|
|
494
489
|
|
|
495
490
|
if panel_title:
|
|
496
491
|
console.print(Panel(Pretty(data), title=panel_title, border_style="blue"))
|
|
497
492
|
else:
|
|
498
|
-
console.print(f"[cyan]{title}:[/cyan]")
|
|
493
|
+
console.print(Text(f"[cyan]{title}:[/cyan]"))
|
|
499
494
|
console.print(Pretty(data))
|
|
500
495
|
|
|
501
496
|
|
|
@@ -532,6 +527,14 @@ def output_list(
|
|
|
532
527
|
except Exception:
|
|
533
528
|
rows = []
|
|
534
529
|
|
|
530
|
+
# Mask secrets (apply before any view)
|
|
531
|
+
mask_fields = _resolve_mask_fields()
|
|
532
|
+
if mask_fields:
|
|
533
|
+
try:
|
|
534
|
+
rows = [_maybe_mask_row(r, mask_fields) for r in rows]
|
|
535
|
+
except Exception:
|
|
536
|
+
pass
|
|
537
|
+
|
|
535
538
|
# JSON view bypasses any UI
|
|
536
539
|
if fmt == "json":
|
|
537
540
|
data = rows or [it.to_dict() if hasattr(it, "to_dict") else it for it in items]
|
|
@@ -563,7 +566,7 @@ def output_list(
|
|
|
563
566
|
return
|
|
564
567
|
|
|
565
568
|
if not items:
|
|
566
|
-
console.print(f"[yellow]No {title.lower()} found.[/yellow]")
|
|
569
|
+
console.print(Text(f"[yellow]No {title.lower()} found.[/yellow]"))
|
|
567
570
|
return
|
|
568
571
|
|
|
569
572
|
# Sort by name by default (unless disabled)
|
|
@@ -578,14 +581,6 @@ def output_list(
|
|
|
578
581
|
except Exception:
|
|
579
582
|
pass
|
|
580
583
|
|
|
581
|
-
# Mask secrets
|
|
582
|
-
mask_fields = _resolve_mask_fields()
|
|
583
|
-
if mask_fields:
|
|
584
|
-
try:
|
|
585
|
-
rows = [_maybe_mask_row(r, mask_fields) for r in rows]
|
|
586
|
-
except Exception:
|
|
587
|
-
pass
|
|
588
|
-
|
|
589
584
|
# === Fuzzy palette is the default for TTY lists ===
|
|
590
585
|
picked: dict[str, Any] | None = None
|
|
591
586
|
if console.is_terminal and os.isatty(1):
|
|
@@ -685,6 +680,240 @@ def output_flags():
|
|
|
685
680
|
# ------------------------- Ambiguity handling --------------------------- #
|
|
686
681
|
|
|
687
682
|
|
|
683
|
+
def coerce_to_row(item, keys: list[str]) -> dict[str, Any]:
|
|
684
|
+
"""Coerce an item (dict or object) to a row dict with specified keys.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
item: The item to coerce (dict or object with attributes)
|
|
688
|
+
keys: List of keys/attribute names to extract
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
Dict with the extracted values, "N/A" for missing values
|
|
692
|
+
"""
|
|
693
|
+
result = {}
|
|
694
|
+
for key in keys:
|
|
695
|
+
if isinstance(item, dict):
|
|
696
|
+
value = item.get(key, "N/A")
|
|
697
|
+
else:
|
|
698
|
+
value = getattr(item, key, "N/A")
|
|
699
|
+
result[key] = str(value) if value is not None else "N/A"
|
|
700
|
+
return result
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def build_renderer(
|
|
704
|
+
ctx,
|
|
705
|
+
*,
|
|
706
|
+
save_path,
|
|
707
|
+
theme="dark",
|
|
708
|
+
verbose=False,
|
|
709
|
+
tty_enabled=True,
|
|
710
|
+
live=None,
|
|
711
|
+
snapshots=None,
|
|
712
|
+
):
|
|
713
|
+
"""Build renderer and capturing console for CLI commands.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
ctx: Click context
|
|
717
|
+
save_path: Path to save output to (enables capturing)
|
|
718
|
+
theme: Color theme ("dark" or "light")
|
|
719
|
+
verbose: Whether to enable verbose mode
|
|
720
|
+
tty_enabled: Whether TTY is available
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
Tuple of (renderer, capturing_console)
|
|
724
|
+
"""
|
|
725
|
+
# Use capturing console if saving output
|
|
726
|
+
working_console = console
|
|
727
|
+
if save_path:
|
|
728
|
+
working_console = CapturingConsole(console, capture=True)
|
|
729
|
+
|
|
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
|
+
|
|
736
|
+
renderer_cfg = RendererConfig(
|
|
737
|
+
theme=theme,
|
|
738
|
+
style="debug" if verbose else "pretty",
|
|
739
|
+
live=live_enabled,
|
|
740
|
+
show_delegate_tool_panels=True,
|
|
741
|
+
append_finished_snapshots=bool(snapshots)
|
|
742
|
+
if snapshots is not None
|
|
743
|
+
else RendererConfig.append_finished_snapshots,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
# Create the renderer instance
|
|
747
|
+
renderer = RichStreamRenderer(
|
|
748
|
+
working_console.original_console
|
|
749
|
+
if isinstance(working_console, CapturingConsole)
|
|
750
|
+
else working_console,
|
|
751
|
+
cfg=renderer_cfg,
|
|
752
|
+
verbose=verbose,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
return renderer, working_console
|
|
756
|
+
|
|
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
|
+
|
|
862
|
+
def resolve_resource(
|
|
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",
|
|
871
|
+
):
|
|
872
|
+
"""Resolve resource reference (ID or name) with ambiguity handling.
|
|
873
|
+
|
|
874
|
+
Args:
|
|
875
|
+
ctx: Click context
|
|
876
|
+
ref: Resource reference (ID or name)
|
|
877
|
+
get_by_id: Function to get resource by ID
|
|
878
|
+
find_by_name: Function to find resources by name
|
|
879
|
+
label: Resource type label for error messages
|
|
880
|
+
select: Optional selection index for ambiguity resolution
|
|
881
|
+
interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
Resolved resource object
|
|
885
|
+
"""
|
|
886
|
+
if is_uuid(ref):
|
|
887
|
+
return get_by_id(ref)
|
|
888
|
+
|
|
889
|
+
# Find resources by name
|
|
890
|
+
matches = find_by_name(name=ref)
|
|
891
|
+
if not matches:
|
|
892
|
+
raise click.ClickException(f"{label} '{ref}' not found")
|
|
893
|
+
|
|
894
|
+
if len(matches) == 1:
|
|
895
|
+
return matches[0]
|
|
896
|
+
|
|
897
|
+
# Multiple matches - handle ambiguity
|
|
898
|
+
if select:
|
|
899
|
+
idx = int(select) - 1
|
|
900
|
+
if not (0 <= idx < len(matches)):
|
|
901
|
+
raise click.ClickException(f"--select must be 1..{len(matches)}")
|
|
902
|
+
return matches[idx]
|
|
903
|
+
|
|
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)
|
|
915
|
+
|
|
916
|
+
|
|
688
917
|
def handle_ambiguous_resource(
|
|
689
918
|
ctx, resource_type: str, ref: str, matches: list[Any]
|
|
690
919
|
) -> Any:
|
|
@@ -712,7 +941,9 @@ def handle_ambiguous_resource(
|
|
|
712
941
|
|
|
713
942
|
# Fallback numeric prompt
|
|
714
943
|
console.print(
|
|
715
|
-
|
|
944
|
+
Text(
|
|
945
|
+
f"[yellow]Multiple {resource_type.replace('{', '{{').replace('}', '}}')}s found matching '{ref.replace('{', '{{').replace('}', '}}')}':[/yellow]"
|
|
946
|
+
)
|
|
716
947
|
)
|
|
717
948
|
table = Table(
|
|
718
949
|
title=f"Select {resource_type.replace('{', '{{').replace('}', '}}').title()}",
|
glaip_sdk/client/__init__.py
CHANGED
|
@@ -5,6 +5,8 @@ Authors:
|
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
8
10
|
from glaip_sdk.client.agents import AgentClient
|
|
9
11
|
from glaip_sdk.client.base import BaseClient
|
|
10
12
|
from glaip_sdk.client.mcps import MCPClient
|
|
@@ -104,10 +106,18 @@ class Client(BaseClient):
|
|
|
104
106
|
return tool
|
|
105
107
|
|
|
106
108
|
def create_tool_from_code(
|
|
107
|
-
self,
|
|
109
|
+
self,
|
|
110
|
+
name: str,
|
|
111
|
+
code: str,
|
|
112
|
+
framework: str = "langchain",
|
|
113
|
+
*,
|
|
114
|
+
description: str | None = None,
|
|
115
|
+
tags: list[str] | None = None,
|
|
108
116
|
) -> Tool:
|
|
109
117
|
"""Create a new tool plugin from code string."""
|
|
110
|
-
tool = self.tools.create_tool_from_code(
|
|
118
|
+
tool = self.tools.create_tool_from_code(
|
|
119
|
+
name, code, framework, description=description, tags=tags
|
|
120
|
+
)
|
|
111
121
|
tool._set_client(self)
|
|
112
122
|
return tool
|
|
113
123
|
|
|
@@ -119,6 +129,16 @@ class Client(BaseClient):
|
|
|
119
129
|
"""Delete a tool by ID."""
|
|
120
130
|
return self.tools.delete_tool(tool_id)
|
|
121
131
|
|
|
132
|
+
def get_tool_script(self, tool_id: str) -> str:
|
|
133
|
+
"""Get tool script content."""
|
|
134
|
+
return self.tools.get_tool_script(tool_id)
|
|
135
|
+
|
|
136
|
+
def update_tool_via_file(self, tool_id: str, file_path: str, **kwargs) -> Tool:
|
|
137
|
+
"""Update a tool plugin via file upload."""
|
|
138
|
+
tool = self.tools.update_tool_via_file(tool_id, file_path, **kwargs)
|
|
139
|
+
tool._set_client(self)
|
|
140
|
+
return tool
|
|
141
|
+
|
|
122
142
|
# ---- MCPs
|
|
123
143
|
def list_mcps(self) -> list[MCP]:
|
|
124
144
|
mcps = self.mcps.list_mcps()
|
|
@@ -150,6 +170,18 @@ class Client(BaseClient):
|
|
|
150
170
|
"""Update an MCP by ID."""
|
|
151
171
|
return self.mcps.update_mcp(mcp_id, **kwargs)
|
|
152
172
|
|
|
173
|
+
def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
174
|
+
"""Test MCP connection using configuration."""
|
|
175
|
+
return self.mcps.test_mcp_connection(config)
|
|
176
|
+
|
|
177
|
+
def test_mcp_connection_from_config(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
178
|
+
"""Test MCP connection using configuration (alias)."""
|
|
179
|
+
return self.mcps.test_mcp_connection_from_config(config)
|
|
180
|
+
|
|
181
|
+
def get_mcp_tools_from_config(self, config: dict[str, Any]) -> list[dict[str, Any]]:
|
|
182
|
+
"""Fetch tools from MCP configuration without saving."""
|
|
183
|
+
return self.mcps.get_mcp_tools_from_config(config)
|
|
184
|
+
|
|
153
185
|
def run_agent(self, agent_id: str, message: str, **kwargs) -> str:
|
|
154
186
|
"""Run an agent with a message."""
|
|
155
187
|
return self.agents.run_agent(agent_id, message, **kwargs)
|
|
@@ -160,6 +192,26 @@ class Client(BaseClient):
|
|
|
160
192
|
data = self._request("GET", "/language-models")
|
|
161
193
|
return data or []
|
|
162
194
|
|
|
195
|
+
# ---- Timeout propagation ----
|
|
196
|
+
@property
|
|
197
|
+
def timeout(self) -> float: # type: ignore[override]
|
|
198
|
+
return super().timeout
|
|
199
|
+
|
|
200
|
+
@timeout.setter
|
|
201
|
+
def timeout(self, value: float) -> None: # type: ignore[override]
|
|
202
|
+
# Rebuild the root http client
|
|
203
|
+
BaseClient.timeout.fset(self, value) # call parent setter
|
|
204
|
+
# Propagate the new session to sub-clients so they don't hold a closed client
|
|
205
|
+
try:
|
|
206
|
+
if hasattr(self, "agents"):
|
|
207
|
+
self.agents.http_client = self.http_client
|
|
208
|
+
if hasattr(self, "tools"):
|
|
209
|
+
self.tools.http_client = self.http_client
|
|
210
|
+
if hasattr(self, "mcps"):
|
|
211
|
+
self.mcps.http_client = self.http_client
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
|
|
163
215
|
# ---- Aliases (back-compat)
|
|
164
216
|
def get_agent(self, agent_id: str) -> Agent:
|
|
165
217
|
return self.get_agent_by_id(agent_id)
|