glaip-sdk 0.6.12__py3-none-any.whl → 0.6.14__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 +42 -5
- {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/METADATA +31 -37
- glaip_sdk-0.6.14.dist-info/RECORD +12 -0
- {glaip_sdk-0.6.12.dist-info → glaip_sdk-0.6.14.dist-info}/WHEEL +2 -1
- glaip_sdk-0.6.14.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.6.14.dist-info/top_level.txt +1 -0
- glaip_sdk/agents/__init__.py +0 -27
- glaip_sdk/agents/base.py +0 -1191
- glaip_sdk/cli/__init__.py +0 -9
- glaip_sdk/cli/account_store.py +0 -540
- glaip_sdk/cli/agent_config.py +0 -78
- glaip_sdk/cli/auth.py +0 -699
- glaip_sdk/cli/commands/__init__.py +0 -5
- glaip_sdk/cli/commands/accounts.py +0 -746
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/common_config.py +0 -101
- glaip_sdk/cli/commands/configure.py +0 -896
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/models.py +0 -69
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/commands/transcripts.py +0 -755
- glaip_sdk/cli/commands/update.py +0 -61
- glaip_sdk/cli/config.py +0 -95
- glaip_sdk/cli/constants.py +0 -38
- glaip_sdk/cli/context.py +0 -150
- glaip_sdk/cli/core/__init__.py +0 -79
- glaip_sdk/cli/core/context.py +0 -124
- glaip_sdk/cli/core/output.py +0 -846
- glaip_sdk/cli/core/prompting.py +0 -649
- glaip_sdk/cli/core/rendering.py +0 -187
- glaip_sdk/cli/display.py +0 -355
- glaip_sdk/cli/hints.py +0 -57
- glaip_sdk/cli/io.py +0 -112
- glaip_sdk/cli/main.py +0 -604
- glaip_sdk/cli/masking.py +0 -136
- glaip_sdk/cli/mcp_validators.py +0 -287
- glaip_sdk/cli/pager.py +0 -266
- glaip_sdk/cli/parsers/__init__.py +0 -7
- glaip_sdk/cli/parsers/json_input.py +0 -177
- glaip_sdk/cli/resolution.py +0 -67
- glaip_sdk/cli/rich_helpers.py +0 -27
- glaip_sdk/cli/slash/__init__.py +0 -15
- glaip_sdk/cli/slash/accounts_controller.py +0 -578
- glaip_sdk/cli/slash/accounts_shared.py +0 -75
- glaip_sdk/cli/slash/agent_session.py +0 -285
- glaip_sdk/cli/slash/prompt.py +0 -256
- glaip_sdk/cli/slash/remote_runs_controller.py +0 -566
- glaip_sdk/cli/slash/session.py +0 -1708
- glaip_sdk/cli/slash/tui/__init__.py +0 -9
- glaip_sdk/cli/slash/tui/accounts_app.py +0 -876
- glaip_sdk/cli/slash/tui/background_tasks.py +0 -72
- glaip_sdk/cli/slash/tui/loading.py +0 -58
- glaip_sdk/cli/slash/tui/remote_runs_app.py +0 -628
- glaip_sdk/cli/transcript/__init__.py +0 -31
- glaip_sdk/cli/transcript/cache.py +0 -536
- glaip_sdk/cli/transcript/capture.py +0 -329
- glaip_sdk/cli/transcript/export.py +0 -38
- glaip_sdk/cli/transcript/history.py +0 -815
- glaip_sdk/cli/transcript/launcher.py +0 -77
- glaip_sdk/cli/transcript/viewer.py +0 -374
- glaip_sdk/cli/update_notifier.py +0 -290
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk/cli/validators.py +0 -238
- glaip_sdk/client/__init__.py +0 -11
- glaip_sdk/client/_agent_payloads.py +0 -520
- glaip_sdk/client/agent_runs.py +0 -147
- glaip_sdk/client/agents.py +0 -1335
- glaip_sdk/client/base.py +0 -502
- glaip_sdk/client/main.py +0 -249
- glaip_sdk/client/mcps.py +0 -370
- glaip_sdk/client/run_rendering.py +0 -700
- glaip_sdk/client/shared.py +0 -21
- glaip_sdk/client/tools.py +0 -661
- glaip_sdk/client/validators.py +0 -198
- glaip_sdk/config/constants.py +0 -52
- glaip_sdk/mcps/__init__.py +0 -21
- glaip_sdk/mcps/base.py +0 -345
- glaip_sdk/models/__init__.py +0 -90
- glaip_sdk/models/agent.py +0 -47
- glaip_sdk/models/agent_runs.py +0 -116
- glaip_sdk/models/common.py +0 -42
- glaip_sdk/models/mcp.py +0 -33
- glaip_sdk/models/tool.py +0 -33
- glaip_sdk/payload_schemas/__init__.py +0 -7
- glaip_sdk/payload_schemas/agent.py +0 -85
- glaip_sdk/registry/__init__.py +0 -55
- glaip_sdk/registry/agent.py +0 -164
- glaip_sdk/registry/base.py +0 -139
- glaip_sdk/registry/mcp.py +0 -253
- glaip_sdk/registry/tool.py +0 -232
- glaip_sdk/runner/__init__.py +0 -59
- glaip_sdk/runner/base.py +0 -84
- glaip_sdk/runner/deps.py +0 -115
- glaip_sdk/runner/langgraph.py +0 -782
- glaip_sdk/runner/mcp_adapter/__init__.py +0 -13
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +0 -43
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +0 -257
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +0 -95
- glaip_sdk/runner/tool_adapter/__init__.py +0 -18
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +0 -44
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +0 -219
- glaip_sdk/tools/__init__.py +0 -22
- glaip_sdk/tools/base.py +0 -435
- glaip_sdk/utils/__init__.py +0 -86
- glaip_sdk/utils/a2a/__init__.py +0 -34
- glaip_sdk/utils/a2a/event_processor.py +0 -188
- glaip_sdk/utils/agent_config.py +0 -194
- glaip_sdk/utils/bundler.py +0 -267
- glaip_sdk/utils/client.py +0 -111
- glaip_sdk/utils/client_utils.py +0 -486
- glaip_sdk/utils/datetime_helpers.py +0 -58
- glaip_sdk/utils/discovery.py +0 -78
- glaip_sdk/utils/display.py +0 -135
- glaip_sdk/utils/export.py +0 -143
- glaip_sdk/utils/general.py +0 -61
- glaip_sdk/utils/import_export.py +0 -168
- glaip_sdk/utils/import_resolver.py +0 -492
- glaip_sdk/utils/instructions.py +0 -101
- glaip_sdk/utils/rendering/__init__.py +0 -115
- glaip_sdk/utils/rendering/formatting.py +0 -264
- glaip_sdk/utils/rendering/layout/__init__.py +0 -64
- glaip_sdk/utils/rendering/layout/panels.py +0 -156
- glaip_sdk/utils/rendering/layout/progress.py +0 -202
- glaip_sdk/utils/rendering/layout/summary.py +0 -74
- glaip_sdk/utils/rendering/layout/transcript.py +0 -606
- glaip_sdk/utils/rendering/models.py +0 -85
- glaip_sdk/utils/rendering/renderer/__init__.py +0 -55
- glaip_sdk/utils/rendering/renderer/base.py +0 -1024
- glaip_sdk/utils/rendering/renderer/config.py +0 -27
- glaip_sdk/utils/rendering/renderer/console.py +0 -55
- glaip_sdk/utils/rendering/renderer/debug.py +0 -178
- glaip_sdk/utils/rendering/renderer/factory.py +0 -138
- glaip_sdk/utils/rendering/renderer/stream.py +0 -202
- glaip_sdk/utils/rendering/renderer/summary_window.py +0 -79
- glaip_sdk/utils/rendering/renderer/thinking.py +0 -273
- glaip_sdk/utils/rendering/renderer/toggle.py +0 -182
- glaip_sdk/utils/rendering/renderer/tool_panels.py +0 -442
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +0 -162
- glaip_sdk/utils/rendering/state.py +0 -204
- glaip_sdk/utils/rendering/step_tree_state.py +0 -100
- glaip_sdk/utils/rendering/steps/__init__.py +0 -34
- glaip_sdk/utils/rendering/steps/event_processor.py +0 -778
- glaip_sdk/utils/rendering/steps/format.py +0 -176
- glaip_sdk/utils/rendering/steps/manager.py +0 -387
- glaip_sdk/utils/rendering/timing.py +0 -36
- glaip_sdk/utils/rendering/viewer/__init__.py +0 -21
- glaip_sdk/utils/rendering/viewer/presenter.py +0 -184
- glaip_sdk/utils/resource_refs.py +0 -195
- glaip_sdk/utils/run_renderer.py +0 -41
- glaip_sdk/utils/runtime_config.py +0 -425
- glaip_sdk/utils/serialization.py +0 -424
- glaip_sdk/utils/sync.py +0 -142
- glaip_sdk/utils/tool_detection.py +0 -33
- glaip_sdk/utils/validation.py +0 -264
- glaip_sdk-0.6.12.dist-info/RECORD +0 -159
- glaip_sdk-0.6.12.dist-info/entry_points.txt +0 -3
glaip_sdk/client/agents.py
DELETED
|
@@ -1,1335 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Agent client for AIP SDK.
|
|
3
|
-
|
|
4
|
-
Authors:
|
|
5
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
6
|
-
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import asyncio
|
|
10
|
-
import json
|
|
11
|
-
import logging
|
|
12
|
-
from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
|
|
13
|
-
from contextlib import asynccontextmanager
|
|
14
|
-
from os import PathLike
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any, BinaryIO
|
|
17
|
-
|
|
18
|
-
import httpx
|
|
19
|
-
from glaip_sdk.agents import Agent
|
|
20
|
-
from glaip_sdk.client._agent_payloads import (
|
|
21
|
-
AgentCreateRequest,
|
|
22
|
-
AgentListParams,
|
|
23
|
-
AgentListResult,
|
|
24
|
-
AgentUpdateRequest,
|
|
25
|
-
)
|
|
26
|
-
from glaip_sdk.client.agent_runs import AgentRunsClient
|
|
27
|
-
from glaip_sdk.client.base import BaseClient
|
|
28
|
-
from glaip_sdk.client.mcps import MCPClient
|
|
29
|
-
from glaip_sdk.client.run_rendering import (
|
|
30
|
-
AgentRunRenderingManager,
|
|
31
|
-
compute_timeout_seconds,
|
|
32
|
-
)
|
|
33
|
-
from glaip_sdk.client.shared import build_shared_config
|
|
34
|
-
from glaip_sdk.client.tools import ToolClient
|
|
35
|
-
from glaip_sdk.config.constants import (
|
|
36
|
-
AGENT_CONFIG_FIELDS,
|
|
37
|
-
DEFAULT_AGENT_FRAMEWORK,
|
|
38
|
-
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
39
|
-
DEFAULT_AGENT_TYPE,
|
|
40
|
-
DEFAULT_AGENT_VERSION,
|
|
41
|
-
DEFAULT_MODEL,
|
|
42
|
-
)
|
|
43
|
-
from glaip_sdk.exceptions import NotFoundError, ValidationError
|
|
44
|
-
from glaip_sdk.models import AgentResponse
|
|
45
|
-
from glaip_sdk.payload_schemas.agent import list_server_only_fields
|
|
46
|
-
from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
|
|
47
|
-
from glaip_sdk.utils.client_utils import (
|
|
48
|
-
aiter_sse_events,
|
|
49
|
-
create_model_instances,
|
|
50
|
-
find_by_name,
|
|
51
|
-
prepare_multipart_data,
|
|
52
|
-
)
|
|
53
|
-
from glaip_sdk.utils.import_export import (
|
|
54
|
-
convert_export_to_import_format,
|
|
55
|
-
merge_import_with_cli_args,
|
|
56
|
-
)
|
|
57
|
-
from glaip_sdk.utils.rendering.renderer import RichStreamRenderer
|
|
58
|
-
from glaip_sdk.utils.resource_refs import is_uuid
|
|
59
|
-
from glaip_sdk.utils.serialization import load_resource_from_file
|
|
60
|
-
from glaip_sdk.utils.validation import validate_agent_instruction
|
|
61
|
-
|
|
62
|
-
# API endpoints
|
|
63
|
-
AGENTS_ENDPOINT = "/agents/"
|
|
64
|
-
|
|
65
|
-
# SSE content type
|
|
66
|
-
SSE_CONTENT_TYPE = "text/event-stream"
|
|
67
|
-
|
|
68
|
-
# Set up module-level logger
|
|
69
|
-
logger = logging.getLogger("glaip_sdk.agents")
|
|
70
|
-
|
|
71
|
-
_SERVER_ONLY_IMPORT_FIELDS = set(list_server_only_fields()) | {"success", "message"}
|
|
72
|
-
_MERGED_SEQUENCE_FIELDS = ("tools", "agents", "mcps")
|
|
73
|
-
_DEFAULT_METADATA_TYPE = "custom"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@asynccontextmanager
|
|
77
|
-
async def _async_timeout_guard(
|
|
78
|
-
timeout_seconds: float | None,
|
|
79
|
-
) -> AsyncGenerator[None, None]:
|
|
80
|
-
"""Apply an asyncio timeout when a custom timeout is provided."""
|
|
81
|
-
if timeout_seconds is None:
|
|
82
|
-
yield
|
|
83
|
-
return
|
|
84
|
-
try:
|
|
85
|
-
async with asyncio.timeout(timeout_seconds):
|
|
86
|
-
yield
|
|
87
|
-
except asyncio.TimeoutError as exc:
|
|
88
|
-
raise httpx.TimeoutException(f"Request timed out after {timeout_seconds}s") from exc
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _normalise_sequence(value: Any) -> list[Any] | None:
|
|
92
|
-
"""Normalise optional sequence inputs to plain lists."""
|
|
93
|
-
if value is None:
|
|
94
|
-
return None
|
|
95
|
-
if isinstance(value, list):
|
|
96
|
-
return value
|
|
97
|
-
if isinstance(value, (tuple, set)):
|
|
98
|
-
return list(value)
|
|
99
|
-
return [value]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _normalise_sequence_fields(mapping: dict[str, Any]) -> None:
|
|
103
|
-
"""Normalise merged sequence fields in-place."""
|
|
104
|
-
for field in _MERGED_SEQUENCE_FIELDS:
|
|
105
|
-
if field in mapping:
|
|
106
|
-
normalised = _normalise_sequence(mapping[field])
|
|
107
|
-
if normalised is not None:
|
|
108
|
-
mapping[field] = normalised
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _merge_override_maps(
|
|
112
|
-
base_values: Mapping[str, Any],
|
|
113
|
-
extra_values: Mapping[str, Any],
|
|
114
|
-
) -> dict[str, Any]:
|
|
115
|
-
"""Merge override mappings while normalising sequence fields."""
|
|
116
|
-
merged: dict[str, Any] = {}
|
|
117
|
-
for source in (base_values, extra_values):
|
|
118
|
-
for key, value in source.items():
|
|
119
|
-
if value is None:
|
|
120
|
-
continue
|
|
121
|
-
merged[key] = _normalise_sequence(value) if key in _MERGED_SEQUENCE_FIELDS else value
|
|
122
|
-
return merged
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _split_known_and_extra(
|
|
126
|
-
payload: Mapping[str, Any],
|
|
127
|
-
known_fields: Mapping[str, Any],
|
|
128
|
-
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
129
|
-
"""Split payload mapping into known request fields and extras."""
|
|
130
|
-
known: dict[str, Any] = {}
|
|
131
|
-
extras: dict[str, Any] = {}
|
|
132
|
-
for key, value in payload.items():
|
|
133
|
-
if value is None:
|
|
134
|
-
continue
|
|
135
|
-
if key in known_fields:
|
|
136
|
-
known[key] = value
|
|
137
|
-
else:
|
|
138
|
-
extras[key] = value
|
|
139
|
-
return known, extras
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _prepare_agent_metadata(value: Any) -> dict[str, Any]:
|
|
143
|
-
"""Ensure agent metadata contains ``type: custom`` by default."""
|
|
144
|
-
if value is None:
|
|
145
|
-
return {"type": _DEFAULT_METADATA_TYPE}
|
|
146
|
-
if not isinstance(value, Mapping):
|
|
147
|
-
return {"type": _DEFAULT_METADATA_TYPE}
|
|
148
|
-
|
|
149
|
-
prepared = dict(value)
|
|
150
|
-
metadata_type = prepared.get("type")
|
|
151
|
-
if not metadata_type:
|
|
152
|
-
prepared["type"] = _DEFAULT_METADATA_TYPE
|
|
153
|
-
return prepared
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _load_agent_file_payload(file_path: Path, *, model_override: str | None) -> dict[str, Any]:
|
|
157
|
-
"""Load agent configuration from disk and normalise legacy fields."""
|
|
158
|
-
if not file_path.exists():
|
|
159
|
-
raise FileNotFoundError(f"Agent configuration file not found: {file_path}")
|
|
160
|
-
if not file_path.is_file():
|
|
161
|
-
raise ValueError(f"Agent configuration path must point to a file: {file_path}")
|
|
162
|
-
|
|
163
|
-
raw_data = load_resource_from_file(file_path)
|
|
164
|
-
if not isinstance(raw_data, Mapping):
|
|
165
|
-
raise ValueError("Agent configuration file must contain a mapping/object.")
|
|
166
|
-
|
|
167
|
-
payload = convert_export_to_import_format(dict(raw_data))
|
|
168
|
-
payload = normalize_agent_config_for_import(payload, model_override)
|
|
169
|
-
|
|
170
|
-
for field in _SERVER_ONLY_IMPORT_FIELDS:
|
|
171
|
-
payload.pop(field, None)
|
|
172
|
-
|
|
173
|
-
return payload
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _prepare_import_payload(
|
|
177
|
-
file_path: Path,
|
|
178
|
-
overrides: Mapping[str, Any],
|
|
179
|
-
*,
|
|
180
|
-
drop_model_fields: bool = False,
|
|
181
|
-
) -> dict[str, Any]:
|
|
182
|
-
"""Prepare merged payload from file contents and explicit overrides."""
|
|
183
|
-
overrides_dict = dict(overrides)
|
|
184
|
-
|
|
185
|
-
raw_definition = load_resource_from_file(file_path)
|
|
186
|
-
original_refs = _extract_original_refs(raw_definition)
|
|
187
|
-
|
|
188
|
-
base_payload = _load_agent_file_payload(file_path, model_override=overrides_dict.get("model"))
|
|
189
|
-
|
|
190
|
-
cli_args = _build_cli_args(overrides_dict)
|
|
191
|
-
|
|
192
|
-
merged = merge_import_with_cli_args(base_payload, cli_args)
|
|
193
|
-
|
|
194
|
-
additional = _build_additional_args(overrides_dict, cli_args)
|
|
195
|
-
merged.update(additional)
|
|
196
|
-
|
|
197
|
-
if drop_model_fields:
|
|
198
|
-
_remove_model_fields_if_needed(merged, overrides_dict)
|
|
199
|
-
|
|
200
|
-
_set_default_refs(merged, original_refs)
|
|
201
|
-
|
|
202
|
-
_normalise_sequence_fields(merged)
|
|
203
|
-
return merged
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _extract_original_refs(raw_definition: dict) -> dict[str, list]:
|
|
207
|
-
"""Extract original tool/agent/mcp references from raw definition."""
|
|
208
|
-
return {
|
|
209
|
-
"tools": list(raw_definition.get("tools") or []),
|
|
210
|
-
"agents": list(raw_definition.get("agents") or []),
|
|
211
|
-
"mcps": list(raw_definition.get("mcps") or []),
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _build_cli_args(overrides_dict: dict) -> dict[str, Any]:
|
|
216
|
-
"""Build CLI args from overrides, filtering out None values."""
|
|
217
|
-
cli_args = {key: overrides_dict.get(key) for key in AGENT_CONFIG_FIELDS if overrides_dict.get(key) is not None}
|
|
218
|
-
|
|
219
|
-
# Normalize sequence fields
|
|
220
|
-
for field in _MERGED_SEQUENCE_FIELDS:
|
|
221
|
-
if field in cli_args:
|
|
222
|
-
cli_args[field] = tuple(_normalise_sequence(cli_args[field]) or [])
|
|
223
|
-
|
|
224
|
-
return cli_args
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def _build_additional_args(overrides_dict: dict, cli_args: dict) -> dict[str, Any]:
|
|
228
|
-
"""Build additional args not already in CLI args."""
|
|
229
|
-
return {key: value for key, value in overrides_dict.items() if value is not None and key not in cli_args}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _remove_model_fields_if_needed(merged: dict, overrides_dict: dict) -> None:
|
|
233
|
-
"""Remove model fields if not explicitly overridden."""
|
|
234
|
-
if overrides_dict.get("language_model_id") is None:
|
|
235
|
-
merged.pop("language_model_id", None)
|
|
236
|
-
if overrides_dict.get("provider") is None:
|
|
237
|
-
merged.pop("provider", None)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _set_default_refs(merged: dict, original_refs: dict) -> None:
|
|
241
|
-
"""Set default references if not already present."""
|
|
242
|
-
merged.setdefault("_tool_refs", original_refs["tools"])
|
|
243
|
-
merged.setdefault("_agent_refs", original_refs["agents"])
|
|
244
|
-
merged.setdefault("_mcp_refs", original_refs["mcps"])
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
class AgentClient(BaseClient):
|
|
248
|
-
"""Client for agent operations."""
|
|
249
|
-
|
|
250
|
-
def __init__(
|
|
251
|
-
self,
|
|
252
|
-
*,
|
|
253
|
-
parent_client: BaseClient | None = None,
|
|
254
|
-
**kwargs: Any,
|
|
255
|
-
) -> None:
|
|
256
|
-
"""Initialize the agent client.
|
|
257
|
-
|
|
258
|
-
Args:
|
|
259
|
-
parent_client: Parent client to adopt session/config from
|
|
260
|
-
**kwargs: Additional arguments for standalone initialization
|
|
261
|
-
"""
|
|
262
|
-
super().__init__(parent_client=parent_client, **kwargs)
|
|
263
|
-
self._renderer_manager = AgentRunRenderingManager(logger)
|
|
264
|
-
self._tool_client: ToolClient | None = None
|
|
265
|
-
self._mcp_client: MCPClient | None = None
|
|
266
|
-
self._runs_client: AgentRunsClient | None = None
|
|
267
|
-
|
|
268
|
-
def list_agents(
|
|
269
|
-
self,
|
|
270
|
-
query: AgentListParams | None = None,
|
|
271
|
-
**kwargs: Any,
|
|
272
|
-
) -> AgentListResult:
|
|
273
|
-
"""List agents with optional filtering and pagination support.
|
|
274
|
-
|
|
275
|
-
Args:
|
|
276
|
-
query: Query parameters for filtering agents. If None, uses kwargs to create query.
|
|
277
|
-
**kwargs: Individual filter parameters for backward compatibility.
|
|
278
|
-
"""
|
|
279
|
-
if query is not None and kwargs:
|
|
280
|
-
# Both query object and individual parameters provided
|
|
281
|
-
raise ValueError("Provide either `query` or individual filter arguments, not both.")
|
|
282
|
-
|
|
283
|
-
if query is None:
|
|
284
|
-
# Create query from individual parameters for backward compatibility
|
|
285
|
-
query = AgentListParams(**kwargs)
|
|
286
|
-
|
|
287
|
-
params = query.to_query_params()
|
|
288
|
-
envelope = self._request_with_envelope(
|
|
289
|
-
"GET",
|
|
290
|
-
AGENTS_ENDPOINT,
|
|
291
|
-
params=params if params else None,
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
if not isinstance(envelope, dict):
|
|
295
|
-
envelope = {"data": envelope}
|
|
296
|
-
|
|
297
|
-
data_payload = envelope.get("data") or []
|
|
298
|
-
items = create_model_instances(data_payload, Agent, self)
|
|
299
|
-
|
|
300
|
-
return AgentListResult(
|
|
301
|
-
items=items,
|
|
302
|
-
total=envelope.get("total"),
|
|
303
|
-
page=envelope.get("page"),
|
|
304
|
-
limit=envelope.get("limit"),
|
|
305
|
-
has_next=envelope.get("has_next"),
|
|
306
|
-
has_prev=envelope.get("has_prev"),
|
|
307
|
-
message=envelope.get("message"),
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
def sync_langflow_agents(
|
|
311
|
-
self,
|
|
312
|
-
base_url: str | None = None,
|
|
313
|
-
api_key: str | None = None,
|
|
314
|
-
) -> dict[str, Any]:
|
|
315
|
-
"""Sync LangFlow agents by fetching flows from the LangFlow server.
|
|
316
|
-
|
|
317
|
-
This method synchronizes agents with LangFlow flows. It fetches all flows
|
|
318
|
-
from the configured LangFlow server and creates/updates corresponding agents.
|
|
319
|
-
|
|
320
|
-
Args:
|
|
321
|
-
base_url: Custom LangFlow server base URL. If not provided, uses LANGFLOW_BASE_URL env var.
|
|
322
|
-
api_key: Custom LangFlow API key. If not provided, uses LANGFLOW_API_KEY env var.
|
|
323
|
-
|
|
324
|
-
Returns:
|
|
325
|
-
Response containing sync results and statistics
|
|
326
|
-
|
|
327
|
-
Raises:
|
|
328
|
-
ValueError: If LangFlow server configuration is missing
|
|
329
|
-
"""
|
|
330
|
-
payload = {}
|
|
331
|
-
if base_url is not None:
|
|
332
|
-
payload["base_url"] = base_url
|
|
333
|
-
if api_key is not None:
|
|
334
|
-
payload["api_key"] = api_key
|
|
335
|
-
|
|
336
|
-
return self._request("POST", "/agents/langflow/sync", json=payload)
|
|
337
|
-
|
|
338
|
-
def get_agent_by_id(self, agent_id: str) -> Agent:
|
|
339
|
-
"""Get agent by ID."""
|
|
340
|
-
try:
|
|
341
|
-
data = self._request("GET", f"/agents/{agent_id}")
|
|
342
|
-
except ValidationError as exc:
|
|
343
|
-
if exc.status_code == 422:
|
|
344
|
-
message = f"Agent '{agent_id}' not found"
|
|
345
|
-
raise NotFoundError(
|
|
346
|
-
message,
|
|
347
|
-
status_code=404,
|
|
348
|
-
error_type=exc.error_type,
|
|
349
|
-
payload=exc.payload,
|
|
350
|
-
request_id=exc.request_id,
|
|
351
|
-
) from exc
|
|
352
|
-
raise
|
|
353
|
-
|
|
354
|
-
if isinstance(data, str):
|
|
355
|
-
# Some backends may respond with plain text for missing agents.
|
|
356
|
-
message = data.strip() or f"Agent '{agent_id}' not found"
|
|
357
|
-
raise NotFoundError(message, status_code=404)
|
|
358
|
-
|
|
359
|
-
if not isinstance(data, dict):
|
|
360
|
-
raise NotFoundError(
|
|
361
|
-
f"Agent '{agent_id}' not found (unexpected response type)",
|
|
362
|
-
status_code=404,
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
response = AgentResponse(**data)
|
|
366
|
-
return Agent.from_response(response, client=self)
|
|
367
|
-
|
|
368
|
-
def find_agents(self, name: str | None = None) -> list[Agent]:
|
|
369
|
-
"""Find agents by name."""
|
|
370
|
-
result = self.list_agents(name=name)
|
|
371
|
-
agents = list(result)
|
|
372
|
-
if name is None:
|
|
373
|
-
return agents
|
|
374
|
-
return find_by_name(agents, name, case_sensitive=False)
|
|
375
|
-
|
|
376
|
-
# ------------------------------------------------------------------ #
|
|
377
|
-
# Renderer delegation helpers
|
|
378
|
-
# ------------------------------------------------------------------ #
|
|
379
|
-
def _get_renderer_manager(self) -> AgentRunRenderingManager:
|
|
380
|
-
"""Get or create the renderer manager instance.
|
|
381
|
-
|
|
382
|
-
Returns:
|
|
383
|
-
AgentRunRenderingManager instance.
|
|
384
|
-
"""
|
|
385
|
-
manager = getattr(self, "_renderer_manager", None)
|
|
386
|
-
if manager is None:
|
|
387
|
-
manager = AgentRunRenderingManager(logger)
|
|
388
|
-
self._renderer_manager = manager
|
|
389
|
-
return manager
|
|
390
|
-
|
|
391
|
-
def _create_renderer(self, renderer: RichStreamRenderer | str | None, **kwargs: Any) -> RichStreamRenderer:
|
|
392
|
-
"""Create or return a renderer instance.
|
|
393
|
-
|
|
394
|
-
Args:
|
|
395
|
-
renderer: Renderer instance, string identifier, or None.
|
|
396
|
-
**kwargs: Additional keyword arguments (e.g., verbose).
|
|
397
|
-
|
|
398
|
-
Returns:
|
|
399
|
-
RichStreamRenderer instance.
|
|
400
|
-
"""
|
|
401
|
-
manager = self._get_renderer_manager()
|
|
402
|
-
verbose = kwargs.get("verbose", False)
|
|
403
|
-
if isinstance(renderer, RichStreamRenderer) or hasattr(renderer, "on_start"):
|
|
404
|
-
return renderer # type: ignore[return-value]
|
|
405
|
-
return manager.create_renderer(renderer, verbose=verbose)
|
|
406
|
-
|
|
407
|
-
def _process_stream_events(
|
|
408
|
-
self,
|
|
409
|
-
stream_response: httpx.Response,
|
|
410
|
-
renderer: RichStreamRenderer,
|
|
411
|
-
timeout_seconds: float,
|
|
412
|
-
agent_name: str | None,
|
|
413
|
-
meta: dict[str, Any],
|
|
414
|
-
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
415
|
-
"""Process stream events from an HTTP response.
|
|
416
|
-
|
|
417
|
-
Args:
|
|
418
|
-
stream_response: HTTP response stream.
|
|
419
|
-
renderer: Renderer to use for displaying events.
|
|
420
|
-
timeout_seconds: Timeout in seconds.
|
|
421
|
-
agent_name: Optional agent name.
|
|
422
|
-
meta: Metadata dictionary.
|
|
423
|
-
|
|
424
|
-
Returns:
|
|
425
|
-
Tuple of (final_text, stats_usage, started_monotonic, finished_monotonic).
|
|
426
|
-
"""
|
|
427
|
-
manager = self._get_renderer_manager()
|
|
428
|
-
return manager.process_stream_events(
|
|
429
|
-
stream_response,
|
|
430
|
-
renderer,
|
|
431
|
-
timeout_seconds,
|
|
432
|
-
agent_name,
|
|
433
|
-
meta,
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
def _finalize_renderer(
|
|
437
|
-
self,
|
|
438
|
-
renderer: RichStreamRenderer,
|
|
439
|
-
final_text: str,
|
|
440
|
-
stats_usage: dict[str, Any],
|
|
441
|
-
started_monotonic: float | None,
|
|
442
|
-
finished_monotonic: float | None,
|
|
443
|
-
) -> str:
|
|
444
|
-
"""Finalize the renderer and return the final response text.
|
|
445
|
-
|
|
446
|
-
Args:
|
|
447
|
-
renderer: Renderer to finalize.
|
|
448
|
-
final_text: Final text content.
|
|
449
|
-
stats_usage: Usage statistics dictionary.
|
|
450
|
-
started_monotonic: Start time (monotonic).
|
|
451
|
-
finished_monotonic: Finish time (monotonic).
|
|
452
|
-
|
|
453
|
-
Returns:
|
|
454
|
-
Final text string.
|
|
455
|
-
"""
|
|
456
|
-
from glaip_sdk.client.run_rendering import finalize_render_manager # noqa: PLC0415
|
|
457
|
-
|
|
458
|
-
manager = self._get_renderer_manager()
|
|
459
|
-
return finalize_render_manager(
|
|
460
|
-
manager, renderer, final_text, stats_usage, started_monotonic, finished_monotonic
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
def _get_tool_client(self) -> ToolClient:
|
|
464
|
-
"""Get or create the tool client instance.
|
|
465
|
-
|
|
466
|
-
Returns:
|
|
467
|
-
ToolClient instance.
|
|
468
|
-
"""
|
|
469
|
-
if self._tool_client is None:
|
|
470
|
-
self._tool_client = ToolClient(parent_client=self)
|
|
471
|
-
return self._tool_client
|
|
472
|
-
|
|
473
|
-
def _get_mcp_client(self) -> MCPClient:
|
|
474
|
-
"""Get or create the MCP client instance.
|
|
475
|
-
|
|
476
|
-
Returns:
|
|
477
|
-
MCPClient instance.
|
|
478
|
-
"""
|
|
479
|
-
if self._mcp_client is None:
|
|
480
|
-
self._mcp_client = MCPClient(parent_client=self)
|
|
481
|
-
return self._mcp_client
|
|
482
|
-
|
|
483
|
-
def _normalise_reference_entry(
|
|
484
|
-
self,
|
|
485
|
-
entry: Any,
|
|
486
|
-
fallback_iter: Iterator[Any] | None,
|
|
487
|
-
) -> tuple[str | None, str | None]:
|
|
488
|
-
"""Normalize a reference entry to extract ID and name.
|
|
489
|
-
|
|
490
|
-
Args:
|
|
491
|
-
entry: Reference entry (string, dict, or other).
|
|
492
|
-
fallback_iter: Optional iterator for fallback values.
|
|
493
|
-
|
|
494
|
-
Returns:
|
|
495
|
-
Tuple of (entry_id, entry_name).
|
|
496
|
-
"""
|
|
497
|
-
entry_id: str | None = None
|
|
498
|
-
entry_name: str | None = None
|
|
499
|
-
|
|
500
|
-
if isinstance(entry, str):
|
|
501
|
-
if is_uuid(entry):
|
|
502
|
-
entry_id = entry
|
|
503
|
-
else:
|
|
504
|
-
entry_name = entry
|
|
505
|
-
elif isinstance(entry, dict):
|
|
506
|
-
entry_id = entry.get("id")
|
|
507
|
-
entry_name = entry.get("name")
|
|
508
|
-
else:
|
|
509
|
-
entry_name = str(entry)
|
|
510
|
-
|
|
511
|
-
if entry_name or fallback_iter is None:
|
|
512
|
-
return entry_id, entry_name
|
|
513
|
-
|
|
514
|
-
try:
|
|
515
|
-
ref = next(fallback_iter)
|
|
516
|
-
except StopIteration:
|
|
517
|
-
ref = None
|
|
518
|
-
if isinstance(ref, dict):
|
|
519
|
-
entry_name = ref.get("name") or entry_name
|
|
520
|
-
|
|
521
|
-
return entry_id, entry_name
|
|
522
|
-
|
|
523
|
-
def _resolve_resource_ids(
|
|
524
|
-
self,
|
|
525
|
-
items: list[Any] | None,
|
|
526
|
-
references: list[Any] | None,
|
|
527
|
-
*,
|
|
528
|
-
fetch_by_id: Callable[[str], Any],
|
|
529
|
-
find_by_name: Callable[[str], list[Any]],
|
|
530
|
-
label: str,
|
|
531
|
-
plural_label: str | None = None,
|
|
532
|
-
) -> list[str] | None:
|
|
533
|
-
"""Resolve a list of resource references to IDs.
|
|
534
|
-
|
|
535
|
-
Args:
|
|
536
|
-
items: List of resource references to resolve.
|
|
537
|
-
references: Optional list of reference objects for fallback.
|
|
538
|
-
fetch_by_id: Function to fetch resource by ID.
|
|
539
|
-
find_by_name: Function to find resources by name.
|
|
540
|
-
label: Singular label for error messages.
|
|
541
|
-
plural_label: Plural label for error messages.
|
|
542
|
-
|
|
543
|
-
Returns:
|
|
544
|
-
List of resolved resource IDs, or None if items is empty.
|
|
545
|
-
"""
|
|
546
|
-
if not items:
|
|
547
|
-
return None
|
|
548
|
-
|
|
549
|
-
if references is None:
|
|
550
|
-
return [self._coerce_reference_value(entry) for entry in items]
|
|
551
|
-
|
|
552
|
-
singular = label
|
|
553
|
-
plural = plural_label or f"{label}s"
|
|
554
|
-
fallback_iter = iter(references or [])
|
|
555
|
-
|
|
556
|
-
return [
|
|
557
|
-
self._resolve_single_resource(
|
|
558
|
-
entry,
|
|
559
|
-
fallback_iter,
|
|
560
|
-
fetch_by_id,
|
|
561
|
-
find_by_name,
|
|
562
|
-
singular,
|
|
563
|
-
plural,
|
|
564
|
-
)
|
|
565
|
-
for entry in items
|
|
566
|
-
]
|
|
567
|
-
|
|
568
|
-
def _resolve_single_resource(
|
|
569
|
-
self,
|
|
570
|
-
entry: Any,
|
|
571
|
-
fallback_iter: Iterator[Any] | None,
|
|
572
|
-
fetch_by_id: Callable[[str], Any],
|
|
573
|
-
find_by_name: Callable[[str], list[Any]],
|
|
574
|
-
singular: str,
|
|
575
|
-
plural: str,
|
|
576
|
-
) -> str:
|
|
577
|
-
"""Resolve a single resource reference to an ID.
|
|
578
|
-
|
|
579
|
-
Args:
|
|
580
|
-
entry: Resource reference to resolve.
|
|
581
|
-
fallback_iter: Optional iterator for fallback values.
|
|
582
|
-
fetch_by_id: Function to fetch resource by ID.
|
|
583
|
-
find_by_name: Function to find resources by name.
|
|
584
|
-
singular: Singular label for error messages.
|
|
585
|
-
plural: Plural label for error messages.
|
|
586
|
-
|
|
587
|
-
Returns:
|
|
588
|
-
Resolved resource ID string.
|
|
589
|
-
|
|
590
|
-
Raises:
|
|
591
|
-
ValueError: If the resource cannot be resolved.
|
|
592
|
-
"""
|
|
593
|
-
entry_id, entry_name = self._normalise_reference_entry(entry, fallback_iter)
|
|
594
|
-
|
|
595
|
-
validated_id = self._validate_resource_id(fetch_by_id, entry_id)
|
|
596
|
-
if validated_id:
|
|
597
|
-
return validated_id
|
|
598
|
-
if entry_id and entry_name is None:
|
|
599
|
-
return entry_id
|
|
600
|
-
|
|
601
|
-
if entry_name:
|
|
602
|
-
resolved, success = self._resolve_resource_by_name(find_by_name, entry_name, singular, plural)
|
|
603
|
-
return resolved if success else entry_name
|
|
604
|
-
|
|
605
|
-
raise ValueError(f"{singular} references must include a valid ID or name.")
|
|
606
|
-
|
|
607
|
-
@staticmethod
|
|
608
|
-
def _coerce_reference_value(entry: Any) -> str:
|
|
609
|
-
"""Coerce a reference entry to a string value.
|
|
610
|
-
|
|
611
|
-
Args:
|
|
612
|
-
entry: Reference entry (dict, string, or other).
|
|
613
|
-
|
|
614
|
-
Returns:
|
|
615
|
-
String representation of the reference.
|
|
616
|
-
"""
|
|
617
|
-
if isinstance(entry, dict):
|
|
618
|
-
if entry.get("id"):
|
|
619
|
-
return str(entry["id"])
|
|
620
|
-
if entry.get("name"):
|
|
621
|
-
return str(entry["name"])
|
|
622
|
-
return str(entry)
|
|
623
|
-
|
|
624
|
-
@staticmethod
|
|
625
|
-
def _validate_resource_id(fetch_by_id: Callable[[str], Any], candidate_id: str | None) -> str | None:
|
|
626
|
-
"""Validate a resource ID by attempting to fetch it.
|
|
627
|
-
|
|
628
|
-
Args:
|
|
629
|
-
fetch_by_id: Function to fetch resource by ID.
|
|
630
|
-
candidate_id: Candidate ID to validate.
|
|
631
|
-
|
|
632
|
-
Returns:
|
|
633
|
-
Validated ID if found, None otherwise.
|
|
634
|
-
"""
|
|
635
|
-
if not candidate_id:
|
|
636
|
-
return None
|
|
637
|
-
try:
|
|
638
|
-
fetch_by_id(candidate_id)
|
|
639
|
-
except Exception:
|
|
640
|
-
return None
|
|
641
|
-
return candidate_id
|
|
642
|
-
|
|
643
|
-
@staticmethod
|
|
644
|
-
def _resolve_resource_by_name(
|
|
645
|
-
find_by_name: Callable[[str], list[Any]],
|
|
646
|
-
entry_name: str,
|
|
647
|
-
singular: str,
|
|
648
|
-
plural: str,
|
|
649
|
-
) -> tuple[str, bool]:
|
|
650
|
-
"""Resolve a resource by name to an ID.
|
|
651
|
-
|
|
652
|
-
Args:
|
|
653
|
-
find_by_name: Function to find resources by name.
|
|
654
|
-
entry_name: Name of the resource to find.
|
|
655
|
-
singular: Singular label for error messages.
|
|
656
|
-
plural: Plural label for error messages.
|
|
657
|
-
|
|
658
|
-
Returns:
|
|
659
|
-
Tuple of (resolved_id, success).
|
|
660
|
-
|
|
661
|
-
Raises:
|
|
662
|
-
ValueError: If resource not found or multiple matches exist.
|
|
663
|
-
"""
|
|
664
|
-
try:
|
|
665
|
-
matches = find_by_name(entry_name)
|
|
666
|
-
except Exception:
|
|
667
|
-
return entry_name, False
|
|
668
|
-
|
|
669
|
-
if not matches:
|
|
670
|
-
raise ValueError(f"{singular} '{entry_name}' not found in current workspace.")
|
|
671
|
-
if len(matches) > 1:
|
|
672
|
-
exact = [m for m in matches if getattr(m, "name", "").lower() == entry_name.lower()]
|
|
673
|
-
if len(exact) == 1:
|
|
674
|
-
matches = exact
|
|
675
|
-
else:
|
|
676
|
-
raise ValueError(f"Multiple {plural} named '{entry_name}'. Please disambiguate.")
|
|
677
|
-
return str(matches[0].id), True
|
|
678
|
-
|
|
679
|
-
def _resolve_tool_ids(
|
|
680
|
-
self,
|
|
681
|
-
tools: list[Any] | None,
|
|
682
|
-
references: list[Any] | None = None,
|
|
683
|
-
) -> list[str] | None:
|
|
684
|
-
"""Resolve tool references to IDs.
|
|
685
|
-
|
|
686
|
-
Args:
|
|
687
|
-
tools: List of tool references to resolve.
|
|
688
|
-
references: Optional list of reference objects for fallback.
|
|
689
|
-
|
|
690
|
-
Returns:
|
|
691
|
-
List of resolved tool IDs, or None if tools is empty.
|
|
692
|
-
"""
|
|
693
|
-
tool_client = self._get_tool_client()
|
|
694
|
-
return self._resolve_resource_ids(
|
|
695
|
-
tools,
|
|
696
|
-
references,
|
|
697
|
-
fetch_by_id=tool_client.get_tool_by_id,
|
|
698
|
-
find_by_name=tool_client.find_tools,
|
|
699
|
-
label="Tool",
|
|
700
|
-
plural_label="tools",
|
|
701
|
-
)
|
|
702
|
-
|
|
703
|
-
def _resolve_agent_ids(
|
|
704
|
-
self,
|
|
705
|
-
agents: list[Any] | None,
|
|
706
|
-
references: list[Any] | None = None,
|
|
707
|
-
) -> list[str] | None:
|
|
708
|
-
"""Resolve agent references to IDs.
|
|
709
|
-
|
|
710
|
-
Args:
|
|
711
|
-
agents: List of agent references to resolve.
|
|
712
|
-
references: Optional list of reference objects for fallback.
|
|
713
|
-
|
|
714
|
-
Returns:
|
|
715
|
-
List of resolved agent IDs, or None if agents is empty.
|
|
716
|
-
"""
|
|
717
|
-
return self._resolve_resource_ids(
|
|
718
|
-
agents,
|
|
719
|
-
references,
|
|
720
|
-
fetch_by_id=self.get_agent_by_id,
|
|
721
|
-
find_by_name=self.find_agents,
|
|
722
|
-
label="Agent",
|
|
723
|
-
plural_label="agents",
|
|
724
|
-
)
|
|
725
|
-
|
|
726
|
-
def _resolve_mcp_ids(
|
|
727
|
-
self,
|
|
728
|
-
mcps: list[Any] | None,
|
|
729
|
-
references: list[Any] | None = None,
|
|
730
|
-
) -> list[str] | None:
|
|
731
|
-
"""Resolve MCP references to IDs.
|
|
732
|
-
|
|
733
|
-
Args:
|
|
734
|
-
mcps: List of MCP references to resolve.
|
|
735
|
-
references: Optional list of reference objects for fallback.
|
|
736
|
-
|
|
737
|
-
Returns:
|
|
738
|
-
List of resolved MCP IDs, or None if mcps is empty.
|
|
739
|
-
"""
|
|
740
|
-
mcp_client = self._get_mcp_client()
|
|
741
|
-
return self._resolve_resource_ids(
|
|
742
|
-
mcps,
|
|
743
|
-
references,
|
|
744
|
-
fetch_by_id=mcp_client.get_mcp_by_id,
|
|
745
|
-
find_by_name=mcp_client.find_mcps,
|
|
746
|
-
label="MCP",
|
|
747
|
-
plural_label="MCPs",
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
|
|
751
|
-
"""Create an agent using a fully prepared payload mapping."""
|
|
752
|
-
known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
|
|
753
|
-
|
|
754
|
-
name = known.pop("name", None)
|
|
755
|
-
instruction = known.pop("instruction", None)
|
|
756
|
-
if not name or not str(name).strip():
|
|
757
|
-
raise ValueError("Agent name cannot be empty or whitespace")
|
|
758
|
-
if not instruction or not str(instruction).strip():
|
|
759
|
-
raise ValueError("Agent instruction cannot be empty or whitespace")
|
|
760
|
-
|
|
761
|
-
validated_instruction = validate_agent_instruction(str(instruction))
|
|
762
|
-
_normalise_sequence_fields(known)
|
|
763
|
-
|
|
764
|
-
resolved_model = known.pop("model", None) or DEFAULT_MODEL
|
|
765
|
-
tool_refs = extras.pop("_tool_refs", None)
|
|
766
|
-
agent_refs = extras.pop("_agent_refs", None)
|
|
767
|
-
mcp_refs = extras.pop("_mcp_refs", None)
|
|
768
|
-
|
|
769
|
-
tools_raw = known.pop("tools", None)
|
|
770
|
-
agents_raw = known.pop("agents", None)
|
|
771
|
-
mcps_raw = known.pop("mcps", None)
|
|
772
|
-
|
|
773
|
-
resolved_tools = self._resolve_tool_ids(tools_raw, tool_refs)
|
|
774
|
-
resolved_agents = self._resolve_agent_ids(agents_raw, agent_refs)
|
|
775
|
-
resolved_mcps = self._resolve_mcp_ids(mcps_raw, mcp_refs)
|
|
776
|
-
|
|
777
|
-
language_model_id = known.pop("language_model_id", None)
|
|
778
|
-
provider = known.pop("provider", None)
|
|
779
|
-
model_name = known.pop("model_name", None)
|
|
780
|
-
|
|
781
|
-
agent_type_value = known.pop("agent_type", None)
|
|
782
|
-
fallback_type_value = known.pop("type", None)
|
|
783
|
-
if agent_type_value is None:
|
|
784
|
-
agent_type_value = fallback_type_value or DEFAULT_AGENT_TYPE
|
|
785
|
-
|
|
786
|
-
framework_value = known.pop("framework", None) or DEFAULT_AGENT_FRAMEWORK
|
|
787
|
-
version_value = known.pop("version", None) or DEFAULT_AGENT_VERSION
|
|
788
|
-
account_id = known.pop("account_id", None)
|
|
789
|
-
description = known.pop("description", None)
|
|
790
|
-
metadata = _prepare_agent_metadata(known.pop("metadata", None))
|
|
791
|
-
tool_configs = known.pop("tool_configs", None)
|
|
792
|
-
agent_config = known.pop("agent_config", None)
|
|
793
|
-
timeout_value = known.pop("timeout", None)
|
|
794
|
-
a2a_profile = known.pop("a2a_profile", None)
|
|
795
|
-
|
|
796
|
-
final_extras = {**known, **extras}
|
|
797
|
-
final_extras.setdefault("model", resolved_model)
|
|
798
|
-
|
|
799
|
-
request = AgentCreateRequest(
|
|
800
|
-
name=str(name).strip(),
|
|
801
|
-
instruction=validated_instruction,
|
|
802
|
-
model=resolved_model,
|
|
803
|
-
language_model_id=language_model_id,
|
|
804
|
-
provider=provider,
|
|
805
|
-
model_name=model_name,
|
|
806
|
-
agent_type=agent_type_value,
|
|
807
|
-
framework=framework_value,
|
|
808
|
-
version=version_value,
|
|
809
|
-
account_id=account_id,
|
|
810
|
-
description=description,
|
|
811
|
-
metadata=metadata,
|
|
812
|
-
tools=resolved_tools,
|
|
813
|
-
agents=resolved_agents,
|
|
814
|
-
mcps=resolved_mcps,
|
|
815
|
-
tool_configs=tool_configs,
|
|
816
|
-
agent_config=agent_config,
|
|
817
|
-
timeout=timeout_value or DEFAULT_AGENT_RUN_TIMEOUT,
|
|
818
|
-
a2a_profile=a2a_profile,
|
|
819
|
-
extras=final_extras,
|
|
820
|
-
)
|
|
821
|
-
|
|
822
|
-
payload_dict = request.to_payload()
|
|
823
|
-
payload_dict.setdefault("model", resolved_model)
|
|
824
|
-
|
|
825
|
-
full_agent_data = self._post_then_fetch(
|
|
826
|
-
id_key="id",
|
|
827
|
-
post_endpoint=AGENTS_ENDPOINT,
|
|
828
|
-
get_endpoint_fmt=f"{AGENTS_ENDPOINT}{{id}}",
|
|
829
|
-
json=payload_dict,
|
|
830
|
-
)
|
|
831
|
-
response = AgentResponse(**full_agent_data)
|
|
832
|
-
return Agent.from_response(response, client=self)
|
|
833
|
-
|
|
834
|
-
def create_agent(
|
|
835
|
-
self,
|
|
836
|
-
name: str | None = None,
|
|
837
|
-
instruction: str | None = None,
|
|
838
|
-
model: str | None = None,
|
|
839
|
-
tools: list[str | Any] | None = None,
|
|
840
|
-
agents: list[str | Any] | None = None,
|
|
841
|
-
timeout: int | None = None,
|
|
842
|
-
*,
|
|
843
|
-
file: str | PathLike[str] | None = None,
|
|
844
|
-
mcps: list[str | Any] | None = None,
|
|
845
|
-
tool_configs: Mapping[str, Any] | None = None,
|
|
846
|
-
**kwargs: Any,
|
|
847
|
-
) -> "Agent":
|
|
848
|
-
"""Create a new agent, optionally loading configuration from a file."""
|
|
849
|
-
base_overrides = {
|
|
850
|
-
"name": name,
|
|
851
|
-
"instruction": instruction,
|
|
852
|
-
"model": model,
|
|
853
|
-
"tools": tools,
|
|
854
|
-
"agents": agents,
|
|
855
|
-
"timeout": timeout,
|
|
856
|
-
"mcps": mcps,
|
|
857
|
-
"tool_configs": tool_configs,
|
|
858
|
-
}
|
|
859
|
-
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
860
|
-
|
|
861
|
-
if file is not None:
|
|
862
|
-
payload = _prepare_import_payload(Path(file).expanduser(), overrides, drop_model_fields=True)
|
|
863
|
-
if overrides.get("model") is None:
|
|
864
|
-
payload.pop("model", None)
|
|
865
|
-
else:
|
|
866
|
-
payload = overrides
|
|
867
|
-
|
|
868
|
-
return self._create_agent_from_payload(payload)
|
|
869
|
-
|
|
870
|
-
def create_agent_from_file( # pragma: no cover - thin compatibility wrapper
|
|
871
|
-
self,
|
|
872
|
-
file_path: str | PathLike[str],
|
|
873
|
-
**overrides: Any,
|
|
874
|
-
) -> "Agent":
|
|
875
|
-
"""Backward-compatible helper to create an agent from a configuration file."""
|
|
876
|
-
return self.create_agent(file=file_path, **overrides)
|
|
877
|
-
|
|
878
|
-
def _update_agent_from_payload(
|
|
879
|
-
self,
|
|
880
|
-
agent_id: str,
|
|
881
|
-
current_agent: Agent,
|
|
882
|
-
payload: Mapping[str, Any],
|
|
883
|
-
) -> "Agent":
|
|
884
|
-
"""Update an agent using a prepared payload mapping."""
|
|
885
|
-
known, extras = _split_known_and_extra(payload, AgentUpdateRequest.__dataclass_fields__)
|
|
886
|
-
_normalise_sequence_fields(known)
|
|
887
|
-
|
|
888
|
-
tool_refs = extras.pop("_tool_refs", None)
|
|
889
|
-
agent_refs = extras.pop("_agent_refs", None)
|
|
890
|
-
mcp_refs = extras.pop("_mcp_refs", None)
|
|
891
|
-
|
|
892
|
-
tools_value = known.pop("tools", None)
|
|
893
|
-
agents_value = known.pop("agents", None)
|
|
894
|
-
mcps_value = known.pop("mcps", None)
|
|
895
|
-
|
|
896
|
-
if tools_value is not None:
|
|
897
|
-
tools_value = self._resolve_tool_ids(tools_value, tool_refs)
|
|
898
|
-
if agents_value is not None:
|
|
899
|
-
agents_value = self._resolve_agent_ids(agents_value, agent_refs)
|
|
900
|
-
if mcps_value is not None:
|
|
901
|
-
mcps_value = self._resolve_mcp_ids(mcps_value, mcp_refs) # pragma: no cover
|
|
902
|
-
|
|
903
|
-
request = AgentUpdateRequest(
|
|
904
|
-
name=known.pop("name", None),
|
|
905
|
-
instruction=known.pop("instruction", None),
|
|
906
|
-
description=known.pop("description", None),
|
|
907
|
-
model=known.pop("model", None),
|
|
908
|
-
language_model_id=known.pop("language_model_id", None),
|
|
909
|
-
provider=known.pop("provider", None),
|
|
910
|
-
model_name=known.pop("model_name", None),
|
|
911
|
-
agent_type=known.pop("agent_type", known.pop("type", None)),
|
|
912
|
-
framework=known.pop("framework", None),
|
|
913
|
-
version=known.pop("version", None),
|
|
914
|
-
account_id=known.pop("account_id", None),
|
|
915
|
-
metadata=known.pop("metadata", None),
|
|
916
|
-
tools=tools_value,
|
|
917
|
-
tool_configs=known.pop("tool_configs", None),
|
|
918
|
-
agents=agents_value,
|
|
919
|
-
mcps=mcps_value,
|
|
920
|
-
agent_config=known.pop("agent_config", None),
|
|
921
|
-
a2a_profile=known.pop("a2a_profile", None),
|
|
922
|
-
extras={**known, **extras},
|
|
923
|
-
)
|
|
924
|
-
|
|
925
|
-
payload_dict = request.to_payload(current_agent)
|
|
926
|
-
|
|
927
|
-
api_response = self._request("PUT", f"/agents/{agent_id}", json=payload_dict)
|
|
928
|
-
response = AgentResponse(**api_response)
|
|
929
|
-
return Agent.from_response(response, client=self)
|
|
930
|
-
|
|
931
|
-
def update_agent(
|
|
932
|
-
self,
|
|
933
|
-
agent_id: str,
|
|
934
|
-
name: str | None = None,
|
|
935
|
-
instruction: str | None = None,
|
|
936
|
-
model: str | None = None,
|
|
937
|
-
*,
|
|
938
|
-
file: str | PathLike[str] | None = None,
|
|
939
|
-
tools: list[str | Any] | None = None,
|
|
940
|
-
agents: list[str | Any] | None = None,
|
|
941
|
-
mcps: list[str | Any] | None = None,
|
|
942
|
-
**kwargs: Any,
|
|
943
|
-
) -> "Agent":
|
|
944
|
-
"""Update an existing agent."""
|
|
945
|
-
base_overrides = {
|
|
946
|
-
"name": name,
|
|
947
|
-
"instruction": instruction,
|
|
948
|
-
"model": model,
|
|
949
|
-
"tools": tools,
|
|
950
|
-
"agents": agents,
|
|
951
|
-
"mcps": mcps,
|
|
952
|
-
}
|
|
953
|
-
overrides = _merge_override_maps(base_overrides, kwargs)
|
|
954
|
-
|
|
955
|
-
if file is not None:
|
|
956
|
-
payload = _prepare_import_payload(Path(file).expanduser(), overrides, drop_model_fields=True)
|
|
957
|
-
else:
|
|
958
|
-
payload = overrides
|
|
959
|
-
|
|
960
|
-
current_agent = self.get_agent_by_id(agent_id)
|
|
961
|
-
return self._update_agent_from_payload(agent_id, current_agent, payload)
|
|
962
|
-
|
|
963
|
-
def update_agent_from_file( # pragma: no cover - thin compatibility wrapper
|
|
964
|
-
self,
|
|
965
|
-
agent_id: str,
|
|
966
|
-
file_path: str | PathLike[str],
|
|
967
|
-
**overrides: Any,
|
|
968
|
-
) -> "Agent":
|
|
969
|
-
"""Backward-compatible helper to update an agent from a configuration file."""
|
|
970
|
-
return self.update_agent(agent_id, file=file_path, **overrides)
|
|
971
|
-
|
|
972
|
-
def delete_agent(self, agent_id: str) -> None:
|
|
973
|
-
"""Delete an agent."""
|
|
974
|
-
self._request("DELETE", f"/agents/{agent_id}")
|
|
975
|
-
|
|
976
|
-
def upsert_agent(self, identifier: str | Agent, **kwargs) -> Agent:
|
|
977
|
-
"""Create or update an agent by instance, ID, or name.
|
|
978
|
-
|
|
979
|
-
Args:
|
|
980
|
-
identifier: Agent instance, ID (UUID string), or name
|
|
981
|
-
**kwargs: Agent configuration (instruction, description, tools, etc.)
|
|
982
|
-
|
|
983
|
-
Returns:
|
|
984
|
-
The created or updated agent.
|
|
985
|
-
|
|
986
|
-
Example:
|
|
987
|
-
>>> # By name (creates if not exists)
|
|
988
|
-
>>> agent = client.agents.upsert_agent(
|
|
989
|
-
... "hello_agent",
|
|
990
|
-
... instruction="You are a helpful assistant.",
|
|
991
|
-
... description="A friendly agent",
|
|
992
|
-
... )
|
|
993
|
-
>>> # By instance
|
|
994
|
-
>>> agent = client.agents.upsert_agent(existing_agent, description="Updated")
|
|
995
|
-
>>> # By ID
|
|
996
|
-
>>> agent = client.agents.upsert_agent("uuid-here", description="Updated")
|
|
997
|
-
"""
|
|
998
|
-
# Handle Agent instance
|
|
999
|
-
if isinstance(identifier, Agent):
|
|
1000
|
-
if identifier.id:
|
|
1001
|
-
logger.info("Updating agent by instance: %s", identifier.name)
|
|
1002
|
-
return self.update_agent(identifier.id, name=identifier.name, **kwargs)
|
|
1003
|
-
identifier = identifier.name
|
|
1004
|
-
|
|
1005
|
-
# Handle string (ID or name)
|
|
1006
|
-
if isinstance(identifier, str):
|
|
1007
|
-
# Check if it's a UUID
|
|
1008
|
-
if is_uuid(identifier):
|
|
1009
|
-
logger.info("Updating agent by ID: %s", identifier)
|
|
1010
|
-
return self.update_agent(identifier, **kwargs)
|
|
1011
|
-
|
|
1012
|
-
# It's a name - find or create
|
|
1013
|
-
return self._upsert_agent_by_name(identifier, **kwargs)
|
|
1014
|
-
|
|
1015
|
-
raise ValueError(f"Invalid identifier type: {type(identifier)}")
|
|
1016
|
-
|
|
1017
|
-
def _upsert_agent_by_name(self, name: str, **kwargs) -> Agent:
|
|
1018
|
-
"""Find agent by name and update, or create if not found."""
|
|
1019
|
-
existing = self.find_agents(name)
|
|
1020
|
-
|
|
1021
|
-
if len(existing) == 1:
|
|
1022
|
-
logger.info("Updating existing agent: %s", name)
|
|
1023
|
-
return self.update_agent(existing[0].id, name=name, **kwargs)
|
|
1024
|
-
|
|
1025
|
-
if len(existing) > 1:
|
|
1026
|
-
raise ValueError(f"Multiple agents found with name '{name}'")
|
|
1027
|
-
|
|
1028
|
-
# Create new agent
|
|
1029
|
-
logger.info("Creating new agent: %s", name)
|
|
1030
|
-
return self.create_agent(name=name, **kwargs)
|
|
1031
|
-
|
|
1032
|
-
def _prepare_sync_request_data(
|
|
1033
|
-
self,
|
|
1034
|
-
message: str,
|
|
1035
|
-
files: list[str | BinaryIO] | None,
|
|
1036
|
-
tty: bool,
|
|
1037
|
-
**kwargs: Any,
|
|
1038
|
-
) -> tuple[dict | None, dict | None, list | None, dict, Any | None]:
|
|
1039
|
-
"""Prepare request data for synchronous agent runs with renderer support.
|
|
1040
|
-
|
|
1041
|
-
Args:
|
|
1042
|
-
message: Message to send
|
|
1043
|
-
files: Optional files to include
|
|
1044
|
-
tty: Whether to enable TTY mode
|
|
1045
|
-
**kwargs: Additional request parameters
|
|
1046
|
-
|
|
1047
|
-
Returns:
|
|
1048
|
-
Tuple of (payload, data_payload, files_payload, headers, multipart_data)
|
|
1049
|
-
"""
|
|
1050
|
-
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
1051
|
-
|
|
1052
|
-
if files:
|
|
1053
|
-
# Handle multipart data for file uploads
|
|
1054
|
-
multipart_data = prepare_multipart_data(message, files)
|
|
1055
|
-
if "chat_history" in kwargs and kwargs["chat_history"] is not None:
|
|
1056
|
-
multipart_data.data["chat_history"] = kwargs["chat_history"]
|
|
1057
|
-
if "pii_mapping" in kwargs and kwargs["pii_mapping"] is not None:
|
|
1058
|
-
multipart_data.data["pii_mapping"] = kwargs["pii_mapping"]
|
|
1059
|
-
|
|
1060
|
-
return (
|
|
1061
|
-
None,
|
|
1062
|
-
multipart_data.data,
|
|
1063
|
-
multipart_data.files,
|
|
1064
|
-
headers,
|
|
1065
|
-
multipart_data,
|
|
1066
|
-
)
|
|
1067
|
-
else:
|
|
1068
|
-
# Simple JSON payload for text-only requests
|
|
1069
|
-
payload = {"input": message, "stream": True, **kwargs}
|
|
1070
|
-
if tty:
|
|
1071
|
-
payload["tty"] = True
|
|
1072
|
-
return payload, None, None, headers, None
|
|
1073
|
-
|
|
1074
|
-
def _get_timeout_values(self, timeout: float | None, **kwargs: Any) -> tuple[float, float]:
|
|
1075
|
-
"""Get request timeout and execution timeout values.
|
|
1076
|
-
|
|
1077
|
-
Args:
|
|
1078
|
-
timeout: Request timeout (overrides instance timeout)
|
|
1079
|
-
**kwargs: Additional parameters including execution timeout
|
|
1080
|
-
|
|
1081
|
-
Returns:
|
|
1082
|
-
Tuple of (request_timeout, execution_timeout)
|
|
1083
|
-
"""
|
|
1084
|
-
request_timeout = timeout or self.timeout
|
|
1085
|
-
execution_timeout = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
1086
|
-
return request_timeout, execution_timeout
|
|
1087
|
-
|
|
1088
|
-
def run_agent(
|
|
1089
|
-
self,
|
|
1090
|
-
agent_id: str,
|
|
1091
|
-
message: str,
|
|
1092
|
-
files: list[str | BinaryIO] | None = None,
|
|
1093
|
-
tty: bool = False,
|
|
1094
|
-
*,
|
|
1095
|
-
renderer: RichStreamRenderer | str | None = "auto",
|
|
1096
|
-
runtime_config: dict[str, Any] | None = None,
|
|
1097
|
-
**kwargs,
|
|
1098
|
-
) -> str:
|
|
1099
|
-
"""Run an agent with a message, streaming via a renderer.
|
|
1100
|
-
|
|
1101
|
-
Args:
|
|
1102
|
-
agent_id: The ID of the agent to run.
|
|
1103
|
-
message: The message to send to the agent.
|
|
1104
|
-
files: Optional list of files to include with the request.
|
|
1105
|
-
tty: Whether to enable TTY mode.
|
|
1106
|
-
renderer: Renderer for streaming output.
|
|
1107
|
-
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1108
|
-
Keys should be platform IDs. Example:
|
|
1109
|
-
{
|
|
1110
|
-
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1111
|
-
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1112
|
-
"agent_config": {"planning": True},
|
|
1113
|
-
}
|
|
1114
|
-
**kwargs: Additional arguments to pass to the run API.
|
|
1115
|
-
|
|
1116
|
-
Returns:
|
|
1117
|
-
The agent's response as a string.
|
|
1118
|
-
"""
|
|
1119
|
-
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1120
|
-
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1121
|
-
kwargs["runtime_config"] = runtime_config
|
|
1122
|
-
(
|
|
1123
|
-
payload,
|
|
1124
|
-
data_payload,
|
|
1125
|
-
files_payload,
|
|
1126
|
-
headers,
|
|
1127
|
-
multipart_data,
|
|
1128
|
-
) = self._prepare_sync_request_data(message, files, tty, **kwargs)
|
|
1129
|
-
|
|
1130
|
-
render_manager = self._get_renderer_manager()
|
|
1131
|
-
verbose = kwargs.get("verbose", False)
|
|
1132
|
-
r = self._create_renderer(renderer, verbose=verbose)
|
|
1133
|
-
meta = render_manager.build_initial_metadata(agent_id, message, kwargs)
|
|
1134
|
-
render_manager.start_renderer(r, meta)
|
|
1135
|
-
|
|
1136
|
-
final_text = ""
|
|
1137
|
-
stats_usage: dict[str, Any] = {}
|
|
1138
|
-
started_monotonic: float | None = None
|
|
1139
|
-
finished_monotonic: float | None = None
|
|
1140
|
-
|
|
1141
|
-
timeout_seconds = compute_timeout_seconds(kwargs)
|
|
1142
|
-
|
|
1143
|
-
try:
|
|
1144
|
-
response = self.http_client.stream(
|
|
1145
|
-
"POST",
|
|
1146
|
-
f"/agents/{agent_id}/run",
|
|
1147
|
-
json=payload,
|
|
1148
|
-
data=data_payload,
|
|
1149
|
-
files=files_payload,
|
|
1150
|
-
headers=headers,
|
|
1151
|
-
timeout=timeout_seconds,
|
|
1152
|
-
)
|
|
1153
|
-
|
|
1154
|
-
with response as stream_response:
|
|
1155
|
-
stream_response.raise_for_status()
|
|
1156
|
-
|
|
1157
|
-
agent_name = kwargs.get("agent_name")
|
|
1158
|
-
|
|
1159
|
-
(
|
|
1160
|
-
final_text,
|
|
1161
|
-
stats_usage,
|
|
1162
|
-
started_monotonic,
|
|
1163
|
-
finished_monotonic,
|
|
1164
|
-
) = self._process_stream_events(
|
|
1165
|
-
stream_response,
|
|
1166
|
-
r,
|
|
1167
|
-
timeout_seconds,
|
|
1168
|
-
agent_name,
|
|
1169
|
-
meta,
|
|
1170
|
-
)
|
|
1171
|
-
|
|
1172
|
-
except KeyboardInterrupt:
|
|
1173
|
-
try:
|
|
1174
|
-
r.close()
|
|
1175
|
-
finally:
|
|
1176
|
-
raise
|
|
1177
|
-
except Exception:
|
|
1178
|
-
try:
|
|
1179
|
-
r.close()
|
|
1180
|
-
finally:
|
|
1181
|
-
raise
|
|
1182
|
-
finally:
|
|
1183
|
-
if multipart_data:
|
|
1184
|
-
multipart_data.close()
|
|
1185
|
-
|
|
1186
|
-
return self._finalize_renderer(
|
|
1187
|
-
r,
|
|
1188
|
-
final_text,
|
|
1189
|
-
stats_usage,
|
|
1190
|
-
started_monotonic,
|
|
1191
|
-
finished_monotonic,
|
|
1192
|
-
)
|
|
1193
|
-
|
|
1194
|
-
def _prepare_request_data(
|
|
1195
|
-
self,
|
|
1196
|
-
message: str,
|
|
1197
|
-
files: list[str | BinaryIO] | None,
|
|
1198
|
-
**kwargs,
|
|
1199
|
-
) -> tuple[dict | None, dict | None, dict | None, dict | None]:
|
|
1200
|
-
"""Prepare request data for async agent runs.
|
|
1201
|
-
|
|
1202
|
-
Returns:
|
|
1203
|
-
Tuple of (payload, data_payload, files_payload, headers)
|
|
1204
|
-
"""
|
|
1205
|
-
if files:
|
|
1206
|
-
# Handle multipart data for file uploads
|
|
1207
|
-
multipart_data = prepare_multipart_data(message, files)
|
|
1208
|
-
# Inject optional multipart extras expected by backend
|
|
1209
|
-
if "chat_history" in kwargs and kwargs["chat_history"] is not None:
|
|
1210
|
-
multipart_data.data["chat_history"] = kwargs["chat_history"]
|
|
1211
|
-
if "pii_mapping" in kwargs and kwargs["pii_mapping"] is not None:
|
|
1212
|
-
multipart_data.data["pii_mapping"] = kwargs["pii_mapping"]
|
|
1213
|
-
|
|
1214
|
-
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
1215
|
-
return None, multipart_data.data, multipart_data.files, headers
|
|
1216
|
-
else:
|
|
1217
|
-
# Simple JSON payload for text-only requests
|
|
1218
|
-
payload = {"input": message, "stream": True, **kwargs}
|
|
1219
|
-
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
1220
|
-
return payload, None, None, headers
|
|
1221
|
-
|
|
1222
|
-
def _create_async_client_config(self, timeout: float | None, headers: dict | None) -> dict:
|
|
1223
|
-
"""Create async client configuration with proper headers and timeout."""
|
|
1224
|
-
config = self._build_async_client(timeout or self.timeout)
|
|
1225
|
-
if headers:
|
|
1226
|
-
config["headers"] = {**config["headers"], **headers}
|
|
1227
|
-
return config
|
|
1228
|
-
|
|
1229
|
-
async def _stream_agent_response(
|
|
1230
|
-
self,
|
|
1231
|
-
async_client: httpx.AsyncClient,
|
|
1232
|
-
agent_id: str,
|
|
1233
|
-
payload: dict | None,
|
|
1234
|
-
data_payload: dict | None,
|
|
1235
|
-
files_payload: dict | None,
|
|
1236
|
-
headers: dict | None,
|
|
1237
|
-
timeout_seconds: float,
|
|
1238
|
-
agent_name: str | None,
|
|
1239
|
-
) -> AsyncGenerator[dict, None]:
|
|
1240
|
-
"""Stream the agent response and yield parsed JSON chunks."""
|
|
1241
|
-
async with async_client.stream(
|
|
1242
|
-
"POST",
|
|
1243
|
-
f"/agents/{agent_id}/run",
|
|
1244
|
-
json=payload,
|
|
1245
|
-
data=data_payload,
|
|
1246
|
-
files=files_payload,
|
|
1247
|
-
headers=headers,
|
|
1248
|
-
) as stream_response:
|
|
1249
|
-
stream_response.raise_for_status()
|
|
1250
|
-
|
|
1251
|
-
async for event in aiter_sse_events(stream_response, timeout_seconds, agent_name):
|
|
1252
|
-
try:
|
|
1253
|
-
chunk = json.loads(event["data"])
|
|
1254
|
-
yield chunk
|
|
1255
|
-
except json.JSONDecodeError:
|
|
1256
|
-
logger.debug("Non-JSON SSE fragment skipped")
|
|
1257
|
-
continue
|
|
1258
|
-
|
|
1259
|
-
async def arun_agent(
|
|
1260
|
-
self,
|
|
1261
|
-
agent_id: str,
|
|
1262
|
-
message: str,
|
|
1263
|
-
files: list[str | BinaryIO] | None = None,
|
|
1264
|
-
*,
|
|
1265
|
-
request_timeout: float | None = None,
|
|
1266
|
-
runtime_config: dict[str, Any] | None = None,
|
|
1267
|
-
**kwargs,
|
|
1268
|
-
) -> AsyncGenerator[dict, None]:
|
|
1269
|
-
"""Async run an agent with a message, yielding streaming JSON chunks.
|
|
1270
|
-
|
|
1271
|
-
Args:
|
|
1272
|
-
agent_id: ID of the agent to run
|
|
1273
|
-
message: Message to send to the agent
|
|
1274
|
-
files: Optional list of files to include
|
|
1275
|
-
request_timeout: Optional request timeout in seconds (defaults to client timeout)
|
|
1276
|
-
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1277
|
-
Keys should be platform IDs. Example:
|
|
1278
|
-
{
|
|
1279
|
-
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1280
|
-
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1281
|
-
"agent_config": {"planning": True},
|
|
1282
|
-
}
|
|
1283
|
-
**kwargs: Additional arguments (chat_history, pii_mapping, etc.)
|
|
1284
|
-
|
|
1285
|
-
Yields:
|
|
1286
|
-
Dictionary containing parsed JSON chunks from the streaming response
|
|
1287
|
-
|
|
1288
|
-
Raises:
|
|
1289
|
-
AgentTimeoutError: When agent execution times out
|
|
1290
|
-
httpx.TimeoutException: When general timeout occurs
|
|
1291
|
-
Exception: For other unexpected errors
|
|
1292
|
-
"""
|
|
1293
|
-
# Include runtime_config in kwargs only when caller hasn't already provided it
|
|
1294
|
-
if runtime_config is not None and "runtime_config" not in kwargs:
|
|
1295
|
-
kwargs["runtime_config"] = runtime_config
|
|
1296
|
-
# Derive timeout values for request/control flow
|
|
1297
|
-
legacy_timeout = kwargs.get("timeout")
|
|
1298
|
-
http_timeout_override = request_timeout if request_timeout is not None else legacy_timeout
|
|
1299
|
-
http_timeout = http_timeout_override or self.timeout
|
|
1300
|
-
|
|
1301
|
-
# Prepare request data
|
|
1302
|
-
payload, data_payload, files_payload, headers = self._prepare_request_data(message, files, **kwargs)
|
|
1303
|
-
|
|
1304
|
-
# Create async client configuration
|
|
1305
|
-
async_client_config = self._create_async_client_config(http_timeout_override, headers)
|
|
1306
|
-
|
|
1307
|
-
# Get execution timeout for streaming control
|
|
1308
|
-
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
1309
|
-
agent_name = kwargs.get("agent_name")
|
|
1310
|
-
|
|
1311
|
-
async def _chunk_stream() -> AsyncGenerator[dict, None]:
|
|
1312
|
-
async with httpx.AsyncClient(**async_client_config) as async_client:
|
|
1313
|
-
async for chunk in self._stream_agent_response(
|
|
1314
|
-
async_client,
|
|
1315
|
-
agent_id,
|
|
1316
|
-
payload,
|
|
1317
|
-
data_payload,
|
|
1318
|
-
files_payload,
|
|
1319
|
-
headers,
|
|
1320
|
-
timeout_seconds,
|
|
1321
|
-
agent_name,
|
|
1322
|
-
):
|
|
1323
|
-
yield chunk
|
|
1324
|
-
|
|
1325
|
-
async with _async_timeout_guard(http_timeout):
|
|
1326
|
-
async for chunk in _chunk_stream():
|
|
1327
|
-
yield chunk
|
|
1328
|
-
|
|
1329
|
-
@property
|
|
1330
|
-
def runs(self) -> "AgentRunsClient":
|
|
1331
|
-
"""Get the agent runs client."""
|
|
1332
|
-
if self._runs_client is None:
|
|
1333
|
-
shared_config = build_shared_config(self)
|
|
1334
|
-
self._runs_client = AgentRunsClient(**shared_config)
|
|
1335
|
-
return self._runs_client
|