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.
@@ -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 _assemble_update_data_from_import_payload(import_payload: dict[str, Any] | None) -> dict[str, Any]:
332
- """Assemble update_data dictionary from import payload.
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
- ) -> dict[str, Any]:
358
- """Build update_data dictionary from CLI options, overriding import values.
338
+ ) -> None:
339
+ """Parse and validate config and auth CLI options, updating dict in-place.
359
340
 
360
341
  Args:
361
- update_data: Existing update_data dictionary (from import payload)
362
- name: MCP name option
363
- transport: Transport option
364
- description: Description option
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
- update_data["config"] = validate_mcp_config_structure(
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
- update_data["authentication"] = validate_mcp_auth_structure(parsed_auth, source="--auth")
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(**create_kwargs)
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
- Uses PATCH for import-based updates, PUT/PATCH for CLI-only updates.
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
- # Build update_data from import payload and CLI options
120
- update_data = _assemble_update_data_from_import_payload(import_payload)
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 update_data:
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, update_data, name, transport, description, config, auth
173
+ import_payload, y, mcp, preview_data, name, transport, description, config, auth
131
174
  ):
132
175
  return
133
176
 
134
- # Update MCP
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, **update_data)
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__ = ["RemoteRunsTextualApp", "RemoteRunsTUICallbacks", "run_remote_runs_textual"]
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
- data = {
116
- "name": name,
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
- # Include any other kwargs in the upload data
132
- for key, value in kwargs.items():
133
- if key not in ["tags"]: # tags already handled above
134
- data[key] = value
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 data
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.
@@ -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
- if not isinstance(ref, type):
85
- name = self._get_string_attr(ref, "name")
86
- if name:
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
- if isinstance(ref, type):
91
- name = self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
92
- if name:
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 _resolve_and_cache(self, ref: Any, name: str) -> Tool:
98
- """Resolve tool reference - upload if class, find if string/native.
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
- ref: The tool reference to resolve.
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 glaip_sdk.models.Tool object.
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
- is_tool_instance = False
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
- # Unresolvable Tool instance - neither native nor has tool_class
151
- raise ValueError(
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
- # Already deployed tool (not a ToolClass, but has id/name) - just cache and return
158
- # This handles API response objects and backward compatibility
159
- if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
160
- if ref.id is not None:
161
- logger.debug("Caching already deployed tool: %s", name)
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
- # Tool without ID (backward compatibility) - look up on platform
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
- # Custom tool class - upload it
174
- if self._is_custom_tool(ref):
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._cache[name] = tool
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
- # Dict from API response - use ID directly if available
183
- if isinstance(ref, dict):
184
- tool_id = ref.get("id")
185
- if tool_id:
186
- tool = ToolClass(id=tool_id, name=ref.get("name", ""))
187
- self._cache[name] = tool
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
- # String name - look up on platform (could be native or existing tool)
192
- if isinstance(ref, str):
193
- logger.info("Looking up tool by name: %s", name)
194
- tool = find_tool(name)
195
- if tool:
196
- self._cache[name] = tool
197
- return tool
198
- raise ValueError(f"Tool not found on platform: {name}")
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
- self._cache[name] = ref
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.6.26
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=7gcylRGrFoqex-JhKN4-_sNQr_bY-xwwFuP3UPs5_sw,3020
44
- glaip_sdk/cli/commands/mcps/_common.py,sha256=-D6F_TfEDt_PozhUnwUCK4HjancGVRbI5ZSFAM4HtOM,15577
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=8w_munynS9ShPVF09Zy9Egao4ZijX12y3JrhUObWvf0,4955
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=dzNeb2zjdrEKLblfys8pYENXihANozzypiursjqepU4,4895
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=h00UlpoIBXFUQ_tYaQnMHyWyqd1AMT7xsC7ZgpSJ_pM,7134
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=ljBAeAFY2qNDkbJrZh5NgXxjwUlsv9-UxgKNIv0AF1Q,274
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=PcMy4dwqLDaN62Ej_ZPzhtXuWJryNtLDaku9db5EQ38,23984
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=rLORO-Y5z-zo_qcomF-VKsfZrNKiZ1o36MMg86L1X9w,9885
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.6.26.dist-info/METADATA,sha256=d91d_r-S4PqMnNMU_yAeZhf6VX3TGyIhj01O0cjCF2A,8299
196
- glaip_sdk-0.6.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
197
- glaip_sdk-0.6.26.dist-info/entry_points.txt,sha256=65vNPUggyYnVGhuw7RhNJ8Fp2jygTcX0yxJBcBY3iLU,48
198
- glaip_sdk-0.6.26.dist-info/top_level.txt,sha256=td7yXttiYX2s94-4wFhv-5KdT0rSZ-pnJRSire341hw,10
199
- glaip_sdk-0.6.26.dist-info/RECORD,,
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
- ]