glaip-sdk 0.0.16__py3-none-any.whl → 0.0.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glaip_sdk/__init__.py +1 -1
- glaip_sdk/branding.py +1 -1
- glaip_sdk/cli/commands/agents.py +10 -10
- glaip_sdk/cli/commands/configure.py +7 -2
- glaip_sdk/cli/commands/mcps.py +18 -19
- glaip_sdk/cli/commands/tools.py +19 -13
- glaip_sdk/cli/display.py +5 -4
- glaip_sdk/cli/rich_helpers.py +29 -0
- glaip_sdk/cli/slash/prompt.py +32 -17
- glaip_sdk/cli/slash/session.py +54 -25
- glaip_sdk/cli/utils.py +6 -6
- glaip_sdk/client/agents.py +581 -88
- glaip_sdk/client/main.py +10 -2
- glaip_sdk/client/tools.py +34 -3
- glaip_sdk/config/constants.py +1 -1
- glaip_sdk/utils/agent_config.py +49 -26
- glaip_sdk/utils/rendering/formatting.py +50 -29
- glaip_sdk/utils/rendering/renderer/base.py +156 -70
- {glaip_sdk-0.0.16.dist-info → glaip_sdk-0.0.18.dist-info}/METADATA +2 -2
- {glaip_sdk-0.0.16.dist-info → glaip_sdk-0.0.18.dist-info}/RECORD +22 -21
- {glaip_sdk-0.0.16.dist-info → glaip_sdk-0.0.18.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.16.dist-info → glaip_sdk-0.0.18.dist-info}/entry_points.txt +0 -0
glaip_sdk/client/agents.py
CHANGED
|
@@ -7,7 +7,9 @@ Authors:
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
-
from collections.abc import AsyncGenerator, Mapping
|
|
10
|
+
from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
|
|
11
|
+
from os import PathLike
|
|
12
|
+
from pathlib import Path
|
|
11
13
|
from typing import Any, BinaryIO
|
|
12
14
|
|
|
13
15
|
import httpx
|
|
@@ -19,10 +21,12 @@ from glaip_sdk.client._agent_payloads import (
|
|
|
19
21
|
AgentUpdateRequest,
|
|
20
22
|
)
|
|
21
23
|
from glaip_sdk.client.base import BaseClient
|
|
24
|
+
from glaip_sdk.client.mcps import MCPClient
|
|
22
25
|
from glaip_sdk.client.run_rendering import (
|
|
23
26
|
AgentRunRenderingManager,
|
|
24
27
|
compute_timeout_seconds,
|
|
25
28
|
)
|
|
29
|
+
from glaip_sdk.client.tools import ToolClient
|
|
26
30
|
from glaip_sdk.config.constants import (
|
|
27
31
|
DEFAULT_AGENT_FRAMEWORK,
|
|
28
32
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
@@ -32,13 +36,21 @@ from glaip_sdk.config.constants import (
|
|
|
32
36
|
)
|
|
33
37
|
from glaip_sdk.exceptions import NotFoundError
|
|
34
38
|
from glaip_sdk.models import Agent
|
|
39
|
+
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
40
|
+
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
35
41
|
from glaip_sdk.utils.client_utils import (
|
|
36
42
|
aiter_sse_events,
|
|
37
43
|
create_model_instances,
|
|
38
44
|
find_by_name,
|
|
39
45
|
prepare_multipart_data,
|
|
40
46
|
)
|
|
47
|
+
from glaip_sdk.utils.import_export import (
|
|
48
|
+
convert_export_to_import_format,
|
|
49
|
+
merge_import_with_cli_args,
|
|
50
|
+
)
|
|
41
51
|
from glaip_sdk.utils.rendering.renderer import RichStreamRenderer
|
|
52
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
53
|
+
from glaip_sdk.utils.serialization import load_resource_from_file
|
|
42
54
|
from glaip_sdk.utils.validation import validate_agent_instruction
|
|
43
55
|
|
|
44
56
|
# API endpoints
|
|
@@ -50,6 +62,188 @@ SSE_CONTENT_TYPE = "text/event-stream"
|
|
|
50
62
|
# Set up module-level logger
|
|
51
63
|
logger = logging.getLogger("glaip_sdk.agents")
|
|
52
64
|
|
|
65
|
+
_SERVER_ONLY_IMPORT_FIELDS = set(list_server_only_fields()) | {"success", "message"}
|
|
66
|
+
_MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
|
|
67
|
+
_DEFAULT_METADATA_TYPE = "custom"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _normalise_sequence(value: Any) -> list[Any] | None:
|
|
71
|
+
"""Normalise optional sequence inputs to plain lists."""
|
|
72
|
+
if value is None:
|
|
73
|
+
return None
|
|
74
|
+
if isinstance(value, list):
|
|
75
|
+
return value
|
|
76
|
+
if isinstance(value, (tuple, set)):
|
|
77
|
+
return list(value)
|
|
78
|
+
return [value]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _normalise_sequence_fields(mapping: dict[str, Any]) -> None:
|
|
82
|
+
"""Normalise merged sequence fields in-place."""
|
|
83
|
+
for field in _MERGED_SEQUENCE_FIELDS:
|
|
84
|
+
if field in mapping:
|
|
85
|
+
normalised = _normalise_sequence(mapping[field])
|
|
86
|
+
if normalised is not None:
|
|
87
|
+
mapping[field] = normalised
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _merge_override_maps(
|
|
91
|
+
base_values: Mapping[str, Any],
|
|
92
|
+
extra_values: Mapping[str, Any],
|
|
93
|
+
) -> dict[str, Any]:
|
|
94
|
+
"""Merge override mappings while normalising sequence fields."""
|
|
95
|
+
merged: dict[str, Any] = {}
|
|
96
|
+
for source in (base_values, extra_values):
|
|
97
|
+
for key, value in source.items():
|
|
98
|
+
if value is None:
|
|
99
|
+
continue
|
|
100
|
+
merged[key] = (
|
|
101
|
+
_normalise_sequence(value) if key in _MERGED_SEQUENCE_FIELDS else value
|
|
102
|
+
)
|
|
103
|
+
return merged
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _split_known_and_extra(
|
|
107
|
+
payload: Mapping[str, Any],
|
|
108
|
+
known_fields: Mapping[str, Any],
|
|
109
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
110
|
+
"""Split payload mapping into known request fields and extras."""
|
|
111
|
+
known: dict[str, Any] = {}
|
|
112
|
+
extras: dict[str, Any] = {}
|
|
113
|
+
for key, value in payload.items():
|
|
114
|
+
if value is None:
|
|
115
|
+
continue
|
|
116
|
+
if key in known_fields:
|
|
117
|
+
known[key] = value
|
|
118
|
+
else:
|
|
119
|
+
extras[key] = value
|
|
120
|
+
return known, extras
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _prepare_agent_metadata(value: Any) -> dict[str, Any]:
|
|
124
|
+
"""Ensure agent metadata contains ``type: custom`` by default."""
|
|
125
|
+
if value is None:
|
|
126
|
+
return {"type": _DEFAULT_METADATA_TYPE}
|
|
127
|
+
if not isinstance(value, Mapping):
|
|
128
|
+
return {"type": _DEFAULT_METADATA_TYPE}
|
|
129
|
+
|
|
130
|
+
prepared = dict(value)
|
|
131
|
+
metadata_type = prepared.get("type")
|
|
132
|
+
if not metadata_type:
|
|
133
|
+
prepared["type"] = _DEFAULT_METADATA_TYPE
|
|
134
|
+
return prepared
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _load_agent_file_payload(
|
|
138
|
+
file_path: Path, *, model_override: str | None
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""Load agent configuration from disk and normalise legacy fields."""
|
|
141
|
+
if not file_path.exists():
|
|
142
|
+
raise FileNotFoundError(f"Agent configuration file not found: {file_path}")
|
|
143
|
+
if not file_path.is_file():
|
|
144
|
+
raise ValueError(f"Agent configuration path must point to a file: {file_path}")
|
|
145
|
+
|
|
146
|
+
raw_data = load_resource_from_file(file_path)
|
|
147
|
+
if not isinstance(raw_data, Mapping):
|
|
148
|
+
raise ValueError("Agent configuration file must contain a mapping/object.")
|
|
149
|
+
|
|
150
|
+
payload = convert_export_to_import_format(dict(raw_data))
|
|
151
|
+
payload = normalize_agent_config_for_import(payload, model_override)
|
|
152
|
+
|
|
153
|
+
for field in _SERVER_ONLY_IMPORT_FIELDS:
|
|
154
|
+
payload.pop(field, None)
|
|
155
|
+
|
|
156
|
+
return payload
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _prepare_import_payload(
|
|
160
|
+
file_path: Path,
|
|
161
|
+
overrides: Mapping[str, Any],
|
|
162
|
+
*,
|
|
163
|
+
drop_model_fields: bool = False,
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
"""Prepare merged payload from file contents and explicit overrides."""
|
|
166
|
+
overrides_dict = dict(overrides)
|
|
167
|
+
|
|
168
|
+
raw_definition = load_resource_from_file(file_path)
|
|
169
|
+
original_refs = _extract_original_refs(raw_definition)
|
|
170
|
+
|
|
171
|
+
base_payload = _load_agent_file_payload(
|
|
172
|
+
file_path, model_override=overrides_dict.get("model")
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
cli_args = _build_cli_args(overrides_dict)
|
|
176
|
+
|
|
177
|
+
merged = merge_import_with_cli_args(base_payload, cli_args)
|
|
178
|
+
|
|
179
|
+
additional = _build_additional_args(overrides_dict, cli_args)
|
|
180
|
+
merged.update(additional)
|
|
181
|
+
|
|
182
|
+
if drop_model_fields:
|
|
183
|
+
_remove_model_fields_if_needed(merged, overrides_dict)
|
|
184
|
+
|
|
185
|
+
_set_default_refs(merged, original_refs)
|
|
186
|
+
|
|
187
|
+
_normalise_sequence_fields(merged)
|
|
188
|
+
return merged
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
|
|
192
|
+
"""Extract original tool/agent/mcp references from raw definition."""
|
|
193
|
+
return {
|
|
194
|
+
"tools": list(raw_definition.get("tools") or []),
|
|
195
|
+
"agents": list(raw_definition.get("agents") or []),
|
|
196
|
+
"mcps": list(raw_definition.get("mcps") or []),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
201
|
+
"""Build CLI args from overrides, filtering out None values."""
|
|
202
|
+
cli_args = {
|
|
203
|
+
key: overrides_dict.get(key)
|
|
204
|
+
for key in (
|
|
205
|
+
"name",
|
|
206
|
+
"instruction",
|
|
207
|
+
"model",
|
|
208
|
+
"tools",
|
|
209
|
+
"agents",
|
|
210
|
+
"mcps",
|
|
211
|
+
"timeout",
|
|
212
|
+
)
|
|
213
|
+
if overrides_dict.get(key) is not None
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Normalize sequence fields
|
|
217
|
+
for field in _MERGED_SEQUENCE_FIELDS:
|
|
218
|
+
if field in cli_args:
|
|
219
|
+
cli_args[field] = tuple(_normalise_sequence(cli_args[field]) or [])
|
|
220
|
+
|
|
221
|
+
return cli_args
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _build_additional_args(overrides_dict: dict, cli_args: dict) -> dict[str, Any]:
|
|
225
|
+
"""Build additional args not already in CLI args."""
|
|
226
|
+
return {
|
|
227
|
+
key: value
|
|
228
|
+
for key, value in overrides_dict.items()
|
|
229
|
+
if value is not None and key not in cli_args
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _remove_model_fields_if_needed(merged: dict, overrides_dict: dict) -> None:
|
|
234
|
+
"""Remove model fields if not explicitly overridden."""
|
|
235
|
+
if overrides_dict.get("language_model_id") is None:
|
|
236
|
+
merged.pop("language_model_id", None)
|
|
237
|
+
if overrides_dict.get("provider") is None:
|
|
238
|
+
merged.pop("provider", None)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _set_default_refs(merged: dict, original_refs: dict) -> None:
|
|
242
|
+
"""Set default references if not already present."""
|
|
243
|
+
merged.setdefault("_tool_refs", original_refs["tools"])
|
|
244
|
+
merged.setdefault("_agent_refs", original_refs["agents"])
|
|
245
|
+
merged.setdefault("_mcp_refs", original_refs["mcps"])
|
|
246
|
+
|
|
53
247
|
|
|
54
248
|
class AgentClient(BaseClient):
|
|
55
249
|
"""Client for agent operations."""
|
|
@@ -68,6 +262,8 @@ class AgentClient(BaseClient):
|
|
|
68
262
|
"""
|
|
69
263
|
super().__init__(parent_client=parent_client, **kwargs)
|
|
70
264
|
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
265
|
+
self._tool_client: ToolClient | None = None
|
|
266
|
+
self._mcp_client: MCPClient | None = None
|
|
71
267
|
|
|
72
268
|
def list_agents(
|
|
73
269
|
self,
|
|
@@ -219,129 +415,426 @@ class AgentClient(BaseClient):
|
|
|
219
415
|
finished_monotonic,
|
|
220
416
|
)
|
|
221
417
|
|
|
222
|
-
def
|
|
418
|
+
def _get_tool_client(self) -> ToolClient:
|
|
419
|
+
if self._tool_client is None:
|
|
420
|
+
self._tool_client = ToolClient(parent_client=self)
|
|
421
|
+
return self._tool_client
|
|
422
|
+
|
|
423
|
+
def _get_mcp_client(self) -> MCPClient:
|
|
424
|
+
if self._mcp_client is None:
|
|
425
|
+
self._mcp_client = MCPClient(parent_client=self)
|
|
426
|
+
return self._mcp_client
|
|
427
|
+
|
|
428
|
+
def _normalise_reference_entry(
|
|
223
429
|
self,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
430
|
+
entry: Any,
|
|
431
|
+
fallback_iter: Iterator[Any] | None,
|
|
432
|
+
) -> tuple[str | None, str | None]:
|
|
433
|
+
entry_id: str | None = None
|
|
434
|
+
entry_name: str | None = None
|
|
435
|
+
|
|
436
|
+
if isinstance(entry, str):
|
|
437
|
+
if is_uuid(entry):
|
|
438
|
+
entry_id = entry
|
|
439
|
+
else:
|
|
440
|
+
entry_name = entry
|
|
441
|
+
elif isinstance(entry, dict):
|
|
442
|
+
entry_id = entry.get("id")
|
|
443
|
+
entry_name = entry.get("name")
|
|
444
|
+
else:
|
|
445
|
+
entry_name = str(entry)
|
|
446
|
+
|
|
447
|
+
if entry_name or fallback_iter is None:
|
|
448
|
+
return entry_id, entry_name
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
ref = next(fallback_iter)
|
|
452
|
+
except StopIteration:
|
|
453
|
+
ref = None
|
|
454
|
+
if isinstance(ref, dict):
|
|
455
|
+
entry_name = ref.get("name") or entry_name
|
|
456
|
+
|
|
457
|
+
return entry_id, entry_name
|
|
458
|
+
|
|
459
|
+
def _resolve_resource_ids(
|
|
460
|
+
self,
|
|
461
|
+
items: list[Any] | None,
|
|
462
|
+
references: list[Any] | None,
|
|
230
463
|
*,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if not
|
|
237
|
-
|
|
464
|
+
fetch_by_id: Callable[[str], Any],
|
|
465
|
+
find_by_name: Callable[[str], list[Any]],
|
|
466
|
+
label: str,
|
|
467
|
+
plural_label: str | None = None,
|
|
468
|
+
) -> list[str] | None:
|
|
469
|
+
if not items:
|
|
470
|
+
return None
|
|
471
|
+
|
|
472
|
+
if references is None:
|
|
473
|
+
return [self._coerce_reference_value(entry) for entry in items]
|
|
474
|
+
|
|
475
|
+
singular = label
|
|
476
|
+
plural = plural_label or f"{label}s"
|
|
477
|
+
fallback_iter = iter(references or [])
|
|
478
|
+
|
|
479
|
+
return [
|
|
480
|
+
self._resolve_single_resource(
|
|
481
|
+
entry,
|
|
482
|
+
fallback_iter,
|
|
483
|
+
fetch_by_id,
|
|
484
|
+
find_by_name,
|
|
485
|
+
singular,
|
|
486
|
+
plural,
|
|
487
|
+
)
|
|
488
|
+
for entry in items
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
def _resolve_single_resource(
|
|
492
|
+
self,
|
|
493
|
+
entry: Any,
|
|
494
|
+
fallback_iter: Iterator[Any] | None,
|
|
495
|
+
fetch_by_id: Callable[[str], Any],
|
|
496
|
+
find_by_name: Callable[[str], list[Any]],
|
|
497
|
+
singular: str,
|
|
498
|
+
plural: str,
|
|
499
|
+
) -> str:
|
|
500
|
+
entry_id, entry_name = self._normalise_reference_entry(entry, fallback_iter)
|
|
501
|
+
|
|
502
|
+
validated_id = self._validate_resource_id(fetch_by_id, entry_id)
|
|
503
|
+
if validated_id:
|
|
504
|
+
return validated_id
|
|
505
|
+
if entry_id and entry_name is None:
|
|
506
|
+
return entry_id
|
|
507
|
+
|
|
508
|
+
if entry_name:
|
|
509
|
+
resolved, success = self._resolve_resource_by_name(
|
|
510
|
+
find_by_name, entry_name, singular, plural
|
|
511
|
+
)
|
|
512
|
+
return resolved if success else entry_name
|
|
513
|
+
|
|
514
|
+
raise ValueError(f"{singular} references must include a valid ID or name.")
|
|
515
|
+
|
|
516
|
+
@staticmethod
|
|
517
|
+
def _coerce_reference_value(entry: Any) -> str:
|
|
518
|
+
if isinstance(entry, dict):
|
|
519
|
+
if entry.get("id"):
|
|
520
|
+
return str(entry["id"])
|
|
521
|
+
if entry.get("name"):
|
|
522
|
+
return str(entry["name"])
|
|
523
|
+
return str(entry)
|
|
524
|
+
|
|
525
|
+
@staticmethod
|
|
526
|
+
def _validate_resource_id(
|
|
527
|
+
fetch_by_id: Callable[[str], Any], candidate_id: str | None
|
|
528
|
+
) -> str | None:
|
|
529
|
+
if not candidate_id:
|
|
530
|
+
return None
|
|
531
|
+
try:
|
|
532
|
+
fetch_by_id(candidate_id)
|
|
533
|
+
except Exception:
|
|
534
|
+
return None
|
|
535
|
+
return candidate_id
|
|
536
|
+
|
|
537
|
+
@staticmethod
|
|
538
|
+
def _resolve_resource_by_name(
|
|
539
|
+
find_by_name: Callable[[str], list[Any]],
|
|
540
|
+
entry_name: str,
|
|
541
|
+
singular: str,
|
|
542
|
+
plural: str,
|
|
543
|
+
) -> tuple[str, bool]:
|
|
544
|
+
try:
|
|
545
|
+
matches = find_by_name(entry_name)
|
|
546
|
+
except Exception:
|
|
547
|
+
return entry_name, False
|
|
238
548
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
549
|
+
if not matches:
|
|
550
|
+
raise ValueError(
|
|
551
|
+
f"{singular} '{entry_name}' not found in current workspace."
|
|
552
|
+
)
|
|
553
|
+
if len(matches) > 1:
|
|
554
|
+
exact = [
|
|
555
|
+
m
|
|
556
|
+
for m in matches
|
|
557
|
+
if getattr(m, "name", "").lower() == entry_name.lower()
|
|
558
|
+
]
|
|
559
|
+
if len(exact) == 1:
|
|
560
|
+
matches = exact
|
|
561
|
+
else:
|
|
562
|
+
raise ValueError(
|
|
563
|
+
f"Multiple {plural} named '{entry_name}'. Please disambiguate."
|
|
564
|
+
)
|
|
565
|
+
return str(matches[0].id), True
|
|
566
|
+
|
|
567
|
+
def _resolve_tool_ids(
|
|
568
|
+
self,
|
|
569
|
+
tools: list[Any] | None,
|
|
570
|
+
references: list[Any] | None = None,
|
|
571
|
+
) -> list[str] | None:
|
|
572
|
+
tool_client = self._get_tool_client()
|
|
573
|
+
return self._resolve_resource_ids(
|
|
574
|
+
tools,
|
|
575
|
+
references,
|
|
576
|
+
fetch_by_id=tool_client.get_tool_by_id,
|
|
577
|
+
find_by_name=tool_client.find_tools,
|
|
578
|
+
label="Tool",
|
|
579
|
+
plural_label="tools",
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
def _resolve_agent_ids(
|
|
583
|
+
self,
|
|
584
|
+
agents: list[Any] | None,
|
|
585
|
+
references: list[Any] | None = None,
|
|
586
|
+
) -> list[str] | None:
|
|
587
|
+
return self._resolve_resource_ids(
|
|
588
|
+
agents,
|
|
589
|
+
references,
|
|
590
|
+
fetch_by_id=self.get_agent_by_id,
|
|
591
|
+
find_by_name=self.find_agents,
|
|
592
|
+
label="Agent",
|
|
593
|
+
plural_label="agents",
|
|
257
594
|
)
|
|
258
595
|
|
|
596
|
+
def _resolve_mcp_ids(
|
|
597
|
+
self,
|
|
598
|
+
mcps: list[Any] | None,
|
|
599
|
+
references: list[Any] | None = None,
|
|
600
|
+
) -> list[str] | None:
|
|
601
|
+
mcp_client = self._get_mcp_client()
|
|
602
|
+
return self._resolve_resource_ids(
|
|
603
|
+
mcps,
|
|
604
|
+
references,
|
|
605
|
+
fetch_by_id=mcp_client.get_mcp_by_id,
|
|
606
|
+
find_by_name=mcp_client.find_mcps,
|
|
607
|
+
label="MCP",
|
|
608
|
+
plural_label="MCPs",
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
|
|
612
|
+
"""Create an agent using a fully prepared payload mapping."""
|
|
613
|
+
known, extras = _split_known_and_extra(
|
|
614
|
+
payload, AgentCreateRequest.__dataclass_fields__
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
name = known.pop("name", None)
|
|
618
|
+
instruction = known.pop("instruction", None)
|
|
619
|
+
if not name or not str(name).strip():
|
|
620
|
+
raise ValueError("Agent name cannot be empty or whitespace")
|
|
621
|
+
if not instruction or not str(instruction).strip():
|
|
622
|
+
raise ValueError("Agent instruction cannot be empty or whitespace")
|
|
623
|
+
|
|
624
|
+
validated_instruction = validate_agent_instruction(str(instruction))
|
|
625
|
+
_normalise_sequence_fields(known)
|
|
626
|
+
|
|
627
|
+
resolved_model = known.pop("model", None) or DEFAULT_MODEL
|
|
628
|
+
tool_refs = extras.pop("_tool_refs", None)
|
|
629
|
+
agent_refs = extras.pop("_agent_refs", None)
|
|
630
|
+
mcp_refs = extras.pop("_mcp_refs", None)
|
|
631
|
+
|
|
632
|
+
tools_raw = known.pop("tools", None)
|
|
633
|
+
agents_raw = known.pop("agents", None)
|
|
634
|
+
mcps_raw = known.pop("mcps", None)
|
|
635
|
+
|
|
636
|
+
resolved_tools = self._resolve_tool_ids(tools_raw, tool_refs)
|
|
637
|
+
resolved_agents = self._resolve_agent_ids(agents_raw, agent_refs)
|
|
638
|
+
resolved_mcps = self._resolve_mcp_ids(mcps_raw, mcp_refs)
|
|
639
|
+
|
|
640
|
+
language_model_id = known.pop("language_model_id", None)
|
|
641
|
+
provider = known.pop("provider", None)
|
|
642
|
+
model_name = known.pop("model_name", None)
|
|
643
|
+
|
|
644
|
+
agent_type_value = known.pop("agent_type", None)
|
|
645
|
+
fallback_type_value = known.pop("type", None)
|
|
646
|
+
if agent_type_value is None:
|
|
647
|
+
agent_type_value = fallback_type_value or DEFAULT_AGENT_TYPE
|
|
648
|
+
|
|
649
|
+
framework_value = known.pop("framework", None) or DEFAULT_AGENT_FRAMEWORK
|
|
650
|
+
version_value = known.pop("version", None) or DEFAULT_AGENT_VERSION
|
|
651
|
+
account_id = known.pop("account_id", None)
|
|
652
|
+
description = known.pop("description", None)
|
|
653
|
+
metadata = _prepare_agent_metadata(known.pop("metadata", None))
|
|
654
|
+
tool_configs = known.pop("tool_configs", None)
|
|
655
|
+
agent_config = known.pop("agent_config", None)
|
|
656
|
+
timeout_value = known.pop("timeout", None)
|
|
657
|
+
a2a_profile = known.pop("a2a_profile", None)
|
|
658
|
+
|
|
659
|
+
final_extras = {**known, **extras}
|
|
660
|
+
final_extras.setdefault("model", resolved_model)
|
|
661
|
+
|
|
259
662
|
request = AgentCreateRequest(
|
|
260
|
-
name=name,
|
|
261
|
-
instruction=
|
|
262
|
-
model=
|
|
663
|
+
name=str(name).strip(),
|
|
664
|
+
instruction=validated_instruction,
|
|
665
|
+
model=resolved_model,
|
|
263
666
|
language_model_id=language_model_id,
|
|
264
|
-
provider=
|
|
265
|
-
model_name=
|
|
266
|
-
agent_type=
|
|
267
|
-
framework=
|
|
268
|
-
version=
|
|
667
|
+
provider=provider,
|
|
668
|
+
model_name=model_name,
|
|
669
|
+
agent_type=agent_type_value,
|
|
670
|
+
framework=framework_value,
|
|
671
|
+
version=version_value,
|
|
269
672
|
account_id=account_id,
|
|
270
673
|
description=description,
|
|
271
674
|
metadata=metadata,
|
|
272
|
-
tools=
|
|
273
|
-
agents=
|
|
274
|
-
mcps=
|
|
675
|
+
tools=resolved_tools,
|
|
676
|
+
agents=resolved_agents,
|
|
677
|
+
mcps=resolved_mcps,
|
|
275
678
|
tool_configs=tool_configs,
|
|
276
679
|
agent_config=agent_config,
|
|
277
|
-
timeout=
|
|
680
|
+
timeout=timeout_value or DEFAULT_AGENT_RUN_TIMEOUT,
|
|
278
681
|
a2a_profile=a2a_profile,
|
|
279
|
-
extras=
|
|
682
|
+
extras=final_extras,
|
|
280
683
|
)
|
|
281
684
|
|
|
282
|
-
|
|
685
|
+
payload_dict = request.to_payload()
|
|
686
|
+
payload_dict.setdefault("model", resolved_model)
|
|
283
687
|
|
|
284
688
|
full_agent_data = self._post_then_fetch(
|
|
285
689
|
id_key="id",
|
|
286
690
|
post_endpoint=AGENTS_ENDPOINT,
|
|
287
691
|
get_endpoint_fmt=f"{AGENTS_ENDPOINT}{{id}}",
|
|
288
|
-
json=
|
|
692
|
+
json=payload_dict,
|
|
289
693
|
)
|
|
290
694
|
return Agent(**full_agent_data)._set_client(self)
|
|
291
695
|
|
|
292
|
-
def
|
|
696
|
+
def create_agent(
|
|
293
697
|
self,
|
|
294
|
-
agent_id: str,
|
|
295
698
|
name: str | None = None,
|
|
296
699
|
instruction: str | None = None,
|
|
297
700
|
model: str | None = None,
|
|
701
|
+
tools: list[str | Any] | None = None,
|
|
702
|
+
agents: list[str | Any] | None = None,
|
|
703
|
+
timeout: int | None = None,
|
|
704
|
+
*,
|
|
705
|
+
file: str | PathLike[str] | None = None,
|
|
706
|
+
mcps: list[str | Any] | None = None,
|
|
707
|
+
tool_configs: Mapping[str, Any] | None = None,
|
|
298
708
|
**kwargs: Any,
|
|
299
709
|
) -> "Agent":
|
|
300
|
-
"""
|
|
301
|
-
|
|
710
|
+
"""Create a new agent, optionally loading configuration from a file."""
|
|
711
|
+
base_overrides = {
|
|
712
|
+
"name": name,
|
|
713
|
+
"instruction": instruction,
|
|
714
|
+
"model": model,
|
|
715
|
+
"tools": tools,
|
|
716
|
+
"agents": agents,
|
|
717
|
+
"timeout": timeout,
|
|
718
|
+
"mcps": mcps,
|
|
719
|
+
"tool_configs": tool_configs,
|
|
720
|
+
}
|
|
721
|
+
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
722
|
+
|
|
723
|
+
if file is not None:
|
|
724
|
+
payload = _prepare_import_payload(
|
|
725
|
+
Path(file).expanduser(), overrides, drop_model_fields=True
|
|
726
|
+
)
|
|
727
|
+
if overrides.get("model") is None:
|
|
728
|
+
payload.pop("model", None)
|
|
729
|
+
else:
|
|
730
|
+
payload = overrides
|
|
731
|
+
|
|
732
|
+
return self._create_agent_from_payload(payload)
|
|
733
|
+
|
|
734
|
+
def create_agent_from_file( # pragma: no cover - thin compatibility wrapper
|
|
735
|
+
self,
|
|
736
|
+
file_path: str | PathLike[str],
|
|
737
|
+
**overrides: Any,
|
|
738
|
+
) -> "Agent":
|
|
739
|
+
"""Backward-compatible helper to create an agent from a configuration file."""
|
|
740
|
+
return self.create_agent(file=file_path, **overrides)
|
|
741
|
+
|
|
742
|
+
def _update_agent_from_payload(
|
|
743
|
+
self,
|
|
744
|
+
agent_id: str,
|
|
745
|
+
current_agent: Agent,
|
|
746
|
+
payload: Mapping[str, Any],
|
|
747
|
+
) -> "Agent":
|
|
748
|
+
"""Update an agent using a prepared payload mapping."""
|
|
749
|
+
known, extras = _split_known_and_extra(
|
|
750
|
+
payload, AgentUpdateRequest.__dataclass_fields__
|
|
751
|
+
)
|
|
752
|
+
_normalise_sequence_fields(known)
|
|
753
|
+
|
|
754
|
+
tool_refs = extras.pop("_tool_refs", None)
|
|
755
|
+
agent_refs = extras.pop("_agent_refs", None)
|
|
756
|
+
mcp_refs = extras.pop("_mcp_refs", None)
|
|
757
|
+
|
|
758
|
+
tools_value = known.pop("tools", None)
|
|
759
|
+
agents_value = known.pop("agents", None)
|
|
760
|
+
mcps_value = known.pop("mcps", None)
|
|
302
761
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
account_id = kwargs.pop("account_id", None)
|
|
310
|
-
description = kwargs.pop("description", None)
|
|
311
|
-
metadata = kwargs.pop("metadata", None)
|
|
312
|
-
tools = kwargs.pop("tools", None)
|
|
313
|
-
tool_configs = kwargs.pop("tool_configs", None)
|
|
314
|
-
agents_value = kwargs.pop("agents", None)
|
|
315
|
-
mcps = kwargs.pop("mcps", None)
|
|
316
|
-
agent_config = kwargs.pop("agent_config", None)
|
|
317
|
-
a2a_profile = kwargs.pop("a2a_profile", None)
|
|
762
|
+
if tools_value is not None:
|
|
763
|
+
tools_value = self._resolve_tool_ids(tools_value, tool_refs)
|
|
764
|
+
if agents_value is not None:
|
|
765
|
+
agents_value = self._resolve_agent_ids(agents_value, agent_refs)
|
|
766
|
+
if mcps_value is not None:
|
|
767
|
+
mcps_value = self._resolve_mcp_ids(mcps_value, mcp_refs) # pragma: no cover
|
|
318
768
|
|
|
319
769
|
request = AgentUpdateRequest(
|
|
320
|
-
name=name,
|
|
321
|
-
instruction=instruction,
|
|
322
|
-
description=description,
|
|
323
|
-
model=model,
|
|
324
|
-
language_model_id=language_model_id,
|
|
325
|
-
provider=
|
|
326
|
-
model_name=
|
|
327
|
-
agent_type=
|
|
328
|
-
framework=
|
|
329
|
-
version=
|
|
330
|
-
account_id=account_id,
|
|
331
|
-
metadata=metadata,
|
|
332
|
-
tools=
|
|
333
|
-
tool_configs=tool_configs,
|
|
770
|
+
name=known.pop("name", None),
|
|
771
|
+
instruction=known.pop("instruction", None),
|
|
772
|
+
description=known.pop("description", None),
|
|
773
|
+
model=known.pop("model", None),
|
|
774
|
+
language_model_id=known.pop("language_model_id", None),
|
|
775
|
+
provider=known.pop("provider", None),
|
|
776
|
+
model_name=known.pop("model_name", None),
|
|
777
|
+
agent_type=known.pop("agent_type", known.pop("type", None)),
|
|
778
|
+
framework=known.pop("framework", None),
|
|
779
|
+
version=known.pop("version", None),
|
|
780
|
+
account_id=known.pop("account_id", None),
|
|
781
|
+
metadata=known.pop("metadata", None),
|
|
782
|
+
tools=tools_value,
|
|
783
|
+
tool_configs=known.pop("tool_configs", None),
|
|
334
784
|
agents=agents_value,
|
|
335
|
-
mcps=
|
|
336
|
-
agent_config=agent_config,
|
|
337
|
-
a2a_profile=a2a_profile,
|
|
338
|
-
extras=
|
|
785
|
+
mcps=mcps_value,
|
|
786
|
+
agent_config=known.pop("agent_config", None),
|
|
787
|
+
a2a_profile=known.pop("a2a_profile", None),
|
|
788
|
+
extras={**known, **extras},
|
|
339
789
|
)
|
|
340
790
|
|
|
341
|
-
|
|
791
|
+
payload_dict = request.to_payload(current_agent)
|
|
342
792
|
|
|
343
|
-
|
|
344
|
-
return Agent(**
|
|
793
|
+
response = self._request("PUT", f"/agents/{agent_id}", json=payload_dict)
|
|
794
|
+
return Agent(**response)._set_client(self)
|
|
795
|
+
|
|
796
|
+
def update_agent(
|
|
797
|
+
self,
|
|
798
|
+
agent_id: str,
|
|
799
|
+
name: str | None = None,
|
|
800
|
+
instruction: str | None = None,
|
|
801
|
+
model: str | None = None,
|
|
802
|
+
*,
|
|
803
|
+
file: str | PathLike[str] | None = None,
|
|
804
|
+
tools: list[str | Any] | None = None,
|
|
805
|
+
agents: list[str | Any] | None = None,
|
|
806
|
+
mcps: list[str | Any] | None = None,
|
|
807
|
+
**kwargs: Any,
|
|
808
|
+
) -> "Agent":
|
|
809
|
+
"""Update an existing agent."""
|
|
810
|
+
base_overrides = {
|
|
811
|
+
"name": name,
|
|
812
|
+
"instruction": instruction,
|
|
813
|
+
"model": model,
|
|
814
|
+
"tools": tools,
|
|
815
|
+
"agents": agents,
|
|
816
|
+
"mcps": mcps,
|
|
817
|
+
}
|
|
818
|
+
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
819
|
+
|
|
820
|
+
if file is not None:
|
|
821
|
+
payload = _prepare_import_payload(
|
|
822
|
+
Path(file).expanduser(), overrides, drop_model_fields=True
|
|
823
|
+
)
|
|
824
|
+
else:
|
|
825
|
+
payload = overrides
|
|
826
|
+
|
|
827
|
+
current_agent = self.get_agent_by_id(agent_id)
|
|
828
|
+
return self._update_agent_from_payload(agent_id, current_agent, payload)
|
|
829
|
+
|
|
830
|
+
def update_agent_from_file( # pragma: no cover - thin compatibility wrapper
|
|
831
|
+
self,
|
|
832
|
+
agent_id: str,
|
|
833
|
+
file_path: str | PathLike[str],
|
|
834
|
+
**overrides: Any,
|
|
835
|
+
) -> "Agent":
|
|
836
|
+
"""Backward-compatible helper to update an agent from a configuration file."""
|
|
837
|
+
return self.update_agent(agent_id, file=file_path, **overrides)
|
|
345
838
|
|
|
346
839
|
def delete_agent(self, agent_id: str) -> None:
|
|
347
840
|
"""Delete an agent."""
|