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
|
@@ -1,39 +1,214 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
4
5
|
|
|
6
|
+
from google.adk.agents import LlmAgent
|
|
7
|
+
from google.adk.agents.run_config import RunConfig, StreamingMode
|
|
8
|
+
from google.adk.events import Event
|
|
9
|
+
from google.adk.runners import Runner
|
|
10
|
+
from google.adk.sessions import InMemorySessionService
|
|
11
|
+
from google.genai import types
|
|
12
|
+
|
|
13
|
+
from .adk_failure import AdkFailureState
|
|
14
|
+
from .adk_model import GovernedOpenAiLlm
|
|
15
|
+
from .adk_tools import GovernedTool
|
|
16
|
+
from .contracts import JsonValue
|
|
5
17
|
from .engine import (
|
|
6
18
|
AgentEngineAdapter,
|
|
19
|
+
EngineCompleted,
|
|
7
20
|
EngineRunRequest,
|
|
8
21
|
EngineSignal,
|
|
22
|
+
OutputDelta,
|
|
9
23
|
SafeEngineError,
|
|
24
|
+
ToolCompleted,
|
|
25
|
+
ToolRequested,
|
|
26
|
+
UsageReported,
|
|
10
27
|
)
|
|
28
|
+
from .openai_engine import cancelled, validate_runtime_request
|
|
29
|
+
from .openai_provider import OpenAiCompatibleProvider
|
|
30
|
+
from .provider_governance import ProviderRequestGovernance
|
|
31
|
+
from .tool_host import ToolHostClient
|
|
11
32
|
|
|
12
33
|
|
|
13
34
|
class AdkEngineAdapter(AgentEngineAdapter):
|
|
14
|
-
"""Google ADK implementation
|
|
15
|
-
|
|
16
|
-
Agent objects remain opaque and internal. A later slice will create ADK Runners,
|
|
17
|
-
consume ADK Events, and translate them into EngineSignal values at this seam.
|
|
18
|
-
"""
|
|
35
|
+
"""Google ADK Runner implementation behind the stable engine seam."""
|
|
19
36
|
|
|
20
|
-
def __init__(
|
|
21
|
-
self
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
provider: OpenAiCompatibleProvider | None = None,
|
|
40
|
+
tool_host: ToolHostClient | None = None,
|
|
41
|
+
governance: ProviderRequestGovernance | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self._provider = provider
|
|
44
|
+
self._tool_host = tool_host
|
|
45
|
+
self._governance = governance
|
|
46
|
+
self._cancellations: dict[str, asyncio.Event] = {}
|
|
22
47
|
|
|
23
48
|
def supports(self, operation_key: str) -> bool:
|
|
24
|
-
return operation_key
|
|
49
|
+
return bool(operation_key.strip())
|
|
25
50
|
|
|
26
51
|
async def execute(
|
|
27
52
|
self,
|
|
28
53
|
request: EngineRunRequest,
|
|
29
54
|
) -> AsyncIterator[EngineSignal]:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
provider, tool_host, governance = self._configured()
|
|
56
|
+
validate_runtime_request(request, provider.config.deployment_key)
|
|
57
|
+
cancellation = self._cancellations.setdefault(
|
|
58
|
+
request.run_id,
|
|
59
|
+
asyncio.Event(),
|
|
35
60
|
)
|
|
36
|
-
|
|
61
|
+
output = ""
|
|
62
|
+
output_bytes = 0
|
|
63
|
+
failures = AdkFailureState()
|
|
64
|
+
event_failure: SafeEngineError | None = None
|
|
65
|
+
try:
|
|
66
|
+
session_service = InMemorySessionService() # type: ignore[no-untyped-call]
|
|
67
|
+
await session_service.create_session(
|
|
68
|
+
app_name="southwind_runtime",
|
|
69
|
+
user_id="platform",
|
|
70
|
+
session_id=request.run_id,
|
|
71
|
+
)
|
|
72
|
+
runner = Runner(
|
|
73
|
+
app_name="southwind_runtime",
|
|
74
|
+
agent=LlmAgent(
|
|
75
|
+
name="southwind_operation",
|
|
76
|
+
model=GovernedOpenAiLlm(
|
|
77
|
+
request,
|
|
78
|
+
provider,
|
|
79
|
+
governance,
|
|
80
|
+
cancellation,
|
|
81
|
+
failures,
|
|
82
|
+
),
|
|
83
|
+
instruction=request.instructions,
|
|
84
|
+
tools=[
|
|
85
|
+
GovernedTool(
|
|
86
|
+
tool,
|
|
87
|
+
request,
|
|
88
|
+
tool_host,
|
|
89
|
+
cancellation,
|
|
90
|
+
failures,
|
|
91
|
+
)
|
|
92
|
+
for tool in request.allowed_tools
|
|
93
|
+
],
|
|
94
|
+
),
|
|
95
|
+
session_service=session_service,
|
|
96
|
+
)
|
|
97
|
+
async for event in runner.run_async(
|
|
98
|
+
user_id="platform",
|
|
99
|
+
session_id=request.run_id,
|
|
100
|
+
new_message=types.Content(
|
|
101
|
+
role="user",
|
|
102
|
+
parts=[types.Part(text=request.user_prompt)],
|
|
103
|
+
),
|
|
104
|
+
run_config=RunConfig(
|
|
105
|
+
streaming_mode=StreamingMode.SSE,
|
|
106
|
+
max_llm_calls=provider.config.max_tool_rounds,
|
|
107
|
+
),
|
|
108
|
+
):
|
|
109
|
+
cancelled(cancellation)
|
|
110
|
+
if event.error_code:
|
|
111
|
+
event_failure = failures.failure or SafeEngineError(
|
|
112
|
+
"PROVIDER_UNAVAILABLE",
|
|
113
|
+
"模型服务暂不可用",
|
|
114
|
+
retryable=True,
|
|
115
|
+
)
|
|
116
|
+
continue
|
|
117
|
+
async for signal in signals_for_event(event):
|
|
118
|
+
if isinstance(signal, OutputDelta):
|
|
119
|
+
output_bytes += len(signal.text.encode())
|
|
120
|
+
if output_bytes > request.limits.max_output_bytes:
|
|
121
|
+
raise SafeEngineError(
|
|
122
|
+
"BUDGET_EXCEEDED",
|
|
123
|
+
"Agent 输出超过 Operation 上限",
|
|
124
|
+
retryable=False,
|
|
125
|
+
)
|
|
126
|
+
output += signal.text
|
|
127
|
+
yield signal
|
|
128
|
+
if event_failure is not None:
|
|
129
|
+
raise event_failure
|
|
130
|
+
cancelled(cancellation)
|
|
131
|
+
yield EngineCompleted({"text": output})
|
|
132
|
+
except SafeEngineError:
|
|
133
|
+
raise
|
|
134
|
+
except asyncio.CancelledError:
|
|
135
|
+
raise SafeEngineError(
|
|
136
|
+
"RUN_CANCELLED",
|
|
137
|
+
"Agent Run 已取消",
|
|
138
|
+
retryable=False,
|
|
139
|
+
) from None
|
|
140
|
+
except Exception:
|
|
141
|
+
raise SafeEngineError(
|
|
142
|
+
"RUNTIME_UNAVAILABLE",
|
|
143
|
+
"Agent Runtime 执行失败",
|
|
144
|
+
retryable=True,
|
|
145
|
+
) from None
|
|
146
|
+
finally:
|
|
147
|
+
self._cancellations.pop(request.run_id, None)
|
|
37
148
|
|
|
38
149
|
async def cancel(self, run_id: str) -> None:
|
|
39
|
-
|
|
150
|
+
cancellation = self._cancellations.get(run_id)
|
|
151
|
+
if cancellation is not None:
|
|
152
|
+
cancellation.set()
|
|
153
|
+
|
|
154
|
+
async def health(self) -> None:
|
|
155
|
+
provider, _, _ = self._configured()
|
|
156
|
+
await provider.health()
|
|
157
|
+
|
|
158
|
+
def governance_health(self) -> dict[str, int | float | str | None]:
|
|
159
|
+
_, _, governance = self._configured()
|
|
160
|
+
return governance.health()
|
|
161
|
+
|
|
162
|
+
def _configured(
|
|
163
|
+
self,
|
|
164
|
+
) -> tuple[
|
|
165
|
+
OpenAiCompatibleProvider,
|
|
166
|
+
ToolHostClient,
|
|
167
|
+
ProviderRequestGovernance,
|
|
168
|
+
]:
|
|
169
|
+
if not self._provider or not self._tool_host or not self._governance:
|
|
170
|
+
raise SafeEngineError(
|
|
171
|
+
"RUNTIME_UNAVAILABLE",
|
|
172
|
+
"Agent Runtime 尚未配置",
|
|
173
|
+
retryable=True,
|
|
174
|
+
)
|
|
175
|
+
return self._provider, self._tool_host, self._governance
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def signals_for_event(event: Event) -> AsyncIterator[EngineSignal]:
|
|
179
|
+
for call in event.get_function_calls():
|
|
180
|
+
if not call.id or not call.name:
|
|
181
|
+
raise SafeEngineError(
|
|
182
|
+
"TOOL_DENIED",
|
|
183
|
+
"模型请求了无效 Tool",
|
|
184
|
+
retryable=False,
|
|
185
|
+
)
|
|
186
|
+
yield ToolRequested(call.id, call.name)
|
|
187
|
+
for response in event.get_function_responses():
|
|
188
|
+
if not response.id:
|
|
189
|
+
raise SafeEngineError(
|
|
190
|
+
"TOOL_DENIED",
|
|
191
|
+
"Agent Tool 响应缺少稳定标识",
|
|
192
|
+
retryable=False,
|
|
193
|
+
)
|
|
194
|
+
yield ToolCompleted(response.id)
|
|
195
|
+
if event.partial and event.content:
|
|
196
|
+
for part in event.content.parts or []:
|
|
197
|
+
if part.text and not part.thought:
|
|
198
|
+
yield OutputDelta(part.text)
|
|
199
|
+
usage = southwind_usage(event)
|
|
200
|
+
if usage is not None:
|
|
201
|
+
yield UsageReported(usage)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def southwind_usage(event: Event) -> dict[str, JsonValue] | None:
|
|
205
|
+
metadata = event.custom_metadata or {}
|
|
206
|
+
value = metadata.get("southwindUsage")
|
|
207
|
+
if not isinstance(value, dict):
|
|
208
|
+
return None
|
|
209
|
+
return {
|
|
210
|
+
key: item
|
|
211
|
+
for key, item in value.items()
|
|
212
|
+
if isinstance(key, str)
|
|
213
|
+
and (item is None or isinstance(item, str | int | float | bool | list | dict))
|
|
214
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .engine import SafeEngineError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AdkFailureState:
|
|
10
|
+
failure: SafeEngineError | None = None
|
|
11
|
+
|
|
12
|
+
def capture(self, error: SafeEngineError) -> None:
|
|
13
|
+
self.failure = error
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from collections.abc import AsyncGenerator
|
|
6
|
+
|
|
7
|
+
from google.adk.models.base_llm import BaseLlm
|
|
8
|
+
from google.adk.models.llm_request import LlmRequest
|
|
9
|
+
from google.adk.models.llm_response import LlmResponse
|
|
10
|
+
from google.genai import types
|
|
11
|
+
from pydantic import PrivateAttr
|
|
12
|
+
|
|
13
|
+
from .adk_failure import AdkFailureState
|
|
14
|
+
from .contracts import JsonValue
|
|
15
|
+
from .engine import EngineRunRequest, SafeEngineError
|
|
16
|
+
from .openai_engine import cancelled, parse_arguments
|
|
17
|
+
from .openai_provider import OpenAiCompatibleProvider, ProviderTurn
|
|
18
|
+
from .provider_governance import ProviderRequestGovernance
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GovernedOpenAiLlm(BaseLlm):
|
|
22
|
+
"""ADK model bridge that preserves Southwind's provider governance."""
|
|
23
|
+
|
|
24
|
+
_request: EngineRunRequest = PrivateAttr()
|
|
25
|
+
_provider: OpenAiCompatibleProvider = PrivateAttr()
|
|
26
|
+
_governance: ProviderRequestGovernance = PrivateAttr()
|
|
27
|
+
_cancellation: asyncio.Event = PrivateAttr()
|
|
28
|
+
_failures: AdkFailureState = PrivateAttr()
|
|
29
|
+
_attempt: int = PrivateAttr(default=0)
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
request: EngineRunRequest,
|
|
34
|
+
provider: OpenAiCompatibleProvider,
|
|
35
|
+
governance: ProviderRequestGovernance,
|
|
36
|
+
cancellation: asyncio.Event,
|
|
37
|
+
failures: AdkFailureState,
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(model=provider.config.model)
|
|
40
|
+
self._request = request
|
|
41
|
+
self._provider = provider
|
|
42
|
+
self._governance = governance
|
|
43
|
+
self._cancellation = cancellation
|
|
44
|
+
self._failures = failures
|
|
45
|
+
|
|
46
|
+
async def generate_content_async(
|
|
47
|
+
self,
|
|
48
|
+
llm_request: LlmRequest,
|
|
49
|
+
stream: bool = False,
|
|
50
|
+
) -> AsyncGenerator[LlmResponse, None]:
|
|
51
|
+
del stream
|
|
52
|
+
try:
|
|
53
|
+
cancelled(self._cancellation)
|
|
54
|
+
self._attempt += 1
|
|
55
|
+
if self._attempt > self._provider.config.max_tool_rounds:
|
|
56
|
+
raise SafeEngineError(
|
|
57
|
+
"BUDGET_EXCEEDED",
|
|
58
|
+
"Agent Tool 调用轮次超过上限",
|
|
59
|
+
retryable=False,
|
|
60
|
+
)
|
|
61
|
+
messages = adk_messages(llm_request, self._request.instructions)
|
|
62
|
+
governed = self._governance.authorize(
|
|
63
|
+
deployment_key=self._provider.config.deployment_key,
|
|
64
|
+
operation_key=self._request.operation_key,
|
|
65
|
+
run_id=self._request.run_id,
|
|
66
|
+
attempt=self._attempt,
|
|
67
|
+
messages=messages,
|
|
68
|
+
tools=self._request.allowed_tools,
|
|
69
|
+
policy=self._request.provider_data_policy,
|
|
70
|
+
input_value=self._request.input,
|
|
71
|
+
measure=lambda values, tools: len(
|
|
72
|
+
self._provider.request_body(values, tools)
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
turn = await self._provider.complete_turn(
|
|
76
|
+
governed.messages,
|
|
77
|
+
governed.tools,
|
|
78
|
+
)
|
|
79
|
+
cancelled(self._cancellation)
|
|
80
|
+
validate_tool_calls(
|
|
81
|
+
turn,
|
|
82
|
+
{tool.key for tool in self._request.allowed_tools},
|
|
83
|
+
)
|
|
84
|
+
async for response in responses_for_turn(turn):
|
|
85
|
+
yield response
|
|
86
|
+
except SafeEngineError as error:
|
|
87
|
+
self._failures.capture(error)
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def adk_messages(
|
|
92
|
+
request: LlmRequest,
|
|
93
|
+
instructions: str,
|
|
94
|
+
) -> list[dict[str, JsonValue]]:
|
|
95
|
+
result: list[dict[str, JsonValue]] = [{"role": "system", "content": instructions}]
|
|
96
|
+
for content in request.contents:
|
|
97
|
+
parts = content.parts or []
|
|
98
|
+
calls = [part.function_call for part in parts if part.function_call]
|
|
99
|
+
responses = [part.function_response for part in parts if part.function_response]
|
|
100
|
+
text = "".join(
|
|
101
|
+
part.text or "" for part in parts if not part.thought and part.text
|
|
102
|
+
)
|
|
103
|
+
if calls:
|
|
104
|
+
result.append(
|
|
105
|
+
{
|
|
106
|
+
"role": "assistant",
|
|
107
|
+
"content": text or None,
|
|
108
|
+
"tool_calls": [
|
|
109
|
+
{
|
|
110
|
+
"id": call.id or "",
|
|
111
|
+
"type": "function",
|
|
112
|
+
"function": {
|
|
113
|
+
"name": call.name or "",
|
|
114
|
+
"arguments": json.dumps(
|
|
115
|
+
call.args or {},
|
|
116
|
+
ensure_ascii=False,
|
|
117
|
+
separators=(",", ":"),
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
for call in calls
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
elif responses:
|
|
126
|
+
result.extend(tool_response_messages(responses))
|
|
127
|
+
elif text:
|
|
128
|
+
result.append(
|
|
129
|
+
{
|
|
130
|
+
"role": "assistant" if content.role == "model" else "user",
|
|
131
|
+
"content": text,
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def tool_response_messages(
|
|
138
|
+
responses: list[types.FunctionResponse],
|
|
139
|
+
) -> list[dict[str, JsonValue]]:
|
|
140
|
+
return [
|
|
141
|
+
{
|
|
142
|
+
"role": "tool",
|
|
143
|
+
"tool_call_id": response.id or "",
|
|
144
|
+
"content": json.dumps(
|
|
145
|
+
response.response or {},
|
|
146
|
+
ensure_ascii=False,
|
|
147
|
+
separators=(",", ":"),
|
|
148
|
+
),
|
|
149
|
+
}
|
|
150
|
+
for response in responses
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def validate_tool_calls(turn: ProviderTurn, allowed: set[str]) -> None:
|
|
155
|
+
for call in turn.tool_calls:
|
|
156
|
+
if not call.call_id or not call.name or call.name not in allowed:
|
|
157
|
+
raise SafeEngineError(
|
|
158
|
+
"TOOL_DENIED",
|
|
159
|
+
"模型请求了未授权 Tool",
|
|
160
|
+
retryable=False,
|
|
161
|
+
)
|
|
162
|
+
parse_arguments(call.arguments)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def responses_for_turn(
|
|
166
|
+
turn: ProviderTurn,
|
|
167
|
+
) -> AsyncGenerator[LlmResponse, None]:
|
|
168
|
+
usage = turn.usage or {"source": "unknown"}
|
|
169
|
+
metadata = {"southwindUsage": usage}
|
|
170
|
+
if turn.tool_calls:
|
|
171
|
+
yield LlmResponse(
|
|
172
|
+
content=types.Content(
|
|
173
|
+
role="model",
|
|
174
|
+
parts=[
|
|
175
|
+
types.Part(
|
|
176
|
+
function_call=types.FunctionCall(
|
|
177
|
+
id=call.call_id,
|
|
178
|
+
name=call.name,
|
|
179
|
+
args=parse_arguments(call.arguments),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
for call in turn.tool_calls
|
|
183
|
+
],
|
|
184
|
+
),
|
|
185
|
+
partial=False,
|
|
186
|
+
custom_metadata=metadata,
|
|
187
|
+
usage_metadata=adk_usage(usage),
|
|
188
|
+
)
|
|
189
|
+
return
|
|
190
|
+
for delta in turn.text_deltas:
|
|
191
|
+
yield LlmResponse(
|
|
192
|
+
content=types.Content(
|
|
193
|
+
role="model",
|
|
194
|
+
parts=[types.Part(text=delta)],
|
|
195
|
+
),
|
|
196
|
+
partial=True,
|
|
197
|
+
)
|
|
198
|
+
yield LlmResponse(
|
|
199
|
+
content=types.Content(
|
|
200
|
+
role="model",
|
|
201
|
+
parts=[types.Part(text=turn.text)],
|
|
202
|
+
),
|
|
203
|
+
partial=False,
|
|
204
|
+
custom_metadata=metadata,
|
|
205
|
+
usage_metadata=adk_usage(usage),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def adk_usage(
|
|
210
|
+
usage: dict[str, JsonValue],
|
|
211
|
+
) -> types.GenerateContentResponseUsageMetadata | None:
|
|
212
|
+
input_tokens = usage.get("inputTokens")
|
|
213
|
+
output_tokens = usage.get("outputTokens")
|
|
214
|
+
total_tokens = usage.get("totalTokens")
|
|
215
|
+
if not any(
|
|
216
|
+
isinstance(value, int) for value in (input_tokens, output_tokens, total_tokens)
|
|
217
|
+
):
|
|
218
|
+
return None
|
|
219
|
+
return types.GenerateContentResponseUsageMetadata(
|
|
220
|
+
prompt_token_count=input_tokens if isinstance(input_tokens, int) else None,
|
|
221
|
+
candidates_token_count=(
|
|
222
|
+
output_tokens if isinstance(output_tokens, int) else None
|
|
223
|
+
),
|
|
224
|
+
total_token_count=total_tokens if isinstance(total_tokens, int) else None,
|
|
225
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from google.adk.tools.base_tool import BaseTool
|
|
7
|
+
from google.adk.tools.tool_context import ToolContext
|
|
8
|
+
from google.genai import types
|
|
9
|
+
|
|
10
|
+
from .adk_failure import AdkFailureState
|
|
11
|
+
from .contracts import JsonValue, RuntimeToolDefinition
|
|
12
|
+
from .engine import EngineRunRequest, SafeEngineError
|
|
13
|
+
from .openai_engine import cancelled
|
|
14
|
+
from .tool_host import ToolHostClient, ToolInvocation
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GovernedTool(BaseTool):
|
|
18
|
+
"""ADK declaration whose only execution path is the governed Tool Host."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
definition: RuntimeToolDefinition,
|
|
23
|
+
request: EngineRunRequest,
|
|
24
|
+
tool_host: ToolHostClient,
|
|
25
|
+
cancellation: asyncio.Event,
|
|
26
|
+
failures: AdkFailureState,
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__(
|
|
29
|
+
name=definition.key,
|
|
30
|
+
description=definition.description,
|
|
31
|
+
)
|
|
32
|
+
self._request = request
|
|
33
|
+
self._tool_host = tool_host
|
|
34
|
+
self._cancellation = cancellation
|
|
35
|
+
self._failures = failures
|
|
36
|
+
|
|
37
|
+
def _get_declaration(self) -> types.FunctionDeclaration:
|
|
38
|
+
return types.FunctionDeclaration(
|
|
39
|
+
name=self.name,
|
|
40
|
+
description=self.description,
|
|
41
|
+
parameters_json_schema={
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": True,
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
async def run_async(
|
|
48
|
+
self,
|
|
49
|
+
*,
|
|
50
|
+
args: dict[str, Any],
|
|
51
|
+
tool_context: ToolContext,
|
|
52
|
+
) -> JsonValue:
|
|
53
|
+
try:
|
|
54
|
+
cancelled(self._cancellation)
|
|
55
|
+
call_id = tool_context.function_call_id
|
|
56
|
+
if not call_id:
|
|
57
|
+
raise SafeEngineError(
|
|
58
|
+
"TOOL_DENIED",
|
|
59
|
+
"Agent Tool Call 缺少稳定标识",
|
|
60
|
+
retryable=False,
|
|
61
|
+
)
|
|
62
|
+
result = await self._tool_host.execute(
|
|
63
|
+
self._request.tool_host_url,
|
|
64
|
+
ToolInvocation(
|
|
65
|
+
self._request.run_id,
|
|
66
|
+
call_id,
|
|
67
|
+
self.name,
|
|
68
|
+
cast(dict[str, JsonValue], args),
|
|
69
|
+
self._request.execution_grant,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
cancelled(self._cancellation)
|
|
73
|
+
return result
|
|
74
|
+
except SafeEngineError as error:
|
|
75
|
+
self._failures.capture(error)
|
|
76
|
+
raise
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import hmac
|
|
4
4
|
import os
|
|
5
5
|
import uuid
|
|
6
|
-
from typing import Annotated
|
|
6
|
+
from typing import Annotated, cast
|
|
7
7
|
|
|
8
8
|
from fastapi import FastAPI, Header, Query, Request
|
|
9
9
|
from fastapi.exceptions import RequestValidationError
|
|
@@ -21,8 +21,9 @@ from .contracts import (
|
|
|
21
21
|
StartRunResponse,
|
|
22
22
|
)
|
|
23
23
|
from .engine import AgentEngineAdapter
|
|
24
|
-
from .openai_engine import OpenAiCompatibleEngineAdapter
|
|
25
24
|
from .openai_provider import OpenAiCompatibleProvider
|
|
25
|
+
from .provider_audit import ProviderAuditStore
|
|
26
|
+
from .provider_governance import ProviderRequestGovernance
|
|
26
27
|
from .service import (
|
|
27
28
|
AgentRunService,
|
|
28
29
|
IdempotencyConflictError,
|
|
@@ -71,6 +72,24 @@ def create_app(
|
|
|
71
72
|
True,
|
|
72
73
|
)
|
|
73
74
|
|
|
75
|
+
@application.get("/health/provider-governance", response_model=None)
|
|
76
|
+
async def provider_governance_health(
|
|
77
|
+
authorization: Annotated[str | None, Header()] = None,
|
|
78
|
+
) -> dict[str, int | float | str | None] | JSONResponse:
|
|
79
|
+
if not authorized(authorization, internal_token):
|
|
80
|
+
return safe_error(403, "FORBIDDEN", "内部调用认证失败", False)
|
|
81
|
+
health = getattr(runtime_engine, "governance_health", None)
|
|
82
|
+
if not health:
|
|
83
|
+
return safe_error(
|
|
84
|
+
503,
|
|
85
|
+
"RUNTIME_UNAVAILABLE",
|
|
86
|
+
"Provider 字段治理尚未配置",
|
|
87
|
+
True,
|
|
88
|
+
)
|
|
89
|
+
return provider_governance_snapshot(
|
|
90
|
+
cast(dict[str, int | float | str | None], health())
|
|
91
|
+
)
|
|
92
|
+
|
|
74
93
|
@application.exception_handler(OperationNotFoundError)
|
|
75
94
|
async def operation_not_found(
|
|
76
95
|
_request: Request,
|
|
@@ -209,12 +228,18 @@ def default_engine() -> AgentEngineAdapter:
|
|
|
209
228
|
internal_token = os.environ.get("AGENT_SERVER_INTERNAL_TOKEN", "").strip()
|
|
210
229
|
if not internal_token:
|
|
211
230
|
raise ValueError("Agent Server Provider 已配置但内部 Token 缺失")
|
|
212
|
-
return
|
|
231
|
+
return AdkEngineAdapter(
|
|
213
232
|
OpenAiCompatibleProvider(
|
|
214
233
|
config,
|
|
215
234
|
EnvironmentSecretResolver(os.environ),
|
|
216
235
|
),
|
|
217
236
|
ToolHostClient(internal_token),
|
|
237
|
+
ProviderRequestGovernance(
|
|
238
|
+
ProviderAuditStore(
|
|
239
|
+
required_environment("AI_PROVIDER_AUDIT_STORE_PATH"),
|
|
240
|
+
retention_days=provider_audit_retention_days(),
|
|
241
|
+
)
|
|
242
|
+
),
|
|
218
243
|
)
|
|
219
244
|
|
|
220
245
|
|
|
@@ -223,6 +248,41 @@ def default_store() -> RunStore:
|
|
|
223
248
|
return SqliteRunStore(path) if path else InMemoryRunStore()
|
|
224
249
|
|
|
225
250
|
|
|
251
|
+
def required_environment(key: str) -> str:
|
|
252
|
+
value = os.environ.get(key, "").strip()
|
|
253
|
+
if not value:
|
|
254
|
+
raise ValueError(f"Provider 已配置但缺少 {key}")
|
|
255
|
+
return value
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def provider_audit_retention_days() -> int:
|
|
259
|
+
raw = os.environ.get("AI_PROVIDER_AUDIT_RETENTION_DAYS", "90")
|
|
260
|
+
try:
|
|
261
|
+
value = int(raw)
|
|
262
|
+
except ValueError as error:
|
|
263
|
+
raise ValueError("AI_PROVIDER_AUDIT_RETENTION_DAYS 必须是整数") from error
|
|
264
|
+
if not 1 <= value <= 3650:
|
|
265
|
+
raise ValueError("AI_PROVIDER_AUDIT_RETENTION_DAYS 超出允许范围")
|
|
266
|
+
return value
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def provider_governance_snapshot(
|
|
270
|
+
value: dict[str, int | float | str | None],
|
|
271
|
+
) -> dict[str, int | float | str | None]:
|
|
272
|
+
allowed = (
|
|
273
|
+
"status",
|
|
274
|
+
"retentionDays",
|
|
275
|
+
"retainedRecords",
|
|
276
|
+
"rejectedAttempts",
|
|
277
|
+
"redactedAttempts",
|
|
278
|
+
"lastAttemptAt",
|
|
279
|
+
"lastPurgeAt",
|
|
280
|
+
"lastDeletedRecords",
|
|
281
|
+
"totalDeletedRecords",
|
|
282
|
+
)
|
|
283
|
+
return {key: value.get(key) for key in allowed}
|
|
284
|
+
|
|
285
|
+
|
|
226
286
|
def authorized(provided: str | None, expected: str | None) -> bool:
|
|
227
287
|
if expected is None:
|
|
228
288
|
return True
|
|
@@ -74,6 +74,40 @@ class ProviderEgress(CanonicalModel):
|
|
|
74
74
|
return self
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
type ProviderDataField = Literal[
|
|
78
|
+
"instructions",
|
|
79
|
+
"user-prompt",
|
|
80
|
+
"tool-definitions",
|
|
81
|
+
"provider-output",
|
|
82
|
+
"tool-results",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ProviderFieldPolicy(CanonicalModel):
|
|
87
|
+
field: ProviderDataField
|
|
88
|
+
classification: Literal["public", "internal", "sensitive"]
|
|
89
|
+
dlp_action: Literal["reject", "redact"]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ProviderInputFieldPolicy(CanonicalModel):
|
|
93
|
+
json_pointer: str = Field(pattern=r"^/[^/]+(?:/[^/]+)*$")
|
|
94
|
+
classification: Literal["public", "internal", "sensitive"]
|
|
95
|
+
dlp_action: Literal["reject", "redact"]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ProviderDataPolicy(CanonicalModel):
|
|
99
|
+
input_fields: list[ProviderInputFieldPolicy] = Field(default_factory=list)
|
|
100
|
+
envelope_fields: list[ProviderFieldPolicy] = Field(default_factory=list)
|
|
101
|
+
|
|
102
|
+
@model_validator(mode="after")
|
|
103
|
+
def validate_fields(self) -> Self:
|
|
104
|
+
names = [rule.field for rule in self.envelope_fields]
|
|
105
|
+
pointers = [rule.json_pointer for rule in self.input_fields]
|
|
106
|
+
if len(names) != len(set(names)) or len(pointers) != len(set(pointers)):
|
|
107
|
+
raise ValueError("Provider 字段发送规则重复")
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
|
|
77
111
|
class ToolNetworkPolicy(CanonicalModel):
|
|
78
112
|
mode: Literal["forbidden"] = "forbidden"
|
|
79
113
|
|
|
@@ -86,6 +120,7 @@ class StartRunRequest(CanonicalModel):
|
|
|
86
120
|
allowed_tools: list[RuntimeToolDefinition] = Field(default_factory=list)
|
|
87
121
|
limits: RuntimeLimits = Field(default_factory=RuntimeLimits)
|
|
88
122
|
provider_egress: ProviderEgress = Field(default_factory=ProviderEgress)
|
|
123
|
+
provider_data_policy: ProviderDataPolicy = Field(default_factory=ProviderDataPolicy)
|
|
89
124
|
tool_network_policy: ToolNetworkPolicy = Field(default_factory=ToolNetworkPolicy)
|
|
90
125
|
execution_grant: str = ""
|
|
91
126
|
tool_host_url: str = ""
|