create-windy 0.4.1 → 0.5.1
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.
- package/dist/cli.js +292 -125
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
- package/template/apps/agent-server/adk_web_agents/services.py +25 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
- package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
- package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
- package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
- package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
- package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
- package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
- package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
- package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
- package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
- package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
- package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
- package/template/apps/agent-server/tests/test_report_store.py +286 -0
- package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
- package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
- package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
- package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
- package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
- package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
- package/template/apps/server/src/agent/debug-session-id.ts +37 -0
- package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
- package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
- package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
- package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
- package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
- package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
- package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
- package/template/apps/server/src/agent/run-authorization.ts +32 -1
- package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +5 -0
- package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
- package/template/apps/server/src/agent/run-facade.test.ts +78 -0
- package/template/apps/server/src/agent/run-facade.ts +33 -3
- package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
- package/template/apps/server/src/agent/run-input-verification.ts +41 -0
- package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
- package/template/apps/server/src/agent/tool-host.test.ts +1 -0
- package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
- package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
- package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
- package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
- package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
- package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
- package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
- package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
- package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
- package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
- package/template/apps/server/src/license/runtime-service.test.ts +6 -0
- package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
- package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
- package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
- package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
- package/template/apps/server/src/module-host/initialize.ts +6 -197
- package/template/apps/server/src/module-host/request-context.ts +1 -0
- package/template/apps/server/src/module-host/scoped-host.ts +214 -0
- package/template/apps/server/src/module-host.ts +1 -0
- package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
- package/template/apps/server/src/runtime-adk-debug.ts +8 -2
- package/template/apps/server/src/system/operations.test.ts +54 -0
- package/template/apps/server/src/system/operations.ts +23 -1
- package/template/apps/server/src/system/seed.test.ts +4 -0
- package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
- package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
- package/template/apps/web/src/layout/AppSidebar.vue +6 -1
- package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
- package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
- package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
- package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
- package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
- package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
- package/template/apps/web/src/router/error-navigation.ts +22 -0
- package/template/apps/web/src/router/index.ts +12 -23
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
- package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
- package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
- package/template/apps/web/src/services/operations-api.ts +28 -0
- package/template/docker-compose.agent-debug.yml +1 -1
- package/template/docs/architecture/ai-runtime.md +2 -1
- package/template/docs/architecture/module-host.md +3 -0
- package/template/docs/platform/agent-run-attestation.md +65 -0
- package/template/docs/platform/agent-runtime-observability.md +135 -0
- package/template/docs/platform/agent-runtime.md +47 -3
- package/template/docs/platform/operations-overview.md +13 -0
- package/template/docs/platform/platform-authentication.md +4 -3
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
- package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
- package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
- package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
- package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
- package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
- package/template/packages/database/drizzle/meta/_journal.json +21 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
- package/template/packages/database/src/schema/agent-runtime.ts +114 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-admin-routes.ts +10 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
- package/template/packages/modules/src/system-api-control-plane.ts +17 -1
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +8 -0
- package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
- package/template/packages/modules/src/system-module-catalog.ts +15 -4
- package/template/packages/modules/src/system-modules-access.test.ts +69 -0
- package/template/packages/modules/src/system-modules.ts +13 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
- package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
- package/template/packages/server-sdk/src/module-host.ts +5 -0
- package/template/packages/shared/index.ts +3 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/ai-runtime-report.ts +134 -0
- package/template/scripts/lib/postgres-regression.ts +201 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from psycopg import AsyncConnection
|
|
6
|
+
|
|
7
|
+
from .report_types import NonTerminalRunStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def start_or_resume_run(
|
|
11
|
+
connection: AsyncConnection[dict[str, Any]],
|
|
12
|
+
run_id: str,
|
|
13
|
+
started_at: float,
|
|
14
|
+
) -> int:
|
|
15
|
+
async with connection.transaction():
|
|
16
|
+
cursor = await connection.execute(
|
|
17
|
+
"""
|
|
18
|
+
update agent_runtime_report_runs
|
|
19
|
+
set status = 'running', started_at = coalesce(started_at, %s),
|
|
20
|
+
updated_at = %s
|
|
21
|
+
where run_id = %s
|
|
22
|
+
and status not in ('completed', 'failed', 'cancelled')
|
|
23
|
+
""",
|
|
24
|
+
(started_at, started_at, run_id),
|
|
25
|
+
)
|
|
26
|
+
if cursor.rowcount != 1:
|
|
27
|
+
existing = await connection.execute(
|
|
28
|
+
"""
|
|
29
|
+
select status from agent_runtime_report_runs
|
|
30
|
+
where run_id = %s
|
|
31
|
+
""",
|
|
32
|
+
(run_id,),
|
|
33
|
+
)
|
|
34
|
+
if not await existing.fetchone():
|
|
35
|
+
raise KeyError(run_id)
|
|
36
|
+
return 0
|
|
37
|
+
abandoned = await connection.execute(
|
|
38
|
+
"""
|
|
39
|
+
update agent_runtime_report_attempts
|
|
40
|
+
set outcome = 'abandoned', finished_at = %s,
|
|
41
|
+
duration_ms = greatest(0, round((%s - started_at) * 1000))
|
|
42
|
+
where run_id = %s and outcome = 'running'
|
|
43
|
+
and finished_at is null
|
|
44
|
+
""",
|
|
45
|
+
(started_at, started_at, run_id),
|
|
46
|
+
)
|
|
47
|
+
return abandoned.rowcount
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def set_nonterminal_run_status(
|
|
51
|
+
connection: AsyncConnection[dict[str, Any]],
|
|
52
|
+
run_id: str,
|
|
53
|
+
status: NonTerminalRunStatus,
|
|
54
|
+
updated_at: float,
|
|
55
|
+
) -> None:
|
|
56
|
+
cursor = await connection.execute(
|
|
57
|
+
"""
|
|
58
|
+
update agent_runtime_report_runs
|
|
59
|
+
set status = %s, updated_at = %s
|
|
60
|
+
where run_id = %s
|
|
61
|
+
and status not in ('completed', 'failed', 'cancelled')
|
|
62
|
+
and updated_at <= %s
|
|
63
|
+
""",
|
|
64
|
+
(status, updated_at, run_id, updated_at),
|
|
65
|
+
)
|
|
66
|
+
if cursor.rowcount == 1:
|
|
67
|
+
return
|
|
68
|
+
current_cursor = await connection.execute(
|
|
69
|
+
"""
|
|
70
|
+
select status, updated_at
|
|
71
|
+
from agent_runtime_report_runs
|
|
72
|
+
where run_id = %s
|
|
73
|
+
""",
|
|
74
|
+
(run_id,),
|
|
75
|
+
)
|
|
76
|
+
current = await current_cursor.fetchone()
|
|
77
|
+
if not current:
|
|
78
|
+
raise KeyError(run_id)
|
|
79
|
+
if current["status"] in {"completed", "failed", "cancelled"}:
|
|
80
|
+
return
|
|
81
|
+
if float(current["updated_at"]) > updated_at:
|
|
82
|
+
return
|
|
83
|
+
raise RuntimeError("Agent Runtime 报表状态更新失败")
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any, Literal, cast
|
|
7
|
+
|
|
8
|
+
from psycopg import AsyncConnection
|
|
9
|
+
from psycopg.rows import dict_row
|
|
10
|
+
from psycopg_pool import AsyncConnectionPool
|
|
11
|
+
|
|
12
|
+
from .contracts import StartRunRequest
|
|
13
|
+
from .postgres_report_insert import RUN_INSERT_SQL, run_insert_values
|
|
14
|
+
from .postgres_report_queries import get_run, list_runs
|
|
15
|
+
from .postgres_report_replay import assert_attempt_terminal_replay
|
|
16
|
+
from .postgres_report_status import (
|
|
17
|
+
set_nonterminal_run_status,
|
|
18
|
+
start_or_resume_run,
|
|
19
|
+
)
|
|
20
|
+
from .postgres_report_summary import summary
|
|
21
|
+
from .report_metrics import AgentRuntimeMetrics
|
|
22
|
+
from .report_store import AgentRuntimeReportStore
|
|
23
|
+
from .report_types import (
|
|
24
|
+
AttemptReport,
|
|
25
|
+
AttemptTerminalOutcome,
|
|
26
|
+
NonTerminalRunStatus,
|
|
27
|
+
ReportFilter,
|
|
28
|
+
UsageFacts,
|
|
29
|
+
retention_seconds,
|
|
30
|
+
validate_retention,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
type DatabaseConnection = AsyncConnection[dict[str, Any]]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PostgresAgentRuntimeReportStore(AgentRuntimeReportStore):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
database_url: str,
|
|
40
|
+
retention_days: int = 90,
|
|
41
|
+
*,
|
|
42
|
+
metrics: AgentRuntimeMetrics | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
validate_retention(retention_days)
|
|
45
|
+
self.retention_days = retention_days
|
|
46
|
+
self.metrics = metrics or AgentRuntimeMetrics()
|
|
47
|
+
self._pool = cast(
|
|
48
|
+
AsyncConnectionPool[DatabaseConnection],
|
|
49
|
+
AsyncConnectionPool(
|
|
50
|
+
database_url,
|
|
51
|
+
min_size=1,
|
|
52
|
+
max_size=10,
|
|
53
|
+
open=False,
|
|
54
|
+
kwargs={"row_factory": dict_row},
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
self._open_lock = asyncio.Lock()
|
|
58
|
+
self._opened = False
|
|
59
|
+
|
|
60
|
+
async def ready(self) -> None:
|
|
61
|
+
if self._opened:
|
|
62
|
+
return
|
|
63
|
+
async with self._open_lock:
|
|
64
|
+
if not self._opened:
|
|
65
|
+
await self._pool.open()
|
|
66
|
+
self._opened = True
|
|
67
|
+
|
|
68
|
+
async def ensure_run(
|
|
69
|
+
self,
|
|
70
|
+
run_id: str,
|
|
71
|
+
request: StartRunRequest,
|
|
72
|
+
created_at: float,
|
|
73
|
+
) -> None:
|
|
74
|
+
await self.ready()
|
|
75
|
+
context = request.reporting
|
|
76
|
+
values = run_insert_values(
|
|
77
|
+
run_id,
|
|
78
|
+
request,
|
|
79
|
+
created_at,
|
|
80
|
+
self.retention_days,
|
|
81
|
+
)
|
|
82
|
+
async with self._pool.connection() as connection:
|
|
83
|
+
await connection.execute(
|
|
84
|
+
"delete from agent_runtime_report_runs where expires_at <= %s",
|
|
85
|
+
(time.time(),),
|
|
86
|
+
)
|
|
87
|
+
cursor = await connection.execute(RUN_INSERT_SQL, values)
|
|
88
|
+
if cursor.rowcount == 1:
|
|
89
|
+
return
|
|
90
|
+
existing_cursor = await connection.execute(
|
|
91
|
+
"""
|
|
92
|
+
select operation_key, release_id, trace_id, actor_id
|
|
93
|
+
from agent_runtime_report_runs where run_id = %s
|
|
94
|
+
""",
|
|
95
|
+
(run_id,),
|
|
96
|
+
)
|
|
97
|
+
existing = await existing_cursor.fetchone()
|
|
98
|
+
identity = (
|
|
99
|
+
request.operation_key,
|
|
100
|
+
request.release.release_id,
|
|
101
|
+
request.trace_id,
|
|
102
|
+
context.actor_id if context else None,
|
|
103
|
+
)
|
|
104
|
+
if (
|
|
105
|
+
not existing
|
|
106
|
+
or (
|
|
107
|
+
str(existing["operation_key"]),
|
|
108
|
+
str(existing["release_id"]),
|
|
109
|
+
str(existing["trace_id"]),
|
|
110
|
+
existing["actor_id"],
|
|
111
|
+
)
|
|
112
|
+
!= identity
|
|
113
|
+
):
|
|
114
|
+
raise ValueError("Agent Runtime 报表 Run 身份冲突")
|
|
115
|
+
|
|
116
|
+
async def start_run(self, run_id: str, started_at: float) -> None:
|
|
117
|
+
await self.ready()
|
|
118
|
+
async with self._pool.connection() as connection:
|
|
119
|
+
abandoned = await start_or_resume_run(connection, run_id, started_at)
|
|
120
|
+
for _ in range(abandoned):
|
|
121
|
+
self.metrics.observe_attempt("abandoned", UsageFacts())
|
|
122
|
+
|
|
123
|
+
async def finish_run(
|
|
124
|
+
self,
|
|
125
|
+
run_id: str,
|
|
126
|
+
status: Literal["completed", "failed", "cancelled"],
|
|
127
|
+
finished_at: float,
|
|
128
|
+
safe_error_code: str | None = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
await self.ready()
|
|
131
|
+
observed = False
|
|
132
|
+
async with self._pool.connection() as connection:
|
|
133
|
+
cursor = await connection.execute(
|
|
134
|
+
"""
|
|
135
|
+
update agent_runtime_report_runs
|
|
136
|
+
set status = %s, safe_error_code = %s, finished_at = %s,
|
|
137
|
+
duration_ms = greatest(
|
|
138
|
+
0, round((%s - coalesce(started_at, created_at)) * 1000)
|
|
139
|
+
),
|
|
140
|
+
updated_at = %s, expires_at = %s
|
|
141
|
+
where run_id = %s
|
|
142
|
+
and status not in ('completed', 'failed', 'cancelled')
|
|
143
|
+
""",
|
|
144
|
+
(
|
|
145
|
+
status,
|
|
146
|
+
safe_error_code,
|
|
147
|
+
finished_at,
|
|
148
|
+
finished_at,
|
|
149
|
+
finished_at,
|
|
150
|
+
finished_at + retention_seconds(self.retention_days),
|
|
151
|
+
run_id,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
observed = cursor.rowcount == 1
|
|
155
|
+
if not observed:
|
|
156
|
+
current_cursor = await connection.execute(
|
|
157
|
+
"select status from agent_runtime_report_runs where run_id = %s",
|
|
158
|
+
(run_id,),
|
|
159
|
+
)
|
|
160
|
+
current = await current_cursor.fetchone()
|
|
161
|
+
if not current:
|
|
162
|
+
raise KeyError(run_id)
|
|
163
|
+
if current["status"] != status:
|
|
164
|
+
raise ValueError("Agent Runtime 报表终态冲突")
|
|
165
|
+
if observed:
|
|
166
|
+
self.metrics.observe_run(status)
|
|
167
|
+
|
|
168
|
+
async def set_run_status(
|
|
169
|
+
self,
|
|
170
|
+
run_id: str,
|
|
171
|
+
status: NonTerminalRunStatus,
|
|
172
|
+
updated_at: float,
|
|
173
|
+
) -> None:
|
|
174
|
+
await self.ready()
|
|
175
|
+
async with self._pool.connection() as connection:
|
|
176
|
+
await set_nonterminal_run_status(connection, run_id, status, updated_at)
|
|
177
|
+
|
|
178
|
+
async def start_attempt(
|
|
179
|
+
self,
|
|
180
|
+
run_id: str,
|
|
181
|
+
tool_round: int,
|
|
182
|
+
deployment_key: str,
|
|
183
|
+
requested_model: str,
|
|
184
|
+
started_at: float,
|
|
185
|
+
) -> AttemptReport:
|
|
186
|
+
await self.ready()
|
|
187
|
+
attempt_id = f"attempt_{uuid.uuid4().hex}"
|
|
188
|
+
async with self._pool.connection() as connection:
|
|
189
|
+
async with connection.transaction():
|
|
190
|
+
await connection.execute(
|
|
191
|
+
"select pg_advisory_xact_lock(hashtext(%s))",
|
|
192
|
+
(run_id,),
|
|
193
|
+
)
|
|
194
|
+
cursor = await connection.execute(
|
|
195
|
+
"""
|
|
196
|
+
select coalesce(max(sequence), 0) + 1 as sequence
|
|
197
|
+
from agent_runtime_report_attempts where run_id = %s
|
|
198
|
+
""",
|
|
199
|
+
(run_id,),
|
|
200
|
+
)
|
|
201
|
+
row = await cursor.fetchone()
|
|
202
|
+
sequence = int(row["sequence"]) if row else 1
|
|
203
|
+
await connection.execute(
|
|
204
|
+
"""
|
|
205
|
+
insert into agent_runtime_report_attempts (
|
|
206
|
+
attempt_id, run_id, sequence, tool_round,
|
|
207
|
+
deployment_key, requested_model, outcome,
|
|
208
|
+
cache_status, usage_source, started_at
|
|
209
|
+
) values (
|
|
210
|
+
%s, %s, %s, %s, %s, %s,
|
|
211
|
+
'running', 'unknown', 'unknown', %s
|
|
212
|
+
)
|
|
213
|
+
""",
|
|
214
|
+
(
|
|
215
|
+
attempt_id,
|
|
216
|
+
run_id,
|
|
217
|
+
sequence,
|
|
218
|
+
tool_round,
|
|
219
|
+
deployment_key,
|
|
220
|
+
requested_model,
|
|
221
|
+
started_at,
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
return AttemptReport(
|
|
225
|
+
attempt_id=attempt_id,
|
|
226
|
+
run_id=run_id,
|
|
227
|
+
sequence=sequence,
|
|
228
|
+
tool_round=tool_round,
|
|
229
|
+
deployment_key=deployment_key,
|
|
230
|
+
requested_model=requested_model,
|
|
231
|
+
reported_model=None,
|
|
232
|
+
outcome="running",
|
|
233
|
+
usage=UsageFacts(),
|
|
234
|
+
duration_ms=None,
|
|
235
|
+
safe_error_code=None,
|
|
236
|
+
started_at=started_at,
|
|
237
|
+
finished_at=None,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
async def finish_attempt(
|
|
241
|
+
self,
|
|
242
|
+
attempt_id: str,
|
|
243
|
+
outcome: AttemptTerminalOutcome,
|
|
244
|
+
usage: UsageFacts,
|
|
245
|
+
finished_at: float,
|
|
246
|
+
reported_model: str | None = None,
|
|
247
|
+
safe_error_code: str | None = None,
|
|
248
|
+
) -> None:
|
|
249
|
+
await self.ready()
|
|
250
|
+
values = (
|
|
251
|
+
outcome,
|
|
252
|
+
usage.cache_status,
|
|
253
|
+
usage.source,
|
|
254
|
+
usage.input_tokens,
|
|
255
|
+
usage.output_tokens,
|
|
256
|
+
usage.cached_input_tokens,
|
|
257
|
+
usage.reasoning_tokens,
|
|
258
|
+
usage.total_tokens,
|
|
259
|
+
usage.first_token_latency_ms,
|
|
260
|
+
reported_model,
|
|
261
|
+
safe_error_code,
|
|
262
|
+
finished_at,
|
|
263
|
+
finished_at,
|
|
264
|
+
attempt_id,
|
|
265
|
+
)
|
|
266
|
+
async with self._pool.connection() as connection:
|
|
267
|
+
cursor = await connection.execute(
|
|
268
|
+
"""
|
|
269
|
+
update agent_runtime_report_attempts set
|
|
270
|
+
outcome = %s, cache_status = %s, usage_source = %s,
|
|
271
|
+
input_tokens = %s, output_tokens = %s,
|
|
272
|
+
cached_input_tokens = %s, reasoning_tokens = %s,
|
|
273
|
+
total_tokens = %s, first_token_latency_ms = %s,
|
|
274
|
+
reported_model = %s, safe_error_code = %s,
|
|
275
|
+
duration_ms = greatest(0, round((%s - started_at) * 1000)),
|
|
276
|
+
finished_at = %s
|
|
277
|
+
where attempt_id = %s and finished_at is null
|
|
278
|
+
""",
|
|
279
|
+
values,
|
|
280
|
+
)
|
|
281
|
+
observed = cursor.rowcount == 1
|
|
282
|
+
if not observed:
|
|
283
|
+
current_cursor = await connection.execute(
|
|
284
|
+
"""
|
|
285
|
+
select outcome, cache_status, usage_source,
|
|
286
|
+
input_tokens, output_tokens, cached_input_tokens,
|
|
287
|
+
reasoning_tokens, total_tokens, first_token_latency_ms,
|
|
288
|
+
reported_model, safe_error_code
|
|
289
|
+
from agent_runtime_report_attempts
|
|
290
|
+
where attempt_id = %s
|
|
291
|
+
""",
|
|
292
|
+
(attempt_id,),
|
|
293
|
+
)
|
|
294
|
+
current = await current_cursor.fetchone()
|
|
295
|
+
if not current:
|
|
296
|
+
raise KeyError(attempt_id)
|
|
297
|
+
assert_attempt_terminal_replay(
|
|
298
|
+
current,
|
|
299
|
+
outcome,
|
|
300
|
+
usage,
|
|
301
|
+
reported_model,
|
|
302
|
+
safe_error_code,
|
|
303
|
+
)
|
|
304
|
+
if observed:
|
|
305
|
+
self.metrics.observe_attempt(outcome, usage)
|
|
306
|
+
|
|
307
|
+
async def summary(self, query: ReportFilter) -> dict[str, object]:
|
|
308
|
+
await self.ready()
|
|
309
|
+
return await summary(
|
|
310
|
+
self._pool,
|
|
311
|
+
query,
|
|
312
|
+
self.retention_days,
|
|
313
|
+
time.time(),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
async def list_runs(
|
|
317
|
+
self,
|
|
318
|
+
query: ReportFilter,
|
|
319
|
+
page: int,
|
|
320
|
+
page_size: int,
|
|
321
|
+
) -> dict[str, object]:
|
|
322
|
+
await self.ready()
|
|
323
|
+
return await list_runs(self._pool, query, page, page_size, time.time())
|
|
324
|
+
|
|
325
|
+
async def get_run(self, run_id: str) -> dict[str, object] | None:
|
|
326
|
+
await self.ready()
|
|
327
|
+
return await get_run(self._pool, run_id, time.time())
|
|
328
|
+
|
|
329
|
+
async def close(self) -> None:
|
|
330
|
+
if self._opened:
|
|
331
|
+
await self._pool.close()
|