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,344 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import replace
|
|
8
|
+
from typing import Literal, Protocol
|
|
9
|
+
|
|
10
|
+
from .contracts import StartRunRequest
|
|
11
|
+
from .report_metrics import AgentRuntimeMetrics
|
|
12
|
+
from .report_presenter import (
|
|
13
|
+
aggregate_summary,
|
|
14
|
+
attempt_value,
|
|
15
|
+
attempts_for,
|
|
16
|
+
filtered_attempts,
|
|
17
|
+
filtered_runs,
|
|
18
|
+
run_value,
|
|
19
|
+
scoped_runs,
|
|
20
|
+
)
|
|
21
|
+
from .report_types import (
|
|
22
|
+
AttemptReport,
|
|
23
|
+
AttemptTerminalOutcome,
|
|
24
|
+
NonTerminalRunStatus,
|
|
25
|
+
ReportFilter,
|
|
26
|
+
RunReport,
|
|
27
|
+
UsageFacts,
|
|
28
|
+
identity,
|
|
29
|
+
retention_seconds,
|
|
30
|
+
validate_retention,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AgentRuntimeReportStore(Protocol):
|
|
35
|
+
retention_days: int
|
|
36
|
+
metrics: AgentRuntimeMetrics
|
|
37
|
+
|
|
38
|
+
async def ensure_run(
|
|
39
|
+
self, run_id: str, request: StartRunRequest, created_at: float
|
|
40
|
+
) -> None: ...
|
|
41
|
+
|
|
42
|
+
async def start_run(self, run_id: str, started_at: float) -> None: ...
|
|
43
|
+
|
|
44
|
+
async def set_run_status(
|
|
45
|
+
self,
|
|
46
|
+
run_id: str,
|
|
47
|
+
status: NonTerminalRunStatus,
|
|
48
|
+
updated_at: float,
|
|
49
|
+
) -> None: ...
|
|
50
|
+
|
|
51
|
+
async def finish_run(
|
|
52
|
+
self,
|
|
53
|
+
run_id: str,
|
|
54
|
+
status: Literal["completed", "failed", "cancelled"],
|
|
55
|
+
finished_at: float,
|
|
56
|
+
safe_error_code: str | None = None,
|
|
57
|
+
) -> None: ...
|
|
58
|
+
|
|
59
|
+
async def start_attempt(
|
|
60
|
+
self,
|
|
61
|
+
run_id: str,
|
|
62
|
+
tool_round: int,
|
|
63
|
+
deployment_key: str,
|
|
64
|
+
requested_model: str,
|
|
65
|
+
started_at: float,
|
|
66
|
+
) -> AttemptReport: ...
|
|
67
|
+
|
|
68
|
+
async def finish_attempt(
|
|
69
|
+
self,
|
|
70
|
+
attempt_id: str,
|
|
71
|
+
outcome: AttemptTerminalOutcome,
|
|
72
|
+
usage: UsageFacts,
|
|
73
|
+
finished_at: float,
|
|
74
|
+
reported_model: str | None = None,
|
|
75
|
+
safe_error_code: str | None = None,
|
|
76
|
+
) -> None: ...
|
|
77
|
+
|
|
78
|
+
async def summary(self, query: ReportFilter) -> dict[str, object]: ...
|
|
79
|
+
|
|
80
|
+
async def list_runs(
|
|
81
|
+
self, query: ReportFilter, page: int, page_size: int
|
|
82
|
+
) -> dict[str, object]: ...
|
|
83
|
+
|
|
84
|
+
async def get_run(self, run_id: str) -> dict[str, object] | None: ...
|
|
85
|
+
|
|
86
|
+
async def close(self) -> None: ...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class InMemoryAgentRuntimeReportStore:
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
retention_days: int = 90,
|
|
93
|
+
*,
|
|
94
|
+
now: Callable[[], float] = time.time,
|
|
95
|
+
metrics: AgentRuntimeMetrics | None = None,
|
|
96
|
+
) -> None:
|
|
97
|
+
validate_retention(retention_days)
|
|
98
|
+
self.retention_days = retention_days
|
|
99
|
+
self._now = now
|
|
100
|
+
self.metrics = metrics or AgentRuntimeMetrics()
|
|
101
|
+
self._runs: dict[str, RunReport] = {}
|
|
102
|
+
self._attempts: dict[str, AttemptReport] = {}
|
|
103
|
+
self._lock = asyncio.Lock()
|
|
104
|
+
|
|
105
|
+
async def ensure_run(
|
|
106
|
+
self, run_id: str, request: StartRunRequest, created_at: float
|
|
107
|
+
) -> None:
|
|
108
|
+
async with self._lock:
|
|
109
|
+
self._prune()
|
|
110
|
+
context = request.reporting
|
|
111
|
+
candidate = RunReport(
|
|
112
|
+
run_id=run_id,
|
|
113
|
+
operation_key=request.operation_key,
|
|
114
|
+
release_id=request.release.release_id,
|
|
115
|
+
trace_id=request.trace_id,
|
|
116
|
+
actor_id=context.actor_id if context else None,
|
|
117
|
+
actor_type=context.actor_type if context else None,
|
|
118
|
+
department_id=context.department_id if context else None,
|
|
119
|
+
status="accepted",
|
|
120
|
+
safe_error_code=None,
|
|
121
|
+
started_at=None,
|
|
122
|
+
finished_at=None,
|
|
123
|
+
duration_ms=None,
|
|
124
|
+
created_at=created_at,
|
|
125
|
+
updated_at=created_at,
|
|
126
|
+
expires_at=created_at + retention_seconds(self.retention_days),
|
|
127
|
+
)
|
|
128
|
+
existing = self._runs.get(run_id)
|
|
129
|
+
if existing and identity(existing) != identity(candidate):
|
|
130
|
+
raise ValueError("Agent Runtime 报表 Run 身份冲突")
|
|
131
|
+
if existing is None:
|
|
132
|
+
self._runs[run_id] = candidate
|
|
133
|
+
|
|
134
|
+
async def start_run(self, run_id: str, started_at: float) -> None:
|
|
135
|
+
async with self._lock:
|
|
136
|
+
run = self._runs[run_id]
|
|
137
|
+
if run.status in {"completed", "failed", "cancelled"}:
|
|
138
|
+
return
|
|
139
|
+
for attempt_id, attempt in self._attempts.items():
|
|
140
|
+
if attempt.run_id != run_id or attempt.outcome != "running":
|
|
141
|
+
continue
|
|
142
|
+
usage = UsageFacts()
|
|
143
|
+
self._attempts[attempt_id] = replace(
|
|
144
|
+
attempt,
|
|
145
|
+
outcome="abandoned",
|
|
146
|
+
usage=usage,
|
|
147
|
+
duration_ms=max(
|
|
148
|
+
0,
|
|
149
|
+
round((started_at - attempt.started_at) * 1_000),
|
|
150
|
+
),
|
|
151
|
+
finished_at=started_at,
|
|
152
|
+
)
|
|
153
|
+
self.metrics.observe_attempt("abandoned", usage)
|
|
154
|
+
self._runs[run_id] = replace(
|
|
155
|
+
run,
|
|
156
|
+
status="running",
|
|
157
|
+
started_at=run.started_at or started_at,
|
|
158
|
+
updated_at=started_at,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
async def set_run_status(
|
|
162
|
+
self,
|
|
163
|
+
run_id: str,
|
|
164
|
+
status: NonTerminalRunStatus,
|
|
165
|
+
updated_at: float,
|
|
166
|
+
) -> None:
|
|
167
|
+
async with self._lock:
|
|
168
|
+
run = self._runs[run_id]
|
|
169
|
+
if run.status in {"completed", "failed", "cancelled"}:
|
|
170
|
+
return
|
|
171
|
+
if updated_at < run.updated_at:
|
|
172
|
+
return
|
|
173
|
+
self._runs[run_id] = replace(
|
|
174
|
+
run,
|
|
175
|
+
status=status,
|
|
176
|
+
updated_at=updated_at,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def finish_run(
|
|
180
|
+
self,
|
|
181
|
+
run_id: str,
|
|
182
|
+
status: Literal["completed", "failed", "cancelled"],
|
|
183
|
+
finished_at: float,
|
|
184
|
+
safe_error_code: str | None = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
async with self._lock:
|
|
187
|
+
run = self._runs[run_id]
|
|
188
|
+
if run.status in {"completed", "failed", "cancelled"}:
|
|
189
|
+
if run.status != status:
|
|
190
|
+
raise ValueError("Agent Runtime 报表终态冲突")
|
|
191
|
+
return
|
|
192
|
+
started = run.started_at or run.created_at
|
|
193
|
+
self._runs[run_id] = replace(
|
|
194
|
+
run,
|
|
195
|
+
status=status,
|
|
196
|
+
safe_error_code=safe_error_code,
|
|
197
|
+
finished_at=finished_at,
|
|
198
|
+
duration_ms=max(0, round((finished_at - started) * 1_000)),
|
|
199
|
+
updated_at=finished_at,
|
|
200
|
+
expires_at=finished_at + retention_seconds(self.retention_days),
|
|
201
|
+
)
|
|
202
|
+
self.metrics.observe_run(status)
|
|
203
|
+
|
|
204
|
+
async def start_attempt(
|
|
205
|
+
self,
|
|
206
|
+
run_id: str,
|
|
207
|
+
tool_round: int,
|
|
208
|
+
deployment_key: str,
|
|
209
|
+
requested_model: str,
|
|
210
|
+
started_at: float,
|
|
211
|
+
) -> AttemptReport:
|
|
212
|
+
async with self._lock:
|
|
213
|
+
sequence = 1 + max(
|
|
214
|
+
(
|
|
215
|
+
attempt.sequence
|
|
216
|
+
for attempt in self._attempts.values()
|
|
217
|
+
if attempt.run_id == run_id
|
|
218
|
+
),
|
|
219
|
+
default=0,
|
|
220
|
+
)
|
|
221
|
+
attempt = AttemptReport(
|
|
222
|
+
attempt_id=f"attempt_{uuid.uuid4().hex}",
|
|
223
|
+
run_id=run_id,
|
|
224
|
+
sequence=sequence,
|
|
225
|
+
tool_round=tool_round,
|
|
226
|
+
deployment_key=deployment_key,
|
|
227
|
+
requested_model=requested_model,
|
|
228
|
+
reported_model=None,
|
|
229
|
+
outcome="running",
|
|
230
|
+
usage=UsageFacts(),
|
|
231
|
+
duration_ms=None,
|
|
232
|
+
safe_error_code=None,
|
|
233
|
+
started_at=started_at,
|
|
234
|
+
finished_at=None,
|
|
235
|
+
)
|
|
236
|
+
self._attempts[attempt.attempt_id] = attempt
|
|
237
|
+
return attempt
|
|
238
|
+
|
|
239
|
+
async def finish_attempt(
|
|
240
|
+
self,
|
|
241
|
+
attempt_id: str,
|
|
242
|
+
outcome: AttemptTerminalOutcome,
|
|
243
|
+
usage: UsageFacts,
|
|
244
|
+
finished_at: float,
|
|
245
|
+
reported_model: str | None = None,
|
|
246
|
+
safe_error_code: str | None = None,
|
|
247
|
+
) -> None:
|
|
248
|
+
async with self._lock:
|
|
249
|
+
attempt = self._attempts[attempt_id]
|
|
250
|
+
if attempt.finished_at is not None:
|
|
251
|
+
if (
|
|
252
|
+
attempt.outcome != outcome
|
|
253
|
+
or attempt.usage != usage
|
|
254
|
+
or attempt.reported_model != reported_model
|
|
255
|
+
or attempt.safe_error_code != safe_error_code
|
|
256
|
+
):
|
|
257
|
+
raise ValueError("Agent Runtime 报表 Attempt 终态冲突")
|
|
258
|
+
return
|
|
259
|
+
self._attempts[attempt_id] = replace(
|
|
260
|
+
attempt,
|
|
261
|
+
outcome=outcome,
|
|
262
|
+
usage=usage,
|
|
263
|
+
reported_model=reported_model,
|
|
264
|
+
safe_error_code=safe_error_code,
|
|
265
|
+
duration_ms=max(0, round((finished_at - attempt.started_at) * 1_000)),
|
|
266
|
+
finished_at=finished_at,
|
|
267
|
+
)
|
|
268
|
+
self.metrics.observe_attempt(outcome, usage)
|
|
269
|
+
|
|
270
|
+
async def summary(self, query: ReportFilter) -> dict[str, object]:
|
|
271
|
+
async with self._lock:
|
|
272
|
+
self._prune()
|
|
273
|
+
visible_runs = scoped_runs(self._runs.values(), query)
|
|
274
|
+
runs = filtered_runs(visible_runs, query)
|
|
275
|
+
attempts = filtered_attempts(
|
|
276
|
+
self._attempts.values(),
|
|
277
|
+
visible_runs,
|
|
278
|
+
query,
|
|
279
|
+
)
|
|
280
|
+
return aggregate_summary(
|
|
281
|
+
runs,
|
|
282
|
+
attempts,
|
|
283
|
+
self.retention_days,
|
|
284
|
+
min(
|
|
285
|
+
[run.created_at for run in runs]
|
|
286
|
+
+ [attempt.started_at for attempt in attempts],
|
|
287
|
+
default=None,
|
|
288
|
+
),
|
|
289
|
+
self._now(),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
async def list_runs(
|
|
293
|
+
self, query: ReportFilter, page: int, page_size: int
|
|
294
|
+
) -> dict[str, object]:
|
|
295
|
+
async with self._lock:
|
|
296
|
+
self._prune()
|
|
297
|
+
runs = sorted(
|
|
298
|
+
filtered_runs(self._runs.values(), query),
|
|
299
|
+
key=lambda run: run.created_at,
|
|
300
|
+
reverse=True,
|
|
301
|
+
)
|
|
302
|
+
start = (page - 1) * page_size
|
|
303
|
+
items = [
|
|
304
|
+
run_value(run, attempts_for(self._attempts.values(), [run]))
|
|
305
|
+
for run in runs[start : start + page_size]
|
|
306
|
+
]
|
|
307
|
+
return {
|
|
308
|
+
"items": items,
|
|
309
|
+
"total": len(runs),
|
|
310
|
+
"page": page,
|
|
311
|
+
"pageSize": page_size,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async def get_run(self, run_id: str) -> dict[str, object] | None:
|
|
315
|
+
async with self._lock:
|
|
316
|
+
self._prune()
|
|
317
|
+
run = self._runs.get(run_id)
|
|
318
|
+
if run is None:
|
|
319
|
+
return None
|
|
320
|
+
attempts = attempts_for(self._attempts.values(), [run])
|
|
321
|
+
return {
|
|
322
|
+
**run_value(run, attempts),
|
|
323
|
+
"releaseId": run.release_id,
|
|
324
|
+
"traceId": run.trace_id,
|
|
325
|
+
"safeErrorCode": run.safe_error_code,
|
|
326
|
+
"attempts": [attempt_value(item) for item in attempts],
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async def close(self) -> None:
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
def _prune(self) -> None:
|
|
333
|
+
expired = {
|
|
334
|
+
run_id
|
|
335
|
+
for run_id, run in self._runs.items()
|
|
336
|
+
if run.expires_at <= self._now()
|
|
337
|
+
}
|
|
338
|
+
for run_id in expired:
|
|
339
|
+
self._runs.pop(run_id, None)
|
|
340
|
+
self._attempts = {
|
|
341
|
+
key: value
|
|
342
|
+
for key, value in self._attempts.items()
|
|
343
|
+
if value.run_id not in expired
|
|
344
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from .contracts import RunStatus
|
|
8
|
+
|
|
9
|
+
type AttemptTerminalOutcome = Literal[
|
|
10
|
+
"completed", "failed", "cancelled", "blocked", "abandoned"
|
|
11
|
+
]
|
|
12
|
+
type AttemptOutcome = Literal["running"] | AttemptTerminalOutcome
|
|
13
|
+
type NonTerminalRunStatus = Literal["running", "awaiting-approval"]
|
|
14
|
+
type CacheStatus = Literal["hit", "miss", "unknown"]
|
|
15
|
+
type UsageSource = Literal["provider-reported", "estimated", "unknown"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class UsageFacts:
|
|
20
|
+
source: UsageSource = "unknown"
|
|
21
|
+
input_tokens: int | None = None
|
|
22
|
+
output_tokens: int | None = None
|
|
23
|
+
cached_input_tokens: int | None = None
|
|
24
|
+
reasoning_tokens: int | None = None
|
|
25
|
+
total_tokens: int | None = None
|
|
26
|
+
first_token_latency_ms: int | None = None
|
|
27
|
+
cache_status: CacheStatus = "unknown"
|
|
28
|
+
|
|
29
|
+
def event_value(self) -> dict[str, str | int]:
|
|
30
|
+
result: dict[str, str | int] = {
|
|
31
|
+
"source": self.source,
|
|
32
|
+
"cacheStatus": self.cache_status,
|
|
33
|
+
}
|
|
34
|
+
for key, value in (
|
|
35
|
+
("inputTokens", self.input_tokens),
|
|
36
|
+
("outputTokens", self.output_tokens),
|
|
37
|
+
("cachedInputTokens", self.cached_input_tokens),
|
|
38
|
+
("reasoningTokens", self.reasoning_tokens),
|
|
39
|
+
("totalTokens", self.total_tokens),
|
|
40
|
+
("firstTokenLatencyMs", self.first_token_latency_ms),
|
|
41
|
+
):
|
|
42
|
+
if value is not None:
|
|
43
|
+
result[key] = value
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class RunReport:
|
|
49
|
+
run_id: str
|
|
50
|
+
operation_key: str
|
|
51
|
+
release_id: str
|
|
52
|
+
trace_id: str
|
|
53
|
+
actor_id: str | None
|
|
54
|
+
actor_type: str | None
|
|
55
|
+
department_id: str | None
|
|
56
|
+
status: RunStatus
|
|
57
|
+
safe_error_code: str | None
|
|
58
|
+
started_at: float | None
|
|
59
|
+
finished_at: float | None
|
|
60
|
+
duration_ms: int | None
|
|
61
|
+
created_at: float
|
|
62
|
+
updated_at: float
|
|
63
|
+
expires_at: float
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class AttemptReport:
|
|
68
|
+
attempt_id: str
|
|
69
|
+
run_id: str
|
|
70
|
+
sequence: int
|
|
71
|
+
tool_round: int
|
|
72
|
+
deployment_key: str
|
|
73
|
+
requested_model: str
|
|
74
|
+
reported_model: str | None
|
|
75
|
+
outcome: AttemptOutcome
|
|
76
|
+
usage: UsageFacts
|
|
77
|
+
duration_ms: int | None
|
|
78
|
+
safe_error_code: str | None
|
|
79
|
+
started_at: float
|
|
80
|
+
finished_at: float | None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class ReportFilter:
|
|
85
|
+
from_timestamp: float
|
|
86
|
+
to_timestamp: float
|
|
87
|
+
selected_actor_id: str | None = None
|
|
88
|
+
scope_restricted: bool = False
|
|
89
|
+
actor_ids: tuple[str, ...] = ()
|
|
90
|
+
department_ids: tuple[str, ...] = ()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def validate_retention(value: int) -> None:
|
|
94
|
+
if not 1 <= value <= 3650:
|
|
95
|
+
raise ValueError("AI Runtime 报表留存天数超出范围")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def retention_seconds(days: int) -> int:
|
|
99
|
+
return days * 24 * 60 * 60
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def iso(value: float | None) -> str | None:
|
|
103
|
+
return (
|
|
104
|
+
datetime.fromtimestamp(value, tz=UTC).isoformat().replace("+00:00", "Z")
|
|
105
|
+
if value is not None
|
|
106
|
+
else None
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def identity(run: RunReport) -> tuple[str, str, str, str | None]:
|
|
111
|
+
return (run.operation_key, run.release_id, run.trace_id, run.actor_id)
|
|
@@ -6,19 +6,26 @@ from .adk_adapter import AdkEngineAdapter
|
|
|
6
6
|
from .config import EnvironmentSecretResolver, OpenAiCompatibleConfig
|
|
7
7
|
from .engine import AgentEngineAdapter
|
|
8
8
|
from .openai_provider import OpenAiCompatibleProvider
|
|
9
|
+
from .postgres_report_store import PostgresAgentRuntimeReportStore
|
|
9
10
|
from .postgres_store import PostgresRunStore
|
|
10
11
|
from .provider_audit import ProviderAuditStore
|
|
11
12
|
from .provider_governance import ProviderRequestGovernance
|
|
13
|
+
from .report_store import (
|
|
14
|
+
AgentRuntimeReportStore,
|
|
15
|
+
InMemoryAgentRuntimeReportStore,
|
|
16
|
+
)
|
|
12
17
|
from .sqlite_store import SqliteRunStore
|
|
13
18
|
from .store import InMemoryRunStore, RunStore
|
|
14
19
|
from .store_cipher import RunStoreCipher
|
|
15
20
|
from .tool_host import ToolHostClient
|
|
16
21
|
|
|
17
22
|
|
|
18
|
-
def default_engine(
|
|
23
|
+
def default_engine(
|
|
24
|
+
report_store: AgentRuntimeReportStore | None = None,
|
|
25
|
+
) -> AgentEngineAdapter:
|
|
19
26
|
config = OpenAiCompatibleConfig.from_environment()
|
|
20
27
|
if config is None:
|
|
21
|
-
return AdkEngineAdapter()
|
|
28
|
+
return AdkEngineAdapter(report_store=report_store)
|
|
22
29
|
internal_token = os.environ.get("AGENT_SERVER_INTERNAL_TOKEN", "").strip()
|
|
23
30
|
if not internal_token:
|
|
24
31
|
raise ValueError("Agent Server Provider 已配置但内部 Token 缺失")
|
|
@@ -34,6 +41,7 @@ def default_engine() -> AgentEngineAdapter:
|
|
|
34
41
|
retention_days=provider_audit_retention_days(),
|
|
35
42
|
)
|
|
36
43
|
),
|
|
44
|
+
report_store,
|
|
37
45
|
)
|
|
38
46
|
|
|
39
47
|
|
|
@@ -50,6 +58,16 @@ def default_store() -> RunStore:
|
|
|
50
58
|
)
|
|
51
59
|
|
|
52
60
|
|
|
61
|
+
def default_report_store() -> AgentRuntimeReportStore:
|
|
62
|
+
database_url = os.environ.get("AGENT_DATABASE_URL", "").strip()
|
|
63
|
+
retention_days = report_retention_days()
|
|
64
|
+
return (
|
|
65
|
+
PostgresAgentRuntimeReportStore(database_url, retention_days)
|
|
66
|
+
if database_url
|
|
67
|
+
else InMemoryAgentRuntimeReportStore(retention_days)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
53
71
|
def required_environment(key: str) -> str:
|
|
54
72
|
value = os.environ.get(key, "").strip()
|
|
55
73
|
if not value:
|
|
@@ -66,3 +84,14 @@ def provider_audit_retention_days() -> int:
|
|
|
66
84
|
if not 1 <= value <= 3650:
|
|
67
85
|
raise ValueError("AI_PROVIDER_AUDIT_RETENTION_DAYS 超出允许范围")
|
|
68
86
|
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def report_retention_days() -> int:
|
|
90
|
+
raw = os.environ.get("AI_RUNTIME_REPORT_RETENTION_DAYS", "90")
|
|
91
|
+
try:
|
|
92
|
+
value = int(raw)
|
|
93
|
+
except ValueError as error:
|
|
94
|
+
raise ValueError("AI_RUNTIME_REPORT_RETENTION_DAYS 必须是整数") from error
|
|
95
|
+
if not 1 <= value <= 3650:
|
|
96
|
+
raise ValueError("AI_RUNTIME_REPORT_RETENTION_DAYS 超出允许范围")
|
|
97
|
+
return value
|