create-windy 0.4.0 → 0.5.0

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.
Files changed (194) hide show
  1. package/dist/cli.js +263 -121
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
  5. package/template/apps/agent-server/adk_web_agents/services.py +25 -0
  6. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
  7. package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
  8. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
  9. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
  10. package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
  11. package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
  14. package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
  15. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
  16. package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
  18. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
  25. package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
  26. package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
  27. package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
  28. package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
  29. package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
  30. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
  31. package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
  32. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
  33. package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
  34. package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
  35. package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
  36. package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
  37. package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
  38. package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
  39. package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
  40. package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
  41. package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
  42. package/template/apps/agent-server/tests/test_report_store.py +286 -0
  43. package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
  44. package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
  45. package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
  46. package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
  47. package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
  48. package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
  49. package/template/apps/server/src/agent/debug-session-id.ts +37 -0
  50. package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
  51. package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
  52. package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
  53. package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
  54. package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
  55. package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
  56. package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
  57. package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
  58. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  59. package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
  60. package/template/apps/server/src/agent/run-authorization.ts +32 -1
  61. package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
  62. package/template/apps/server/src/agent/run-contracts.ts +5 -0
  63. package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
  64. package/template/apps/server/src/agent/run-facade.test.ts +78 -0
  65. package/template/apps/server/src/agent/run-facade.ts +33 -3
  66. package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
  67. package/template/apps/server/src/agent/run-input-verification.ts +41 -0
  68. package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
  69. package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
  70. package/template/apps/server/src/agent/tool-host.test.ts +1 -0
  71. package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
  72. package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
  73. package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
  74. package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
  75. package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
  76. package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
  77. package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
  78. package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
  79. package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
  80. package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
  81. package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
  82. package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
  83. package/template/apps/server/src/license/runtime-service.test.ts +6 -0
  84. package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
  85. package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
  86. package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
  87. package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
  88. package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
  89. package/template/apps/server/src/module-host/initialize.ts +6 -197
  90. package/template/apps/server/src/module-host/request-context.ts +1 -0
  91. package/template/apps/server/src/module-host/scoped-host.ts +214 -0
  92. package/template/apps/server/src/module-host.ts +1 -0
  93. package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
  94. package/template/apps/server/src/runtime-adk-debug.ts +8 -2
  95. package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
  96. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
  97. package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
  98. package/template/apps/server/src/settings/watermark-routes.ts +29 -1
  99. package/template/apps/server/src/system/operations.test.ts +54 -0
  100. package/template/apps/server/src/system/operations.ts +23 -1
  101. package/template/apps/server/src/system/seed.test.ts +4 -0
  102. package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
  103. package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
  104. package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
  105. package/template/apps/web/src/composables/watermark-settings.ts +40 -11
  106. package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
  107. package/template/apps/web/src/layout/AppSidebar.vue +6 -1
  108. package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
  109. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
  110. package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
  111. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
  112. package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
  113. package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
  114. package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
  115. package/template/apps/web/src/pages/settings/WatermarkPreview.vue +72 -0
  116. package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
  117. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
  118. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
  119. package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
  120. package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
  121. package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
  122. package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
  123. package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
  124. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
  125. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
  126. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
  127. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
  128. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
  129. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
  130. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
  131. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
  132. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
  133. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
  134. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
  135. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
  136. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
  137. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
  138. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
  139. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
  140. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
  141. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
  142. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
  143. package/template/apps/web/src/router/error-navigation.ts +22 -0
  144. package/template/apps/web/src/router/index.ts +12 -23
  145. package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
  146. package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
  147. package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
  148. package/template/apps/web/src/services/operations-api.ts +28 -0
  149. package/template/docker-compose.agent-debug.yml +1 -1
  150. package/template/docs/architecture/ai-runtime.md +2 -1
  151. package/template/docs/architecture/module-host.md +3 -0
  152. package/template/docs/platform/agent-run-attestation.md +65 -0
  153. package/template/docs/platform/agent-runtime-observability.md +135 -0
  154. package/template/docs/platform/agent-runtime.md +47 -3
  155. package/template/docs/platform/operations-overview.md +13 -0
  156. package/template/docs/platform/platform-authentication.md +4 -3
  157. package/template/docs/platform/platform-shell-settings.md +15 -10
  158. package/template/docs/platform/web-system-crud.md +1 -1
  159. package/template/packages/config/package.json +1 -1
  160. package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
  161. package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
  162. package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
  163. package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
  164. package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
  165. package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
  166. package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
  167. package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
  168. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  169. package/template/packages/database/package.json +1 -1
  170. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  171. package/template/packages/database/src/schema/agent-runtime.ts +114 -0
  172. package/template/packages/database/src/schema/platform-settings.ts +3 -0
  173. package/template/packages/jobs/package.json +1 -1
  174. package/template/packages/modules/package.json +1 -1
  175. package/template/packages/modules/src/system-admin-routes.ts +10 -0
  176. package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
  177. package/template/packages/modules/src/system-api-control-plane.ts +17 -1
  178. package/template/packages/modules/src/system-audit-actions.ts +10 -0
  179. package/template/packages/modules/src/system-features.ts +8 -0
  180. package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
  181. package/template/packages/modules/src/system-module-catalog.ts +15 -4
  182. package/template/packages/modules/src/system-modules-access.test.ts +69 -0
  183. package/template/packages/modules/src/system-modules.ts +13 -0
  184. package/template/packages/modules/src/system-permissions.ts +2 -0
  185. package/template/packages/server-sdk/index.ts +11 -0
  186. package/template/packages/server-sdk/package.json +2 -1
  187. package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
  188. package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
  189. package/template/packages/server-sdk/src/module-host.ts +5 -0
  190. package/template/packages/shared/index.ts +3 -0
  191. package/template/packages/shared/package.json +1 -1
  192. package/template/packages/shared/src/ai-runtime-report.ts +134 -0
  193. package/template/packages/shared/src/platform-settings.ts +17 -0
  194. package/template/scripts/lib/postgres-regression.ts +201 -0
@@ -14,9 +14,22 @@ from .adk_failure import AdkFailureState
14
14
  from .contracts import JsonValue
15
15
  from .engine import EngineRunRequest, SafeEngineError
16
16
  from .multimodal import attach_artifacts
17
- from .openai_engine import cancelled, parse_arguments
17
+ from .openai_engine_validation import (
18
+ attempt_outcome,
19
+ cancelled,
20
+ parse_arguments,
21
+ )
18
22
  from .openai_provider import OpenAiCompatibleProvider, ProviderTurn
19
23
  from .provider_governance import ProviderRequestGovernance
24
+ from .report_lifecycle import (
25
+ finish_attempt_report_safely,
26
+ start_attempt_report_safely,
27
+ )
28
+ from .report_store import (
29
+ AgentRuntimeReportStore,
30
+ InMemoryAgentRuntimeReportStore,
31
+ )
32
+ from .report_types import UsageFacts
20
33
 
21
34
 
22
35
  class GovernedOpenAiLlm(BaseLlm):
@@ -27,6 +40,7 @@ class GovernedOpenAiLlm(BaseLlm):
27
40
  _governance: ProviderRequestGovernance = PrivateAttr()
28
41
  _cancellation: asyncio.Event = PrivateAttr()
29
42
  _failures: AdkFailureState = PrivateAttr()
43
+ _report_store: AgentRuntimeReportStore = PrivateAttr()
30
44
  _attempt: int = PrivateAttr(default=0)
31
45
 
32
46
  def __init__(
@@ -36,6 +50,7 @@ class GovernedOpenAiLlm(BaseLlm):
36
50
  governance: ProviderRequestGovernance,
37
51
  cancellation: asyncio.Event,
38
52
  failures: AdkFailureState,
53
+ report_store: AgentRuntimeReportStore | None = None,
39
54
  ) -> None:
40
55
  super().__init__(model=provider.config.model)
41
56
  self._request = request
@@ -43,6 +58,7 @@ class GovernedOpenAiLlm(BaseLlm):
43
58
  self._governance = governance
44
59
  self._cancellation = cancellation
45
60
  self._failures = failures
61
+ self._report_store = report_store or InMemoryAgentRuntimeReportStore()
46
62
 
47
63
  async def generate_content_async(
48
64
  self,
@@ -50,6 +66,9 @@ class GovernedOpenAiLlm(BaseLlm):
50
66
  stream: bool = False,
51
67
  ) -> AsyncGenerator[LlmResponse, None]:
52
68
  del stream
69
+ report_attempt = None
70
+ finalized = False
71
+ turn: ProviderTurn | None = None
53
72
  try:
54
73
  cancelled(self._cancellation)
55
74
  self._attempt += 1
@@ -59,6 +78,16 @@ class GovernedOpenAiLlm(BaseLlm):
59
78
  "Agent Tool 调用轮次超过上限",
60
79
  retryable=False,
61
80
  )
81
+ report_attempt = await start_attempt_report_safely(
82
+ self._report_store,
83
+ self._request.run_id,
84
+ self._attempt,
85
+ self._provider.config.deployment_key,
86
+ self._provider.config.model,
87
+ )
88
+ attempt_sequence = (
89
+ report_attempt.sequence if report_attempt else self._attempt
90
+ )
62
91
  messages = attach_artifacts(
63
92
  adk_messages(llm_request, self._request.instructions),
64
93
  self._request.artifacts,
@@ -67,7 +96,7 @@ class GovernedOpenAiLlm(BaseLlm):
67
96
  deployment_key=self._provider.config.deployment_key,
68
97
  operation_key=self._request.operation_key,
69
98
  run_id=self._request.run_id,
70
- attempt=self._attempt,
99
+ attempt=attempt_sequence,
71
100
  messages=messages,
72
101
  tools=self._request.allowed_tools,
73
102
  policy=self._request.provider_data_policy,
@@ -96,11 +125,42 @@ class GovernedOpenAiLlm(BaseLlm):
96
125
  turn,
97
126
  {tool.key for tool in self._request.allowed_tools},
98
127
  )
99
- async for response in responses_for_turn(turn):
128
+ await finish_attempt_report_safely(
129
+ self._report_store,
130
+ report_attempt,
131
+ "completed",
132
+ turn.usage,
133
+ reported_model=turn.reported_model,
134
+ )
135
+ finalized = True
136
+ async for response in responses_for_turn(
137
+ turn,
138
+ attempt_sequence,
139
+ ):
100
140
  yield response
101
141
  except SafeEngineError as error:
142
+ if not finalized:
143
+ await finish_attempt_report_safely(
144
+ self._report_store,
145
+ report_attempt,
146
+ attempt_outcome(error),
147
+ turn.usage if turn else UsageFacts(),
148
+ reported_model=turn.reported_model if turn else None,
149
+ safe_error_code=error.code,
150
+ )
102
151
  self._failures.capture(error)
103
152
  raise
153
+ except Exception:
154
+ if not finalized:
155
+ await finish_attempt_report_safely(
156
+ self._report_store,
157
+ report_attempt,
158
+ "failed",
159
+ turn.usage if turn else UsageFacts(),
160
+ reported_model=turn.reported_model if turn else None,
161
+ safe_error_code="RUNTIME_UNAVAILABLE",
162
+ )
163
+ raise
104
164
 
105
165
 
106
166
  def adk_messages(
@@ -179,9 +239,13 @@ def validate_tool_calls(turn: ProviderTurn, allowed: set[str]) -> None:
179
239
 
180
240
  async def responses_for_turn(
181
241
  turn: ProviderTurn,
242
+ attempt_sequence: int,
182
243
  ) -> AsyncGenerator[LlmResponse, None]:
183
- usage = turn.usage or {"source": "unknown"}
184
- metadata = {"southwindUsage": usage}
244
+ usage = turn.usage.event_value()
245
+ metadata = {
246
+ "southwindUsage": usage,
247
+ "southwindAttemptSequence": attempt_sequence,
248
+ }
185
249
  if turn.tool_calls:
186
250
  yield LlmResponse(
187
251
  content=types.Content(
@@ -222,7 +286,7 @@ async def responses_for_turn(
222
286
 
223
287
 
224
288
  def adk_usage(
225
- usage: dict[str, JsonValue],
289
+ usage: dict[str, str | int],
226
290
  ) -> types.GenerateContentResponseUsageMetadata | None:
227
291
  input_tokens = usage.get("inputTokens")
228
292
  output_tokens = usage.get("outputTokens")
@@ -16,7 +16,7 @@ from .engine import (
16
16
  EngineSignal,
17
17
  SafeEngineError,
18
18
  )
19
- from .openai_engine import cancelled
19
+ from .openai_engine_validation import cancelled
20
20
  from .tool_host import (
21
21
  ToolApprovalPending,
22
22
  ToolApprovalResolved,
@@ -29,16 +29,21 @@ from .http_helpers import (
29
29
  provider_governance_snapshot,
30
30
  safe_error,
31
31
  )
32
- from .runtime_factory import default_engine, default_store
32
+ from .report_routes import create_report_router
33
+ from .report_store import AgentRuntimeReportStore
34
+ from .runtime_factory import (
35
+ default_engine,
36
+ default_report_store,
37
+ default_store,
38
+ )
33
39
  from .service import (
34
40
  AgentRunService,
35
- IdempotencyConflictError,
36
41
  OperationNotFoundError,
37
42
  RunNotFoundError,
38
43
  )
39
44
  from .sqlite_store import RunStoreCapacityError
40
45
  from .sse import event_stream
41
- from .store import ReleaseNotFoundError, RunStore
46
+ from .store import IdempotencyConflictError, ReleaseNotFoundError, RunStore
42
47
  from .telemetry import configure_telemetry
43
48
 
44
49
 
@@ -48,12 +53,15 @@ def create_app(
48
53
  heartbeat_seconds: float = 15.0,
49
54
  internal_token: str | None = None,
50
55
  store: RunStore | None = None,
56
+ report_store: AgentRuntimeReportStore | None = None,
51
57
  ) -> FastAPI:
52
58
  configure_telemetry()
53
- runtime_engine = engine or default_engine()
59
+ runtime_report_store = report_store or default_report_store()
60
+ runtime_engine = engine or default_engine(runtime_report_store)
54
61
  runtime = AgentRunService(
55
62
  runtime_engine,
56
63
  store or default_store(),
64
+ runtime_report_store,
57
65
  (
58
66
  ArtifactLoader(
59
67
  os.environ.get(
@@ -80,6 +88,9 @@ def create_app(
80
88
  version="0.1.0",
81
89
  lifespan=lifespan,
82
90
  )
91
+ application.include_router(
92
+ create_report_router(runtime_report_store, internal_token)
93
+ )
83
94
 
84
95
  @application.get("/health/live")
85
96
  async def live() -> dict[str, str]:
@@ -322,6 +333,7 @@ def create_app(
322
333
  )
323
334
 
324
335
  application.state.run_service = runtime
336
+ application.state.report_store = runtime_report_store
325
337
  return application
326
338
 
327
339
 
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from .store import RunRecord, RunStore
4
+
5
+
6
+ async def resolve_pending_approval(
7
+ store: RunStore,
8
+ record: RunRecord,
9
+ outcome: str,
10
+ ) -> None:
11
+ pending: dict[str, object] | None = None
12
+ for event in record.events:
13
+ if event.get("type") == "approval.required":
14
+ pending = event
15
+ elif event.get("type") == "approval.resolved":
16
+ pending = None
17
+ if not pending:
18
+ return
19
+ work_item_id = pending.get("workItemId")
20
+ tool_call_id = pending.get("toolCallId")
21
+ if isinstance(work_item_id, str) and isinstance(tool_call_id, str):
22
+ await store.append(
23
+ record.run_id,
24
+ "approval.resolved",
25
+ {
26
+ "workItemId": work_item_id,
27
+ "toolCallId": tool_call_id,
28
+ "outcome": outcome,
29
+ },
30
+ )
@@ -139,6 +139,14 @@ class RuntimeRelease(CanonicalModel):
139
139
  manifest_digest: str = Field(pattern=r"^[a-f0-9]{64}$")
140
140
 
141
141
 
142
+ class RunReportingContext(CanonicalModel):
143
+ """Trusted platform metadata for reporting, never an authorization fact."""
144
+
145
+ actor_id: str = Field(min_length=1, max_length=64)
146
+ actor_type: str = Field(min_length=1, max_length=32)
147
+ department_id: str | None = Field(default=None, max_length=64)
148
+
149
+
142
150
  def legacy_release() -> RuntimeRelease:
143
151
  return RuntimeRelease(
144
152
  release_id="legacy",
@@ -174,6 +182,7 @@ class StartRunRequest(CanonicalModel):
174
182
  tool_host_url: str = ""
175
183
  release: RuntimeRelease = Field(default_factory=legacy_release)
176
184
  trace_id: str = Field(default="", max_length=160)
185
+ reporting: RunReportingContext | None = None
177
186
 
178
187
 
179
188
  class StartRunResponse(CanonicalModel):
@@ -10,6 +10,7 @@ from .engine import (
10
10
  OutputDelta,
11
11
  UsageReported,
12
12
  )
13
+ from .report_types import UsageFacts
13
14
 
14
15
 
15
16
  class DeterministicEngineAdapter:
@@ -49,7 +50,7 @@ class DeterministicEngineAdapter:
49
50
  if isinstance(input_text, str):
50
51
  text = input_text
51
52
  yield OutputDelta(text)
52
- yield UsageReported({"source": "unknown"})
53
+ yield UsageReported(1, UsageFacts())
53
54
  yield EngineCompleted(request.input)
54
55
  finally:
55
56
  self._cancellations.pop(request.run_id, None)
@@ -13,6 +13,7 @@ from .contracts import (
13
13
  RuntimeToolDefinition,
14
14
  ToolNetworkPolicy,
15
15
  )
16
+ from .report_types import UsageFacts
16
17
 
17
18
 
18
19
  @dataclass(frozen=True)
@@ -75,7 +76,8 @@ class ToolCompleted:
75
76
 
76
77
  @dataclass(frozen=True)
77
78
  class UsageReported:
78
- usage: dict[str, JsonValue]
79
+ attempt_sequence: int
80
+ usage: UsageFacts
79
81
 
80
82
 
81
83
  @dataclass(frozen=True)
@@ -19,8 +19,29 @@ from .engine import (
19
19
  UsageReported,
20
20
  )
21
21
  from .multimodal import attach_artifacts, final_output
22
- from .openai_provider import OpenAiCompatibleProvider, ProviderToolCall
22
+ from .openai_engine_validation import (
23
+ assistant_tool_message,
24
+ attempt_outcome,
25
+ cancelled,
26
+ parse_arguments,
27
+ validate_provider_tool_calls,
28
+ validate_runtime_request,
29
+ )
30
+ from .openai_provider import (
31
+ OpenAiCompatibleProvider,
32
+ ProviderToolCall,
33
+ ProviderTurn,
34
+ )
23
35
  from .provider_governance import ProviderRequestGovernance
36
+ from .report_lifecycle import (
37
+ finish_attempt_report_safely,
38
+ start_attempt_report_safely,
39
+ )
40
+ from .report_store import (
41
+ AgentRuntimeReportStore,
42
+ InMemoryAgentRuntimeReportStore,
43
+ )
44
+ from .report_types import UsageFacts
24
45
  from .tool_host import (
25
46
  ToolApprovalPending,
26
47
  ToolApprovalResolved,
@@ -36,10 +57,12 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
36
57
  provider: OpenAiCompatibleProvider,
37
58
  tool_host: ToolHostClient,
38
59
  governance: ProviderRequestGovernance,
60
+ report_store: AgentRuntimeReportStore | None = None,
39
61
  ) -> None:
40
62
  self._provider = provider
41
63
  self._tool_host = tool_host
42
64
  self._governance = governance
65
+ self._report_store = report_store or InMemoryAgentRuntimeReportStore()
43
66
  self._cancellations: dict[str, asyncio.Event] = {}
44
67
 
45
68
  @property
@@ -79,34 +102,79 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
79
102
  try:
80
103
  for attempt in range(1, self._provider.config.max_tool_rounds + 1):
81
104
  cancelled(cancellation)
82
- governed = self._governance.authorize(
83
- deployment_key=self._provider.config.deployment_key,
84
- operation_key=request.operation_key,
85
- run_id=request.run_id,
86
- attempt=attempt,
87
- messages=messages,
88
- tools=request.allowed_tools,
89
- policy=request.provider_data_policy,
90
- input_value=request.input,
91
- artifact_bytes=sum(
92
- artifact.byte_size for artifact in request.artifacts
93
- ),
94
- output_schema=request.output_schema,
95
- measure=lambda messages, tools, schema: len(
96
- self._provider.request_body(
97
- messages,
98
- tools,
99
- request.output_schema_key,
100
- schema,
101
- )
102
- ),
105
+ report_attempt = await start_attempt_report_safely(
106
+ self._report_store,
107
+ request.run_id,
108
+ attempt,
109
+ self._provider.config.deployment_key,
110
+ self._provider.config.model,
103
111
  )
104
- turn = await self._provider.complete_turn(
105
- governed.messages,
106
- governed.tools,
107
- request.output_schema_key,
108
- governed.output_schema,
112
+ attempt_sequence = (
113
+ report_attempt.sequence if report_attempt else attempt
109
114
  )
115
+ finalized = False
116
+ turn: ProviderTurn | None = None
117
+ try:
118
+ governed = self._governance.authorize(
119
+ deployment_key=self._provider.config.deployment_key,
120
+ operation_key=request.operation_key,
121
+ run_id=request.run_id,
122
+ attempt=attempt_sequence,
123
+ messages=messages,
124
+ tools=request.allowed_tools,
125
+ policy=request.provider_data_policy,
126
+ input_value=request.input,
127
+ artifact_bytes=sum(
128
+ artifact.byte_size for artifact in request.artifacts
129
+ ),
130
+ output_schema=request.output_schema,
131
+ measure=lambda messages, tools, schema: len(
132
+ self._provider.request_body(
133
+ messages,
134
+ tools,
135
+ request.output_schema_key,
136
+ schema,
137
+ )
138
+ ),
139
+ )
140
+ turn = await self._provider.complete_turn(
141
+ governed.messages,
142
+ governed.tools,
143
+ request.output_schema_key,
144
+ governed.output_schema,
145
+ )
146
+ validate_provider_tool_calls(turn.tool_calls, allowed)
147
+ await finish_attempt_report_safely(
148
+ self._report_store,
149
+ report_attempt,
150
+ "completed",
151
+ turn.usage,
152
+ reported_model=turn.reported_model,
153
+ )
154
+ finalized = True
155
+ except SafeEngineError as error:
156
+ if not finalized:
157
+ await finish_attempt_report_safely(
158
+ self._report_store,
159
+ report_attempt,
160
+ attempt_outcome(error),
161
+ turn.usage if turn else UsageFacts(),
162
+ reported_model=(turn.reported_model if turn else None),
163
+ safe_error_code=error.code,
164
+ )
165
+ raise
166
+ except Exception:
167
+ if not finalized:
168
+ await finish_attempt_report_safely(
169
+ self._report_store,
170
+ report_attempt,
171
+ "failed",
172
+ turn.usage if turn else UsageFacts(),
173
+ reported_model=(turn.reported_model if turn else None),
174
+ safe_error_code="RUNTIME_UNAVAILABLE",
175
+ )
176
+ raise
177
+ yield UsageReported(attempt_sequence, turn.usage)
110
178
  if turn.tool_calls:
111
179
  messages.append(assistant_tool_message(turn.tool_calls))
112
180
  async for signal in self._execute_tools(
@@ -127,7 +195,6 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
127
195
  retryable=False,
128
196
  )
129
197
  yield OutputDelta(delta)
130
- yield UsageReported(turn.usage or {"source": "unknown"})
131
198
  yield EngineCompleted(final_output(turn.text, governed.output_schema))
132
199
  return
133
200
  raise SafeEngineError(
@@ -231,105 +298,3 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
231
298
 
232
299
  def governance_health(self) -> dict[str, int | float | str | None]:
233
300
  return self._governance.health()
234
-
235
-
236
- def validate_runtime_request(
237
- request: EngineRunRequest,
238
- provider_deployment_key: str,
239
- provider_capabilities: tuple[str, ...],
240
- provider_input_modalities: tuple[str, ...],
241
- ) -> None:
242
- if (
243
- not request.instructions.strip()
244
- or not request.user_prompt.strip()
245
- or not request.execution_grant.strip()
246
- or not request.tool_host_url.strip()
247
- ):
248
- raise SafeEngineError(
249
- "INVALID_INPUT",
250
- "Agent Operation 运行计划不完整",
251
- retryable=False,
252
- )
253
- if set(request.required_capabilities) - set(provider_capabilities):
254
- raise SafeEngineError(
255
- "MODEL_ROUTE_UNAVAILABLE",
256
- "模型 Deployment 缺少 Operation 所需能力",
257
- retryable=True,
258
- )
259
- if set(request.input_modalities) - set(provider_input_modalities):
260
- raise SafeEngineError(
261
- "MODEL_ROUTE_UNAVAILABLE",
262
- "模型 Deployment 不支持 Operation 输入模态",
263
- retryable=True,
264
- )
265
- if (
266
- request.provider_egress.mode != "configured-endpoint"
267
- or request.provider_egress.deployment_key != provider_deployment_key
268
- ):
269
- raise SafeEngineError(
270
- "FORBIDDEN",
271
- "Agent Operation 未授权当前模型部署出网",
272
- retryable=False,
273
- )
274
- if request.tool_network_policy.mode != "forbidden":
275
- raise SafeEngineError(
276
- "TOOL_DENIED",
277
- "Agent Tool 网络策略不受支持",
278
- retryable=False,
279
- )
280
- if (
281
- not request.provider_data_policy.input_fields
282
- or not request.provider_data_policy.envelope_fields
283
- ):
284
- raise SafeEngineError(
285
- "FORBIDDEN",
286
- "Agent Operation 缺少 Provider 字段治理策略",
287
- retryable=False,
288
- )
289
-
290
-
291
- def cancelled(event: asyncio.Event) -> None:
292
- if event.is_set():
293
- raise SafeEngineError(
294
- "RUN_CANCELLED",
295
- "Agent Run 已取消",
296
- retryable=False,
297
- )
298
-
299
-
300
- def parse_arguments(raw: str) -> dict[str, JsonValue]:
301
- try:
302
- value = json.loads(raw or "{}")
303
- except json.JSONDecodeError:
304
- raise SafeEngineError(
305
- "TOOL_DENIED",
306
- "Agent Tool 参数无效",
307
- retryable=False,
308
- ) from None
309
- if not isinstance(value, dict):
310
- raise SafeEngineError(
311
- "TOOL_DENIED",
312
- "Agent Tool 参数必须是对象",
313
- retryable=False,
314
- )
315
- return value
316
-
317
-
318
- def assistant_tool_message(
319
- calls: list[ProviderToolCall],
320
- ) -> dict[str, JsonValue]:
321
- return {
322
- "role": "assistant",
323
- "content": None,
324
- "tool_calls": [
325
- {
326
- "id": call.call_id,
327
- "type": "function",
328
- "function": {
329
- "name": call.name,
330
- "arguments": call.arguments,
331
- },
332
- }
333
- for call in calls
334
- ],
335
- }