codex-autorunner 0.1.2__py3-none-any.whl → 1.0.0__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.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +15 -9
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -196
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .controller import FlowController
|
|
2
|
+
from .definition import FlowDefinition, StepFn, StepOutcome
|
|
3
|
+
from .models import (
|
|
4
|
+
FlowArtifact,
|
|
5
|
+
FlowEvent,
|
|
6
|
+
FlowEventType,
|
|
7
|
+
FlowRunRecord,
|
|
8
|
+
FlowRunStatus,
|
|
9
|
+
)
|
|
10
|
+
from .runtime import FlowRuntime
|
|
11
|
+
from .store import FlowStore
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"FlowController",
|
|
15
|
+
"FlowDefinition",
|
|
16
|
+
"StepFn",
|
|
17
|
+
"StepOutcome",
|
|
18
|
+
"FlowArtifact",
|
|
19
|
+
"FlowEvent",
|
|
20
|
+
"FlowEventType",
|
|
21
|
+
"FlowRunRecord",
|
|
22
|
+
"FlowRunStatus",
|
|
23
|
+
"FlowRuntime",
|
|
24
|
+
"FlowStore",
|
|
25
|
+
]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import uuid
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, AsyncGenerator, Callable, Dict, Optional, Set
|
|
6
|
+
|
|
7
|
+
from .definition import FlowDefinition
|
|
8
|
+
from .models import FlowEvent, FlowRunRecord, FlowRunStatus
|
|
9
|
+
from .runtime import FlowRuntime
|
|
10
|
+
from .store import FlowStore
|
|
11
|
+
|
|
12
|
+
_logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FlowController:
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
definition: FlowDefinition,
|
|
19
|
+
db_path: Path,
|
|
20
|
+
artifacts_root: Path,
|
|
21
|
+
):
|
|
22
|
+
self.definition = definition
|
|
23
|
+
self.db_path = db_path
|
|
24
|
+
self.artifacts_root = artifacts_root
|
|
25
|
+
self.store = FlowStore(db_path)
|
|
26
|
+
self._event_listeners: Set[Callable[[FlowEvent], None]] = set()
|
|
27
|
+
self._lock = asyncio.Lock()
|
|
28
|
+
|
|
29
|
+
def initialize(self) -> None:
|
|
30
|
+
self.artifacts_root.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
self.store.initialize()
|
|
32
|
+
|
|
33
|
+
def shutdown(self) -> None:
|
|
34
|
+
self.store.close()
|
|
35
|
+
|
|
36
|
+
async def start_flow(
|
|
37
|
+
self,
|
|
38
|
+
input_data: Dict[str, Any],
|
|
39
|
+
run_id: Optional[str] = None,
|
|
40
|
+
initial_state: Optional[Dict[str, Any]] = None,
|
|
41
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
42
|
+
) -> FlowRunRecord:
|
|
43
|
+
"""Create a new flow run record without executing the flow."""
|
|
44
|
+
if run_id is None:
|
|
45
|
+
run_id = str(uuid.uuid4())
|
|
46
|
+
|
|
47
|
+
async with self._lock:
|
|
48
|
+
existing = self.store.get_flow_run(run_id)
|
|
49
|
+
if existing:
|
|
50
|
+
raise ValueError(f"Flow run {run_id} already exists")
|
|
51
|
+
|
|
52
|
+
self._prepare_artifacts_dir(run_id)
|
|
53
|
+
|
|
54
|
+
record = self.store.create_flow_run(
|
|
55
|
+
run_id=run_id,
|
|
56
|
+
flow_type=self.definition.flow_type,
|
|
57
|
+
input_data=input_data,
|
|
58
|
+
metadata=metadata,
|
|
59
|
+
state=initial_state or {},
|
|
60
|
+
current_step=self.definition.initial_step,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return record
|
|
64
|
+
|
|
65
|
+
async def run_flow(
|
|
66
|
+
self, run_id: str, initial_state: Optional[Dict[str, Any]] = None
|
|
67
|
+
) -> FlowRunRecord:
|
|
68
|
+
"""Run or resume a flow to completion in-process (used by workers/tests)."""
|
|
69
|
+
runtime = FlowRuntime(
|
|
70
|
+
definition=self.definition,
|
|
71
|
+
store=self.store,
|
|
72
|
+
emit_event=self._emit_event,
|
|
73
|
+
)
|
|
74
|
+
return await runtime.run_flow(run_id=run_id, initial_state=initial_state)
|
|
75
|
+
|
|
76
|
+
async def stop_flow(self, run_id: str) -> FlowRunRecord:
|
|
77
|
+
record = self.store.set_stop_requested(run_id, True)
|
|
78
|
+
if not record:
|
|
79
|
+
raise ValueError(f"Flow run {run_id} not found")
|
|
80
|
+
|
|
81
|
+
if record.status == FlowRunStatus.RUNNING:
|
|
82
|
+
updated = self.store.update_flow_run_status(
|
|
83
|
+
run_id=run_id,
|
|
84
|
+
status=FlowRunStatus.STOPPING,
|
|
85
|
+
)
|
|
86
|
+
if updated:
|
|
87
|
+
record = updated
|
|
88
|
+
|
|
89
|
+
updated = self.store.get_flow_run(run_id)
|
|
90
|
+
if not updated:
|
|
91
|
+
raise RuntimeError(f"Failed to get record for run {run_id}")
|
|
92
|
+
return updated
|
|
93
|
+
|
|
94
|
+
async def resume_flow(self, run_id: str) -> FlowRunRecord:
|
|
95
|
+
async with self._lock:
|
|
96
|
+
record = self.store.get_flow_run(run_id)
|
|
97
|
+
if not record:
|
|
98
|
+
raise ValueError(f"Flow run {run_id} not found")
|
|
99
|
+
|
|
100
|
+
if record.status == FlowRunStatus.RUNNING:
|
|
101
|
+
raise ValueError(f"Flow run {run_id} is already active")
|
|
102
|
+
|
|
103
|
+
cleared = self.store.set_stop_requested(run_id, False)
|
|
104
|
+
if not cleared:
|
|
105
|
+
raise RuntimeError(f"Failed to clear stop flag for run {run_id}")
|
|
106
|
+
return cleared
|
|
107
|
+
|
|
108
|
+
def get_status(self, run_id: str) -> Optional[FlowRunRecord]:
|
|
109
|
+
return self.store.get_flow_run(run_id)
|
|
110
|
+
|
|
111
|
+
def list_runs(self, status: Optional[FlowRunStatus] = None) -> list[FlowRunRecord]:
|
|
112
|
+
return self.store.list_flow_runs(
|
|
113
|
+
flow_type=self.definition.flow_type, status=status
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
async def stream_events(
|
|
117
|
+
self, run_id: str, after_seq: Optional[int] = None
|
|
118
|
+
) -> AsyncGenerator[FlowEvent, None]:
|
|
119
|
+
last_seq = after_seq
|
|
120
|
+
|
|
121
|
+
while True:
|
|
122
|
+
events = self.store.get_events(
|
|
123
|
+
run_id=run_id,
|
|
124
|
+
after_seq=last_seq,
|
|
125
|
+
limit=100,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
for event in events:
|
|
129
|
+
yield event
|
|
130
|
+
last_seq = event.seq
|
|
131
|
+
|
|
132
|
+
record = self.store.get_flow_run(run_id)
|
|
133
|
+
if (
|
|
134
|
+
record
|
|
135
|
+
and (record.status.is_terminal() or record.status.is_paused())
|
|
136
|
+
and not events
|
|
137
|
+
):
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
await asyncio.sleep(0.5)
|
|
141
|
+
|
|
142
|
+
def get_events(
|
|
143
|
+
self, run_id: str, after_seq: Optional[int] = None
|
|
144
|
+
) -> list[FlowEvent]:
|
|
145
|
+
return self.store.get_events(run_id=run_id, after_seq=after_seq)
|
|
146
|
+
|
|
147
|
+
def add_event_listener(self, listener: Callable[[FlowEvent], None]) -> None:
|
|
148
|
+
self._event_listeners.add(listener)
|
|
149
|
+
|
|
150
|
+
def remove_event_listener(self, listener: Callable[[FlowEvent], None]) -> None:
|
|
151
|
+
self._event_listeners.discard(listener)
|
|
152
|
+
|
|
153
|
+
def _emit_event(self, event: FlowEvent) -> None:
|
|
154
|
+
for listener in self._event_listeners:
|
|
155
|
+
try:
|
|
156
|
+
listener(event)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
_logger.exception("Error in event listener: %s", e)
|
|
159
|
+
|
|
160
|
+
def _prepare_artifacts_dir(self, run_id: str) -> Path:
|
|
161
|
+
artifacts_dir = self.artifacts_root / run_id
|
|
162
|
+
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
|
163
|
+
return artifacts_dir
|
|
164
|
+
|
|
165
|
+
def get_artifacts_dir(self, run_id: str) -> Optional[Path]:
|
|
166
|
+
artifacts_dir = self.artifacts_root / run_id
|
|
167
|
+
if artifacts_dir.exists():
|
|
168
|
+
return artifacts_dir
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def get_artifacts(self, run_id: str) -> list:
|
|
172
|
+
return self.store.get_artifacts(run_id)
|
|
173
|
+
|
|
174
|
+
async def stream_events_since(
|
|
175
|
+
self, run_id: str, start_seq: Optional[int] = None
|
|
176
|
+
) -> AsyncGenerator[FlowEvent, None]:
|
|
177
|
+
async for event in self.stream_events(run_id, after_seq=start_seq):
|
|
178
|
+
yield event
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Set, Union
|
|
4
|
+
|
|
5
|
+
from .models import FlowEventType, FlowRunRecord, FlowRunStatus
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StepOutcome:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
status: FlowRunStatus,
|
|
14
|
+
next_steps: Optional[Set[str]] = None,
|
|
15
|
+
output: Optional[Dict[str, Any]] = None,
|
|
16
|
+
error: Optional[str] = None,
|
|
17
|
+
):
|
|
18
|
+
self.status = status
|
|
19
|
+
self.next_steps = next_steps or set()
|
|
20
|
+
self.output = output or {}
|
|
21
|
+
self.error = error
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def continue_to(
|
|
25
|
+
cls, next_steps: Set[str], output: Optional[Dict[str, Any]] = None
|
|
26
|
+
) -> "StepOutcome":
|
|
27
|
+
return cls(status=FlowRunStatus.RUNNING, next_steps=next_steps, output=output)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def complete(cls, output: Optional[Dict[str, Any]] = None) -> "StepOutcome":
|
|
31
|
+
return cls(status=FlowRunStatus.COMPLETED, output=output)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def fail(cls, error: str, output: Optional[Dict[str, Any]] = None) -> "StepOutcome":
|
|
35
|
+
return cls(status=FlowRunStatus.FAILED, error=error, output=output)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def stop(cls, output: Optional[Dict[str, Any]] = None) -> "StepOutcome":
|
|
39
|
+
return cls(status=FlowRunStatus.STOPPED, output=output)
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def pause(cls, output: Optional[Dict[str, Any]] = None) -> "StepOutcome":
|
|
43
|
+
return cls(status=FlowRunStatus.PAUSED, output=output)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
EmitEventFn = Callable[[FlowEventType, Dict[str, Any]], None]
|
|
47
|
+
StepFn2 = Callable[[FlowRunRecord, Dict[str, Any]], Awaitable[StepOutcome]]
|
|
48
|
+
StepFn3 = Callable[
|
|
49
|
+
[FlowRunRecord, Dict[str, Any], Optional[EmitEventFn]], Awaitable[StepOutcome]
|
|
50
|
+
]
|
|
51
|
+
StepFn = Union[StepFn2, StepFn3]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class FlowDefinition:
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
flow_type: str,
|
|
58
|
+
initial_step: str,
|
|
59
|
+
steps: Dict[str, StepFn],
|
|
60
|
+
*,
|
|
61
|
+
name: Optional[str] = None,
|
|
62
|
+
description: Optional[str] = None,
|
|
63
|
+
input_schema: Optional[Dict[str, Any]] = None,
|
|
64
|
+
):
|
|
65
|
+
self.flow_type = flow_type
|
|
66
|
+
self.initial_step = initial_step
|
|
67
|
+
self.steps = steps
|
|
68
|
+
self.name = name or flow_type
|
|
69
|
+
self.description = description
|
|
70
|
+
self.input_schema = input_schema
|
|
71
|
+
|
|
72
|
+
def validate(self) -> None:
|
|
73
|
+
if self.initial_step not in self.steps:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"Initial step '{self.initial_step}' not found in steps: {list(self.steps.keys())}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
for step_id, step_fn in self.steps.items():
|
|
79
|
+
if not asyncio.iscoroutinefunction(step_fn):
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Step function for '{step_id}' must be async (coroutine function)"
|
|
82
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FlowRunStatus(str, Enum):
|
|
11
|
+
PENDING = "pending"
|
|
12
|
+
RUNNING = "running"
|
|
13
|
+
PAUSED = "paused"
|
|
14
|
+
STOPPING = "stopping"
|
|
15
|
+
STOPPED = "stopped"
|
|
16
|
+
COMPLETED = "completed"
|
|
17
|
+
FAILED = "failed"
|
|
18
|
+
|
|
19
|
+
def is_terminal(self) -> bool:
|
|
20
|
+
return self in {self.COMPLETED, self.FAILED, self.STOPPED}
|
|
21
|
+
|
|
22
|
+
def is_active(self) -> bool:
|
|
23
|
+
return self in {self.PENDING, self.RUNNING, self.STOPPING}
|
|
24
|
+
|
|
25
|
+
def is_paused(self) -> bool:
|
|
26
|
+
return self == self.PAUSED
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FlowEventType(str, Enum):
|
|
30
|
+
STEP_STARTED = "step_started"
|
|
31
|
+
STEP_PROGRESS = "step_progress"
|
|
32
|
+
STEP_COMPLETED = "step_completed"
|
|
33
|
+
STEP_FAILED = "step_failed"
|
|
34
|
+
AGENT_STREAM_DELTA = "agent_stream_delta"
|
|
35
|
+
APP_SERVER_EVENT = "app_server_event"
|
|
36
|
+
TOKEN_USAGE = "token_usage"
|
|
37
|
+
FLOW_STARTED = "flow_started"
|
|
38
|
+
FLOW_STOPPED = "flow_stopped"
|
|
39
|
+
FLOW_RESUMED = "flow_resumed"
|
|
40
|
+
FLOW_COMPLETED = "flow_completed"
|
|
41
|
+
FLOW_FAILED = "flow_failed"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FlowRunRecord(BaseModel):
|
|
45
|
+
id: str
|
|
46
|
+
flow_type: str
|
|
47
|
+
status: FlowRunStatus
|
|
48
|
+
input_data: Dict[str, Any] = Field(default_factory=dict)
|
|
49
|
+
state: Dict[str, Any] = Field(default_factory=dict)
|
|
50
|
+
current_step: Optional[str] = None
|
|
51
|
+
stop_requested: bool = False
|
|
52
|
+
created_at: str
|
|
53
|
+
started_at: Optional[str] = None
|
|
54
|
+
finished_at: Optional[str] = None
|
|
55
|
+
error_message: Optional[str] = None
|
|
56
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FlowEvent(BaseModel):
|
|
60
|
+
seq: int
|
|
61
|
+
id: str
|
|
62
|
+
run_id: str
|
|
63
|
+
event_type: FlowEventType
|
|
64
|
+
timestamp: str
|
|
65
|
+
data: Dict[str, Any] = Field(default_factory=dict)
|
|
66
|
+
step_id: Optional[str] = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FlowArtifact(BaseModel):
|
|
70
|
+
id: str
|
|
71
|
+
run_id: str
|
|
72
|
+
kind: str
|
|
73
|
+
path: str
|
|
74
|
+
created_at: str
|
|
75
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|