glaip-sdk 0.6.26__py3-none-any.whl → 0.7.0__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/cli/commands/mcps/__init__.py +0 -4
- glaip_sdk/cli/commands/mcps/_common.py +11 -42
- glaip_sdk/cli/commands/mcps/create.py +8 -9
- glaip_sdk/cli/commands/mcps/update.py +56 -12
- glaip_sdk/cli/commands/tools/create.py +2 -2
- glaip_sdk/cli/slash/tui/__init__.py +10 -1
- glaip_sdk/cli/slash/tui/context.py +51 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/client/tools.py +14 -20
- glaip_sdk/registry/tool.py +193 -81
- {glaip_sdk-0.6.26.dist-info → glaip_sdk-0.7.0.dist-info}/METADATA +2 -2
- {glaip_sdk-0.6.26.dist-info → glaip_sdk-0.7.0.dist-info}/RECORD +15 -14
- glaip_sdk/client/_agent_payloads.py +0 -52
- {glaip_sdk-0.6.26.dist-info → glaip_sdk-0.7.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.26.dist-info → glaip_sdk-0.7.0.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.6.26.dist-info → glaip_sdk-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -14,8 +14,6 @@ from glaip_sdk.cli.commands.mcps._common import ( # noqa: E402
|
|
|
14
14
|
console,
|
|
15
15
|
_load_import_ready_payload,
|
|
16
16
|
_merge_import_payload,
|
|
17
|
-
_assemble_update_data_from_import_payload,
|
|
18
|
-
_assemble_update_data_from_cli_options,
|
|
19
17
|
_strip_server_only_fields,
|
|
20
18
|
_coerce_cli_string,
|
|
21
19
|
_collect_cli_overrides,
|
|
@@ -73,8 +71,6 @@ __all__ = [
|
|
|
73
71
|
"console",
|
|
74
72
|
"_load_import_ready_payload",
|
|
75
73
|
"_merge_import_payload",
|
|
76
|
-
"_assemble_update_data_from_import_payload",
|
|
77
|
-
"_assemble_update_data_from_cli_options",
|
|
78
74
|
"_strip_server_only_fields",
|
|
79
75
|
"_coerce_cli_string",
|
|
80
76
|
"_collect_cli_overrides",
|
|
@@ -328,66 +328,35 @@ def _handle_cli_error(ctx: Any, error: Exception, operation: str) -> None:
|
|
|
328
328
|
ctx.exit(1)
|
|
329
329
|
|
|
330
330
|
|
|
331
|
-
def
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
import_payload: Import payload dictionary or None
|
|
336
|
-
|
|
337
|
-
Returns:
|
|
338
|
-
Dictionary with update fields from import payload
|
|
339
|
-
"""
|
|
340
|
-
update_data: dict[str, Any] = {}
|
|
341
|
-
if import_payload:
|
|
342
|
-
for field in ("name", "transport", "description", "config", "authentication"):
|
|
343
|
-
if field in import_payload:
|
|
344
|
-
update_data[field] = import_payload[field]
|
|
345
|
-
return update_data
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
def _assemble_update_data_from_cli_options(
|
|
349
|
-
update_data: dict[str, Any],
|
|
350
|
-
name: str | None,
|
|
351
|
-
transport: str | None,
|
|
352
|
-
description: str | None,
|
|
331
|
+
def _parse_and_validate_config_auth(
|
|
332
|
+
update_dict: dict[str, Any],
|
|
353
333
|
config: str | None,
|
|
354
334
|
auth: str | None,
|
|
335
|
+
transport: str | None,
|
|
355
336
|
import_payload: dict[str, Any] | None,
|
|
356
337
|
mcp: Any,
|
|
357
|
-
) ->
|
|
358
|
-
"""
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Parse and validate config and auth CLI options, updating dict in-place.
|
|
359
340
|
|
|
360
341
|
Args:
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
config: Config option
|
|
366
|
-
auth: Auth option
|
|
342
|
+
update_dict: Dictionary to update with parsed config/auth
|
|
343
|
+
config: Config option string
|
|
344
|
+
auth: Auth option string
|
|
345
|
+
transport: Transport option for config validation
|
|
367
346
|
import_payload: Import payload dictionary or None
|
|
368
347
|
mcp: Current MCP object
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
Updated dictionary with CLI option values
|
|
372
348
|
"""
|
|
373
|
-
if name is not None:
|
|
374
|
-
update_data["name"] = name
|
|
375
|
-
if transport is not None:
|
|
376
|
-
update_data["transport"] = transport
|
|
377
|
-
if description is not None:
|
|
378
|
-
update_data["description"] = description
|
|
379
349
|
if config is not None:
|
|
380
350
|
parsed_config = parse_json_input(config)
|
|
381
351
|
config_transport = _get_config_transport(transport, import_payload, mcp)
|
|
382
|
-
|
|
352
|
+
update_dict["config"] = validate_mcp_config_structure(
|
|
383
353
|
parsed_config,
|
|
384
354
|
transport=config_transport,
|
|
385
355
|
source="--config",
|
|
386
356
|
)
|
|
387
357
|
if auth is not None:
|
|
388
358
|
parsed_auth = parse_json_input(auth)
|
|
389
|
-
|
|
390
|
-
return update_data
|
|
359
|
+
update_dict["authentication"] = validate_mcp_auth_structure(parsed_auth, source="--auth")
|
|
391
360
|
|
|
392
361
|
|
|
393
362
|
def _generate_update_preview(mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]) -> str:
|
|
@@ -111,29 +111,28 @@ def create(
|
|
|
111
111
|
effective_description = merged_payload.get("description")
|
|
112
112
|
effective_config = merged_payload.get("config") or {}
|
|
113
113
|
effective_auth = merged_payload.get("authentication")
|
|
114
|
+
mcp_metadata = merged_payload.get("mcp_metadata")
|
|
114
115
|
|
|
115
116
|
with spinner_context(
|
|
116
117
|
ctx,
|
|
117
118
|
"[bold blue]Creating MCP…[/bold blue]",
|
|
118
119
|
console_override=console,
|
|
119
120
|
):
|
|
121
|
+
# Use SDK client method to create MCP
|
|
120
122
|
create_kwargs: dict[str, Any] = {
|
|
121
|
-
"name": effective_name,
|
|
122
|
-
"config": effective_config,
|
|
123
123
|
"transport": effective_transport,
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
if effective_description is not None:
|
|
127
|
-
create_kwargs["description"] = effective_description
|
|
128
|
-
|
|
129
125
|
if effective_auth:
|
|
130
126
|
create_kwargs["authentication"] = effective_auth
|
|
131
|
-
|
|
132
|
-
mcp_metadata = merged_payload.get("mcp_metadata")
|
|
133
127
|
if mcp_metadata is not None:
|
|
134
128
|
create_kwargs["mcp_metadata"] = mcp_metadata
|
|
135
129
|
|
|
136
|
-
mcp = api_client.mcps.create_mcp(
|
|
130
|
+
mcp = api_client.mcps.create_mcp(
|
|
131
|
+
name=effective_name,
|
|
132
|
+
description=effective_description,
|
|
133
|
+
config=effective_config,
|
|
134
|
+
**create_kwargs,
|
|
135
|
+
)
|
|
137
136
|
|
|
138
137
|
# Handle JSON output
|
|
139
138
|
handle_json_output(ctx, mcp.model_dump())
|
|
@@ -16,11 +16,10 @@ from glaip_sdk.cli.core.context import get_client
|
|
|
16
16
|
from glaip_sdk.cli.core.rendering import spinner_context
|
|
17
17
|
|
|
18
18
|
from ._common import (
|
|
19
|
-
_assemble_update_data_from_cli_options,
|
|
20
|
-
_assemble_update_data_from_import_payload,
|
|
21
19
|
_handle_cli_error,
|
|
22
20
|
_handle_update_preview_and_confirmation,
|
|
23
21
|
_load_import_ready_payload,
|
|
22
|
+
_parse_and_validate_config_auth,
|
|
24
23
|
_resolve_mcp,
|
|
25
24
|
_validate_import_payload_fields,
|
|
26
25
|
_validate_update_inputs,
|
|
@@ -29,6 +28,49 @@ from ._common import (
|
|
|
29
28
|
)
|
|
30
29
|
|
|
31
30
|
|
|
31
|
+
def _merge_update_kwargs(
|
|
32
|
+
import_payload: dict[str, Any] | None,
|
|
33
|
+
name: str | None,
|
|
34
|
+
transport: str | None,
|
|
35
|
+
description: str | None,
|
|
36
|
+
config: str | None,
|
|
37
|
+
auth: str | None,
|
|
38
|
+
mcp: Any,
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""Merge import payload and CLI options into kwargs for SDK builder.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
import_payload: Import payload dictionary or None
|
|
44
|
+
name: MCP name option
|
|
45
|
+
transport: Transport option
|
|
46
|
+
description: Description option
|
|
47
|
+
config: Config option
|
|
48
|
+
auth: Auth option
|
|
49
|
+
mcp: Current MCP object
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dictionary with merged update kwargs
|
|
53
|
+
"""
|
|
54
|
+
update_kwargs: dict[str, Any] = {}
|
|
55
|
+
|
|
56
|
+
# Start with import payload fields
|
|
57
|
+
if import_payload:
|
|
58
|
+
for field in ("name", "transport", "description", "config", "authentication"):
|
|
59
|
+
if field in import_payload:
|
|
60
|
+
update_kwargs[field] = import_payload[field]
|
|
61
|
+
|
|
62
|
+
# Override with CLI options (CLI takes precedence)
|
|
63
|
+
if name is not None:
|
|
64
|
+
update_kwargs["name"] = name
|
|
65
|
+
if transport is not None:
|
|
66
|
+
update_kwargs["transport"] = transport
|
|
67
|
+
if description is not None:
|
|
68
|
+
update_kwargs["description"] = description
|
|
69
|
+
_parse_and_validate_config_auth(update_kwargs, config, auth, transport, import_payload, mcp)
|
|
70
|
+
|
|
71
|
+
return update_kwargs
|
|
72
|
+
|
|
73
|
+
|
|
32
74
|
@mcps_group.command()
|
|
33
75
|
@click.argument("mcp_ref")
|
|
34
76
|
@click.option("--name", help="New MCP name")
|
|
@@ -87,7 +129,8 @@ def update(
|
|
|
87
129
|
Note:
|
|
88
130
|
Must specify either --import OR at least one CLI field.
|
|
89
131
|
CLI options override imported values when both are specified.
|
|
90
|
-
|
|
132
|
+
Method selection (PATCH vs PUT) is handled automatically by the SDK client
|
|
133
|
+
based on the fields provided.
|
|
91
134
|
|
|
92
135
|
\b
|
|
93
136
|
Examples:
|
|
@@ -116,28 +159,29 @@ def update(
|
|
|
116
159
|
if not _validate_import_payload_fields(import_payload):
|
|
117
160
|
return
|
|
118
161
|
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
update_data = _assemble_update_data_from_cli_options(
|
|
122
|
-
update_data, name, transport, description, config, auth, import_payload, mcp
|
|
123
|
-
)
|
|
162
|
+
# Merge import payload and CLI options into kwargs for SDK builder
|
|
163
|
+
update_kwargs = _merge_update_kwargs(import_payload, name, transport, description, config, auth, mcp)
|
|
124
164
|
|
|
125
|
-
if not
|
|
165
|
+
if not update_kwargs:
|
|
126
166
|
raise click.ClickException("No update fields specified")
|
|
127
167
|
|
|
168
|
+
# Build preview data for confirmation (using the same structure as before)
|
|
169
|
+
preview_data = update_kwargs.copy()
|
|
170
|
+
|
|
128
171
|
# Show confirmation preview for import-based updates (unless -y flag)
|
|
129
172
|
if not _handle_update_preview_and_confirmation(
|
|
130
|
-
import_payload, y, mcp,
|
|
173
|
+
import_payload, y, mcp, preview_data, name, transport, description, config, auth
|
|
131
174
|
):
|
|
132
175
|
return
|
|
133
176
|
|
|
134
|
-
#
|
|
177
|
+
# Use SDK client method to update MCP
|
|
178
|
+
# Pass mcp object (not mcp.id) to avoid extra fetch; SDK accepts str | MCP
|
|
135
179
|
with spinner_context(
|
|
136
180
|
ctx,
|
|
137
181
|
"[bold blue]Updating MCP…[/bold blue]",
|
|
138
182
|
console_override=console,
|
|
139
183
|
):
|
|
140
|
-
updated_mcp = client.mcps.update_mcp(mcp, **
|
|
184
|
+
updated_mcp = client.mcps.update_mcp(mcp, **update_kwargs)
|
|
141
185
|
|
|
142
186
|
handle_json_output(ctx, updated_mcp.model_dump())
|
|
143
187
|
handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
|
|
@@ -13,14 +13,14 @@ from typing import Any
|
|
|
13
13
|
import click
|
|
14
14
|
|
|
15
15
|
from glaip_sdk.cli.context import get_ctx_value, output_flags
|
|
16
|
+
from glaip_sdk.cli.core.context import get_client, handle_best_effort_check
|
|
17
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
16
18
|
from glaip_sdk.cli.display import (
|
|
17
19
|
display_api_error,
|
|
18
20
|
display_creation_success,
|
|
19
21
|
handle_json_output,
|
|
20
22
|
handle_rich_output,
|
|
21
23
|
)
|
|
22
|
-
from glaip_sdk.cli.core.context import get_client, handle_best_effort_check
|
|
23
|
-
from glaip_sdk.cli.core.rendering import spinner_context
|
|
24
24
|
from glaip_sdk.cli.io import load_resource_from_file_with_validation as load_resource_from_file
|
|
25
25
|
from glaip_sdk.utils.import_export import merge_import_with_cli_args
|
|
26
26
|
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"""Textual UI helpers for slash commands."""
|
|
2
2
|
|
|
3
|
+
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
3
4
|
from glaip_sdk.cli.slash.tui.remote_runs_app import (
|
|
4
5
|
RemoteRunsTextualApp,
|
|
5
6
|
RemoteRunsTUICallbacks,
|
|
6
7
|
run_remote_runs_textual,
|
|
7
8
|
)
|
|
9
|
+
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities, detect_terminal_background
|
|
8
10
|
|
|
9
|
-
__all__ = [
|
|
11
|
+
__all__ = [
|
|
12
|
+
"TUIContext",
|
|
13
|
+
"TerminalCapabilities",
|
|
14
|
+
"detect_terminal_background",
|
|
15
|
+
"RemoteRunsTextualApp",
|
|
16
|
+
"RemoteRunsTUICallbacks",
|
|
17
|
+
"run_remote_runs_textual",
|
|
18
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Shared context for all TUI components.
|
|
2
|
+
|
|
3
|
+
This module provides the TUIContext dataclass, which serves as the Python equivalent
|
|
4
|
+
of OpenCode's nested provider pattern. It provides a single container for all TUI
|
|
5
|
+
services and state that can be injected into components.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class TUIContext:
|
|
20
|
+
"""Shared context for all TUI components (Python equivalent of OpenCode's providers).
|
|
21
|
+
|
|
22
|
+
This context provides access to all TUI services and state. Components that will
|
|
23
|
+
be implemented in later phases are typed as Optional and will be None initially.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
terminal: Terminal capability detection results.
|
|
27
|
+
keybinds: Central keybind registry (Phase 3).
|
|
28
|
+
theme: Theme manager for light/dark mode and color tokens (Phase 2).
|
|
29
|
+
toasts: Toast notification bus (Phase 4).
|
|
30
|
+
clipboard: Clipboard adapter with OSC 52 support (Phase 4).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
terminal: TerminalCapabilities
|
|
34
|
+
keybinds: object | None = None
|
|
35
|
+
theme: object | None = None
|
|
36
|
+
toasts: object | None = None
|
|
37
|
+
clipboard: object | None = None
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
async def create(cls) -> TUIContext:
|
|
41
|
+
"""Create a TUIContext instance with detected terminal capabilities.
|
|
42
|
+
|
|
43
|
+
This factory method detects terminal capabilities asynchronously and
|
|
44
|
+
returns a populated TUIContext instance. Other components (keybinds,
|
|
45
|
+
theme, toasts, clipboard) will be set incrementally as they are created.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
TUIContext instance with terminal capabilities detected.
|
|
49
|
+
"""
|
|
50
|
+
terminal = await TerminalCapabilities.detect()
|
|
51
|
+
return cls(terminal=terminal)
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""Terminal capability detection for TUI applications.
|
|
2
|
+
|
|
3
|
+
This module provides terminal capability detection including TTY status, ANSI support,
|
|
4
|
+
OSC 52 clipboard support, mouse support, truecolor support, and OSC 11 background
|
|
5
|
+
color detection for automatic theme selection.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import select
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Literal
|
|
21
|
+
|
|
22
|
+
# Windows compatibility: termios and tty may not be available
|
|
23
|
+
try:
|
|
24
|
+
import termios
|
|
25
|
+
import tty
|
|
26
|
+
|
|
27
|
+
_TERMIOS_AVAILABLE = True
|
|
28
|
+
except ImportError: # pragma: no cover
|
|
29
|
+
# Platform-specific: Windows doesn't have termios/tty modules
|
|
30
|
+
# This exception is only raised on Windows or systems without termios support
|
|
31
|
+
# Testing would require complex module reloading and platform-specific test setup
|
|
32
|
+
_TERMIOS_AVAILABLE = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class TerminalCapabilities:
|
|
37
|
+
"""Terminal feature detection results.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
tty: Whether stdout is a TTY.
|
|
41
|
+
ansi: Whether ANSI escape sequences are supported.
|
|
42
|
+
osc52: Whether OSC 52 (clipboard) is supported.
|
|
43
|
+
osc11_bg: Raw RGB color string from OSC 11 query, or None if not detected.
|
|
44
|
+
mouse: Whether mouse support is available.
|
|
45
|
+
truecolor: Whether truecolor (24-bit) color is supported.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
tty: bool
|
|
49
|
+
ansi: bool
|
|
50
|
+
osc52: bool
|
|
51
|
+
osc11_bg: str | None
|
|
52
|
+
mouse: bool
|
|
53
|
+
truecolor: bool
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def background_mode(self) -> Literal["light", "dark"]:
|
|
57
|
+
"""Derive light/dark mode from OSC 11 background color.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
"light" if luminance > 0.5, "dark" otherwise. Defaults to "dark"
|
|
61
|
+
if osc11_bg is None.
|
|
62
|
+
"""
|
|
63
|
+
if self.osc11_bg is None:
|
|
64
|
+
return "dark"
|
|
65
|
+
|
|
66
|
+
rgb = _parse_color_response(self.osc11_bg)
|
|
67
|
+
if rgb is None:
|
|
68
|
+
return "dark"
|
|
69
|
+
|
|
70
|
+
luminance = _calculate_luminance(rgb[0], rgb[1], rgb[2])
|
|
71
|
+
return "light" if luminance > 0.5 else "dark"
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
async def detect(cls) -> TerminalCapabilities:
|
|
75
|
+
"""Detect terminal capabilities asynchronously with fast timeout.
|
|
76
|
+
|
|
77
|
+
This method performs capability detection including OSC 11 background
|
|
78
|
+
color detection with a 100ms timeout. The method completes quickly
|
|
79
|
+
(< 100ms) as required by the roadmap. OSC 11 detection may return None
|
|
80
|
+
if the terminal doesn't respond within the timeout; use
|
|
81
|
+
detect_terminal_background() for full 1-second timeout when needed.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
TerminalCapabilities instance with detected capabilities.
|
|
85
|
+
"""
|
|
86
|
+
tty_available = sys.stdout.isatty()
|
|
87
|
+
term = os.environ.get("TERM", "")
|
|
88
|
+
colorterm = os.environ.get("COLORTERM", "")
|
|
89
|
+
|
|
90
|
+
# Basic capability detection
|
|
91
|
+
ansi = tty_available and term not in ("dumb", "")
|
|
92
|
+
osc52 = _detect_osc52_support()
|
|
93
|
+
mouse = tty_available and term not in ("dumb", "")
|
|
94
|
+
truecolor = colorterm in ("truecolor", "24bit")
|
|
95
|
+
|
|
96
|
+
# OSC 11 detection: use fast path (<100ms timeout)
|
|
97
|
+
osc11_bg: str | None = await _detect_osc11_fast()
|
|
98
|
+
|
|
99
|
+
return cls(
|
|
100
|
+
tty=tty_available,
|
|
101
|
+
ansi=ansi,
|
|
102
|
+
osc52=osc52,
|
|
103
|
+
osc11_bg=osc11_bg,
|
|
104
|
+
mouse=mouse,
|
|
105
|
+
truecolor=truecolor,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def detect_terminal_background() -> str | None:
|
|
110
|
+
"""Detect terminal background color using OSC 11 with full timeout.
|
|
111
|
+
|
|
112
|
+
This function can be called separately to await OSC 11 detection with the
|
|
113
|
+
full 1-second timeout. Useful for theme initialization where a slight delay
|
|
114
|
+
is acceptable.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Raw RGB color string from terminal, or None if detection fails or times out.
|
|
118
|
+
"""
|
|
119
|
+
if not sys.stdout.isatty() or not sys.stdin.isatty():
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
if not _TERMIOS_AVAILABLE:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
return await _detect_osc11_full()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def _detect_osc11_fast() -> str | None:
|
|
129
|
+
"""Fast-path OSC 11 detection (used by detect())."""
|
|
130
|
+
return await _detect_osc11_impl(timeout=0.1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def _detect_osc11_full() -> str | None:
|
|
134
|
+
"""Full-timeout OSC 11 detection (used by detect_terminal_background())."""
|
|
135
|
+
return await _detect_osc11_impl(timeout=1.0)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _read_osc11_char_with_timeout(start_time: float, timeout_seconds: float) -> str | None:
|
|
139
|
+
"""Read a single character from stdin with timeout.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
start_time: Start time for timeout calculation.
|
|
143
|
+
timeout_seconds: Maximum time to wait.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Character read or None on timeout/error.
|
|
147
|
+
"""
|
|
148
|
+
elapsed = time.time() - start_time
|
|
149
|
+
if elapsed >= timeout_seconds:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
remaining = timeout_seconds - elapsed
|
|
154
|
+
ready, _, _ = select.select([sys.stdin], [], [], min(0.1, remaining))
|
|
155
|
+
if not ready:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
char = sys.stdin.read(1)
|
|
159
|
+
return char if char else None
|
|
160
|
+
except (OSError, ValueError):
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _check_osc11_complete(response_text: str, response_length: int) -> str | None:
|
|
165
|
+
"""Check if OSC 11 response is complete.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
response_text: Current response text.
|
|
169
|
+
response_length: Length of response characters.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Matched color string if complete, None otherwise.
|
|
173
|
+
"""
|
|
174
|
+
match = _match_osc11_response(response_text)
|
|
175
|
+
if match:
|
|
176
|
+
return match
|
|
177
|
+
|
|
178
|
+
# If we see BEL (\x07) terminator, check one more time then give up
|
|
179
|
+
if "\x07" in response_text and response_length >= 10:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _read_osc11_response_sync(timeout_seconds: float) -> str | None:
|
|
186
|
+
"""Synchronously read OSC 11 response from stdin.
|
|
187
|
+
|
|
188
|
+
This runs in a thread to avoid blocking the event loop.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
timeout_seconds: Maximum time to wait.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Color string or None.
|
|
195
|
+
"""
|
|
196
|
+
response_chars: list[str] = []
|
|
197
|
+
start_time = time.time()
|
|
198
|
+
max_chars = 200 # Reasonable limit to prevent infinite loops
|
|
199
|
+
|
|
200
|
+
while len(response_chars) < max_chars:
|
|
201
|
+
elapsed = time.time() - start_time
|
|
202
|
+
if elapsed >= timeout_seconds:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
char = _read_osc11_char_with_timeout(start_time, timeout_seconds)
|
|
206
|
+
if char is None:
|
|
207
|
+
# Check timeout again after failed read
|
|
208
|
+
if time.time() - start_time >= timeout_seconds:
|
|
209
|
+
return None
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
response_chars.append(char)
|
|
213
|
+
response_text = "".join(response_chars)
|
|
214
|
+
|
|
215
|
+
result = _check_osc11_complete(response_text, len(response_chars))
|
|
216
|
+
if result is not None:
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def _detect_osc11_impl(timeout: float) -> str | None:
|
|
223
|
+
"""Internal OSC 11 detection implementation.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
timeout: Maximum time to wait for terminal response in seconds.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Raw RGB color string, or None on timeout/error.
|
|
230
|
+
"""
|
|
231
|
+
if not _TERMIOS_AVAILABLE:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
old_settings = None
|
|
235
|
+
try:
|
|
236
|
+
# Save terminal settings
|
|
237
|
+
old_settings = termios.tcgetattr(sys.stdin)
|
|
238
|
+
tty.setraw(sys.stdin.fileno())
|
|
239
|
+
|
|
240
|
+
# Send OSC 11 query
|
|
241
|
+
sys.stdout.write("\x1b]11;?\x07")
|
|
242
|
+
sys.stdout.flush()
|
|
243
|
+
|
|
244
|
+
# Read response in a thread to avoid blocking
|
|
245
|
+
try:
|
|
246
|
+
result = await asyncio.wait_for(asyncio.to_thread(_read_osc11_response_sync, timeout), timeout=timeout)
|
|
247
|
+
return result
|
|
248
|
+
except TimeoutError:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
except Exception:
|
|
252
|
+
return None
|
|
253
|
+
finally:
|
|
254
|
+
# Restore terminal settings
|
|
255
|
+
if old_settings is not None:
|
|
256
|
+
try:
|
|
257
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _match_osc11_response(text: str) -> str | None:
|
|
263
|
+
"""Extract OSC 11 color response from text.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
text: Raw text from stdin.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Color string (e.g., "rgb:RRRR/GGGG/BBBB") or None if not found.
|
|
270
|
+
"""
|
|
271
|
+
# Match OSC 11 response: \x1b]11;...\x07
|
|
272
|
+
match = re.search(r"\x1b\]11;([^\x07\x1b]+)", text)
|
|
273
|
+
if match:
|
|
274
|
+
return match.group(1)
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _parse_color_response(color_str: str) -> tuple[int, int, int] | None:
|
|
279
|
+
"""Parse RGB color from various terminal color formats.
|
|
280
|
+
|
|
281
|
+
Supports:
|
|
282
|
+
- rgb:RRRR/GGGG/BBBB (16-bit per channel)
|
|
283
|
+
- rgb:RR/GG/BB (8-bit per channel)
|
|
284
|
+
- #RRGGBB (hex)
|
|
285
|
+
- rgb(R,G,B) (decimal)
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
color_str: Color string from terminal.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Tuple of (R, G, B) values in 0-255 range, or None if parsing fails.
|
|
292
|
+
"""
|
|
293
|
+
if not color_str:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
if color_str.startswith("rgb:"):
|
|
298
|
+
# Format: rgb:RRRR/GGGG/BBBB (16-bit) or rgb:RR/GG/BB (8-bit)
|
|
299
|
+
parts = color_str[4:].split("/")
|
|
300
|
+
if len(parts) == 3:
|
|
301
|
+
r_val = int(parts[0], 16)
|
|
302
|
+
g_val = int(parts[1], 16)
|
|
303
|
+
b_val = int(parts[2], 16)
|
|
304
|
+
|
|
305
|
+
# Convert 16-bit to 8-bit: if hex string has 4 digits, it's 16-bit
|
|
306
|
+
# and we take the high byte (>> 8). If 2 digits, it's already 8-bit.
|
|
307
|
+
if len(parts[0]) == 4: # 16-bit format
|
|
308
|
+
r_val = r_val >> 8
|
|
309
|
+
if len(parts[1]) == 4: # 16-bit format
|
|
310
|
+
g_val = g_val >> 8
|
|
311
|
+
if len(parts[2]) == 4: # 16-bit format
|
|
312
|
+
b_val = b_val >> 8
|
|
313
|
+
|
|
314
|
+
return (r_val, g_val, b_val)
|
|
315
|
+
|
|
316
|
+
elif color_str.startswith("#"):
|
|
317
|
+
# Format: #RRGGBB
|
|
318
|
+
if len(color_str) == 7:
|
|
319
|
+
r = int(color_str[1:3], 16)
|
|
320
|
+
g = int(color_str[3:5], 16)
|
|
321
|
+
b = int(color_str[5:7], 16)
|
|
322
|
+
return (r, g, b)
|
|
323
|
+
|
|
324
|
+
elif color_str.startswith("rgb("):
|
|
325
|
+
# Format: rgb(R,G,B)
|
|
326
|
+
parts = color_str[4:-1].split(",")
|
|
327
|
+
if len(parts) == 3:
|
|
328
|
+
r = int(parts[0].strip())
|
|
329
|
+
g = int(parts[1].strip())
|
|
330
|
+
b = int(parts[2].strip())
|
|
331
|
+
return (r, g, b)
|
|
332
|
+
|
|
333
|
+
except (ValueError, IndexError):
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _calculate_luminance(r: int, g: int, b: int) -> float:
|
|
340
|
+
"""Calculate relative luminance from RGB values.
|
|
341
|
+
|
|
342
|
+
Uses the relative luminance formula from WCAG:
|
|
343
|
+
L = 0.299*R + 0.587*G + 0.114*B
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
r: Red component (0-255).
|
|
347
|
+
g: Green component (0-255).
|
|
348
|
+
b: Blue component (0-255).
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Luminance value normalized to 0.0-1.0 range.
|
|
352
|
+
"""
|
|
353
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255.0
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _check_terminal_in_env(env_value: str, terminals: list[str]) -> bool:
|
|
357
|
+
"""Check if any terminal name appears in environment value.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
env_value: Environment variable value to check.
|
|
361
|
+
terminals: List of terminal names to search for.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
True if any terminal name is found in env_value.
|
|
365
|
+
"""
|
|
366
|
+
return any(terminal in env_value for terminal in terminals)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _detect_osc52_support() -> bool:
|
|
370
|
+
"""Check if terminal likely supports OSC 52 (clipboard).
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
True if terminal name suggests OSC 52 support.
|
|
374
|
+
"""
|
|
375
|
+
term = os.environ.get("TERM", "").lower()
|
|
376
|
+
term_program = os.environ.get("TERM_PROGRAM", "").lower()
|
|
377
|
+
term_program_version = os.environ.get("TERM_PROGRAM_VERSION", "").lower()
|
|
378
|
+
|
|
379
|
+
# Known terminals that support OSC 52
|
|
380
|
+
osc52_terminals = [
|
|
381
|
+
"iterm",
|
|
382
|
+
"kitty",
|
|
383
|
+
"alacritty",
|
|
384
|
+
"wezterm",
|
|
385
|
+
"vscode",
|
|
386
|
+
"windows terminal",
|
|
387
|
+
"mintty", # Windows terminal emulator
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
# Check TERM_PROGRAM first (most reliable)
|
|
391
|
+
if term_program and _check_terminal_in_env(term_program, osc52_terminals):
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
# Check TERM_PROGRAM_VERSION (VS Code uses this)
|
|
395
|
+
if term_program_version and _check_terminal_in_env(term_program_version, osc52_terminals):
|
|
396
|
+
return True
|
|
397
|
+
|
|
398
|
+
# Check TERM (less reliable but sometimes works)
|
|
399
|
+
if term and _check_terminal_in_env(term, osc52_terminals):
|
|
400
|
+
return True
|
|
401
|
+
|
|
402
|
+
return False
|
glaip_sdk/client/tools.py
CHANGED
|
@@ -103,6 +103,9 @@ class ToolClient(BaseClient):
|
|
|
103
103
|
def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
|
|
104
104
|
"""Prepare upload data dictionary.
|
|
105
105
|
|
|
106
|
+
Uses the same payload building logic as _build_create_payload to ensure
|
|
107
|
+
consistency between upload and metadata-only tool creation.
|
|
108
|
+
|
|
106
109
|
Args:
|
|
107
110
|
name: Tool name
|
|
108
111
|
framework: Tool framework
|
|
@@ -112,28 +115,19 @@ class ToolClient(BaseClient):
|
|
|
112
115
|
Returns:
|
|
113
116
|
dict: Upload data dictionary
|
|
114
117
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"framework": framework,
|
|
118
|
-
"type": kwargs.pop("tool_type", DEFAULT_TOOL_TYPE), # Default to custom
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if description:
|
|
122
|
-
data["description"] = description
|
|
123
|
-
|
|
124
|
-
# Handle tags if provided in kwargs
|
|
125
|
-
if kwargs.get("tags"):
|
|
126
|
-
if isinstance(kwargs["tags"], list):
|
|
127
|
-
data["tags"] = ",".join(kwargs["tags"])
|
|
128
|
-
else:
|
|
129
|
-
data["tags"] = kwargs["tags"]
|
|
118
|
+
# Extract tool_type from kwargs if present, defaulting to DEFAULT_TOOL_TYPE
|
|
119
|
+
tool_type = kwargs.pop("tool_type", DEFAULT_TOOL_TYPE)
|
|
130
120
|
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
121
|
+
# Use _build_create_payload to build the payload consistently
|
|
122
|
+
payload = self._build_create_payload(
|
|
123
|
+
name=name,
|
|
124
|
+
description=description,
|
|
125
|
+
framework=framework,
|
|
126
|
+
tool_type=tool_type,
|
|
127
|
+
**kwargs,
|
|
128
|
+
)
|
|
135
129
|
|
|
136
|
-
return
|
|
130
|
+
return payload
|
|
137
131
|
|
|
138
132
|
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
139
133
|
"""Upload tool file to server.
|
glaip_sdk/registry/tool.py
CHANGED
|
@@ -54,6 +54,32 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
54
54
|
value = getattr(obj, attr, None)
|
|
55
55
|
return value if isinstance(value, str) else None
|
|
56
56
|
|
|
57
|
+
def _extract_name_from_instance(self, ref: Any) -> str | None:
|
|
58
|
+
"""Extract name from a non-type instance.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
ref: The instance to extract name from.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The extracted name, or None if not found.
|
|
65
|
+
"""
|
|
66
|
+
if isinstance(ref, type):
|
|
67
|
+
return None
|
|
68
|
+
return self._get_string_attr(ref, "name")
|
|
69
|
+
|
|
70
|
+
def _extract_name_from_class(self, ref: Any) -> str | None:
|
|
71
|
+
"""Extract name from a class.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
ref: The class to extract name from.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The extracted name, or None if not found.
|
|
78
|
+
"""
|
|
79
|
+
if not isinstance(ref, type):
|
|
80
|
+
return None
|
|
81
|
+
return self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
|
|
82
|
+
|
|
57
83
|
def _extract_name(self, ref: Any) -> str:
|
|
58
84
|
"""Extract tool name from a reference.
|
|
59
85
|
|
|
@@ -81,31 +107,37 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
81
107
|
return ref.get("name") or ref.get("id") or ""
|
|
82
108
|
|
|
83
109
|
# Tool instance (not a class) with name attribute
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return name
|
|
110
|
+
name = self._extract_name_from_instance(ref)
|
|
111
|
+
if name:
|
|
112
|
+
return name
|
|
88
113
|
|
|
89
114
|
# Tool class - try direct attribute first, then model_fields
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return name
|
|
115
|
+
name = self._extract_name_from_class(ref)
|
|
116
|
+
if name:
|
|
117
|
+
return name
|
|
94
118
|
|
|
95
119
|
raise ValueError(f"Cannot extract name from: {ref}")
|
|
96
120
|
|
|
97
|
-
def
|
|
98
|
-
"""
|
|
121
|
+
def _cache_tool(self, tool: Tool, name: str) -> None:
|
|
122
|
+
"""Cache a tool by name and ID if available.
|
|
99
123
|
|
|
100
124
|
Args:
|
|
101
|
-
|
|
125
|
+
tool: The tool to cache.
|
|
126
|
+
name: The tool name.
|
|
127
|
+
"""
|
|
128
|
+
self._cache[name] = tool
|
|
129
|
+
if hasattr(tool, "id") and tool.id:
|
|
130
|
+
self._cache[tool.id] = tool
|
|
131
|
+
|
|
132
|
+
def _resolve_tool_instance(self, ref: Any, name: str) -> Tool | None:
|
|
133
|
+
"""Resolve a ToolClass instance.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
ref: The ToolClass instance to resolve.
|
|
102
137
|
name: The extracted tool name.
|
|
103
138
|
|
|
104
139
|
Returns:
|
|
105
|
-
The resolved
|
|
106
|
-
|
|
107
|
-
Raises:
|
|
108
|
-
ValueError: If the tool cannot be resolved.
|
|
140
|
+
The resolved tool, or None if not a ToolClass instance.
|
|
109
141
|
"""
|
|
110
142
|
# Lazy imports to avoid circular dependency
|
|
111
143
|
from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
|
|
@@ -113,56 +145,23 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
113
145
|
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
114
146
|
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
115
147
|
|
|
116
|
-
# Tool instance from Tool.from_native() or Tool.from_langchain()
|
|
117
148
|
# Use try/except to handle mocked Tool class in tests
|
|
118
149
|
try:
|
|
119
150
|
is_tool_instance = isinstance(ref, ToolClass)
|
|
120
151
|
except TypeError:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if is_tool_instance:
|
|
124
|
-
# If Tool has an ID, it's already deployed - return as-is
|
|
125
|
-
if ref.id is not None:
|
|
126
|
-
logger.debug("Caching already deployed tool: %s", name)
|
|
127
|
-
self._cache[name] = ref
|
|
128
|
-
# Also cache by id for consistency with other resolution branches
|
|
129
|
-
self._cache[ref.id] = ref
|
|
130
|
-
return ref
|
|
131
|
-
|
|
132
|
-
# Tool.from_native() - look up on platform
|
|
133
|
-
if ref.tool_type == ToolType.NATIVE:
|
|
134
|
-
logger.info("Looking up native tool: %s", name)
|
|
135
|
-
tool = find_tool(name)
|
|
136
|
-
if tool:
|
|
137
|
-
self._cache[name] = tool
|
|
138
|
-
return tool
|
|
139
|
-
raise ValueError(f"Native tool not found on platform: {name}")
|
|
140
|
-
|
|
141
|
-
# Tool.from_langchain() - upload the tool_class
|
|
142
|
-
if ref.tool_class is not None:
|
|
143
|
-
logger.info("Uploading custom tool: %s", name)
|
|
144
|
-
tool = update_or_create_tool(ref.tool_class)
|
|
145
|
-
self._cache[name] = tool
|
|
146
|
-
if tool.id:
|
|
147
|
-
self._cache[tool.id] = tool
|
|
148
|
-
return tool
|
|
152
|
+
return None
|
|
149
153
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
f"Cannot resolve Tool instance: {ref}. "
|
|
153
|
-
f"Tool has no id, is not NATIVE type, and has no tool_class. "
|
|
154
|
-
f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
|
|
155
|
-
)
|
|
154
|
+
if not is_tool_instance:
|
|
155
|
+
return None
|
|
156
156
|
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
self._cache[name] = ref
|
|
163
|
-
return ref
|
|
157
|
+
# If Tool has an ID, it's already deployed - return as-is
|
|
158
|
+
if ref.id is not None:
|
|
159
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
160
|
+
self._cache_tool(ref, name)
|
|
161
|
+
return ref
|
|
164
162
|
|
|
165
|
-
|
|
163
|
+
# Tool.from_native() - look up on platform
|
|
164
|
+
if ref.tool_type == ToolType.NATIVE:
|
|
166
165
|
logger.info("Looking up native tool: %s", name)
|
|
167
166
|
tool = find_tool(name)
|
|
168
167
|
if tool:
|
|
@@ -170,32 +169,144 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
170
169
|
return tool
|
|
171
170
|
raise ValueError(f"Native tool not found on platform: {name}")
|
|
172
171
|
|
|
173
|
-
#
|
|
174
|
-
if
|
|
172
|
+
# Tool.from_langchain() - upload the tool_class
|
|
173
|
+
if ref.tool_class is not None:
|
|
175
174
|
logger.info("Uploading custom tool: %s", name)
|
|
176
|
-
tool = update_or_create_tool(ref)
|
|
177
|
-
self.
|
|
178
|
-
if tool.id:
|
|
179
|
-
self._cache[tool.id] = tool
|
|
175
|
+
tool = update_or_create_tool(ref.tool_class)
|
|
176
|
+
self._cache_tool(tool, name)
|
|
180
177
|
return tool
|
|
181
178
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return tool
|
|
189
|
-
raise ValueError(f"Tool dict missing 'id': {ref}")
|
|
179
|
+
# Unresolvable Tool instance - neither native nor has tool_class
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Cannot resolve Tool instance: {ref}. "
|
|
182
|
+
f"Tool has no id, is not NATIVE type, and has no tool_class. "
|
|
183
|
+
f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
|
|
184
|
+
)
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
186
|
+
def _resolve_deployed_tool(self, ref: Any, name: str) -> Tool | None:
|
|
187
|
+
"""Resolve an already deployed tool (has id/name attributes).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
ref: The tool reference to resolve.
|
|
191
|
+
name: The extracted tool name.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The resolved tool, or None if not a deployed tool.
|
|
195
|
+
"""
|
|
196
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
197
|
+
|
|
198
|
+
# Already deployed tool (not a ToolClass, but has id/name)
|
|
199
|
+
# This handles API response objects and backward compatibility
|
|
200
|
+
if not (hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type)):
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
if ref.id is not None:
|
|
204
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
205
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
206
|
+
self._cache_tool(ref, name)
|
|
207
|
+
return ref
|
|
208
|
+
|
|
209
|
+
# Tool without ID (backward compatibility) - look up on platform
|
|
210
|
+
logger.info("Looking up native tool: %s", name)
|
|
211
|
+
tool = find_tool(name)
|
|
212
|
+
if tool:
|
|
213
|
+
# Use _cache_tool to cache by both name and ID if available
|
|
214
|
+
self._cache_tool(tool, name)
|
|
215
|
+
return tool
|
|
216
|
+
raise ValueError(f"Native tool not found on platform: {name}")
|
|
217
|
+
|
|
218
|
+
def _resolve_custom_tool(self, ref: Any, name: str) -> Tool | None:
|
|
219
|
+
"""Resolve a custom tool class.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
ref: The tool reference to resolve.
|
|
223
|
+
name: The extracted tool name.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The resolved tool, or None if not a custom tool.
|
|
227
|
+
"""
|
|
228
|
+
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
229
|
+
|
|
230
|
+
if not self._is_custom_tool(ref):
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
logger.info("Uploading custom tool: %s", name)
|
|
234
|
+
tool = update_or_create_tool(ref)
|
|
235
|
+
self._cache_tool(tool, name)
|
|
236
|
+
return tool
|
|
237
|
+
|
|
238
|
+
def _resolve_dict_tool(self, ref: Any, name: str) -> Tool | None:
|
|
239
|
+
"""Resolve a tool from a dict (API response).
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
ref: The dict to resolve.
|
|
243
|
+
name: The extracted tool name.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
The resolved tool, or None if not a dict.
|
|
247
|
+
"""
|
|
248
|
+
from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
|
|
249
|
+
|
|
250
|
+
if not isinstance(ref, dict):
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
tool_id = ref.get("id")
|
|
254
|
+
if tool_id:
|
|
255
|
+
tool = ToolClass(id=tool_id, name=ref.get("name", ""))
|
|
256
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
257
|
+
self._cache_tool(tool, name)
|
|
258
|
+
return tool
|
|
259
|
+
raise ValueError(f"Tool dict missing 'id': {ref}")
|
|
260
|
+
|
|
261
|
+
def _resolve_string_tool(self, ref: Any, name: str) -> Tool | None:
|
|
262
|
+
"""Resolve a tool from a string name.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
ref: The string to resolve.
|
|
266
|
+
name: The extracted tool name.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The resolved tool, or None if not a string.
|
|
270
|
+
"""
|
|
271
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
272
|
+
|
|
273
|
+
if not isinstance(ref, str):
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
logger.info("Looking up tool by name: %s", name)
|
|
277
|
+
tool = find_tool(name)
|
|
278
|
+
if tool:
|
|
279
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
280
|
+
self._cache_tool(tool, name)
|
|
281
|
+
return tool
|
|
282
|
+
raise ValueError(f"Tool not found on platform: {name}")
|
|
283
|
+
|
|
284
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> Tool:
|
|
285
|
+
"""Resolve tool reference - upload if class, find if string/native.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
ref: The tool reference to resolve.
|
|
289
|
+
name: The extracted tool name.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The resolved glaip_sdk.models.Tool object.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If the tool cannot be resolved.
|
|
296
|
+
"""
|
|
297
|
+
# Try each resolution strategy in order
|
|
298
|
+
resolvers = [
|
|
299
|
+
self._resolve_tool_instance,
|
|
300
|
+
self._resolve_deployed_tool,
|
|
301
|
+
self._resolve_custom_tool,
|
|
302
|
+
self._resolve_dict_tool,
|
|
303
|
+
self._resolve_string_tool,
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
for resolver in resolvers:
|
|
307
|
+
result = resolver(ref, name)
|
|
308
|
+
if result is not None:
|
|
309
|
+
return result
|
|
199
310
|
|
|
200
311
|
raise ValueError(f"Could not resolve tool reference: {ref}")
|
|
201
312
|
|
|
@@ -234,7 +345,8 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
234
345
|
if ref.id is not None:
|
|
235
346
|
name = self._extract_name(ref)
|
|
236
347
|
if name not in self._cache:
|
|
237
|
-
|
|
348
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
349
|
+
self._cache_tool(ref, name)
|
|
238
350
|
return ref
|
|
239
351
|
|
|
240
352
|
# Tool without ID (e.g., from Tool.from_native()) - needs platform lookup
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glaip-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python SDK and CLI for GL AIP (GDP Labs AI Agent Package) - Build, run, and manage AI agents
|
|
5
5
|
Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
|
|
6
6
|
License: MIT
|
|
@@ -144,7 +144,7 @@ print("--- Stream complete ---")
|
|
|
144
144
|
|
|
145
145
|
🎉 **SDK Success!** You're now ready to build AI-powered applications with Python.
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
______________________________________________________________________
|
|
148
148
|
|
|
149
149
|
## 💻 Hello World - CLI
|
|
150
150
|
|
|
@@ -40,20 +40,20 @@ glaip_sdk/cli/commands/agents/list.py,sha256=u4gGYYMLJZatKVtpIovcxqzU8caIyvZCuou
|
|
|
40
40
|
glaip_sdk/cli/commands/agents/run.py,sha256=XtahMOHhh8K3kaUODXGxbuvA4FfcVEO8yBGfCPqP8zY,8187
|
|
41
41
|
glaip_sdk/cli/commands/agents/sync_langflow.py,sha256=NVejCglmKAzy9WUnj_VkutyOl-jF8ro4Rh_JLul3xxs,2329
|
|
42
42
|
glaip_sdk/cli/commands/agents/update.py,sha256=uMX_-DFhOTBS-tboG-JEkGLlf1q-cfj1FGABGIQSh9g,3441
|
|
43
|
-
glaip_sdk/cli/commands/mcps/__init__.py,sha256=
|
|
44
|
-
glaip_sdk/cli/commands/mcps/_common.py,sha256
|
|
43
|
+
glaip_sdk/cli/commands/mcps/__init__.py,sha256=QRCdjBQlYc0ocXazbvqA0xA32FnrJF0XvErdb5OwVTE,2834
|
|
44
|
+
glaip_sdk/cli/commands/mcps/_common.py,sha256=YOSOijID1s8UMIm98K6fyXrp1jkHv2ovWS1x9ipFcP0,14578
|
|
45
45
|
glaip_sdk/cli/commands/mcps/connect.py,sha256=dxz4Y43boZivRGwe5jWM5KwwUNNqiZE6HLKb_BZWgD8,2416
|
|
46
|
-
glaip_sdk/cli/commands/mcps/create.py,sha256=
|
|
46
|
+
glaip_sdk/cli/commands/mcps/create.py,sha256=QryzfgVeI8XPJRdY2FWnUYWktSBTrwlfJqtoZ5CphNU,4955
|
|
47
47
|
glaip_sdk/cli/commands/mcps/delete.py,sha256=yIEFuzY6DswVblTdEql3k6b6JSNstNqIHg0vZqazTXc,1945
|
|
48
48
|
glaip_sdk/cli/commands/mcps/get.py,sha256=XQns1wfydmN-7fiNGzlXLWTktLr4pwgW1jhoHVf9NYM,7072
|
|
49
49
|
glaip_sdk/cli/commands/mcps/list.py,sha256=e0qtTtkmOsZVsBNu_ytfyFPV0eDtdlVrUfTfcoI8Ivk,2051
|
|
50
50
|
glaip_sdk/cli/commands/mcps/tools.py,sha256=iMi2mfwQS-lE4yhhHRiBrVeK6qG-IfVKGyV1P4stZVs,7089
|
|
51
|
-
glaip_sdk/cli/commands/mcps/update.py,sha256=
|
|
51
|
+
glaip_sdk/cli/commands/mcps/update.py,sha256=7a8b77nDdSRz1jwh-0RoSNbcKw6K6XuZsl2qSC1AnS0,6330
|
|
52
52
|
glaip_sdk/cli/commands/shared/__init__.py,sha256=LA1GQMwBSNpeSHifPOJ9V4VjOuGAlVOyD1MIQO1z1ms,465
|
|
53
53
|
glaip_sdk/cli/commands/shared/formatters.py,sha256=QWjVTihmQV7O6MjMI_8tnTycu0rgGHKF5vMh_FanZMg,2499
|
|
54
54
|
glaip_sdk/cli/commands/tools/__init__.py,sha256=KkcMYJNe164V25Eqp2Bygwf49LIcyECm3r5k59p6cQU,2111
|
|
55
55
|
glaip_sdk/cli/commands/tools/_common.py,sha256=IFJEoyP-lphu0X3eR6txr4QD8Qr1g-AP1kLtahZ29Fo,2190
|
|
56
|
-
glaip_sdk/cli/commands/tools/create.py,sha256=
|
|
56
|
+
glaip_sdk/cli/commands/tools/create.py,sha256=X0xSKG9MyuZC_ZdSGHX2RIk7xGvlfNzgT1WSobMA-Es,7134
|
|
57
57
|
glaip_sdk/cli/commands/tools/delete.py,sha256=lSACJivmpT4Z7KVWOYVErdcWb487UpnlBpjCrlMI_lM,1696
|
|
58
58
|
glaip_sdk/cli/commands/tools/get.py,sha256=VBexy7ZJI418OCYBGQhn5vUO9r22kTctGrTih78qDa8,3530
|
|
59
59
|
glaip_sdk/cli/commands/tools/list.py,sha256=cHHc5pj-NWJaGXpAgdbtuA1gOrqjecUk83eUOuFrp78,1991
|
|
@@ -77,12 +77,14 @@ glaip_sdk/cli/slash/agent_session.py,sha256=tuVOme-NbEyr6rwJvsBEKZYWQmsaRf4piJeR
|
|
|
77
77
|
glaip_sdk/cli/slash/prompt.py,sha256=q4f1c2zr7ZMUeO6AgOBF2Nz4qgMOXrVPt6WzPRQMbAM,8501
|
|
78
78
|
glaip_sdk/cli/slash/remote_runs_controller.py,sha256=a5X5rYgb9l6dHhvTewRUCj-hAo7mKRnuM_MwGvxs8jI,21363
|
|
79
79
|
glaip_sdk/cli/slash/session.py,sha256=f6yetP4ih_x7MZCbv4sVQfqHH7JJYgxLY0Q6VHHWTes,64423
|
|
80
|
-
glaip_sdk/cli/slash/tui/__init__.py,sha256=
|
|
80
|
+
glaip_sdk/cli/slash/tui/__init__.py,sha256=78O54cB5v0nt6vlC1hoXr8YDmST2RjZkq8PUeYratcE,518
|
|
81
81
|
glaip_sdk/cli/slash/tui/accounts.tcss,sha256=xuQjQ0tBM08K1DUv6lI5Sfu1zgZzQxg60c9-RlEWB4s,1160
|
|
82
82
|
glaip_sdk/cli/slash/tui/accounts_app.py,sha256=QDaOpVStS6Z51tfXcS8GRRjTrVfMO26-guHepqysU9k,33715
|
|
83
83
|
glaip_sdk/cli/slash/tui/background_tasks.py,sha256=SAe1mV2vXB3mJcSGhelU950vf8Lifjhws9iomyIVFKw,2422
|
|
84
|
+
glaip_sdk/cli/slash/tui/context.py,sha256=oEw4P0ym77uPI0cbHiBam4xpSL2TT0OPvqpBo8gLR30,1835
|
|
84
85
|
glaip_sdk/cli/slash/tui/loading.py,sha256=nW5pv_Tnl9FUOPR3Qf2O5gt1AGHSo3b5-Uofg34F6AE,1909
|
|
85
86
|
glaip_sdk/cli/slash/tui/remote_runs_app.py,sha256=RCrI-c5ilKV6Iy1lz2Aok9xo2Ou02vqcXACMXTdodnE,24716
|
|
87
|
+
glaip_sdk/cli/slash/tui/terminal.py,sha256=SnMOuA6uGeQKA3sGEEM3yTm8j_1LzpYmqsJKyGEKWcg,12110
|
|
86
88
|
glaip_sdk/cli/transcript/__init__.py,sha256=yiYHyNtebMCu3BXu56Xm5RBC2tDc865q8UGPnoe6QRs,920
|
|
87
89
|
glaip_sdk/cli/transcript/cache.py,sha256=Wi1uln6HP1U6F-MRTrfnxi9bn6XJTxwWXhREIRPoMqQ,17439
|
|
88
90
|
glaip_sdk/cli/transcript/capture.py,sha256=t8j_62cC6rhb51oCluZd17N04vcXqyjkhPRcRd3ZcmM,10291
|
|
@@ -91,7 +93,6 @@ glaip_sdk/cli/transcript/history.py,sha256=IAUaY41QCr9jKgQ1t8spDJiO3Me5r1vAoTX47
|
|
|
91
93
|
glaip_sdk/cli/transcript/launcher.py,sha256=z5ivkPXDQJpATIqtRLUK8jH3p3WIZ72PvOPqYRDMJvw,2327
|
|
92
94
|
glaip_sdk/cli/transcript/viewer.py,sha256=Y4G40WR6v1g4TfxRbGSZqdrqhLcqBxoWkQgToQoGGxM,13198
|
|
93
95
|
glaip_sdk/client/__init__.py,sha256=s2REOumgE8Z8lA9dWJpwXqpgMdzSELSuCQkZ7sYngX0,381
|
|
94
|
-
glaip_sdk/client/_agent_payloads.py,sha256=ElpukrTQo2mUrtQ5TrIPIxuoAHnk8NXYaRP7s7dUpGg,1423
|
|
95
96
|
glaip_sdk/client/_schedule_payloads.py,sha256=9BXa75CCx3clsKgwmG9AWyvhPY6kVwzQtoLvTTw40CQ,2759
|
|
96
97
|
glaip_sdk/client/agent_runs.py,sha256=tZSFEZZ3Yx0uYRgnwkLe-X0TlmgKJQ-ivzb6SrVnxY8,4862
|
|
97
98
|
glaip_sdk/client/agents.py,sha256=jR6DmpW1XRvpa_ewtXfzOH-q2ipTIF--yHZDdasq5Mk,48249
|
|
@@ -101,7 +102,7 @@ glaip_sdk/client/mcps.py,sha256=-JdaIkg0QE3egJ8p93eoOPULup8KbM2WRCcwlvqlqrA,1449
|
|
|
101
102
|
glaip_sdk/client/run_rendering.py,sha256=kERp78v50jojsNWHrjNEkbC8sgOpMacaqUdw5YZuK6A,26074
|
|
102
103
|
glaip_sdk/client/schedules.py,sha256=ZfPzCYzk4YRuPkjkTTgLe5Rqa07mi-h2WmP4H91mMZ0,14113
|
|
103
104
|
glaip_sdk/client/shared.py,sha256=esHlsR0LEfL-pFDaWebQjKKOLl09jsRY-2pllBUn4nU,522
|
|
104
|
-
glaip_sdk/client/tools.py,sha256=
|
|
105
|
+
glaip_sdk/client/tools.py,sha256=NzQTIsn-bjYN9EfGWCBqqawCIVs7auaccFv7BM_3oCc,23871
|
|
105
106
|
glaip_sdk/client/validators.py,sha256=ioF9VCs-LG2yLkaRDd7Hff74lojDZZ0_Q3CiLbdm1RY,8381
|
|
106
107
|
glaip_sdk/client/payloads/agent/__init__.py,sha256=gItEH2zt2secVq6n60oGA-ztdE5mc0GLECn-QMX47ew,558
|
|
107
108
|
glaip_sdk/client/payloads/agent/requests.py,sha256=5FuGEuypaEXlWBhB07JrDca_ecLg4bvo8mjyFBxAV9U,17139
|
|
@@ -124,7 +125,7 @@ glaip_sdk/registry/__init__.py,sha256=mjvElYE-wwmbriGe-c6qy4on0ccEuWxW_EWWrSbptC
|
|
|
124
125
|
glaip_sdk/registry/agent.py,sha256=F0axW4BIUODqnttIOzxnoS5AqQkLZ1i48FTeZNnYkhA,5203
|
|
125
126
|
glaip_sdk/registry/base.py,sha256=0x2ZBhiERGUcf9mQeWlksSYs5TxDG6FxBYQToYZa5D4,4143
|
|
126
127
|
glaip_sdk/registry/mcp.py,sha256=kNJmiijIbZL9Btx5o2tFtbaT-WG6O4Xf_nl3wz356Ow,7978
|
|
127
|
-
glaip_sdk/registry/tool.py,sha256=
|
|
128
|
+
glaip_sdk/registry/tool.py,sha256=QnbAlk09lYvEb9PEdCsvpg4CGxlLbvvFWBS8WkM1ZoM,12955
|
|
128
129
|
glaip_sdk/runner/__init__.py,sha256=8RrngoGfpF8x9X27RPdX4gJjch75ZvhtVt_6UV0ULLQ,1615
|
|
129
130
|
glaip_sdk/runner/base.py,sha256=KIjcSAyDCP9_mn2H4rXR5gu1FZlwD9pe0gkTBmr6Yi4,2663
|
|
130
131
|
glaip_sdk/runner/deps.py,sha256=Du3hr2R5RHOYCRAv7RVmx661x-ayVXIeZ8JD7ODirTA,3884
|
|
@@ -192,8 +193,8 @@ glaip_sdk/utils/rendering/steps/format.py,sha256=Chnq7OBaj8XMeBntSBxrX5zSmrYeGcO
|
|
|
192
193
|
glaip_sdk/utils/rendering/steps/manager.py,sha256=BiBmTeQMQhjRMykgICXsXNYh1hGsss-fH9BIGVMWFi0,13194
|
|
193
194
|
glaip_sdk/utils/rendering/viewer/__init__.py,sha256=XrxmE2cMAozqrzo1jtDFm8HqNtvDcYi2mAhXLXn5CjI,457
|
|
194
195
|
glaip_sdk/utils/rendering/viewer/presenter.py,sha256=mlLMTjnyeyPVtsyrAbz1BJu9lFGQSlS-voZ-_Cuugv0,5725
|
|
195
|
-
glaip_sdk-0.
|
|
196
|
-
glaip_sdk-0.
|
|
197
|
-
glaip_sdk-0.
|
|
198
|
-
glaip_sdk-0.
|
|
199
|
-
glaip_sdk-0.
|
|
196
|
+
glaip_sdk-0.7.0.dist-info/METADATA,sha256=o7SZ0YWA7OLjK7DsXMcecJJZ0TZFtEjglxJNmdajLpk,8365
|
|
197
|
+
glaip_sdk-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
198
|
+
glaip_sdk-0.7.0.dist-info/entry_points.txt,sha256=65vNPUggyYnVGhuw7RhNJ8Fp2jygTcX0yxJBcBY3iLU,48
|
|
199
|
+
glaip_sdk-0.7.0.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
|
|
200
|
+
glaip_sdk-0.7.0.dist-info/RECORD,,
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"""Backward-compatible shim for agent payloads.
|
|
2
|
-
|
|
3
|
-
This module provides backward compatibility for imports from glaip_sdk.client._agent_payloads.
|
|
4
|
-
New code should import from glaip_sdk.client.payloads.agent package directly.
|
|
5
|
-
|
|
6
|
-
This file contains code that is duplicated in glaip_sdk.client.payloads.agent.__init__
|
|
7
|
-
for backward compatibility. The duplication is intentional.
|
|
8
|
-
|
|
9
|
-
Authors:
|
|
10
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
# pylint: disable=duplicate-code
|
|
14
|
-
import warnings
|
|
15
|
-
|
|
16
|
-
_warned = False
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _warn_deprecated_import() -> None:
|
|
20
|
-
"""Emit deprecation warning for importing from _agent_payloads.py shim."""
|
|
21
|
-
global _warned
|
|
22
|
-
if not _warned:
|
|
23
|
-
warnings.warn(
|
|
24
|
-
"Importing from glaip_sdk.client._agent_payloads is deprecated. "
|
|
25
|
-
"Import from glaip_sdk.client.payloads.agent package directly instead.",
|
|
26
|
-
DeprecationWarning,
|
|
27
|
-
stacklevel=3,
|
|
28
|
-
)
|
|
29
|
-
_warned = True
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Import from new package structure
|
|
33
|
-
from glaip_sdk.client.payloads.agent import ( # noqa: E402
|
|
34
|
-
AgentCreateRequest,
|
|
35
|
-
AgentListParams,
|
|
36
|
-
AgentListResult,
|
|
37
|
-
AgentUpdateRequest,
|
|
38
|
-
merge_payload_fields,
|
|
39
|
-
resolve_language_model_fields,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
_warn_deprecated_import()
|
|
43
|
-
|
|
44
|
-
# Re-export everything
|
|
45
|
-
__all__ = [
|
|
46
|
-
"AgentCreateRequest",
|
|
47
|
-
"AgentListParams",
|
|
48
|
-
"AgentListResult",
|
|
49
|
-
"AgentUpdateRequest",
|
|
50
|
-
"merge_payload_fields",
|
|
51
|
-
"resolve_language_model_fields",
|
|
52
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|