glaip-sdk 0.6.5b9__py3-none-any.whl → 0.6.8b1__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/agents/base.py +87 -22
- glaip_sdk/cli/commands/configure.py +1 -1
- glaip_sdk/cli/slash/accounts_controller.py +110 -32
- glaip_sdk/cli/slash/agent_session.py +4 -1
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/session.py +35 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +4 -0
- glaip_sdk/cli/transcript/viewer.py +4 -2
- glaip_sdk/client/tools.py +5 -3
- glaip_sdk/registry/tool.py +5 -4
- glaip_sdk/runner/langgraph.py +133 -24
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +50 -8
- glaip_sdk/utils/runtime_config.py +15 -12
- {glaip_sdk-0.6.5b9.dist-info → glaip_sdk-0.6.8b1.dist-info}/METADATA +4 -4
- {glaip_sdk-0.6.5b9.dist-info → glaip_sdk-0.6.8b1.dist-info}/RECORD +18 -18
- {glaip_sdk-0.6.5b9.dist-info → glaip_sdk-0.6.8b1.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.5b9.dist-info → glaip_sdk-0.6.8b1.dist-info}/entry_points.txt +0 -0
glaip_sdk/agents/base.py
CHANGED
|
@@ -46,6 +46,8 @@ import inspect
|
|
|
46
46
|
import logging
|
|
47
47
|
import warnings
|
|
48
48
|
from collections.abc import AsyncGenerator
|
|
49
|
+
|
|
50
|
+
|
|
49
51
|
from pathlib import Path
|
|
50
52
|
from typing import TYPE_CHECKING, Any
|
|
51
53
|
|
|
@@ -869,7 +871,7 @@ class Agent:
|
|
|
869
871
|
ValueError: If the agent hasn't been deployed yet.
|
|
870
872
|
RuntimeError: If client is not available.
|
|
871
873
|
"""
|
|
872
|
-
if not self.id:
|
|
874
|
+
if not self.id: # pragma: no cover - defensive: called only when self.id is truthy
|
|
873
875
|
raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
|
|
874
876
|
if not self._client:
|
|
875
877
|
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
@@ -893,6 +895,48 @@ class Agent:
|
|
|
893
895
|
call_kwargs.update(kwargs)
|
|
894
896
|
return agent_client, call_kwargs
|
|
895
897
|
|
|
898
|
+
def _get_local_runner_or_raise(self) -> Any:
|
|
899
|
+
"""Get the local runner if available, otherwise raise ValueError.
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
The default local runner instance.
|
|
903
|
+
|
|
904
|
+
Raises:
|
|
905
|
+
ValueError: If local runtime is not available.
|
|
906
|
+
"""
|
|
907
|
+
if check_local_runtime_available():
|
|
908
|
+
return get_default_runner()
|
|
909
|
+
raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
|
|
910
|
+
|
|
911
|
+
def _prepare_local_runner_kwargs(
|
|
912
|
+
self,
|
|
913
|
+
message: str,
|
|
914
|
+
verbose: bool,
|
|
915
|
+
runtime_config: dict[str, Any] | None,
|
|
916
|
+
chat_history: list[dict[str, str]] | None,
|
|
917
|
+
**kwargs: Any,
|
|
918
|
+
) -> dict[str, Any]:
|
|
919
|
+
"""Prepare kwargs for local runner execution.
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
message: The message to send to the agent.
|
|
923
|
+
verbose: If True, print streaming output to console.
|
|
924
|
+
runtime_config: Optional runtime configuration.
|
|
925
|
+
chat_history: Optional list of prior conversation messages.
|
|
926
|
+
**kwargs: Additional arguments.
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
Dictionary of prepared kwargs for runner.run() or runner.arun().
|
|
930
|
+
"""
|
|
931
|
+
return {
|
|
932
|
+
"agent": self,
|
|
933
|
+
"message": message,
|
|
934
|
+
"verbose": verbose,
|
|
935
|
+
"runtime_config": runtime_config,
|
|
936
|
+
"chat_history": chat_history,
|
|
937
|
+
**kwargs,
|
|
938
|
+
}
|
|
939
|
+
|
|
896
940
|
def run(
|
|
897
941
|
self,
|
|
898
942
|
message: str,
|
|
@@ -943,32 +987,29 @@ class Agent:
|
|
|
943
987
|
return agent_client.run_agent(**call_kwargs)
|
|
944
988
|
|
|
945
989
|
# Local execution path (agent is not deployed)
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
agent=self,
|
|
950
|
-
message=message,
|
|
951
|
-
verbose=verbose,
|
|
952
|
-
runtime_config=runtime_config,
|
|
953
|
-
chat_history=chat_history,
|
|
954
|
-
**kwargs,
|
|
955
|
-
)
|
|
956
|
-
|
|
957
|
-
# Neither deployed nor local runtime available - provide actionable error
|
|
958
|
-
raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
|
|
990
|
+
runner = self._get_local_runner_or_raise()
|
|
991
|
+
local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
|
|
992
|
+
return runner.run(**local_kwargs)
|
|
959
993
|
|
|
960
994
|
async def arun(
|
|
961
995
|
self,
|
|
962
996
|
message: str,
|
|
963
997
|
verbose: bool = False,
|
|
964
998
|
runtime_config: dict[str, Any] | None = None,
|
|
999
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
965
1000
|
**kwargs: Any,
|
|
966
1001
|
) -> AsyncGenerator[dict, None]:
|
|
967
1002
|
"""Run the agent asynchronously with streaming output.
|
|
968
1003
|
|
|
1004
|
+
Supports two execution modes:
|
|
1005
|
+
- **Server-backed**: When the agent is deployed (has an ID), execution
|
|
1006
|
+
happens via the AIP backend server with streaming.
|
|
1007
|
+
- **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
|
|
1008
|
+
execution happens locally via aip-agents (no server required).
|
|
1009
|
+
|
|
969
1010
|
Args:
|
|
970
1011
|
message: The message to send to the agent.
|
|
971
|
-
verbose: If True, print streaming output to console.
|
|
1012
|
+
verbose: If True, print streaming output to console. Defaults to False.
|
|
972
1013
|
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
973
1014
|
Keys can be SDK objects, UUIDs, or names. Example:
|
|
974
1015
|
{
|
|
@@ -976,20 +1017,44 @@ class Agent:
|
|
|
976
1017
|
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
977
1018
|
"agent_config": {"planning": True},
|
|
978
1019
|
}
|
|
1020
|
+
Defaults to None.
|
|
1021
|
+
chat_history: Optional list of prior conversation messages for context.
|
|
1022
|
+
Each message is a dict with "role" and "content" keys.
|
|
1023
|
+
Defaults to None.
|
|
979
1024
|
**kwargs: Additional arguments to pass to the run API.
|
|
980
1025
|
|
|
981
1026
|
Yields:
|
|
982
1027
|
Streaming response chunks from the agent.
|
|
983
1028
|
|
|
984
1029
|
Raises:
|
|
985
|
-
ValueError: If the agent
|
|
986
|
-
RuntimeError: If
|
|
1030
|
+
ValueError: If the agent is not deployed and local runtime is not available.
|
|
1031
|
+
RuntimeError: If server-backed execution fails due to client issues.
|
|
987
1032
|
"""
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1033
|
+
# Backend routing: deployed agents use server, undeployed use local (if available)
|
|
1034
|
+
if self.id:
|
|
1035
|
+
# Server-backed execution path (agent is deployed)
|
|
1036
|
+
agent_client, call_kwargs = self._prepare_run_kwargs(
|
|
1037
|
+
message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
|
|
1038
|
+
)
|
|
1039
|
+
if chat_history is not None:
|
|
1040
|
+
call_kwargs["chat_history"] = chat_history
|
|
1041
|
+
|
|
1042
|
+
async for chunk in agent_client.arun_agent(**call_kwargs):
|
|
1043
|
+
yield chunk
|
|
1044
|
+
return
|
|
1045
|
+
|
|
1046
|
+
# Local execution path (agent is not deployed)
|
|
1047
|
+
runner = self._get_local_runner_or_raise()
|
|
1048
|
+
local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
|
|
1049
|
+
result = await runner.arun(**local_kwargs)
|
|
1050
|
+
# Yield a final_response event for consistency with server-backed execution
|
|
1051
|
+
# Include event_type for A2A event shape parity
|
|
1052
|
+
yield {
|
|
1053
|
+
"event_type": "final_response",
|
|
1054
|
+
"metadata": {"kind": "final_response"},
|
|
1055
|
+
"content": result,
|
|
1056
|
+
"is_final": True,
|
|
1057
|
+
}
|
|
993
1058
|
|
|
994
1059
|
def update(self, **kwargs: Any) -> Agent:
|
|
995
1060
|
"""Update the deployed agent with new configuration.
|
|
@@ -22,7 +22,7 @@ from glaip_sdk.branding import ACCENT_STYLE, ERROR_STYLE, INFO, NEUTRAL, SUCCESS
|
|
|
22
22
|
# Optional import for gitignore support; warn when missing to avoid silent expansion
|
|
23
23
|
try:
|
|
24
24
|
import pathspec # type: ignore[import-untyped] # noqa: PLC0415
|
|
25
|
-
except ImportError:
|
|
25
|
+
except ImportError: # pragma: no cover - optional dependency
|
|
26
26
|
pathspec = None # type: ignore[assignment]
|
|
27
27
|
from glaip_sdk.cli.account_store import get_account_store
|
|
28
28
|
from glaip_sdk.cli.commands.common_config import check_connection, render_branding_header
|
|
@@ -151,79 +151,147 @@ class AccountsController:
|
|
|
151
151
|
|
|
152
152
|
def _render_textual(self, rows: list[dict[str, str | bool]], store: AccountStore, env_lock: bool) -> None:
|
|
153
153
|
"""Launch the Textual accounts browser."""
|
|
154
|
-
|
|
154
|
+
active_before = store.get_active_account()
|
|
155
|
+
notified = False
|
|
156
|
+
|
|
157
|
+
def _switch_in_textual(name: str) -> tuple[bool, str]:
|
|
158
|
+
nonlocal notified
|
|
159
|
+
switched, message = self._switch_account(
|
|
160
|
+
store,
|
|
161
|
+
name,
|
|
162
|
+
env_lock,
|
|
163
|
+
emit_console=False,
|
|
164
|
+
invalidate_session=True,
|
|
165
|
+
)
|
|
166
|
+
if switched:
|
|
167
|
+
notified = True
|
|
168
|
+
return switched, message
|
|
169
|
+
|
|
170
|
+
callbacks = AccountsTUICallbacks(switch_account=_switch_in_textual)
|
|
155
171
|
active = next((row["name"] for row in rows if row.get("active")), None)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
try:
|
|
173
|
+
run_accounts_textual(rows, active_account=active, env_lock=env_lock, callbacks=callbacks)
|
|
174
|
+
except Exception as exc: # pragma: no cover - defensive around Textual failures
|
|
175
|
+
self.console.print(f"[{WARNING_STYLE}]Accounts browser exited unexpectedly: {exc}[/]")
|
|
176
|
+
|
|
177
|
+
# Exit snapshot: surface a success banner when a switch occurred inside the TUI.
|
|
178
|
+
# Always notify when the active account changed, even if Textual raised.
|
|
179
|
+
active_after = store.get_active_account()
|
|
180
|
+
if active_after != active_before and not notified:
|
|
181
|
+
self._notify_account_switched(active_after)
|
|
159
182
|
if active_after != active:
|
|
160
183
|
host_after = ""
|
|
161
|
-
|
|
184
|
+
display_account = active_after or "default"
|
|
185
|
+
account_after = store.get_account(display_account) if hasattr(store, "get_account") else None
|
|
162
186
|
if account_after:
|
|
163
187
|
host_after = account_after.get("api_url", "")
|
|
164
188
|
host_suffix = f" • {host_after}" if host_after else ""
|
|
165
189
|
self.console.print(
|
|
166
190
|
AIPPanel(
|
|
167
|
-
f"[{SUCCESS_STYLE}]Active account ➜ {
|
|
191
|
+
f"[{SUCCESS_STYLE}]Active account ➜ {display_account}[/]{host_suffix}",
|
|
168
192
|
title="✅ Account Switched",
|
|
169
193
|
border_style=SUCCESS_STYLE,
|
|
170
194
|
)
|
|
171
195
|
)
|
|
172
196
|
|
|
173
|
-
def
|
|
174
|
-
"""
|
|
197
|
+
def _format_connection_error_message(self, error_reason: str, account_name: str, api_url: str) -> str:
|
|
198
|
+
"""Format error message for connection validation failures."""
|
|
199
|
+
code, detail = self._parse_error_reason(error_reason)
|
|
200
|
+
if code == "connection_failed":
|
|
201
|
+
return f"Switch aborted: cannot reach {api_url}. Check URL or network."
|
|
202
|
+
if code == "api_failed":
|
|
203
|
+
return f"Switch aborted: API error for '{account_name}'. Check credentials."
|
|
204
|
+
detail_suffix = f": {detail}" if detail else ""
|
|
205
|
+
return f"Switch aborted: {code or 'Validation failed'}{detail_suffix}"
|
|
206
|
+
|
|
207
|
+
def _emit_error_message(self, msg: str, style: str = ERROR_STYLE) -> None:
|
|
208
|
+
"""Emit an error or warning message to the console."""
|
|
209
|
+
self.console.print(f"[{style}]{msg}[/]")
|
|
210
|
+
|
|
211
|
+
def _validate_account_switch(
|
|
212
|
+
self, store: AccountStore, name: str, env_lock: bool, emit_console: bool
|
|
213
|
+
) -> tuple[bool, str, dict[str, str] | None]:
|
|
214
|
+
"""Validate account switch prerequisites; returns (is_valid, error_msg, account_dict)."""
|
|
175
215
|
if env_lock:
|
|
176
216
|
msg = "Env credentials detected (AIP_API_URL/AIP_API_KEY); switching is disabled."
|
|
177
|
-
|
|
178
|
-
|
|
217
|
+
if emit_console:
|
|
218
|
+
self._emit_error_message(msg, WARNING_STYLE)
|
|
219
|
+
return False, msg, None
|
|
179
220
|
|
|
180
221
|
account = store.get_account(name)
|
|
181
222
|
if not account:
|
|
182
223
|
msg = f"Account '{name}' not found."
|
|
183
|
-
|
|
184
|
-
|
|
224
|
+
if emit_console:
|
|
225
|
+
self._emit_error_message(msg)
|
|
226
|
+
return False, msg, None
|
|
185
227
|
|
|
186
228
|
api_url = account.get("api_url", "")
|
|
187
229
|
api_key = account.get("api_key", "")
|
|
188
230
|
if not api_url or not api_key:
|
|
189
231
|
edit_cmd = f"aip accounts edit {name}"
|
|
190
232
|
msg = f"Account '{name}' is missing credentials. Use `/login` or `{edit_cmd}`."
|
|
191
|
-
|
|
192
|
-
|
|
233
|
+
if emit_console:
|
|
234
|
+
self._emit_error_message(msg)
|
|
235
|
+
return False, msg, None
|
|
193
236
|
|
|
194
237
|
ok, error_reason = check_connection_with_reason(api_url, api_key, abort_on_error=False)
|
|
195
238
|
if not ok:
|
|
196
|
-
|
|
197
|
-
if
|
|
198
|
-
msg
|
|
199
|
-
|
|
200
|
-
msg = f"Switch aborted: API error for '{name}'. Check credentials."
|
|
201
|
-
else:
|
|
202
|
-
detail_suffix = f": {detail}" if detail else ""
|
|
203
|
-
msg = f"Switch aborted: {code or 'Validation failed'}{detail_suffix}"
|
|
204
|
-
self.console.print(f"[{WARNING_STYLE}]{msg}[/]")
|
|
205
|
-
return False, msg
|
|
239
|
+
msg = self._format_connection_error_message(error_reason, name, api_url)
|
|
240
|
+
if emit_console:
|
|
241
|
+
self._emit_error_message(msg, WARNING_STYLE)
|
|
242
|
+
return False, msg, None
|
|
206
243
|
|
|
244
|
+
return True, "", account
|
|
245
|
+
|
|
246
|
+
def _execute_account_switch(
|
|
247
|
+
self, store: AccountStore, name: str, account: dict[str, str], invalidate_session: bool, emit_console: bool
|
|
248
|
+
) -> tuple[bool, str]:
|
|
249
|
+
"""Execute the account switch and emit success message."""
|
|
207
250
|
try:
|
|
208
251
|
store.set_active_account(name)
|
|
252
|
+
api_url = account.get("api_url", "")
|
|
253
|
+
api_key = account.get("api_key", "")
|
|
209
254
|
masked_key = mask_api_key_display(api_key)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
255
|
+
if invalidate_session:
|
|
256
|
+
self._notify_account_switched(name)
|
|
257
|
+
if emit_console:
|
|
258
|
+
self.console.print(
|
|
259
|
+
AIPPanel(
|
|
260
|
+
f"[{SUCCESS_STYLE}]Active account ➜ {name}[/]\nAPI URL: {api_url}\nKey: {masked_key}",
|
|
261
|
+
title="✅ Account Switched",
|
|
262
|
+
border_style=SUCCESS_STYLE,
|
|
263
|
+
)
|
|
215
264
|
)
|
|
216
|
-
)
|
|
217
265
|
return True, f"Switched to '{name}'."
|
|
218
266
|
except AccountStoreError as exc:
|
|
219
267
|
msg = f"Failed to set active account: {exc}"
|
|
220
|
-
|
|
268
|
+
if emit_console:
|
|
269
|
+
self._emit_error_message(msg)
|
|
221
270
|
return False, msg
|
|
222
271
|
except Exception as exc: # NOSONAR(S1045) - catch-all needed for unexpected errors
|
|
223
272
|
msg = f"Unexpected error while switching to '{name}': {exc}"
|
|
224
|
-
|
|
273
|
+
if emit_console:
|
|
274
|
+
self._emit_error_message(msg)
|
|
225
275
|
return False, msg
|
|
226
276
|
|
|
277
|
+
def _switch_account(
|
|
278
|
+
self,
|
|
279
|
+
store: AccountStore,
|
|
280
|
+
name: str,
|
|
281
|
+
env_lock: bool,
|
|
282
|
+
*,
|
|
283
|
+
emit_console: bool = True,
|
|
284
|
+
invalidate_session: bool = True,
|
|
285
|
+
) -> tuple[bool, str]:
|
|
286
|
+
"""Validate and switch active account; returns (success, message)."""
|
|
287
|
+
is_valid, error_msg, account = self._validate_account_switch(store, name, env_lock, emit_console)
|
|
288
|
+
if not is_valid:
|
|
289
|
+
return False, error_msg
|
|
290
|
+
|
|
291
|
+
if account is None: # Defensive – should never happen, but avoid crashing in production
|
|
292
|
+
return False, "Unable to locate account after validation."
|
|
293
|
+
return self._execute_account_switch(store, name, account, invalidate_session, emit_console)
|
|
294
|
+
|
|
227
295
|
@staticmethod
|
|
228
296
|
def _parse_error_reason(reason: str | None) -> tuple[str, str]:
|
|
229
297
|
"""Parse error reason into (code, detail) to avoid fragile substring checks."""
|
|
@@ -404,8 +472,18 @@ class AccountsController:
|
|
|
404
472
|
except Exception as exc:
|
|
405
473
|
self.console.print(f"[{WARNING_STYLE}]Account saved but could not set active: {exc}[/]")
|
|
406
474
|
else:
|
|
475
|
+
self._notify_account_switched(name)
|
|
407
476
|
self._announce_active_change(store, name)
|
|
408
477
|
|
|
478
|
+
def _notify_account_switched(self, name: str | None) -> None:
|
|
479
|
+
"""Best-effort notify the hosting session that the active account changed."""
|
|
480
|
+
notify = getattr(self.session, "on_account_switched", None)
|
|
481
|
+
if callable(notify):
|
|
482
|
+
try:
|
|
483
|
+
notify(name)
|
|
484
|
+
except Exception: # pragma: no cover - best-effort callback
|
|
485
|
+
pass
|
|
486
|
+
|
|
409
487
|
def _confirm_delete_prompt(self, name: str) -> bool:
|
|
410
488
|
"""Ask for delete confirmation; return True when confirmed."""
|
|
411
489
|
self.console.print(f"[{WARNING_STYLE}]Type '{name}' to confirm deletion. This cannot be undone.[/]")
|
|
@@ -38,7 +38,10 @@ class AgentRunSession:
|
|
|
38
38
|
self.console = session.console
|
|
39
39
|
self._agent_id = str(getattr(agent, "id", ""))
|
|
40
40
|
self._agent_name = getattr(agent, "name", "") or self._agent_id
|
|
41
|
-
self._prompt_placeholder: str =
|
|
41
|
+
self._prompt_placeholder: str = (
|
|
42
|
+
"Chat with this agent here; use / for shortcuts. "
|
|
43
|
+
"Alt+Enter inserts a newline. Ctrl+T opens the last transcript."
|
|
44
|
+
)
|
|
42
45
|
self._contextual_completion_help: dict[str, str] = {
|
|
43
46
|
"details": "Show this agent's configuration (+ expands prompt).",
|
|
44
47
|
"help": "Display this context-aware menu.",
|
glaip_sdk/cli/slash/prompt.py
CHANGED
|
@@ -162,6 +162,17 @@ def _create_key_bindings(_session: SlashSession) -> Any:
|
|
|
162
162
|
if buffer.complete_state is not None:
|
|
163
163
|
buffer.cancel_completion()
|
|
164
164
|
|
|
165
|
+
@bindings.add("c-t") # type: ignore[misc]
|
|
166
|
+
def _handle_ctrl_t_key(event: Any) -> None: # vulture: ignore
|
|
167
|
+
"""Handle Ctrl+T key - open the transcript viewer (when available)."""
|
|
168
|
+
buffer = event.app.current_buffer
|
|
169
|
+
if buffer.complete_state is not None:
|
|
170
|
+
buffer.cancel_completion()
|
|
171
|
+
|
|
172
|
+
open_viewer = getattr(_session, "open_transcript_viewer", None)
|
|
173
|
+
if callable(open_viewer):
|
|
174
|
+
open_viewer(announce=True)
|
|
175
|
+
|
|
165
176
|
return bindings
|
|
166
177
|
|
|
167
178
|
|
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -548,7 +548,7 @@ class SlashSession:
|
|
|
548
548
|
try:
|
|
549
549
|
# Use the modern account-aware wizard directly (bypasses legacy config gating)
|
|
550
550
|
_configure_interactive(account_name=None)
|
|
551
|
-
self.
|
|
551
|
+
self.on_account_switched()
|
|
552
552
|
if self._suppress_login_layout:
|
|
553
553
|
self._welcome_rendered = False
|
|
554
554
|
self._default_actions_shown = False
|
|
@@ -1211,6 +1211,33 @@ class SlashSession:
|
|
|
1211
1211
|
self._client = get_client(self.ctx)
|
|
1212
1212
|
return self._client
|
|
1213
1213
|
|
|
1214
|
+
def on_account_switched(self, _account_name: str | None = None) -> None:
|
|
1215
|
+
"""Reset any state that depends on the active account.
|
|
1216
|
+
|
|
1217
|
+
The active account can change via `/accounts` (or other flows that call
|
|
1218
|
+
AccountStore.set_active_account). The slash session caches a configured
|
|
1219
|
+
client instance, so we must invalidate it to avoid leaking the previous
|
|
1220
|
+
account's API URL/key into subsequent commands like `/agents` or `/runs`.
|
|
1221
|
+
|
|
1222
|
+
This method clears:
|
|
1223
|
+
- Client and config cache (account-specific credentials)
|
|
1224
|
+
- Current agent and recent agents (agent data is account-scoped)
|
|
1225
|
+
- Runs pagination state (runs are account-scoped)
|
|
1226
|
+
- Active renderer and transcript ready state (UI state tied to account context)
|
|
1227
|
+
- Contextual commands (may be account-specific)
|
|
1228
|
+
|
|
1229
|
+
These broader resets ensure a clean slate when switching accounts, preventing
|
|
1230
|
+
stale data from the previous account from appearing in the new account's context.
|
|
1231
|
+
"""
|
|
1232
|
+
self._client = None
|
|
1233
|
+
self._config_cache = None
|
|
1234
|
+
self._current_agent = None
|
|
1235
|
+
self.recent_agents = []
|
|
1236
|
+
self._runs_pagination_state.clear()
|
|
1237
|
+
self.clear_active_renderer()
|
|
1238
|
+
self.clear_agent_transcript_ready()
|
|
1239
|
+
self.set_contextual_commands(None)
|
|
1240
|
+
|
|
1214
1241
|
def set_contextual_commands(self, commands: dict[str, str] | None, *, include_global: bool = True) -> None:
|
|
1215
1242
|
"""Set context-specific commands that should appear in completions."""
|
|
1216
1243
|
self._contextual_commands = dict(commands or {})
|
|
@@ -1340,14 +1367,16 @@ class SlashSession:
|
|
|
1340
1367
|
f"[{ACCENT_STYLE}]{agent_info['id']}[/]"
|
|
1341
1368
|
)
|
|
1342
1369
|
status_line = f"[{SUCCESS_STYLE}]ready[/]"
|
|
1343
|
-
|
|
1370
|
+
if not transcript_status["has_transcript"]:
|
|
1371
|
+
status_line += " · no transcript"
|
|
1372
|
+
elif transcript_status["transcript_ready"]:
|
|
1373
|
+
status_line += " · transcript ready"
|
|
1374
|
+
else:
|
|
1375
|
+
status_line += " · transcript pending"
|
|
1344
1376
|
header_grid.add_row(primary_line, status_line)
|
|
1345
1377
|
|
|
1346
1378
|
if agent_info["description"]:
|
|
1347
|
-
|
|
1348
|
-
if not transcript_status["transcript_ready"]:
|
|
1349
|
-
description = f"{description} (transcript pending)"
|
|
1350
|
-
header_grid.add_row(f"[dim]{description}[/dim]", "")
|
|
1379
|
+
header_grid.add_row(f"[dim]{agent_info['description']}[/dim]", "")
|
|
1351
1380
|
|
|
1352
1381
|
return header_grid
|
|
1353
1382
|
|
|
@@ -684,6 +684,10 @@ class AccountsTextualApp(BackgroundTaskMixin, _AppBase): # pragma: no cover - i
|
|
|
684
684
|
return
|
|
685
685
|
self.exit()
|
|
686
686
|
|
|
687
|
+
def action_app_exit(self) -> None:
|
|
688
|
+
"""Exit the application regardless of focus state."""
|
|
689
|
+
self.exit()
|
|
690
|
+
|
|
687
691
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
688
692
|
"""Handle filter bar buttons."""
|
|
689
693
|
if event.button.id == "filter-clear":
|
|
@@ -23,6 +23,7 @@ except Exception: # pragma: no cover - optional dependency
|
|
|
23
23
|
from glaip_sdk.cli.transcript.cache import suggest_filename
|
|
24
24
|
from glaip_sdk.cli.utils import prompt_export_choice_questionary, questionary_safe_ask
|
|
25
25
|
from glaip_sdk.utils.rendering.layout.progress import is_delegation_tool
|
|
26
|
+
from glaip_sdk.utils.rendering.layout.transcript import DEFAULT_TRANSCRIPT_THEME
|
|
26
27
|
from glaip_sdk.utils.rendering.viewer import (
|
|
27
28
|
ViewerContext as PresenterViewerContext,
|
|
28
29
|
prepare_viewer_snapshot as presenter_prepare_viewer_snapshot,
|
|
@@ -93,8 +94,9 @@ class PostRunViewer: # pragma: no cover - interactive flows are not unit tested
|
|
|
93
94
|
if self._view_mode == "default":
|
|
94
95
|
presenter_render_post_run_view(self.console, self.ctx)
|
|
95
96
|
else:
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
theme = DEFAULT_TRANSCRIPT_THEME
|
|
98
|
+
snapshot, state = presenter_prepare_viewer_snapshot(self.ctx, glyphs=None, theme=theme)
|
|
99
|
+
presenter_render_transcript_view(self.console, snapshot, theme=theme)
|
|
98
100
|
presenter_render_transcript_events(self.console, state.events)
|
|
99
101
|
|
|
100
102
|
# ------------------------------------------------------------------
|
glaip_sdk/client/tools.py
CHANGED
|
@@ -562,12 +562,14 @@ class ToolClient(BaseClient):
|
|
|
562
562
|
) -> Tool:
|
|
563
563
|
"""Find tool by name and update, or create if not found."""
|
|
564
564
|
existing = self.find_tools(name)
|
|
565
|
+
name_lower = name.lower()
|
|
566
|
+
exact_matches = [tool for tool in existing if tool.name and tool.name.lower() == name_lower]
|
|
565
567
|
|
|
566
|
-
if len(
|
|
568
|
+
if len(exact_matches) == 1:
|
|
567
569
|
logger.info("Updating existing tool: %s", name)
|
|
568
|
-
return self._do_tool_upsert_update(
|
|
570
|
+
return self._do_tool_upsert_update(exact_matches[0].id, name, code, description, framework, **kwargs)
|
|
569
571
|
|
|
570
|
-
if len(
|
|
572
|
+
if len(exact_matches) > 1:
|
|
571
573
|
raise ValueError(f"Multiple tools found with name '{name}'")
|
|
572
574
|
|
|
573
575
|
# Create new tool - code is required
|
glaip_sdk/registry/tool.py
CHANGED
|
@@ -160,13 +160,14 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
160
160
|
True if ref is a custom tool that needs uploading.
|
|
161
161
|
"""
|
|
162
162
|
try:
|
|
163
|
-
from glaip_sdk.utils.tool_detection import
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
from glaip_sdk.utils.tool_detection import ( # noqa: PLC0415
|
|
164
|
+
is_langchain_tool,
|
|
165
|
+
)
|
|
166
166
|
except ImportError:
|
|
167
|
-
# Handle case where langchain_core is not available or tool_detection import fails
|
|
168
167
|
return False
|
|
169
168
|
|
|
169
|
+
return is_langchain_tool(ref)
|
|
170
|
+
|
|
170
171
|
def resolve(self, ref: Any) -> Tool:
|
|
171
172
|
"""Resolve a tool reference to a platform Tool object.
|
|
172
173
|
|
glaip_sdk/runner/langgraph.py
CHANGED
|
@@ -18,6 +18,7 @@ Example:
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import asyncio
|
|
21
|
+
import inspect
|
|
21
22
|
from dataclasses import dataclass
|
|
22
23
|
from typing import TYPE_CHECKING, Any
|
|
23
24
|
|
|
@@ -30,6 +31,8 @@ from glaip_sdk.utils.a2a import A2AEventStreamProcessor
|
|
|
30
31
|
from gllm_core.utils import LoggerManager
|
|
31
32
|
|
|
32
33
|
if TYPE_CHECKING:
|
|
34
|
+
from langchain_core.messages import BaseMessage
|
|
35
|
+
|
|
33
36
|
from glaip_sdk.agents.base import Agent
|
|
34
37
|
|
|
35
38
|
logger = LoggerManager().get_logger(__name__)
|
|
@@ -38,6 +41,42 @@ logger = LoggerManager().get_logger(__name__)
|
|
|
38
41
|
_event_processor = A2AEventStreamProcessor()
|
|
39
42
|
|
|
40
43
|
|
|
44
|
+
def _convert_chat_history_to_messages(
|
|
45
|
+
chat_history: list[dict[str, str]] | None,
|
|
46
|
+
) -> list[BaseMessage]:
|
|
47
|
+
"""Convert chat history dicts to LangChain messages.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
chat_history: List of dicts with "role" and "content" keys.
|
|
51
|
+
Supported roles: "user"/"human", "assistant"/"ai", "system".
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of LangChain BaseMessage instances.
|
|
55
|
+
"""
|
|
56
|
+
if not chat_history:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage # noqa: PLC0415
|
|
60
|
+
|
|
61
|
+
messages: list[BaseMessage] = []
|
|
62
|
+
for msg in chat_history:
|
|
63
|
+
role = msg.get("role", "").lower()
|
|
64
|
+
content = msg.get("content", "")
|
|
65
|
+
|
|
66
|
+
if role in ("user", "human"):
|
|
67
|
+
messages.append(HumanMessage(content=content))
|
|
68
|
+
elif role in ("assistant", "ai"):
|
|
69
|
+
messages.append(AIMessage(content=content))
|
|
70
|
+
elif role == "system":
|
|
71
|
+
messages.append(SystemMessage(content=content))
|
|
72
|
+
else:
|
|
73
|
+
# Default to human message for unknown roles
|
|
74
|
+
logger.warning("Unknown chat history role '%s', treating as user message", role)
|
|
75
|
+
messages.append(HumanMessage(content=content))
|
|
76
|
+
|
|
77
|
+
return messages
|
|
78
|
+
|
|
79
|
+
|
|
41
80
|
@dataclass(frozen=True, slots=True)
|
|
42
81
|
class LangGraphRunner(BaseRunner):
|
|
43
82
|
"""Runner implementation using aip-agents LangGraphReactAgent.
|
|
@@ -58,8 +97,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
58
97
|
agent: Agent,
|
|
59
98
|
message: str,
|
|
60
99
|
verbose: bool = False,
|
|
61
|
-
runtime_config: dict[str, Any] | None = None,
|
|
62
|
-
chat_history:
|
|
100
|
+
runtime_config: dict[str, Any] | None = None,
|
|
101
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
63
102
|
**kwargs: Any,
|
|
64
103
|
) -> str:
|
|
65
104
|
"""Execute agent synchronously and return final response text.
|
|
@@ -72,7 +111,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
72
111
|
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
73
112
|
Defaults to None. (Implemented in PR-04+)
|
|
74
113
|
chat_history: Optional list of prior conversation messages.
|
|
75
|
-
|
|
114
|
+
Each message is a dict with "role" and "content" keys.
|
|
115
|
+
Defaults to None.
|
|
76
116
|
**kwargs: Additional keyword arguments passed to the backend.
|
|
77
117
|
|
|
78
118
|
Returns:
|
|
@@ -85,23 +125,34 @@ class LangGraphRunner(BaseRunner):
|
|
|
85
125
|
if not check_local_runtime_available():
|
|
86
126
|
raise RuntimeError(get_local_runtime_missing_message())
|
|
87
127
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
try:
|
|
129
|
+
asyncio.get_running_loop()
|
|
130
|
+
except RuntimeError:
|
|
131
|
+
pass
|
|
132
|
+
else:
|
|
133
|
+
raise RuntimeError(
|
|
134
|
+
"LangGraphRunner.run() cannot be called from a running event loop. "
|
|
135
|
+
"Use 'await LangGraphRunner.arun(...)' instead."
|
|
95
136
|
)
|
|
137
|
+
|
|
138
|
+
coro = self._arun_internal(
|
|
139
|
+
agent=agent,
|
|
140
|
+
message=message,
|
|
141
|
+
verbose=verbose,
|
|
142
|
+
runtime_config=runtime_config,
|
|
143
|
+
chat_history=chat_history,
|
|
144
|
+
**kwargs,
|
|
96
145
|
)
|
|
97
146
|
|
|
147
|
+
return asyncio.run(coro)
|
|
148
|
+
|
|
98
149
|
async def arun(
|
|
99
150
|
self,
|
|
100
151
|
agent: Agent,
|
|
101
152
|
message: str,
|
|
102
153
|
verbose: bool = False,
|
|
103
154
|
runtime_config: dict[str, Any] | None = None,
|
|
104
|
-
chat_history:
|
|
155
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
105
156
|
**kwargs: Any,
|
|
106
157
|
) -> str:
|
|
107
158
|
"""Execute agent asynchronously and return final response text.
|
|
@@ -114,7 +165,8 @@ class LangGraphRunner(BaseRunner):
|
|
|
114
165
|
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
115
166
|
Defaults to None. (Implemented in PR-04+)
|
|
116
167
|
chat_history: Optional list of prior conversation messages.
|
|
117
|
-
|
|
168
|
+
Each message is a dict with "role" and "content" keys.
|
|
169
|
+
Defaults to None.
|
|
118
170
|
**kwargs: Additional keyword arguments passed to the backend.
|
|
119
171
|
|
|
120
172
|
Returns:
|
|
@@ -128,6 +180,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
128
180
|
message=message,
|
|
129
181
|
verbose=verbose,
|
|
130
182
|
runtime_config=runtime_config,
|
|
183
|
+
chat_history=chat_history,
|
|
131
184
|
**kwargs,
|
|
132
185
|
)
|
|
133
186
|
|
|
@@ -137,6 +190,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
137
190
|
message: str,
|
|
138
191
|
verbose: bool = False,
|
|
139
192
|
runtime_config: dict[str, Any] | None = None,
|
|
193
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
140
194
|
**kwargs: Any,
|
|
141
195
|
) -> str:
|
|
142
196
|
"""Internal async implementation of agent execution.
|
|
@@ -146,6 +200,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
146
200
|
message: The user message to send to the agent.
|
|
147
201
|
verbose: If True, emit debug trace output during execution.
|
|
148
202
|
runtime_config: Optional runtime configuration for tools, MCPs, etc.
|
|
203
|
+
chat_history: Optional list of prior conversation messages.
|
|
149
204
|
**kwargs: Additional keyword arguments passed to the backend.
|
|
150
205
|
|
|
151
206
|
Returns:
|
|
@@ -154,6 +209,16 @@ class LangGraphRunner(BaseRunner):
|
|
|
154
209
|
# Build the local LangGraphReactAgent from the glaip_sdk Agent
|
|
155
210
|
local_agent = self.build_langgraph_agent(agent, runtime_config=runtime_config)
|
|
156
211
|
|
|
212
|
+
# Convert chat history to LangChain messages for the agent
|
|
213
|
+
langchain_messages = _convert_chat_history_to_messages(chat_history)
|
|
214
|
+
if langchain_messages:
|
|
215
|
+
kwargs["messages"] = langchain_messages
|
|
216
|
+
logger.debug(
|
|
217
|
+
"Passing %d chat history messages to agent '%s'",
|
|
218
|
+
len(langchain_messages),
|
|
219
|
+
agent.name,
|
|
220
|
+
)
|
|
221
|
+
|
|
157
222
|
# Collect A2AEvents from the stream and extract final response
|
|
158
223
|
events: list[dict[str, Any]] = []
|
|
159
224
|
|
|
@@ -187,6 +252,9 @@ class LangGraphRunner(BaseRunner):
|
|
|
187
252
|
from glaip_sdk.runner.tool_adapter import LangChainToolAdapter # noqa: PLC0415
|
|
188
253
|
|
|
189
254
|
# Adapt tools for local execution
|
|
255
|
+
# NOTE: CLI parity waiver - local tool execution is SDK-only for MVP.
|
|
256
|
+
# See specs/f/local-agent-runtime/plan.md: "CLI parity is explicitly deferred
|
|
257
|
+
# and will require SDK Technical Lead sign-off per constitution principle IV."
|
|
190
258
|
langchain_tools: list[Any] = []
|
|
191
259
|
if agent.tools:
|
|
192
260
|
adapter = LangChainToolAdapter()
|
|
@@ -255,14 +323,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
255
323
|
|
|
256
324
|
sub_agent_instances = []
|
|
257
325
|
for sub_agent in sub_agents:
|
|
258
|
-
|
|
259
|
-
agent_name = getattr(sub_agent, "name", "<unknown>")
|
|
260
|
-
raise ValueError(
|
|
261
|
-
f"Sub-agent '{agent_name}' is not supported in local mode. "
|
|
262
|
-
"Platform agents (from_id, from_native) cannot be used as "
|
|
263
|
-
"sub-agents in local execution. "
|
|
264
|
-
"Define the sub-agent locally with Agent(name=..., instruction=...) instead."
|
|
265
|
-
)
|
|
326
|
+
self._validate_sub_agent_for_local_mode(sub_agent)
|
|
266
327
|
sub_agent_instances.append(self.build_langgraph_agent(sub_agent, runtime_config))
|
|
267
328
|
return sub_agent_instances
|
|
268
329
|
|
|
@@ -286,16 +347,12 @@ class LangGraphRunner(BaseRunner):
|
|
|
286
347
|
|
|
287
348
|
mcp_adapter = LangChainMCPAdapter()
|
|
288
349
|
base_mcp_configs = mcp_adapter.adapt_mcps(agent.mcps)
|
|
289
|
-
logger.debug("Base MCP configs from adapter: %s", base_mcp_configs)
|
|
290
350
|
|
|
291
351
|
# Apply merged mcp_configs overrides (agent definition + runtime)
|
|
292
|
-
logger.debug("Merged mcp_configs to apply: %s", merged_mcp_configs)
|
|
293
352
|
if merged_mcp_configs:
|
|
294
353
|
base_mcp_configs = self._apply_runtime_mcp_configs(base_mcp_configs, merged_mcp_configs)
|
|
295
|
-
logger.debug("MCP configs after override: %s", base_mcp_configs)
|
|
296
354
|
|
|
297
355
|
if base_mcp_configs:
|
|
298
|
-
logger.info("MCP configs being sent to aip-agents: %s", base_mcp_configs)
|
|
299
356
|
local_agent.add_mcp_server(base_mcp_configs)
|
|
300
357
|
logger.debug(
|
|
301
358
|
"Registered %d MCP server(s) for agent '%s'",
|
|
@@ -361,6 +418,7 @@ class LangGraphRunner(BaseRunner):
|
|
|
361
418
|
Returns:
|
|
362
419
|
Agent-specific config dict, or empty dict if not found.
|
|
363
420
|
"""
|
|
421
|
+
from glaip_sdk.utils.resource_refs import is_uuid # noqa: PLC0415
|
|
364
422
|
from glaip_sdk.utils.runtime_config import get_name_from_key # noqa: PLC0415
|
|
365
423
|
|
|
366
424
|
# Reserved keys at the top level
|
|
@@ -371,6 +429,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
371
429
|
if key in reserved_keys:
|
|
372
430
|
continue # Skip global configs
|
|
373
431
|
|
|
432
|
+
if isinstance(key, str) and is_uuid(key):
|
|
433
|
+
logger.warning(
|
|
434
|
+
"UUID agent override key '%s' is not supported in local mode; skipping. "
|
|
435
|
+
"Use agent name string or Agent instance as the key instead.",
|
|
436
|
+
key,
|
|
437
|
+
)
|
|
438
|
+
continue
|
|
439
|
+
|
|
374
440
|
# Check if this key matches the agent
|
|
375
441
|
try:
|
|
376
442
|
key_name = get_name_from_key(key)
|
|
@@ -579,6 +645,49 @@ class LangGraphRunner(BaseRunner):
|
|
|
579
645
|
|
|
580
646
|
return merged
|
|
581
647
|
|
|
648
|
+
def _validate_sub_agent_for_local_mode(self, sub_agent: Any) -> None:
|
|
649
|
+
"""Validate that a sub-agent reference is supported for local execution.
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
sub_agent: The sub-agent reference to validate.
|
|
653
|
+
|
|
654
|
+
Raises:
|
|
655
|
+
ValueError: If the sub-agent is not supported in local mode.
|
|
656
|
+
"""
|
|
657
|
+
# String references are allowed by SDK API but not for local mode
|
|
658
|
+
if isinstance(sub_agent, str):
|
|
659
|
+
raise ValueError(
|
|
660
|
+
f"Sub-agent '{sub_agent}' is a string reference and cannot be used in local mode. "
|
|
661
|
+
"String sub-agent references are only supported for server execution. "
|
|
662
|
+
"For local mode, define the sub-agent with Agent(name=..., instruction=...)."
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# Validate sub-agent is not a class
|
|
666
|
+
if inspect.isclass(sub_agent):
|
|
667
|
+
raise ValueError(
|
|
668
|
+
f"Sub-agent '{sub_agent.__name__}' is a class, not an instance. "
|
|
669
|
+
"Local mode requires Agent INSTANCES. "
|
|
670
|
+
"Did you forget to instantiate it? e.g., Agent(...), not Agent"
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Validate sub-agent is an Agent-like object (has required attributes)
|
|
674
|
+
if not hasattr(sub_agent, "name") or not hasattr(sub_agent, "instruction"):
|
|
675
|
+
raise ValueError(
|
|
676
|
+
f"Sub-agent {type(sub_agent).__name__} is not supported in local mode. "
|
|
677
|
+
"Local mode requires Agent instances with 'name' and 'instruction' attributes. "
|
|
678
|
+
"Define the sub-agent with Agent(name=..., instruction=...)."
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Validate sub-agent is not platform-only (from_id, from_native)
|
|
682
|
+
if getattr(sub_agent, "_lookup_only", False):
|
|
683
|
+
agent_name = getattr(sub_agent, "name", "<unknown>")
|
|
684
|
+
raise ValueError(
|
|
685
|
+
f"Sub-agent '{agent_name}' is not supported in local mode. "
|
|
686
|
+
"Platform agents (from_id, from_native) cannot be used as "
|
|
687
|
+
"sub-agents in local execution. "
|
|
688
|
+
"Define the sub-agent locally with Agent(name=..., instruction=...) instead."
|
|
689
|
+
)
|
|
690
|
+
|
|
582
691
|
def _log_event(self, event: dict[str, Any]) -> None:
|
|
583
692
|
"""Log an A2AEvent for verbose debug output.
|
|
584
693
|
|
|
@@ -9,10 +9,9 @@ Authors:
|
|
|
9
9
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from gllm_core.utils import LoggerManager
|
|
13
|
-
|
|
14
12
|
from glaip_sdk.runner.mcp_adapter.base_mcp_adapter import BaseMCPAdapter
|
|
15
13
|
from glaip_sdk.runner.mcp_adapter.mcp_config_builder import MCPConfigBuilder
|
|
14
|
+
from gllm_core.utils import LoggerManager
|
|
16
15
|
|
|
17
16
|
logger = LoggerManager().get_logger(__name__)
|
|
18
17
|
|
|
@@ -115,17 +114,117 @@ class LangChainMCPAdapter(BaseMCPAdapter):
|
|
|
115
114
|
if "server_url" in config and "url" not in config:
|
|
116
115
|
config["url"] = config.pop("server_url")
|
|
117
116
|
|
|
117
|
+
self._validate_converted_config(
|
|
118
|
+
mcp_name=mcp.name,
|
|
119
|
+
transport=mcp.transport,
|
|
120
|
+
config=config,
|
|
121
|
+
)
|
|
122
|
+
|
|
118
123
|
# Convert authentication to headers using MCPConfigBuilder
|
|
124
|
+
# Merge with existing headers (auth headers take precedence for conflicts)
|
|
119
125
|
if hasattr(mcp, "authentication") and mcp.authentication:
|
|
120
|
-
|
|
121
|
-
if
|
|
122
|
-
config
|
|
126
|
+
auth_headers = MCPConfigBuilder.build_headers_from_auth(mcp.authentication)
|
|
127
|
+
if auth_headers:
|
|
128
|
+
existing_headers = config.get("headers", {})
|
|
129
|
+
config["headers"] = {**existing_headers, **auth_headers}
|
|
123
130
|
else:
|
|
124
131
|
logger.warning("Failed to build headers from authentication for MCP '%s'", mcp.name)
|
|
125
132
|
|
|
126
133
|
logger.debug("Converted MCP '%s' with transport '%s'", mcp.name, mcp.transport)
|
|
127
134
|
return config
|
|
128
135
|
|
|
136
|
+
def _validate_converted_config(self, mcp_name: str, transport: str, config: dict[str, Any]) -> None:
|
|
137
|
+
"""Validate converted MCP config matches aip-agents schema expectations.
|
|
138
|
+
|
|
139
|
+
This method performs transport-specific validation after the glaip-sdk MCP
|
|
140
|
+
has been converted into the `aip-agents` `mcp_config` dictionary.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
mcp_name: The MCP server name.
|
|
144
|
+
transport: The MCP transport type.
|
|
145
|
+
config: The converted MCP configuration dictionary.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ValueError: If the configuration is invalid for the chosen transport.
|
|
149
|
+
"""
|
|
150
|
+
self._validate_transport_config(mcp_name, transport)
|
|
151
|
+
if transport in ("http", "sse"):
|
|
152
|
+
self._validate_http_sse_config(
|
|
153
|
+
mcp_name=mcp_name,
|
|
154
|
+
transport=transport,
|
|
155
|
+
config=config,
|
|
156
|
+
)
|
|
157
|
+
return
|
|
158
|
+
if transport == "stdio":
|
|
159
|
+
self._validate_stdio_config(
|
|
160
|
+
mcp_name=mcp_name,
|
|
161
|
+
config=config,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _validate_transport_config(self, mcp_name: str, transport: str) -> None:
|
|
165
|
+
"""Validate that the MCP transport is supported by local mode.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
mcp_name: The MCP server name.
|
|
169
|
+
transport: The MCP transport type.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ValueError: If the transport is not one of 'http', 'sse', or 'stdio'.
|
|
173
|
+
"""
|
|
174
|
+
if transport not in ("http", "sse", "stdio"):
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"Invalid MCP config for '{mcp_name}': transport must be one of "
|
|
177
|
+
f"'http', 'sse', or 'stdio'. Got: {transport!r}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _validate_http_sse_config(self, mcp_name: str, transport: str, config: dict[str, Any]) -> None:
|
|
181
|
+
"""Validate http/sse config has a usable URL.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
mcp_name: The MCP server name.
|
|
185
|
+
transport: The MCP transport type ('http' or 'sse').
|
|
186
|
+
config: The converted MCP configuration dictionary.
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
ValueError: If url is missing/empty or does not use http(s) scheme.
|
|
190
|
+
"""
|
|
191
|
+
url = config.get("url")
|
|
192
|
+
if not isinstance(url, str) or not url:
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"Invalid MCP config for '{mcp_name}': transport='{transport}' "
|
|
195
|
+
"requires config['url'] as a non-empty string."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if not (url.startswith("http://") or url.startswith("https://")):
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"Invalid MCP config for '{mcp_name}': config['url'] must start with "
|
|
201
|
+
f"'http://' or 'https://'. Got: {url!r}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _validate_stdio_config(self, mcp_name: str, config: dict[str, Any]) -> None:
|
|
205
|
+
"""Validate stdio config has a usable command and optional args list.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
mcp_name: The MCP server name.
|
|
209
|
+
config: The converted MCP configuration dictionary.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If command is missing/empty or args is not a list of strings.
|
|
213
|
+
"""
|
|
214
|
+
command = config.get("command")
|
|
215
|
+
if not isinstance(command, str) or not command:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"Invalid MCP config for '{mcp_name}': transport='stdio' "
|
|
218
|
+
"requires config['command'] as a non-empty string."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
args = config.get("args")
|
|
222
|
+
if args is not None and (not isinstance(args, list) or any(not isinstance(x, str) for x in args)):
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Invalid MCP config for '{mcp_name}': transport='stdio' expects "
|
|
225
|
+
"config['args'] to be a list[str] if provided."
|
|
226
|
+
)
|
|
227
|
+
|
|
129
228
|
def _is_platform_mcp(self, ref: Any) -> bool:
|
|
130
229
|
"""Check if ref is platform-specific (not supported locally)."""
|
|
131
230
|
# MCP.from_native() or MCP.from_id() instances
|
|
@@ -9,12 +9,14 @@ Authors:
|
|
|
9
9
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from gllm_core.utils import LoggerManager
|
|
13
|
-
|
|
14
12
|
from glaip_sdk.runner.tool_adapter.base_tool_adapter import BaseToolAdapter
|
|
13
|
+
from gllm_core.utils import LoggerManager
|
|
15
14
|
|
|
16
15
|
logger = LoggerManager().get_logger(__name__)
|
|
17
16
|
|
|
17
|
+
# Constant for unknown tool name placeholder
|
|
18
|
+
_UNKNOWN_TOOL_NAME = "<unknown>"
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
class LangChainToolAdapter(BaseToolAdapter):
|
|
20
22
|
"""Adapts glaip-sdk tools to LangChain BaseTool format for aip-agents.
|
|
@@ -81,6 +83,15 @@ class LangChainToolAdapter(BaseToolAdapter):
|
|
|
81
83
|
"Local mode only supports LangChain BaseTool classes/instances."
|
|
82
84
|
)
|
|
83
85
|
|
|
86
|
+
def _has_explicit_attr(self, ref: Any, attr: str) -> bool:
|
|
87
|
+
"""Check if attribute is explicitly set on the object.
|
|
88
|
+
|
|
89
|
+
This avoids false positives from objects like MagicMock, where hasattr()
|
|
90
|
+
can return True even if the attribute was never set.
|
|
91
|
+
"""
|
|
92
|
+
ref_dict = getattr(ref, "__dict__", None)
|
|
93
|
+
return isinstance(ref_dict, dict) and attr in ref_dict
|
|
94
|
+
|
|
84
95
|
def _is_tool_wrapper(self, ref: Any) -> bool:
|
|
85
96
|
"""Check if ref is a Tool.from_langchain() wrapper.
|
|
86
97
|
|
|
@@ -90,7 +101,13 @@ class LangChainToolAdapter(BaseToolAdapter):
|
|
|
90
101
|
Returns:
|
|
91
102
|
True if ref is a Tool.from_langchain() wrapper.
|
|
92
103
|
"""
|
|
93
|
-
|
|
104
|
+
if self._has_explicit_attr(ref, "langchain_tool") and hasattr(ref, "id") and hasattr(ref, "name"):
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
if self._has_explicit_attr(ref, "tool_class"):
|
|
108
|
+
return getattr(ref, "tool_class", None) is not None
|
|
109
|
+
|
|
110
|
+
return False
|
|
94
111
|
|
|
95
112
|
def _extract_from_wrapper(self, wrapper: Any) -> Any:
|
|
96
113
|
"""Extract underlying LangChain tool from Tool.from_langchain().
|
|
@@ -100,8 +117,29 @@ class LangChainToolAdapter(BaseToolAdapter):
|
|
|
100
117
|
|
|
101
118
|
Returns:
|
|
102
119
|
LangChain BaseTool instance.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ValueError: If the wrapper's underlying tool is not a valid LangChain tool.
|
|
103
123
|
"""
|
|
104
|
-
langchain_tool = wrapper
|
|
124
|
+
langchain_tool = getattr(wrapper, "langchain_tool", None)
|
|
125
|
+
if langchain_tool is None:
|
|
126
|
+
langchain_tool = getattr(wrapper, "tool_class", None)
|
|
127
|
+
|
|
128
|
+
# Validate the extracted object is a valid LangChain tool
|
|
129
|
+
if langchain_tool is None:
|
|
130
|
+
wrapper_name = getattr(wrapper, "name", _UNKNOWN_TOOL_NAME)
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"Tool wrapper '{wrapper_name}' does not contain a valid LangChain tool. "
|
|
133
|
+
"Ensure Tool.from_langchain() was called with a LangChain BaseTool class or instance."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Validate it's actually a LangChain tool (class or instance)
|
|
137
|
+
if not self._is_langchain_tool(langchain_tool):
|
|
138
|
+
wrapper_name = getattr(wrapper, "name", _UNKNOWN_TOOL_NAME)
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Tool wrapper '{wrapper_name}' contains an invalid tool type: {type(langchain_tool)}. "
|
|
141
|
+
"Expected a LangChain BaseTool class or instance."
|
|
142
|
+
)
|
|
105
143
|
|
|
106
144
|
# If it's a class, instantiate it
|
|
107
145
|
if isinstance(langchain_tool, type):
|
|
@@ -109,7 +147,7 @@ class LangChainToolAdapter(BaseToolAdapter):
|
|
|
109
147
|
|
|
110
148
|
logger.debug(
|
|
111
149
|
"Extracted LangChain tool from wrapper: %s",
|
|
112
|
-
getattr(langchain_tool, "name",
|
|
150
|
+
getattr(langchain_tool, "name", _UNKNOWN_TOOL_NAME),
|
|
113
151
|
)
|
|
114
152
|
return langchain_tool
|
|
115
153
|
|
|
@@ -155,8 +193,10 @@ class LangChainToolAdapter(BaseToolAdapter):
|
|
|
155
193
|
return True
|
|
156
194
|
|
|
157
195
|
# Tool.from_native() instances
|
|
158
|
-
if hasattr(ref, "id") and hasattr(ref, "name") and not
|
|
159
|
-
|
|
196
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not self._has_explicit_attr(ref, "langchain_tool"):
|
|
197
|
+
tool_class = getattr(ref, "tool_class", None) if self._has_explicit_attr(ref, "tool_class") else None
|
|
198
|
+
if tool_class is None:
|
|
199
|
+
return True
|
|
160
200
|
|
|
161
201
|
return False
|
|
162
202
|
|
|
@@ -173,5 +213,7 @@ class LangChainToolAdapter(BaseToolAdapter):
|
|
|
173
213
|
get_local_mode_not_supported_for_tool_message,
|
|
174
214
|
)
|
|
175
215
|
|
|
176
|
-
tool_name = ref if isinstance(ref, str) else getattr(ref, "name",
|
|
216
|
+
tool_name = ref if isinstance(ref, str) else getattr(ref, "name", None)
|
|
217
|
+
if tool_name is None:
|
|
218
|
+
tool_name = getattr(getattr(ref, "tool_class", None), "__name__", _UNKNOWN_TOOL_NAME)
|
|
177
219
|
return get_local_mode_not_supported_for_tool_message(tool_name)
|
|
@@ -315,7 +315,7 @@ def normalize_runtime_config_keys(
|
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def _get_name_from_class(cls: type) -> str:
|
|
318
|
-
"""Extract name from a class, handling Pydantic models.
|
|
318
|
+
"""Extract name from a class, handling Pydantic models and @property descriptors.
|
|
319
319
|
|
|
320
320
|
Args:
|
|
321
321
|
cls: The class to extract name from.
|
|
@@ -323,9 +323,10 @@ def _get_name_from_class(cls: type) -> str:
|
|
|
323
323
|
Returns:
|
|
324
324
|
The resolved name string.
|
|
325
325
|
"""
|
|
326
|
-
# Try class-level name attribute first
|
|
326
|
+
# Try class-level name attribute first, but guard against @property descriptors
|
|
327
|
+
# When a class has @property name, getattr returns the property object, not a string
|
|
327
328
|
class_name = getattr(cls, "name", None)
|
|
328
|
-
if class_name:
|
|
329
|
+
if isinstance(class_name, str) and class_name:
|
|
329
330
|
return class_name
|
|
330
331
|
|
|
331
332
|
# For Pydantic models, check model_fields for default value
|
|
@@ -355,24 +356,26 @@ def get_name_from_key(key: object) -> str | None:
|
|
|
355
356
|
Raises:
|
|
356
357
|
ValueError: If the key cannot be resolved to a valid name.
|
|
357
358
|
"""
|
|
358
|
-
#
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if name:
|
|
362
|
-
return name
|
|
363
|
-
raise ValueError(f"Unable to resolve config key: {key!r}")
|
|
364
|
-
|
|
365
|
-
# Class type (not instance)
|
|
359
|
+
# Class type (not instance) - must check BEFORE hasattr("name")
|
|
360
|
+
# because classes with @property name will have hasattr return True
|
|
361
|
+
# but getattr returns the property descriptor, not a string
|
|
366
362
|
if isinstance(key, type):
|
|
367
363
|
return _get_name_from_class(key)
|
|
368
364
|
|
|
369
|
-
# String key
|
|
365
|
+
# String key - check early to avoid attribute access
|
|
370
366
|
if isinstance(key, str):
|
|
371
367
|
if is_uuid(key):
|
|
372
368
|
logger.warning("UUID '%s' not supported in local mode, skipping", key)
|
|
373
369
|
return None
|
|
374
370
|
return key
|
|
375
371
|
|
|
372
|
+
# Instance with name attribute
|
|
373
|
+
if hasattr(key, "name"):
|
|
374
|
+
name = getattr(key, "name", None)
|
|
375
|
+
# Guard against @property that returns non-string (e.g., descriptor)
|
|
376
|
+
if isinstance(name, str) and name:
|
|
377
|
+
return name
|
|
378
|
+
|
|
376
379
|
raise ValueError(f"Unable to resolve config key: {key!r}")
|
|
377
380
|
|
|
378
381
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: glaip-sdk
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8b1
|
|
4
4
|
Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Raymond Christopher
|
|
@@ -13,9 +13,9 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Provides-Extra: memory
|
|
15
15
|
Provides-Extra: privacy
|
|
16
|
-
Requires-Dist: aip-agents-binary (
|
|
17
|
-
Requires-Dist: aip-agents[memory] (
|
|
18
|
-
Requires-Dist: aip-agents[privacy] (
|
|
16
|
+
Requires-Dist: aip-agents-binary (>=0.5.1)
|
|
17
|
+
Requires-Dist: aip-agents[memory] (>=0.5.1) ; (python_version >= "3.11" and python_version < "3.13") and (extra == "memory")
|
|
18
|
+
Requires-Dist: aip-agents[privacy] (>=0.5.1) ; (python_version >= "3.11" and python_version < "3.13") and (extra == "privacy")
|
|
19
19
|
Requires-Dist: click (>=8.2.0,<8.3.0)
|
|
20
20
|
Requires-Dist: gllm-core-binary (>=0.1.0)
|
|
21
21
|
Requires-Dist: gllm-tools-binary (>=0.1.3)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
glaip_sdk/__init__.py,sha256=0PAFfodqAEdggIiV1Es_JryDokZrhYLFFIXosqdguJU,420
|
|
2
2
|
glaip_sdk/_version.py,sha256=5CHGCxx_36fgmMWuEx6jJ2CzzM-i9eBFyQWFwBi23XE,2259
|
|
3
3
|
glaip_sdk/agents/__init__.py,sha256=VfYov56edbWuySXFEbWJ_jLXgwnFzPk1KB-9-mfsUCc,776
|
|
4
|
-
glaip_sdk/agents/base.py,sha256=
|
|
4
|
+
glaip_sdk/agents/base.py,sha256=5ZX7bVf64AB1DodBriHrzJg5QYvpTai_vYk6Br0JmAU,42338
|
|
5
5
|
glaip_sdk/branding.py,sha256=tLqYCIHMkUf8p2smpuAGNptwaKUN38G4mlh0A0DOl_w,7823
|
|
6
6
|
glaip_sdk/cli/__init__.py,sha256=xCCfuF1Yc7mpCDcfhHZTX0vizvtrDSLeT8MJ3V7m5A0,156
|
|
7
7
|
glaip_sdk/cli/account_store.py,sha256=TK4iTV93Q1uD9mCY_2ZMT6EazHKU2jX0qhgWfEM4V-4,18459
|
|
@@ -11,7 +11,7 @@ glaip_sdk/cli/commands/__init__.py,sha256=6Z3ASXDut0lAbUX_umBFtxPzzFyqoiZfVeTahT
|
|
|
11
11
|
glaip_sdk/cli/commands/accounts.py,sha256=J89chwJWWpEv6TBXaGPUJH-aLrM9Ymxp4jywp5YUUEo,24685
|
|
12
12
|
glaip_sdk/cli/commands/agents.py,sha256=WCOzllyh_Znwlju5camT4vE6OeRJbsAmjWwcyiAqWs4,48429
|
|
13
13
|
glaip_sdk/cli/commands/common_config.py,sha256=chCa0B5t6JER-pGPzItUK7fk_qQgTwf7bIRU004PrUI,3731
|
|
14
|
-
glaip_sdk/cli/commands/configure.py,sha256=
|
|
14
|
+
glaip_sdk/cli/commands/configure.py,sha256=Y3ST1I33rXqlLvUyhKFOl9JUjDe01QCrL1dzOjO1E-c,30304
|
|
15
15
|
glaip_sdk/cli/commands/mcps.py,sha256=tttqQnfM89iI9Pm94u8YRhiHMQNYNouecFX0brsT4cQ,42551
|
|
16
16
|
glaip_sdk/cli/commands/models.py,sha256=vfcGprK5CHprQ0CNpNzQlNNTELvdgKC7JxTG_ijOwmE,2009
|
|
17
17
|
glaip_sdk/cli/commands/tools.py,sha256=_VBqG-vIjnn-gqvDlSTvcU7_F4N3ANGGKEECcQVR-BM,18430
|
|
@@ -37,15 +37,15 @@ glaip_sdk/cli/parsers/json_input.py,sha256=kxoxeIlgfsaH2jhe6apZAgSxAtwlpSINLTMRs
|
|
|
37
37
|
glaip_sdk/cli/resolution.py,sha256=K-VaEHm9SYY_qfb9538VNHykL4_2N6F8iQqI1zMx_64,2402
|
|
38
38
|
glaip_sdk/cli/rich_helpers.py,sha256=kO47N8e506rxrN6Oc9mbAWN3Qb536oQPWZy1s9A616g,819
|
|
39
39
|
glaip_sdk/cli/slash/__init__.py,sha256=J9TPL2UcNTkW8eifG6nRmAEGHhyEgdYMYk4cHaaObC0,386
|
|
40
|
-
glaip_sdk/cli/slash/accounts_controller.py,sha256
|
|
40
|
+
glaip_sdk/cli/slash/accounts_controller.py,sha256=-7v_4nTAVCqXySbOLtTfMpUpsqCzDTWmZYkBU880AzI,24803
|
|
41
41
|
glaip_sdk/cli/slash/accounts_shared.py,sha256=Mq5HxlI0YsVEQ0KKISWvyBZhzOFFWCzwRbhF5xwvUbM,2626
|
|
42
|
-
glaip_sdk/cli/slash/agent_session.py,sha256=
|
|
43
|
-
glaip_sdk/cli/slash/prompt.py,sha256=
|
|
42
|
+
glaip_sdk/cli/slash/agent_session.py,sha256=ZK51zrwhFtun26Lu3a70Kcp3VFh0jwu37crWDKx7Ivk,11377
|
|
43
|
+
glaip_sdk/cli/slash/prompt.py,sha256=q4f1c2zr7ZMUeO6AgOBF2Nz4qgMOXrVPt6WzPRQMbAM,8501
|
|
44
44
|
glaip_sdk/cli/slash/remote_runs_controller.py,sha256=Ok6CezIeF1CPGQ8-QN3TRx5kGGEACOrgyPwH_BRRCyI,21354
|
|
45
|
-
glaip_sdk/cli/slash/session.py,sha256=
|
|
45
|
+
glaip_sdk/cli/slash/session.py,sha256=JieIjUCTMW350LDqdSOdfPP8U0OJSmRYvqPBbddO2bw,64333
|
|
46
46
|
glaip_sdk/cli/slash/tui/__init__.py,sha256=ljBAeAFY2qNDkbJrZh5NgXxjwUlsv9-UxgKNIv0AF1Q,274
|
|
47
47
|
glaip_sdk/cli/slash/tui/accounts.tcss,sha256=xuQjQ0tBM08K1DUv6lI5Sfu1zgZzQxg60c9-RlEWB4s,1160
|
|
48
|
-
glaip_sdk/cli/slash/tui/accounts_app.py,sha256=
|
|
48
|
+
glaip_sdk/cli/slash/tui/accounts_app.py,sha256=QDaOpVStS6Z51tfXcS8GRRjTrVfMO26-guHepqysU9k,33715
|
|
49
49
|
glaip_sdk/cli/slash/tui/background_tasks.py,sha256=SAe1mV2vXB3mJcSGhelU950vf8Lifjhws9iomyIVFKw,2422
|
|
50
50
|
glaip_sdk/cli/slash/tui/loading.py,sha256=nW5pv_Tnl9FUOPR3Qf2O5gt1AGHSo3b5-Uofg34F6AE,1909
|
|
51
51
|
glaip_sdk/cli/slash/tui/remote_runs_app.py,sha256=RCrI-c5ilKV6Iy1lz2Aok9xo2Ou02vqcXACMXTdodnE,24716
|
|
@@ -55,7 +55,7 @@ glaip_sdk/cli/transcript/capture.py,sha256=t8j_62cC6rhb51oCluZd17N04vcXqyjkhPRcR
|
|
|
55
55
|
glaip_sdk/cli/transcript/export.py,sha256=reCvrZVzli8_LzYe5ZNdaa-MwZ1ov2RjnDzKZWr_6-E,1117
|
|
56
56
|
glaip_sdk/cli/transcript/history.py,sha256=2FBjawxP8CX9gRPMUMP8bDjG50BGM2j2zk6IfHvAMH4,26211
|
|
57
57
|
glaip_sdk/cli/transcript/launcher.py,sha256=z5ivkPXDQJpATIqtRLUK8jH3p3WIZ72PvOPqYRDMJvw,2327
|
|
58
|
-
glaip_sdk/cli/transcript/viewer.py,sha256=
|
|
58
|
+
glaip_sdk/cli/transcript/viewer.py,sha256=HKL3U-FrhluKSmxLdE_kTbdTalG-LCE0wu1MXsf22Ao,13189
|
|
59
59
|
glaip_sdk/cli/update_notifier.py,sha256=FnTjzS8YT94RmP6c5aU_XNIyRi7FRHvAskMy-VJikl8,10064
|
|
60
60
|
glaip_sdk/cli/utils.py,sha256=iemmKkpPndoZFBasoVqV7QArplchtr08yYWLA2efMzg,11996
|
|
61
61
|
glaip_sdk/cli/validators.py,sha256=d-kq4y7HWMo6Gc7wLXWUsCt8JwFvJX_roZqRm1Nko1I,5622
|
|
@@ -68,7 +68,7 @@ glaip_sdk/client/main.py,sha256=RTREAOgGouYm4lFKkpNBQ9dmxalnBsIpSSaQLWVFSmU,9054
|
|
|
68
68
|
glaip_sdk/client/mcps.py,sha256=gFRuLOGeh6ieIhR4PeD6yNVT6NhvUMTqPq9iuu1vkAY,13019
|
|
69
69
|
glaip_sdk/client/run_rendering.py,sha256=ubBO-NzyZoYRELNwxVvrQFRGQVJCuLfqqJNiXrBZDoQ,14223
|
|
70
70
|
glaip_sdk/client/shared.py,sha256=esHlsR0LEfL-pFDaWebQjKKOLl09jsRY-2pllBUn4nU,522
|
|
71
|
-
glaip_sdk/client/tools.py,sha256=
|
|
71
|
+
glaip_sdk/client/tools.py,sha256=kK0rBwX1e_5AlGQRjlO6rNz6gDlohhXWdlxN9AwotdE,22585
|
|
72
72
|
glaip_sdk/client/validators.py,sha256=ioF9VCs-LG2yLkaRDd7Hff74lojDZZ0_Q3CiLbdm1RY,8381
|
|
73
73
|
glaip_sdk/config/constants.py,sha256=Y03c6op0e7K0jTQ8bmWXhWAqsnjWxkAhWniq8Z0iEKY,1081
|
|
74
74
|
glaip_sdk/exceptions.py,sha256=iAChFClkytXRBLP0vZq1_YjoZxA9i4m4bW1gDLiGR1g,2321
|
|
@@ -87,19 +87,19 @@ glaip_sdk/registry/__init__.py,sha256=mjvElYE-wwmbriGe-c6qy4on0ccEuWxW_EWWrSbptC
|
|
|
87
87
|
glaip_sdk/registry/agent.py,sha256=F0axW4BIUODqnttIOzxnoS5AqQkLZ1i48FTeZNnYkhA,5203
|
|
88
88
|
glaip_sdk/registry/base.py,sha256=0x2ZBhiERGUcf9mQeWlksSYs5TxDG6FxBYQToYZa5D4,4143
|
|
89
89
|
glaip_sdk/registry/mcp.py,sha256=kNJmiijIbZL9Btx5o2tFtbaT-WG6O4Xf_nl3wz356Ow,7978
|
|
90
|
-
glaip_sdk/registry/tool.py,sha256=
|
|
90
|
+
glaip_sdk/registry/tool.py,sha256=rxrVxnO_VwO6E5kccqxxEUC337J9qbKpje-Gwl5a3sY,7699
|
|
91
91
|
glaip_sdk/rich_components.py,sha256=44Z0V1ZQleVh9gUDGwRR5mriiYFnVGOhm7fFxZYbP8c,4052
|
|
92
92
|
glaip_sdk/runner/__init__.py,sha256=8RrngoGfpF8x9X27RPdX4gJjch75ZvhtVt_6UV0ULLQ,1615
|
|
93
93
|
glaip_sdk/runner/base.py,sha256=KIjcSAyDCP9_mn2H4rXR5gu1FZlwD9pe0gkTBmr6Yi4,2663
|
|
94
94
|
glaip_sdk/runner/deps.py,sha256=3ZDWyvWu4LFJOGHd18tv3VzVo8NY5gb1VeZIelMknyI,3934
|
|
95
|
-
glaip_sdk/runner/langgraph.py,sha256=
|
|
95
|
+
glaip_sdk/runner/langgraph.py,sha256=N9jhuCI-7dS6gzrsKzG6xT3CGWKuo7GEpEwfszgORVo,26050
|
|
96
96
|
glaip_sdk/runner/mcp_adapter/__init__.py,sha256=Rdttfg3N6kg3-DaTCKqaGXKByZyBt0Mwf6FV8s_5kI8,462
|
|
97
97
|
glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py,sha256=ic56fKgb3zgVZZQm3ClWUZi7pE1t4EVq8mOg6AM6hdA,1374
|
|
98
|
-
glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py,sha256=
|
|
98
|
+
glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py,sha256=b58GuadPz7q7aXoJyTYs0eeJ_oqp-wLR1tcr_5cbV1s,9723
|
|
99
99
|
glaip_sdk/runner/mcp_adapter/mcp_config_builder.py,sha256=fQcRaueDuyUzXUSVn9N8QxfaYNIteEO_R_uibx_0Icw,3440
|
|
100
100
|
glaip_sdk/runner/tool_adapter/__init__.py,sha256=scv8sSPxSWjlSNEace03R230YbmWgphLgqINKvDjWmM,480
|
|
101
101
|
glaip_sdk/runner/tool_adapter/base_tool_adapter.py,sha256=nL--eicV0St5_0PZZSEhRurHDZHNwhGN2cKOUh0C5IY,1400
|
|
102
|
-
glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py,sha256=
|
|
102
|
+
glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py,sha256=goSSDOpubuplsKpfemlbesf_bZBdpDKSTqLILvApcjA,7438
|
|
103
103
|
glaip_sdk/tools/__init__.py,sha256=rhGzEqQFCzeMrxmikBuNrMz4PyYczwic28boDKVmoHs,585
|
|
104
104
|
glaip_sdk/tools/base.py,sha256=bvumLJ-DiQTmuYKgq2yCnlwrTZ9nYXpOwWU0e1vWR5g,15185
|
|
105
105
|
glaip_sdk/utils/__init__.py,sha256=ntohV7cxlY2Yksi2nFuFm_Mg2XVJbBbSJVRej7Mi9YE,2770
|
|
@@ -148,12 +148,12 @@ glaip_sdk/utils/rendering/viewer/__init__.py,sha256=XrxmE2cMAozqrzo1jtDFm8HqNtvD
|
|
|
148
148
|
glaip_sdk/utils/rendering/viewer/presenter.py,sha256=mlLMTjnyeyPVtsyrAbz1BJu9lFGQSlS-voZ-_Cuugv0,5725
|
|
149
149
|
glaip_sdk/utils/resource_refs.py,sha256=vF34kyAtFBLnaKnQVrsr2st1JiSxVbIZ4yq0DelJvCI,5966
|
|
150
150
|
glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
|
|
151
|
-
glaip_sdk/utils/runtime_config.py,sha256=
|
|
151
|
+
glaip_sdk/utils/runtime_config.py,sha256=Gl9-CQ4lYZ39vRSgtdfcSU3CXshVDDuTOdSzjvsCgG0,14070
|
|
152
152
|
glaip_sdk/utils/serialization.py,sha256=z-qpvWLSBrGK3wbUclcA1UIKLXJedTnMSwPdq-FF4lo,13308
|
|
153
153
|
glaip_sdk/utils/sync.py,sha256=3VKqs1UfNGWSobgRXohBKP7mMMzdUW3SU0bJQ1uxOgw,4872
|
|
154
154
|
glaip_sdk/utils/tool_detection.py,sha256=g410GNug_PhLye8rd9UU-LVFIKq3jHPbmSItEkLxPTc,807
|
|
155
155
|
glaip_sdk/utils/validation.py,sha256=hB_k3lvHdIFUiSwHStrC0Eqnhx0OG2UvwqASeem0HuQ,6859
|
|
156
|
-
glaip_sdk-0.6.
|
|
157
|
-
glaip_sdk-0.6.
|
|
158
|
-
glaip_sdk-0.6.
|
|
159
|
-
glaip_sdk-0.6.
|
|
156
|
+
glaip_sdk-0.6.8b1.dist-info/METADATA,sha256=mwvQlQevc5j99zjtialgGWO62muor8JP13GA0VwRCt4,7921
|
|
157
|
+
glaip_sdk-0.6.8b1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
158
|
+
glaip_sdk-0.6.8b1.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
|
|
159
|
+
glaip_sdk-0.6.8b1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|