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.
- package/README.md +27 -9
- package/dist/cli.js +825 -117
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +16 -3
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +4 -0
- package/template/README.md +18 -11
- package/template/_gitignore +1 -0
- package/template/apps/agent-server/README.md +2 -0
- package/template/apps/agent-server/pyproject.toml +8 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +87 -10
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +18 -3
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +78 -11
- package/template/apps/agent-server/src/southwind_agent_server/app.py +120 -115
- package/template/apps/agent-server/src/southwind_agent_server/artifact_input.py +153 -0
- package/template/apps/agent-server/src/southwind_agent_server/config.py +29 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +81 -1
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +8 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +28 -0
- package/template/apps/agent-server/src/southwind_agent_server/fingerprint.py +41 -0
- package/template/apps/agent-server/src/southwind_agent_server/http_helpers.py +62 -0
- package/template/apps/agent-server/src/southwind_agent_server/lease.py +15 -0
- package/template/apps/agent-server/src/southwind_agent_server/multimodal.py +76 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +112 -20
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +14 -1
- package/template/apps/agent-server/src/southwind_agent_server/postgres_store.py +329 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_store_backend.py +332 -0
- package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +64 -3
- package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +68 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +218 -56
- package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +36 -0
- package/template/apps/agent-server/src/southwind_agent_server/sqlite_catalog.py +107 -0
- package/template/apps/agent-server/src/southwind_agent_server/sqlite_schema.py +65 -0
- package/template/apps/agent-server/src/southwind_agent_server/sqlite_serialization.py +16 -0
- package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +89 -57
- package/template/apps/agent-server/src/southwind_agent_server/store.py +105 -2
- package/template/apps/agent-server/src/southwind_agent_server/store_cipher.py +40 -0
- package/template/apps/agent-server/src/southwind_agent_server/telemetry.py +89 -0
- package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +72 -0
- package/template/apps/agent-server/tests/test_adk_adapter.py +6 -2
- package/template/apps/agent-server/tests/test_api.py +24 -1
- package/template/apps/agent-server/tests/test_artifact_input.py +110 -0
- package/template/apps/agent-server/tests/test_config.py +4 -0
- package/template/apps/agent-server/tests/test_fingerprint.py +10 -0
- package/template/apps/agent-server/tests/test_multimodal.py +51 -0
- package/template/apps/agent-server/tests/test_openai_provider.py +6 -2
- package/template/apps/agent-server/tests/test_postgres_store.py +131 -0
- package/template/apps/agent-server/tests/test_provider_governance.py +61 -0
- package/template/apps/agent-server/tests/test_sqlite_store.py +95 -10
- package/template/apps/agent-server/tests/test_telemetry.py +25 -0
- package/template/apps/agent-server/tests/test_tool_approval.py +28 -0
- package/template/apps/agent-server/uv.lock +165 -0
- package/template/apps/document-server/Dockerfile +19 -0
- package/template/apps/document-server/pyproject.toml +43 -0
- package/template/apps/document-server/src/southwind_document_server/__init__.py +1 -0
- package/template/apps/document-server/src/southwind_document_server/adapter.py +337 -0
- package/template/apps/document-server/src/southwind_document_server/app.py +151 -0
- package/template/apps/document-server/src/southwind_document_server/config.py +94 -0
- package/template/apps/document-server/src/southwind_document_server/contracts.py +103 -0
- package/template/apps/document-server/src/southwind_document_server/service.py +149 -0
- package/template/apps/document-server/src/southwind_document_server/store.py +185 -0
- package/template/apps/document-server/tests/__init__.py +1 -0
- package/template/apps/document-server/tests/test_config.py +42 -0
- package/template/apps/document-server/tests/test_service.py +140 -0
- package/template/apps/document-server/uv.lock +430 -0
- package/template/apps/embedding-server/Dockerfile +17 -0
- package/template/apps/embedding-server/pyproject.toml +44 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/__init__.py +1 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/adapter.py +254 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/app.py +119 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/config.py +120 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/contracts.py +106 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/error.py +6 -0
- package/template/apps/embedding-server/src/southwind_embedding_server/image_input.py +87 -0
- package/template/apps/embedding-server/tests/__init__.py +1 -0
- package/template/apps/embedding-server/tests/test_adapter.py +92 -0
- package/template/apps/embedding-server/tests/test_config.py +38 -0
- package/template/apps/embedding-server/uv.lock +449 -0
- package/template/apps/server/src/agent/background-operation-port.ts +37 -0
- package/template/apps/server/src/agent/background-operation.test.ts +231 -0
- package/template/apps/server/src/agent/background-operation.ts +252 -0
- package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +114 -0
- package/template/apps/server/src/agent/execution-grants.ts +7 -1
- package/template/apps/server/src/agent/fake-runtime.test-support.ts +43 -0
- package/template/apps/server/src/agent/idempotency-key.test.ts +28 -0
- package/template/apps/server/src/agent/idempotency-key.ts +11 -0
- package/template/apps/server/src/agent/remote-runtime.test.ts +115 -1
- package/template/apps/server/src/agent/remote-runtime.ts +173 -5
- package/template/apps/server/src/agent/run-authorization.test-support.ts +9 -0
- package/template/apps/server/src/agent/run-authorization.test.ts +126 -0
- package/template/apps/server/src/agent/run-authorization.ts +180 -0
- package/template/apps/server/src/agent/run-binding-repository.ts +93 -0
- package/template/apps/server/src/agent/run-contracts.ts +88 -1
- package/template/apps/server/src/agent/run-definition-support.ts +58 -0
- package/template/apps/server/src/agent/run-errors.ts +25 -0
- package/template/apps/server/src/agent/run-facade.test.ts +57 -46
- package/template/apps/server/src/agent/run-facade.ts +185 -65
- package/template/apps/server/src/agent/run-release.ts +78 -0
- package/template/apps/server/src/agent/run-route-input.ts +169 -0
- package/template/apps/server/src/agent/run-routes.ts +63 -76
- package/template/apps/server/src/agent/runtime-bootstrap.ts +169 -13
- package/template/apps/server/src/agent/sandbox-client.ts +200 -0
- package/template/apps/server/src/agent/sandbox-protocol.ts +76 -0
- package/template/apps/server/src/agent/sandbox-registry.ts +205 -0
- package/template/apps/server/src/agent/sandbox-runtime.test.ts +137 -0
- package/template/apps/server/src/agent/sandbox-worker-server.ts +195 -0
- package/template/apps/server/src/agent/sandbox-worker.ts +12 -0
- package/template/apps/server/src/agent/tool-approval.test.ts +75 -0
- package/template/apps/server/src/agent/tool-approval.ts +115 -0
- package/template/apps/server/src/agent/tool-audit.ts +62 -0
- package/template/apps/server/src/agent/tool-host.test.ts +39 -11
- package/template/apps/server/src/agent/tool-host.ts +184 -17
- package/template/apps/server/src/agent/tool-registry.ts +52 -47
- package/template/apps/server/src/agent/vision-facade.test.ts +256 -0
- package/template/apps/server/src/agent/vision-input.ts +120 -0
- package/template/apps/server/src/ai-artifact/codec.test.ts +44 -0
- package/template/apps/server/src/ai-artifact/codec.ts +137 -0
- package/template/apps/server/src/ai-artifact/routes.ts +37 -0
- package/template/apps/server/src/ai-artifact/runtime.ts +205 -0
- package/template/apps/server/src/application-services.ts +62 -2
- package/template/apps/server/src/auth/reauthentication.test.ts +40 -0
- package/template/apps/server/src/auth/reauthentication.ts +19 -0
- package/template/apps/server/src/auth/runtime.ts +9 -1
- package/template/apps/server/src/data-access/context.ts +6 -4
- package/template/apps/server/src/data-access/scope-snapshot.ts +27 -0
- package/template/apps/server/src/data-governance/export/approval-port.ts +6 -3
- package/template/apps/server/src/data-governance/export/authorization-port.test.ts +23 -4
- package/template/apps/server/src/data-governance/export/authorization-port.ts +89 -25
- package/template/apps/server/src/data-governance/export/drizzle-repository.integration.test.ts +2 -0
- package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -0
- package/template/apps/server/src/data-governance/export/job-runtime.test.ts +15 -4
- package/template/apps/server/src/data-governance/export/job-runtime.ts +68 -15
- package/template/apps/server/src/data-governance/export/ndjson-stream.ts +25 -6
- package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +4 -2
- package/template/apps/server/src/data-governance/export/renderer-job-runtime.test.ts +235 -0
- package/template/apps/server/src/data-governance/export/renderer-runtime.ts +293 -0
- package/template/apps/server/src/data-governance/export/renderer-selection.test.ts +67 -0
- package/template/apps/server/src/data-governance/export/repository.test.ts +2 -0
- package/template/apps/server/src/data-governance/export/request-fingerprint.test.ts +13 -0
- package/template/apps/server/src/data-governance/export/request-fingerprint.ts +2 -0
- package/template/apps/server/src/data-governance/export/routes.ts +11 -1
- package/template/apps/server/src/data-governance/export/scope-snapshot.ts +6 -18
- package/template/apps/server/src/data-governance/export/service-support.ts +188 -0
- package/template/apps/server/src/data-governance/export/service.ts +90 -113
- package/template/apps/server/src/data-governance/export/source.ts +101 -0
- package/template/apps/server/src/data-governance/export/types.ts +27 -0
- package/template/apps/server/src/document/contracts.ts +76 -0
- package/template/apps/server/src/document/facade.ts +339 -0
- package/template/apps/server/src/document/remote-runtime.test.ts +96 -0
- package/template/apps/server/src/document/remote-runtime.ts +223 -0
- package/template/apps/server/src/document/routes.ts +193 -0
- package/template/apps/server/src/embedding/contracts.ts +83 -0
- package/template/apps/server/src/embedding/error.ts +10 -0
- package/template/apps/server/src/embedding/facade.ts +217 -0
- package/template/apps/server/src/embedding/input.ts +172 -0
- package/template/apps/server/src/embedding/remote-runtime.test.ts +66 -0
- package/template/apps/server/src/embedding/remote-runtime.ts +218 -0
- package/template/apps/server/src/embedding/routes.ts +152 -0
- package/template/apps/server/src/index.ts +40 -43
- package/template/apps/server/src/jobs/artifact-service.test.ts +14 -2
- package/template/apps/server/src/jobs/artifact-service.ts +10 -0
- package/template/apps/server/src/jobs/drizzle-execution-repository.ts +17 -18
- package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +19 -1
- package/template/apps/server/src/jobs/drizzle-mappers.ts +4 -1
- package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +49 -2
- package/template/apps/server/src/jobs/drizzle-repository.ts +1 -0
- package/template/apps/server/src/jobs/execution-repository.ts +17 -0
- package/template/apps/server/src/jobs/execution.test.ts +34 -0
- package/template/apps/server/src/jobs/in-memory-control.ts +135 -0
- package/template/apps/server/src/jobs/in-memory-execution.ts +34 -65
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +5 -2
- package/template/apps/server/src/jobs/repository.ts +3 -0
- package/template/apps/server/src/jobs/worker.ts +80 -41
- package/template/apps/server/src/module-host/ai-operation-port.ts +170 -0
- package/template/apps/server/src/module-host/durable-context.test.ts +312 -0
- package/template/apps/server/src/module-host/durable-context.ts +124 -0
- package/template/apps/server/src/module-host/durable-handler-registration.ts +72 -0
- package/template/apps/server/src/module-host/governed-export-route-support.ts +171 -0
- package/template/apps/server/src/module-host/governed-export-routes.test.ts +327 -0
- package/template/apps/server/src/module-host/governed-export-routes.ts +238 -0
- package/template/apps/server/src/module-host/governed-renderer-registration.test.ts +247 -0
- package/template/apps/server/src/module-host/governed-resource-routes.ts +331 -0
- package/template/apps/server/src/module-host/governed-resources.test.ts +344 -0
- package/template/apps/server/src/module-host/governed-resources.ts +203 -0
- package/template/apps/server/src/module-host/http-runtime.ts +95 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
- package/template/apps/server/src/module-host/initialize.test.ts +49 -1
- package/template/apps/server/src/module-host/initialize.ts +39 -16
- package/template/apps/server/src/module-host/job-port.test.ts +262 -0
- package/template/apps/server/src/module-host/job-port.ts +334 -0
- package/template/apps/server/src/module-host/request-context.test.ts +88 -2
- package/template/apps/server/src/module-host/request-context.ts +56 -22
- package/template/apps/server/src/module-host/route-installer.test.ts +10 -0
- package/template/apps/server/src/module-host/route-installer.ts +36 -1
- package/template/apps/server/src/module-host/sandbox-tool-registration.ts +42 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +67 -31
- package/template/apps/server/src/module-host.ts +1 -0
- package/template/apps/server/src/module-runtime-validation.ts +7 -3
- package/template/apps/server/src/speech/contracts.ts +76 -0
- package/template/apps/server/src/speech/facade.ts +316 -0
- package/template/apps/server/src/speech/remote-runtime.test.ts +88 -0
- package/template/apps/server/src/speech/remote-runtime.ts +227 -0
- package/template/apps/server/src/speech/routes.ts +180 -0
- package/template/apps/server/src/system/actor.ts +39 -0
- package/template/apps/server/src/system/route-helpers.ts +1 -0
- package/template/apps/server/src/workflow/service.ts +2 -1
- package/template/apps/speech-server/Dockerfile +22 -0
- package/template/apps/speech-server/pyproject.toml +43 -0
- package/template/apps/speech-server/src/southwind_speech_server/__init__.py +1 -0
- package/template/apps/speech-server/src/southwind_speech_server/adapter.py +249 -0
- package/template/apps/speech-server/src/southwind_speech_server/app.py +183 -0
- package/template/apps/speech-server/src/southwind_speech_server/config.py +100 -0
- package/template/apps/speech-server/src/southwind_speech_server/contracts.py +103 -0
- package/template/apps/speech-server/src/southwind_speech_server/errors.py +6 -0
- package/template/apps/speech-server/src/southwind_speech_server/normalizer.py +108 -0
- package/template/apps/speech-server/src/southwind_speech_server/output_validation.py +128 -0
- package/template/apps/speech-server/src/southwind_speech_server/service.py +162 -0
- package/template/apps/speech-server/src/southwind_speech_server/store.py +209 -0
- package/template/apps/speech-server/tests/__init__.py +1 -0
- package/template/apps/speech-server/tests/test_config.py +33 -0
- package/template/apps/speech-server/tests/test_service.py +154 -0
- package/template/apps/speech-server/uv.lock +430 -0
- package/template/apps/web/src/app/business-routes.ts +22 -0
- package/template/apps/web/src/components/common/AccessSnapshotBoundary.vue +112 -0
- package/template/apps/web/src/components/common/AccessSnapshotBoundary.webtest.ts +130 -0
- package/template/apps/web/src/components/common/AccessSnapshotNotice.vue +117 -0
- package/template/apps/web/src/components/common/FormDialog.vue +6 -0
- package/template/apps/web/src/components/common/ModalLayout.webtest.ts +13 -0
- package/template/apps/web/src/composables/access-snapshot-change.ts +131 -0
- package/template/apps/web/src/composables/access-snapshot-change.webtest.ts +103 -0
- package/template/apps/web/src/composables/useAccessSnapshot.ts +48 -2
- package/template/apps/web/src/composables/useAccessSnapshot.webtest.ts +54 -0
- package/template/apps/web/src/composables/useAccessSnapshotMonitor.ts +42 -0
- package/template/apps/web/src/layout/AppShell.vue +28 -25
- package/template/apps/web/src/layout/AppShell.webtest.ts +1 -0
- package/template/apps/web/src/router/business-routes.ts +68 -0
- package/template/apps/web/src/router/business-routes.webtest.ts +85 -0
- package/template/apps/web/src/router/index.ts +7 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
- package/template/apps/web/src/services/access-api.ts +5 -1
- package/template/apps/web/src/services/access-api.webtest.ts +35 -0
- package/template/docker-compose.yml +127 -0
- package/template/docs/architecture/ai-egress.md +12 -9
- package/template/docs/architecture/ai-runtime.md +23 -11
- package/template/docs/architecture/durable-jobs.md +25 -3
- package/template/docs/architecture/foundation-infrastructure.md +163 -0
- package/template/docs/architecture/module-host.md +115 -0
- package/template/docs/architecture/server-module-interface.md +39 -0
- package/template/docs/architecture/web-access-snapshot-resilience.md +60 -0
- package/template/docs/development/business-web-routes.md +66 -0
- package/template/docs/development/custom-login-page.md +3 -3
- package/template/docs/development/module-lifecycle.md +71 -0
- package/template/docs/operations/observability.md +5 -1
- package/template/docs/platform/agent-runtime.md +21 -11
- package/template/docs/platform/agent-tool-sandbox.md +58 -0
- package/template/docs/platform/agent-tools.md +5 -0
- package/template/docs/platform/document-ai-runtime.md +204 -0
- package/template/docs/platform/governed-export-renderers.md +132 -0
- package/template/docs/platform/navigation-registry.md +5 -0
- package/template/docs/platform/organization-membership.md +2 -1
- package/template/docs/platform/speech-ai-runtime.md +156 -0
- package/template/docs/platform/system-crud-api.md +7 -1
- package/template/docs/platform/vision-ai-runtime.md +97 -0
- package/template/docs/platform/web-system-crud.md +6 -0
- package/template/package.json +16 -2
- package/template/packages/ai-client/index.ts +4 -0
- package/template/packages/ai-client/package.json +5 -2
- package/template/packages/ai-client/src/agent/client/artifact.ts +26 -0
- package/template/packages/ai-client/src/agent/client/attach.test.ts +124 -0
- package/template/packages/ai-client/src/agent/client/client.ts +119 -7
- package/template/packages/ai-client/src/agent/client/run.ts +14 -0
- package/template/packages/ai-client/src/agent/client/types.ts +18 -0
- package/template/packages/ai-client/src/agent/client/vision.test.ts +53 -0
- package/template/packages/ai-client/src/agent/contracts/contracts.test.ts +1 -0
- package/template/packages/ai-client/src/agent/contracts/events.ts +33 -0
- package/template/packages/ai-client/src/agent/contracts/runs.ts +71 -0
- package/template/packages/ai-client/src/agent/index.ts +1 -0
- package/template/packages/ai-client/src/artifact-ref.ts +8 -0
- package/template/packages/ai-client/src/document/client.test.ts +114 -0
- package/template/packages/ai-client/src/document/client.ts +134 -0
- package/template/packages/ai-client/src/document/contracts.ts +152 -0
- package/template/packages/ai-client/src/document/error.ts +43 -0
- package/template/packages/ai-client/src/document/index.ts +3 -0
- package/template/packages/ai-client/src/document/run.ts +104 -0
- package/template/packages/ai-client/src/document/validation.ts +115 -0
- package/template/packages/ai-client/src/embedding/client.test.ts +77 -0
- package/template/packages/ai-client/src/embedding/client.ts +96 -0
- package/template/packages/ai-client/src/embedding/contracts.ts +46 -0
- package/template/packages/ai-client/src/embedding/error.ts +61 -0
- package/template/packages/ai-client/src/embedding/index.ts +3 -0
- package/template/packages/ai-client/src/embedding/validation.ts +117 -0
- package/template/packages/ai-client/src/speech/client.test.ts +135 -0
- package/template/packages/ai-client/src/speech/client.ts +104 -0
- package/template/packages/ai-client/src/speech/contracts.ts +129 -0
- package/template/packages/ai-client/src/speech/error.ts +43 -0
- package/template/packages/ai-client/src/speech/index.ts +3 -0
- package/template/packages/ai-client/src/speech/run.ts +133 -0
- package/template/packages/ai-client/src/speech/validation.ts +132 -0
- package/template/packages/config/package.json +2 -2
- package/template/packages/database/drizzle/0039_early_ezekiel_stane.sql +1 -0
- package/template/packages/database/drizzle/0040_colossal_baron_strucker.sql +63 -0
- package/template/packages/database/drizzle/0041_yummy_prowler.sql +21 -0
- package/template/packages/database/drizzle/0042_puzzling_rumiko_fujikawa.sql +27 -0
- package/template/packages/database/drizzle/meta/0039_snapshot.json +7755 -0
- package/template/packages/database/drizzle/meta/0040_snapshot.json +8150 -0
- package/template/packages/database/drizzle/meta/0041_snapshot.json +8323 -0
- package/template/packages/database/drizzle/meta/0042_snapshot.json +8541 -0
- package/template/packages/database/drizzle/meta/_journal.json +28 -0
- package/template/packages/database/package.json +2 -2
- package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
- package/template/packages/database/src/schema/agent-runtime.ts +149 -0
- package/template/packages/database/src/schema/common.ts +2 -14
- package/template/packages/database/src/schema/durable-jobs.ts +5 -0
- package/template/packages/database/src/schema/governed-exports.ts +17 -5
- package/template/packages/database/src/schema/index.ts +3 -0
- package/template/packages/database/src/schema-durable-recovery.test.ts +31 -0
- package/template/packages/database/src/schema-governed-exports.test.ts +21 -1
- package/template/packages/database/src/schema.test.ts +6 -23
- package/template/packages/jobs/package.json +3 -2
- package/template/packages/jobs/src/handlers.test.ts +13 -0
- package/template/packages/jobs/src/handlers.ts +7 -1
- package/template/packages/jobs/src/types.ts +4 -0
- package/template/packages/modules/package.json +2 -2
- package/template/packages/modules/src/ai-operation-composition.test-support.ts +165 -0
- package/template/packages/modules/src/ai-operation-composition.test.ts +91 -127
- package/template/packages/modules/src/ai-operation-composition.ts +59 -1
- package/template/packages/modules/src/manifest.ts +25 -1
- package/template/packages/modules/src/tool-composition.ts +18 -0
- package/template/packages/server-sdk/index.ts +50 -0
- package/template/packages/server-sdk/package.json +10 -4
- package/template/packages/server-sdk/src/ai-artifact-port.ts +34 -0
- package/template/packages/server-sdk/src/ai-operation-port.ts +65 -0
- package/template/packages/server-sdk/src/ai-operation-registration.ts +42 -1
- package/template/packages/server-sdk/src/background-ai-port.ts +43 -0
- package/template/packages/server-sdk/src/data-governance-registration.ts +116 -0
- package/template/packages/server-sdk/src/durable-handler.ts +29 -0
- package/template/packages/server-sdk/src/jobs-port.ts +69 -0
- package/template/packages/server-sdk/src/module-host.ts +16 -2
- package/template/packages/server-sdk/src/request-context.ts +6 -2
- package/template/packages/server-sdk/src/sandbox-tool.ts +21 -0
- package/template/packages/server-sdk/src/tool-registration.ts +2 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/rbac.ts +30 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sqlite3
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from .contracts import JsonValue
|
|
9
|
+
from .sqlite_serialization import encode
|
|
10
|
+
from .store import IdempotencyConflictError, ReleaseNotFoundError, RunRecord
|
|
11
|
+
from .store_cipher import RunStoreCipher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SqliteCatalogMixin:
|
|
15
|
+
_database: sqlite3.Connection
|
|
16
|
+
_condition: asyncio.Condition
|
|
17
|
+
_cipher: RunStoreCipher
|
|
18
|
+
|
|
19
|
+
async def save_invocation(self, run_id: str, value: JsonValue) -> None:
|
|
20
|
+
async with self._condition:
|
|
21
|
+
updated = self._database.execute(
|
|
22
|
+
"""
|
|
23
|
+
update agent_runs set invocation_cipher = ?, updated_at = ?
|
|
24
|
+
where run_id = ? and status not in ('completed', 'failed', 'cancelled')
|
|
25
|
+
""",
|
|
26
|
+
(self._cipher.seal(value), time.time(), run_id),
|
|
27
|
+
)
|
|
28
|
+
if updated.rowcount != 1:
|
|
29
|
+
raise KeyError(run_id)
|
|
30
|
+
self._database.commit()
|
|
31
|
+
|
|
32
|
+
async def record_evaluation(
|
|
33
|
+
self,
|
|
34
|
+
release_id: str,
|
|
35
|
+
evaluation_set_key: str,
|
|
36
|
+
evaluation_set_version: str,
|
|
37
|
+
metrics: dict[str, float],
|
|
38
|
+
) -> str:
|
|
39
|
+
async with self._condition:
|
|
40
|
+
release = self._database.execute(
|
|
41
|
+
"select release_id from agent_releases where release_id = ?",
|
|
42
|
+
(release_id,),
|
|
43
|
+
).fetchone()
|
|
44
|
+
if not release:
|
|
45
|
+
raise ReleaseNotFoundError
|
|
46
|
+
existing = self._database.execute(
|
|
47
|
+
"""
|
|
48
|
+
select evaluation_id, metrics_json
|
|
49
|
+
from agent_evaluation_results
|
|
50
|
+
where release_id = ? and evaluation_set_key = ?
|
|
51
|
+
and evaluation_set_version = ?
|
|
52
|
+
""",
|
|
53
|
+
(release_id, evaluation_set_key, evaluation_set_version),
|
|
54
|
+
).fetchone()
|
|
55
|
+
encoded = encode(metrics)
|
|
56
|
+
if existing:
|
|
57
|
+
if str(existing["metrics_json"]) != encoded:
|
|
58
|
+
raise IdempotencyConflictError
|
|
59
|
+
return str(existing["evaluation_id"])
|
|
60
|
+
evaluation_id = f"eval_{uuid.uuid4().hex}"
|
|
61
|
+
try:
|
|
62
|
+
self._database.execute(
|
|
63
|
+
"""
|
|
64
|
+
insert into agent_evaluation_results values (?, ?, ?, ?, ?, ?)
|
|
65
|
+
""",
|
|
66
|
+
(
|
|
67
|
+
evaluation_id,
|
|
68
|
+
release_id,
|
|
69
|
+
evaluation_set_key,
|
|
70
|
+
evaluation_set_version,
|
|
71
|
+
encoded,
|
|
72
|
+
time.time(),
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
self._database.commit()
|
|
76
|
+
except sqlite3.IntegrityError:
|
|
77
|
+
self._database.rollback()
|
|
78
|
+
raise IdempotencyConflictError from None
|
|
79
|
+
return evaluation_id
|
|
80
|
+
|
|
81
|
+
def _ensure_release(self, record: RunRecord) -> None:
|
|
82
|
+
if not isinstance(record.release, dict):
|
|
83
|
+
raise IdempotencyConflictError
|
|
84
|
+
release_id = record.release.get("releaseId")
|
|
85
|
+
if not isinstance(release_id, str):
|
|
86
|
+
raise IdempotencyConflictError
|
|
87
|
+
encoded = encode(record.release)
|
|
88
|
+
existing = self._database.execute(
|
|
89
|
+
"""
|
|
90
|
+
select operation_key, snapshot_json from agent_releases
|
|
91
|
+
where release_id = ?
|
|
92
|
+
""",
|
|
93
|
+
(release_id,),
|
|
94
|
+
).fetchone()
|
|
95
|
+
if existing and (
|
|
96
|
+
str(existing["operation_key"]) != record.operation_key
|
|
97
|
+
or str(existing["snapshot_json"]) != encoded
|
|
98
|
+
):
|
|
99
|
+
raise IdempotencyConflictError
|
|
100
|
+
self._database.execute(
|
|
101
|
+
"""
|
|
102
|
+
insert or ignore into agent_releases (
|
|
103
|
+
release_id, operation_key, snapshot_json, created_at
|
|
104
|
+
) values (?, ?, ?, ?)
|
|
105
|
+
""",
|
|
106
|
+
(release_id, record.operation_key, encoded, time.time()),
|
|
107
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def initialize_database(database: sqlite3.Connection) -> None:
|
|
7
|
+
database.execute("pragma foreign_keys = on")
|
|
8
|
+
database.executescript(
|
|
9
|
+
"""
|
|
10
|
+
create table if not exists agent_runs (
|
|
11
|
+
run_id text primary key,
|
|
12
|
+
operation_key text not null,
|
|
13
|
+
input_json text not null,
|
|
14
|
+
idempotency_key text not null unique,
|
|
15
|
+
fingerprint text not null,
|
|
16
|
+
status text not null,
|
|
17
|
+
output_json text not null,
|
|
18
|
+
created_at real not null,
|
|
19
|
+
updated_at real not null,
|
|
20
|
+
invocation_cipher text,
|
|
21
|
+
release_json text not null default 'null',
|
|
22
|
+
trace_id text not null default ''
|
|
23
|
+
);
|
|
24
|
+
create table if not exists agent_events (
|
|
25
|
+
run_id text not null references agent_runs(run_id) on delete cascade,
|
|
26
|
+
sequence integer not null,
|
|
27
|
+
event_json text not null,
|
|
28
|
+
primary key (run_id, sequence)
|
|
29
|
+
);
|
|
30
|
+
"""
|
|
31
|
+
)
|
|
32
|
+
columns = {
|
|
33
|
+
str(row["name"]) for row in database.execute("pragma table_info(agent_runs)")
|
|
34
|
+
}
|
|
35
|
+
if "invocation_cipher" not in columns:
|
|
36
|
+
database.execute("alter table agent_runs add column invocation_cipher text")
|
|
37
|
+
if "release_json" not in columns:
|
|
38
|
+
database.execute(
|
|
39
|
+
"alter table agent_runs add column release_json "
|
|
40
|
+
"text not null default 'null'"
|
|
41
|
+
)
|
|
42
|
+
if "trace_id" not in columns:
|
|
43
|
+
database.execute(
|
|
44
|
+
"alter table agent_runs add column trace_id text not null default ''"
|
|
45
|
+
)
|
|
46
|
+
database.executescript(
|
|
47
|
+
"""
|
|
48
|
+
create table if not exists agent_releases (
|
|
49
|
+
release_id text primary key,
|
|
50
|
+
operation_key text not null,
|
|
51
|
+
snapshot_json text not null,
|
|
52
|
+
created_at real not null
|
|
53
|
+
);
|
|
54
|
+
create table if not exists agent_evaluation_results (
|
|
55
|
+
evaluation_id text primary key,
|
|
56
|
+
release_id text not null references agent_releases(release_id),
|
|
57
|
+
evaluation_set_key text not null,
|
|
58
|
+
evaluation_set_version text not null,
|
|
59
|
+
metrics_json text not null,
|
|
60
|
+
created_at real not null,
|
|
61
|
+
unique(release_id, evaluation_set_key, evaluation_set_version)
|
|
62
|
+
);
|
|
63
|
+
"""
|
|
64
|
+
)
|
|
65
|
+
database.commit()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from .store import CanonicalEvent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def encode(value: object) -> str:
|
|
9
|
+
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def sequence_of(event: CanonicalEvent) -> int:
|
|
13
|
+
value = event.get("sequence")
|
|
14
|
+
if not isinstance(value, int):
|
|
15
|
+
raise RuntimeError("Agent Event sequence 无效")
|
|
16
|
+
return value
|
|
@@ -8,24 +8,29 @@ from pathlib import Path
|
|
|
8
8
|
from typing import cast
|
|
9
9
|
|
|
10
10
|
from .contracts import AgentError, AgentErrorCode, JsonValue, RunStatus
|
|
11
|
+
from .sqlite_catalog import SqliteCatalogMixin
|
|
12
|
+
from .sqlite_schema import initialize_database
|
|
13
|
+
from .sqlite_serialization import encode, sequence_of
|
|
11
14
|
from .store import (
|
|
12
15
|
CanonicalEvent,
|
|
13
16
|
IdempotencyConflictError,
|
|
14
17
|
RunRecord,
|
|
15
18
|
status_for,
|
|
16
19
|
)
|
|
20
|
+
from .store_cipher import RunStoreCipher
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class RunStoreCapacityError(Exception):
|
|
20
24
|
pass
|
|
21
25
|
|
|
22
26
|
|
|
23
|
-
class SqliteRunStore:
|
|
27
|
+
class SqliteRunStore(SqliteCatalogMixin):
|
|
24
28
|
"""Single-replica durable Run store with bounded rows, events and output."""
|
|
25
29
|
|
|
26
30
|
def __init__(
|
|
27
31
|
self,
|
|
28
32
|
path: str,
|
|
33
|
+
cipher: RunStoreCipher,
|
|
29
34
|
*,
|
|
30
35
|
max_runs: int = 10_000,
|
|
31
36
|
max_events_per_run: int = 2_000,
|
|
@@ -40,10 +45,15 @@ class SqliteRunStore:
|
|
|
40
45
|
self._max_output_bytes = max_output_bytes
|
|
41
46
|
self._terminal_ttl = terminal_ttl_seconds
|
|
42
47
|
self._condition = asyncio.Condition()
|
|
43
|
-
self.
|
|
48
|
+
self._cipher = cipher
|
|
49
|
+
initialize_database(self._database)
|
|
44
50
|
self._recover_incomplete()
|
|
45
51
|
|
|
46
|
-
async def create_or_get(
|
|
52
|
+
async def create_or_get(
|
|
53
|
+
self,
|
|
54
|
+
record: RunRecord,
|
|
55
|
+
invocation: JsonValue = None,
|
|
56
|
+
) -> tuple[RunRecord, bool]:
|
|
47
57
|
async with self._condition:
|
|
48
58
|
existing = self._database.execute(
|
|
49
59
|
"select run_id from agent_runs where idempotency_key = ?",
|
|
@@ -61,24 +71,35 @@ class SqliteRunStore:
|
|
|
61
71
|
if count >= self._max_runs:
|
|
62
72
|
raise RunStoreCapacityError("Agent Run Store 容量已满")
|
|
63
73
|
now = time.time()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
try:
|
|
75
|
+
self._database.execute(
|
|
76
|
+
"""
|
|
77
|
+
insert into agent_runs (
|
|
78
|
+
run_id, operation_key, input_json, idempotency_key,
|
|
79
|
+
fingerprint, status, output_json, created_at, updated_at,
|
|
80
|
+
release_json, trace_id, invocation_cipher
|
|
81
|
+
) values (?, ?, ?, ?, ?, 'accepted', 'null', ?, ?, ?, ?, ?)
|
|
82
|
+
""",
|
|
83
|
+
(
|
|
84
|
+
record.run_id,
|
|
85
|
+
record.operation_key,
|
|
86
|
+
"null",
|
|
87
|
+
record.idempotency_key,
|
|
88
|
+
record.fingerprint,
|
|
89
|
+
now,
|
|
90
|
+
now,
|
|
91
|
+
encode(record.release),
|
|
92
|
+
record.trace_id,
|
|
93
|
+
self._cipher.seal(invocation)
|
|
94
|
+
if invocation is not None
|
|
95
|
+
else None,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
self._ensure_release(record)
|
|
99
|
+
self._database.commit()
|
|
100
|
+
except Exception:
|
|
101
|
+
self._database.rollback()
|
|
102
|
+
raise
|
|
82
103
|
self._condition.notify_all()
|
|
83
104
|
return record, True
|
|
84
105
|
|
|
@@ -168,38 +189,58 @@ class SqliteRunStore:
|
|
|
168
189
|
record.terminal,
|
|
169
190
|
)
|
|
170
191
|
|
|
171
|
-
def
|
|
172
|
-
self.
|
|
192
|
+
async def recoverable(self) -> list[tuple[RunRecord, JsonValue]]:
|
|
193
|
+
async with self._condition:
|
|
194
|
+
rows = self._database.execute(
|
|
195
|
+
"""
|
|
196
|
+
select run_id, invocation_cipher from agent_runs
|
|
197
|
+
where status not in ('completed', 'failed', 'cancelled')
|
|
198
|
+
and invocation_cipher is not null
|
|
199
|
+
order by created_at
|
|
200
|
+
"""
|
|
201
|
+
).fetchall()
|
|
202
|
+
result: list[tuple[RunRecord, JsonValue]] = []
|
|
203
|
+
for row in rows:
|
|
204
|
+
record = self._read(str(row["run_id"]))
|
|
205
|
+
if record is not None:
|
|
206
|
+
result.append(
|
|
207
|
+
(
|
|
208
|
+
record,
|
|
209
|
+
self._cipher.open(str(row["invocation_cipher"])),
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
return result
|
|
173
213
|
|
|
174
|
-
def
|
|
175
|
-
self.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
self._database.
|
|
214
|
+
async def clear_invocation(self, run_id: str) -> None:
|
|
215
|
+
async with self._condition:
|
|
216
|
+
self._database.execute(
|
|
217
|
+
"update agent_runs set invocation_cipher = null where run_id = ?",
|
|
218
|
+
(run_id,),
|
|
219
|
+
)
|
|
220
|
+
self._database.commit()
|
|
221
|
+
|
|
222
|
+
async def renew_lease(self, run_id: str) -> None:
|
|
223
|
+
record = await self.get(run_id)
|
|
224
|
+
if record is None or record.terminal:
|
|
225
|
+
raise KeyError(run_id)
|
|
226
|
+
|
|
227
|
+
async def release_lease(self, run_id: str) -> None:
|
|
228
|
+
if await self.get(run_id) is None:
|
|
229
|
+
raise KeyError(run_id)
|
|
230
|
+
|
|
231
|
+
async def probe(self) -> None:
|
|
232
|
+
async with self._condition:
|
|
233
|
+
self._database.execute("select 1").fetchone()
|
|
234
|
+
|
|
235
|
+
async def close(self) -> None:
|
|
236
|
+
self._database.close()
|
|
197
237
|
|
|
198
238
|
def _recover_incomplete(self) -> None:
|
|
199
239
|
rows = self._database.execute(
|
|
200
240
|
"""
|
|
201
241
|
select run_id from agent_runs
|
|
202
242
|
where status not in ('completed', 'failed', 'cancelled')
|
|
243
|
+
and invocation_cipher is null
|
|
203
244
|
"""
|
|
204
245
|
).fetchall()
|
|
205
246
|
for row in rows:
|
|
@@ -248,6 +289,8 @@ class SqliteRunStore:
|
|
|
248
289
|
status=cast(RunStatus, str(row["status"])),
|
|
249
290
|
events=events,
|
|
250
291
|
output=json.loads(str(row["output_json"])),
|
|
292
|
+
release=json.loads(str(row["release_json"])),
|
|
293
|
+
trace_id=str(row["trace_id"]),
|
|
251
294
|
)
|
|
252
295
|
|
|
253
296
|
def _insert_event(
|
|
@@ -304,14 +347,3 @@ class SqliteRunStore:
|
|
|
304
347
|
(cutoff,),
|
|
305
348
|
)
|
|
306
349
|
self._database.commit()
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def encode(value: object) -> str:
|
|
310
|
-
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def sequence_of(event: CanonicalEvent) -> int:
|
|
314
|
-
value = event.get("sequence")
|
|
315
|
-
if not isinstance(value, int):
|
|
316
|
-
raise RuntimeError("Agent Event sequence 无效")
|
|
317
|
-
return value
|
|
@@ -17,6 +17,8 @@ class RunRecord:
|
|
|
17
17
|
input: JsonValue
|
|
18
18
|
idempotency_key: str
|
|
19
19
|
fingerprint: str
|
|
20
|
+
release: JsonValue = None
|
|
21
|
+
trace_id: str = ""
|
|
20
22
|
status: RunStatus = "accepted"
|
|
21
23
|
events: list[CanonicalEvent] = field(default_factory=list)
|
|
22
24
|
output: JsonValue = None
|
|
@@ -30,8 +32,16 @@ class IdempotencyConflictError(Exception):
|
|
|
30
32
|
pass
|
|
31
33
|
|
|
32
34
|
|
|
35
|
+
class ReleaseNotFoundError(Exception):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
33
39
|
class RunStore(Protocol):
|
|
34
|
-
async def create_or_get(
|
|
40
|
+
async def create_or_get(
|
|
41
|
+
self,
|
|
42
|
+
record: RunRecord,
|
|
43
|
+
invocation: JsonValue = None,
|
|
44
|
+
) -> tuple[RunRecord, bool]: ...
|
|
35
45
|
async def get(self, run_id: str) -> RunRecord | None: ...
|
|
36
46
|
async def append(
|
|
37
47
|
self,
|
|
@@ -46,6 +56,20 @@ class RunStore(Protocol):
|
|
|
46
56
|
after: int,
|
|
47
57
|
wait_seconds: float,
|
|
48
58
|
) -> tuple[list[CanonicalEvent], bool]: ...
|
|
59
|
+
async def save_invocation(self, run_id: str, value: JsonValue) -> None: ...
|
|
60
|
+
async def recoverable(self) -> list[tuple[RunRecord, JsonValue]]: ...
|
|
61
|
+
async def clear_invocation(self, run_id: str) -> None: ...
|
|
62
|
+
async def record_evaluation(
|
|
63
|
+
self,
|
|
64
|
+
release_id: str,
|
|
65
|
+
evaluation_set_key: str,
|
|
66
|
+
evaluation_set_version: str,
|
|
67
|
+
metrics: dict[str, float],
|
|
68
|
+
) -> str: ...
|
|
69
|
+
async def renew_lease(self, run_id: str) -> None: ...
|
|
70
|
+
async def release_lease(self, run_id: str) -> None: ...
|
|
71
|
+
async def probe(self) -> None: ...
|
|
72
|
+
async def close(self) -> None: ...
|
|
49
73
|
|
|
50
74
|
|
|
51
75
|
class InMemoryRunStore:
|
|
@@ -55,8 +79,15 @@ class InMemoryRunStore:
|
|
|
55
79
|
self._runs: dict[str, RunRecord] = {}
|
|
56
80
|
self._idempotency: dict[str, str] = {}
|
|
57
81
|
self._condition = asyncio.Condition()
|
|
82
|
+
self._invocations: dict[str, JsonValue] = {}
|
|
83
|
+
self._evaluations: dict[tuple[str, str, str], tuple[str, dict[str, float]]] = {}
|
|
84
|
+
self._releases: dict[str, tuple[str, JsonValue]] = {}
|
|
58
85
|
|
|
59
|
-
async def create_or_get(
|
|
86
|
+
async def create_or_get(
|
|
87
|
+
self,
|
|
88
|
+
record: RunRecord,
|
|
89
|
+
invocation: JsonValue = None,
|
|
90
|
+
) -> tuple[RunRecord, bool]:
|
|
60
91
|
async with self._condition:
|
|
61
92
|
existing_id = self._idempotency.get(record.idempotency_key)
|
|
62
93
|
if existing_id is not None:
|
|
@@ -64,8 +95,16 @@ class InMemoryRunStore:
|
|
|
64
95
|
if existing.fingerprint != record.fingerprint:
|
|
65
96
|
raise IdempotencyConflictError
|
|
66
97
|
return existing, False
|
|
98
|
+
release_id = release_id_of(record)
|
|
99
|
+
release = (record.operation_key, record.release)
|
|
100
|
+
existing_release = self._releases.get(release_id)
|
|
101
|
+
if existing_release is not None and existing_release != release:
|
|
102
|
+
raise IdempotencyConflictError
|
|
103
|
+
self._releases[release_id] = release
|
|
67
104
|
self._runs[record.run_id] = record
|
|
68
105
|
self._idempotency[record.idempotency_key] = record.run_id
|
|
106
|
+
if invocation is not None:
|
|
107
|
+
self._invocations[record.run_id] = invocation
|
|
69
108
|
self._condition.notify_all()
|
|
70
109
|
return record, True
|
|
71
110
|
|
|
@@ -131,14 +170,78 @@ class InMemoryRunStore:
|
|
|
131
170
|
return [], False
|
|
132
171
|
return list(record.events[after:]), record.terminal
|
|
133
172
|
|
|
173
|
+
async def save_invocation(self, run_id: str, value: JsonValue) -> None:
|
|
174
|
+
async with self._condition:
|
|
175
|
+
if run_id not in self._runs:
|
|
176
|
+
raise KeyError(run_id)
|
|
177
|
+
self._invocations[run_id] = value
|
|
178
|
+
|
|
179
|
+
async def recoverable(self) -> list[tuple[RunRecord, JsonValue]]:
|
|
180
|
+
async with self._condition:
|
|
181
|
+
return [
|
|
182
|
+
(record, self._invocations[run_id])
|
|
183
|
+
for run_id, record in self._runs.items()
|
|
184
|
+
if not record.terminal and run_id in self._invocations
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
async def clear_invocation(self, run_id: str) -> None:
|
|
188
|
+
async with self._condition:
|
|
189
|
+
self._invocations.pop(run_id, None)
|
|
190
|
+
|
|
191
|
+
async def record_evaluation(
|
|
192
|
+
self,
|
|
193
|
+
release_id: str,
|
|
194
|
+
evaluation_set_key: str,
|
|
195
|
+
evaluation_set_version: str,
|
|
196
|
+
metrics: dict[str, float],
|
|
197
|
+
) -> str:
|
|
198
|
+
async with self._condition:
|
|
199
|
+
if release_id not in self._releases:
|
|
200
|
+
raise ReleaseNotFoundError
|
|
201
|
+
key = (release_id, evaluation_set_key, evaluation_set_version)
|
|
202
|
+
existing = self._evaluations.get(key)
|
|
203
|
+
if existing:
|
|
204
|
+
if existing[1] != metrics:
|
|
205
|
+
raise IdempotencyConflictError
|
|
206
|
+
return existing[0]
|
|
207
|
+
evaluation_id = f"eval_{len(self._evaluations) + 1}"
|
|
208
|
+
self._evaluations[key] = (evaluation_id, metrics)
|
|
209
|
+
return evaluation_id
|
|
210
|
+
|
|
211
|
+
async def renew_lease(self, run_id: str) -> None:
|
|
212
|
+
record = self._runs.get(run_id)
|
|
213
|
+
if record is None or record.terminal:
|
|
214
|
+
raise KeyError(run_id)
|
|
215
|
+
|
|
216
|
+
async def release_lease(self, run_id: str) -> None:
|
|
217
|
+
if run_id not in self._runs:
|
|
218
|
+
raise KeyError(run_id)
|
|
219
|
+
|
|
220
|
+
async def probe(self) -> None:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
async def close(self) -> None:
|
|
224
|
+
return None
|
|
225
|
+
|
|
134
226
|
|
|
135
227
|
def status_for(event_type: str, current: RunStatus) -> RunStatus:
|
|
136
228
|
if event_type == "run.started":
|
|
137
229
|
return "running"
|
|
138
230
|
if event_type == "approval.required":
|
|
139
231
|
return "awaiting-approval"
|
|
232
|
+
if event_type == "approval.resolved":
|
|
233
|
+
return "running"
|
|
140
234
|
if event_type == "run.failed":
|
|
141
235
|
return "failed"
|
|
142
236
|
if event_type == "run.cancelled":
|
|
143
237
|
return "cancelled"
|
|
144
238
|
return current
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def release_id_of(record: RunRecord) -> str:
|
|
242
|
+
if not isinstance(record.release, dict):
|
|
243
|
+
raise IdempotencyConflictError
|
|
244
|
+
release_id = record.release.get("releaseId")
|
|
245
|
+
if not isinstance(release_id, str):
|
|
246
|
+
raise IdempotencyConflictError
|
|
247
|
+
return release_id
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
10
|
+
|
|
11
|
+
from .contracts import JsonValue
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RunStoreCipher:
|
|
15
|
+
def __init__(self, secret: str) -> None:
|
|
16
|
+
if len(secret.encode()) < 32:
|
|
17
|
+
raise ValueError("AGENT_RUN_ENCRYPTION_SECRET 至少需要 32 字节")
|
|
18
|
+
self._key = hashlib.sha256(secret.encode()).digest()
|
|
19
|
+
|
|
20
|
+
def seal(self, value: JsonValue) -> str:
|
|
21
|
+
nonce = os.urandom(12)
|
|
22
|
+
body = json.dumps(
|
|
23
|
+
value,
|
|
24
|
+
ensure_ascii=False,
|
|
25
|
+
separators=(",", ":"),
|
|
26
|
+
).encode()
|
|
27
|
+
encrypted = AESGCM(self._key).encrypt(nonce, body, b"agent-run-v1")
|
|
28
|
+
return base64.urlsafe_b64encode(nonce + encrypted).decode()
|
|
29
|
+
|
|
30
|
+
def open(self, value: str) -> JsonValue:
|
|
31
|
+
try:
|
|
32
|
+
raw = base64.urlsafe_b64decode(value.encode())
|
|
33
|
+
body = AESGCM(self._key).decrypt(
|
|
34
|
+
raw[:12],
|
|
35
|
+
raw[12:],
|
|
36
|
+
b"agent-run-v1",
|
|
37
|
+
)
|
|
38
|
+
return cast(JsonValue, json.loads(body))
|
|
39
|
+
except Exception as error:
|
|
40
|
+
raise ValueError("Agent Run 加密上下文无法解密") from error
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import secrets
|
|
5
|
+
from contextvars import Token
|
|
6
|
+
|
|
7
|
+
from opentelemetry import context, trace
|
|
8
|
+
from opentelemetry.context import Context
|
|
9
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
10
|
+
from opentelemetry.sdk.resources import Resource
|
|
11
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
12
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
13
|
+
from opentelemetry.trace import (
|
|
14
|
+
NonRecordingSpan,
|
|
15
|
+
Span,
|
|
16
|
+
SpanContext,
|
|
17
|
+
TraceFlags,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .store import RunRecord
|
|
21
|
+
|
|
22
|
+
_configured = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def configure_telemetry() -> None:
|
|
26
|
+
global _configured
|
|
27
|
+
endpoint = os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "").strip()
|
|
28
|
+
if _configured or not endpoint:
|
|
29
|
+
return
|
|
30
|
+
provider = TracerProvider(
|
|
31
|
+
resource=Resource.create({"service.name": "southwind-agent-server"})
|
|
32
|
+
)
|
|
33
|
+
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint)))
|
|
34
|
+
trace.set_tracer_provider(provider)
|
|
35
|
+
_configured = True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AgentRunTrace:
|
|
39
|
+
"""OTel span carrying metadata only; prompts, inputs and outputs stay excluded."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, record: RunRecord) -> None:
|
|
42
|
+
parent = parent_context(record.trace_id)
|
|
43
|
+
self._span: Span = trace.get_tracer(__name__).start_span(
|
|
44
|
+
"agent.run",
|
|
45
|
+
context=parent,
|
|
46
|
+
attributes={
|
|
47
|
+
"agent.run.id": record.run_id,
|
|
48
|
+
"agent.operation.key": record.operation_key,
|
|
49
|
+
"agent.release.id": release_id(record),
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
self._token: Token[Context] | None = None
|
|
53
|
+
|
|
54
|
+
def start(self) -> None:
|
|
55
|
+
self._token = context.attach(trace.set_span_in_context(self._span))
|
|
56
|
+
|
|
57
|
+
def finish(self, status: str) -> None:
|
|
58
|
+
self._span.set_attribute("agent.run.status", status)
|
|
59
|
+
if self._token is not None:
|
|
60
|
+
context.detach(self._token)
|
|
61
|
+
self._span.end()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def parent_context(trace_id: str) -> context.Context | None:
|
|
65
|
+
if len(trace_id) != 32:
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
parsed = int(trace_id, 16)
|
|
69
|
+
except ValueError:
|
|
70
|
+
return None
|
|
71
|
+
if parsed == 0:
|
|
72
|
+
return None
|
|
73
|
+
parent = NonRecordingSpan(
|
|
74
|
+
SpanContext(
|
|
75
|
+
trace_id=parsed,
|
|
76
|
+
span_id=secrets.randbits(64) or 1,
|
|
77
|
+
is_remote=True,
|
|
78
|
+
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
return trace.set_span_in_context(parent)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def release_id(record: RunRecord) -> str:
|
|
85
|
+
if isinstance(record.release, dict):
|
|
86
|
+
value = record.release.get("releaseId")
|
|
87
|
+
if isinstance(value, str):
|
|
88
|
+
return value
|
|
89
|
+
return "unknown"
|