stackgen-sdk 0.1.6__tar.gz → 0.1.7__tar.gz

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 (105) hide show
  1. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/PKG-INFO +1 -1
  2. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/_version.py +2 -2
  3. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/__init__.py +4 -0
  4. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/ask.py +9 -6
  5. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/namespace.py +73 -2
  6. stackgen_sdk-0.1.7/src/stackgen/aiden/run_status.py +202 -0
  7. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/webhook_ops.py +30 -0
  8. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/.gitignore +0 -0
  9. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/README.md +0 -0
  10. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/pyproject.toml +0 -0
  11. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/__init__.py +0 -0
  12. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/_http.py +0 -0
  13. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/runs.py +0 -0
  14. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/sessions.py +0 -0
  15. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/webhook.py +0 -0
  16. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/workflow_builder.py +0 -0
  17. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/aiden/workflow_publish.py +0 -0
  18. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/cli.py +0 -0
  19. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/client.py +0 -0
  20. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/config.py +0 -0
  21. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/errors.py +0 -0
  22. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/.openapi-generator/FILES +0 -0
  23. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/.openapi-generator/VERSION +0 -0
  24. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/.openapi-generator-ignore +0 -0
  25. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/__init__.py +0 -0
  26. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/api/__init__.py +0 -0
  27. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/api/aiden_api.py +0 -0
  28. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/api/sre_api.py +0 -0
  29. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/api_client.py +0 -0
  30. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/api_response.py +0 -0
  31. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/configuration.py +0 -0
  32. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/exceptions.py +0 -0
  33. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/__init__.py +0 -0
  34. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/agent_status.py +0 -0
  35. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_analysis_status.py +0 -0
  36. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_attention.py +0 -0
  37. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_categorization_summary.py +0 -0
  38. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_role.py +0 -0
  39. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_sort_by.py +0 -0
  40. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_status.py +0 -0
  41. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_summary.py +0 -0
  42. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_sync_run_status.py +0 -0
  43. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/alert_v1.py +0 -0
  44. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/artifact_info.py +0 -0
  45. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/ask_guild_request.py +0 -0
  46. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/ask_guild_start_response.py +0 -0
  47. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/candidate_type.py +0 -0
  48. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/dag_edge.py +0 -0
  49. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/dag_node.py +0 -0
  50. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/entity_ref.py +0 -0
  51. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/error_response.py +0 -0
  52. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/execution_list_item.py +0 -0
  53. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/execution_meta.py +0 -0
  54. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/execution_structured_output.py +0 -0
  55. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigate_alert_request.py +0 -0
  56. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigate_response.py +0 -0
  57. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation.py +0 -0
  58. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_evidence.py +0 -0
  59. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_evidence_kind.py +0 -0
  60. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_evidence_source.py +0 -0
  61. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_hypothesis.py +0 -0
  62. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_plain_summary.py +0 -0
  63. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_prior_incident.py +0 -0
  64. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_recommended_next_step.py +0 -0
  65. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_ref.py +0 -0
  66. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_status.py +0 -0
  67. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_structured_hypothesis_entry.py +0 -0
  68. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_structured_limitation_entry.py +0 -0
  69. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_structured_rca.py +0 -0
  70. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/investigation_triage_metadata.py +0 -0
  71. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/json_error.py +0 -0
  72. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/list_alerts_response.py +0 -0
  73. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/list_investigations_response.py +0 -0
  74. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/pagination.py +0 -0
  75. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/prior_session_policy.py +0 -0
  76. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/qualified_candidate.py +0 -0
  77. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/register_agent_request.py +0 -0
  78. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/register_agent_request_auto_approve_tools_inner.py +0 -0
  79. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/register_agent_request_hitl.py +0 -0
  80. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/schedule_run_status.py +0 -0
  81. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/schedule_target_type.py +0 -0
  82. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session.py +0 -0
  83. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session_export.py +0 -0
  84. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session_export_part.py +0 -0
  85. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session_export_part_kind.py +0 -0
  86. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session_list_response.py +0 -0
  87. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session_responder_kind.py +0 -0
  88. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/session_summary.py +0 -0
  89. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/signal_severity.py +0 -0
  90. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/stage_binding_write.py +0 -0
  91. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/sync_response.py +0 -0
  92. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/terminate_session_response.py +0 -0
  93. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/trace_dag_response.py +0 -0
  94. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/trigger_webhook202_response.py +0 -0
  95. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/webhook_run.py +0 -0
  96. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/webhook_run_detail.py +0 -0
  97. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/webhook_run_list_response.py +0 -0
  98. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/workflow.py +0 -0
  99. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/workflow_stage_write.py +0 -0
  100. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/models/workflow_write.py +0 -0
  101. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/generated/rest.py +0 -0
  102. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/sre/__init__.py +0 -0
  103. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/sre/namespace.py +0 -0
  104. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/vault/__init__.py +0 -0
  105. {stackgen_sdk-0.1.6 → stackgen_sdk-0.1.7}/src/stackgen/vault/namespace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stackgen-sdk
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: StackGen SDK — Aiden, SRE, and Vault from one client
5
5
  Project-URL: Homepage, https://appcd-dev.github.io/stackgen-sdk/
6
6
  Project-URL: Documentation, https://appcd-dev.github.io/stackgen-sdk/
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.6'
22
- __version_tuple__ = version_tuple = (0, 1, 6)
21
+ __version__ = version = '0.1.7'
22
+ __version_tuple__ = version_tuple = (0, 1, 7)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -2,6 +2,7 @@
2
2
 
3
3
  from stackgen.aiden.ask import AskArtifactResult
4
4
  from stackgen.aiden.namespace import AidenNamespace
5
+ from stackgen.aiden.run_status import ExecutionStatus, RunPhase, RunStatus
5
6
  from stackgen.aiden.webhook import WebhookReportResult
6
7
  from stackgen.aiden.workflow_builder import (
7
8
  AgentDefinition,
@@ -18,7 +19,10 @@ __all__ = [
18
19
  "AgentDefinition",
19
20
  "AidenNamespace",
20
21
  "AskArtifactResult",
22
+ "ExecutionStatus",
21
23
  "PublishWorkflowResult",
24
+ "RunPhase",
25
+ "RunStatus",
22
26
  "StageDefinition",
23
27
  "WebhookReportResult",
24
28
  "WorkflowBuilder",
@@ -10,11 +10,10 @@ from typing import Any
10
10
 
11
11
  from stackgen import _http
12
12
  from stackgen.aiden import sessions as session_helpers
13
+ from stackgen.aiden.run_status import ExecutionStatus
13
14
  from stackgen.config import StackgenConfig
14
15
  from stackgen.errors import StackgenError, TimeoutError
15
16
 
16
- _TERMINAL_STATUSES = frozenset({"completed", "error"})
17
-
18
17
 
19
18
  @dataclass(frozen=True)
20
19
  class AskArtifactResult:
@@ -156,16 +155,20 @@ class AskArtifactJourney:
156
155
  if not isinstance(data, dict):
157
156
  raise StackgenError(f"unexpected getExecutionTrace payload: {data!r}")
158
157
  execution = data.get("execution") or {}
159
- status = _http.text(execution.get("status")).casefold()
158
+ status = ExecutionStatus.parse(_http.text(execution.get("status")))
160
159
  # Prefer explicit settle when the mothership sends it. Some builds
161
160
  # omit trace_settled on the thin getExecutionTrace summary; treat
162
161
  # terminal status alone as done so callers are not stuck forever.
163
162
  settled = execution.get("trace_settled")
164
- ready = status in _TERMINAL_STATUSES and settled is not False
163
+ ready = (
164
+ status is not None
165
+ and status.is_terminal()
166
+ and settled is not False
167
+ )
165
168
  if ready:
166
- if status == "error":
169
+ if status in {ExecutionStatus.ERROR, ExecutionStatus.FAILED}:
167
170
  raise StackgenError(
168
- f"execution {trace_id} ended with status error"
171
+ f"execution {trace_id} ended with status {status.value}"
169
172
  )
170
173
  return data
171
174
  time.sleep(cfg.poll_interval_seconds)
@@ -6,11 +6,13 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  from stackgen.aiden.ask import AskArtifactJourney, AskArtifactResult
9
+ from stackgen.aiden.run_status import RunStatus, build_run_status, require_run_ids
9
10
  from stackgen.aiden.webhook import WebhookReportJourney, WebhookReportResult
10
11
  from stackgen.aiden.webhook_ops import WebhookOps
11
12
  from stackgen.aiden.workflow_builder import AgentDefinition, WorkflowDefinition
12
13
  from stackgen.aiden.workflow_publish import PublishWorkflowResult, WorkflowPublishJourney
13
14
  from stackgen.config import StackgenConfig
15
+ from stackgen import _http
14
16
 
15
17
 
16
18
  class AidenNamespace:
@@ -142,7 +144,12 @@ class AidenNamespace:
142
144
  source_app: str | None = None,
143
145
  source_metadata: dict[str, str] | None = None,
144
146
  ) -> dict[str, Any]:
145
- """POST /guild/api/v1/guild/ask/start (advanced callers)."""
147
+ """POST /guild/api/v1/guild/ask/start (fire-and-forget).
148
+
149
+ Returns ``session_id`` / ``trace_id`` immediately. Persist those ids and
150
+ call :meth:`get_run_status` from a separate scheduler — this method does
151
+ not wait for completion.
152
+ """
146
153
  return self._ask.start_ask(
147
154
  message,
148
155
  entity_refs=entity_refs,
@@ -152,8 +159,72 @@ class AidenNamespace:
152
159
  source_metadata=source_metadata,
153
160
  )
154
161
 
162
+ def start_workflow_run(
163
+ self,
164
+ workflow_name: str,
165
+ *,
166
+ input: dict[str, Any] | None = None,
167
+ ) -> dict[str, Any]:
168
+ """POST /guild/api/v1/workflows/{name}/run (fire-and-forget).
169
+
170
+ Returns ``session_id`` / ``trace_id`` immediately. Does not wait.
171
+ """
172
+ return self._webhook_ops.start_workflow_run(workflow_name, input=input)
173
+
174
+ def get_run_status(
175
+ self,
176
+ *,
177
+ session_id: str | None = None,
178
+ trace_id: str | None = None,
179
+ ready_artifact: str | None = None,
180
+ ) -> RunStatus:
181
+ """Stateless status for a run started elsewhere (queue / scheduler).
182
+
183
+ One HTTP round-trip set only — never blocks or polls. Use from a
184
+ separate process that holds ``session_id`` and/or ``trace_id`` in your
185
+ own store.
186
+
187
+ When ``ready_artifact`` is set (e.g. ``session-report.md``), ``ready`` is
188
+ true only once that artifact appears on the session.
189
+ """
190
+ require_run_ids(session_id=session_id, trace_id=trace_id)
191
+ sid = _http.text(session_id)
192
+ tid = _http.text(trace_id)
193
+ session: dict[str, Any] = {}
194
+ trace: dict[str, Any] = {}
195
+ names: list[str] = []
196
+
197
+ if tid:
198
+ trace = self.get_execution_trace(tid)
199
+ if not sid:
200
+ sid = _http.text(trace.get("session_id"))
201
+ execution = trace.get("execution") or {}
202
+ if isinstance(execution, dict) and not sid:
203
+ sid = _http.text(execution.get("session_id"))
204
+
205
+ if sid:
206
+ session = self.get_session(sid)
207
+ arts = self.list_session_artifacts(sid)
208
+ for a in arts:
209
+ if isinstance(a, dict):
210
+ name = _http.text(a.get("name"))
211
+ if name:
212
+ names.append(name)
213
+
214
+ return build_run_status(
215
+ session_id=sid,
216
+ trace_id=tid,
217
+ session=session,
218
+ trace=trace,
219
+ artifact_names=names,
220
+ ready_artifact=ready_artifact or "",
221
+ )
222
+
155
223
  def wait_for_execution(self, trace_id: str) -> dict[str, Any]:
156
- """Poll GET /guild/api/v1/executions/{traceId} until settled."""
224
+ """Poll GET /guild/api/v1/executions/{traceId} until settled (blocking).
225
+
226
+ Prefer :meth:`get_run_status` for queue/scheduler architectures.
227
+ """
157
228
  return self._ask.wait_for_execution(trace_id)
158
229
 
159
230
  def publish_workflow(
@@ -0,0 +1,202 @@
1
+ """Stateless run status for queue / scheduler architectures.
2
+
3
+ Callers that submit work on one process and poll from another should persist
4
+ ``session_id`` / ``trace_id``, then use :meth:`AidenNamespace.get_run_status`.
5
+ These helpers never block.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any
13
+
14
+ from stackgen import _http
15
+ from stackgen.errors import StackgenError
16
+
17
+
18
+ class ExecutionStatus(str, Enum):
19
+ """Raw Guild execution ``status`` values (OpenAPI ExecutionMeta.status).
20
+
21
+ OpenAPI documents ``running``, ``completed``, and ``error``; Guild also
22
+ emits cancel variants. Unknown wire values are left as strings on
23
+ :attr:`RunStatus.execution_status` rather than forced into this enum.
24
+ """
25
+
26
+ RUNNING = "running"
27
+ COMPLETED = "completed"
28
+ ERROR = "error"
29
+ FAILED = "failed"
30
+ CANCELLING = "cancelling"
31
+ CANCELLED = "cancelled"
32
+ QUEUED = "queued"
33
+ PENDING = "pending"
34
+
35
+ @classmethod
36
+ def parse(cls, value: str) -> ExecutionStatus | None:
37
+ key = (value or "").strip().casefold()
38
+ if not key:
39
+ return None
40
+ aliases = {
41
+ "canceled": cls.CANCELLED,
42
+ "in_progress": cls.RUNNING,
43
+ "active": cls.RUNNING,
44
+ "started": cls.RUNNING,
45
+ "success": cls.COMPLETED,
46
+ "succeeded": cls.COMPLETED,
47
+ "terminated": cls.CANCELLED,
48
+ }
49
+ if key in aliases:
50
+ return aliases[key]
51
+ try:
52
+ return cls(key)
53
+ except ValueError:
54
+ return None
55
+
56
+ def is_terminal(self) -> bool:
57
+ return self in {
58
+ ExecutionStatus.COMPLETED,
59
+ ExecutionStatus.ERROR,
60
+ ExecutionStatus.FAILED,
61
+ ExecutionStatus.CANCELLED,
62
+ }
63
+
64
+ def is_active(self) -> bool:
65
+ return self in {
66
+ ExecutionStatus.RUNNING,
67
+ ExecutionStatus.CANCELLING,
68
+ ExecutionStatus.QUEUED,
69
+ ExecutionStatus.PENDING,
70
+ }
71
+
72
+
73
+ class RunPhase(str, Enum):
74
+ """Normalized scheduler-facing phase from :meth:`AidenNamespace.get_run_status`."""
75
+
76
+ PENDING = "pending"
77
+ RUNNING = "running"
78
+ COMPLETED = "completed"
79
+ ERROR = "error"
80
+ UNKNOWN = "unknown"
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class RunStatus:
85
+ """Normalized one-shot status for a previously started run."""
86
+
87
+ phase: RunPhase
88
+ session_id: str = ""
89
+ trace_id: str = ""
90
+ run_id: str = ""
91
+ execution_status: str = ""
92
+ session_status: str = ""
93
+ artifact_names: tuple[str, ...] = ()
94
+ ready: bool = False
95
+ ready_artifact: str = ""
96
+ session: dict[str, Any] = field(default_factory=dict)
97
+ execution: dict[str, Any] = field(default_factory=dict)
98
+ trace: dict[str, Any] = field(default_factory=dict)
99
+
100
+ def to_dict(self) -> dict[str, Any]:
101
+ return {
102
+ "phase": self.phase.value,
103
+ "session_id": self.session_id,
104
+ "trace_id": self.trace_id,
105
+ "run_id": self.run_id,
106
+ "execution_status": self.execution_status,
107
+ "session_status": self.session_status,
108
+ "artifact_names": list(self.artifact_names),
109
+ "ready": self.ready,
110
+ "ready_artifact": self.ready_artifact,
111
+ }
112
+
113
+
114
+ def _phase_from_status(status: str) -> RunPhase:
115
+ parsed = ExecutionStatus.parse(status)
116
+ if parsed is None:
117
+ return RunPhase.UNKNOWN if not status else RunPhase.RUNNING
118
+ if parsed is ExecutionStatus.COMPLETED:
119
+ return RunPhase.COMPLETED
120
+ if parsed in {
121
+ ExecutionStatus.ERROR,
122
+ ExecutionStatus.FAILED,
123
+ ExecutionStatus.CANCELLED,
124
+ }:
125
+ return RunPhase.ERROR
126
+ if parsed in {ExecutionStatus.QUEUED, ExecutionStatus.PENDING}:
127
+ return RunPhase.PENDING
128
+ if parsed.is_active():
129
+ return RunPhase.RUNNING
130
+ return RunPhase.RUNNING
131
+
132
+
133
+ def build_run_status(
134
+ *,
135
+ session_id: str = "",
136
+ trace_id: str = "",
137
+ session: dict[str, Any] | None = None,
138
+ trace: dict[str, Any] | None = None,
139
+ artifact_names: list[str] | tuple[str, ...] = (),
140
+ ready_artifact: str = "",
141
+ ) -> RunStatus:
142
+ """Derive :class:`RunStatus` from optional session + execution payloads."""
143
+ session = session or {}
144
+ trace = trace or {}
145
+ execution = trace.get("execution") if isinstance(trace.get("execution"), dict) else {}
146
+ if not execution and isinstance(trace, dict):
147
+ # Some thin responses put status at the top level.
148
+ if "status" in trace and "execution" not in trace:
149
+ execution = trace
150
+
151
+ sid = session_id or _http.text(session.get("id") or session.get("session_id") or trace.get("session_id"))
152
+ tid = trace_id or _http.text(trace.get("trace_id") or execution.get("trace_id"))
153
+ run_id = _http.text(
154
+ execution.get("run_id")
155
+ or session.get("run_id")
156
+ or trace.get("run_id")
157
+ )
158
+ exec_status = _http.text(execution.get("status"))
159
+ sess_status = _http.text(session.get("status") or session.get("state"))
160
+ names = tuple(n for n in artifact_names if n)
161
+ ready_name = ready_artifact.strip()
162
+ ready = bool(ready_name and ready_name in names)
163
+
164
+ phase = RunPhase.UNKNOWN
165
+ if ready:
166
+ phase = RunPhase.COMPLETED
167
+ else:
168
+ exec_phase = _phase_from_status(exec_status)
169
+ sess_phase = _phase_from_status(sess_status)
170
+ if exec_phase is RunPhase.ERROR or sess_phase is RunPhase.ERROR:
171
+ phase = RunPhase.ERROR
172
+ elif exec_phase is RunPhase.COMPLETED or sess_phase is RunPhase.COMPLETED:
173
+ # Terminal execution without the caller's ready artifact yet —
174
+ # still running from a report-consumer perspective when a
175
+ # ready_artifact was requested; otherwise completed.
176
+ phase = RunPhase.RUNNING if ready_name else RunPhase.COMPLETED
177
+ elif exec_phase in {RunPhase.RUNNING, RunPhase.PENDING}:
178
+ phase = exec_phase
179
+ elif sess_phase in {RunPhase.RUNNING, RunPhase.PENDING}:
180
+ phase = sess_phase
181
+ elif sid or tid:
182
+ phase = RunPhase.RUNNING
183
+
184
+ return RunStatus(
185
+ phase=phase,
186
+ session_id=sid,
187
+ trace_id=tid,
188
+ run_id=run_id,
189
+ execution_status=exec_status,
190
+ session_status=sess_status,
191
+ artifact_names=names,
192
+ ready=ready,
193
+ ready_artifact=ready_name,
194
+ session=dict(session),
195
+ execution=dict(execution) if isinstance(execution, dict) else {},
196
+ trace=dict(trace),
197
+ )
198
+
199
+
200
+ def require_run_ids(*, session_id: str | None, trace_id: str | None) -> None:
201
+ if not (session_id or trace_id):
202
+ raise StackgenError("get_run_status requires session_id and/or trace_id")
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  import urllib.parse
6
7
  from pathlib import Path
7
8
  from typing import Any
@@ -170,3 +171,32 @@ class WebhookOps:
170
171
  if not isinstance(data, dict):
171
172
  raise StackgenError(f"unexpected terminate session payload: {data!r}")
172
173
  return data
174
+
175
+ def start_workflow_run(
176
+ self,
177
+ workflow_name: str,
178
+ *,
179
+ input: dict[str, Any] | None = None,
180
+ ) -> dict[str, Any]:
181
+ """POST /guild/api/v1/workflows/{name}/run (fire-and-forget).
182
+
183
+ Returns the start body (typically ``session_id`` + ``trace_id``). Does
184
+ not wait for completion — persist those ids and call
185
+ :meth:`AidenNamespace.get_run_status` from a scheduler.
186
+ """
187
+ name = urllib.parse.quote(workflow_name, safe="")
188
+ url = (
189
+ f"{self._config.aiden_url()}/api/v1/workflows/{name}/run"
190
+ f"{_http.query(self._config.org_id)}"
191
+ )
192
+ body: dict[str, Any] = {"input": input or {}}
193
+ data = _http.request(
194
+ url,
195
+ self._config.api_token,
196
+ method="POST",
197
+ body=json.dumps(body).encode("utf-8"),
198
+ content_type="application/json",
199
+ )
200
+ if not isinstance(data, dict):
201
+ raise StackgenError(f"unexpected workflow run start payload: {data!r}")
202
+ return data
File without changes
File without changes