stackgen-sdk 0.1.2__tar.gz → 0.1.5__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 (90) hide show
  1. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/.gitignore +6 -0
  2. stackgen_sdk-0.1.5/PKG-INFO +92 -0
  3. stackgen_sdk-0.1.5/README.md +71 -0
  4. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/pyproject.toml +4 -1
  5. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/__init__.py +7 -2
  6. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/_version.py +2 -2
  7. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/aiden/__init__.py +3 -1
  8. stackgen_sdk-0.1.5/src/stackgen/aiden/ask.py +168 -0
  9. stackgen_sdk-0.1.5/src/stackgen/aiden/namespace.py +78 -0
  10. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/aiden/runs.py +1 -1
  11. stackgen_sdk-0.1.5/src/stackgen/aiden/sessions.py +46 -0
  12. stackgen_sdk-0.1.5/src/stackgen/aiden/webhook.py +81 -0
  13. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/cli.py +9 -1
  14. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/config.py +14 -32
  15. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/.openapi-generator/FILES +33 -0
  16. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/__init__.py +22 -0
  17. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/api/aiden_api.py +578 -0
  18. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/configuration.py +3 -3
  19. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/__init__.py +11 -0
  20. stackgen_sdk-0.1.5/src/stackgen/generated/models/ask_guild_request.py +119 -0
  21. stackgen_sdk-0.1.5/src/stackgen/generated/models/ask_guild_start_response.py +109 -0
  22. stackgen_sdk-0.1.5/src/stackgen/generated/models/candidate_type.py +40 -0
  23. stackgen_sdk-0.1.5/src/stackgen/generated/models/dag_edge.py +89 -0
  24. stackgen_sdk-0.1.5/src/stackgen/generated/models/dag_node.py +103 -0
  25. stackgen_sdk-0.1.5/src/stackgen/generated/models/entity_ref.py +90 -0
  26. stackgen_sdk-0.1.5/src/stackgen/generated/models/execution_meta.py +102 -0
  27. stackgen_sdk-0.1.5/src/stackgen/generated/models/execution_structured_output.py +93 -0
  28. stackgen_sdk-0.1.5/src/stackgen/generated/models/prior_session_policy.py +110 -0
  29. stackgen_sdk-0.1.5/src/stackgen/generated/models/qualified_candidate.py +92 -0
  30. stackgen_sdk-0.1.5/src/stackgen/generated/models/trace_dag_response.py +119 -0
  31. stackgen_sdk-0.1.2/PKG-INFO +0 -59
  32. stackgen_sdk-0.1.2/README.md +0 -41
  33. stackgen_sdk-0.1.2/src/stackgen/aiden/namespace.py +0 -26
  34. stackgen_sdk-0.1.2/src/stackgen/aiden/webhook.py +0 -185
  35. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/_http.py +0 -0
  36. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/client.py +0 -0
  37. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/errors.py +0 -0
  38. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/.openapi-generator/VERSION +0 -0
  39. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/.openapi-generator-ignore +0 -0
  40. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/api/__init__.py +0 -0
  41. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/api/sre_api.py +0 -0
  42. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/api_client.py +0 -0
  43. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/api_response.py +0 -0
  44. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/exceptions.py +0 -0
  45. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_analysis_status.py +0 -0
  46. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_attention.py +0 -0
  47. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_categorization_summary.py +0 -0
  48. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_role.py +0 -0
  49. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_sort_by.py +0 -0
  50. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_status.py +0 -0
  51. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_summary.py +0 -0
  52. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_sync_run_status.py +0 -0
  53. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/alert_v1.py +0 -0
  54. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/artifact_info.py +0 -0
  55. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/error_response.py +0 -0
  56. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigate_alert_request.py +0 -0
  57. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigate_response.py +0 -0
  58. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation.py +0 -0
  59. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_evidence.py +0 -0
  60. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_evidence_kind.py +0 -0
  61. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_evidence_source.py +0 -0
  62. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_hypothesis.py +0 -0
  63. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_plain_summary.py +0 -0
  64. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_prior_incident.py +0 -0
  65. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_recommended_next_step.py +0 -0
  66. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_ref.py +0 -0
  67. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_status.py +0 -0
  68. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_structured_hypothesis_entry.py +0 -0
  69. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_structured_limitation_entry.py +0 -0
  70. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_structured_rca.py +0 -0
  71. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/investigation_triage_metadata.py +0 -0
  72. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/json_error.py +0 -0
  73. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/list_alerts_response.py +0 -0
  74. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/list_investigations_response.py +0 -0
  75. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/pagination.py +0 -0
  76. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/schedule_run_status.py +0 -0
  77. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/schedule_target_type.py +0 -0
  78. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/session.py +0 -0
  79. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/session_responder_kind.py +0 -0
  80. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/signal_severity.py +0 -0
  81. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/sync_response.py +0 -0
  82. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/trigger_webhook202_response.py +0 -0
  83. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/webhook_run.py +0 -0
  84. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/webhook_run_detail.py +0 -0
  85. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/models/webhook_run_list_response.py +0 -0
  86. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/generated/rest.py +0 -0
  87. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/sre/__init__.py +0 -0
  88. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/sre/namespace.py +0 -0
  89. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/vault/__init__.py +0 -0
  90. {stackgen_sdk-0.1.2 → stackgen_sdk-0.1.5}/src/stackgen/vault/namespace.py +0 -0
@@ -32,5 +32,11 @@ coverage.out
32
32
  .env
33
33
  .env.*
34
34
 
35
+ # Jekyll (only if native Ruby was used by mistake)
36
+ docs/_site/
37
+ docs/.jekyll-cache/
38
+ docs/.bundle/
39
+ docs/vendor/
40
+
35
41
  # hatch-vcs generated version module (written at build time)
36
42
  clients/python/src/stackgen/_version.py
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.5
2
+ Name: stackgen-sdk
3
+ Version: 0.1.5
4
+ Summary: StackGen SDK — Aiden, SRE, and Vault from one client
5
+ Project-URL: Homepage, https://appcd-dev.github.io/stackgen-sdk/
6
+ Project-URL: Documentation, https://appcd-dev.github.io/stackgen-sdk/
7
+ Project-URL: Repository, https://github.com/appcd-dev/stackgen-sdk
8
+ Project-URL: Bug Tracker, https://github.com/appcd-dev/stackgen-sdk/issues
9
+ Author: StackGen
10
+ License-Expression: Apache-2.0
11
+ Keywords: aiden,openapi,sre,stackgen
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: python-dateutil>=2.8
15
+ Requires-Dist: typing-extensions>=4.0
16
+ Requires-Dist: urllib3>=2.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: build>=1.2; extra == 'dev'
19
+ Requires-Dist: pytest>=8.0; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # StackGen Python SDK
23
+
24
+ **Documentation:** [https://appcd-dev.github.io/stackgen-sdk/](https://appcd-dev.github.io/stackgen-sdk/) — Aiden guide, code samples, FAQ, and CI integration.
25
+
26
+ Install:
27
+
28
+ ```bash
29
+ pip install stackgen-sdk
30
+ ```
31
+
32
+ Import package name is `stackgen`. Construct the client with an explicit config:
33
+
34
+ ```python
35
+ from pathlib import Path
36
+ from stackgen import StackgenClient, StackgenConfig
37
+
38
+ client = StackgenClient(
39
+ StackgenConfig(
40
+ base_url="https://azure-eu.cloud.stackgen.com",
41
+ api_token="stackgen_…",
42
+ org_id="<project-uuid>",
43
+ webhook_token="sg_aios_…", # webhook journeys only
44
+ )
45
+ )
46
+
47
+ result = client.aiden.run_webhook_and_download_report(
48
+ Path("alert.json").read_text(encoding="utf-8")
49
+ )
50
+ print(result.session_id, result.output_path)
51
+
52
+ ask = client.aiden.run_ask_and_download_artifact(
53
+ message="Run triage for checkout CPU alert",
54
+ entity_refs=[{"name": "observe-uber-triage", "type": "workflow"}],
55
+ )
56
+ print(ask.trace_id, ask.output_path)
57
+
58
+ alerts = client.sre.list_alerts()
59
+ ```
60
+
61
+ ## Default environment variables
62
+
63
+ `StackgenConfig.from_env()` reads **only** these (optional convenience):
64
+
65
+ | Variable | Purpose |
66
+ |----------|---------|
67
+ | `STACKGEN_URL` | Mothership URL (no path suffix) |
68
+ | `STACKGEN_TOKEN` | API token (`stackgen_…`) |
69
+ | `STACKGEN_PROJECT` | Project / org UUID (`orgId`) |
70
+
71
+ Pass `webhook_token`, `webhook_id`, timeouts, and artifact paths as `StackgenConfig` fields — not via env.
72
+
73
+ ```python
74
+ from dataclasses import replace
75
+ from stackgen import StackgenClient, StackgenConfig
76
+
77
+ client = StackgenClient(
78
+ replace(StackgenConfig.from_env(), webhook_token="sg_aios_…")
79
+ )
80
+ ```
81
+
82
+ ## Namespaces
83
+
84
+ - `client.aiden` — webhooks, Ask Guild, sessions, artifact download
85
+ - `client.sre` — alerts and investigations
86
+ - `client.vault` — secrets (not enabled in this release)
87
+
88
+ Prefer `StackgenClient` over `stackgen.generated`.
89
+
90
+ Pin versions in CI: `pip install stackgen-sdk==0.1.5`.
91
+
92
+ **Documentation:** [https://appcd-dev.github.io/stackgen-sdk/](https://appcd-dev.github.io/stackgen-sdk/) (Aiden guide, [FAQ](https://appcd-dev.github.io/stackgen-sdk/faq/), [code samples](https://appcd-dev.github.io/stackgen-sdk/code-samples/))
@@ -0,0 +1,71 @@
1
+ # StackGen Python SDK
2
+
3
+ **Documentation:** [https://appcd-dev.github.io/stackgen-sdk/](https://appcd-dev.github.io/stackgen-sdk/) — Aiden guide, code samples, FAQ, and CI integration.
4
+
5
+ Install:
6
+
7
+ ```bash
8
+ pip install stackgen-sdk
9
+ ```
10
+
11
+ Import package name is `stackgen`. Construct the client with an explicit config:
12
+
13
+ ```python
14
+ from pathlib import Path
15
+ from stackgen import StackgenClient, StackgenConfig
16
+
17
+ client = StackgenClient(
18
+ StackgenConfig(
19
+ base_url="https://azure-eu.cloud.stackgen.com",
20
+ api_token="stackgen_…",
21
+ org_id="<project-uuid>",
22
+ webhook_token="sg_aios_…", # webhook journeys only
23
+ )
24
+ )
25
+
26
+ result = client.aiden.run_webhook_and_download_report(
27
+ Path("alert.json").read_text(encoding="utf-8")
28
+ )
29
+ print(result.session_id, result.output_path)
30
+
31
+ ask = client.aiden.run_ask_and_download_artifact(
32
+ message="Run triage for checkout CPU alert",
33
+ entity_refs=[{"name": "observe-uber-triage", "type": "workflow"}],
34
+ )
35
+ print(ask.trace_id, ask.output_path)
36
+
37
+ alerts = client.sre.list_alerts()
38
+ ```
39
+
40
+ ## Default environment variables
41
+
42
+ `StackgenConfig.from_env()` reads **only** these (optional convenience):
43
+
44
+ | Variable | Purpose |
45
+ |----------|---------|
46
+ | `STACKGEN_URL` | Mothership URL (no path suffix) |
47
+ | `STACKGEN_TOKEN` | API token (`stackgen_…`) |
48
+ | `STACKGEN_PROJECT` | Project / org UUID (`orgId`) |
49
+
50
+ Pass `webhook_token`, `webhook_id`, timeouts, and artifact paths as `StackgenConfig` fields — not via env.
51
+
52
+ ```python
53
+ from dataclasses import replace
54
+ from stackgen import StackgenClient, StackgenConfig
55
+
56
+ client = StackgenClient(
57
+ replace(StackgenConfig.from_env(), webhook_token="sg_aios_…")
58
+ )
59
+ ```
60
+
61
+ ## Namespaces
62
+
63
+ - `client.aiden` — webhooks, Ask Guild, sessions, artifact download
64
+ - `client.sre` — alerts and investigations
65
+ - `client.vault` — secrets (not enabled in this release)
66
+
67
+ Prefer `StackgenClient` over `stackgen.generated`.
68
+
69
+ Pin versions in CI: `pip install stackgen-sdk==0.1.5`.
70
+
71
+ **Documentation:** [https://appcd-dev.github.io/stackgen-sdk/](https://appcd-dev.github.io/stackgen-sdk/) (Aiden guide, [FAQ](https://appcd-dev.github.io/stackgen-sdk/faq/), [code samples](https://appcd-dev.github.io/stackgen-sdk/code-samples/))
@@ -19,7 +19,10 @@ dependencies = [
19
19
  ]
20
20
 
21
21
  [project.urls]
22
- Homepage = "https://pypi.org/project/stackgen-sdk/"
22
+ Homepage = "https://appcd-dev.github.io/stackgen-sdk/"
23
+ Documentation = "https://appcd-dev.github.io/stackgen-sdk/"
24
+ Repository = "https://github.com/appcd-dev/stackgen-sdk"
25
+ "Bug Tracker" = "https://github.com/appcd-dev/stackgen-sdk/issues"
23
26
 
24
27
  [project.optional-dependencies]
25
28
  dev = ["pytest>=8.0", "build>=1.2"]
@@ -4,8 +4,13 @@ Public entrypoint::
4
4
 
5
5
  from stackgen import StackgenClient, StackgenConfig
6
6
 
7
- client = StackgenClient.from_env()
8
- result = client.aiden.run_webhook_and_download_report(payload)
7
+ client = StackgenClient(
8
+ StackgenConfig(
9
+ base_url="https://azure-eu.cloud.stackgen.com",
10
+ api_token="stackgen_…",
11
+ org_id="<project-uuid>",
12
+ )
13
+ )
9
14
  """
10
15
 
11
16
  from __future__ import annotations
@@ -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.2'
22
- __version_tuple__ = version_tuple = (0, 1, 2)
21
+ __version__ = version = '0.1.5'
22
+ __version_tuple__ = version_tuple = (0, 1, 5)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,9 +1,11 @@
1
- """Aiden (platform) namespace — webhooks, sessions, artifacts."""
1
+ """Aiden (platform) namespace — webhooks, sessions, artifacts, Ask Guild."""
2
2
 
3
+ from stackgen.aiden.ask import AskArtifactResult
3
4
  from stackgen.aiden.namespace import AidenNamespace
4
5
  from stackgen.aiden.webhook import WebhookReportResult
5
6
 
6
7
  __all__ = [
7
8
  "AidenNamespace",
9
+ "AskArtifactResult",
8
10
  "WebhookReportResult",
9
11
  ]
@@ -0,0 +1,168 @@
1
+ """Aiden Ask → execution poll → artifact download journey."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from stackgen import _http
12
+ from stackgen.aiden import sessions as session_helpers
13
+ from stackgen.config import StackgenConfig
14
+ from stackgen.errors import StackgenError, TimeoutError
15
+
16
+ _TERMINAL_STATUSES = frozenset({"completed", "error"})
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class AskArtifactResult:
21
+ """Result of Ask start → wait → download session artifact."""
22
+
23
+ trace_id: str
24
+ run_id: str
25
+ session_id: str
26
+ artifacts: tuple[dict[str, Any], ...]
27
+ output_path: Path
28
+ candidates: tuple[dict[str, Any], ...] = ()
29
+ plan: str = ""
30
+
31
+
32
+ class AskArtifactJourney:
33
+ """Start Ask Guild, poll execution trace, wait for artifact, download it.
34
+
35
+ Uses ``api_token`` and ``org_id`` only (no webhook token).
36
+ """
37
+
38
+ def __init__(self, config: StackgenConfig) -> None:
39
+ self._config = config
40
+
41
+ def run(
42
+ self,
43
+ message: str,
44
+ *,
45
+ entity_refs: list[dict[str, str]] | None = None,
46
+ artifact_name: str | None = None,
47
+ output_path: str | None = None,
48
+ inputs: dict[str, str] | None = None,
49
+ session_id: str | None = None,
50
+ source_app: str | None = None,
51
+ source_metadata: dict[str, str] | None = None,
52
+ ) -> AskArtifactResult:
53
+ cfg = self._config
54
+ artifact = artifact_name or cfg.artifact_name
55
+ dest = output_path or cfg.output_path
56
+
57
+ start = self.start_ask(
58
+ message,
59
+ entity_refs=entity_refs,
60
+ inputs=inputs,
61
+ session_id=session_id,
62
+ source_app=source_app,
63
+ source_metadata=source_metadata,
64
+ )
65
+ trace_id = _http.text(start.get("trace_id"))
66
+ if not trace_id:
67
+ raise StackgenError(
68
+ "startAskGuild did not return trace_id; "
69
+ f"body={json.dumps(start)}"
70
+ )
71
+
72
+ run_id = _http.text(start.get("run_id"))
73
+ resolved_session = _http.text(start.get("session_id")) or _http.text(session_id)
74
+ plan = _http.text(start.get("plan"))
75
+ candidates = tuple(start.get("candidates") or ())
76
+
77
+ trace = self.wait_for_execution(trace_id)
78
+ if not resolved_session:
79
+ resolved_session = _http.text(trace.get("session_id"))
80
+ execution = trace.get("execution") or {}
81
+ if not resolved_session:
82
+ raise StackgenError(
83
+ f"execution {trace_id} settled without session_id; "
84
+ "cannot download artifacts"
85
+ )
86
+ if not run_id:
87
+ run_id = _http.text(execution.get("run_id"))
88
+
89
+ artifacts = session_helpers.wait_for_artifact(cfg, resolved_session, artifact)
90
+ path = session_helpers.download_artifact(cfg, resolved_session, artifact, dest)
91
+ return AskArtifactResult(
92
+ trace_id=trace_id,
93
+ run_id=run_id,
94
+ session_id=resolved_session,
95
+ artifacts=tuple(artifacts),
96
+ output_path=path,
97
+ candidates=candidates,
98
+ plan=plan,
99
+ )
100
+
101
+ def start_ask(
102
+ self,
103
+ message: str,
104
+ *,
105
+ entity_refs: list[dict[str, str]] | None = None,
106
+ inputs: dict[str, str] | None = None,
107
+ session_id: str | None = None,
108
+ source_app: str | None = None,
109
+ source_metadata: dict[str, str] | None = None,
110
+ ) -> dict[str, Any]:
111
+ """POST /guild/api/v1/guild/ask/start."""
112
+ body: dict[str, Any] = {"message": message}
113
+ if entity_refs:
114
+ body["entity_refs"] = entity_refs
115
+ if inputs:
116
+ body["inputs"] = inputs
117
+ if session_id:
118
+ body["session_id"] = session_id
119
+ if source_app:
120
+ body["source_app"] = source_app
121
+ if source_metadata:
122
+ body["source_metadata"] = source_metadata
123
+
124
+ url = (
125
+ f"{self._config.aiden_url()}/api/v1/guild/ask/start"
126
+ f"{_http.query(self._config.org_id)}"
127
+ )
128
+ data = _http.request(
129
+ url,
130
+ self._config.api_token,
131
+ method="POST",
132
+ body=json.dumps(body).encode("utf-8"),
133
+ content_type="application/json",
134
+ )
135
+ if not isinstance(data, dict):
136
+ raise StackgenError(f"unexpected startAskGuild payload: {data!r}")
137
+
138
+ inputs_required = data.get("inputs_required") or []
139
+ if inputs_required:
140
+ raise StackgenError(
141
+ "Ask requires additional inputs before execution can start: "
142
+ f"{inputs_required}"
143
+ )
144
+ return data
145
+
146
+ def wait_for_execution(self, trace_id: str) -> dict[str, Any]:
147
+ """Poll GET /guild/api/v1/executions/{traceId} until settled."""
148
+ cfg = self._config
149
+ deadline = time.monotonic() + cfg.timeout_seconds
150
+ url = (
151
+ f"{cfg.aiden_url()}/api/v1/executions/{trace_id}"
152
+ f"{_http.query(cfg.org_id)}"
153
+ )
154
+ while time.monotonic() < deadline:
155
+ data = _http.request(url, cfg.api_token)
156
+ if not isinstance(data, dict):
157
+ raise StackgenError(f"unexpected getExecutionTrace payload: {data!r}")
158
+ execution = data.get("execution") or {}
159
+ status = _http.text(execution.get("status")).casefold()
160
+ trace_settled = execution.get("trace_settled") is True
161
+ if trace_settled and status in _TERMINAL_STATUSES:
162
+ if status == "error":
163
+ raise StackgenError(
164
+ f"execution {trace_id} ended with status error"
165
+ )
166
+ return data
167
+ time.sleep(cfg.poll_interval_seconds)
168
+ raise TimeoutError("execution", cfg.timeout_seconds)
@@ -0,0 +1,78 @@
1
+ """Aiden namespace — public methods for platform APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from stackgen.aiden.ask import AskArtifactJourney, AskArtifactResult
8
+ from stackgen.aiden.webhook import WebhookReportJourney, WebhookReportResult
9
+ from stackgen.config import StackgenConfig
10
+
11
+
12
+ class AidenNamespace:
13
+ """Aiden platform APIs (sessions, webhooks, artifacts, Ask Guild)."""
14
+
15
+ def __init__(self, config: StackgenConfig) -> None:
16
+ self._config = config
17
+ self._webhook = WebhookReportJourney(config)
18
+ self._ask = AskArtifactJourney(config)
19
+
20
+ def run_webhook_and_download_report(
21
+ self,
22
+ payload: str,
23
+ *,
24
+ artifact_name: str | None = None,
25
+ output_path: str | None = None,
26
+ ) -> WebhookReportResult:
27
+ """Trigger a webhook, wait for the session artifact, download it."""
28
+ return self._webhook.run(
29
+ payload, artifact_name=artifact_name, output_path=output_path
30
+ )
31
+
32
+ def run_ask_and_download_artifact(
33
+ self,
34
+ message: str,
35
+ *,
36
+ entity_refs: list[dict[str, str]] | None = None,
37
+ artifact_name: str | None = None,
38
+ output_path: str | None = None,
39
+ inputs: dict[str, str] | None = None,
40
+ session_id: str | None = None,
41
+ source_app: str | None = None,
42
+ source_metadata: dict[str, str] | None = None,
43
+ ) -> AskArtifactResult:
44
+ """Start Ask Guild, poll execution, wait for artifact, download it."""
45
+ return self._ask.run(
46
+ message,
47
+ entity_refs=entity_refs,
48
+ artifact_name=artifact_name,
49
+ output_path=output_path,
50
+ inputs=inputs,
51
+ session_id=session_id,
52
+ source_app=source_app,
53
+ source_metadata=source_metadata,
54
+ )
55
+
56
+ def start_ask(
57
+ self,
58
+ message: str,
59
+ *,
60
+ entity_refs: list[dict[str, str]] | None = None,
61
+ inputs: dict[str, str] | None = None,
62
+ session_id: str | None = None,
63
+ source_app: str | None = None,
64
+ source_metadata: dict[str, str] | None = None,
65
+ ) -> dict[str, Any]:
66
+ """POST /guild/api/v1/guild/ask/start (advanced callers)."""
67
+ return self._ask.start_ask(
68
+ message,
69
+ entity_refs=entity_refs,
70
+ inputs=inputs,
71
+ session_id=session_id,
72
+ source_app=source_app,
73
+ source_metadata=source_metadata,
74
+ )
75
+
76
+ def wait_for_execution(self, trace_id: str) -> dict[str, Any]:
77
+ """Poll GET /guild/api/v1/executions/{traceId} until settled."""
78
+ return self._ask.wait_for_execution(trace_id)
@@ -17,7 +17,7 @@ def pick_run(
17
17
  Preference order:
18
18
  1. Exact match on ``id`` / ``run_id`` to ``dispatch_run_id``
19
19
  2. First run whose id is not in ``known_run_ids`` (new since pre-trigger snapshot)
20
- 3. If no known ids and runs exist, the first run (best-effort on older motherships)
20
+ 3. If no known ids and runs exist, the first run (best-effort fallback)
21
21
  """
22
22
  if dispatch_run_id:
23
23
  for item in runs:
@@ -0,0 +1,46 @@
1
+ """Shared session artifact wait/download helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ import urllib.parse
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from stackgen import _http
11
+ from stackgen.config import StackgenConfig
12
+ from stackgen.errors import TimeoutError
13
+
14
+
15
+ def wait_for_artifact(
16
+ config: StackgenConfig, session_id: str, artifact_name: str
17
+ ) -> list[dict[str, Any]]:
18
+ """Poll session artifacts until ``artifact_name`` appears (case-insensitive)."""
19
+ deadline = time.monotonic() + config.timeout_seconds
20
+ url = (
21
+ f"{config.aiden_url()}/api/v1/sessions/{session_id}/artifacts"
22
+ f"{_http.query(config.org_id)}"
23
+ )
24
+ want = artifact_name.casefold()
25
+ artifacts: list[dict[str, Any]] = []
26
+ while time.monotonic() < deadline:
27
+ artifacts = _http.as_list(_http.request(url, config.api_token))
28
+ if any(_http.text(item.get("name")).casefold() == want for item in artifacts):
29
+ return artifacts
30
+ time.sleep(config.poll_interval_seconds)
31
+ raise TimeoutError(artifact_name, config.timeout_seconds)
32
+
33
+
34
+ def download_artifact(
35
+ config: StackgenConfig, session_id: str, artifact_name: str, output_path: str
36
+ ) -> Path:
37
+ """Download a session artifact to ``output_path``."""
38
+ encoded = urllib.parse.quote(artifact_name, safe="")
39
+ url = (
40
+ f"{config.aiden_url()}/api/v1/sessions/{session_id}/artifacts/"
41
+ f"{encoded}/download{_http.query(config.org_id)}"
42
+ )
43
+ raw = _http.request(url, config.api_token, expect_json=False)
44
+ dest = Path(output_path)
45
+ dest.write_bytes(raw)
46
+ return dest
@@ -0,0 +1,81 @@
1
+ """Aiden webhook → session-report journey."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from stackgen import _http
11
+ from stackgen.aiden import sessions as session_helpers
12
+ from stackgen.config import StackgenConfig
13
+ from stackgen.errors import StackgenError
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class WebhookReportResult:
18
+ """Result of trigger → wait → download session-report."""
19
+
20
+ webhook_id: str
21
+ invocation_id: str
22
+ session_id: str
23
+ artifacts: tuple[dict[str, Any], ...]
24
+ output_path: Path
25
+
26
+
27
+ class WebhookReportJourney:
28
+ """Trigger a webhook, wait for the session artifact, and download it.
29
+
30
+ Uses ``webhook_token`` for POST trigger and ``api_token`` for poll/download.
31
+ """
32
+
33
+ def __init__(self, config: StackgenConfig) -> None:
34
+ self._config = config
35
+
36
+ def run(
37
+ self,
38
+ payload: str,
39
+ *,
40
+ artifact_name: str | None = None,
41
+ output_path: str | None = None,
42
+ ) -> WebhookReportResult:
43
+ cfg = self._config
44
+ if not cfg.webhook_token:
45
+ raise ValueError("webhook_token is required for run_webhook_and_download_report")
46
+
47
+ artifact = artifact_name or cfg.artifact_name
48
+ dest = output_path or cfg.output_path
49
+
50
+ trigger = self._trigger(payload)
51
+ webhook_id = _http.text(trigger.get("webhook_id")) or _http.text(cfg.webhook_id)
52
+ invocation_id = _http.text(trigger.get("invocation_id"))
53
+ session_id = _http.text(trigger.get("session_id"))
54
+
55
+ if not session_id:
56
+ raise StackgenError(
57
+ f"trigger response missing session_id; body={json.dumps(trigger)}"
58
+ )
59
+
60
+ artifacts = session_helpers.wait_for_artifact(cfg, session_id, artifact)
61
+ path = session_helpers.download_artifact(cfg, session_id, artifact, dest)
62
+ return WebhookReportResult(
63
+ webhook_id=webhook_id,
64
+ invocation_id=invocation_id,
65
+ session_id=session_id,
66
+ artifacts=tuple(artifacts),
67
+ output_path=path,
68
+ )
69
+
70
+ def _trigger(self, payload: str) -> dict[str, Any]:
71
+ url = f"{self._config.aiden_url()}/api/v1/webhooks/trigger{_http.query(self._config.org_id)}"
72
+ data = _http.request(
73
+ url,
74
+ self._config.webhook_token,
75
+ method="POST",
76
+ body=payload.encode("utf-8"),
77
+ content_type="text/plain",
78
+ )
79
+ if not isinstance(data, dict):
80
+ raise StackgenError(f"unexpected trigger payload: {data!r}")
81
+ return data
@@ -2,7 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  import sys
7
+ from dataclasses import replace
6
8
  from pathlib import Path
7
9
 
8
10
  from stackgen.client import StackgenClient
@@ -20,7 +22,13 @@ def main(argv: list[str] | None = None) -> None:
20
22
  sys.exit(1)
21
23
  payload = path.read_text(encoding="utf-8")
22
24
  try:
23
- client = StackgenClient(StackgenConfig.from_env())
25
+ # CLI-only: webhook fields may be supplied alongside the three default env vars.
26
+ cfg = replace(
27
+ StackgenConfig.from_env(),
28
+ webhook_token=os.environ.get("WEBHOOK_TOKEN", "").strip(),
29
+ webhook_id=os.environ.get("WEBHOOK_ID", "").strip(),
30
+ )
31
+ client = StackgenClient(cfg)
24
32
  result = client.aiden.run_webhook_and_download_report(payload)
25
33
  except (StackgenError, ValueError) as err:
26
34
  print(f"error: {err}", file=sys.stderr)