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.
- glaip_sdk/_version.py +1 -3
- glaip_sdk/branding.py +2 -6
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +11 -30
- glaip_sdk/cli/commands/agents.py +45 -107
- glaip_sdk/cli/commands/configure.py +12 -36
- glaip_sdk/cli/commands/mcps.py +26 -63
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/tools.py +22 -35
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +1 -3
- glaip_sdk/cli/display.py +4 -12
- glaip_sdk/cli/io.py +8 -14
- glaip_sdk/cli/main.py +10 -30
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +3 -9
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/agent_session.py +5 -10
- glaip_sdk/cli/slash/prompt.py +3 -10
- glaip_sdk/cli/slash/session.py +46 -95
- glaip_sdk/cli/transcript/cache.py +6 -19
- glaip_sdk/cli/transcript/capture.py +6 -20
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +11 -40
- glaip_sdk/cli/update_notifier.py +165 -21
- glaip_sdk/cli/utils.py +33 -84
- glaip_sdk/cli/validators.py +11 -12
- glaip_sdk/client/_agent_payloads.py +10 -30
- glaip_sdk/client/agents.py +33 -63
- glaip_sdk/client/base.py +6 -22
- glaip_sdk/client/mcps.py +1 -3
- glaip_sdk/client/run_rendering.py +6 -14
- glaip_sdk/client/tools.py +8 -24
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/models.py +14 -33
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/client_utils.py +7 -21
- glaip_sdk/utils/display.py +2 -6
- glaip_sdk/utils/general.py +1 -3
- glaip_sdk/utils/import_export.py +3 -9
- glaip_sdk/utils/rendering/formatting.py +2 -5
- glaip_sdk/utils/rendering/models.py +2 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
- glaip_sdk/utils/rendering/renderer/base.py +63 -189
- glaip_sdk/utils/rendering/renderer/debug.py +4 -14
- glaip_sdk/utils/rendering/renderer/panels.py +1 -3
- glaip_sdk/utils/rendering/renderer/progress.py +3 -11
- glaip_sdk/utils/rendering/renderer/stream.py +7 -19
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps.py +29 -83
- glaip_sdk/utils/resource_refs.py +4 -13
- glaip_sdk/utils/serialization.py +14 -46
- glaip_sdk/utils/validation.py +4 -4
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/METADATA +1 -1
- glaip_sdk-0.1.1.dist-info/RECORD +82 -0
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.1.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
glaip_sdk/utils/resource_refs.py
CHANGED
|
@@ -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.")
|
glaip_sdk/utils/serialization.py
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
glaip_sdk/utils/validation.py
CHANGED
|
@@ -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:
|