stackgen-sdk 0.1.5__tar.gz → 0.1.6__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.
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/PKG-INFO +2 -2
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/README.md +1 -1
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/__init__.py +19 -1
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/_version.py +2 -2
- stackgen_sdk-0.1.6/src/stackgen/aiden/__init__.py +29 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/aiden/ask.py +6 -2
- stackgen_sdk-0.1.6/src/stackgen/aiden/namespace.py +170 -0
- stackgen_sdk-0.1.6/src/stackgen/aiden/runs.py +137 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/aiden/webhook.py +17 -21
- stackgen_sdk-0.1.6/src/stackgen/aiden/webhook_ops.py +172 -0
- stackgen_sdk-0.1.6/src/stackgen/aiden/workflow_builder.py +198 -0
- stackgen_sdk-0.1.6/src/stackgen/aiden/workflow_publish.py +211 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/config.py +1 -1
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/.openapi-generator/FILES +45 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/__init__.py +30 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/api/aiden_api.py +6054 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/api/sre_api.py +6 -6
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/__init__.py +15 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/agent_status.py +89 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/execution_list_item.py +112 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/register_agent_request.py +107 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/register_agent_request_auto_approve_tools_inner.py +87 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/register_agent_request_hitl.py +87 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/session_export.py +100 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/session_export_part.py +94 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/session_export_part_kind.py +38 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/session_list_response.py +97 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/session_summary.py +119 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/stage_binding_write.py +95 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/terminate_session_response.py +91 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/trigger_webhook202_response.py +6 -2
- stackgen_sdk-0.1.6/src/stackgen/generated/models/workflow.py +117 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/workflow_stage_write.py +93 -0
- stackgen_sdk-0.1.6/src/stackgen/generated/models/workflow_write.py +129 -0
- stackgen_sdk-0.1.5/src/stackgen/aiden/__init__.py +0 -11
- stackgen_sdk-0.1.5/src/stackgen/aiden/namespace.py +0 -78
- stackgen_sdk-0.1.5/src/stackgen/aiden/runs.py +0 -36
- stackgen_sdk-0.1.5/src/stackgen/generated/api/aiden_api.py +0 -2384
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/.gitignore +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/pyproject.toml +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/_http.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/aiden/sessions.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/cli.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/client.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/errors.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/.openapi-generator/VERSION +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/.openapi-generator-ignore +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/api/__init__.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/api_client.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/api_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/configuration.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/exceptions.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_analysis_status.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_attention.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_categorization_summary.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_role.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_sort_by.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_status.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_summary.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_sync_run_status.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/alert_v1.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/artifact_info.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/ask_guild_request.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/ask_guild_start_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/candidate_type.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/dag_edge.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/dag_node.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/entity_ref.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/error_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/execution_meta.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/execution_structured_output.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigate_alert_request.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigate_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_evidence.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_evidence_kind.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_evidence_source.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_hypothesis.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_plain_summary.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_prior_incident.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_recommended_next_step.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_ref.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_status.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_structured_hypothesis_entry.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_structured_limitation_entry.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_structured_rca.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/investigation_triage_metadata.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/json_error.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/list_alerts_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/list_investigations_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/pagination.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/prior_session_policy.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/qualified_candidate.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/schedule_run_status.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/schedule_target_type.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/session.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/session_responder_kind.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/signal_severity.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/sync_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/trace_dag_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/webhook_run.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/webhook_run_detail.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/models/webhook_run_list_response.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/generated/rest.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/sre/__init__.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/sre/namespace.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/src/stackgen/vault/__init__.py +0 -0
- {stackgen_sdk-0.1.5 → stackgen_sdk-0.1.6}/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.
|
|
3
|
+
Version: 0.1.6
|
|
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/
|
|
@@ -37,7 +37,7 @@ from stackgen import StackgenClient, StackgenConfig
|
|
|
37
37
|
|
|
38
38
|
client = StackgenClient(
|
|
39
39
|
StackgenConfig(
|
|
40
|
-
base_url="https://
|
|
40
|
+
base_url="https://app.stackgen.com",
|
|
41
41
|
api_token="stackgen_…",
|
|
42
42
|
org_id="<project-uuid>",
|
|
43
43
|
webhook_token="sg_aios_…", # webhook journeys only
|
|
@@ -16,7 +16,7 @@ from stackgen import StackgenClient, StackgenConfig
|
|
|
16
16
|
|
|
17
17
|
client = StackgenClient(
|
|
18
18
|
StackgenConfig(
|
|
19
|
-
base_url="https://
|
|
19
|
+
base_url="https://app.stackgen.com",
|
|
20
20
|
api_token="stackgen_…",
|
|
21
21
|
org_id="<project-uuid>",
|
|
22
22
|
webhook_token="sg_aios_…", # webhook journeys only
|
|
@@ -6,7 +6,7 @@ Public entrypoint::
|
|
|
6
6
|
|
|
7
7
|
client = StackgenClient(
|
|
8
8
|
StackgenConfig(
|
|
9
|
-
base_url="https://
|
|
9
|
+
base_url="https://app.stackgen.com",
|
|
10
10
|
api_token="stackgen_…",
|
|
11
11
|
org_id="<project-uuid>",
|
|
12
12
|
)
|
|
@@ -18,14 +18,32 @@ from __future__ import annotations
|
|
|
18
18
|
from stackgen.client import StackgenClient
|
|
19
19
|
from stackgen.config import StackgenConfig
|
|
20
20
|
from stackgen.errors import HttpError, NotAllowlistedError, StackgenError, TimeoutError
|
|
21
|
+
from stackgen.aiden.workflow_builder import (
|
|
22
|
+
AgentDefinition,
|
|
23
|
+
StageDefinition,
|
|
24
|
+
WorkflowBuilder,
|
|
25
|
+
WorkflowDefinition,
|
|
26
|
+
create_agent,
|
|
27
|
+
create_stage,
|
|
28
|
+
create_workflow,
|
|
29
|
+
)
|
|
30
|
+
from stackgen.aiden.workflow_publish import PublishWorkflowResult
|
|
21
31
|
|
|
22
32
|
__all__ = [
|
|
33
|
+
"AgentDefinition",
|
|
23
34
|
"HttpError",
|
|
24
35
|
"NotAllowlistedError",
|
|
36
|
+
"PublishWorkflowResult",
|
|
25
37
|
"StackgenClient",
|
|
26
38
|
"StackgenConfig",
|
|
27
39
|
"StackgenError",
|
|
40
|
+
"StageDefinition",
|
|
28
41
|
"TimeoutError",
|
|
42
|
+
"WorkflowBuilder",
|
|
43
|
+
"WorkflowDefinition",
|
|
44
|
+
"create_agent",
|
|
45
|
+
"create_stage",
|
|
46
|
+
"create_workflow",
|
|
29
47
|
]
|
|
30
48
|
|
|
31
49
|
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 6)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Aiden (platform) namespace — webhooks, sessions, artifacts, Ask Guild, workflows."""
|
|
2
|
+
|
|
3
|
+
from stackgen.aiden.ask import AskArtifactResult
|
|
4
|
+
from stackgen.aiden.namespace import AidenNamespace
|
|
5
|
+
from stackgen.aiden.webhook import WebhookReportResult
|
|
6
|
+
from stackgen.aiden.workflow_builder import (
|
|
7
|
+
AgentDefinition,
|
|
8
|
+
StageDefinition,
|
|
9
|
+
WorkflowBuilder,
|
|
10
|
+
WorkflowDefinition,
|
|
11
|
+
create_agent,
|
|
12
|
+
create_stage,
|
|
13
|
+
create_workflow,
|
|
14
|
+
)
|
|
15
|
+
from stackgen.aiden.workflow_publish import PublishWorkflowResult
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AgentDefinition",
|
|
19
|
+
"AidenNamespace",
|
|
20
|
+
"AskArtifactResult",
|
|
21
|
+
"PublishWorkflowResult",
|
|
22
|
+
"StageDefinition",
|
|
23
|
+
"WebhookReportResult",
|
|
24
|
+
"WorkflowBuilder",
|
|
25
|
+
"WorkflowDefinition",
|
|
26
|
+
"create_agent",
|
|
27
|
+
"create_stage",
|
|
28
|
+
"create_workflow",
|
|
29
|
+
]
|
|
@@ -157,8 +157,12 @@ class AskArtifactJourney:
|
|
|
157
157
|
raise StackgenError(f"unexpected getExecutionTrace payload: {data!r}")
|
|
158
158
|
execution = data.get("execution") or {}
|
|
159
159
|
status = _http.text(execution.get("status")).casefold()
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
# Prefer explicit settle when the mothership sends it. Some builds
|
|
161
|
+
# omit trace_settled on the thin getExecutionTrace summary; treat
|
|
162
|
+
# terminal status alone as done so callers are not stuck forever.
|
|
163
|
+
settled = execution.get("trace_settled")
|
|
164
|
+
ready = status in _TERMINAL_STATUSES and settled is not False
|
|
165
|
+
if ready:
|
|
162
166
|
if status == "error":
|
|
163
167
|
raise StackgenError(
|
|
164
168
|
f"execution {trace_id} ended with status error"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Aiden namespace — public methods for platform APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from stackgen.aiden.ask import AskArtifactJourney, AskArtifactResult
|
|
9
|
+
from stackgen.aiden.webhook import WebhookReportJourney, WebhookReportResult
|
|
10
|
+
from stackgen.aiden.webhook_ops import WebhookOps
|
|
11
|
+
from stackgen.aiden.workflow_builder import AgentDefinition, WorkflowDefinition
|
|
12
|
+
from stackgen.aiden.workflow_publish import PublishWorkflowResult, WorkflowPublishJourney
|
|
13
|
+
from stackgen.config import StackgenConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AidenNamespace:
|
|
17
|
+
"""Aiden platform APIs (sessions, webhooks, artifacts, Ask Guild, workflows)."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: StackgenConfig) -> None:
|
|
20
|
+
self._config = config
|
|
21
|
+
self._webhook = WebhookReportJourney(config)
|
|
22
|
+
self._ask = AskArtifactJourney(config)
|
|
23
|
+
self._workflows = WorkflowPublishJourney(config)
|
|
24
|
+
self._webhook_ops = WebhookOps(config)
|
|
25
|
+
|
|
26
|
+
def run_webhook_and_download_report(
|
|
27
|
+
self,
|
|
28
|
+
payload: str,
|
|
29
|
+
*,
|
|
30
|
+
artifact_name: str | None = None,
|
|
31
|
+
output_path: str | None = None,
|
|
32
|
+
) -> WebhookReportResult:
|
|
33
|
+
"""Trigger a webhook, wait for the session artifact, download it."""
|
|
34
|
+
return self._webhook.run(
|
|
35
|
+
payload, artifact_name=artifact_name, output_path=output_path
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def trigger_webhook(self, payload: str) -> dict[str, Any]:
|
|
39
|
+
"""POST /guild/api/v1/webhooks/trigger (stateless; returns 202 body)."""
|
|
40
|
+
return self._webhook_ops.trigger_webhook(payload)
|
|
41
|
+
|
|
42
|
+
def get_webhook_run(self, webhook_id: str, invocation_id: str) -> dict[str, Any]:
|
|
43
|
+
"""GET webhook invocation detail by invocation UUID."""
|
|
44
|
+
return self._webhook_ops.get_webhook_run(webhook_id, invocation_id)
|
|
45
|
+
|
|
46
|
+
def list_webhook_runs(self, webhook_id: str) -> list[dict[str, Any]]:
|
|
47
|
+
"""List webhook invocations for a webhook id."""
|
|
48
|
+
return self._webhook_ops.list_webhook_runs(webhook_id)
|
|
49
|
+
|
|
50
|
+
def wait_for_webhook_session(
|
|
51
|
+
self,
|
|
52
|
+
webhook_id: str,
|
|
53
|
+
invocation_id: str,
|
|
54
|
+
*,
|
|
55
|
+
dispatch_run_id: str = "",
|
|
56
|
+
known_run_ids: frozenset[str] | None = None,
|
|
57
|
+
) -> str:
|
|
58
|
+
"""Poll webhook run detail until session_id is set."""
|
|
59
|
+
return self._webhook_ops.wait_for_webhook_session(
|
|
60
|
+
webhook_id,
|
|
61
|
+
invocation_id,
|
|
62
|
+
dispatch_run_id=dispatch_run_id,
|
|
63
|
+
known_run_ids=known_run_ids,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def get_execution_trace(self, trace_id: str) -> dict[str, Any]:
|
|
67
|
+
"""GET /guild/api/v1/executions/{traceId}."""
|
|
68
|
+
return self._webhook_ops.get_execution_trace(trace_id)
|
|
69
|
+
|
|
70
|
+
def list_executions(self, **params: Any) -> list[dict[str, Any]]:
|
|
71
|
+
"""GET /guild/api/v1/executions (optional session_id filter)."""
|
|
72
|
+
return self._webhook_ops.list_executions(**params)
|
|
73
|
+
|
|
74
|
+
def list_sessions(self, **params: Any) -> dict[str, Any]:
|
|
75
|
+
"""GET /guild/api/v1/sessions (search with q / source_app)."""
|
|
76
|
+
return self._webhook_ops.list_sessions(**params)
|
|
77
|
+
|
|
78
|
+
def get_session(self, session_id: str) -> dict[str, Any]:
|
|
79
|
+
"""GET /guild/api/v1/sessions/{sessionId}."""
|
|
80
|
+
return self._webhook_ops.get_session(session_id)
|
|
81
|
+
|
|
82
|
+
def list_session_artifacts(self, session_id: str) -> list[dict[str, Any]]:
|
|
83
|
+
"""GET session artifact metadata list."""
|
|
84
|
+
return self._webhook_ops.list_session_artifacts(session_id)
|
|
85
|
+
|
|
86
|
+
def download_session_artifact(
|
|
87
|
+
self,
|
|
88
|
+
session_id: str,
|
|
89
|
+
artifact_name: str,
|
|
90
|
+
output_path: str,
|
|
91
|
+
) -> Path:
|
|
92
|
+
"""Download one session artifact to a local path."""
|
|
93
|
+
return self._webhook_ops.download_session_artifact(
|
|
94
|
+
session_id, artifact_name, output_path
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def export_session(
|
|
98
|
+
self,
|
|
99
|
+
session_id: str,
|
|
100
|
+
*,
|
|
101
|
+
parts: str | None = None,
|
|
102
|
+
format: str = "json",
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
"""GET /guild/api/v1/sessions/{sessionId}/export (report / notes / evidence)."""
|
|
105
|
+
return self._webhook_ops.export_session(session_id, parts=parts, format=format)
|
|
106
|
+
|
|
107
|
+
def terminate_session(self, session_id: str, *, reason: str | None = None) -> dict[str, Any]:
|
|
108
|
+
"""POST /guild/api/v1/sessions/{sessionId}/terminate."""
|
|
109
|
+
return self._webhook_ops.terminate_session(session_id, reason=reason)
|
|
110
|
+
|
|
111
|
+
def run_ask_and_download_artifact(
|
|
112
|
+
self,
|
|
113
|
+
message: str,
|
|
114
|
+
*,
|
|
115
|
+
entity_refs: list[dict[str, str]] | None = None,
|
|
116
|
+
artifact_name: str | None = None,
|
|
117
|
+
output_path: str | None = None,
|
|
118
|
+
inputs: dict[str, str] | None = None,
|
|
119
|
+
session_id: str | None = None,
|
|
120
|
+
source_app: str | None = None,
|
|
121
|
+
source_metadata: dict[str, str] | None = None,
|
|
122
|
+
) -> AskArtifactResult:
|
|
123
|
+
"""Start Ask Guild, poll execution, wait for artifact, download it."""
|
|
124
|
+
return self._ask.run(
|
|
125
|
+
message,
|
|
126
|
+
entity_refs=entity_refs,
|
|
127
|
+
artifact_name=artifact_name,
|
|
128
|
+
output_path=output_path,
|
|
129
|
+
inputs=inputs,
|
|
130
|
+
session_id=session_id,
|
|
131
|
+
source_app=source_app,
|
|
132
|
+
source_metadata=source_metadata,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def start_ask(
|
|
136
|
+
self,
|
|
137
|
+
message: str,
|
|
138
|
+
*,
|
|
139
|
+
entity_refs: list[dict[str, str]] | None = None,
|
|
140
|
+
inputs: dict[str, str] | None = None,
|
|
141
|
+
session_id: str | None = None,
|
|
142
|
+
source_app: str | None = None,
|
|
143
|
+
source_metadata: dict[str, str] | None = None,
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
"""POST /guild/api/v1/guild/ask/start (advanced callers)."""
|
|
146
|
+
return self._ask.start_ask(
|
|
147
|
+
message,
|
|
148
|
+
entity_refs=entity_refs,
|
|
149
|
+
inputs=inputs,
|
|
150
|
+
session_id=session_id,
|
|
151
|
+
source_app=source_app,
|
|
152
|
+
source_metadata=source_metadata,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def wait_for_execution(self, trace_id: str) -> dict[str, Any]:
|
|
156
|
+
"""Poll GET /guild/api/v1/executions/{traceId} until settled."""
|
|
157
|
+
return self._ask.wait_for_execution(trace_id)
|
|
158
|
+
|
|
159
|
+
def publish_workflow(
|
|
160
|
+
self,
|
|
161
|
+
workflow: WorkflowDefinition,
|
|
162
|
+
*,
|
|
163
|
+
agents: list[AgentDefinition] | None = None,
|
|
164
|
+
approve: bool = True,
|
|
165
|
+
) -> PublishWorkflowResult:
|
|
166
|
+
"""Register/update agents and create/update a workflow, optionally approving it.
|
|
167
|
+
|
|
168
|
+
Pass a committed builder from ``create_workflow(...).then(...).commit()``.
|
|
169
|
+
"""
|
|
170
|
+
return self._workflows.publish(workflow, agents=agents, approve=approve)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Webhook run polling and selection helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from stackgen import _http
|
|
10
|
+
from stackgen.config import StackgenConfig
|
|
11
|
+
from stackgen.errors import StackgenError, TimeoutError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def pick_run(
|
|
15
|
+
runs: list[dict[str, Any]],
|
|
16
|
+
dispatch_run_id: str,
|
|
17
|
+
known_run_ids: frozenset[str],
|
|
18
|
+
) -> dict[str, Any] | None:
|
|
19
|
+
"""Choose the webhook run that corresponds to a new trigger.
|
|
20
|
+
|
|
21
|
+
Preference order:
|
|
22
|
+
1. Exact match on ``id`` / ``run_id`` to ``dispatch_run_id``
|
|
23
|
+
2. First run whose id is not in ``known_run_ids`` (new since pre-trigger snapshot)
|
|
24
|
+
3. If no known ids and runs exist, the first run (best-effort fallback)
|
|
25
|
+
"""
|
|
26
|
+
if dispatch_run_id:
|
|
27
|
+
for item in runs:
|
|
28
|
+
if dispatch_run_id in {
|
|
29
|
+
_http.text(item.get("id")),
|
|
30
|
+
_http.text(item.get("run_id")),
|
|
31
|
+
}:
|
|
32
|
+
return item
|
|
33
|
+
for item in runs:
|
|
34
|
+
if _http.text(item.get("id")) not in known_run_ids:
|
|
35
|
+
return item
|
|
36
|
+
if not runs:
|
|
37
|
+
return None
|
|
38
|
+
if not known_run_ids:
|
|
39
|
+
return runs[0]
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class WebhookRunPoller:
|
|
44
|
+
"""Poll webhook invocation rows until a session_id is available.
|
|
45
|
+
|
|
46
|
+
Mirrors the dual-path flow in Guild's webhook_session_artifact reference script.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: StackgenConfig) -> None:
|
|
50
|
+
self._config = config
|
|
51
|
+
|
|
52
|
+
def resolve_ids(
|
|
53
|
+
self, trigger: dict[str, Any]
|
|
54
|
+
) -> tuple[str, str]:
|
|
55
|
+
"""Pick webhook_id and invocation_id from the 202 body or config."""
|
|
56
|
+
cfg = self._config
|
|
57
|
+
webhook_id = _http.text(trigger.get("webhook_id")) or _http.text(cfg.webhook_id)
|
|
58
|
+
invocation_id = _http.text(trigger.get("invocation_id"))
|
|
59
|
+
if webhook_id:
|
|
60
|
+
return webhook_id, invocation_id
|
|
61
|
+
if _http.text(trigger.get("session_id")):
|
|
62
|
+
return "", invocation_id
|
|
63
|
+
raise StackgenError(
|
|
64
|
+
"trigger 202 did not include session_id or webhook_id; "
|
|
65
|
+
"set webhook_id for older motherships that only return run_id. "
|
|
66
|
+
f"body={json.dumps(trigger)}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def get_run(self, webhook_id: str, run_id: str) -> dict[str, Any]:
|
|
70
|
+
"""GET one webhook invocation (invocation UUID, not Temporal run_id)."""
|
|
71
|
+
url = (
|
|
72
|
+
f"{self._config.aiden_url()}/api/v1/webhooks/{webhook_id}/runs/{run_id}"
|
|
73
|
+
f"{_http.query(self._config.org_id)}"
|
|
74
|
+
)
|
|
75
|
+
data = _http.request(url, self._config.api_token)
|
|
76
|
+
if not isinstance(data, dict):
|
|
77
|
+
raise StackgenError(f"unexpected run payload: {data!r}")
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
def list_runs(self, webhook_id: str) -> list[dict[str, Any]]:
|
|
81
|
+
"""List invocations for a webhook."""
|
|
82
|
+
url = (
|
|
83
|
+
f"{self._config.aiden_url()}/api/v1/webhooks/{webhook_id}/runs"
|
|
84
|
+
f"{_http.query(self._config.org_id)}"
|
|
85
|
+
)
|
|
86
|
+
return _http.as_list(_http.request(url, self._config.api_token))
|
|
87
|
+
|
|
88
|
+
def snapshot_known_run_ids(self, webhook_id: str) -> frozenset[str]:
|
|
89
|
+
"""Capture invocation ids before trigger for older mothership matching."""
|
|
90
|
+
return frozenset(
|
|
91
|
+
_http.text(item.get("id"))
|
|
92
|
+
for item in self.list_runs(webhook_id)
|
|
93
|
+
if _http.text(item.get("id"))
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def wait_for_session(
|
|
97
|
+
self,
|
|
98
|
+
webhook_id: str,
|
|
99
|
+
invocation_id: str,
|
|
100
|
+
dispatch_run_id: str = "",
|
|
101
|
+
known_run_ids: frozenset[str] | None = None,
|
|
102
|
+
) -> str:
|
|
103
|
+
"""Poll the webhook invocation until Aiden attaches a session_id."""
|
|
104
|
+
cfg = self._config
|
|
105
|
+
known = known_run_ids if known_run_ids is not None else frozenset()
|
|
106
|
+
deadline = time.monotonic() + cfg.timeout_seconds
|
|
107
|
+
run_id_hint = invocation_id or dispatch_run_id
|
|
108
|
+
while time.monotonic() < deadline:
|
|
109
|
+
run: dict[str, Any] | None = None
|
|
110
|
+
if run_id_hint:
|
|
111
|
+
try:
|
|
112
|
+
run = self.get_run(webhook_id, run_id_hint)
|
|
113
|
+
except StackgenError:
|
|
114
|
+
run = None
|
|
115
|
+
if run is None:
|
|
116
|
+
run = pick_run(
|
|
117
|
+
self.list_runs(webhook_id),
|
|
118
|
+
dispatch_run_id,
|
|
119
|
+
known,
|
|
120
|
+
)
|
|
121
|
+
if run is None:
|
|
122
|
+
time.sleep(cfg.poll_interval_seconds)
|
|
123
|
+
continue
|
|
124
|
+
if not run_id_hint:
|
|
125
|
+
run_id_hint = _http.text(run.get("id")) or _http.text(run.get("run_id"))
|
|
126
|
+
|
|
127
|
+
status = _http.text(run.get("status")).casefold()
|
|
128
|
+
session_id = _http.text(run.get("session_id"))
|
|
129
|
+
if status in {"failed", "skipped"}:
|
|
130
|
+
raise StackgenError(
|
|
131
|
+
f"webhook invocation ended with status {status}: "
|
|
132
|
+
f"{run.get('error') or 'no error detail'}"
|
|
133
|
+
)
|
|
134
|
+
if session_id:
|
|
135
|
+
return session_id
|
|
136
|
+
time.sleep(cfg.poll_interval_seconds)
|
|
137
|
+
raise TimeoutError("webhook session", cfg.timeout_seconds)
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
10
9
|
from stackgen import _http
|
|
11
10
|
from stackgen.aiden import sessions as session_helpers
|
|
11
|
+
from stackgen.aiden.runs import WebhookRunPoller
|
|
12
|
+
from stackgen.aiden.webhook_ops import WebhookOps
|
|
12
13
|
from stackgen.config import StackgenConfig
|
|
13
|
-
from stackgen.errors import StackgenError
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass(frozen=True)
|
|
@@ -28,10 +28,14 @@ class WebhookReportJourney:
|
|
|
28
28
|
"""Trigger a webhook, wait for the session artifact, and download it.
|
|
29
29
|
|
|
30
30
|
Uses ``webhook_token`` for POST trigger and ``api_token`` for poll/download.
|
|
31
|
+
When the 202 body includes ``session_id``, skips run polling. Otherwise polls
|
|
32
|
+
GET webhook run detail until ``session_id`` is available.
|
|
31
33
|
"""
|
|
32
34
|
|
|
33
35
|
def __init__(self, config: StackgenConfig) -> None:
|
|
34
36
|
self._config = config
|
|
37
|
+
self._ops = WebhookOps(config)
|
|
38
|
+
self._poller = WebhookRunPoller(config)
|
|
35
39
|
|
|
36
40
|
def run(
|
|
37
41
|
self,
|
|
@@ -47,14 +51,19 @@ class WebhookReportJourney:
|
|
|
47
51
|
artifact = artifact_name or cfg.artifact_name
|
|
48
52
|
dest = output_path or cfg.output_path
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
session_id = _http.text(trigger.get("session_id"))
|
|
54
|
+
known_run_ids = frozenset[str]()
|
|
55
|
+
if cfg.webhook_id:
|
|
56
|
+
known_run_ids = self._poller.snapshot_known_run_ids(cfg.webhook_id)
|
|
54
57
|
|
|
58
|
+
trigger = self._ops.trigger_webhook(payload)
|
|
59
|
+
webhook_id, invocation_id = self._poller.resolve_ids(trigger)
|
|
60
|
+
session_id = _http.text(trigger.get("session_id"))
|
|
55
61
|
if not session_id:
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
session_id = self._poller.wait_for_session(
|
|
63
|
+
webhook_id,
|
|
64
|
+
invocation_id,
|
|
65
|
+
_http.text(trigger.get("run_id")),
|
|
66
|
+
known_run_ids,
|
|
58
67
|
)
|
|
59
68
|
|
|
60
69
|
artifacts = session_helpers.wait_for_artifact(cfg, session_id, artifact)
|
|
@@ -66,16 +75,3 @@ class WebhookReportJourney:
|
|
|
66
75
|
artifacts=tuple(artifacts),
|
|
67
76
|
output_path=path,
|
|
68
77
|
)
|
|
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
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Stateless Aiden webhook and session artifact API helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import urllib.parse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from stackgen import _http
|
|
10
|
+
from stackgen.aiden.runs import WebhookRunPoller
|
|
11
|
+
from stackgen.config import StackgenConfig
|
|
12
|
+
from stackgen.errors import StackgenError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _query_string(params: dict[str, Any]) -> str:
|
|
16
|
+
cleaned = {k: v for k, v in params.items() if v is not None and v != ""}
|
|
17
|
+
if not cleaned:
|
|
18
|
+
return ""
|
|
19
|
+
return "?" + urllib.parse.urlencode(cleaned)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WebhookOps:
|
|
23
|
+
"""One-shot webhook trigger, run polling, execution trace, and artifact APIs."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, config: StackgenConfig) -> None:
|
|
26
|
+
self._config = config
|
|
27
|
+
self._poller = WebhookRunPoller(config)
|
|
28
|
+
|
|
29
|
+
def trigger_webhook(self, payload: str) -> dict[str, Any]:
|
|
30
|
+
"""POST /guild/api/v1/webhooks/trigger using the webhook token."""
|
|
31
|
+
cfg = self._config
|
|
32
|
+
if not cfg.webhook_token:
|
|
33
|
+
raise ValueError("webhook_token is required for trigger_webhook")
|
|
34
|
+
url = f"{cfg.aiden_url()}/api/v1/webhooks/trigger{_http.query(cfg.org_id)}"
|
|
35
|
+
data = _http.request(
|
|
36
|
+
url,
|
|
37
|
+
cfg.webhook_token,
|
|
38
|
+
method="POST",
|
|
39
|
+
body=payload.encode("utf-8"),
|
|
40
|
+
content_type="text/plain",
|
|
41
|
+
)
|
|
42
|
+
if not isinstance(data, dict):
|
|
43
|
+
raise StackgenError(f"unexpected trigger payload: {data!r}")
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
def get_webhook_run(self, webhook_id: str, invocation_id: str) -> dict[str, Any]:
|
|
47
|
+
"""GET /guild/api/v1/webhooks/{webhook_id}/runs/{invocation_id}."""
|
|
48
|
+
return self._poller.get_run(webhook_id, invocation_id)
|
|
49
|
+
|
|
50
|
+
def list_webhook_runs(self, webhook_id: str) -> list[dict[str, Any]]:
|
|
51
|
+
"""GET /guild/api/v1/webhooks/{webhook_id}/runs."""
|
|
52
|
+
return self._poller.list_runs(webhook_id)
|
|
53
|
+
|
|
54
|
+
def wait_for_webhook_session(
|
|
55
|
+
self,
|
|
56
|
+
webhook_id: str,
|
|
57
|
+
invocation_id: str,
|
|
58
|
+
*,
|
|
59
|
+
dispatch_run_id: str = "",
|
|
60
|
+
known_run_ids: frozenset[str] | None = None,
|
|
61
|
+
) -> str:
|
|
62
|
+
"""Poll webhook run detail until session_id is set."""
|
|
63
|
+
return self._poller.wait_for_session(
|
|
64
|
+
webhook_id,
|
|
65
|
+
invocation_id,
|
|
66
|
+
dispatch_run_id,
|
|
67
|
+
known_run_ids,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def get_execution_trace(self, trace_id: str) -> dict[str, Any]:
|
|
71
|
+
"""GET /guild/api/v1/executions/{traceId}."""
|
|
72
|
+
url = (
|
|
73
|
+
f"{self._config.aiden_url()}/api/v1/executions/{trace_id}"
|
|
74
|
+
f"{_http.query(self._config.org_id)}"
|
|
75
|
+
)
|
|
76
|
+
data = _http.request(url, self._config.api_token)
|
|
77
|
+
if not isinstance(data, dict):
|
|
78
|
+
raise StackgenError(f"unexpected execution trace payload: {data!r}")
|
|
79
|
+
return data
|
|
80
|
+
|
|
81
|
+
def list_executions(self, **params: Any) -> list[dict[str, Any]]:
|
|
82
|
+
"""GET /guild/api/v1/executions (optional session_id, agent_name, limit, offset)."""
|
|
83
|
+
query = dict(params)
|
|
84
|
+
if self._config.org_id and "orgId" not in query:
|
|
85
|
+
query["orgId"] = self._config.org_id
|
|
86
|
+
url = f"{self._config.aiden_url()}/api/v1/executions{_query_string(query)}"
|
|
87
|
+
return _http.as_list(_http.request(url, self._config.api_token))
|
|
88
|
+
|
|
89
|
+
def list_sessions(self, **params: Any) -> dict[str, Any]:
|
|
90
|
+
"""GET /guild/api/v1/sessions (optional q, source_app, limit, offset)."""
|
|
91
|
+
query = dict(params)
|
|
92
|
+
if self._config.org_id and "orgId" not in query:
|
|
93
|
+
query["orgId"] = self._config.org_id
|
|
94
|
+
url = f"{self._config.aiden_url()}/api/v1/sessions{_query_string(query)}"
|
|
95
|
+
data = _http.request(url, self._config.api_token)
|
|
96
|
+
if not isinstance(data, dict):
|
|
97
|
+
raise StackgenError(f"unexpected session list payload: {data!r}")
|
|
98
|
+
return data
|
|
99
|
+
|
|
100
|
+
def get_session(self, session_id: str) -> dict[str, Any]:
|
|
101
|
+
"""GET /guild/api/v1/sessions/{sessionId}."""
|
|
102
|
+
url = (
|
|
103
|
+
f"{self._config.aiden_url()}/api/v1/sessions/{session_id}"
|
|
104
|
+
f"{_http.query(self._config.org_id)}"
|
|
105
|
+
)
|
|
106
|
+
data = _http.request(url, self._config.api_token)
|
|
107
|
+
if not isinstance(data, dict):
|
|
108
|
+
raise StackgenError(f"unexpected get session payload: {data!r}")
|
|
109
|
+
return data
|
|
110
|
+
|
|
111
|
+
def list_session_artifacts(self, session_id: str) -> list[dict[str, Any]]:
|
|
112
|
+
"""GET /guild/api/v1/sessions/{sessionId}/artifacts."""
|
|
113
|
+
url = (
|
|
114
|
+
f"{self._config.aiden_url()}/api/v1/sessions/{session_id}/artifacts"
|
|
115
|
+
f"{_http.query(self._config.org_id)}"
|
|
116
|
+
)
|
|
117
|
+
return _http.as_list(_http.request(url, self._config.api_token))
|
|
118
|
+
|
|
119
|
+
def download_session_artifact(
|
|
120
|
+
self,
|
|
121
|
+
session_id: str,
|
|
122
|
+
artifact_name: str,
|
|
123
|
+
output_path: str,
|
|
124
|
+
) -> Path:
|
|
125
|
+
"""GET /guild/api/v1/sessions/{sessionId}/artifacts/{name}/download."""
|
|
126
|
+
encoded = urllib.parse.quote(artifact_name, safe="")
|
|
127
|
+
url = (
|
|
128
|
+
f"{self._config.aiden_url()}/api/v1/sessions/{session_id}/artifacts/"
|
|
129
|
+
f"{encoded}/download{_http.query(self._config.org_id)}"
|
|
130
|
+
)
|
|
131
|
+
raw = _http.request(url, self._config.api_token, expect_json=False)
|
|
132
|
+
dest = Path(output_path)
|
|
133
|
+
dest.write_bytes(raw)
|
|
134
|
+
return dest
|
|
135
|
+
|
|
136
|
+
def export_session(
|
|
137
|
+
self,
|
|
138
|
+
session_id: str,
|
|
139
|
+
*,
|
|
140
|
+
parts: str | None = None,
|
|
141
|
+
format: str = "json",
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
"""GET /guild/api/v1/sessions/{sessionId}/export."""
|
|
144
|
+
query: dict[str, Any] = {"format": format}
|
|
145
|
+
if parts:
|
|
146
|
+
query["parts"] = parts
|
|
147
|
+
if self._config.org_id:
|
|
148
|
+
query["orgId"] = self._config.org_id
|
|
149
|
+
url = (
|
|
150
|
+
f"{self._config.aiden_url()}/api/v1/sessions/{session_id}/export"
|
|
151
|
+
f"{_query_string(query)}"
|
|
152
|
+
)
|
|
153
|
+
data = _http.request(url, self._config.api_token)
|
|
154
|
+
if not isinstance(data, dict):
|
|
155
|
+
raise StackgenError(f"unexpected session export payload: {data!r}")
|
|
156
|
+
return data
|
|
157
|
+
|
|
158
|
+
def terminate_session(self, session_id: str, *, reason: str | None = None) -> dict[str, Any]:
|
|
159
|
+
"""POST /guild/api/v1/sessions/{sessionId}/terminate."""
|
|
160
|
+
query: dict[str, Any] = {}
|
|
161
|
+
if self._config.org_id:
|
|
162
|
+
query["orgId"] = self._config.org_id
|
|
163
|
+
if reason:
|
|
164
|
+
query["reason"] = reason
|
|
165
|
+
url = (
|
|
166
|
+
f"{self._config.aiden_url()}/api/v1/sessions/{session_id}/terminate"
|
|
167
|
+
f"{_query_string(query)}"
|
|
168
|
+
)
|
|
169
|
+
data = _http.request(url, self._config.api_token, method="POST")
|
|
170
|
+
if not isinstance(data, dict):
|
|
171
|
+
raise StackgenError(f"unexpected terminate session payload: {data!r}")
|
|
172
|
+
return data
|