glaip-sdk 0.0.20__py3-none-any.whl → 0.7.7__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 +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1250 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +271 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -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 +734 -143
- 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 +14 -12
- 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 +164 -23
- glaip_sdk/cli/config.py +49 -7
- 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 +45 -32
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +344 -167
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +15 -22
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +5 -10
- glaip_sdk/cli/rich_helpers.py +1 -3
- 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 +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +827 -232
- glaip_sdk/cli/slash/tui/__init__.py +34 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +88 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +933 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/clipboard.py +147 -0
- glaip_sdk/cli/slash/tui/context.py +59 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -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 +86 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +123 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -329
- glaip_sdk/cli/update_notifier.py +385 -24
- glaip_sdk/cli/validators.py +16 -18
- 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 +370 -100
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +25 -10
- glaip_sdk/client/mcps.py +166 -27
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +65 -74
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +583 -79
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +214 -56
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- 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/icons.py +9 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +107 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +117 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -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 -3
- 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 +112 -0
- glaip_sdk/runner/langgraph.py +872 -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 +95 -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 +468 -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 +4 -14
- glaip_sdk/utils/bundler.py +403 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- 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 +38 -23
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +18 -8
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -51
- glaip_sdk/utils/rendering/renderer/base.py +534 -882
- glaip_sdk/utils/rendering/renderer/config.py +4 -10
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +13 -54
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +182 -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/step_tree_state.py +100 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/steps/event_processor.py +778 -0
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/{steps.py → steps/manager.py} +122 -26
- 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 +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- 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 +20 -28
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/METADATA +78 -23
- glaip_sdk-0.7.7.dist-info/RECORD +213 -0
- {glaip_sdk-0.0.20.dist-info → glaip_sdk-0.7.7.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.7.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.7.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1412
- glaip_sdk/cli/commands/mcps.py +0 -1225
- glaip_sdk/cli/commands/tools.py +0 -597
- glaip_sdk/cli/utils.py +0 -1330
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.0.20.dist-info/RECORD +0 -80
- glaip_sdk-0.0.20.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
|
+
from collections.abc import Iterable, Mapping
|
|
10
12
|
from typing import Any, NoReturn, Union
|
|
11
13
|
|
|
12
14
|
import httpx
|
|
@@ -151,12 +153,7 @@ class BaseClient:
|
|
|
151
153
|
def timeout(self, value: float) -> None:
|
|
152
154
|
"""Set timeout and rebuild client."""
|
|
153
155
|
self._timeout = value
|
|
154
|
-
if (
|
|
155
|
-
hasattr(self, "http_client")
|
|
156
|
-
and self.http_client
|
|
157
|
-
and not self._session_scoped
|
|
158
|
-
and not self._parent_client
|
|
159
|
-
):
|
|
156
|
+
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
|
160
157
|
self.http_client.close()
|
|
161
158
|
self.http_client = self._build_client(value)
|
|
162
159
|
|
|
@@ -246,18 +243,14 @@ class BaseClient:
|
|
|
246
243
|
client_log.debug(f"Response status: {response.status_code}")
|
|
247
244
|
return response
|
|
248
245
|
except httpx.ConnectError as e:
|
|
249
|
-
client_log.warning(
|
|
250
|
-
f"Connection error on {method} {endpoint}, retrying once: {e}"
|
|
251
|
-
)
|
|
246
|
+
client_log.warning(f"Connection error on {method} {endpoint}, retrying once: {e}")
|
|
252
247
|
try:
|
|
253
248
|
response = self.http_client.request(method, endpoint, **kwargs)
|
|
254
|
-
client_log.debug(
|
|
255
|
-
f"Retry successful, response status: {response.status_code}"
|
|
256
|
-
)
|
|
249
|
+
client_log.debug(f"Retry successful, response status: {response.status_code}")
|
|
257
250
|
return response
|
|
258
251
|
except httpx.ConnectError:
|
|
259
252
|
client_log.error(f"Retry failed for {method} {endpoint}: {e}")
|
|
260
|
-
raise
|
|
253
|
+
raise
|
|
261
254
|
|
|
262
255
|
def _request(self, method: str, endpoint: str, **kwargs) -> Any:
|
|
263
256
|
"""Make HTTP request with error handling and unwrap success envelopes."""
|
|
@@ -298,7 +291,7 @@ class BaseClient:
|
|
|
298
291
|
return parsed.get("data", parsed) if unwrap else parsed
|
|
299
292
|
else:
|
|
300
293
|
error_type = parsed.get("error", "UnknownError")
|
|
301
|
-
message = parsed.
|
|
294
|
+
message = self._format_error_dict({key: value for key, value in parsed.items() if key != "success"})
|
|
302
295
|
self._raise_api_error(
|
|
303
296
|
400,
|
|
304
297
|
message,
|
|
@@ -341,12 +334,58 @@ class BaseClient:
|
|
|
341
334
|
return validation_message
|
|
342
335
|
return f"Validation error: {parsed}"
|
|
343
336
|
|
|
337
|
+
formatted_details = None
|
|
338
|
+
if "details" in parsed:
|
|
339
|
+
formatted_details = self._format_error_details(parsed["details"])
|
|
340
|
+
|
|
344
341
|
message = parsed.get("message")
|
|
345
342
|
if message:
|
|
343
|
+
if formatted_details:
|
|
344
|
+
return f"{message}\n{formatted_details}"
|
|
346
345
|
return message
|
|
347
346
|
|
|
347
|
+
if formatted_details:
|
|
348
|
+
return formatted_details
|
|
349
|
+
|
|
348
350
|
return str(parsed) if parsed else DEFAULT_ERROR_MESSAGE
|
|
349
351
|
|
|
352
|
+
def _format_error_details(self, details: Any) -> str | None:
|
|
353
|
+
"""Render generic error details into a human-readable string."""
|
|
354
|
+
if details is None:
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
if isinstance(details, dict):
|
|
358
|
+
return self._format_detail_mapping(details)
|
|
359
|
+
|
|
360
|
+
if isinstance(details, (list, tuple, set)):
|
|
361
|
+
return self._format_detail_iterable(details)
|
|
362
|
+
|
|
363
|
+
return f"Details: {details}"
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def _format_detail_mapping(details: Mapping[str, Any]) -> str | None:
|
|
367
|
+
"""Format details provided as a mapping."""
|
|
368
|
+
entries = [f" {key}: {value}" for key, value in details.items()]
|
|
369
|
+
if not entries:
|
|
370
|
+
return None
|
|
371
|
+
return "Details:\n" + "\n".join(entries)
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def _format_detail_iterable(details: Iterable[Any]) -> str | None:
|
|
375
|
+
"""Format details provided as an iterable collection."""
|
|
376
|
+
entries: list[str] = []
|
|
377
|
+
for item in details:
|
|
378
|
+
if isinstance(item, Mapping):
|
|
379
|
+
inner = ", ".join(f"{k}={v}" for k, v in item.items())
|
|
380
|
+
entries.append(f" - {inner if inner else '{}'}")
|
|
381
|
+
else:
|
|
382
|
+
entries.append(f" - {item}")
|
|
383
|
+
|
|
384
|
+
if not entries:
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
return "Details:\n" + "\n".join(entries)
|
|
388
|
+
|
|
350
389
|
def _format_validation_errors(self, errors: list[Any]) -> str | None:
|
|
351
390
|
"""Render validation errors into a human-readable string."""
|
|
352
391
|
entries: list[str] = []
|
|
@@ -365,6 +404,22 @@ class BaseClient:
|
|
|
365
404
|
|
|
366
405
|
return "Validation errors:\n" + "\n".join(entries)
|
|
367
406
|
|
|
407
|
+
@staticmethod
|
|
408
|
+
def _is_no_content_response(response: httpx.Response) -> bool:
|
|
409
|
+
"""Return True when the response contains no content."""
|
|
410
|
+
return response.status_code == 204
|
|
411
|
+
|
|
412
|
+
@staticmethod
|
|
413
|
+
def _is_success_status(response: httpx.Response) -> bool:
|
|
414
|
+
"""Return True for successful HTTP status codes."""
|
|
415
|
+
return 200 <= response.status_code < 300
|
|
416
|
+
|
|
417
|
+
def _handle_error_response(self, response: httpx.Response) -> None:
|
|
418
|
+
"""Raise an API error for non-success responses."""
|
|
419
|
+
error_message = self._get_error_message(response)
|
|
420
|
+
parsed_content = self._parse_response_content(response)
|
|
421
|
+
self._raise_api_error(response.status_code, error_message, payload=parsed_content)
|
|
422
|
+
|
|
368
423
|
def _handle_response(
|
|
369
424
|
self,
|
|
370
425
|
response: httpx.Response,
|
|
@@ -373,24 +428,17 @@ class BaseClient:
|
|
|
373
428
|
) -> Any:
|
|
374
429
|
"""Handle HTTP response with proper error handling."""
|
|
375
430
|
# Handle no-content success before general error handling
|
|
376
|
-
if response
|
|
431
|
+
if self._is_no_content_response(response):
|
|
377
432
|
return None
|
|
378
433
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
self._raise_api_error(
|
|
385
|
-
response.status_code, error_message, payload=parsed_content
|
|
386
|
-
)
|
|
387
|
-
return None # Won't be reached but helps with type checking
|
|
388
|
-
|
|
389
|
-
parsed = self._parse_response_content(response)
|
|
390
|
-
if parsed is None:
|
|
391
|
-
return None
|
|
434
|
+
if self._is_success_status(response):
|
|
435
|
+
parsed = self._parse_response_content(response)
|
|
436
|
+
if parsed is None:
|
|
437
|
+
return None
|
|
438
|
+
return self._handle_success_response(parsed, unwrap=unwrap)
|
|
392
439
|
|
|
393
|
-
|
|
440
|
+
self._handle_error_response(response)
|
|
441
|
+
return None
|
|
394
442
|
|
|
395
443
|
def _raise_api_error(
|
|
396
444
|
self,
|
|
@@ -435,12 +483,7 @@ class BaseClient:
|
|
|
435
483
|
|
|
436
484
|
def close(self) -> None:
|
|
437
485
|
"""Close the HTTP client."""
|
|
438
|
-
if (
|
|
439
|
-
hasattr(self, "http_client")
|
|
440
|
-
and self.http_client
|
|
441
|
-
and not self._session_scoped
|
|
442
|
-
and not self._parent_client
|
|
443
|
-
):
|
|
486
|
+
if hasattr(self, "http_client") and self.http_client and not self._session_scoped and not self._parent_client:
|
|
444
487
|
self.http_client.close()
|
|
445
488
|
|
|
446
489
|
def __enter__(self) -> "BaseClient":
|
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,
|
|
@@ -217,6 +225,11 @@ class Client(BaseClient):
|
|
|
217
225
|
|
|
218
226
|
@timeout.setter
|
|
219
227
|
def timeout(self, value: float) -> None: # type: ignore[override]
|
|
228
|
+
"""Set the client timeout and propagate to sub-clients.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
value: Timeout value in seconds.
|
|
232
|
+
"""
|
|
220
233
|
# Rebuild the root http client
|
|
221
234
|
BaseClient.timeout.fset(self, value) # call parent setter
|
|
222
235
|
# Propagate the new session to sub-clients so they don't hold a closed client
|
|
@@ -227,6 +240,8 @@ class Client(BaseClient):
|
|
|
227
240
|
self.tools.http_client = self.http_client
|
|
228
241
|
if hasattr(self, "mcps"):
|
|
229
242
|
self.mcps.http_client = self.http_client
|
|
243
|
+
if hasattr(self, "schedules"):
|
|
244
|
+
self.schedules.http_client = self.http_client
|
|
230
245
|
except Exception:
|
|
231
246
|
pass
|
|
232
247
|
|
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
|
|
|
@@ -179,9 +304,7 @@ class MCPClient(BaseClient):
|
|
|
179
304
|
update_data = {
|
|
180
305
|
"name": name if name is not None else current_mcp.name,
|
|
181
306
|
"type": DEFAULT_MCP_TYPE, # Required by backend, MCPs are always server type
|
|
182
|
-
"transport": kwargs.get(
|
|
183
|
-
"transport", getattr(current_mcp, "transport", DEFAULT_MCP_TRANSPORT)
|
|
184
|
-
),
|
|
307
|
+
"transport": kwargs.get("transport", getattr(current_mcp, "transport", DEFAULT_MCP_TRANSPORT)),
|
|
185
308
|
}
|
|
186
309
|
|
|
187
310
|
# Handle description with proper None handling
|
|
@@ -208,7 +331,23 @@ class MCPClient(BaseClient):
|
|
|
208
331
|
def get_mcp_tools(self, mcp_id: str) -> list[dict[str, Any]]:
|
|
209
332
|
"""Get tools available from an MCP."""
|
|
210
333
|
data = self._request("GET", f"{MCPS_ENDPOINT}{mcp_id}/tools")
|
|
211
|
-
|
|
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 []
|
|
212
351
|
|
|
213
352
|
def test_mcp_connection(self, config: dict[str, Any]) -> dict[str, Any]:
|
|
214
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
|
+
]
|