create-windy 0.2.34 → 0.3.1
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 +26 -3
- package/dist/cli.js +8121 -3746
- package/package.json +3 -3
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +5 -0
- package/template/README.md +28 -4
- package/template/_gitignore +4 -0
- package/template/apps/agent-server/Dockerfile +1 -0
- package/template/apps/agent-server/README.md +18 -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 +1 -2
- package/template/apps/agent-server/tests/__init__.py +1 -0
- package/template/apps/agent-server/tests/openai_provider_test_support.py +131 -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_openai_provider.py +7 -123
- package/template/apps/server/package.json +4 -1
- package/template/apps/server/src/agent/run-facade.ts +2 -7
- 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-support.ts +94 -0
- package/template/apps/server/src/auth/routes.test.ts +51 -78
- 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 +40 -40
- 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/mcp/adapter.test.ts +3 -2
- package/template/apps/server/src/mcp/adapter.ts +9 -9
- package/template/apps/server/src/mcp/routes.ts +4 -3
- package/template/apps/server/src/module-bootstrap.ts +2 -2
- package/template/apps/server/src/module-host/request-context.ts +6 -6
- package/template/apps/server/src/module-host/storage-scope.ts +2 -9
- package/template/apps/server/src/module-host/user-directory-port.ts +2 -6
- 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/platform-http-runtime.ts +87 -0
- 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 +1 -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/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-configs.ts +319 -0
- package/template/apps/server/src/system/drizzle-system.ts +59 -311
- 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/resource-routes.ts +270 -0
- 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 +35 -267
- 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/SystemResourceLoadError.vue +21 -0
- package/template/apps/web/src/pages/system/components/SystemResourceLoadError.webtest.ts +17 -0
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +96 -108
- 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 +66 -0
- package/template/docs/architecture/ai-runtime.md +14 -5
- package/template/docs/architecture/mcp-business-capabilities.md +107 -0
- 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 +32 -6
- package/template/docs/platform/agent-tools.md +4 -0
- package/template/docs/platform/audit-reliability.md +46 -2
- 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/composition-fixtures.test-support.ts +164 -0
- package/template/packages/modules/src/composition.test.ts +5 -158
- package/template/packages/modules/src/system-api-operational.ts +234 -0
- package/template/packages/modules/src/system-api-permissions.ts +14 -181
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +1 -1
- package/template/packages/modules/src/system-module-catalog.ts +2 -1
- package/template/packages/modules/src/system-module-tasks.ts +86 -0
- package/template/packages/modules/src/system-modules-access.test.ts +60 -0
- package/template/packages/modules/src/system-modules.test.ts +13 -54
- package/template/packages/modules/src/system-modules.ts +2 -72
- package/template/packages/modules/src/system-permissions.ts +9 -5
- package/template/packages/server-sdk/package.json +1 -1
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.ts +25 -0
- package/template/packages/shared/src/mcp.test.ts +12 -0
- package/template/packages/shared/src/mcp.ts +7 -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
|
@@ -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
|
|
@@ -21,7 +21,6 @@ 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
|
|
26
25
|
from .provider_audit import ProviderAuditStore
|
|
27
26
|
from .provider_governance import ProviderRequestGovernance
|
|
@@ -229,7 +228,7 @@ def default_engine() -> AgentEngineAdapter:
|
|
|
229
228
|
internal_token = os.environ.get("AGENT_SERVER_INTERNAL_TOKEN", "").strip()
|
|
230
229
|
if not internal_token:
|
|
231
230
|
raise ValueError("Agent Server Provider 已配置但内部 Token 缺失")
|
|
232
|
-
return
|
|
231
|
+
return AdkEngineAdapter(
|
|
233
232
|
OpenAiCompatibleProvider(
|
|
234
233
|
config,
|
|
235
234
|
EnvironmentSecretResolver(os.environ),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent Server test package."""
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from southwind_agent_server.config import (
|
|
9
|
+
EnvironmentSecretResolver,
|
|
10
|
+
OpenAiCompatibleConfig,
|
|
11
|
+
SecretRef,
|
|
12
|
+
)
|
|
13
|
+
from southwind_agent_server.contracts import (
|
|
14
|
+
ProviderDataPolicy,
|
|
15
|
+
ProviderEgress,
|
|
16
|
+
ProviderFieldPolicy,
|
|
17
|
+
ProviderInputFieldPolicy,
|
|
18
|
+
RuntimeLimits,
|
|
19
|
+
RuntimeToolDefinition,
|
|
20
|
+
ToolNetworkPolicy,
|
|
21
|
+
)
|
|
22
|
+
from southwind_agent_server.engine import EngineRunRequest
|
|
23
|
+
from southwind_agent_server.openai_provider import OpenAiCompatibleProvider
|
|
24
|
+
from southwind_agent_server.provider_audit import ProviderAuditStore
|
|
25
|
+
from southwind_agent_server.provider_governance import ProviderRequestGovernance
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_provider(
|
|
29
|
+
transport: httpx.AsyncBaseTransport,
|
|
30
|
+
) -> OpenAiCompatibleProvider:
|
|
31
|
+
environment = {"QWEN_API_KEY": "server-secret"}
|
|
32
|
+
return OpenAiCompatibleProvider(
|
|
33
|
+
OpenAiCompatibleConfig(
|
|
34
|
+
base_url="https://provider.test/v1",
|
|
35
|
+
model="qwen-compatible",
|
|
36
|
+
api_key_ref=SecretRef("env", "QWEN_API_KEY"),
|
|
37
|
+
https_host_allowlist=("provider.test",),
|
|
38
|
+
),
|
|
39
|
+
EnvironmentSecretResolver(environment),
|
|
40
|
+
transport=transport,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def runtime_request(tool_key: str) -> EngineRunRequest:
|
|
45
|
+
return EngineRunRequest(
|
|
46
|
+
run_id="run_1",
|
|
47
|
+
operation_key="business.answer",
|
|
48
|
+
input={"question": "记录"},
|
|
49
|
+
instructions="回答业务问题。",
|
|
50
|
+
user_prompt="有多少记录?",
|
|
51
|
+
allowed_tools=(
|
|
52
|
+
RuntimeToolDefinition(
|
|
53
|
+
key=tool_key,
|
|
54
|
+
label="查询记录",
|
|
55
|
+
description="查询当前数据范围内的记录。",
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
limits=RuntimeLimits(
|
|
59
|
+
max_duration_seconds=60,
|
|
60
|
+
max_output_bytes=4096,
|
|
61
|
+
),
|
|
62
|
+
provider_egress=ProviderEgress(
|
|
63
|
+
mode="configured-endpoint",
|
|
64
|
+
deployment_key="ai.openai-compatible",
|
|
65
|
+
),
|
|
66
|
+
provider_data_policy=provider_data_policy(),
|
|
67
|
+
tool_network_policy=ToolNetworkPolicy(mode="forbidden"),
|
|
68
|
+
execution_grant="grant_1",
|
|
69
|
+
tool_host_url="http://server:9747",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def provider_data_policy() -> ProviderDataPolicy:
|
|
74
|
+
return ProviderDataPolicy(
|
|
75
|
+
input_fields=[
|
|
76
|
+
ProviderInputFieldPolicy(
|
|
77
|
+
json_pointer="/question",
|
|
78
|
+
classification="internal",
|
|
79
|
+
dlp_action="redact",
|
|
80
|
+
)
|
|
81
|
+
],
|
|
82
|
+
envelope_fields=[
|
|
83
|
+
ProviderFieldPolicy(
|
|
84
|
+
field="instructions",
|
|
85
|
+
classification="internal",
|
|
86
|
+
dlp_action="reject",
|
|
87
|
+
),
|
|
88
|
+
ProviderFieldPolicy(
|
|
89
|
+
field="user-prompt",
|
|
90
|
+
classification="internal",
|
|
91
|
+
dlp_action="redact",
|
|
92
|
+
),
|
|
93
|
+
ProviderFieldPolicy(
|
|
94
|
+
field="tool-definitions",
|
|
95
|
+
classification="internal",
|
|
96
|
+
dlp_action="reject",
|
|
97
|
+
),
|
|
98
|
+
ProviderFieldPolicy(
|
|
99
|
+
field="provider-output",
|
|
100
|
+
classification="internal",
|
|
101
|
+
dlp_action="redact",
|
|
102
|
+
),
|
|
103
|
+
ProviderFieldPolicy(
|
|
104
|
+
field="tool-results",
|
|
105
|
+
classification="sensitive",
|
|
106
|
+
dlp_action="redact",
|
|
107
|
+
),
|
|
108
|
+
],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def governance(path: str | None = None) -> ProviderRequestGovernance:
|
|
113
|
+
path = (
|
|
114
|
+
path
|
|
115
|
+
or tempfile.NamedTemporaryFile(
|
|
116
|
+
suffix=".sqlite3",
|
|
117
|
+
delete=False,
|
|
118
|
+
).name
|
|
119
|
+
)
|
|
120
|
+
return ProviderRequestGovernance(ProviderAuditStore(path, retention_days=90))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def sse_response(chunks: list[object]) -> httpx.Response:
|
|
124
|
+
body = "".join(
|
|
125
|
+
f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n" for chunk in chunks
|
|
126
|
+
)
|
|
127
|
+
return httpx.Response(
|
|
128
|
+
200,
|
|
129
|
+
headers={"content-type": "text/event-stream"},
|
|
130
|
+
content=f"{body}data: [DONE]\n\n",
|
|
131
|
+
)
|