glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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 (156) hide show
  1. glaip_sdk/__init__.py +5 -2
  2. glaip_sdk/_version.py +10 -3
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1191 -0
  5. glaip_sdk/branding.py +15 -6
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/agent_config.py +2 -6
  8. glaip_sdk/cli/auth.py +265 -45
  9. glaip_sdk/cli/commands/__init__.py +2 -2
  10. glaip_sdk/cli/commands/accounts.py +746 -0
  11. glaip_sdk/cli/commands/agents.py +251 -173
  12. glaip_sdk/cli/commands/common_config.py +101 -0
  13. glaip_sdk/cli/commands/configure.py +735 -143
  14. glaip_sdk/cli/commands/mcps.py +266 -134
  15. glaip_sdk/cli/commands/models.py +13 -9
  16. glaip_sdk/cli/commands/tools.py +67 -88
  17. glaip_sdk/cli/commands/transcripts.py +755 -0
  18. glaip_sdk/cli/commands/update.py +3 -8
  19. glaip_sdk/cli/config.py +49 -7
  20. glaip_sdk/cli/constants.py +38 -0
  21. glaip_sdk/cli/context.py +8 -0
  22. glaip_sdk/cli/core/__init__.py +79 -0
  23. glaip_sdk/cli/core/context.py +124 -0
  24. glaip_sdk/cli/core/output.py +846 -0
  25. glaip_sdk/cli/core/prompting.py +649 -0
  26. glaip_sdk/cli/core/rendering.py +187 -0
  27. glaip_sdk/cli/display.py +45 -32
  28. glaip_sdk/cli/hints.py +57 -0
  29. glaip_sdk/cli/io.py +14 -17
  30. glaip_sdk/cli/main.py +232 -143
  31. glaip_sdk/cli/masking.py +21 -33
  32. glaip_sdk/cli/mcp_validators.py +5 -15
  33. glaip_sdk/cli/pager.py +12 -19
  34. glaip_sdk/cli/parsers/__init__.py +1 -3
  35. glaip_sdk/cli/parsers/json_input.py +11 -22
  36. glaip_sdk/cli/resolution.py +3 -9
  37. glaip_sdk/cli/rich_helpers.py +1 -3
  38. glaip_sdk/cli/slash/__init__.py +0 -9
  39. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  40. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  41. glaip_sdk/cli/slash/agent_session.py +65 -29
  42. glaip_sdk/cli/slash/prompt.py +24 -10
  43. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  44. glaip_sdk/cli/slash/session.py +807 -225
  45. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  46. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  47. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  48. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  49. glaip_sdk/cli/slash/tui/loading.py +58 -0
  50. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  51. glaip_sdk/cli/transcript/__init__.py +12 -52
  52. glaip_sdk/cli/transcript/cache.py +258 -60
  53. glaip_sdk/cli/transcript/capture.py +72 -21
  54. glaip_sdk/cli/transcript/history.py +815 -0
  55. glaip_sdk/cli/transcript/launcher.py +1 -3
  56. glaip_sdk/cli/transcript/viewer.py +79 -499
  57. glaip_sdk/cli/update_notifier.py +177 -24
  58. glaip_sdk/cli/utils.py +242 -1308
  59. glaip_sdk/cli/validators.py +16 -18
  60. glaip_sdk/client/__init__.py +2 -1
  61. glaip_sdk/client/_agent_payloads.py +53 -37
  62. glaip_sdk/client/agent_runs.py +147 -0
  63. glaip_sdk/client/agents.py +320 -92
  64. glaip_sdk/client/base.py +78 -35
  65. glaip_sdk/client/main.py +19 -10
  66. glaip_sdk/client/mcps.py +123 -15
  67. glaip_sdk/client/run_rendering.py +136 -101
  68. glaip_sdk/client/shared.py +21 -0
  69. glaip_sdk/client/tools.py +163 -34
  70. glaip_sdk/client/validators.py +20 -48
  71. glaip_sdk/config/constants.py +11 -0
  72. glaip_sdk/exceptions.py +1 -3
  73. glaip_sdk/mcps/__init__.py +21 -0
  74. glaip_sdk/mcps/base.py +345 -0
  75. glaip_sdk/models/__init__.py +90 -0
  76. glaip_sdk/models/agent.py +47 -0
  77. glaip_sdk/models/agent_runs.py +116 -0
  78. glaip_sdk/models/common.py +42 -0
  79. glaip_sdk/models/mcp.py +33 -0
  80. glaip_sdk/models/tool.py +33 -0
  81. glaip_sdk/payload_schemas/__init__.py +1 -13
  82. glaip_sdk/payload_schemas/agent.py +1 -3
  83. glaip_sdk/registry/__init__.py +55 -0
  84. glaip_sdk/registry/agent.py +164 -0
  85. glaip_sdk/registry/base.py +139 -0
  86. glaip_sdk/registry/mcp.py +253 -0
  87. glaip_sdk/registry/tool.py +232 -0
  88. glaip_sdk/rich_components.py +58 -2
  89. glaip_sdk/runner/__init__.py +59 -0
  90. glaip_sdk/runner/base.py +84 -0
  91. glaip_sdk/runner/deps.py +115 -0
  92. glaip_sdk/runner/langgraph.py +706 -0
  93. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  94. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  95. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  96. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  97. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  98. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  99. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  100. glaip_sdk/tools/__init__.py +22 -0
  101. glaip_sdk/tools/base.py +435 -0
  102. glaip_sdk/utils/__init__.py +58 -12
  103. glaip_sdk/utils/a2a/__init__.py +34 -0
  104. glaip_sdk/utils/a2a/event_processor.py +188 -0
  105. glaip_sdk/utils/agent_config.py +4 -14
  106. glaip_sdk/utils/bundler.py +267 -0
  107. glaip_sdk/utils/client.py +111 -0
  108. glaip_sdk/utils/client_utils.py +46 -28
  109. glaip_sdk/utils/datetime_helpers.py +58 -0
  110. glaip_sdk/utils/discovery.py +78 -0
  111. glaip_sdk/utils/display.py +25 -21
  112. glaip_sdk/utils/export.py +143 -0
  113. glaip_sdk/utils/general.py +1 -36
  114. glaip_sdk/utils/import_export.py +15 -16
  115. glaip_sdk/utils/import_resolver.py +492 -0
  116. glaip_sdk/utils/instructions.py +101 -0
  117. glaip_sdk/utils/rendering/__init__.py +115 -1
  118. glaip_sdk/utils/rendering/formatting.py +7 -35
  119. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  120. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
  121. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
  122. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  123. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  124. glaip_sdk/utils/rendering/models.py +3 -6
  125. glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
  126. glaip_sdk/utils/rendering/renderer/base.py +258 -1577
  127. glaip_sdk/utils/rendering/renderer/config.py +1 -5
  128. glaip_sdk/utils/rendering/renderer/debug.py +30 -34
  129. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  130. glaip_sdk/utils/rendering/renderer/stream.py +10 -51
  131. glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
  132. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  133. glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
  134. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  135. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  136. glaip_sdk/utils/rendering/state.py +204 -0
  137. glaip_sdk/utils/rendering/step_tree_state.py +1 -3
  138. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  139. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
  140. glaip_sdk/utils/rendering/steps/format.py +176 -0
  141. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  142. glaip_sdk/utils/rendering/timing.py +36 -0
  143. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  144. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  145. glaip_sdk/utils/resource_refs.py +29 -26
  146. glaip_sdk/utils/runtime_config.py +425 -0
  147. glaip_sdk/utils/serialization.py +32 -46
  148. glaip_sdk/utils/sync.py +142 -0
  149. glaip_sdk/utils/tool_detection.py +33 -0
  150. glaip_sdk/utils/validation.py +20 -28
  151. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
  152. glaip_sdk-0.6.10.dist-info/RECORD +159 -0
  153. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
  154. glaip_sdk/models.py +0 -259
  155. glaip_sdk-0.1.0.dist-info/RECORD +0 -82
  156. {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,273 @@
1
+ """Thinking scope controller used by the renderer.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from time import monotonic
10
+ from typing import Any
11
+
12
+ from glaip_sdk.utils.rendering.formatting import is_step_finished
13
+ from glaip_sdk.utils.rendering.models import Step
14
+ from glaip_sdk.utils.rendering.state import ThinkingScopeState
15
+ from glaip_sdk.utils.rendering.steps import StepManager
16
+ from glaip_sdk.utils.rendering.timing import calculate_timeline_duration, coerce_server_time
17
+
18
+ FINISHED_STATUS_HINTS = {
19
+ "finished",
20
+ "success",
21
+ "succeeded",
22
+ "completed",
23
+ "failed",
24
+ "stopped",
25
+ "error",
26
+ }
27
+
28
+
29
+ class ThinkingScopeController:
30
+ """Encapsulates deterministic thinking bookkeeping for the renderer."""
31
+
32
+ def __init__(self, steps: StepManager, *, step_server_start_times: dict[str, float]) -> None:
33
+ """Initialize the thinking scope controller.
34
+
35
+ Args:
36
+ steps: Step manager instance for tracking steps
37
+ step_server_start_times: Dictionary mapping step IDs to server start times
38
+ """
39
+ self._steps = steps
40
+ self._step_server_start_times = step_server_start_times
41
+ self._scopes: dict[str, ThinkingScopeState] = {}
42
+
43
+ def update_timeline(self, step: Step | None, payload: dict[str, Any], *, enabled: bool) -> None:
44
+ """Update thinking spans for a streamed step event."""
45
+ if not enabled or not step:
46
+ return
47
+
48
+ now_monotonic = monotonic()
49
+ server_time = coerce_server_time(payload.get("time"))
50
+ status_hint = (payload.get("status") or "").lower()
51
+
52
+ if self._is_scope_anchor(step):
53
+ self._update_anchor_thinking(
54
+ step=step,
55
+ server_time=server_time,
56
+ status_hint=status_hint,
57
+ now_monotonic=now_monotonic,
58
+ )
59
+ return
60
+
61
+ self._update_child_thinking(
62
+ step=step,
63
+ server_time=server_time,
64
+ status_hint=status_hint,
65
+ now_monotonic=now_monotonic,
66
+ )
67
+
68
+ def close_active_scopes(self, server_time: float | None) -> None:
69
+ """Finish any in-flight thinking nodes during finalization."""
70
+ now = monotonic()
71
+ for scope in self._scopes.values():
72
+ if not scope.active_thinking_id:
73
+ continue
74
+ self._finish_scope_thinking(scope, server_time, now)
75
+
76
+ # ------------------------------------------------------------------
77
+ # Internal helpers mirroring the previous renderer implementation.
78
+ # ------------------------------------------------------------------
79
+ def _update_anchor_thinking(
80
+ self,
81
+ *,
82
+ step: Step,
83
+ server_time: float | None,
84
+ status_hint: str,
85
+ now_monotonic: float,
86
+ ) -> None:
87
+ scope = self._get_or_create_scope(step)
88
+ if scope.anchor_started_at is None and server_time is not None:
89
+ scope.anchor_started_at = server_time
90
+
91
+ if not scope.closed and scope.active_thinking_id is None:
92
+ self._start_scope_thinking(
93
+ scope,
94
+ start_server_time=scope.anchor_started_at or server_time,
95
+ start_monotonic=now_monotonic,
96
+ )
97
+
98
+ is_anchor_finished = status_hint in FINISHED_STATUS_HINTS or (not status_hint and is_step_finished(step))
99
+ if is_anchor_finished:
100
+ scope.anchor_finished_at = server_time or scope.anchor_finished_at
101
+ self._finish_scope_thinking(scope, server_time, now_monotonic)
102
+ scope.closed = True
103
+
104
+ parent_anchor_id = self._resolve_anchor_id(step)
105
+ if parent_anchor_id:
106
+ self._cascade_anchor_update(
107
+ parent_anchor_id=parent_anchor_id,
108
+ child_step=step,
109
+ server_time=server_time,
110
+ now_monotonic=now_monotonic,
111
+ is_finished=is_anchor_finished,
112
+ )
113
+
114
+ def _cascade_anchor_update(
115
+ self,
116
+ *,
117
+ parent_anchor_id: str,
118
+ child_step: Step,
119
+ server_time: float | None,
120
+ now_monotonic: float,
121
+ is_finished: bool,
122
+ ) -> None:
123
+ parent_scope = self._scopes.get(parent_anchor_id)
124
+ if not parent_scope or parent_scope.closed:
125
+ return
126
+ if is_finished:
127
+ self._mark_child_finished(parent_scope, child_step.step_id, server_time, now_monotonic)
128
+ else:
129
+ self._mark_child_running(parent_scope, child_step, server_time, now_monotonic)
130
+
131
+ def _update_child_thinking(
132
+ self,
133
+ *,
134
+ step: Step,
135
+ server_time: float | None,
136
+ status_hint: str,
137
+ now_monotonic: float,
138
+ ) -> None:
139
+ anchor_id = self._resolve_anchor_id(step)
140
+ if not anchor_id:
141
+ return
142
+
143
+ scope = self._scopes.get(anchor_id)
144
+ if not scope or scope.closed or step.kind == "thinking":
145
+ return
146
+
147
+ is_finish_event = status_hint in FINISHED_STATUS_HINTS or (not status_hint and is_step_finished(step))
148
+ if is_finish_event:
149
+ self._mark_child_finished(scope, step.step_id, server_time, now_monotonic)
150
+ else:
151
+ self._mark_child_running(scope, step, server_time, now_monotonic)
152
+
153
+ def _resolve_anchor_id(self, step: Step) -> str | None:
154
+ parent_id = step.parent_id
155
+ while parent_id:
156
+ parent = self._steps.by_id.get(parent_id)
157
+ if not parent:
158
+ return None
159
+ if self._is_scope_anchor(parent):
160
+ return parent.step_id
161
+ parent_id = parent.parent_id
162
+ return None
163
+
164
+ def _get_or_create_scope(self, step: Step) -> ThinkingScopeState:
165
+ scope = self._scopes.get(step.step_id)
166
+ if scope:
167
+ if scope.task_id is None:
168
+ scope.task_id = step.task_id
169
+ if scope.context_id is None:
170
+ scope.context_id = step.context_id
171
+ return scope
172
+ scope = ThinkingScopeState(
173
+ anchor_id=step.step_id,
174
+ task_id=step.task_id,
175
+ context_id=step.context_id,
176
+ )
177
+ self._scopes[step.step_id] = scope
178
+ return scope
179
+
180
+ def _is_scope_anchor(self, step: Step) -> bool:
181
+ if step.kind in {"agent", "delegate"}:
182
+ return True
183
+ name = (step.name or "").lower()
184
+ return name.startswith(("delegate_to_", "delegate_", "delegate "))
185
+
186
+ def _start_scope_thinking(
187
+ self,
188
+ scope: ThinkingScopeState,
189
+ *,
190
+ start_server_time: float | None,
191
+ start_monotonic: float,
192
+ ) -> None:
193
+ if scope.closed or scope.active_thinking_id or not scope.anchor_id:
194
+ return
195
+ step = self._steps.start_or_get(
196
+ task_id=scope.task_id,
197
+ context_id=scope.context_id,
198
+ kind="thinking",
199
+ name=f"agent_thinking_step::{scope.anchor_id}",
200
+ parent_id=scope.anchor_id,
201
+ args={"reason": "deterministic_timeline"},
202
+ )
203
+ step.display_label = "💭 Thinking…"
204
+ scope.active_thinking_id = step.step_id
205
+ scope.idle_started_at = start_server_time
206
+ scope.idle_started_monotonic = start_monotonic
207
+
208
+ def _finish_scope_thinking(
209
+ self,
210
+ scope: ThinkingScopeState,
211
+ end_server_time: float | None,
212
+ end_monotonic: float,
213
+ ) -> None:
214
+ if not scope.active_thinking_id:
215
+ return
216
+ thinking_step = self._steps.by_id.get(scope.active_thinking_id)
217
+ if not thinking_step:
218
+ scope.active_thinking_id = None
219
+ scope.idle_started_at = None
220
+ scope.idle_started_monotonic = None
221
+ return
222
+
223
+ duration = calculate_timeline_duration(
224
+ scope.idle_started_at,
225
+ end_server_time,
226
+ scope.idle_started_monotonic,
227
+ end_monotonic,
228
+ )
229
+ thinking_step.display_label = thinking_step.display_label or "💭 Thinking…"
230
+ if duration is not None:
231
+ thinking_step.finish(duration, source="timeline")
232
+ else:
233
+ thinking_step.finish(None, source="timeline")
234
+ scope.active_thinking_id = None
235
+ scope.idle_started_at = None
236
+ scope.idle_started_monotonic = None
237
+
238
+ def _mark_child_running(
239
+ self,
240
+ scope: ThinkingScopeState,
241
+ step: Step,
242
+ server_time: float | None,
243
+ now_monotonic: float,
244
+ ) -> None:
245
+ if step.step_id in scope.running_children:
246
+ return
247
+ scope.running_children.add(step.step_id)
248
+ if not scope.active_thinking_id:
249
+ return
250
+
251
+ start_server = self._step_server_start_times.get(step.step_id)
252
+ if start_server is None:
253
+ start_server = server_time
254
+ self._finish_scope_thinking(scope, start_server, now_monotonic)
255
+
256
+ def _mark_child_finished(
257
+ self,
258
+ scope: ThinkingScopeState,
259
+ step_id: str,
260
+ server_time: float | None,
261
+ now_monotonic: float,
262
+ ) -> None:
263
+ scope.running_children.discard(step_id)
264
+ if scope.active_thinking_id or scope.closed or scope.running_children:
265
+ return
266
+ self._start_scope_thinking(
267
+ scope,
268
+ start_server_time=server_time,
269
+ start_monotonic=now_monotonic,
270
+ )
271
+
272
+
273
+ __all__ = ["ThinkingScopeController", "FINISHED_STATUS_HINTS"]
@@ -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: