create-windy 0.3.2 → 0.3.5

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 (343) hide show
  1. package/README.md +27 -9
  2. package/dist/cli.js +825 -117
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +16 -3
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +4 -0
  7. package/template/README.md +18 -11
  8. package/template/_gitignore +1 -0
  9. package/template/apps/agent-server/README.md +2 -0
  10. package/template/apps/agent-server/pyproject.toml +8 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +87 -10
  12. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +18 -3
  13. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +78 -11
  14. package/template/apps/agent-server/src/southwind_agent_server/app.py +120 -115
  15. package/template/apps/agent-server/src/southwind_agent_server/artifact_input.py +153 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/config.py +29 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +81 -1
  18. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +8 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/engine.py +28 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/fingerprint.py +41 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/http_helpers.py +62 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/lease.py +15 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/multimodal.py +76 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +112 -20
  25. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +14 -1
  26. package/template/apps/agent-server/src/southwind_agent_server/postgres_store.py +329 -0
  27. package/template/apps/agent-server/src/southwind_agent_server/postgres_store_backend.py +332 -0
  28. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +64 -3
  29. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +68 -0
  30. package/template/apps/agent-server/src/southwind_agent_server/service.py +218 -56
  31. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +36 -0
  32. package/template/apps/agent-server/src/southwind_agent_server/sqlite_catalog.py +107 -0
  33. package/template/apps/agent-server/src/southwind_agent_server/sqlite_schema.py +65 -0
  34. package/template/apps/agent-server/src/southwind_agent_server/sqlite_serialization.py +16 -0
  35. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +89 -57
  36. package/template/apps/agent-server/src/southwind_agent_server/store.py +105 -2
  37. package/template/apps/agent-server/src/southwind_agent_server/store_cipher.py +40 -0
  38. package/template/apps/agent-server/src/southwind_agent_server/telemetry.py +89 -0
  39. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +72 -0
  40. package/template/apps/agent-server/tests/test_adk_adapter.py +6 -2
  41. package/template/apps/agent-server/tests/test_api.py +24 -1
  42. package/template/apps/agent-server/tests/test_artifact_input.py +110 -0
  43. package/template/apps/agent-server/tests/test_config.py +4 -0
  44. package/template/apps/agent-server/tests/test_fingerprint.py +10 -0
  45. package/template/apps/agent-server/tests/test_multimodal.py +51 -0
  46. package/template/apps/agent-server/tests/test_openai_provider.py +6 -2
  47. package/template/apps/agent-server/tests/test_postgres_store.py +131 -0
  48. package/template/apps/agent-server/tests/test_provider_governance.py +61 -0
  49. package/template/apps/agent-server/tests/test_sqlite_store.py +95 -10
  50. package/template/apps/agent-server/tests/test_telemetry.py +25 -0
  51. package/template/apps/agent-server/tests/test_tool_approval.py +28 -0
  52. package/template/apps/agent-server/uv.lock +165 -0
  53. package/template/apps/document-server/Dockerfile +19 -0
  54. package/template/apps/document-server/pyproject.toml +43 -0
  55. package/template/apps/document-server/src/southwind_document_server/__init__.py +1 -0
  56. package/template/apps/document-server/src/southwind_document_server/adapter.py +337 -0
  57. package/template/apps/document-server/src/southwind_document_server/app.py +151 -0
  58. package/template/apps/document-server/src/southwind_document_server/config.py +94 -0
  59. package/template/apps/document-server/src/southwind_document_server/contracts.py +103 -0
  60. package/template/apps/document-server/src/southwind_document_server/service.py +149 -0
  61. package/template/apps/document-server/src/southwind_document_server/store.py +185 -0
  62. package/template/apps/document-server/tests/__init__.py +1 -0
  63. package/template/apps/document-server/tests/test_config.py +42 -0
  64. package/template/apps/document-server/tests/test_service.py +140 -0
  65. package/template/apps/document-server/uv.lock +430 -0
  66. package/template/apps/embedding-server/Dockerfile +17 -0
  67. package/template/apps/embedding-server/pyproject.toml +44 -0
  68. package/template/apps/embedding-server/src/southwind_embedding_server/__init__.py +1 -0
  69. package/template/apps/embedding-server/src/southwind_embedding_server/adapter.py +254 -0
  70. package/template/apps/embedding-server/src/southwind_embedding_server/app.py +119 -0
  71. package/template/apps/embedding-server/src/southwind_embedding_server/config.py +120 -0
  72. package/template/apps/embedding-server/src/southwind_embedding_server/contracts.py +106 -0
  73. package/template/apps/embedding-server/src/southwind_embedding_server/error.py +6 -0
  74. package/template/apps/embedding-server/src/southwind_embedding_server/image_input.py +87 -0
  75. package/template/apps/embedding-server/tests/__init__.py +1 -0
  76. package/template/apps/embedding-server/tests/test_adapter.py +92 -0
  77. package/template/apps/embedding-server/tests/test_config.py +38 -0
  78. package/template/apps/embedding-server/uv.lock +449 -0
  79. package/template/apps/server/src/agent/background-operation-port.ts +37 -0
  80. package/template/apps/server/src/agent/background-operation.test.ts +231 -0
  81. package/template/apps/server/src/agent/background-operation.ts +252 -0
  82. package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +114 -0
  83. package/template/apps/server/src/agent/execution-grants.ts +7 -1
  84. package/template/apps/server/src/agent/fake-runtime.test-support.ts +43 -0
  85. package/template/apps/server/src/agent/idempotency-key.test.ts +28 -0
  86. package/template/apps/server/src/agent/idempotency-key.ts +11 -0
  87. package/template/apps/server/src/agent/remote-runtime.test.ts +115 -1
  88. package/template/apps/server/src/agent/remote-runtime.ts +173 -5
  89. package/template/apps/server/src/agent/run-authorization.test-support.ts +9 -0
  90. package/template/apps/server/src/agent/run-authorization.test.ts +126 -0
  91. package/template/apps/server/src/agent/run-authorization.ts +180 -0
  92. package/template/apps/server/src/agent/run-binding-repository.ts +93 -0
  93. package/template/apps/server/src/agent/run-contracts.ts +88 -1
  94. package/template/apps/server/src/agent/run-definition-support.ts +58 -0
  95. package/template/apps/server/src/agent/run-errors.ts +25 -0
  96. package/template/apps/server/src/agent/run-facade.test.ts +57 -46
  97. package/template/apps/server/src/agent/run-facade.ts +185 -65
  98. package/template/apps/server/src/agent/run-release.ts +78 -0
  99. package/template/apps/server/src/agent/run-route-input.ts +169 -0
  100. package/template/apps/server/src/agent/run-routes.ts +63 -76
  101. package/template/apps/server/src/agent/runtime-bootstrap.ts +169 -13
  102. package/template/apps/server/src/agent/sandbox-client.ts +200 -0
  103. package/template/apps/server/src/agent/sandbox-protocol.ts +76 -0
  104. package/template/apps/server/src/agent/sandbox-registry.ts +205 -0
  105. package/template/apps/server/src/agent/sandbox-runtime.test.ts +137 -0
  106. package/template/apps/server/src/agent/sandbox-worker-server.ts +195 -0
  107. package/template/apps/server/src/agent/sandbox-worker.ts +12 -0
  108. package/template/apps/server/src/agent/tool-approval.test.ts +75 -0
  109. package/template/apps/server/src/agent/tool-approval.ts +115 -0
  110. package/template/apps/server/src/agent/tool-audit.ts +62 -0
  111. package/template/apps/server/src/agent/tool-host.test.ts +39 -11
  112. package/template/apps/server/src/agent/tool-host.ts +184 -17
  113. package/template/apps/server/src/agent/tool-registry.ts +52 -47
  114. package/template/apps/server/src/agent/vision-facade.test.ts +256 -0
  115. package/template/apps/server/src/agent/vision-input.ts +120 -0
  116. package/template/apps/server/src/ai-artifact/codec.test.ts +44 -0
  117. package/template/apps/server/src/ai-artifact/codec.ts +137 -0
  118. package/template/apps/server/src/ai-artifact/routes.ts +37 -0
  119. package/template/apps/server/src/ai-artifact/runtime.ts +205 -0
  120. package/template/apps/server/src/application-services.ts +62 -2
  121. package/template/apps/server/src/auth/reauthentication.test.ts +40 -0
  122. package/template/apps/server/src/auth/reauthentication.ts +19 -0
  123. package/template/apps/server/src/auth/runtime.ts +9 -1
  124. package/template/apps/server/src/data-access/context.ts +6 -4
  125. package/template/apps/server/src/data-access/scope-snapshot.ts +27 -0
  126. package/template/apps/server/src/data-governance/export/approval-port.ts +6 -3
  127. package/template/apps/server/src/data-governance/export/authorization-port.test.ts +23 -4
  128. package/template/apps/server/src/data-governance/export/authorization-port.ts +89 -25
  129. package/template/apps/server/src/data-governance/export/drizzle-repository.integration.test.ts +2 -0
  130. package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -0
  131. package/template/apps/server/src/data-governance/export/job-runtime.test.ts +15 -4
  132. package/template/apps/server/src/data-governance/export/job-runtime.ts +68 -15
  133. package/template/apps/server/src/data-governance/export/ndjson-stream.ts +25 -6
  134. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +4 -2
  135. package/template/apps/server/src/data-governance/export/renderer-job-runtime.test.ts +235 -0
  136. package/template/apps/server/src/data-governance/export/renderer-runtime.ts +293 -0
  137. package/template/apps/server/src/data-governance/export/renderer-selection.test.ts +67 -0
  138. package/template/apps/server/src/data-governance/export/repository.test.ts +2 -0
  139. package/template/apps/server/src/data-governance/export/request-fingerprint.test.ts +13 -0
  140. package/template/apps/server/src/data-governance/export/request-fingerprint.ts +2 -0
  141. package/template/apps/server/src/data-governance/export/routes.ts +11 -1
  142. package/template/apps/server/src/data-governance/export/scope-snapshot.ts +6 -18
  143. package/template/apps/server/src/data-governance/export/service-support.ts +188 -0
  144. package/template/apps/server/src/data-governance/export/service.ts +90 -113
  145. package/template/apps/server/src/data-governance/export/source.ts +101 -0
  146. package/template/apps/server/src/data-governance/export/types.ts +27 -0
  147. package/template/apps/server/src/document/contracts.ts +76 -0
  148. package/template/apps/server/src/document/facade.ts +339 -0
  149. package/template/apps/server/src/document/remote-runtime.test.ts +96 -0
  150. package/template/apps/server/src/document/remote-runtime.ts +223 -0
  151. package/template/apps/server/src/document/routes.ts +193 -0
  152. package/template/apps/server/src/embedding/contracts.ts +83 -0
  153. package/template/apps/server/src/embedding/error.ts +10 -0
  154. package/template/apps/server/src/embedding/facade.ts +217 -0
  155. package/template/apps/server/src/embedding/input.ts +172 -0
  156. package/template/apps/server/src/embedding/remote-runtime.test.ts +66 -0
  157. package/template/apps/server/src/embedding/remote-runtime.ts +218 -0
  158. package/template/apps/server/src/embedding/routes.ts +152 -0
  159. package/template/apps/server/src/index.ts +40 -43
  160. package/template/apps/server/src/jobs/artifact-service.test.ts +14 -2
  161. package/template/apps/server/src/jobs/artifact-service.ts +10 -0
  162. package/template/apps/server/src/jobs/drizzle-execution-repository.ts +17 -18
  163. package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +19 -1
  164. package/template/apps/server/src/jobs/drizzle-mappers.ts +4 -1
  165. package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +49 -2
  166. package/template/apps/server/src/jobs/drizzle-repository.ts +1 -0
  167. package/template/apps/server/src/jobs/execution-repository.ts +17 -0
  168. package/template/apps/server/src/jobs/execution.test.ts +34 -0
  169. package/template/apps/server/src/jobs/in-memory-control.ts +135 -0
  170. package/template/apps/server/src/jobs/in-memory-execution.ts +34 -65
  171. package/template/apps/server/src/jobs/legacy-service-runtime.ts +5 -2
  172. package/template/apps/server/src/jobs/repository.ts +3 -0
  173. package/template/apps/server/src/jobs/worker.ts +80 -41
  174. package/template/apps/server/src/module-host/ai-operation-port.ts +170 -0
  175. package/template/apps/server/src/module-host/durable-context.test.ts +312 -0
  176. package/template/apps/server/src/module-host/durable-context.ts +124 -0
  177. package/template/apps/server/src/module-host/durable-handler-registration.ts +72 -0
  178. package/template/apps/server/src/module-host/governed-export-route-support.ts +171 -0
  179. package/template/apps/server/src/module-host/governed-export-routes.test.ts +327 -0
  180. package/template/apps/server/src/module-host/governed-export-routes.ts +238 -0
  181. package/template/apps/server/src/module-host/governed-renderer-registration.test.ts +247 -0
  182. package/template/apps/server/src/module-host/governed-resource-routes.ts +331 -0
  183. package/template/apps/server/src/module-host/governed-resources.test.ts +344 -0
  184. package/template/apps/server/src/module-host/governed-resources.ts +203 -0
  185. package/template/apps/server/src/module-host/http-runtime.ts +95 -0
  186. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  187. package/template/apps/server/src/module-host/initialize.test.ts +49 -1
  188. package/template/apps/server/src/module-host/initialize.ts +39 -16
  189. package/template/apps/server/src/module-host/job-port.test.ts +262 -0
  190. package/template/apps/server/src/module-host/job-port.ts +334 -0
  191. package/template/apps/server/src/module-host/request-context.test.ts +88 -2
  192. package/template/apps/server/src/module-host/request-context.ts +56 -22
  193. package/template/apps/server/src/module-host/route-installer.test.ts +10 -0
  194. package/template/apps/server/src/module-host/route-installer.ts +36 -1
  195. package/template/apps/server/src/module-host/sandbox-tool-registration.ts +42 -0
  196. package/template/apps/server/src/module-host/tool-handlers.ts +67 -31
  197. package/template/apps/server/src/module-host.ts +1 -0
  198. package/template/apps/server/src/module-runtime-validation.ts +7 -3
  199. package/template/apps/server/src/speech/contracts.ts +76 -0
  200. package/template/apps/server/src/speech/facade.ts +316 -0
  201. package/template/apps/server/src/speech/remote-runtime.test.ts +88 -0
  202. package/template/apps/server/src/speech/remote-runtime.ts +227 -0
  203. package/template/apps/server/src/speech/routes.ts +180 -0
  204. package/template/apps/server/src/system/actor.ts +39 -0
  205. package/template/apps/server/src/system/route-helpers.ts +1 -0
  206. package/template/apps/server/src/workflow/service.ts +2 -1
  207. package/template/apps/speech-server/Dockerfile +22 -0
  208. package/template/apps/speech-server/pyproject.toml +43 -0
  209. package/template/apps/speech-server/src/southwind_speech_server/__init__.py +1 -0
  210. package/template/apps/speech-server/src/southwind_speech_server/adapter.py +249 -0
  211. package/template/apps/speech-server/src/southwind_speech_server/app.py +183 -0
  212. package/template/apps/speech-server/src/southwind_speech_server/config.py +100 -0
  213. package/template/apps/speech-server/src/southwind_speech_server/contracts.py +103 -0
  214. package/template/apps/speech-server/src/southwind_speech_server/errors.py +6 -0
  215. package/template/apps/speech-server/src/southwind_speech_server/normalizer.py +108 -0
  216. package/template/apps/speech-server/src/southwind_speech_server/output_validation.py +128 -0
  217. package/template/apps/speech-server/src/southwind_speech_server/service.py +162 -0
  218. package/template/apps/speech-server/src/southwind_speech_server/store.py +209 -0
  219. package/template/apps/speech-server/tests/__init__.py +1 -0
  220. package/template/apps/speech-server/tests/test_config.py +33 -0
  221. package/template/apps/speech-server/tests/test_service.py +154 -0
  222. package/template/apps/speech-server/uv.lock +430 -0
  223. package/template/apps/web/src/app/business-routes.ts +22 -0
  224. package/template/apps/web/src/components/common/AccessSnapshotBoundary.vue +112 -0
  225. package/template/apps/web/src/components/common/AccessSnapshotBoundary.webtest.ts +130 -0
  226. package/template/apps/web/src/components/common/AccessSnapshotNotice.vue +117 -0
  227. package/template/apps/web/src/components/common/FormDialog.vue +6 -0
  228. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +13 -0
  229. package/template/apps/web/src/composables/access-snapshot-change.ts +131 -0
  230. package/template/apps/web/src/composables/access-snapshot-change.webtest.ts +103 -0
  231. package/template/apps/web/src/composables/useAccessSnapshot.ts +48 -2
  232. package/template/apps/web/src/composables/useAccessSnapshot.webtest.ts +54 -0
  233. package/template/apps/web/src/composables/useAccessSnapshotMonitor.ts +42 -0
  234. package/template/apps/web/src/layout/AppShell.vue +28 -25
  235. package/template/apps/web/src/layout/AppShell.webtest.ts +1 -0
  236. package/template/apps/web/src/router/business-routes.ts +68 -0
  237. package/template/apps/web/src/router/business-routes.webtest.ts +85 -0
  238. package/template/apps/web/src/router/index.ts +7 -0
  239. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  240. package/template/apps/web/src/services/access-api.ts +5 -1
  241. package/template/apps/web/src/services/access-api.webtest.ts +35 -0
  242. package/template/docker-compose.yml +127 -0
  243. package/template/docs/architecture/ai-egress.md +12 -9
  244. package/template/docs/architecture/ai-runtime.md +23 -11
  245. package/template/docs/architecture/durable-jobs.md +25 -3
  246. package/template/docs/architecture/foundation-infrastructure.md +163 -0
  247. package/template/docs/architecture/module-host.md +115 -0
  248. package/template/docs/architecture/server-module-interface.md +39 -0
  249. package/template/docs/architecture/web-access-snapshot-resilience.md +60 -0
  250. package/template/docs/development/business-web-routes.md +66 -0
  251. package/template/docs/development/custom-login-page.md +3 -3
  252. package/template/docs/development/module-lifecycle.md +71 -0
  253. package/template/docs/operations/observability.md +5 -1
  254. package/template/docs/platform/agent-runtime.md +21 -11
  255. package/template/docs/platform/agent-tool-sandbox.md +58 -0
  256. package/template/docs/platform/agent-tools.md +5 -0
  257. package/template/docs/platform/document-ai-runtime.md +204 -0
  258. package/template/docs/platform/governed-export-renderers.md +132 -0
  259. package/template/docs/platform/navigation-registry.md +5 -0
  260. package/template/docs/platform/organization-membership.md +2 -1
  261. package/template/docs/platform/speech-ai-runtime.md +156 -0
  262. package/template/docs/platform/system-crud-api.md +7 -1
  263. package/template/docs/platform/vision-ai-runtime.md +97 -0
  264. package/template/docs/platform/web-system-crud.md +6 -0
  265. package/template/package.json +16 -2
  266. package/template/packages/ai-client/index.ts +4 -0
  267. package/template/packages/ai-client/package.json +5 -2
  268. package/template/packages/ai-client/src/agent/client/artifact.ts +26 -0
  269. package/template/packages/ai-client/src/agent/client/attach.test.ts +124 -0
  270. package/template/packages/ai-client/src/agent/client/client.ts +119 -7
  271. package/template/packages/ai-client/src/agent/client/run.ts +14 -0
  272. package/template/packages/ai-client/src/agent/client/types.ts +18 -0
  273. package/template/packages/ai-client/src/agent/client/vision.test.ts +53 -0
  274. package/template/packages/ai-client/src/agent/contracts/contracts.test.ts +1 -0
  275. package/template/packages/ai-client/src/agent/contracts/events.ts +33 -0
  276. package/template/packages/ai-client/src/agent/contracts/runs.ts +71 -0
  277. package/template/packages/ai-client/src/agent/index.ts +1 -0
  278. package/template/packages/ai-client/src/artifact-ref.ts +8 -0
  279. package/template/packages/ai-client/src/document/client.test.ts +114 -0
  280. package/template/packages/ai-client/src/document/client.ts +134 -0
  281. package/template/packages/ai-client/src/document/contracts.ts +152 -0
  282. package/template/packages/ai-client/src/document/error.ts +43 -0
  283. package/template/packages/ai-client/src/document/index.ts +3 -0
  284. package/template/packages/ai-client/src/document/run.ts +104 -0
  285. package/template/packages/ai-client/src/document/validation.ts +115 -0
  286. package/template/packages/ai-client/src/embedding/client.test.ts +77 -0
  287. package/template/packages/ai-client/src/embedding/client.ts +96 -0
  288. package/template/packages/ai-client/src/embedding/contracts.ts +46 -0
  289. package/template/packages/ai-client/src/embedding/error.ts +61 -0
  290. package/template/packages/ai-client/src/embedding/index.ts +3 -0
  291. package/template/packages/ai-client/src/embedding/validation.ts +117 -0
  292. package/template/packages/ai-client/src/speech/client.test.ts +135 -0
  293. package/template/packages/ai-client/src/speech/client.ts +104 -0
  294. package/template/packages/ai-client/src/speech/contracts.ts +129 -0
  295. package/template/packages/ai-client/src/speech/error.ts +43 -0
  296. package/template/packages/ai-client/src/speech/index.ts +3 -0
  297. package/template/packages/ai-client/src/speech/run.ts +133 -0
  298. package/template/packages/ai-client/src/speech/validation.ts +132 -0
  299. package/template/packages/config/package.json +2 -2
  300. package/template/packages/database/drizzle/0039_early_ezekiel_stane.sql +1 -0
  301. package/template/packages/database/drizzle/0040_colossal_baron_strucker.sql +63 -0
  302. package/template/packages/database/drizzle/0041_yummy_prowler.sql +21 -0
  303. package/template/packages/database/drizzle/0042_puzzling_rumiko_fujikawa.sql +27 -0
  304. package/template/packages/database/drizzle/meta/0039_snapshot.json +7755 -0
  305. package/template/packages/database/drizzle/meta/0040_snapshot.json +8150 -0
  306. package/template/packages/database/drizzle/meta/0041_snapshot.json +8323 -0
  307. package/template/packages/database/drizzle/meta/0042_snapshot.json +8541 -0
  308. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  309. package/template/packages/database/package.json +2 -2
  310. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  311. package/template/packages/database/src/schema/agent-runtime.ts +149 -0
  312. package/template/packages/database/src/schema/common.ts +2 -14
  313. package/template/packages/database/src/schema/durable-jobs.ts +5 -0
  314. package/template/packages/database/src/schema/governed-exports.ts +17 -5
  315. package/template/packages/database/src/schema/index.ts +3 -0
  316. package/template/packages/database/src/schema-durable-recovery.test.ts +31 -0
  317. package/template/packages/database/src/schema-governed-exports.test.ts +21 -1
  318. package/template/packages/database/src/schema.test.ts +6 -23
  319. package/template/packages/jobs/package.json +3 -2
  320. package/template/packages/jobs/src/handlers.test.ts +13 -0
  321. package/template/packages/jobs/src/handlers.ts +7 -1
  322. package/template/packages/jobs/src/types.ts +4 -0
  323. package/template/packages/modules/package.json +2 -2
  324. package/template/packages/modules/src/ai-operation-composition.test-support.ts +165 -0
  325. package/template/packages/modules/src/ai-operation-composition.test.ts +91 -127
  326. package/template/packages/modules/src/ai-operation-composition.ts +59 -1
  327. package/template/packages/modules/src/manifest.ts +25 -1
  328. package/template/packages/modules/src/tool-composition.ts +18 -0
  329. package/template/packages/server-sdk/index.ts +50 -0
  330. package/template/packages/server-sdk/package.json +10 -4
  331. package/template/packages/server-sdk/src/ai-artifact-port.ts +34 -0
  332. package/template/packages/server-sdk/src/ai-operation-port.ts +65 -0
  333. package/template/packages/server-sdk/src/ai-operation-registration.ts +42 -1
  334. package/template/packages/server-sdk/src/background-ai-port.ts +43 -0
  335. package/template/packages/server-sdk/src/data-governance-registration.ts +116 -0
  336. package/template/packages/server-sdk/src/durable-handler.ts +29 -0
  337. package/template/packages/server-sdk/src/jobs-port.ts +69 -0
  338. package/template/packages/server-sdk/src/module-host.ts +16 -2
  339. package/template/packages/server-sdk/src/request-context.ts +6 -2
  340. package/template/packages/server-sdk/src/sandbox-tool.ts +21 -0
  341. package/template/packages/server-sdk/src/tool-registration.ts +2 -0
  342. package/template/packages/shared/package.json +1 -1
  343. package/template/packages/shared/src/rbac.ts +30 -0
@@ -0,0 +1,337 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import hashlib
5
+ import json
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+
11
+ from .config import DocumentRuntimeConfig
12
+ from .contracts import ArtifactInput, JsonValue, StartDocumentRunRequest
13
+
14
+
15
+ class SafeDocumentError(Exception):
16
+ def __init__(self, code: str, message: str, retryable: bool) -> None:
17
+ super().__init__(message)
18
+ self.code = code
19
+ self.safe_message = message
20
+ self.retryable = retryable
21
+
22
+
23
+ class DocumentAdapter:
24
+ def __init__(self, config: DocumentRuntimeConfig) -> None:
25
+ self.config = config
26
+
27
+ @property
28
+ def available(self) -> bool:
29
+ return self.config.adapter_url is not None
30
+
31
+ async def execute(self, request: StartDocumentRunRequest) -> JsonValue:
32
+ if not self.config.adapter_url:
33
+ raise SafeDocumentError(
34
+ "CAPABILITY_UNAVAILABLE",
35
+ "Document Adapter 未配置",
36
+ True,
37
+ )
38
+ missing = set(request.required_capabilities) - set(self.config.capabilities)
39
+ if missing:
40
+ raise SafeDocumentError(
41
+ "CAPABILITY_UNAVAILABLE",
42
+ "Document Adapter 缺少所需能力",
43
+ True,
44
+ )
45
+ self._authorize_deployment(request)
46
+ if request.limits.max_units > self.config.max_units:
47
+ raise SafeDocumentError(
48
+ "BUDGET_EXCEEDED",
49
+ "Document 页数上限超过 Runtime 配置",
50
+ False,
51
+ )
52
+ paths: list[Path] = []
53
+ try:
54
+ for source in request.sources:
55
+ paths.append(await self._download(request, source))
56
+ return await self._invoke(request, paths)
57
+ finally:
58
+ for path in paths:
59
+ await remove(path)
60
+
61
+ async def _download(
62
+ self, request: StartDocumentRunRequest, source: ArtifactInput
63
+ ) -> Path:
64
+ digest = hashlib.sha256()
65
+ total = 0
66
+ with tempfile.NamedTemporaryFile(delete=False) as target:
67
+ path = Path(target.name)
68
+ try:
69
+ async with httpx.AsyncClient(
70
+ timeout=httpx.Timeout(
71
+ self.config.request_timeout_seconds,
72
+ connect=self.config.connect_timeout_seconds,
73
+ )
74
+ ) as client:
75
+ async with client.stream(
76
+ "GET",
77
+ self.config.artifact_url,
78
+ headers={
79
+ "authorization": f"Bearer {self.config.internal_token}",
80
+ "x-ai-artifact-ref": source.value,
81
+ },
82
+ ) as response:
83
+ response.raise_for_status()
84
+ async for chunk in response.aiter_bytes():
85
+ total += len(chunk)
86
+ if total > min(
87
+ source.byte_size,
88
+ request.limits.max_input_bytes,
89
+ ):
90
+ raise SafeDocumentError(
91
+ "BUDGET_EXCEEDED",
92
+ "文档输入超过声明上限",
93
+ False,
94
+ )
95
+ digest.update(chunk)
96
+ target.write(chunk)
97
+ except SafeDocumentError:
98
+ await remove(path)
99
+ raise
100
+ except Exception as error:
101
+ await remove(path)
102
+ raise SafeDocumentError(
103
+ "ARTIFACT_UNAVAILABLE",
104
+ "Document Runtime 无法读取 Artifact",
105
+ True,
106
+ ) from error
107
+ if total != source.byte_size or digest.hexdigest() != source.sha256:
108
+ await remove(path)
109
+ raise SafeDocumentError(
110
+ "ARTIFACT_UNAVAILABLE",
111
+ "Artifact 完整性校验失败",
112
+ False,
113
+ )
114
+ return path
115
+
116
+ async def _invoke(
117
+ self, request: StartDocumentRunRequest, paths: list[Path]
118
+ ) -> JsonValue:
119
+ adapter_url = self.config.adapter_url
120
+ if not adapter_url:
121
+ raise SafeDocumentError(
122
+ "CAPABILITY_UNAVAILABLE", "Document Adapter 未配置", True
123
+ )
124
+ headers = (
125
+ {"authorization": f"Bearer {self.config.adapter_token}"}
126
+ if self.config.adapter_token
127
+ else {}
128
+ )
129
+ try:
130
+ async with httpx.AsyncClient(
131
+ timeout=httpx.Timeout(
132
+ self.config.request_timeout_seconds,
133
+ connect=self.config.connect_timeout_seconds,
134
+ )
135
+ ) as client:
136
+ opened = [path.open("rb") for path in paths]
137
+ try:
138
+ response = await client.post(
139
+ f"{adapter_url.rstrip('/')}/v1/document/process",
140
+ headers=headers,
141
+ files=[
142
+ (
143
+ "file" if len(opened) == 1 else "files",
144
+ (
145
+ f"source-{index + 1}",
146
+ source,
147
+ request.sources[index].media_type,
148
+ ),
149
+ )
150
+ for index, source in enumerate(opened)
151
+ ],
152
+ data={
153
+ "operationKey": request.operation_key,
154
+ "dataClassification": request.data_classification,
155
+ "options": json.dumps(
156
+ request.options,
157
+ ensure_ascii=False,
158
+ separators=(",", ":"),
159
+ ),
160
+ "providerEgress": request.provider_egress.model_dump_json(
161
+ by_alias=True
162
+ ),
163
+ "providerDataPolicy": (
164
+ request.provider_data_policy.model_dump_json(
165
+ by_alias=True
166
+ )
167
+ if request.provider_data_policy
168
+ else "null"
169
+ ),
170
+ "limits": request.limits.model_dump_json(by_alias=True),
171
+ },
172
+ )
173
+ finally:
174
+ for source in opened:
175
+ source.close()
176
+ response.raise_for_status()
177
+ if len(response.content) > request.limits.max_output_bytes:
178
+ raise SafeDocumentError(
179
+ "BUDGET_EXCEEDED",
180
+ "Document 输出超过上限",
181
+ False,
182
+ )
183
+ value: JsonValue = response.json()
184
+ validate_output(value, request.limits.max_units)
185
+ return value
186
+ except SafeDocumentError:
187
+ raise
188
+ except Exception as error:
189
+ raise SafeDocumentError(
190
+ "RUNTIME_UNAVAILABLE",
191
+ "Document Adapter 执行失败",
192
+ True,
193
+ ) from error
194
+
195
+ def _authorize_deployment(self, request: StartDocumentRunRequest) -> None:
196
+ if request.provider_egress.mode == "forbidden":
197
+ return
198
+ if not request.provider_data_policy:
199
+ raise SafeDocumentError(
200
+ "FORBIDDEN",
201
+ "Document Operation 缺少 Provider 字段治理策略",
202
+ False,
203
+ )
204
+ if (
205
+ not self.config.deployment_key
206
+ or request.provider_egress.deployment_key != self.config.deployment_key
207
+ ):
208
+ raise SafeDocumentError(
209
+ "FORBIDDEN",
210
+ "Document Operation 未授权当前 Deployment",
211
+ False,
212
+ )
213
+
214
+
215
+ def validate_output(value: JsonValue, max_units: int) -> None:
216
+ if not isinstance(value, dict):
217
+ raise SafeDocumentError(
218
+ "OUTPUT_VALIDATION_FAILED", "Document 输出结构无效", False
219
+ )
220
+ pages = value.get("pages")
221
+ if not isinstance(pages, list):
222
+ raise SafeDocumentError(
223
+ "OUTPUT_VALIDATION_FAILED", "Document 输出缺少页级结果", False
224
+ )
225
+ if len(pages) > max_units:
226
+ raise invalid_output("Document 输出页数超过上限")
227
+ page_numbers: set[int] = set()
228
+ for page in pages:
229
+ if not isinstance(page, dict):
230
+ raise invalid_output("Document 页级结果无效")
231
+ page_number = page.get("pageNumber")
232
+ if (
233
+ not isinstance(page_number, int)
234
+ or isinstance(page_number, bool)
235
+ or page_number < 1
236
+ or page_number > max_units
237
+ or page_number in page_numbers
238
+ or not isinstance(page.get("text"), str)
239
+ or not isinstance(page.get("blocks"), list)
240
+ ):
241
+ raise invalid_output("Document 页码、文本或 Block 无效")
242
+ page_numbers.add(page_number)
243
+ blocks = page.get("blocks")
244
+ if not isinstance(blocks, list):
245
+ raise invalid_output("Document Block 无效")
246
+ for block in blocks:
247
+ validate_block(block)
248
+ layout = page.get("layout")
249
+ if layout is not None:
250
+ if not isinstance(layout, list):
251
+ raise invalid_output("Document 版面树无效")
252
+ for node in layout:
253
+ validate_layout(node, 0)
254
+ tables = value.get("tables", [])
255
+ if not isinstance(tables, list):
256
+ raise invalid_output("Document 表格结果无效")
257
+ for table in tables:
258
+ rows = table.get("rows") if isinstance(table, dict) else None
259
+ if (
260
+ not isinstance(table, dict)
261
+ or table.get("pageNumber") not in page_numbers
262
+ or not isinstance(rows, list)
263
+ or any(
264
+ not isinstance(row, list)
265
+ or any(not isinstance(cell, str) for cell in row)
266
+ for row in rows
267
+ )
268
+ ):
269
+ raise invalid_output("Document 表格结果无效")
270
+ validate_bounds(table.get("bounds"))
271
+ if value.get("artifacts", []) != []:
272
+ raise invalid_output("Document Adapter 不得直接签发 Artifact")
273
+ metadata = value.get("metadata", {})
274
+ if not isinstance(metadata, dict) or any(
275
+ not isinstance(item, str | int | float | bool) for item in metadata.values()
276
+ ):
277
+ raise invalid_output("Document 元数据无效")
278
+
279
+
280
+ def validate_block(value: object) -> None:
281
+ if (
282
+ not isinstance(value, dict)
283
+ or not isinstance(value.get("id"), str)
284
+ or not isinstance(value.get("text"), str)
285
+ ):
286
+ raise invalid_output("Document 文本 Block 无效")
287
+ confidence = value.get("confidence")
288
+ if confidence is not None and (
289
+ not isinstance(confidence, int | float)
290
+ or isinstance(confidence, bool)
291
+ or confidence < 0
292
+ or confidence > 1
293
+ ):
294
+ raise invalid_output("Document Block 置信度无效")
295
+ validate_bounds(value.get("bounds"))
296
+
297
+
298
+ def validate_layout(value: object, depth: int) -> None:
299
+ if depth > 32 or not isinstance(value, dict):
300
+ raise invalid_output("Document 版面树无效")
301
+ if not isinstance(value.get("id"), str) or not isinstance(value.get("type"), str):
302
+ raise invalid_output("Document 版面节点无效")
303
+ text = value.get("text")
304
+ if text is not None and not isinstance(text, str):
305
+ raise invalid_output("Document 版面节点文本无效")
306
+ validate_bounds(value.get("bounds"))
307
+ children = value.get("children", [])
308
+ if not isinstance(children, list):
309
+ raise invalid_output("Document 版面子节点无效")
310
+ for child in children:
311
+ validate_layout(child, depth + 1)
312
+
313
+
314
+ def validate_bounds(value: object) -> None:
315
+ if value is None:
316
+ return
317
+ if not isinstance(value, dict) or value.get("unit") not in {
318
+ "pixel",
319
+ "normalized",
320
+ }:
321
+ raise invalid_output("Document 坐标无效")
322
+ for field in ("x", "y", "width", "height"):
323
+ coordinate = value.get(field)
324
+ if (
325
+ not isinstance(coordinate, int | float)
326
+ or isinstance(coordinate, bool)
327
+ or coordinate < 0
328
+ ):
329
+ raise invalid_output("Document 坐标无效")
330
+
331
+
332
+ def invalid_output(message: str) -> SafeDocumentError:
333
+ return SafeDocumentError("OUTPUT_VALIDATION_FAILED", message, False)
334
+
335
+
336
+ async def remove(path: Path) -> None:
337
+ await asyncio.to_thread(path.unlink, missing_ok=True)
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import hmac
5
+ import os
6
+ import uuid
7
+ from collections.abc import AsyncIterator
8
+
9
+ from fastapi import Depends, FastAPI, Header, HTTPException, Query
10
+ from fastapi.exceptions import RequestValidationError
11
+ from fastapi.responses import JSONResponse, StreamingResponse
12
+
13
+ from .adapter import DocumentAdapter, SafeDocumentError
14
+ from .config import DocumentRuntimeConfig
15
+ from .contracts import (
16
+ ErrorResponse,
17
+ StartDocumentRunRequest,
18
+ StartRunResponse,
19
+ )
20
+ from .service import DocumentRunService
21
+ from .store import DocumentRunStore, RunConflictError
22
+
23
+
24
+ def create_app(config: DocumentRuntimeConfig) -> FastAPI:
25
+ app = FastAPI(title="Southwind Document Runtime", docs_url=None, redoc_url=None)
26
+ store = DocumentRunStore(config.store_path)
27
+ service = DocumentRunService(DocumentAdapter(config), store)
28
+
29
+ def authorize(authorization: str | None = Header(default=None)) -> None:
30
+ provided = (authorization or "").removeprefix("Bearer ")
31
+ if not provided or not hmac.compare_digest(provided, config.internal_token):
32
+ raise HTTPException(status_code=403, detail="FORBIDDEN")
33
+
34
+ @app.get("/health/ready")
35
+ async def ready(_: None = Depends(authorize)) -> JSONResponse:
36
+ if not service.adapter.available:
37
+ return JSONResponse(
38
+ {"status": "unavailable", "capabilities": []},
39
+ status_code=503,
40
+ )
41
+ return JSONResponse(
42
+ {
43
+ "status": "ready",
44
+ "capabilities": list(config.capabilities),
45
+ "limits": {"maxUnits": config.max_units},
46
+ }
47
+ )
48
+
49
+ @app.post("/api/document/runs", status_code=202)
50
+ async def start(
51
+ request: StartDocumentRunRequest,
52
+ idempotency_key: str = Header(alias="idempotency-key"),
53
+ _: None = Depends(authorize),
54
+ ) -> StartRunResponse:
55
+ if not idempotency_key.strip() or len(idempotency_key) > 160:
56
+ raise HTTPException(status_code=400, detail="INVALID_INPUT")
57
+ try:
58
+ record = await service.start(request, idempotency_key)
59
+ except RunConflictError:
60
+ raise HTTPException(status_code=409, detail="RUN_CONFLICT") from None
61
+ return StartRunResponse(
62
+ run_id=record.run_id,
63
+ status=record.status,
64
+ events_url=f"/api/document/runs/{record.run_id}/events",
65
+ )
66
+
67
+ @app.get("/api/document/runs/{run_id}/events")
68
+ async def events(
69
+ run_id: str,
70
+ after: int = Query(default=0, ge=0),
71
+ _: None = Depends(authorize),
72
+ ) -> StreamingResponse:
73
+ service.require(run_id)
74
+
75
+ async def stream() -> AsyncIterator[str]:
76
+ cursor = after
77
+ for _attempt in range(30):
78
+ current = service.require(run_id)
79
+ events = store.events_after(run_id, cursor)
80
+ for event in events:
81
+ sequence = event.get("sequence")
82
+ if not isinstance(sequence, int):
83
+ raise RuntimeError("Document Event sequence 无效")
84
+ cursor = sequence
85
+ yield f"data: {encode_event(event)}\n\n"
86
+ if current.terminal:
87
+ return
88
+ if not events:
89
+ yield ": heartbeat\n\n"
90
+ await asyncio.sleep(1)
91
+
92
+ return StreamingResponse(stream(), media_type="text/event-stream")
93
+
94
+ @app.get("/api/document/runs/{run_id}/result")
95
+ async def result(run_id: str, _: None = Depends(authorize)) -> JSONResponse:
96
+ record = service.require(run_id)
97
+ if record.status != "completed":
98
+ return JSONResponse({"code": "RUN_NOT_READY"}, status_code=409)
99
+ return JSONResponse(
100
+ {"runId": run_id, "status": "completed", "output": record.output}
101
+ )
102
+
103
+ @app.post("/api/document/runs/{run_id}/cancel")
104
+ async def cancel(run_id: str, _: None = Depends(authorize)) -> JSONResponse:
105
+ record = await service.cancel(run_id)
106
+ return JSONResponse({"runId": run_id, "status": record.status})
107
+
108
+ @app.exception_handler(KeyError)
109
+ async def missing(_request: object, _error: KeyError) -> JSONResponse:
110
+ return safe_error("RUN_NOT_FOUND", "Document Run 不存在", 404, False)
111
+
112
+ @app.exception_handler(HTTPException)
113
+ async def http_error(_request: object, error: HTTPException) -> JSONResponse:
114
+ code = str(error.detail)
115
+ return safe_error(code, "Document 请求被拒绝", error.status_code, False)
116
+
117
+ @app.exception_handler(RequestValidationError)
118
+ async def validation_error(
119
+ _request: object, _error: RequestValidationError
120
+ ) -> JSONResponse:
121
+ return safe_error("INVALID_INPUT", "Document 请求格式无效", 400, False)
122
+
123
+ @app.exception_handler(SafeDocumentError)
124
+ async def safe_document_error(
125
+ _request: object, error: SafeDocumentError
126
+ ) -> JSONResponse:
127
+ status = 403 if error.code == "FORBIDDEN" else 400
128
+ return safe_error(error.code, error.safe_message, status, error.retryable)
129
+
130
+ return app
131
+
132
+
133
+ def safe_error(code: str, message: str, status: int, retryable: bool) -> JSONResponse:
134
+ return JSONResponse(
135
+ ErrorResponse(
136
+ code=code,
137
+ message=message,
138
+ request_id=f"document_{uuid.uuid4().hex}",
139
+ retryable=retryable,
140
+ ).model_dump(by_alias=True),
141
+ status_code=status,
142
+ )
143
+
144
+
145
+ def encode_event(event: dict[str, object]) -> str:
146
+ import json
147
+
148
+ return json.dumps(event, ensure_ascii=False, separators=(",", ":"))
149
+
150
+
151
+ app = create_app(DocumentRuntimeConfig.from_environment(dict(os.environ)))
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ import ipaddress
4
+ from dataclasses import dataclass
5
+ from urllib.parse import urlparse
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class DocumentRuntimeConfig:
10
+ internal_token: str
11
+ store_path: str
12
+ artifact_url: str
13
+ adapter_url: str | None
14
+ adapter_token: str | None
15
+ deployment_key: str | None
16
+ capabilities: tuple[str, ...]
17
+ max_units: int = 500
18
+ connect_timeout_seconds: float = 5
19
+ request_timeout_seconds: float = 300
20
+
21
+ @classmethod
22
+ def from_environment(cls, environment: dict[str, str]) -> DocumentRuntimeConfig:
23
+ internal_token = required(environment, "DOCUMENT_SERVER_INTERNAL_TOKEN")
24
+ store_path = environment.get(
25
+ "DOCUMENT_RUN_STORE_PATH", "/app/data/document/runs.sqlite3"
26
+ ).strip()
27
+ artifact_url = environment.get(
28
+ "DOCUMENT_ARTIFACT_URL",
29
+ "http://server:9747/api/internal/ai/artifacts",
30
+ ).strip()
31
+ validate_intranet_url(artifact_url)
32
+ adapter_url = environment.get("DOCUMENT_ADAPTER_URL", "").strip() or None
33
+ if adapter_url:
34
+ validate_intranet_url(adapter_url)
35
+ capabilities = tuple(
36
+ sorted(
37
+ {
38
+ item.strip()
39
+ for item in environment.get(
40
+ "DOCUMENT_ADAPTER_CAPABILITIES",
41
+ "document.ocr,document.layout,document.table",
42
+ ).split(",")
43
+ if item.strip()
44
+ }
45
+ )
46
+ )
47
+ return cls(
48
+ internal_token=internal_token,
49
+ store_path=store_path,
50
+ artifact_url=artifact_url,
51
+ adapter_url=adapter_url,
52
+ adapter_token=environment.get("DOCUMENT_ADAPTER_TOKEN", "").strip() or None,
53
+ deployment_key=environment.get("DOCUMENT_DEPLOYMENT_KEY", "").strip()
54
+ or None,
55
+ capabilities=capabilities,
56
+ max_units=positive_int(environment, "DOCUMENT_MAX_UNITS", 500),
57
+ )
58
+
59
+
60
+ def required(environment: dict[str, str], key: str) -> str:
61
+ value = environment.get(key, "").strip()
62
+ if not value:
63
+ raise ValueError(f"缺少 {key}")
64
+ return value
65
+
66
+
67
+ def positive_int(environment: dict[str, str], key: str, default: int) -> int:
68
+ raw = environment.get(key, str(default)).strip()
69
+ try:
70
+ value = int(raw)
71
+ except ValueError:
72
+ raise ValueError(f"{key} 必须是正整数") from None
73
+ if value <= 0:
74
+ raise ValueError(f"{key} 必须是正整数")
75
+ return value
76
+
77
+
78
+ def validate_intranet_url(raw: str) -> None:
79
+ parsed = urlparse(raw)
80
+ if parsed.scheme not in {"http", "https"} or not parsed.hostname:
81
+ raise ValueError("Document Adapter URL 无效")
82
+ if parsed.username or parsed.password or parsed.query or parsed.fragment:
83
+ raise ValueError("Document Adapter URL 不允许凭据、查询串或片段")
84
+ host = parsed.hostname.lower()
85
+ if host == "localhost" or host.endswith(".internal") or "." not in host:
86
+ return
87
+ try:
88
+ address = ipaddress.ip_address(host)
89
+ except ValueError:
90
+ raise ValueError(
91
+ "Document Adapter 必须使用 localhost、内部服务名、私网 IP 或 .internal"
92
+ ) from None
93
+ if not (address.is_private or address.is_loopback):
94
+ raise ValueError("Document Adapter 禁止使用公网地址")
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Self
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
6
+
7
+ type JsonValue = (
8
+ str | int | float | bool | None | list[JsonValue] | dict[str, JsonValue]
9
+ )
10
+ type RunStatus = Literal["accepted", "running", "completed", "failed", "cancelled"]
11
+
12
+
13
+ def to_camel(value: str) -> str:
14
+ first, *rest = value.split("_")
15
+ return first + "".join(part.capitalize() for part in rest)
16
+
17
+
18
+ class CanonicalModel(BaseModel):
19
+ model_config = ConfigDict(
20
+ alias_generator=to_camel,
21
+ populate_by_name=True,
22
+ extra="forbid",
23
+ )
24
+
25
+
26
+ class ArtifactInput(CanonicalModel):
27
+ value: str = Field(min_length=1, max_length=4096)
28
+ media_type: str = Field(min_length=1, max_length=160)
29
+ byte_size: int = Field(ge=0, le=512 * 1024 * 1024)
30
+ sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
31
+
32
+
33
+ class RuntimeLimits(CanonicalModel):
34
+ max_duration_seconds: int = Field(ge=1, le=3600)
35
+ max_input_bytes: int = Field(ge=1, le=512 * 1024 * 1024)
36
+ max_output_bytes: int = Field(ge=1024, le=64 * 1024 * 1024)
37
+ max_units: int = Field(ge=1, le=10_000)
38
+ max_artifacts: int = Field(ge=1, le=100)
39
+
40
+
41
+ class ProviderEgress(CanonicalModel):
42
+ mode: Literal["forbidden", "configured-endpoint"]
43
+ deployment_key: str | None = None
44
+
45
+ @model_validator(mode="after")
46
+ def validate_deployment(self) -> Self:
47
+ if self.mode == "configured-endpoint":
48
+ if not self.deployment_key or not self.deployment_key.strip():
49
+ raise ValueError("Provider Deployment Key 缺失")
50
+ elif self.deployment_key is not None:
51
+ raise ValueError("禁止出网时不能指定 Provider Deployment")
52
+ return self
53
+
54
+
55
+ class ProviderFieldPolicy(CanonicalModel):
56
+ field: Literal["artifact-content", "provider-output"]
57
+ classification: Literal["public", "internal", "sensitive"]
58
+ dlp_action: Literal["reject", "redact"]
59
+
60
+
61
+ class ProviderInputFieldPolicy(CanonicalModel):
62
+ json_pointer: str = Field(pattern=r"^/[^/]+(?:/[^/]+)*$")
63
+ classification: Literal["public", "internal", "sensitive"]
64
+ dlp_action: Literal["reject", "redact"]
65
+
66
+
67
+ class ProviderDataPolicy(CanonicalModel):
68
+ input_fields: list[ProviderInputFieldPolicy] = Field(min_length=1)
69
+ envelope_fields: list[ProviderFieldPolicy] = Field(min_length=2)
70
+
71
+ @model_validator(mode="after")
72
+ def validate_fields(self) -> Self:
73
+ fields = [rule.field for rule in self.envelope_fields]
74
+ if set(fields) != {"artifact-content", "provider-output"}:
75
+ raise ValueError("Document Provider 字段治理策略不完整")
76
+ if len(fields) != len(set(fields)):
77
+ raise ValueError("Document Provider 字段治理策略重复")
78
+ return self
79
+
80
+
81
+ class StartDocumentRunRequest(CanonicalModel):
82
+ kind: Literal["document"]
83
+ operation_key: str = Field(min_length=1, max_length=160)
84
+ options: JsonValue
85
+ sources: list[ArtifactInput] = Field(min_length=1, max_length=100)
86
+ required_capabilities: list[str] = Field(min_length=1, max_length=32)
87
+ data_classification: Literal["public", "internal", "sensitive"]
88
+ limits: RuntimeLimits
89
+ provider_egress: ProviderEgress
90
+ provider_data_policy: ProviderDataPolicy | None = None
91
+
92
+
93
+ class StartRunResponse(CanonicalModel):
94
+ run_id: str
95
+ status: RunStatus
96
+ events_url: str
97
+
98
+
99
+ class ErrorResponse(CanonicalModel):
100
+ code: str
101
+ message: str
102
+ request_id: str
103
+ retryable: bool