steerdev 0.4.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- steerdev-0.4.27.dist-info/METADATA +224 -0
- steerdev-0.4.27.dist-info/RECORD +57 -0
- steerdev-0.4.27.dist-info/WHEEL +4 -0
- steerdev-0.4.27.dist-info/entry_points.txt +2 -0
- steerdev_agent/__init__.py +10 -0
- steerdev_agent/api/__init__.py +32 -0
- steerdev_agent/api/activity.py +278 -0
- steerdev_agent/api/agents.py +145 -0
- steerdev_agent/api/client.py +158 -0
- steerdev_agent/api/commands.py +399 -0
- steerdev_agent/api/configs.py +238 -0
- steerdev_agent/api/context.py +306 -0
- steerdev_agent/api/events.py +294 -0
- steerdev_agent/api/hooks.py +178 -0
- steerdev_agent/api/implementation_plan.py +408 -0
- steerdev_agent/api/messages.py +231 -0
- steerdev_agent/api/prd.py +281 -0
- steerdev_agent/api/runs.py +526 -0
- steerdev_agent/api/sessions.py +403 -0
- steerdev_agent/api/specs.py +321 -0
- steerdev_agent/api/tasks.py +659 -0
- steerdev_agent/api/workflow_runs.py +351 -0
- steerdev_agent/api/workflows.py +191 -0
- steerdev_agent/cli.py +2254 -0
- steerdev_agent/config/__init__.py +19 -0
- steerdev_agent/config/models.py +236 -0
- steerdev_agent/config/platform.py +272 -0
- steerdev_agent/config/settings.py +62 -0
- steerdev_agent/daemon.py +675 -0
- steerdev_agent/executor/__init__.py +64 -0
- steerdev_agent/executor/base.py +121 -0
- steerdev_agent/executor/claude.py +328 -0
- steerdev_agent/executor/stream.py +163 -0
- steerdev_agent/git/__init__.py +1 -0
- steerdev_agent/handlers/__init__.py +5 -0
- steerdev_agent/handlers/prd.py +533 -0
- steerdev_agent/integration.py +334 -0
- steerdev_agent/prompt/__init__.py +10 -0
- steerdev_agent/prompt/builder.py +263 -0
- steerdev_agent/prompt/templates.py +422 -0
- steerdev_agent/py.typed +0 -0
- steerdev_agent/runner.py +829 -0
- steerdev_agent/setup/__init__.py +5 -0
- steerdev_agent/setup/claude_setup.py +560 -0
- steerdev_agent/setup/templates/claude_md_section.md +140 -0
- steerdev_agent/setup/templates/settings.json +69 -0
- steerdev_agent/setup/templates/skills/activity/SKILL.md +160 -0
- steerdev_agent/setup/templates/skills/context/SKILL.md +122 -0
- steerdev_agent/setup/templates/skills/git-workflow/SKILL.md +218 -0
- steerdev_agent/setup/templates/skills/progress-logging/SKILL.md +211 -0
- steerdev_agent/setup/templates/skills/specs-management/SKILL.md +161 -0
- steerdev_agent/setup/templates/skills/task-management/SKILL.md +343 -0
- steerdev_agent/setup/templates/steerdev.yaml +51 -0
- steerdev_agent/version.py +149 -0
- steerdev_agent/workflow/__init__.py +10 -0
- steerdev_agent/workflow/executor.py +494 -0
- steerdev_agent/workflow/memory.py +185 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Workflow runs API client for managing workflow execution."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal, Self
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from steerdev_agent.api.client import get_api_endpoint, get_api_key
|
|
10
|
+
|
|
11
|
+
WorkflowRunStatus = Literal["pending", "running", "completed", "failed", "cancelled"]
|
|
12
|
+
PhaseRunStatus = Literal["pending", "running", "completed", "failed", "skipped"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PhaseRunResponse(BaseModel):
|
|
16
|
+
"""Response model for phase run data."""
|
|
17
|
+
|
|
18
|
+
id: str
|
|
19
|
+
workflow_run_id: str
|
|
20
|
+
phase_id: str
|
|
21
|
+
phase_name: str
|
|
22
|
+
phase_type: str
|
|
23
|
+
status: PhaseRunStatus
|
|
24
|
+
attempt_number: int = 1
|
|
25
|
+
input_context: dict[str, Any] = Field(default_factory=dict)
|
|
26
|
+
output_context: dict[str, Any] = Field(default_factory=dict)
|
|
27
|
+
result_summary: str | None = None
|
|
28
|
+
started_at: str | None = None
|
|
29
|
+
completed_at: str | None = None
|
|
30
|
+
error_message: str | None = None
|
|
31
|
+
created_at: str
|
|
32
|
+
updated_at: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WorkflowRunResponse(BaseModel):
|
|
36
|
+
"""Response model for workflow run data."""
|
|
37
|
+
|
|
38
|
+
id: str
|
|
39
|
+
workflow_id: str
|
|
40
|
+
workflow_name: str
|
|
41
|
+
project_id: str
|
|
42
|
+
run_id: str | None = None
|
|
43
|
+
linear_issue_id: str | None = None
|
|
44
|
+
status: WorkflowRunStatus
|
|
45
|
+
current_phase_id: str | None = None
|
|
46
|
+
current_phase_name: str | None = None
|
|
47
|
+
context: dict[str, Any] = Field(default_factory=dict)
|
|
48
|
+
phases_completed: int = 0
|
|
49
|
+
phases_failed: int = 0
|
|
50
|
+
phases_skipped: int = 0
|
|
51
|
+
total_phases: int = 0
|
|
52
|
+
phase_runs: list[PhaseRunResponse] | None = None
|
|
53
|
+
started_at: str | None = None
|
|
54
|
+
completed_at: str | None = None
|
|
55
|
+
error_message: str | None = None
|
|
56
|
+
created_at: str
|
|
57
|
+
updated_at: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class WorkflowRunsClient:
|
|
61
|
+
"""Async HTTP client for workflow run lifecycle management.
|
|
62
|
+
|
|
63
|
+
Manages the full workflow run lifecycle: start, advance phases,
|
|
64
|
+
complete/fail phases, and track progress.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, api_key: str | None = None, timeout: float = 30.0) -> None:
|
|
68
|
+
"""Initialize the client.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
api_key: API key for authentication. If not provided, reads from STEERDEV_API_KEY.
|
|
72
|
+
timeout: Request timeout in seconds.
|
|
73
|
+
"""
|
|
74
|
+
self.api_key = api_key or get_api_key()
|
|
75
|
+
self.api_base = get_api_endpoint()
|
|
76
|
+
self.timeout = timeout
|
|
77
|
+
self._client: httpx.AsyncClient | None = None
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def headers(self) -> dict[str, str]:
|
|
81
|
+
"""Get request headers with authentication."""
|
|
82
|
+
return {
|
|
83
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
88
|
+
"""Get or create async HTTP client."""
|
|
89
|
+
if self._client is None:
|
|
90
|
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
|
91
|
+
return self._client
|
|
92
|
+
|
|
93
|
+
async def close(self) -> None:
|
|
94
|
+
"""Close the HTTP client."""
|
|
95
|
+
if self._client is not None:
|
|
96
|
+
await self._client.aclose()
|
|
97
|
+
self._client = None
|
|
98
|
+
|
|
99
|
+
async def __aenter__(self) -> Self:
|
|
100
|
+
"""Enter async context manager."""
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
104
|
+
"""Exit async context manager."""
|
|
105
|
+
await self.close()
|
|
106
|
+
|
|
107
|
+
async def start_workflow(
|
|
108
|
+
self,
|
|
109
|
+
workflow_id: str,
|
|
110
|
+
run_id: str | None = None,
|
|
111
|
+
linear_issue_id: str | None = None,
|
|
112
|
+
initial_context: dict[str, Any] | None = None,
|
|
113
|
+
) -> WorkflowRunResponse | None:
|
|
114
|
+
"""Start a new workflow run.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
workflow_id: ID of the workflow to execute.
|
|
118
|
+
run_id: Optional associated agent run ID.
|
|
119
|
+
linear_issue_id: Optional Linear issue being processed.
|
|
120
|
+
initial_context: Initial context data for the workflow.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Created workflow run or None on failure.
|
|
124
|
+
"""
|
|
125
|
+
client = await self._get_client()
|
|
126
|
+
logger.debug(f"Starting workflow {workflow_id}")
|
|
127
|
+
|
|
128
|
+
payload: dict[str, Any] = {"workflow_id": workflow_id}
|
|
129
|
+
if run_id:
|
|
130
|
+
payload["run_id"] = run_id
|
|
131
|
+
if linear_issue_id:
|
|
132
|
+
payload["linear_issue_id"] = linear_issue_id
|
|
133
|
+
if initial_context:
|
|
134
|
+
payload["initial_context"] = initial_context
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
response = await client.post(
|
|
138
|
+
f"{self.api_base}/workflow-runs",
|
|
139
|
+
headers=self.headers,
|
|
140
|
+
json=payload,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if response.status_code == 201:
|
|
144
|
+
return WorkflowRunResponse(**response.json())
|
|
145
|
+
|
|
146
|
+
logger.error(f"Failed to start workflow: {response.status_code} - {response.text}")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
except httpx.RequestError as e:
|
|
150
|
+
logger.error(f"Request error starting workflow: {e}")
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
async def get_workflow_run(
|
|
154
|
+
self,
|
|
155
|
+
workflow_run_id: str,
|
|
156
|
+
) -> WorkflowRunResponse | None:
|
|
157
|
+
"""Get a specific workflow run by ID.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
workflow_run_id: Workflow run ID to fetch.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Workflow run data or None if not found.
|
|
164
|
+
"""
|
|
165
|
+
client = await self._get_client()
|
|
166
|
+
logger.debug(f"Fetching workflow run {workflow_run_id}")
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
response = await client.get(
|
|
170
|
+
f"{self.api_base}/workflow-runs/{workflow_run_id}",
|
|
171
|
+
headers=self.headers,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if response.status_code == 200:
|
|
175
|
+
return WorkflowRunResponse(**response.json())
|
|
176
|
+
if response.status_code == 404:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
logger.error(f"Failed to get workflow run: {response.status_code} - {response.text}")
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
except httpx.RequestError as e:
|
|
183
|
+
logger.error(f"Request error getting workflow run: {e}")
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
async def advance_phase(
|
|
187
|
+
self,
|
|
188
|
+
workflow_run_id: str,
|
|
189
|
+
output_context: dict[str, Any] | None = None,
|
|
190
|
+
result_summary: str | None = None,
|
|
191
|
+
) -> WorkflowRunResponse | None:
|
|
192
|
+
"""Advance to the next phase after completing the current one.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
workflow_run_id: Workflow run ID to advance.
|
|
196
|
+
output_context: Context data produced by the current phase.
|
|
197
|
+
result_summary: Human-readable summary of the phase result.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Updated workflow run or None on failure.
|
|
201
|
+
"""
|
|
202
|
+
client = await self._get_client()
|
|
203
|
+
logger.debug(f"Advancing workflow run {workflow_run_id}")
|
|
204
|
+
|
|
205
|
+
payload: dict[str, Any] = {"action": "complete"}
|
|
206
|
+
if output_context:
|
|
207
|
+
payload["output_context"] = output_context
|
|
208
|
+
if result_summary:
|
|
209
|
+
payload["result_summary"] = result_summary
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
response = await client.post(
|
|
213
|
+
f"{self.api_base}/workflow-runs/{workflow_run_id}/advance",
|
|
214
|
+
headers=self.headers,
|
|
215
|
+
json=payload,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if response.status_code == 200:
|
|
219
|
+
return WorkflowRunResponse(**response.json())
|
|
220
|
+
|
|
221
|
+
logger.error(f"Failed to advance phase: {response.status_code} - {response.text}")
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
except httpx.RequestError as e:
|
|
225
|
+
logger.error(f"Request error advancing phase: {e}")
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
async def fail_phase(
|
|
229
|
+
self,
|
|
230
|
+
workflow_run_id: str,
|
|
231
|
+
error_message: str,
|
|
232
|
+
) -> WorkflowRunResponse | None:
|
|
233
|
+
"""Mark the current phase as failed.
|
|
234
|
+
|
|
235
|
+
The workflow engine will handle retries or skip logic based on
|
|
236
|
+
the phase configuration.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
workflow_run_id: Workflow run ID.
|
|
240
|
+
error_message: Error message describing the failure.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Updated workflow run or None on failure.
|
|
244
|
+
"""
|
|
245
|
+
client = await self._get_client()
|
|
246
|
+
logger.debug(f"Failing phase for workflow run {workflow_run_id}")
|
|
247
|
+
|
|
248
|
+
payload: dict[str, Any] = {
|
|
249
|
+
"action": "fail",
|
|
250
|
+
"error_message": error_message,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
response = await client.post(
|
|
255
|
+
f"{self.api_base}/workflow-runs/{workflow_run_id}/advance",
|
|
256
|
+
headers=self.headers,
|
|
257
|
+
json=payload,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if response.status_code == 200:
|
|
261
|
+
return WorkflowRunResponse(**response.json())
|
|
262
|
+
|
|
263
|
+
logger.error(f"Failed to fail phase: {response.status_code} - {response.text}")
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
except httpx.RequestError as e:
|
|
267
|
+
logger.error(f"Request error failing phase: {e}")
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
async def update_phase_run(
|
|
271
|
+
self,
|
|
272
|
+
workflow_run_id: str,
|
|
273
|
+
phase_id: str,
|
|
274
|
+
status: PhaseRunStatus | None = None,
|
|
275
|
+
output_context: dict[str, Any] | None = None,
|
|
276
|
+
result_summary: str | None = None,
|
|
277
|
+
error_message: str | None = None,
|
|
278
|
+
) -> PhaseRunResponse | None:
|
|
279
|
+
"""Update a phase run's status or context.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
workflow_run_id: Workflow run ID.
|
|
283
|
+
phase_id: Phase ID to update.
|
|
284
|
+
status: New phase run status.
|
|
285
|
+
output_context: Updated output context.
|
|
286
|
+
result_summary: Updated result summary.
|
|
287
|
+
error_message: Error message if status is failed.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Updated phase run or None on failure.
|
|
291
|
+
"""
|
|
292
|
+
client = await self._get_client()
|
|
293
|
+
logger.debug(f"Updating phase run {phase_id} for workflow run {workflow_run_id}")
|
|
294
|
+
|
|
295
|
+
payload: dict[str, Any] = {}
|
|
296
|
+
if status is not None:
|
|
297
|
+
payload["status"] = status
|
|
298
|
+
if output_context is not None:
|
|
299
|
+
payload["output_context"] = output_context
|
|
300
|
+
if result_summary is not None:
|
|
301
|
+
payload["result_summary"] = result_summary
|
|
302
|
+
if error_message is not None:
|
|
303
|
+
payload["error_message"] = error_message
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
response = await client.patch(
|
|
307
|
+
f"{self.api_base}/workflow-runs/{workflow_run_id}/phases/{phase_id}",
|
|
308
|
+
headers=self.headers,
|
|
309
|
+
json=payload,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if response.status_code == 200:
|
|
313
|
+
return PhaseRunResponse(**response.json())
|
|
314
|
+
|
|
315
|
+
logger.error(f"Failed to update phase run: {response.status_code} - {response.text}")
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
except httpx.RequestError as e:
|
|
319
|
+
logger.error(f"Request error updating phase run: {e}")
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
async def cancel_workflow_run(
|
|
323
|
+
self,
|
|
324
|
+
workflow_run_id: str,
|
|
325
|
+
) -> WorkflowRunResponse | None:
|
|
326
|
+
"""Cancel a workflow run.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
workflow_run_id: Workflow run ID to cancel.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Updated workflow run or None on failure.
|
|
333
|
+
"""
|
|
334
|
+
client = await self._get_client()
|
|
335
|
+
logger.debug(f"Cancelling workflow run {workflow_run_id}")
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
response = await client.delete(
|
|
339
|
+
f"{self.api_base}/workflow-runs/{workflow_run_id}",
|
|
340
|
+
headers=self.headers,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if response.status_code == 200:
|
|
344
|
+
return WorkflowRunResponse(**response.json())
|
|
345
|
+
|
|
346
|
+
logger.error(f"Failed to cancel workflow run: {response.status_code} - {response.text}")
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
except httpx.RequestError as e:
|
|
350
|
+
logger.error(f"Request error cancelling workflow run: {e}")
|
|
351
|
+
return None
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Workflows API client for fetching workflow definitions."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Self
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from steerdev_agent.api.client import get_api_endpoint, get_api_key
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkflowPhaseResponse(BaseModel):
|
|
13
|
+
"""Response model for workflow phase data."""
|
|
14
|
+
|
|
15
|
+
id: str
|
|
16
|
+
workflow_id: str
|
|
17
|
+
name: str
|
|
18
|
+
phase_type: str
|
|
19
|
+
phase_order: int
|
|
20
|
+
prompt_template: str | None = None
|
|
21
|
+
is_required: bool = True
|
|
22
|
+
max_retries: int = 0
|
|
23
|
+
config_ids: list[str] = Field(default_factory=list)
|
|
24
|
+
created_at: str
|
|
25
|
+
updated_at: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WorkflowResponse(BaseModel):
|
|
29
|
+
"""Response model for workflow data."""
|
|
30
|
+
|
|
31
|
+
id: str
|
|
32
|
+
project_id: str
|
|
33
|
+
name: str
|
|
34
|
+
description: str | None = None
|
|
35
|
+
is_default: bool = False
|
|
36
|
+
max_retries_per_phase: int = 3
|
|
37
|
+
phases: list[WorkflowPhaseResponse] = Field(default_factory=list)
|
|
38
|
+
created_at: str
|
|
39
|
+
updated_at: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WorkflowsClient:
|
|
43
|
+
"""Async HTTP client for fetching workflow definitions.
|
|
44
|
+
|
|
45
|
+
Workflows define multi-phase task execution patterns.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, api_key: str | None = None, timeout: float = 30.0) -> None:
|
|
49
|
+
"""Initialize the client.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
api_key: API key for authentication. If not provided, reads from STEERDEV_API_KEY.
|
|
53
|
+
timeout: Request timeout in seconds.
|
|
54
|
+
"""
|
|
55
|
+
self.api_key = api_key or get_api_key()
|
|
56
|
+
self.api_base = get_api_endpoint()
|
|
57
|
+
self.timeout = timeout
|
|
58
|
+
self._client: httpx.AsyncClient | None = None
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def headers(self) -> dict[str, str]:
|
|
62
|
+
"""Get request headers with authentication."""
|
|
63
|
+
return {
|
|
64
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
69
|
+
"""Get or create async HTTP client."""
|
|
70
|
+
if self._client is None:
|
|
71
|
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
|
72
|
+
return self._client
|
|
73
|
+
|
|
74
|
+
async def close(self) -> None:
|
|
75
|
+
"""Close the HTTP client."""
|
|
76
|
+
if self._client is not None:
|
|
77
|
+
await self._client.aclose()
|
|
78
|
+
self._client = None
|
|
79
|
+
|
|
80
|
+
async def __aenter__(self) -> Self:
|
|
81
|
+
"""Enter async context manager."""
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
85
|
+
"""Exit async context manager."""
|
|
86
|
+
await self.close()
|
|
87
|
+
|
|
88
|
+
async def get_workflow(self, workflow_id: str) -> WorkflowResponse | None:
|
|
89
|
+
"""Get a workflow by ID.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
workflow_id: Workflow ID to fetch.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Workflow data or None if not found.
|
|
96
|
+
"""
|
|
97
|
+
client = await self._get_client()
|
|
98
|
+
logger.debug(f"Fetching workflow {workflow_id}")
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
response = await client.get(
|
|
102
|
+
f"{self.api_base}/workflows/{workflow_id}",
|
|
103
|
+
headers=self.headers,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if response.status_code == 200:
|
|
107
|
+
return WorkflowResponse(**response.json())
|
|
108
|
+
if response.status_code == 404:
|
|
109
|
+
logger.warning(f"Workflow not found: {workflow_id}")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
logger.error(f"Failed to get workflow: {response.status_code} - {response.text}")
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
except httpx.RequestError as e:
|
|
116
|
+
logger.error(f"Request error getting workflow: {e}")
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
async def get_default_workflow(self, project_id: str) -> WorkflowResponse | None:
|
|
120
|
+
"""Get the default workflow for a project.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
project_id: Project ID to get default workflow for.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Default workflow or None if not found.
|
|
127
|
+
"""
|
|
128
|
+
client = await self._get_client()
|
|
129
|
+
logger.debug(f"Fetching default workflow for project {project_id}")
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
response = await client.get(
|
|
133
|
+
f"{self.api_base}/workflows",
|
|
134
|
+
headers=self.headers,
|
|
135
|
+
params={"project_id": project_id, "is_default": "true"},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if response.status_code == 200:
|
|
139
|
+
data = response.json()
|
|
140
|
+
workflows = data.get("workflows", [])
|
|
141
|
+
if workflows:
|
|
142
|
+
return WorkflowResponse(**workflows[0])
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
logger.error(
|
|
146
|
+
f"Failed to get default workflow: {response.status_code} - {response.text}"
|
|
147
|
+
)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
except httpx.RequestError as e:
|
|
151
|
+
logger.error(f"Request error getting default workflow: {e}")
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
async def list_workflows(
|
|
155
|
+
self,
|
|
156
|
+
project_id: str | None = None,
|
|
157
|
+
limit: int = 50,
|
|
158
|
+
) -> list[WorkflowResponse]:
|
|
159
|
+
"""List workflows with optional filters.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
project_id: Filter by project ID.
|
|
163
|
+
limit: Maximum number of workflows to return.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
List of workflows.
|
|
167
|
+
"""
|
|
168
|
+
client = await self._get_client()
|
|
169
|
+
logger.debug("Listing workflows")
|
|
170
|
+
|
|
171
|
+
params: dict[str, Any] = {"limit": limit}
|
|
172
|
+
if project_id:
|
|
173
|
+
params["project_id"] = project_id
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
response = await client.get(
|
|
177
|
+
f"{self.api_base}/workflows",
|
|
178
|
+
headers=self.headers,
|
|
179
|
+
params=params,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if response.status_code == 200:
|
|
183
|
+
data = response.json()
|
|
184
|
+
return [WorkflowResponse(**w) for w in data.get("workflows", [])]
|
|
185
|
+
|
|
186
|
+
logger.error(f"Failed to list workflows: {response.status_code} - {response.text}")
|
|
187
|
+
return []
|
|
188
|
+
|
|
189
|
+
except httpx.RequestError as e:
|
|
190
|
+
logger.error(f"Request error listing workflows: {e}")
|
|
191
|
+
return []
|