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
@@ -48,15 +48,11 @@ def format_working_indicator(
48
48
  """Format a working indicator with elapsed time."""
49
49
  base_message = "Working..."
50
50
 
51
- if started_at is None and (
52
- server_elapsed_time is None or streaming_started_at is None
53
- ):
51
+ if started_at is None and (server_elapsed_time is None or streaming_started_at is None):
54
52
  return base_message
55
53
 
56
54
  spinner_chip = f"{get_spinner_char()} {base_message}"
57
- elapsed = _resolve_elapsed_time(
58
- started_at, server_elapsed_time, streaming_started_at
59
- )
55
+ elapsed = _resolve_elapsed_time(started_at, server_elapsed_time, streaming_started_at)
60
56
  if elapsed is None:
61
57
  return spinner_chip
62
58
 
@@ -93,11 +89,7 @@ def is_delegation_tool(tool_name: str) -> bool:
93
89
  Returns:
94
90
  True if this is a delegation tool
95
91
  """
96
- return (
97
- tool_name.startswith("delegate_to_")
98
- or tool_name.startswith("delegate_")
99
- or "sub_agent" in tool_name.lower()
100
- )
92
+ return tool_name.startswith("delegate_to_") or tool_name.startswith("delegate_") or "sub_agent" in tool_name.lower()
101
93
 
102
94
 
103
95
  def _delegation_tool_title(tool_name: str) -> str | None:
@@ -42,7 +42,7 @@ class StreamProcessor:
42
42
  # Update server elapsed timing if backend provides it
43
43
  try:
44
44
  t = metadata.get("time")
45
- if isinstance(t, int | float):
45
+ if isinstance(t, (int, float)):
46
46
  self.server_elapsed_time = float(t)
47
47
  except Exception:
48
48
  pass
@@ -56,9 +56,7 @@ class StreamProcessor:
56
56
  "metadata": metadata,
57
57
  }
58
58
 
59
- def _extract_metadata_tool_calls(
60
- self, metadata: dict[str, Any]
61
- ) -> tuple[str | None, dict, Any, list]:
59
+ def _extract_metadata_tool_calls(self, metadata: dict[str, Any]) -> tuple[str | None, dict, Any, list]:
62
60
  """Extract tool calls from metadata."""
63
61
  tool_calls = metadata.get("tool_calls", [])
64
62
  if not tool_calls:
@@ -84,9 +82,7 @@ class StreamProcessor:
84
82
 
85
83
  return tool_name, tool_args, tool_out, tool_calls_info
86
84
 
87
- def _extract_tool_info_calls(
88
- self, tool_info: dict[str, Any]
89
- ) -> tuple[str | None, dict, Any, list]:
85
+ def _extract_tool_info_calls(self, tool_info: dict[str, Any]) -> tuple[str | None, dict, Any, list]:
90
86
  """Extract tool calls from tool_info structure."""
91
87
  tool_calls_info = []
92
88
  tool_name = None
@@ -98,9 +94,7 @@ class StreamProcessor:
98
94
  if isinstance(ti_calls, list) and ti_calls:
99
95
  for call in ti_calls:
100
96
  if isinstance(call, dict) and call.get("name"):
101
- tool_calls_info.append(
102
- (call.get("name"), call.get("args", {}), call.get("output"))
103
- )
97
+ tool_calls_info.append((call.get("name"), call.get("args", {}), call.get("output")))
104
98
  if tool_calls_info:
105
99
  tool_name, tool_args, tool_out = tool_calls_info[0]
106
100
  return tool_name, tool_args, tool_out, tool_calls_info
@@ -114,9 +108,7 @@ class StreamProcessor:
114
108
 
115
109
  return tool_name, tool_args, tool_out, tool_calls_info
116
110
 
117
- def _extract_tool_calls_from_metadata(
118
- self, metadata: dict[str, Any]
119
- ) -> tuple[str | None, dict, Any, list]:
111
+ def _extract_tool_calls_from_metadata(self, metadata: dict[str, Any]) -> tuple[str | None, dict, Any, list]:
120
112
  """Extract tool calls from metadata structure."""
121
113
  tool_info = metadata.get("tool_info", {}) or {}
122
114
 
@@ -125,9 +117,7 @@ class StreamProcessor:
125
117
 
126
118
  return None, {}, None, []
127
119
 
128
- def parse_tool_calls(
129
- self, event: dict[str, Any]
130
- ) -> tuple[str | None, Any, Any, list[tuple[str, Any, Any]]]:
120
+ def parse_tool_calls(self, event: dict[str, Any]) -> tuple[str | None, Any, Any, list[tuple[str, Any, Any]]]:
131
121
  """Parse tool call information from an event.
132
122
 
133
123
  Args:
@@ -166,9 +156,7 @@ class StreamProcessor:
166
156
  if context_id:
167
157
  self.last_event_time_by_ctx[context_id] = monotonic()
168
158
 
169
- def should_insert_thinking_gap(
170
- self, task_id: str | None, context_id: str | None, think_threshold: float
171
- ) -> bool:
159
+ def should_insert_thinking_gap(self, task_id: str | None, context_id: str | None, think_threshold: float) -> bool:
172
160
  """Determine if a thinking gap should be inserted.
173
161
 
174
162
  Args:
@@ -159,9 +159,7 @@ class TranscriptToggleController:
159
159
  return
160
160
 
161
161
  self._stop_event.clear()
162
- self._poll_thread = threading.Thread(
163
- target=self._poll_loop, args=(renderer,), daemon=True
164
- )
162
+ self._poll_thread = threading.Thread(target=self._poll_loop, args=(renderer,), daemon=True)
165
163
  self._poll_thread.start()
166
164
 
167
165
  def _stop_polling_thread(self) -> None:
@@ -20,9 +20,7 @@ class StepTreeState:
20
20
  root_order: list[str] = field(default_factory=list)
21
21
  child_map: dict[str, list[str]] = field(default_factory=dict)
22
22
  buffered_children: dict[str, list[str]] = field(default_factory=dict)
23
- running_by_context: dict[tuple[str | None, str | None], set[str]] = field(
24
- default_factory=dict
25
- )
23
+ running_by_context: dict[tuple[str | None, str | None], set[str]] = field(default_factory=dict)
26
24
  retained_ids: set[str] = field(default_factory=set)
27
25
  step_index: dict[str, Step] = field(default_factory=dict)
28
26
  pending_branch_failures: set[str] = field(default_factory=set)
@@ -104,9 +104,7 @@ class StepManager:
104
104
  Returns:
105
105
  The Step instance (new or existing)
106
106
  """
107
- existing = self.find_running(
108
- task_id=task_id, context_id=context_id, kind=kind, name=name
109
- )
107
+ existing = self.find_running(task_id=task_id, context_id=context_id, kind=kind, name=name)
110
108
  if existing:
111
109
  if args and existing.args != args:
112
110
  existing.args = args
@@ -228,18 +226,12 @@ class StepManager:
228
226
  self.state.pending_branch_failures.discard(step_id)
229
227
  self.state.discard_running(step_id)
230
228
 
231
- self.key_index = {
232
- key: sid for key, sid in self.key_index.items() if sid != step_id
233
- }
229
+ self.key_index = {key: sid for key, sid in self.key_index.items() if sid != step_id}
234
230
  for key, last_sid in self._last_running.copy().items():
235
231
  if last_sid == step_id:
236
232
  self._last_running.pop(key, None)
237
233
 
238
- aliases = [
239
- alias
240
- for alias, target in self._step_aliases.items()
241
- if alias == step_id or target == step_id
242
- ]
234
+ aliases = [alias for alias, target in self._step_aliases.items() if alias == step_id or target == step_id]
243
235
  for alias in aliases:
244
236
  self._step_aliases.pop(alias, None)
245
237
 
@@ -321,9 +313,7 @@ class StepManager:
321
313
  Raises:
322
314
  RuntimeError: If no matching step is found
323
315
  """
324
- st = self.find_running(
325
- task_id=task_id, context_id=context_id, kind=kind, name=name
326
- )
316
+ st = self.find_running(task_id=task_id, context_id=context_id, kind=kind, name=name)
327
317
  if not st:
328
318
  # Try to find any existing step with matching parameters, even if not running
329
319
  for sid in reversed(list(self._iter_all_steps())):
@@ -340,9 +330,7 @@ class StepManager:
340
330
 
341
331
  # If still no step found, create a new one
342
332
  if not st:
343
- st = self.start_or_get(
344
- task_id=task_id, context_id=context_id, kind=kind, name=name
345
- )
333
+ st = self.start_or_get(task_id=task_id, context_id=context_id, kind=kind, name=name)
346
334
 
347
335
  if output:
348
336
  st.output = output
@@ -457,18 +445,14 @@ class StepManager:
457
445
  return cloned
458
446
 
459
447
  @staticmethod
460
- def _copy_tool_call_field(
461
- call: dict[str, Any], target: dict[str, Any], field: str
462
- ) -> None:
448
+ def _copy_tool_call_field(call: dict[str, Any], target: dict[str, Any], field: str) -> None:
463
449
  """Copy a field from the tool call when it exists."""
464
450
  value = call.get(field)
465
451
  if value:
466
452
  target[field] = value
467
453
 
468
454
  @staticmethod
469
- def _derive_call_step_id(
470
- call: dict[str, Any], base_step_id: str, index: int
471
- ) -> str:
455
+ def _derive_call_step_id(call: dict[str, Any], base_step_id: str, index: int) -> str:
472
456
  """Determine the per-call step identifier."""
473
457
  call_id = call.get("id")
474
458
  if isinstance(call_id, str):
@@ -495,15 +479,11 @@ class StepManager:
495
479
  self._link_step(step, parent_id)
496
480
 
497
481
  self.state.retained_ids.add(step.step_id)
498
- step.display_label = self._compose_display_label(
499
- step.kind, tool_name, args, metadata
500
- )
482
+ step.display_label = self._compose_display_label(step.kind, tool_name, args, metadata)
501
483
  self._flush_buffered_children(step.step_id)
502
484
  self._apply_pending_branch_flags(step.step_id)
503
485
 
504
- status = self._normalise_status(
505
- metadata.get("status"), event.get("status"), event.get("task_state")
506
- )
486
+ status = self._normalise_status(metadata.get("status"), event.get("status"), event.get("task_state"))
507
487
  status = self._apply_failure_state(step, status, event)
508
488
 
509
489
  server_time = self._coerce_server_time(metadata.get("time"))
@@ -530,9 +510,7 @@ class StepManager:
530
510
  self._prune_steps()
531
511
  return step
532
512
 
533
- def _parse_event_payload(
534
- self, event: dict[str, Any]
535
- ) -> tuple[dict[str, Any], str, dict[str, Any], dict[str, Any]]:
513
+ def _parse_event_payload(self, event: dict[str, Any]) -> tuple[dict[str, Any], str, dict[str, Any], dict[str, Any]]:
536
514
  metadata = event.get("metadata") or {}
537
515
  if not isinstance(metadata, dict):
538
516
  raise ValueError("Step event missing metadata payload")
@@ -553,9 +531,7 @@ class StepManager:
553
531
 
554
532
  return metadata, step_id, tool_info, args
555
533
 
556
- def _resolve_tool_name(
557
- self, tool_info: dict[str, Any], metadata: dict[str, Any], step_id: str
558
- ) -> str:
534
+ def _resolve_tool_name(self, tool_info: dict[str, Any], metadata: dict[str, Any], step_id: str) -> str:
559
535
  name = tool_info.get("name")
560
536
  if not name:
561
537
  call = self._first_tool_call(tool_info)
@@ -601,12 +577,8 @@ class StepManager:
601
577
  ) -> Step:
602
578
  existing = self.by_id.get(step_id)
603
579
  if existing:
604
- return self._update_existing_step(
605
- existing, kind, tool_name, event, metadata, args
606
- )
607
- return self._create_step_from_event(
608
- step_id, kind, tool_name, event, metadata, args
609
- )
580
+ return self._update_existing_step(existing, kind, tool_name, event, metadata, args)
581
+ return self._create_step_from_event(step_id, kind, tool_name, event, metadata, args)
610
582
 
611
583
  def _create_step_from_event(
612
584
  self,
@@ -621,12 +593,8 @@ class StepManager:
621
593
  step_id=step_id,
622
594
  kind=kind,
623
595
  name=tool_name or step_id,
624
- task_id=self._coalesce_metadata_value(
625
- "task_id", event, metadata, fallback=None
626
- ),
627
- context_id=self._coalesce_metadata_value(
628
- "context_id", event, metadata, fallback=None
629
- ),
596
+ task_id=self._coalesce_metadata_value("task_id", event, metadata, fallback=None),
597
+ context_id=self._coalesce_metadata_value("context_id", event, metadata, fallback=None),
630
598
  args=args or {},
631
599
  )
632
600
  self.by_id[step_id] = step
@@ -646,20 +614,12 @@ class StepManager:
646
614
  step.name = tool_name or step.name
647
615
  if args:
648
616
  step.args = args
649
- step.task_id = self._coalesce_metadata_value(
650
- "task_id", event, metadata, fallback=step.task_id
651
- )
652
- step.context_id = self._coalesce_metadata_value(
653
- "context_id", event, metadata, fallback=step.context_id
654
- )
617
+ step.task_id = self._coalesce_metadata_value("task_id", event, metadata, fallback=step.task_id)
618
+ step.context_id = self._coalesce_metadata_value("context_id", event, metadata, fallback=step.context_id)
655
619
  return step
656
620
 
657
- def _apply_failure_state(
658
- self, step: Step, status: str, event: dict[str, Any]
659
- ) -> str:
660
- failure_reason = self._extract_failure_reason(
661
- status, event.get("task_state"), event.get("content")
662
- )
621
+ def _apply_failure_state(self, step: Step, status: str, event: dict[str, Any]) -> str:
622
+ failure_reason = self._extract_failure_reason(status, event.get("task_state"), event.get("content"))
663
623
  if not failure_reason:
664
624
  step.status = status
665
625
  return status
@@ -679,9 +639,7 @@ class StepManager:
679
639
  args: dict[str, Any],
680
640
  server_time: float | None,
681
641
  ) -> None:
682
- duration_ms, duration_source = self._resolve_duration_from_event(
683
- tool_info, args
684
- )
642
+ duration_ms, duration_source = self._resolve_duration_from_event(tool_info, args)
685
643
  if duration_ms is not None:
686
644
  step.duration_ms = duration_ms
687
645
  step.duration_source = duration_source
@@ -750,6 +708,8 @@ class StepManager:
750
708
  return "delegate"
751
709
  if tool.startswith("agent_"):
752
710
  return "agent"
711
+ if kind == "agent_step":
712
+ return "tool" if tool else "agent_step"
753
713
  return kind or "tool"
754
714
 
755
715
  def _clean_kind(self, metadata_kind: Any) -> str:
@@ -763,9 +723,7 @@ class StepManager:
763
723
  def _is_delegate_tool(self, tool: str) -> bool:
764
724
  return tool.startswith(("delegate_to_", "delegate-", "delegate ", "delegate_"))
765
725
 
766
- def _is_top_level_agent(
767
- self, tool_name: str | None, metadata: dict[str, Any], kind: str
768
- ) -> bool:
726
+ def _is_top_level_agent(self, tool_name: str | None, metadata: dict[str, Any], kind: str) -> bool:
769
727
  if kind != "agent_step":
770
728
  return False
771
729
  agent_name = metadata.get("agent_name")
@@ -864,9 +822,7 @@ class StepManager:
864
822
  content: Any,
865
823
  ) -> str | None:
866
824
  failure_states = {"failed", "stopped", "error"}
867
- task_state_str = (
868
- (task_state or "").lower() if isinstance(task_state, str) else ""
869
- )
825
+ task_state_str = (task_state or "").lower() if isinstance(task_state, str) else ""
870
826
  if status in failure_states or task_state_str in failure_states:
871
827
  if isinstance(content, str) and content.strip():
872
828
  return content.strip()
@@ -893,15 +849,11 @@ class StepManager:
893
849
 
894
850
  return None, None
895
851
 
896
- def _determine_parent_id(
897
- self, step: Step, metadata: dict[str, Any], parent_hint: str | None
898
- ) -> str | None:
852
+ def _determine_parent_id(self, step: Step, metadata: dict[str, Any], parent_hint: str | None) -> str | None:
899
853
  scope_parent = self._lookup_scope_parent(metadata, step)
900
854
  candidate = scope_parent or parent_hint
901
855
  if candidate == step.step_id:
902
- logger.debug(
903
- "Step %s cannot parent itself; dropping parent hint", candidate
904
- )
856
+ logger.debug("Step %s cannot parent itself; dropping parent hint", candidate)
905
857
  return None
906
858
  return candidate
907
859
 
@@ -926,9 +878,7 @@ class StepManager:
926
878
  self._detach_from_current_parent(step)
927
879
  self._attach_to_parent(step, parent_id)
928
880
 
929
- def _sanitize_parent_reference(
930
- self, step: Step, parent_id: str | None
931
- ) -> str | None:
881
+ def _sanitize_parent_reference(self, step: Step, parent_id: str | None) -> str | None:
932
882
  """Guard against self-referential parent assignments."""
933
883
  if parent_id != step.step_id:
934
884
  return parent_id
@@ -1053,9 +1003,7 @@ class StepManager:
1053
1003
  except (TypeError, ValueError):
1054
1004
  return None
1055
1005
 
1056
- def _update_server_timestamps(
1057
- self, step: Step, server_time: float | None, status: str
1058
- ) -> None:
1006
+ def _update_server_timestamps(self, step: Step, server_time: float | None, status: str) -> None:
1059
1007
  if server_time is None:
1060
1008
  return
1061
1009
  if status == "running" and step.server_started_at is None:
@@ -1065,9 +1013,7 @@ class StepManager:
1065
1013
  if step.server_started_at is None:
1066
1014
  step.server_started_at = server_time
1067
1015
 
1068
- def _calculate_server_duration(
1069
- self, step: Step, server_time: float | None
1070
- ) -> int | None:
1016
+ def _calculate_server_duration(self, step: Step, server_time: float | None) -> int | None:
1071
1017
  start = step.server_started_at
1072
1018
  end = server_time if server_time is not None else step.server_finished_at
1073
1019
  if start is None or end is None:
@@ -95,9 +95,7 @@ def extract_names(items: list[str | Any] | None) -> list[str]:
95
95
  return names
96
96
 
97
97
 
98
- def find_by_name(
99
- items: list[Any], name: str, case_sensitive: bool = False
100
- ) -> list[Any]:
98
+ def find_by_name(items: list[Any], name: str, case_sensitive: bool = False) -> list[Any]:
101
99
  """Filter items by name with optional case sensitivity.
102
100
 
103
101
  This is a common pattern used across different clients for client-side
@@ -165,16 +163,12 @@ def validate_name_format(name: str, resource_type: str = "resource") -> str:
165
163
 
166
164
  # Check for valid characters (alphanumeric, hyphens, underscores)
167
165
  if not re.match(r"^[a-zA-Z0-9_-]+$", cleaned_name):
168
- raise ValueError(
169
- f"{display_type} name can only contain letters, numbers, hyphens, and underscores"
170
- )
166
+ raise ValueError(f"{display_type} name can only contain letters, numbers, hyphens, and underscores")
171
167
 
172
168
  return cleaned_name
173
169
 
174
170
 
175
- def validate_name_uniqueness(
176
- name: str, existing_names: list[str], resource_type: str = "resource"
177
- ) -> None:
171
+ def validate_name_uniqueness(name: str, existing_names: list[str], resource_type: str = "resource") -> None:
178
172
  """Validate that a resource name is unique.
179
173
 
180
174
  Args:
@@ -186,7 +180,4 @@ def validate_name_uniqueness(
186
180
  ValueError: If name is not unique
187
181
  """
188
182
  if name.lower() in [existing.lower() for existing in existing_names]:
189
- raise ValueError(
190
- f"A {resource_type.lower()} named '{name}' already exists. "
191
- "Please choose a unique name."
192
- )
183
+ raise ValueError(f"A {resource_type.lower()} named '{name}' already exists. Please choose a unique name.")
@@ -67,20 +67,12 @@ def read_yaml(file_path: Path) -> dict[str, Any]:
67
67
  data = yaml.safe_load(f)
68
68
 
69
69
  # Handle instruction_lines array format for user-friendly YAML
70
- if (
71
- isinstance(data, dict)
72
- and "instruction_lines" in data
73
- and isinstance(data["instruction_lines"], list)
74
- ):
70
+ if isinstance(data, dict) and "instruction_lines" in data and isinstance(data["instruction_lines"], list):
75
71
  data["instruction"] = "\n\n".join(data["instruction_lines"])
76
72
  del data["instruction_lines"]
77
73
 
78
74
  # Handle instruction as list from YAML export (convert back to string)
79
- if (
80
- isinstance(data, dict)
81
- and "instruction" in data
82
- and isinstance(data["instruction"], list)
83
- ):
75
+ if isinstance(data, dict) and "instruction" in data and isinstance(data["instruction"], list):
84
76
  data["instruction"] = "\n\n".join(data["instruction"])
85
77
 
86
78
  return data
@@ -98,9 +90,7 @@ def write_yaml(file_path: Path, data: dict[str, Any]) -> None:
98
90
  class LiteralString(str):
99
91
  pass
100
92
 
101
- def literal_string_representer(
102
- dumper: yaml.Dumper, data: "LiteralString"
103
- ) -> yaml.nodes.Node:
93
+ def literal_string_representer(dumper: yaml.Dumper, data: "LiteralString") -> yaml.nodes.Node:
104
94
  # Use literal block scalar (|) for multiline strings to preserve formatting
105
95
  if "\n" in data:
106
96
  return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
@@ -115,9 +105,7 @@ def write_yaml(file_path: Path, data: dict[str, Any]) -> None:
115
105
  data["instruction"] = LiteralString(data["instruction"])
116
106
 
117
107
  with open(file_path, "w", encoding="utf-8") as f:
118
- yaml.dump(
119
- data, f, default_flow_style=False, allow_unicode=True, sort_keys=False
120
- )
108
+ yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
121
109
 
122
110
 
123
111
  def load_resource_from_file(file_path: Path) -> dict[str, Any]:
@@ -137,15 +125,10 @@ def load_resource_from_file(file_path: Path) -> dict[str, Any]:
137
125
  elif file_path.suffix.lower() == ".json":
138
126
  return read_json(file_path)
139
127
  else:
140
- raise ValueError(
141
- f"Unsupported file format: {file_path.suffix}. "
142
- f"Only JSON and YAML files are supported."
143
- )
128
+ raise ValueError(f"Unsupported file format: {file_path.suffix}. Only JSON and YAML files are supported.")
144
129
 
145
130
 
146
- def write_resource_export(
147
- file_path: Path, data: dict[str, Any], format: str = "json"
148
- ) -> None:
131
+ def write_resource_export(file_path: Path, data: dict[str, Any], format: str = "json") -> None:
149
132
  """Write resource export data to file.
150
133
 
151
134
  Args:
@@ -190,13 +173,8 @@ def collect_attributes_for_export(resource: Any) -> dict[str, Any]:
190
173
  data.
191
174
  """
192
175
  mapping = _coerce_resource_to_mapping(resource)
193
- if (
194
- mapping is None
195
- ): # pragma: no cover - defensive fallback when attribute introspection fails
196
- items = (
197
- (name, _safe_getattr(resource, name))
198
- for name in _iter_public_attribute_names(resource)
199
- )
176
+ if mapping is None: # pragma: no cover - defensive fallback when attribute introspection fails
177
+ items = ((name, _safe_getattr(resource, name)) for name in _iter_public_attribute_names(resource))
200
178
  else:
201
179
  items = mapping.items()
202
180
 
@@ -249,9 +227,7 @@ def _coerce_resource_to_mapping(resource: Any) -> dict[str, Any] | None:
249
227
  try:
250
228
  if hasattr(resource, "__dict__"):
251
229
  return dict(resource.__dict__)
252
- except (
253
- Exception
254
- ): # pragma: no cover - pathological objects can still defeat coercion
230
+ except Exception: # pragma: no cover - pathological objects can still defeat coercion
255
231
  return None
256
232
 
257
233
  return None
@@ -284,9 +260,7 @@ def _iter_public_attribute_names(resource: Any) -> Iterable[str]:
284
260
  return iter(names)
285
261
 
286
262
 
287
- def _collect_from_dict(
288
- resource: Any, collect_func: Callable[[Iterable[str]], None]
289
- ) -> None:
263
+ def _collect_from_dict(resource: Any, collect_func: Callable[[Iterable[str]], None]) -> None:
290
264
  """Safely collect attribute names from __dict__."""
291
265
  try:
292
266
  if hasattr(resource, "__dict__"):
@@ -297,18 +271,14 @@ def _collect_from_dict(
297
271
  pass
298
272
 
299
273
 
300
- def _collect_from_annotations(
301
- resource: Any, collect_func: Callable[[Iterable[str]], None]
302
- ) -> None:
274
+ def _collect_from_annotations(resource: Any, collect_func: Callable[[Iterable[str]], None]) -> None:
303
275
  """Safely collect attribute names from __annotations__."""
304
276
  annotations = getattr(resource, "__annotations__", {})
305
277
  if annotations:
306
278
  collect_func(annotations.keys())
307
279
 
308
280
 
309
- def _collect_from_dir(
310
- resource: Any, collect_func: Callable[[Iterable[str]], None]
311
- ) -> None:
281
+ def _collect_from_dir(resource: Any, collect_func: Callable[[Iterable[str]], None]) -> None:
312
282
  """Safely collect attribute names from dir()."""
313
283
  try:
314
284
  collect_func(name for name in dir(resource) if not name.startswith("__"))
@@ -387,9 +357,7 @@ def build_mcp_export_payload(
387
357
  ImportError: If required modules (auth helpers) are not available
388
358
  """
389
359
  auth_module = importlib.import_module("glaip_sdk.cli.auth")
390
- prepare_authentication_export = getattr(
391
- auth_module, "prepare_authentication_export"
392
- )
360
+ prepare_authentication_export = auth_module.prepare_authentication_export
393
361
 
394
362
  # Start with model dump (excludes None values automatically)
395
363
  payload = mcp.model_dump(exclude_none=True)
@@ -435,4 +403,4 @@ def validate_json_string(json_str: str) -> dict[str, Any]:
435
403
  try:
436
404
  return json.loads(json_str)
437
405
  except json.JSONDecodeError as e:
438
- raise ValueError(f"Invalid JSON: {e}")
406
+ raise ValueError(f"Invalid JSON: {e}") from e
@@ -155,13 +155,13 @@ def coerce_timeout(value: Any) -> int:
155
155
  try:
156
156
  fval = float(value)
157
157
  return validate_timeout(int(fval))
158
- except ValueError:
159
- raise ValueError(f"Invalid timeout value: {value}")
158
+ except ValueError as err:
159
+ raise ValueError(f"Invalid timeout value: {value}") from err
160
160
  else:
161
161
  try:
162
162
  return validate_timeout(int(value))
163
- except (TypeError, ValueError):
164
- raise ValueError(f"Invalid timeout value: {value}")
163
+ except (TypeError, ValueError) as err:
164
+ raise ValueError(f"Invalid timeout value: {value}") from err
165
165
 
166
166
 
167
167
  def validate_file_path(file_path: str | Path, must_exist: bool = True) -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: glaip-sdk
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
5
5
  License: MIT
6
6
  Author: Raymond Christopher