glaip-sdk 0.1.2__py3-none-any.whl → 0.7.17__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 +44 -4
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1413 -0
- glaip_sdk/branding.py +126 -2
- glaip_sdk/cli/account_store.py +555 -0
- glaip_sdk/cli/auth.py +260 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +104 -0
- glaip_sdk/cli/commands/configure.py +728 -113
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +12 -8
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/transcripts_original.py +756 -0
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +49 -4
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +851 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +41 -20
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +340 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +12 -13
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +580 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +568 -0
- glaip_sdk/cli/slash/session.py +1105 -153
- glaip_sdk/cli/slash/tui/__init__.py +36 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +177 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +1853 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
- glaip_sdk/cli/slash/tui/loading.py +80 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +760 -0
- glaip_sdk/cli/slash/tui/terminal.py +407 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +388 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +66 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -463
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +227 -10
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +3 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +576 -44
- glaip_sdk/client/base.py +26 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -14
- glaip_sdk/client/mcps.py +165 -24
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +63 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +546 -92
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +206 -32
- glaip_sdk/config/constants.py +33 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +136 -0
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +48 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +445 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +1055 -0
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +116 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +488 -0
- glaip_sdk/utils/__init__.py +59 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- glaip_sdk/utils/import_resolver.py +524 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +299 -1434
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -33
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +25 -13
- glaip_sdk/utils/runtime_config.py +426 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +162 -0
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +69 -23
- glaip_sdk-0.7.17.dist-info/RECORD +224 -0
- {glaip_sdk-0.1.2.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1369
- glaip_sdk/cli/commands/mcps.py +0 -1187
- glaip_sdk/cli/commands/tools.py +0 -584
- glaip_sdk/cli/utils.py +0 -1278
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.2.dist-info/RECORD +0 -82
- glaip_sdk-0.1.2.dist-info/entry_points.txt +0 -3
glaip_sdk/client/base.py
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import logging
|
|
9
10
|
import os
|
|
11
|
+
import time
|
|
10
12
|
from collections.abc import Iterable, Mapping
|
|
11
13
|
from typing import Any, NoReturn, Union
|
|
12
14
|
|
|
@@ -88,6 +90,11 @@ class BaseClient:
|
|
|
88
90
|
client_log.info(f"Initializing client with API URL: {self.api_url}")
|
|
89
91
|
self.http_client = self._build_client(timeout)
|
|
90
92
|
|
|
93
|
+
# Language model cache (shared by all clients)
|
|
94
|
+
self._lm_cache: list[dict[str, Any]] | None = None
|
|
95
|
+
self._lm_cache_time: float = 0.0
|
|
96
|
+
self._lm_cache_ttl: float = 300.0 # 5 minutes TTL
|
|
97
|
+
|
|
91
98
|
def _build_client(self, timeout: float) -> httpx.Client:
|
|
92
99
|
"""Build HTTP client with configuration."""
|
|
93
100
|
# For streaming operations, we need more generous read timeouts
|
|
@@ -480,6 +487,25 @@ class BaseClient:
|
|
|
480
487
|
request_id=request_id,
|
|
481
488
|
)
|
|
482
489
|
|
|
490
|
+
def list_language_models(self, force_refresh: bool = False) -> list[dict[str, Any]]:
|
|
491
|
+
"""List available language models with TTL caching.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
force_refresh: Whether to ignore cache and fetch fresh list.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
List of available language models.
|
|
498
|
+
"""
|
|
499
|
+
now = time.monotonic()
|
|
500
|
+
if not force_refresh and self._lm_cache is not None:
|
|
501
|
+
if now - self._lm_cache_time < self._lm_cache_ttl:
|
|
502
|
+
return self._lm_cache
|
|
503
|
+
|
|
504
|
+
models = self._request("GET", "/language-models") or []
|
|
505
|
+
self._lm_cache = models
|
|
506
|
+
self._lm_cache_time = now
|
|
507
|
+
return models
|
|
508
|
+
|
|
483
509
|
def close(self) -> None:
|
|
484
510
|
"""Close the HTTP client."""
|
|
485
511
|
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
glaip_sdk/client/hitl.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""HITL REST client for manual approval operations.
|
|
3
|
+
|
|
4
|
+
Authors:
|
|
5
|
+
GLAIP SDK Team
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from glaip_sdk.client.base import BaseClient
|
|
11
|
+
from glaip_sdk.hitl.base import HITLDecision
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HITLClient(BaseClient):
|
|
15
|
+
"""Client for HITL REST endpoints.
|
|
16
|
+
|
|
17
|
+
Use for manual approval workflows separate from agent runs.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> # List pending approvals
|
|
21
|
+
>>> pending = client.hitl.list_pending()
|
|
22
|
+
>>>
|
|
23
|
+
>>> # Approve a request
|
|
24
|
+
>>> client.hitl.approve(
|
|
25
|
+
... request_id="bc4d0a77-7800-470e-a91c-7fd663a66b4d",
|
|
26
|
+
... operator_input="Verified and approved",
|
|
27
|
+
... )
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def approve(
|
|
31
|
+
self,
|
|
32
|
+
request_id: str,
|
|
33
|
+
operator_input: str | None = None,
|
|
34
|
+
run_id: str | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
"""Approve a HITL request.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
request_id: HITL request ID from SSE stream
|
|
40
|
+
operator_input: Optional notes/reason for approval
|
|
41
|
+
run_id: Optional client-side run correlation ID
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Response dict: {"status": "ok", "message": "..."}
|
|
45
|
+
"""
|
|
46
|
+
return self._post_decision(
|
|
47
|
+
request_id,
|
|
48
|
+
HITLDecision.APPROVED,
|
|
49
|
+
operator_input,
|
|
50
|
+
run_id,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def reject(
|
|
54
|
+
self,
|
|
55
|
+
request_id: str,
|
|
56
|
+
operator_input: str | None = None,
|
|
57
|
+
run_id: str | None = None,
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""Reject a HITL request.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
request_id: HITL request ID
|
|
63
|
+
operator_input: Optional reason for rejection
|
|
64
|
+
run_id: Optional run correlation ID
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Response dict
|
|
68
|
+
"""
|
|
69
|
+
return self._post_decision(
|
|
70
|
+
request_id,
|
|
71
|
+
HITLDecision.REJECTED,
|
|
72
|
+
operator_input,
|
|
73
|
+
run_id,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def skip(
|
|
77
|
+
self,
|
|
78
|
+
request_id: str,
|
|
79
|
+
operator_input: str | None = None,
|
|
80
|
+
run_id: str | None = None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""Skip a HITL request.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
request_id: HITL request ID
|
|
86
|
+
operator_input: Optional notes
|
|
87
|
+
run_id: Optional run correlation ID
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Response dict
|
|
91
|
+
"""
|
|
92
|
+
return self._post_decision(
|
|
93
|
+
request_id,
|
|
94
|
+
HITLDecision.SKIPPED,
|
|
95
|
+
operator_input,
|
|
96
|
+
run_id,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _post_decision(
|
|
100
|
+
self,
|
|
101
|
+
request_id: str,
|
|
102
|
+
decision: HITLDecision,
|
|
103
|
+
operator_input: str | None,
|
|
104
|
+
run_id: str | None,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
"""Post HITL decision to backend."""
|
|
107
|
+
payload = {
|
|
108
|
+
"request_id": request_id,
|
|
109
|
+
"decision": decision.value,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if operator_input:
|
|
113
|
+
payload["operator_input"] = operator_input
|
|
114
|
+
if run_id:
|
|
115
|
+
payload["run_id"] = run_id
|
|
116
|
+
|
|
117
|
+
return self._request("POST", "/agents/hitl/decision", json=payload)
|
|
118
|
+
|
|
119
|
+
def list_pending(self) -> list[dict[str, Any]]:
|
|
120
|
+
"""List all pending HITL requests.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of pending request dicts with metadata:
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
"request_id": "...",
|
|
127
|
+
"tool": "...",
|
|
128
|
+
"arguments": {...},
|
|
129
|
+
"created_at": "...",
|
|
130
|
+
"agent_id": "...",
|
|
131
|
+
"hitl_metadata": {...},
|
|
132
|
+
},
|
|
133
|
+
...
|
|
134
|
+
]
|
|
135
|
+
"""
|
|
136
|
+
return self._request("GET", "/agents/hitl/pending")
|
glaip_sdk/client/main.py
CHANGED
|
@@ -3,15 +3,26 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
|
-
from
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
9
12
|
|
|
10
13
|
from glaip_sdk.client.agents import AgentClient
|
|
11
14
|
from glaip_sdk.client.base import BaseClient
|
|
15
|
+
from glaip_sdk.client.hitl import HITLClient
|
|
12
16
|
from glaip_sdk.client.mcps import MCPClient
|
|
17
|
+
from glaip_sdk.client.schedules import ScheduleClient
|
|
18
|
+
from glaip_sdk.client.shared import build_shared_config
|
|
13
19
|
from glaip_sdk.client.tools import ToolClient
|
|
14
|
-
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
22
|
+
from glaip_sdk.agents import Agent
|
|
23
|
+
from glaip_sdk.client.payloads.agent import AgentListResult
|
|
24
|
+
from glaip_sdk.mcps import MCP
|
|
25
|
+
from glaip_sdk.tools import Tool
|
|
15
26
|
|
|
16
27
|
|
|
17
28
|
class Client(BaseClient):
|
|
@@ -25,15 +36,12 @@ class Client(BaseClient):
|
|
|
25
36
|
"""
|
|
26
37
|
super().__init__(**kwargs)
|
|
27
38
|
# Share the single httpx.Client + config with sub-clients
|
|
28
|
-
shared_config =
|
|
29
|
-
"parent_client": self,
|
|
30
|
-
"api_url": self.api_url,
|
|
31
|
-
"api_key": self.api_key,
|
|
32
|
-
"timeout": self._timeout,
|
|
33
|
-
}
|
|
39
|
+
shared_config = build_shared_config(self)
|
|
34
40
|
self.agents = AgentClient(**shared_config)
|
|
35
41
|
self.tools = ToolClient(**shared_config)
|
|
36
42
|
self.mcps = MCPClient(**shared_config)
|
|
43
|
+
self.schedules = ScheduleClient(**shared_config)
|
|
44
|
+
self.hitl = HITLClient(**shared_config)
|
|
37
45
|
|
|
38
46
|
# ---- Core API Methods (Public Interface) ----
|
|
39
47
|
|
|
@@ -53,7 +61,7 @@ class Client(BaseClient):
|
|
|
53
61
|
name: str | None = None,
|
|
54
62
|
version: str | None = None,
|
|
55
63
|
sync_langflow_agents: bool = False,
|
|
56
|
-
) ->
|
|
64
|
+
) -> AgentListResult:
|
|
57
65
|
"""List agents with optional filtering.
|
|
58
66
|
|
|
59
67
|
Args:
|
|
@@ -64,7 +72,7 @@ class Client(BaseClient):
|
|
|
64
72
|
sync_langflow_agents: Sync with LangFlow server before listing (only applies when agent_type=langflow)
|
|
65
73
|
|
|
66
74
|
Returns:
|
|
67
|
-
|
|
75
|
+
AgentListResult with agents and pagination metadata. Supports iteration and indexing.
|
|
68
76
|
"""
|
|
69
77
|
return self.agents.list_agents(
|
|
70
78
|
agent_type=agent_type,
|
|
@@ -204,10 +212,6 @@ class Client(BaseClient):
|
|
|
204
212
|
return self.mcps.get_mcp_tools_from_config(config)
|
|
205
213
|
|
|
206
214
|
# Language Models
|
|
207
|
-
def list_language_models(self) -> list[dict]:
|
|
208
|
-
"""List available language models."""
|
|
209
|
-
data = self._request("GET", "/language-models")
|
|
210
|
-
return data or []
|
|
211
215
|
|
|
212
216
|
# ---- Timeout propagation ----
|
|
213
217
|
@property
|
|
@@ -217,6 +221,11 @@ class Client(BaseClient):
|
|
|
217
221
|
|
|
218
222
|
@timeout.setter
|
|
219
223
|
def timeout(self, value: float) -> None: # type: ignore[override]
|
|
224
|
+
"""Set the client timeout and propagate to sub-clients.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
value: Timeout value in seconds.
|
|
228
|
+
"""
|
|
220
229
|
# Rebuild the root http client
|
|
221
230
|
BaseClient.timeout.fset(self, value) # call parent setter
|
|
222
231
|
# Propagate the new session to sub-clients so they don't hold a closed client
|
|
@@ -227,6 +236,8 @@ class Client(BaseClient):
|
|
|
227
236
|
self.tools.http_client = self.http_client
|
|
228
237
|
if hasattr(self, "mcps"):
|
|
229
238
|
self.mcps.http_client = self.http_client
|
|
239
|
+
if hasattr(self, "schedules"):
|
|
240
|
+
self.schedules.http_client = self.http_client
|
|
230
241
|
except Exception:
|
|
231
242
|
pass
|
|
232
243
|
|
glaip_sdk/client/mcps.py
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
|
|
4
4
|
Authors:
|
|
5
5
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import logging
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from glaip_sdk.client.base import BaseClient
|
|
12
|
-
from glaip_sdk.config.constants import
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
from glaip_sdk.config.constants import DEFAULT_MCP_TRANSPORT, DEFAULT_MCP_TYPE
|
|
14
|
+
from glaip_sdk.mcps import MCP
|
|
15
|
+
from glaip_sdk.models import MCPResponse
|
|
16
|
+
from glaip_sdk.utils.client_utils import (
|
|
17
|
+
add_kwargs_to_payload,
|
|
18
|
+
create_model_instances,
|
|
19
|
+
find_by_name,
|
|
15
20
|
)
|
|
16
|
-
from glaip_sdk.
|
|
17
|
-
from glaip_sdk.utils.client_utils import create_model_instances, find_by_name
|
|
21
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
18
22
|
|
|
19
23
|
# API endpoints
|
|
20
24
|
MCPS_ENDPOINT = "/mcps/"
|
|
@@ -45,7 +49,8 @@ class MCPClient(BaseClient):
|
|
|
45
49
|
def get_mcp_by_id(self, mcp_id: str) -> MCP:
|
|
46
50
|
"""Get MCP by ID."""
|
|
47
51
|
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}")
|
|
48
|
-
|
|
52
|
+
response = MCPResponse(**data)
|
|
53
|
+
return MCP.from_response(response, client=self)
|
|
49
54
|
|
|
50
55
|
def find_mcps(self, name: str | None = None) -> list[MCP]:
|
|
51
56
|
"""Find MCPs by name."""
|
|
@@ -77,34 +82,156 @@ class MCPClient(BaseClient):
|
|
|
77
82
|
get_endpoint_fmt=f"{MCPS_ENDPOINT}{{id}}",
|
|
78
83
|
json=payload,
|
|
79
84
|
)
|
|
80
|
-
|
|
85
|
+
response = MCPResponse(**full_mcp_data)
|
|
86
|
+
return MCP.from_response(response, client=self)
|
|
81
87
|
|
|
82
|
-
def update_mcp(self, mcp_id: str, **kwargs) -> MCP:
|
|
88
|
+
def update_mcp(self, mcp_id: str | MCP, **kwargs) -> MCP:
|
|
83
89
|
"""Update an existing MCP.
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
Notes:
|
|
92
|
+
- Payload construction is centralized via ``_build_update_payload`` so required
|
|
93
|
+
defaults (e.g., ``type``) and value normalization stay consistent across SDK and CLI.
|
|
94
|
+
- For backward compatibility, still chooses PATCH vs PUT based on which fields the
|
|
95
|
+
caller provided, but uses the SDK payload builder for the final payload.
|
|
89
96
|
"""
|
|
90
|
-
#
|
|
97
|
+
# Backward-compatible: allow passing an MCP instance to avoid an extra fetch.
|
|
98
|
+
if isinstance(mcp_id, MCP):
|
|
99
|
+
current_mcp = mcp_id
|
|
100
|
+
if not current_mcp.id:
|
|
101
|
+
raise ValueError("MCP instance has no id; cannot update.")
|
|
102
|
+
mcp_id_value = str(current_mcp.id)
|
|
103
|
+
else:
|
|
104
|
+
current_mcp = None
|
|
105
|
+
mcp_id_value = mcp_id
|
|
106
|
+
|
|
91
107
|
required_fields = {"name", "config", "transport"}
|
|
92
108
|
provided_fields = set(kwargs.keys())
|
|
109
|
+
method = "PUT" if required_fields.issubset(provided_fields) else "PATCH"
|
|
110
|
+
|
|
111
|
+
if not kwargs:
|
|
112
|
+
data = self._request(method, f"{MCPS_ENDPOINT}{mcp_id_value}", json={})
|
|
113
|
+
response = MCPResponse(**data)
|
|
114
|
+
return MCP.from_response(response, client=self)
|
|
115
|
+
|
|
116
|
+
if current_mcp is None:
|
|
117
|
+
current_mcp = self.get_mcp_by_id(mcp_id_value)
|
|
118
|
+
|
|
119
|
+
payload_kwargs = kwargs.copy()
|
|
120
|
+
name = payload_kwargs.pop("name", None)
|
|
121
|
+
description = payload_kwargs.pop("description", None)
|
|
122
|
+
full_payload = self._build_update_payload(
|
|
123
|
+
current_mcp=current_mcp,
|
|
124
|
+
name=name,
|
|
125
|
+
description=description,
|
|
126
|
+
**payload_kwargs,
|
|
127
|
+
)
|
|
93
128
|
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
method = "PUT"
|
|
129
|
+
if method == "PUT":
|
|
130
|
+
json_payload = full_payload
|
|
97
131
|
else:
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
json_payload = {key: full_payload[key] for key in provided_fields if key in full_payload}
|
|
133
|
+
json_payload["type"] = full_payload["type"]
|
|
134
|
+
if "config" in provided_fields and "transport" not in provided_fields and "transport" in full_payload:
|
|
135
|
+
json_payload["transport"] = full_payload["transport"]
|
|
100
136
|
|
|
101
|
-
data = self._request(method, f"{MCPS_ENDPOINT}{
|
|
102
|
-
|
|
137
|
+
data = self._request(method, f"{MCPS_ENDPOINT}{mcp_id_value}", json=json_payload)
|
|
138
|
+
response = MCPResponse(**data)
|
|
139
|
+
return MCP.from_response(response, client=self)
|
|
103
140
|
|
|
104
141
|
def delete_mcp(self, mcp_id: str) -> None:
|
|
105
142
|
"""Delete an MCP."""
|
|
106
143
|
self._request("DELETE", f"{MCPS_ENDPOINT}{mcp_id}")
|
|
107
144
|
|
|
145
|
+
def upsert_mcp(
|
|
146
|
+
self,
|
|
147
|
+
identifier: str | MCP,
|
|
148
|
+
description: str | None = None,
|
|
149
|
+
config: dict[str, Any] | None = None,
|
|
150
|
+
**kwargs,
|
|
151
|
+
) -> MCP:
|
|
152
|
+
"""Create or update an MCP by instance, ID, or name.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
identifier: MCP instance, ID (UUID string), or name
|
|
156
|
+
description: MCP description
|
|
157
|
+
config: MCP configuration dictionary
|
|
158
|
+
**kwargs: Additional parameters (transport, metadata, etc.)
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The created or updated MCP.
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
>>> # By name (creates if not exists)
|
|
165
|
+
>>> mcp = client.mcps.upsert_mcp(
|
|
166
|
+
... "deepwiki",
|
|
167
|
+
... transport="sse",
|
|
168
|
+
... config={"url": "https://mcp.deepwiki.com/sse"},
|
|
169
|
+
... )
|
|
170
|
+
>>> # By instance
|
|
171
|
+
>>> mcp = client.mcps.upsert_mcp(existing_mcp, description="Updated")
|
|
172
|
+
>>> # By ID
|
|
173
|
+
>>> mcp = client.mcps.upsert_mcp("uuid-here", description="Updated")
|
|
174
|
+
"""
|
|
175
|
+
# Handle MCP instance
|
|
176
|
+
if isinstance(identifier, MCP):
|
|
177
|
+
if identifier.id:
|
|
178
|
+
logger.info("Updating MCP by instance: %s", identifier.name)
|
|
179
|
+
return self._do_upsert_update(identifier.id, identifier.name, description, config, **kwargs)
|
|
180
|
+
# MCP without ID - treat name as identifier
|
|
181
|
+
identifier = identifier.name
|
|
182
|
+
|
|
183
|
+
# Handle string (ID or name)
|
|
184
|
+
if isinstance(identifier, str):
|
|
185
|
+
if is_uuid(identifier):
|
|
186
|
+
logger.info("Updating MCP by ID: %s", identifier)
|
|
187
|
+
existing = self.get_mcp_by_id(identifier)
|
|
188
|
+
return self._do_upsert_update(identifier, existing.name, description, config, **kwargs)
|
|
189
|
+
|
|
190
|
+
# It's a name - find or create
|
|
191
|
+
return self._upsert_by_name(identifier, description, config, **kwargs)
|
|
192
|
+
|
|
193
|
+
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
194
|
+
|
|
195
|
+
def _do_upsert_update(
|
|
196
|
+
self,
|
|
197
|
+
mcp_id: str,
|
|
198
|
+
name: str | None,
|
|
199
|
+
description: str | None,
|
|
200
|
+
config: dict[str, Any] | None,
|
|
201
|
+
**kwargs,
|
|
202
|
+
) -> MCP:
|
|
203
|
+
"""Perform the update part of upsert."""
|
|
204
|
+
update_kwargs = {**kwargs}
|
|
205
|
+
if name is not None:
|
|
206
|
+
update_kwargs["name"] = name
|
|
207
|
+
if description is not None:
|
|
208
|
+
update_kwargs["description"] = description
|
|
209
|
+
if config is not None:
|
|
210
|
+
update_kwargs["config"] = config
|
|
211
|
+
return self.update_mcp(mcp_id, **update_kwargs)
|
|
212
|
+
|
|
213
|
+
def _upsert_by_name(
|
|
214
|
+
self,
|
|
215
|
+
name: str,
|
|
216
|
+
description: str | None,
|
|
217
|
+
config: dict[str, Any] | None,
|
|
218
|
+
**kwargs,
|
|
219
|
+
) -> MCP:
|
|
220
|
+
"""Find by name and update, or create if not found."""
|
|
221
|
+
all_mcps = self.list_mcps()
|
|
222
|
+
existing = [mcp for mcp in all_mcps if mcp.name.lower() == name.lower()]
|
|
223
|
+
|
|
224
|
+
if len(existing) == 1:
|
|
225
|
+
logger.info("Updating existing MCP: %s", name)
|
|
226
|
+
return self._do_upsert_update(existing[0].id, name, description, config, **kwargs)
|
|
227
|
+
|
|
228
|
+
if len(existing) > 1:
|
|
229
|
+
raise ValueError(f"Multiple MCPs found with name '{name}'")
|
|
230
|
+
|
|
231
|
+
# Create new MCP
|
|
232
|
+
logger.info("Creating new MCP: %s", name)
|
|
233
|
+
return self.create_mcp(name=name, description=description, config=config, **kwargs)
|
|
234
|
+
|
|
108
235
|
def _build_create_payload(
|
|
109
236
|
self,
|
|
110
237
|
name: str,
|
|
@@ -147,9 +274,7 @@ class MCPClient(BaseClient):
|
|
|
147
274
|
|
|
148
275
|
# Add any other kwargs (excluding already handled ones)
|
|
149
276
|
excluded_keys = {"type"} # type is handled above
|
|
150
|
-
|
|
151
|
-
if key not in excluded_keys:
|
|
152
|
-
payload[key] = value
|
|
277
|
+
add_kwargs_to_payload(payload, kwargs, excluded_keys)
|
|
153
278
|
|
|
154
279
|
return payload
|
|
155
280
|
|
|
@@ -206,7 +331,23 @@ class MCPClient(BaseClient):
|
|
|
206
331
|
def get_mcp_tools(self, mcp_id: str) -> list[dict[str, Any]]:
|
|
207
332
|
"""Get tools available from an MCP."""
|
|
208
333
|
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}/tools")
|
|
209
|
-
|
|
334
|
+
if data is None:
|
|
335
|
+
return []
|
|
336
|
+
if isinstance(data, list):
|
|
337
|
+
return data
|
|
338
|
+
if isinstance(data, dict):
|
|
339
|
+
if "tools" in data:
|
|
340
|
+
return data.get("tools", []) or []
|
|
341
|
+
logger.warning(
|
|
342
|
+
"Unexpected MCP tools response keys %s; returning empty list",
|
|
343
|
+
list(data.keys()),
|
|
344
|
+
)
|
|
345
|
+
return []
|
|
346
|
+
logger.warning(
|
|
347
|
+
"Unexpected MCP tools response type %s; returning empty list",
|
|
348
|
+
type(data).__name__,
|
|
349
|
+
)
|
|
350
|
+
return []
|
|
210
351
|
|
|
211
352
|
def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
212
353
|
"""Test MCP connection using configuration.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Agent payload types for requests and responses.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from glaip_sdk.client.payloads.agent.requests import (
|
|
8
|
+
AgentCreateRequest,
|
|
9
|
+
AgentListParams,
|
|
10
|
+
AgentUpdateRequest,
|
|
11
|
+
merge_payload_fields,
|
|
12
|
+
resolve_language_model_fields,
|
|
13
|
+
)
|
|
14
|
+
from glaip_sdk.client.payloads.agent.responses import AgentListResult
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AgentCreateRequest",
|
|
18
|
+
"AgentListParams",
|
|
19
|
+
"AgentListResult",
|
|
20
|
+
"AgentUpdateRequest",
|
|
21
|
+
"merge_payload_fields",
|
|
22
|
+
"resolve_language_model_fields",
|
|
23
|
+
]
|