create-windy 0.2.33 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +173 -17
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +9 -0
- package/template/README.md +3 -1
- package/template/apps/agent-server/Dockerfile +1 -0
- package/template/apps/agent-server/README.md +28 -5
- package/template/apps/agent-server/adk_web_agents/__init__.py +1 -0
- package/template/apps/agent-server/adk_web_agents/safe_debug/__init__.py +3 -0
- package/template/apps/agent-server/adk_web_agents/safe_debug/agent.py +40 -0
- package/template/apps/agent-server/pyproject.toml +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +191 -16
- package/template/apps/agent-server/src/southwind_agent_server/adk_failure.py +13 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +225 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +76 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +63 -3
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
- package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
- package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
- package/template/apps/agent-server/tests/test_adk_adapter.py +305 -0
- package/template/apps/agent-server/tests/test_adk_web_agent.py +12 -0
- package/template/apps/agent-server/tests/test_api.py +16 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
- package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
- package/template/apps/server/package.json +4 -1
- package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
- package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +2 -0
- package/template/apps/server/src/agent/run-facade.test.ts +52 -2
- package/template/apps/server/src/agent/run-facade.ts +38 -9
- package/template/apps/server/src/agent/tool-registry.ts +2 -6
- package/template/apps/server/src/application-services.ts +50 -0
- package/template/apps/server/src/audit/drizzle-log-store.ts +25 -0
- package/template/apps/server/src/audit/search-summary.ts +2 -6
- package/template/apps/server/src/auth/credential-restriction.ts +1 -0
- package/template/apps/server/src/auth/password.ts +30 -21
- package/template/apps/server/src/auth/routes.test.ts +44 -3
- package/template/apps/server/src/auth/routes.ts +6 -1
- package/template/apps/server/src/auth/runtime.ts +4 -1
- package/template/apps/server/src/auth/service.ts +25 -7
- package/template/apps/server/src/configuration/bootstrap.ts +20 -1
- package/template/apps/server/src/configuration/definition.test.ts +71 -0
- package/template/apps/server/src/configuration/definition.ts +242 -12
- package/template/apps/server/src/configuration/drizzle-repository.ts +36 -2
- package/template/apps/server/src/configuration/repository.ts +27 -2
- package/template/apps/server/src/configuration/routes.ts +4 -1
- package/template/apps/server/src/configuration/service.test.ts +100 -0
- package/template/apps/server/src/configuration/service.ts +50 -10
- package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +3 -2
- package/template/apps/server/src/data-governance/export/service.ts +2 -6
- package/template/apps/server/src/example-modules.ts +4 -1
- package/template/apps/server/src/guards.ts +18 -5
- package/template/apps/server/src/http/request-source.test.ts +85 -0
- package/template/apps/server/src/http/request-source.ts +90 -0
- package/template/apps/server/src/index.ts +45 -45
- package/template/apps/server/src/license/routes.ts +2 -0
- package/template/apps/server/src/license/runtime-service.ts +10 -11
- package/template/apps/server/src/module-bootstrap.ts +2 -2
- package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
- package/template/apps/server/src/module-host/initialize.ts +30 -44
- package/template/apps/server/src/module-host/request-context.ts +29 -23
- package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
- package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
- package/template/apps/server/src/module-host/route-declaration.ts +45 -0
- package/template/apps/server/src/module-host/route-installer.ts +27 -3
- package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
- package/template/apps/server/src/module-host/storage-port.ts +134 -1
- package/template/apps/server/src/module-host/storage-scope.ts +89 -0
- package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
- package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
- package/template/apps/server/src/module-host/user-directory-port.ts +139 -0
- package/template/apps/server/src/module-host.ts +4 -0
- package/template/apps/server/src/notification/channel-adapter.ts +46 -0
- package/template/apps/server/src/notification/delivery-policy.test.ts +59 -0
- package/template/apps/server/src/notification/delivery-policy.ts +37 -0
- package/template/apps/server/src/notification/delivery-repository.ts +198 -0
- package/template/apps/server/src/notification/delivery-worker.test.ts +87 -0
- package/template/apps/server/src/notification/delivery-worker.ts +88 -0
- package/template/apps/server/src/notification/drizzle-management.ts +180 -0
- package/template/apps/server/src/notification/drizzle-query.ts +160 -0
- package/template/apps/server/src/notification/drizzle-repository.integration.test.ts +301 -0
- package/template/apps/server/src/notification/drizzle-repository.ts +156 -90
- package/template/apps/server/src/notification/extension-routes.test.ts +119 -0
- package/template/apps/server/src/notification/extension-routes.ts +111 -0
- package/template/apps/server/src/notification/memory-delivery-repository.ts +137 -0
- package/template/apps/server/src/notification/memory-support.ts +72 -0
- package/template/apps/server/src/notification/preference-repository.ts +95 -0
- package/template/apps/server/src/notification/recipient-directory.ts +104 -0
- package/template/apps/server/src/notification/repository.test.ts +171 -0
- package/template/apps/server/src/notification/repository.ts +179 -32
- package/template/apps/server/src/notification/revision-routes.test.ts +186 -0
- package/template/apps/server/src/notification/route-support.ts +225 -0
- package/template/apps/server/src/notification/routes.test.ts +182 -0
- package/template/apps/server/src/notification/routes.ts +225 -132
- package/template/apps/server/src/notification/service.test.ts +104 -0
- package/template/apps/server/src/notification/service.ts +140 -0
- package/template/apps/server/src/notification/smtp-adapter.test.ts +65 -0
- package/template/apps/server/src/notification/smtp-adapter.ts +68 -0
- package/template/apps/server/src/persistence.integration.test.ts +41 -2
- package/template/apps/server/src/route-guards.ts +1 -0
- package/template/apps/server/src/runtime-audit-list.test.ts +39 -0
- package/template/apps/server/src/runtime-development.ts +2 -0
- package/template/apps/server/src/runtime.test.ts +23 -9
- package/template/apps/server/src/runtime.ts +39 -2
- package/template/apps/server/src/scheduler/audit.ts +2 -11
- package/template/apps/server/src/scheduler/definitions.test.ts +6 -0
- package/template/apps/server/src/scheduler/definitions.ts +7 -0
- package/template/apps/server/src/scheduler/recovery-service.ts +2 -11
- package/template/apps/server/src/scheduler/routes.ts +2 -7
- package/template/apps/server/src/search/opensearch-client.test.ts +45 -0
- package/template/apps/server/src/search/opensearch-client.ts +145 -0
- package/template/apps/server/src/search/service.ts +2 -6
- package/template/apps/server/src/settings/routes.ts +15 -3
- package/template/apps/server/src/settings/runtime.ts +10 -1
- package/template/apps/server/src/storage/runtime.ts +13 -0
- package/template/apps/server/src/system/audit-query.ts +33 -4
- package/template/apps/server/src/system/audit-routes.test.ts +58 -4
- package/template/apps/server/src/system/audit-routes.ts +6 -5
- package/template/apps/server/src/system/built-in-roles.ts +1 -0
- package/template/apps/server/src/system/department-organization.ts +320 -0
- package/template/apps/server/src/system/department-routes.test.ts +207 -48
- package/template/apps/server/src/system/department-routes.ts +114 -33
- package/template/apps/server/src/system/drizzle-repository.ts +15 -7
- package/template/apps/server/src/system/drizzle-scoped-repository.ts +5 -3
- package/template/apps/server/src/system/drizzle-system.ts +37 -9
- package/template/apps/server/src/system/drizzle-transaction-context.ts +14 -0
- package/template/apps/server/src/system/identity-route-config.ts +7 -2
- package/template/apps/server/src/system/mutation-runner.ts +23 -0
- package/template/apps/server/src/system/organization-postgres.integration.test.ts +176 -0
- package/template/apps/server/src/system/resource-access-response.ts +12 -6
- package/template/apps/server/src/system/role-department-scope-policy.ts +55 -0
- package/template/apps/server/src/system/route-helpers.ts +2 -5
- package/template/apps/server/src/system/route-types.ts +12 -3
- package/template/apps/server/src/system/routes-validation.test.ts +26 -0
- package/template/apps/server/src/system/routes.test.ts +10 -2
- package/template/apps/server/src/system/routes.ts +70 -36
- package/template/apps/server/src/system/seed.ts +7 -0
- package/template/apps/server/src/work-order/data-scope.integration.test.ts +9 -2
- package/template/apps/server/src/work-order/opensearch-index.test.ts +97 -0
- package/template/apps/server/src/work-order/opensearch-index.ts +313 -0
- package/template/apps/server/src/work-order/search-provider.ts +60 -1
- package/template/apps/server/src/work-order/service.ts +42 -10
- package/template/apps/web/src/app/error-pages.ts +9 -0
- package/template/apps/web/src/components/auth/PasswordChangeForm.vue +16 -11
- package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +14 -0
- package/template/apps/web/src/components/common/FormDialog.vue +16 -2
- package/template/apps/web/src/components/common/ModalLayout.webtest.ts +20 -1
- package/template/apps/web/src/composables/useGlobalSearch.ts +68 -0
- package/template/apps/web/src/composables/usePlatformSettings.ts +8 -7
- package/template/apps/web/src/layout/GlobalSearchResults.vue +58 -0
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +42 -6
- package/template/apps/web/src/layout/NavigationSearchDialog.webtest.ts +71 -0
- package/template/apps/web/src/layout/NotificationMenu.vue +73 -31
- package/template/apps/web/src/layout/NotificationMenu.webtest.ts +44 -1
- package/template/apps/web/src/layout/NotificationMenuItem.vue +61 -0
- package/template/apps/web/src/layout/NotificationPreferencesDialog.vue +181 -0
- package/template/apps/web/src/layout/NotificationPreferencesDialog.webtest.ts +80 -0
- package/template/apps/web/src/lib/date-time.ts +22 -10
- package/template/apps/web/src/lib/date-time.webtest.ts +9 -1
- package/template/apps/web/src/main.ts +14 -1
- package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +16 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -0
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +10 -0
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +37 -6
- package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +115 -0
- package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +44 -0
- package/template/apps/web/src/pages/errors/error-page-extension.ts +26 -0
- package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +136 -68
- package/template/apps/web/src/pages/system/SystemNotificationsPage.webtest.ts +224 -12
- package/template/apps/web/src/pages/system/audit-log-presenter.ts +86 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +28 -0
- package/template/apps/web/src/pages/system/components/AuditActionCell.vue +10 -69
- package/template/apps/web/src/pages/system/components/AuditActionCell.webtest.ts +20 -27
- package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.vue +134 -0
- package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.webtest.ts +76 -0
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +116 -52
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +52 -15
- package/template/apps/web/src/pages/system/components/NotificationHistoryDialog.vue +129 -0
- package/template/apps/web/src/pages/system/components/NotificationManagementCard.vue +91 -0
- package/template/apps/web/src/pages/system/components/NotificationPublishDialog.vue +121 -0
- package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +162 -1
- package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +68 -0
- package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +74 -0
- package/template/apps/web/src/pages/system/components/SystemResourceFilters.vue +3 -4
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +91 -98
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +68 -0
- package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourceTable.webtest.ts +28 -7
- package/template/apps/web/src/pages/system/components/department/DepartmentOrganizationForm.vue +210 -0
- package/template/apps/web/src/pages/system/components/department/DepartmentTransitionDialog.vue +112 -0
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +17 -4
- package/template/apps/web/src/pages/system/composables/useDepartmentTransitions.ts +79 -0
- package/template/apps/web/src/pages/system/composables/useNotificationPermissions.ts +14 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +2 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.webtest.ts +15 -2
- package/template/apps/web/src/pages/system/resource-config.ts +12 -4
- package/template/apps/web/src/router/error-navigation.ts +50 -0
- package/template/apps/web/src/router/error-navigation.webtest.ts +106 -0
- package/template/apps/web/src/router/index.ts +71 -0
- package/template/apps/web/src/router/index.webtest.ts +29 -0
- package/template/apps/web/src/router/manifest-routes.webtest.ts +4 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
- package/template/apps/web/src/services/auth-api.ts +5 -0
- package/template/apps/web/src/services/http.ts +43 -6
- package/template/apps/web/src/services/http.webtest.ts +70 -0
- package/template/apps/web/src/services/notification-api.ts +85 -2
- package/template/apps/web/src/services/notification-api.webtest.ts +110 -0
- package/template/apps/web/src/services/search-api.ts +39 -0
- package/template/apps/web/src/services/system-api.ts +18 -0
- package/template/apps/web/vitest.config.ts +1 -0
- package/template/docker-compose.yml +68 -0
- package/template/docs/architecture/ai-egress.md +49 -7
- package/template/docs/architecture/ai-runtime.md +34 -6
- package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
- package/template/docs/architecture/object-storage.md +22 -2
- package/template/docs/architecture/search-provider-protocol.md +38 -2
- package/template/docs/development/custom-error-pages.md +78 -0
- package/template/docs/development/custom-login-page.md +3 -3
- package/template/docs/platform/agent-runtime.md +76 -7
- package/template/docs/platform/audit-reliability.md +46 -2
- package/template/docs/platform/business-user-directory.md +82 -0
- package/template/docs/platform/module-configuration.md +13 -4
- package/template/docs/platform/notifications.md +67 -11
- package/template/docs/platform/organization-membership.md +17 -3
- package/template/docs/platform/system-crud-api.md +16 -0
- package/template/docs/platform/web-system-crud.md +6 -2
- package/template/package.json +4 -0
- package/template/packages/config/index.test.ts +45 -0
- package/template/packages/config/package.json +1 -1
- package/template/packages/config/src/platform.ts +169 -48
- package/template/packages/database/drizzle/0035_material_nightshade.sql +10 -0
- package/template/packages/database/drizzle/0036_hesitant_speed.sql +35 -0
- package/template/packages/database/drizzle/0037_absent_nick_fury.sql +2 -0
- package/template/packages/database/drizzle/0038_uneven_wasp.sql +38 -0
- package/template/packages/database/drizzle/meta/0035_snapshot.json +7331 -0
- package/template/packages/database/drizzle/meta/0036_snapshot.json +7436 -0
- package/template/packages/database/drizzle/meta/0037_snapshot.json +7457 -0
- package/template/packages/database/drizzle/meta/0038_snapshot.json +7749 -0
- package/template/packages/database/drizzle/meta/_journal.json +28 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/schema/identity.ts +7 -0
- package/template/packages/database/src/schema/notifications.ts +135 -0
- package/template/packages/database/src/schema-notification-delivery.test.ts +54 -0
- package/template/packages/database/src/schema-workflow-notification.test.ts +40 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
- package/template/packages/modules/src/ai-operation-composition.ts +115 -0
- package/template/packages/modules/src/manifest.ts +26 -0
- package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
- package/template/packages/modules/src/system-api-permissions.ts +54 -0
- package/template/packages/modules/src/system-audit-actions.ts +15 -0
- package/template/packages/modules/src/system-features.ts +6 -1
- package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
- package/template/packages/modules/src/system-module-catalog.ts +28 -4
- package/template/packages/modules/src/system-modules.test.ts +13 -1
- package/template/packages/modules/src/system-modules.ts +12 -0
- package/template/packages/modules/src/system-permissions.ts +11 -5
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +3 -2
- package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
- package/template/packages/server-sdk/src/module-host.ts +8 -0
- package/template/packages/server-sdk/src/request-context.ts +2 -0
- package/template/packages/server-sdk/src/storage-port.ts +66 -0
- package/template/packages/server-sdk/src/tool-registration.ts +6 -0
- package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.ts +25 -0
- package/template/packages/shared/src/module-configuration.ts +5 -0
- package/template/packages/shared/src/notification.ts +92 -1
- package/template/packages/shared/src/password.ts +7 -2
- package/template/packages/shared/src/platform-settings.ts +2 -0
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage/src/upload-service.test.ts +16 -0
- package/template/packages/storage/src/upload-service.ts +5 -1
- package/template/scripts/notification-postgres-regression.ts +27 -0
|
@@ -7,6 +7,7 @@ from typing import Protocol
|
|
|
7
7
|
from .contracts import (
|
|
8
8
|
AgentErrorCode,
|
|
9
9
|
JsonValue,
|
|
10
|
+
ProviderDataPolicy,
|
|
10
11
|
ProviderEgress,
|
|
11
12
|
RuntimeLimits,
|
|
12
13
|
RuntimeToolDefinition,
|
|
@@ -24,6 +25,7 @@ class EngineRunRequest:
|
|
|
24
25
|
allowed_tools: tuple[RuntimeToolDefinition, ...] = ()
|
|
25
26
|
limits: RuntimeLimits = field(default_factory=RuntimeLimits)
|
|
26
27
|
provider_egress: ProviderEgress = field(default_factory=ProviderEgress)
|
|
28
|
+
provider_data_policy: ProviderDataPolicy = field(default_factory=ProviderDataPolicy)
|
|
27
29
|
tool_network_policy: ToolNetworkPolicy = field(default_factory=ToolNetworkPolicy)
|
|
28
30
|
execution_grant: str = ""
|
|
29
31
|
tool_host_url: str = ""
|
|
@@ -17,6 +17,7 @@ from .engine import (
|
|
|
17
17
|
UsageReported,
|
|
18
18
|
)
|
|
19
19
|
from .openai_provider import OpenAiCompatibleProvider, ProviderToolCall
|
|
20
|
+
from .provider_governance import ProviderRequestGovernance
|
|
20
21
|
from .tool_host import ToolHostClient, ToolInvocation
|
|
21
22
|
|
|
22
23
|
|
|
@@ -25,9 +26,11 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
|
|
|
25
26
|
self,
|
|
26
27
|
provider: OpenAiCompatibleProvider,
|
|
27
28
|
tool_host: ToolHostClient,
|
|
29
|
+
governance: ProviderRequestGovernance,
|
|
28
30
|
) -> None:
|
|
29
31
|
self._provider = provider
|
|
30
32
|
self._tool_host = tool_host
|
|
33
|
+
self._governance = governance
|
|
31
34
|
self._cancellations: dict[str, asyncio.Event] = {}
|
|
32
35
|
|
|
33
36
|
def supports(self, operation_key: str) -> bool:
|
|
@@ -49,11 +52,24 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
|
|
|
49
52
|
]
|
|
50
53
|
output_bytes = 0
|
|
51
54
|
try:
|
|
52
|
-
for
|
|
55
|
+
for attempt in range(1, self._provider.config.max_tool_rounds + 1):
|
|
53
56
|
cancelled(cancellation)
|
|
57
|
+
governed = self._governance.authorize(
|
|
58
|
+
deployment_key=self._provider.config.deployment_key,
|
|
59
|
+
operation_key=request.operation_key,
|
|
60
|
+
run_id=request.run_id,
|
|
61
|
+
attempt=attempt,
|
|
62
|
+
messages=messages,
|
|
63
|
+
tools=request.allowed_tools,
|
|
64
|
+
policy=request.provider_data_policy,
|
|
65
|
+
input_value=request.input,
|
|
66
|
+
measure=lambda messages, tools: len(
|
|
67
|
+
self._provider.request_body(messages, tools)
|
|
68
|
+
),
|
|
69
|
+
)
|
|
54
70
|
turn = await self._provider.complete_turn(
|
|
55
|
-
messages,
|
|
56
|
-
|
|
71
|
+
governed.messages,
|
|
72
|
+
governed.tools,
|
|
57
73
|
)
|
|
58
74
|
if turn.tool_calls:
|
|
59
75
|
messages.append(assistant_tool_message(turn.tool_calls))
|
|
@@ -135,6 +151,9 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
|
|
|
135
151
|
async def health(self) -> None:
|
|
136
152
|
await self._provider.health()
|
|
137
153
|
|
|
154
|
+
def governance_health(self) -> dict[str, int | float | str | None]:
|
|
155
|
+
return self._governance.health()
|
|
156
|
+
|
|
138
157
|
|
|
139
158
|
def validate_runtime_request(
|
|
140
159
|
request: EngineRunRequest,
|
|
@@ -166,6 +185,15 @@ def validate_runtime_request(
|
|
|
166
185
|
"Agent Tool 网络策略不受支持",
|
|
167
186
|
retryable=False,
|
|
168
187
|
)
|
|
188
|
+
if (
|
|
189
|
+
not request.provider_data_policy.input_fields
|
|
190
|
+
or not request.provider_data_policy.envelope_fields
|
|
191
|
+
):
|
|
192
|
+
raise SafeEngineError(
|
|
193
|
+
"FORBIDDEN",
|
|
194
|
+
"Agent Operation 缺少 Provider 字段治理策略",
|
|
195
|
+
retryable=False,
|
|
196
|
+
)
|
|
169
197
|
|
|
170
198
|
|
|
171
199
|
def cancelled(event: asyncio.Event) -> None:
|
|
@@ -59,21 +59,15 @@ class OpenAiCompatibleProvider:
|
|
|
59
59
|
messages: list[dict[str, JsonValue]],
|
|
60
60
|
tools: tuple[RuntimeToolDefinition, ...],
|
|
61
61
|
) -> ProviderTurn:
|
|
62
|
-
|
|
63
|
-
"model": self.config.model,
|
|
64
|
-
"messages": cast(JsonValue, messages),
|
|
65
|
-
"stream": True,
|
|
66
|
-
"stream_options": {"include_usage": True},
|
|
67
|
-
}
|
|
68
|
-
if tools:
|
|
69
|
-
payload["tools"] = [tool_schema(tool) for tool in tools]
|
|
62
|
+
body = self.request_body(messages, tools)
|
|
70
63
|
turn = ProviderTurn()
|
|
71
64
|
async with self._client() as client:
|
|
72
65
|
try:
|
|
73
66
|
async with client.stream(
|
|
74
67
|
"POST",
|
|
75
68
|
"/chat/completions",
|
|
76
|
-
|
|
69
|
+
content=body,
|
|
70
|
+
headers={"content-type": "application/json"},
|
|
77
71
|
) as response:
|
|
78
72
|
if not response.is_success:
|
|
79
73
|
await self._bounded_stream(response)
|
|
@@ -85,6 +79,25 @@ class OpenAiCompatibleProvider:
|
|
|
85
79
|
raise provider_failure(error) from None
|
|
86
80
|
return turn
|
|
87
81
|
|
|
82
|
+
def request_body(
|
|
83
|
+
self,
|
|
84
|
+
messages: list[dict[str, JsonValue]],
|
|
85
|
+
tools: tuple[RuntimeToolDefinition, ...],
|
|
86
|
+
) -> bytes:
|
|
87
|
+
payload: dict[str, JsonValue] = {
|
|
88
|
+
"model": self.config.model,
|
|
89
|
+
"messages": cast(JsonValue, messages),
|
|
90
|
+
"stream": True,
|
|
91
|
+
"stream_options": {"include_usage": True},
|
|
92
|
+
}
|
|
93
|
+
if tools:
|
|
94
|
+
payload["tools"] = [tool_schema(tool) for tool in tools]
|
|
95
|
+
return json.dumps(
|
|
96
|
+
payload,
|
|
97
|
+
ensure_ascii=False,
|
|
98
|
+
separators=(",", ":"),
|
|
99
|
+
).encode()
|
|
100
|
+
|
|
88
101
|
def _client(self) -> httpx.AsyncClient:
|
|
89
102
|
api_key = (
|
|
90
103
|
self._resolver.resolve(self.config.api_key_ref)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class ProviderAttemptAudit:
|
|
13
|
+
deployment_key: str
|
|
14
|
+
operation_key: str
|
|
15
|
+
run_id: str
|
|
16
|
+
attempt: int
|
|
17
|
+
classifications: tuple[str, ...]
|
|
18
|
+
sent_bytes: int
|
|
19
|
+
decision: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProviderAuditStore:
|
|
23
|
+
"""Append-only Provider attempt metadata, except explicit TTL expiry."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, path: str, *, retention_days: int) -> None:
|
|
26
|
+
if not path.strip():
|
|
27
|
+
raise ValueError("Provider 发送审计 Store 路径不能为空")
|
|
28
|
+
if not 1 <= retention_days <= 3650:
|
|
29
|
+
raise ValueError("Provider 发送审计留存天数超出范围")
|
|
30
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
self._database = sqlite3.connect(path, check_same_thread=False)
|
|
32
|
+
self._lock = threading.RLock()
|
|
33
|
+
self._retention_seconds = retention_days * 24 * 60 * 60
|
|
34
|
+
self.retention_days = retention_days
|
|
35
|
+
self._initialize()
|
|
36
|
+
self.purge_expired()
|
|
37
|
+
|
|
38
|
+
def append(self, audit: ProviderAttemptAudit) -> None:
|
|
39
|
+
with self._lock:
|
|
40
|
+
self.purge_expired()
|
|
41
|
+
try:
|
|
42
|
+
self._database.execute(
|
|
43
|
+
"""
|
|
44
|
+
insert into provider_attempt_audit (
|
|
45
|
+
event_id, deployment_key, operation_key, run_id, attempt,
|
|
46
|
+
classifications, sent_bytes, decision, occurred_at
|
|
47
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
48
|
+
""",
|
|
49
|
+
(
|
|
50
|
+
f"pa_{uuid.uuid4().hex}",
|
|
51
|
+
audit.deployment_key,
|
|
52
|
+
audit.operation_key,
|
|
53
|
+
audit.run_id,
|
|
54
|
+
audit.attempt,
|
|
55
|
+
",".join(audit.classifications),
|
|
56
|
+
audit.sent_bytes,
|
|
57
|
+
audit.decision,
|
|
58
|
+
time.time(),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
self._database.commit()
|
|
62
|
+
except Exception:
|
|
63
|
+
self._database.rollback()
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
def purge_expired(self, *, now: float | None = None) -> int:
|
|
67
|
+
with self._lock:
|
|
68
|
+
cutoff = (now or time.time()) - self._retention_seconds
|
|
69
|
+
cursor = self._database.execute(
|
|
70
|
+
"delete from provider_attempt_audit where occurred_at < ?",
|
|
71
|
+
(cutoff,),
|
|
72
|
+
)
|
|
73
|
+
deleted = max(cursor.rowcount, 0)
|
|
74
|
+
self._database.execute(
|
|
75
|
+
"""
|
|
76
|
+
update provider_audit_retention
|
|
77
|
+
set last_purge_at = ?, last_deleted = ?,
|
|
78
|
+
total_deleted = total_deleted + ?
|
|
79
|
+
where singleton = 1
|
|
80
|
+
""",
|
|
81
|
+
(now or time.time(), deleted, deleted),
|
|
82
|
+
)
|
|
83
|
+
self._database.commit()
|
|
84
|
+
return deleted
|
|
85
|
+
|
|
86
|
+
def health(self) -> dict[str, int | float | str | None]:
|
|
87
|
+
with self._lock:
|
|
88
|
+
return self._health()
|
|
89
|
+
|
|
90
|
+
def _health(self) -> dict[str, int | float | str | None]:
|
|
91
|
+
row = self._database.execute(
|
|
92
|
+
"""
|
|
93
|
+
select count(*) as retained,
|
|
94
|
+
sum(case when decision = 'reject' then 1 else 0 end) as rejected,
|
|
95
|
+
sum(case when decision = 'redact' then 1 else 0 end) as redacted,
|
|
96
|
+
max(occurred_at) as last_attempt_at
|
|
97
|
+
from provider_attempt_audit
|
|
98
|
+
"""
|
|
99
|
+
).fetchone()
|
|
100
|
+
retention = self._database.execute(
|
|
101
|
+
"select * from provider_audit_retention where singleton = 1"
|
|
102
|
+
).fetchone()
|
|
103
|
+
return {
|
|
104
|
+
"status": "healthy",
|
|
105
|
+
"retentionDays": self.retention_days,
|
|
106
|
+
"retainedRecords": int(row[0] or 0),
|
|
107
|
+
"rejectedAttempts": int(row[1] or 0),
|
|
108
|
+
"redactedAttempts": int(row[2] or 0),
|
|
109
|
+
"lastAttemptAt": row[3],
|
|
110
|
+
"lastPurgeAt": retention[1],
|
|
111
|
+
"lastDeletedRecords": int(retention[2]),
|
|
112
|
+
"totalDeletedRecords": int(retention[3]),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def close(self) -> None:
|
|
116
|
+
with self._lock:
|
|
117
|
+
self._database.close()
|
|
118
|
+
|
|
119
|
+
def _initialize(self) -> None:
|
|
120
|
+
self._database.executescript(
|
|
121
|
+
"""
|
|
122
|
+
create table if not exists provider_attempt_audit (
|
|
123
|
+
event_id text primary key,
|
|
124
|
+
deployment_key text not null,
|
|
125
|
+
operation_key text not null,
|
|
126
|
+
run_id text not null,
|
|
127
|
+
attempt integer not null,
|
|
128
|
+
classifications text not null,
|
|
129
|
+
sent_bytes integer not null,
|
|
130
|
+
decision text not null check(decision in ('allow', 'redact', 'reject')),
|
|
131
|
+
occurred_at real not null,
|
|
132
|
+
unique(run_id, attempt)
|
|
133
|
+
);
|
|
134
|
+
create table if not exists provider_audit_retention (
|
|
135
|
+
singleton integer primary key check(singleton = 1),
|
|
136
|
+
last_purge_at real,
|
|
137
|
+
last_deleted integer not null,
|
|
138
|
+
total_deleted integer not null
|
|
139
|
+
);
|
|
140
|
+
insert or ignore into provider_audit_retention
|
|
141
|
+
values (1, null, 0, 0);
|
|
142
|
+
"""
|
|
143
|
+
)
|
|
144
|
+
self._database.commit()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
from .contracts import (
|
|
10
|
+
JsonValue,
|
|
11
|
+
ProviderDataField,
|
|
12
|
+
ProviderDataPolicy,
|
|
13
|
+
RuntimeToolDefinition,
|
|
14
|
+
)
|
|
15
|
+
from .engine import SafeEngineError
|
|
16
|
+
from .provider_audit import ProviderAttemptAudit, ProviderAuditStore
|
|
17
|
+
|
|
18
|
+
_DLP_PATTERNS = (
|
|
19
|
+
re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]{8,}"),
|
|
20
|
+
re.compile(r"(?i)\b(?:api[_-]?key|secret|password|token)\s*[:=]\s*\S+"),
|
|
21
|
+
re.compile(r"\b1[3-9]\d{9}\b"),
|
|
22
|
+
re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.I),
|
|
23
|
+
re.compile(r"\b\d{17}[\dXx]\b"),
|
|
24
|
+
re.compile(r"\b(?:\d[ -]?){16,30}\b"),
|
|
25
|
+
)
|
|
26
|
+
_REDACTED = "[REDACTED]"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class GovernedAttempt:
|
|
31
|
+
messages: list[dict[str, JsonValue]]
|
|
32
|
+
tools: tuple[RuntimeToolDefinition, ...]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ProviderRequestGovernance:
|
|
36
|
+
def __init__(self, audit: ProviderAuditStore) -> None:
|
|
37
|
+
self.audit = audit
|
|
38
|
+
|
|
39
|
+
def authorize(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
deployment_key: str,
|
|
43
|
+
operation_key: str,
|
|
44
|
+
run_id: str,
|
|
45
|
+
attempt: int,
|
|
46
|
+
messages: list[dict[str, JsonValue]],
|
|
47
|
+
tools: tuple[RuntimeToolDefinition, ...],
|
|
48
|
+
policy: ProviderDataPolicy,
|
|
49
|
+
input_value: JsonValue = None,
|
|
50
|
+
measure: Callable[
|
|
51
|
+
[list[dict[str, JsonValue]], tuple[RuntimeToolDefinition, ...]], int
|
|
52
|
+
]
|
|
53
|
+
| None = None,
|
|
54
|
+
) -> GovernedAttempt:
|
|
55
|
+
rules = {rule.field: rule for rule in policy.envelope_fields}
|
|
56
|
+
classified: set[str] = set()
|
|
57
|
+
classified.update(input_classifications(input_value, policy))
|
|
58
|
+
redacted = False
|
|
59
|
+
governed_messages: list[dict[str, JsonValue]] = []
|
|
60
|
+
try:
|
|
61
|
+
for message in messages:
|
|
62
|
+
field = field_for_message(message)
|
|
63
|
+
rule = rules.get(field)
|
|
64
|
+
if rule is None:
|
|
65
|
+
raise GovernanceDenied(f"Provider 未授权发送字段:{field}")
|
|
66
|
+
classified.add(rule.classification)
|
|
67
|
+
value, changed = govern_value(message, rule.dlp_action)
|
|
68
|
+
redacted = redacted or changed
|
|
69
|
+
governed_messages.append(cast(dict[str, JsonValue], value))
|
|
70
|
+
governed_tools = tools
|
|
71
|
+
if tools:
|
|
72
|
+
rule = rules.get("tool-definitions")
|
|
73
|
+
if rule is None:
|
|
74
|
+
raise GovernanceDenied("Provider 未授权发送字段:tool-definitions")
|
|
75
|
+
classified.add(rule.classification)
|
|
76
|
+
raw_tools = [tool.model_dump(by_alias=True) for tool in tools]
|
|
77
|
+
governed, changed = govern_value(raw_tools, rule.dlp_action)
|
|
78
|
+
redacted = redacted or changed
|
|
79
|
+
governed_tools = tuple(
|
|
80
|
+
RuntimeToolDefinition.model_validate(item)
|
|
81
|
+
for item in cast(list[object], governed)
|
|
82
|
+
)
|
|
83
|
+
sent_bytes = (measure or payload_size)(
|
|
84
|
+
governed_messages,
|
|
85
|
+
governed_tools,
|
|
86
|
+
)
|
|
87
|
+
decision = "redact" if redacted else "allow"
|
|
88
|
+
self._record(
|
|
89
|
+
deployment_key,
|
|
90
|
+
operation_key,
|
|
91
|
+
run_id,
|
|
92
|
+
attempt,
|
|
93
|
+
classified,
|
|
94
|
+
sent_bytes,
|
|
95
|
+
decision,
|
|
96
|
+
)
|
|
97
|
+
return GovernedAttempt(governed_messages, governed_tools)
|
|
98
|
+
except GovernanceDenied:
|
|
99
|
+
self._record(
|
|
100
|
+
deployment_key,
|
|
101
|
+
operation_key,
|
|
102
|
+
run_id,
|
|
103
|
+
attempt,
|
|
104
|
+
classified,
|
|
105
|
+
0,
|
|
106
|
+
"reject",
|
|
107
|
+
)
|
|
108
|
+
raise SafeEngineError(
|
|
109
|
+
"FORBIDDEN",
|
|
110
|
+
"Provider 请求被字段治理策略拒绝",
|
|
111
|
+
retryable=False,
|
|
112
|
+
) from None
|
|
113
|
+
|
|
114
|
+
def health(self) -> dict[str, int | float | str | None]:
|
|
115
|
+
return self.audit.health()
|
|
116
|
+
|
|
117
|
+
def _record(
|
|
118
|
+
self,
|
|
119
|
+
deployment_key: str,
|
|
120
|
+
operation_key: str,
|
|
121
|
+
run_id: str,
|
|
122
|
+
attempt: int,
|
|
123
|
+
classified: set[str],
|
|
124
|
+
sent_bytes: int,
|
|
125
|
+
decision: str,
|
|
126
|
+
) -> None:
|
|
127
|
+
try:
|
|
128
|
+
self.audit.append(
|
|
129
|
+
ProviderAttemptAudit(
|
|
130
|
+
deployment_key,
|
|
131
|
+
operation_key,
|
|
132
|
+
run_id,
|
|
133
|
+
attempt,
|
|
134
|
+
tuple(sorted(classified)),
|
|
135
|
+
sent_bytes,
|
|
136
|
+
decision,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
except Exception:
|
|
140
|
+
raise SafeEngineError(
|
|
141
|
+
"FORBIDDEN",
|
|
142
|
+
"Provider 发送审计不可用",
|
|
143
|
+
retryable=False,
|
|
144
|
+
) from None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class GovernanceDenied(Exception):
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def field_for_message(message: dict[str, JsonValue]) -> ProviderDataField:
|
|
152
|
+
role = message.get("role")
|
|
153
|
+
if role == "system":
|
|
154
|
+
return "instructions"
|
|
155
|
+
if role == "user":
|
|
156
|
+
return "user-prompt"
|
|
157
|
+
if role == "assistant":
|
|
158
|
+
return "provider-output"
|
|
159
|
+
if role == "tool":
|
|
160
|
+
return "tool-results"
|
|
161
|
+
raise GovernanceDenied("Provider 消息角色未知")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def input_classifications(
|
|
165
|
+
value: JsonValue,
|
|
166
|
+
policy: ProviderDataPolicy,
|
|
167
|
+
) -> set[str]:
|
|
168
|
+
result: set[str] = set()
|
|
169
|
+
for rule in policy.input_fields:
|
|
170
|
+
current = value
|
|
171
|
+
found = True
|
|
172
|
+
for encoded in rule.json_pointer.removeprefix("/").split("/"):
|
|
173
|
+
segment = encoded.replace("~1", "/").replace("~0", "~")
|
|
174
|
+
if not isinstance(current, dict) or segment not in current:
|
|
175
|
+
found = False
|
|
176
|
+
break
|
|
177
|
+
current = current[segment]
|
|
178
|
+
if found:
|
|
179
|
+
result.add(rule.classification)
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def govern_value(value: object, action: str) -> tuple[object, bool]:
|
|
184
|
+
if isinstance(value, str):
|
|
185
|
+
detected = any(pattern.search(value) for pattern in _DLP_PATTERNS)
|
|
186
|
+
if detected and action == "reject":
|
|
187
|
+
raise GovernanceDenied("DLP reject")
|
|
188
|
+
if detected:
|
|
189
|
+
result = value
|
|
190
|
+
for pattern in _DLP_PATTERNS:
|
|
191
|
+
result = pattern.sub(_REDACTED, result)
|
|
192
|
+
return result, result != value
|
|
193
|
+
return value, False
|
|
194
|
+
if isinstance(value, list):
|
|
195
|
+
changed = False
|
|
196
|
+
items: list[object] = []
|
|
197
|
+
for item in value:
|
|
198
|
+
governed, item_changed = govern_value(item, action)
|
|
199
|
+
items.append(governed)
|
|
200
|
+
changed = changed or item_changed
|
|
201
|
+
return items, changed
|
|
202
|
+
if isinstance(value, dict):
|
|
203
|
+
changed = False
|
|
204
|
+
governed_record: dict[str, object] = {}
|
|
205
|
+
for key, item in value.items():
|
|
206
|
+
governed, item_changed = govern_value(item, action)
|
|
207
|
+
governed_record[key] = governed
|
|
208
|
+
changed = changed or item_changed
|
|
209
|
+
return governed_record, changed
|
|
210
|
+
return value, False
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def payload_size(
|
|
214
|
+
messages: list[dict[str, JsonValue]],
|
|
215
|
+
tools: tuple[RuntimeToolDefinition, ...],
|
|
216
|
+
) -> int:
|
|
217
|
+
payload = {
|
|
218
|
+
"messages": messages,
|
|
219
|
+
"tools": [tool.model_dump(by_alias=True) for tool in tools],
|
|
220
|
+
}
|
|
221
|
+
return len(
|
|
222
|
+
json.dumps(
|
|
223
|
+
payload,
|
|
224
|
+
ensure_ascii=False,
|
|
225
|
+
separators=(",", ":"),
|
|
226
|
+
).encode()
|
|
227
|
+
)
|
|
@@ -86,6 +86,7 @@ class AgentRunService:
|
|
|
86
86
|
allowed_tools=tuple(invocation.allowed_tools),
|
|
87
87
|
limits=invocation.limits,
|
|
88
88
|
provider_egress=invocation.provider_egress,
|
|
89
|
+
provider_data_policy=invocation.provider_data_policy,
|
|
89
90
|
tool_network_policy=invocation.tool_network_policy,
|
|
90
91
|
execution_grant=invocation.execution_grant,
|
|
91
92
|
tool_host_url=invocation.tool_host_url,
|