glaip-sdk 0.1.0__py3-none-any.whl → 0.1.1__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.
Files changed (63) hide show
  1. glaip_sdk/_version.py +1 -3
  2. glaip_sdk/branding.py +2 -6
  3. glaip_sdk/cli/agent_config.py +2 -6
  4. glaip_sdk/cli/auth.py +11 -30
  5. glaip_sdk/cli/commands/agents.py +45 -107
  6. glaip_sdk/cli/commands/configure.py +12 -36
  7. glaip_sdk/cli/commands/mcps.py +26 -63
  8. glaip_sdk/cli/commands/models.py +2 -4
  9. glaip_sdk/cli/commands/tools.py +22 -35
  10. glaip_sdk/cli/commands/update.py +3 -8
  11. glaip_sdk/cli/config.py +1 -3
  12. glaip_sdk/cli/display.py +4 -12
  13. glaip_sdk/cli/io.py +8 -14
  14. glaip_sdk/cli/main.py +10 -30
  15. glaip_sdk/cli/mcp_validators.py +5 -15
  16. glaip_sdk/cli/pager.py +3 -9
  17. glaip_sdk/cli/parsers/json_input.py +11 -22
  18. glaip_sdk/cli/resolution.py +3 -9
  19. glaip_sdk/cli/rich_helpers.py +1 -3
  20. glaip_sdk/cli/slash/agent_session.py +5 -10
  21. glaip_sdk/cli/slash/prompt.py +3 -10
  22. glaip_sdk/cli/slash/session.py +46 -95
  23. glaip_sdk/cli/transcript/cache.py +6 -19
  24. glaip_sdk/cli/transcript/capture.py +6 -20
  25. glaip_sdk/cli/transcript/launcher.py +1 -3
  26. glaip_sdk/cli/transcript/viewer.py +11 -40
  27. glaip_sdk/cli/update_notifier.py +165 -21
  28. glaip_sdk/cli/utils.py +33 -84
  29. glaip_sdk/cli/validators.py +11 -12
  30. glaip_sdk/client/_agent_payloads.py +10 -30
  31. glaip_sdk/client/agents.py +33 -63
  32. glaip_sdk/client/base.py +6 -22
  33. glaip_sdk/client/mcps.py +1 -3
  34. glaip_sdk/client/run_rendering.py +6 -14
  35. glaip_sdk/client/tools.py +8 -24
  36. glaip_sdk/client/validators.py +20 -48
  37. glaip_sdk/exceptions.py +1 -3
  38. glaip_sdk/models.py +14 -33
  39. glaip_sdk/payload_schemas/agent.py +1 -3
  40. glaip_sdk/utils/agent_config.py +4 -14
  41. glaip_sdk/utils/client_utils.py +7 -21
  42. glaip_sdk/utils/display.py +2 -6
  43. glaip_sdk/utils/general.py +1 -3
  44. glaip_sdk/utils/import_export.py +3 -9
  45. glaip_sdk/utils/rendering/formatting.py +2 -5
  46. glaip_sdk/utils/rendering/models.py +2 -6
  47. glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
  48. glaip_sdk/utils/rendering/renderer/base.py +63 -189
  49. glaip_sdk/utils/rendering/renderer/debug.py +4 -14
  50. glaip_sdk/utils/rendering/renderer/panels.py +1 -3
  51. glaip_sdk/utils/rendering/renderer/progress.py +3 -11
  52. glaip_sdk/utils/rendering/renderer/stream.py +7 -19
  53. glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
  54. glaip_sdk/utils/rendering/step_tree_state.py +1 -3
  55. glaip_sdk/utils/rendering/steps.py +29 -83
  56. glaip_sdk/utils/resource_refs.py +4 -13
  57. glaip_sdk/utils/serialization.py +14 -46
  58. glaip_sdk/utils/validation.py +4 -4
  59. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/METADATA +1 -1
  60. glaip_sdk-0.1.1.dist-info/RECORD +82 -0
  61. glaip_sdk-0.1.0.dist-info/RECORD +0 -82
  62. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/WHEEL +0 -0
  63. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/entry_points.txt +0 -0
glaip_sdk/models.py CHANGED
@@ -13,6 +13,9 @@ from pydantic import BaseModel
13
13
 
14
14
  from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
15
15
 
16
+ _AGENT_CLIENT_REQUIRED_MSG = "No client available. Use client.get_agent_by_id() to get a client-connected agent."
17
+ _MCP_CLIENT_REQUIRED_MSG = "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
18
+
16
19
 
17
20
  class Agent(BaseModel):
18
21
  """Agent model for API responses."""
@@ -27,9 +30,7 @@ class Agent(BaseModel):
27
30
  tools: list[dict[str, Any]] | None = None # Backend returns ToolReference objects
28
31
  agents: list[dict[str, Any]] | None = None # Backend returns AgentReference objects
29
32
  mcps: list[dict[str, Any]] | None = None # Backend returns MCPReference objects
30
- tool_configs: dict[str, Any] | None = (
31
- None # Backend returns tool configurations keyed by tool UUID
32
- )
33
+ tool_configs: dict[str, Any] | None = None # Backend returns tool configurations keyed by tool UUID
33
34
  agent_config: dict[str, Any] | None = None
34
35
  timeout: int = DEFAULT_AGENT_RUN_TIMEOUT
35
36
  metadata: dict[str, Any] | None = None
@@ -53,9 +54,7 @@ class Agent(BaseModel):
53
54
  **kwargs: Additional arguments passed to run_agent
54
55
  """
55
56
  if not self._client:
56
- raise RuntimeError(
57
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
58
- )
57
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
59
58
  # Automatically pass the agent name for better renderer display
60
59
  kwargs.setdefault("agent_name", self.name)
61
60
  # Pass the agent's configured timeout if not explicitly overridden
@@ -80,9 +79,7 @@ class Agent(BaseModel):
80
79
  Exception: For other unexpected errors
81
80
  """
82
81
  if not self._client:
83
- raise RuntimeError(
84
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
85
- )
82
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
86
83
  # Automatically pass the agent name for better context
87
84
  kwargs.setdefault("agent_name", self.name)
88
85
  # Pass the agent's configured timeout if not explicitly overridden
@@ -95,9 +92,7 @@ class Agent(BaseModel):
95
92
  def update(self, **kwargs) -> "Agent":
96
93
  """Update agent attributes."""
97
94
  if not self._client:
98
- raise RuntimeError(
99
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
100
- )
95
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
101
96
  updated_agent = self._client.update_agent(self.id, **kwargs)
102
97
  # Update current instance with new data
103
98
  for key, value in updated_agent.model_dump().items():
@@ -108,9 +103,7 @@ class Agent(BaseModel):
108
103
  def delete(self) -> None:
109
104
  """Delete the agent."""
110
105
  if not self._client:
111
- raise RuntimeError(
112
- "No client available. Use client.get_agent_by_id() to get a client-connected agent."
113
- )
106
+ raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
114
107
  self._client.delete_agent(self.id)
115
108
 
116
109
 
@@ -149,16 +142,12 @@ class Tool(BaseModel):
149
142
  Pass 'file' parameter to update tool code via file upload.
150
143
  """
151
144
  if not self._client:
152
- raise RuntimeError(
153
- "No client available. Use client.get_tool_by_id() to get a client-connected tool."
154
- )
145
+ raise RuntimeError("No client available. Use client.get_tool_by_id() to get a client-connected tool.")
155
146
 
156
147
  # Check if file upload is requested
157
148
  if "file" in kwargs:
158
149
  file_path = kwargs.pop("file") # Remove file from kwargs for metadata
159
- updated_tool = self._client.tools.update_tool_via_file(
160
- self.id, file_path, **kwargs
161
- )
150
+ updated_tool = self._client.tools.update_tool_via_file(self.id, file_path, **kwargs)
162
151
  else:
163
152
  # Regular metadata update
164
153
  updated_tool = self._client.tools.update_tool(self.id, **kwargs)
@@ -172,9 +161,7 @@ class Tool(BaseModel):
172
161
  def delete(self) -> None:
173
162
  """Delete the tool."""
174
163
  if not self._client:
175
- raise RuntimeError(
176
- "No client available. Use client.get_tool_by_id() to get a client-connected tool."
177
- )
164
+ raise RuntimeError("No client available. Use client.get_tool_by_id() to get a client-connected tool.")
178
165
  self._client.delete_tool(self.id)
179
166
 
180
167
 
@@ -198,9 +185,7 @@ class MCP(BaseModel):
198
185
  def get_tools(self) -> list[dict[str, Any]]:
199
186
  """Get tools available from this MCP."""
200
187
  if not self._client:
201
- raise RuntimeError(
202
- "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
203
- )
188
+ raise RuntimeError(_MCP_CLIENT_REQUIRED_MSG)
204
189
  # This would delegate to the client's MCP tools endpoint
205
190
  # For now, return empty list as placeholder
206
191
  return []
@@ -208,9 +193,7 @@ class MCP(BaseModel):
208
193
  def update(self, **kwargs) -> "MCP":
209
194
  """Update MCP attributes."""
210
195
  if not self._client:
211
- raise RuntimeError(
212
- "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
213
- )
196
+ raise RuntimeError(_MCP_CLIENT_REQUIRED_MSG)
214
197
  updated_mcp = self._client.update_mcp(self.id, **kwargs)
215
198
  # Update current instance with new data
216
199
  for key, value in updated_mcp.model_dump().items():
@@ -221,9 +204,7 @@ class MCP(BaseModel):
221
204
  def delete(self) -> None:
222
205
  """Delete the MCP."""
223
206
  if not self._client:
224
- raise RuntimeError(
225
- "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
226
- )
207
+ raise RuntimeError("No client available. Use client.get_mcp_by_id() to get a client-connected MCP.")
227
208
  self._client.delete_mcp(self.id)
228
209
 
229
210
 
@@ -60,9 +60,7 @@ AGENT_FIELD_RULES: Mapping[str, FieldRule] = {
60
60
  }
61
61
 
62
62
 
63
- def get_import_field_plan(
64
- field_name: str, operation: AgentImportOperation
65
- ) -> ImportFieldPlan:
63
+ def get_import_field_plan(field_name: str, operation: AgentImportOperation) -> ImportFieldPlan:
66
64
  """Return the import handling plan for ``field_name`` under ``operation``.
67
65
 
68
66
  Unknown fields default to being copied as-is so new API fields propagate
@@ -54,17 +54,11 @@ def sanitize_agent_config(
54
54
  cfg = agent_config or {}
55
55
 
56
56
  if strip_lm_identity and isinstance(cfg, dict):
57
- cfg = {
58
- k: v
59
- for k, v in cfg.items()
60
- if k not in {"lm_provider", "lm_name", "lm_base_url"}
61
- }
57
+ cfg = {k: v for k, v in cfg.items() if k not in {"lm_provider", "lm_name", "lm_base_url"}}
62
58
  return cfg
63
59
 
64
60
 
65
- def resolve_language_model_selection(
66
- merged_data: dict[str, Any], cli_model: str | None
67
- ) -> tuple[dict[str, Any], bool]:
61
+ def resolve_language_model_selection(merged_data: dict[str, Any], cli_model: str | None) -> tuple[dict[str, Any], bool]:
68
62
  """Resolve language model selection from merged data and CLI args.
69
63
 
70
64
  Implements the LM selection priority:
@@ -98,17 +92,13 @@ def resolve_language_model_selection(
98
92
  # Priority 3: Legacy lm_name from agent_config
99
93
  agent_config = merged_data.get("agent_config") or {}
100
94
  if isinstance(agent_config, dict) and agent_config.get("lm_name"):
101
- return {
102
- "model": agent_config["lm_name"]
103
- }, True # Strip LM identity when extracting from agent_config
95
+ return {"model": agent_config["lm_name"]}, True # Strip LM identity when extracting from agent_config
104
96
 
105
97
  # No LM selection found
106
98
  return {}, False
107
99
 
108
100
 
109
- def normalize_agent_config_for_import(
110
- agent_data: dict[str, Any], cli_model: str | None = None
111
- ) -> dict[str, Any]:
101
+ def normalize_agent_config_for_import(agent_data: dict[str, Any], cli_model: str | None = None) -> dict[str, Any]:
112
102
  """Automatically normalize agent configuration by extracting LM settings from agent_config.
113
103
 
114
104
  This function addresses the common issue where exported agent configurations contain
@@ -90,9 +90,7 @@ def extract_ids(items: list[str | Any] | None) -> list[str] | None:
90
90
  return result if result else None
91
91
 
92
92
 
93
- def create_model_instances(
94
- data: list[dict] | None, model_class: type, client: Any
95
- ) -> list[Any]:
93
+ def create_model_instances(data: list[dict] | None, model_class: type, client: Any) -> list[Any]:
96
94
  """Create model instances from API data with client association.
97
95
 
98
96
  This is a common pattern used across different clients (agents, tools, mcps)
@@ -112,9 +110,7 @@ def create_model_instances(
112
110
  return [model_class(**item_data)._set_client(client) for item_data in data]
113
111
 
114
112
 
115
- def find_by_name(
116
- items: list[Any], name: str, case_sensitive: bool = False
117
- ) -> list[Any]:
113
+ def find_by_name(items: list[Any], name: str, case_sensitive: bool = False) -> list[Any]:
118
114
  """Filter items by name with optional case sensitivity.
119
115
 
120
116
  This is a common pattern used across different clients for client-side
@@ -234,9 +230,7 @@ def _handle_streaming_error(
234
230
  if isinstance(e, httpx.ReadTimeout):
235
231
  logger.error(f"Read timeout during streaming: {e}")
236
232
  logger.error("This usually indicates the backend is taking too long to respond")
237
- logger.error(
238
- "Consider increasing the timeout value or checking backend performance"
239
- )
233
+ logger.error("Consider increasing the timeout value or checking backend performance")
240
234
  raise AgentTimeoutError(timeout_seconds or 30.0, agent_name)
241
235
 
242
236
  elif isinstance(e, httpx.TimeoutException):
@@ -275,9 +269,7 @@ def _yield_event_data(event_data: dict[str, Any] | None) -> Iterator[dict[str, A
275
269
  yield event_data
276
270
 
277
271
 
278
- def _flush_remaining_buffer(
279
- buf: list[str], event_type: str | None, event_id: str | None
280
- ) -> Iterator[dict[str, Any]]:
272
+ def _flush_remaining_buffer(buf: list[str], event_type: str | None, event_id: str | None) -> Iterator[dict[str, Any]]:
281
273
  """Flush any remaining data in buffer."""
282
274
  if buf:
283
275
  yield {
@@ -317,9 +309,7 @@ def iter_sse_events(
317
309
  if line is None:
318
310
  continue
319
311
 
320
- buf, event_type, event_id, event_data, completed = _process_sse_line(
321
- line, buf, event_type, event_id
322
- )
312
+ buf, event_type, event_id, event_data, completed = _process_sse_line(line, buf, event_type, event_id)
323
313
 
324
314
  yield from _yield_event_data(event_data)
325
315
  if completed:
@@ -385,9 +375,7 @@ def _create_form_data(message: str) -> dict[str, Any]:
385
375
  return {"input": message, "message": message, "stream": True}
386
376
 
387
377
 
388
- def _prepare_file_entry(
389
- item: str | BinaryIO, stack: ExitStack
390
- ) -> tuple[str, tuple[str, BinaryIO, str]]:
378
+ def _prepare_file_entry(item: str | BinaryIO, stack: ExitStack) -> tuple[str, tuple[str, BinaryIO, str]]:
391
379
  """Prepare a single file entry for multipart data."""
392
380
  if isinstance(item, str):
393
381
  return _prepare_path_entry(item, stack)
@@ -395,9 +383,7 @@ def _prepare_file_entry(
395
383
  return _prepare_stream_entry(item)
396
384
 
397
385
 
398
- def _prepare_path_entry(
399
- path_str: str, stack: ExitStack
400
- ) -> tuple[str, tuple[str, BinaryIO, str]]:
386
+ def _prepare_path_entry(path_str: str, stack: ExitStack) -> tuple[str, tuple[str, BinaryIO, str]]:
401
387
  """Prepare a file path entry."""
402
388
  file_path = Path(path_str)
403
389
  if not file_path.exists():
@@ -109,9 +109,7 @@ def print_agent_updated(agent: Any) -> None:
109
109
  """
110
110
  if RICH_AVAILABLE:
111
111
  console = _create_console()
112
- console.print(
113
- f"[{SUCCESS_STYLE}]✅ Agent '{agent.name}' updated successfully[/]"
114
- )
112
+ console.print(f"[{SUCCESS_STYLE}]✅ Agent '{agent.name}' updated successfully[/]")
115
113
  else:
116
114
  print(f"✅ Agent '{agent.name}' updated successfully")
117
115
 
@@ -124,8 +122,6 @@ def print_agent_deleted(agent_id: str) -> None:
124
122
  """
125
123
  if RICH_AVAILABLE:
126
124
  console = _create_console()
127
- console.print(
128
- f"[{SUCCESS_STYLE}]✅ Agent deleted successfully (ID: {agent_id})[/]"
129
- )
125
+ console.print(f"[{SUCCESS_STYLE}]✅ Agent deleted successfully (ID: {agent_id})[/]")
130
126
  else:
131
127
  print(f"✅ Agent deleted successfully (ID: {agent_id})")
@@ -76,9 +76,7 @@ def format_datetime(dt: datetime | str | None) -> str:
76
76
  return str(dt)
77
77
 
78
78
 
79
- def progress_bar(
80
- iterable: Iterable[Any], description: str = "Processing"
81
- ) -> Iterator[Any]:
79
+ def progress_bar(iterable: Iterable[Any], description: str = "Processing") -> Iterator[Any]:
82
80
  """Simple progress bar using click.
83
81
 
84
82
  Args:
@@ -71,14 +71,10 @@ def _get_default_array_fields() -> list[str]:
71
71
 
72
72
  def _should_use_cli_value(cli_value: Any) -> bool:
73
73
  """Check if CLI value should be used."""
74
- return cli_value is not None and (
75
- not isinstance(cli_value, list | tuple) or len(cli_value) > 0
76
- )
74
+ return cli_value is not None and (not isinstance(cli_value, (list, tuple)) or len(cli_value) > 0)
77
75
 
78
76
 
79
- def _handle_array_field_merge(
80
- key: str, cli_value: Any, import_data: dict[str, Any]
81
- ) -> Any:
77
+ def _handle_array_field_merge(key: str, cli_value: Any, import_data: dict[str, Any]) -> Any:
82
78
  """Handle merging of array fields."""
83
79
  import_value = import_data[key]
84
80
  if isinstance(import_value, list):
@@ -107,9 +103,7 @@ def _merge_cli_values_with_import(
107
103
  merged[key] = import_data[key]
108
104
 
109
105
 
110
- def _add_import_only_fields(
111
- merged: dict[str, Any], import_data: dict[str, Any]
112
- ) -> None:
106
+ def _add_import_only_fields(merged: dict[str, Any], import_data: dict[str, Any]) -> None:
113
107
  """Add fields that exist only in import data."""
114
108
  for key, import_value in import_data.items():
115
109
  if key not in merged:
@@ -123,15 +123,12 @@ def _redact_string_content(text: str) -> str:
123
123
  def _is_sensitive_key(key: str) -> bool:
124
124
  """Check if a key contains sensitive information."""
125
125
  key_lower = key.lower()
126
- return any(
127
- sensitive in key_lower
128
- for sensitive in ["password", "secret", "token", "key", "api_key"]
129
- )
126
+ return any(sensitive in key_lower for sensitive in ["password", "secret", "token", "key", "api_key"])
130
127
 
131
128
 
132
129
  def _should_recurse_redaction(value: Any) -> bool:
133
130
  """Check if a value should be recursively processed."""
134
- return isinstance(value, dict | list) or isinstance(value, str)
131
+ return isinstance(value, (dict, list)) or isinstance(value, str)
135
132
 
136
133
 
137
134
  def glyph_for_status(icon_key: str | None) -> str | None:
@@ -48,7 +48,7 @@ class Step:
48
48
  source: Optional duration source tag
49
49
  """
50
50
  self.duration_unknown = False
51
- if isinstance(duration_raw, int | float) and duration_raw > 0:
51
+ if isinstance(duration_raw, (int, float)) and duration_raw > 0:
52
52
  # Use provided duration if it's a positive number (even if very small)
53
53
  self.duration_ms = round(float(duration_raw) * 1000)
54
54
  self.duration_source = source or self.duration_source or "provided"
@@ -81,8 +81,4 @@ class RunStats:
81
81
  Returns:
82
82
  Duration in seconds if run is finished, None otherwise
83
83
  """
84
- return (
85
- None
86
- if self.finished_at is None
87
- else round(self.finished_at - self.started_at, 2)
88
- )
84
+ return None if self.finished_at is None else round(self.finished_at - self.started_at, 2)
@@ -37,9 +37,7 @@ def make_silent_renderer() -> RichStreamRenderer:
37
37
  persist_live=False,
38
38
  render_thinking=False,
39
39
  )
40
- return RichStreamRenderer(
41
- console=Console(file=io.StringIO(), force_terminal=False), cfg=cfg
42
- )
40
+ return RichStreamRenderer(console=Console(file=io.StringIO(), force_terminal=False), cfg=cfg)
43
41
 
44
42
 
45
43
  def make_minimal_renderer() -> RichStreamRenderer: