@k2b/cloud 0.6.0-rc.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/README.md +98 -0
- package/package.json +128 -0
- package/public/logo.svg +1 -0
- package/scripts/app-favicon.test.ts +49 -0
- package/scripts/app-favicon.ts +51 -0
- package/scripts/build.ts +205 -0
- package/scripts/preload.ts +164 -0
- package/scripts/sync-recovery-smoke.ts +200 -0
- package/src/_internal/app-favicon.ts +2 -0
- package/src/_internal/app-presentation.ts +61 -0
- package/src/_internal/app-workspace-state.ts +24 -0
- package/src/_internal/bounded-json.ts +47 -0
- package/src/_internal/build-metadata.ts +9 -0
- package/src/_internal/capabilities.ts +1118 -0
- package/src/_internal/define-app.ts +801 -0
- package/src/_internal/heartbeat.ts +167 -0
- package/src/_internal/help-catalog.ts +241 -0
- package/src/_internal/help.ts +95 -0
- package/src/_internal/nats-connection.ts +34 -0
- package/src/_internal/page-responses.ts +40 -0
- package/src/_internal/process-sync.ts +117 -0
- package/src/_internal/readiness.ts +3 -0
- package/src/_internal/registry-validation.ts +136 -0
- package/src/_internal/registry-watch.ts +52 -0
- package/src/_internal/registry.ts +338 -0
- package/src/_internal/runtime-compatibility.ts +64 -0
- package/src/_internal/runtime-context.ts +84 -0
- package/src/_internal/runtime-watcher.ts +78 -0
- package/src/_internal/static-assets.ts +86 -0
- package/src/_internal/status-preserving-ssr.ts +49 -0
- package/src/access/PermissionEditor.tsx +486 -0
- package/src/access/ResourceApiKeys.tsx +271 -0
- package/src/access/messages.ts +121 -0
- package/src/access/ui.ts +16 -0
- package/src/account/Avatar.tsx +28 -0
- package/src/account/EntitySearch.tsx +363 -0
- package/src/account/avatar-upload-dialog.tsx +197 -0
- package/src/account/avatar-upload.ts +149 -0
- package/src/account/ui.ts +3 -0
- package/src/ai/admin.ts +47 -0
- package/src/ai/approval-routes.ts +130 -0
- package/src/ai/approvals.ts +139 -0
- package/src/ai/assistant-models.ts +61 -0
- package/src/ai/attachments.ts +40 -0
- package/src/ai/browser.ts +92 -0
- package/src/ai/capabilities.ts +714 -0
- package/src/ai/capability-execution.ts +151 -0
- package/src/ai/chat/blocks.render.test.tsx +1108 -0
- package/src/ai/chat/blocks.tsx +730 -0
- package/src/ai/chat/builtin-tools.tsx +428 -0
- package/src/ai/chat/capability-table.tsx +87 -0
- package/src/ai/chat/composer-adapter.ts +169 -0
- package/src/ai/chat/file-tools.tsx +81 -0
- package/src/ai/chat/message-actions.tsx +370 -0
- package/src/ai/chat/message-utils.ts +479 -0
- package/src/ai/chat/messages.ts +43 -0
- package/src/ai/chat/presentation.tsx +395 -0
- package/src/ai/chat/primitives.tsx +3 -0
- package/src/ai/chat/tool-disclosure.tsx +41 -0
- package/src/ai/chat/turn-navigator-utils.ts +28 -0
- package/src/ai/chat/turn-navigator.tsx +164 -0
- package/src/ai/chat/user-message.tsx +149 -0
- package/src/ai/chat/visual-tools.tsx +435 -0
- package/src/ai/chat/web-tools.tsx +202 -0
- package/src/ai/chat-task-contracts.ts +113 -0
- package/src/ai/chat-tasks.ts +952 -0
- package/src/ai/client/controller.ts +1211 -0
- package/src/ai/client/file-source.ts +95 -0
- package/src/ai/client/live-connection.ts +140 -0
- package/src/ai/client/projection.ts +178 -0
- package/src/ai/client/transport.ts +111 -0
- package/src/ai/compaction.ts +182 -0
- package/src/ai/credentials.ts +124 -0
- package/src/ai/default-tool-contracts.ts +108 -0
- package/src/ai/default-tools.ts +127 -0
- package/src/ai/enrich.ts +280 -0
- package/src/ai/executor.ts +1624 -0
- package/src/ai/fetch-file-tool.ts +313 -0
- package/src/ai/file-context.ts +92 -0
- package/src/ai/file-media-type.ts +28 -0
- package/src/ai/file-mount.ts +16 -0
- package/src/ai/file-tools.ts +566 -0
- package/src/ai/files-store.ts +390 -0
- package/src/ai/firecrawl-tools.ts +305 -0
- package/src/ai/grids-skill.ts +126 -0
- package/src/ai/http.ts +265 -0
- package/src/ai/index.ts +337 -0
- package/src/ai/kit-skill.ts +22 -0
- package/src/ai/limits.ts +5 -0
- package/src/ai/live-events.ts +289 -0
- package/src/ai/live-messages.ts +45 -0
- package/src/ai/live-outbox.ts +102 -0
- package/src/ai/live-routes.ts +412 -0
- package/src/ai/live.ts +2 -0
- package/src/ai/maintenance.ts +302 -0
- package/src/ai/markdown-pdf-tool.ts +95 -0
- package/src/ai/memories.ts +605 -0
- package/src/ai/memory-learning-runs.ts +222 -0
- package/src/ai/memory-learning.ts +619 -0
- package/src/ai/memory-tool.ts +120 -0
- package/src/ai/memory-workflow-evidence.ts +158 -0
- package/src/ai/migrate.ts +2082 -0
- package/src/ai/model-access-migrate.ts +30 -0
- package/src/ai/model-access.ts +191 -0
- package/src/ai/personal-agent.ts +6 -0
- package/src/ai/prefs.ts +72 -0
- package/src/ai/project-tool.ts +94 -0
- package/src/ai/projects-routes.ts +277 -0
- package/src/ai/projects.ts +902 -0
- package/src/ai/protocol.ts +279 -0
- package/src/ai/provider.ts +73 -0
- package/src/ai/public-projection.ts +25 -0
- package/src/ai/resource-markers.ts +31 -0
- package/src/ai/resource-refs.ts +70 -0
- package/src/ai/routes.ts +1131 -0
- package/src/ai/runtime-tools.ts +39 -0
- package/src/ai/runtime.ts +544 -0
- package/src/ai/settings.ts +529 -0
- package/src/ai/short-id.ts +45 -0
- package/src/ai/skill-catalog.ts +73 -0
- package/src/ai/skill-format.ts +145 -0
- package/src/ai/skill-seeds.ts +430 -0
- package/src/ai/skill-tool.ts +81 -0
- package/src/ai/skills-routes.ts +143 -0
- package/src/ai/skills.ts +934 -0
- package/src/ai/solid.ts +27 -0
- package/src/ai/store.ts +3113 -0
- package/src/ai/stream.ts +308 -0
- package/src/ai/structured-runs.ts +66 -0
- package/src/ai/structured.ts +186 -0
- package/src/ai/system-prompt.ts +170 -0
- package/src/ai/task-prompt.ts +22 -0
- package/src/ai/timeline.ts +148 -0
- package/src/ai/tool-audit.ts +177 -0
- package/src/ai/tool-result-budget.ts +16 -0
- package/src/ai/tools.ts +155 -0
- package/src/ai/types.ts +967 -0
- package/src/ai/ui.tsx +39 -0
- package/src/ai/usage-migrate.ts +81 -0
- package/src/ai/usage.ts +315 -0
- package/src/ai/validate.ts +54 -0
- package/src/ai/vision-tool.ts +79 -0
- package/src/api/accounts-entities.ts +174 -0
- package/src/api/admin-ai-projects.ts +74 -0
- package/src/api/admin-ai-skills.ts +76 -0
- package/src/api/admin-ai-usage.ts +42 -0
- package/src/api/admin-core-settings.ts +508 -0
- package/src/api/admin-identity.ts +148 -0
- package/src/api/admin-lifecycle.ts +206 -0
- package/src/api/admin-linux-identities.ts +66 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/app-approval.ts +129 -0
- package/src/api/apps.ts +51 -0
- package/src/api/auth/schemas.ts +54 -0
- package/src/api/auth.ts +370 -0
- package/src/api/capabilities.ts +658 -0
- package/src/api/help-messages.ts +19 -0
- package/src/api/help.ts +78 -0
- package/src/api/index.ts +108 -0
- package/src/api/mcp.ts +744 -0
- package/src/api/me-rail.ts +45 -0
- package/src/api/me.ts +753 -0
- package/src/api/search/schemas.ts +79 -0
- package/src/api/search.bench.ts +185 -0
- package/src/api/search.ts +407 -0
- package/src/api/sync-ops.ts +79 -0
- package/src/api/widgets.ts +143 -0
- package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
- package/src/browser/CloudResourceSearch.tsx +791 -0
- package/src/browser/app-approval-vault.ts +305 -0
- package/src/browser/app-approval.ts +234 -0
- package/src/browser/live-websocket.ts +268 -0
- package/src/browser/locale-preference.ts +25 -0
- package/src/browser/notification-target.ts +27 -0
- package/src/browser/notification-timeout.ts +14 -0
- package/src/browser/notifications.ts +181 -0
- package/src/browser/resource-clipboard.ts +70 -0
- package/src/browser/resource-picker.tsx +39 -0
- package/src/browser/resource-search-messages.ts +55 -0
- package/src/browser/resource-search.ts +27 -0
- package/src/capabilities/client.ts +149 -0
- package/src/capabilities/response.ts +39 -0
- package/src/capabilities/server.ts +215 -0
- package/src/capabilities/signals.ts +4 -0
- package/src/capabilities/testing.ts +12 -0
- package/src/capabilities/types.ts +32 -0
- package/src/cli/access.ts +499 -0
- package/src/cli/account.ts +382 -0
- package/src/cli/admin/account-administration.ts +45 -0
- package/src/cli/admin/account-categories.ts +49 -0
- package/src/cli/admin/ai-usage.ts +118 -0
- package/src/cli/admin/app-sign-in.ts +49 -0
- package/src/cli/admin/data.ts +343 -0
- package/src/cli/admin/documentation.ts +34 -0
- package/src/cli/admin/gateway.ts +155 -0
- package/src/cli/admin/index.ts +61 -0
- package/src/cli/admin/instance.ts +250 -0
- package/src/cli/admin/jobs.ts +223 -0
- package/src/cli/admin/legal.ts +122 -0
- package/src/cli/admin/linux.ts +53 -0
- package/src/cli/admin/logs.ts +324 -0
- package/src/cli/admin/metrics.ts +142 -0
- package/src/cli/admin/nats.ts +167 -0
- package/src/cli/admin/notifications.ts +516 -0
- package/src/cli/admin/shared.ts +113 -0
- package/src/cli/admin/sync.ts +188 -0
- package/src/cli/admin/telemetry.ts +314 -0
- package/src/cli/admin/webhooks.ts +222 -0
- package/src/cli/admin/workflows.ts +367 -0
- package/src/cli/apps.ts +40 -0
- package/src/cli/capabilities.ts +239 -0
- package/src/cli/commands.ts +604 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/locale.ts +31 -0
- package/src/cli/output.ts +58 -0
- package/src/clients/core.ts +20 -0
- package/src/config/env.ts +54 -0
- package/src/config/index.ts +6 -0
- package/src/contracts/account-categories.ts +62 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app-approval.ts +197 -0
- package/src/contracts/app.ts +162 -0
- package/src/contracts/capabilities.ts +484 -0
- package/src/contracts/index.ts +14 -0
- package/src/contracts/notification-live.ts +66 -0
- package/src/contracts/notification-types.ts +235 -0
- package/src/contracts/posix.ts +69 -0
- package/src/contracts/profile.ts +85 -0
- package/src/contracts/rail-preferences.ts +53 -0
- package/src/contracts/registry.ts +110 -0
- package/src/contracts/resource-clipboard.ts +33 -0
- package/src/contracts/settings-types.ts +98 -0
- package/src/contracts/shared.ts +408 -0
- package/src/contracts/user-notifications.ts +101 -0
- package/src/contracts/widgets.ts +183 -0
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/index.ts +58 -0
- package/src/server/actor.ts +41 -0
- package/src/server/api/index.ts +2 -0
- package/src/server/api/respond.ts +96 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/help.ts +187 -0
- package/src/server/index.ts +89 -0
- package/src/server/locale.ts +55 -0
- package/src/server/middleware/auth.ts +386 -0
- package/src/server/middleware/index.ts +28 -0
- package/src/server/middleware/invocation.ts +55 -0
- package/src/server/middleware/middleware.ts +65 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +125 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/route-template.ts +46 -0
- package/src/server/middleware/runtime.ts +22 -0
- package/src/server/middleware/settings.ts +55 -0
- package/src/server/middleware/validator.ts +49 -0
- package/src/server/middleware/workload.ts +22 -0
- package/src/server/ratelimit.ts +151 -0
- package/src/server/request-cookies.ts +18 -0
- package/src/server/services/access.ts +644 -0
- package/src/server/services/freeipa/client.ts +137 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +123 -0
- package/src/server/services/freeipa/test-certificates.ts +44 -0
- package/src/server/services/freeipa/tls.ts +44 -0
- package/src/server/services/freeipa/transport.ts +116 -0
- package/src/server/services/freeipa/util.ts +59 -0
- package/src/server/services/geo.ts +198 -0
- package/src/server/services/index.ts +48 -0
- package/src/server/services/pagination.ts +22 -0
- package/src/server/services/services.ts +13 -0
- package/src/server/time.ts +33 -0
- package/src/services/account-category-policy.ts +51 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +940 -0
- package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
- package/src/services/account-lifecycle/notification-sender.ts +13 -0
- package/src/services/account-lifecycle/scheduler.ts +506 -0
- package/src/services/account-model.ts +22 -0
- package/src/services/accounts/app.ts +1671 -0
- package/src/services/accounts/authz.ts +44 -0
- package/src/services/accounts/avatar.ts +120 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +46 -0
- package/src/services/accounts/duplicate-emails.ts +82 -0
- package/src/services/accounts/entities.ts +622 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +272 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +66 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +499 -0
- package/src/services/accounts/model.ts +129 -0
- package/src/services/accounts/notification-sender.ts +31 -0
- package/src/services/accounts/posix.ts +386 -0
- package/src/services/accounts/request-policy.ts +11 -0
- package/src/services/accounts/switching.ts +126 -0
- package/src/services/accounts/users.ts +745 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/app-approval.ts +498 -0
- package/src/services/audit/index.ts +438 -0
- package/src/services/auth-flows/index.ts +15 -0
- package/src/services/auth-flows/ipa.ts +197 -0
- package/src/services/auth-flows/magic-link.ts +185 -0
- package/src/services/auth-flows/notification-sender.ts +10 -0
- package/src/services/auth-flows/password-reset.ts +268 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/document-extraction.ts +148 -0
- package/src/services/freeipa-config.ts +179 -0
- package/src/services/gateway.ts +186 -0
- package/src/services/identity/constants.ts +18 -0
- package/src/services/identity/index.ts +52 -0
- package/src/services/identity/invocation-actor.ts +120 -0
- package/src/services/identity/invocation-authority.ts +53 -0
- package/src/services/identity/invocation-operations.ts +15 -0
- package/src/services/identity/invocation-token.ts +276 -0
- package/src/services/identity/key-config.ts +41 -0
- package/src/services/identity/key-ring.ts +530 -0
- package/src/services/identity/metrics.ts +29 -0
- package/src/services/identity/routes.ts +18 -0
- package/src/services/identity/runtime-config.ts +88 -0
- package/src/services/identity/session-token.ts +152 -0
- package/src/services/identity/signer-refresh.ts +44 -0
- package/src/services/identity/workload-auth.ts +64 -0
- package/src/services/index.ts +187 -0
- package/src/services/ipa/auth.ts +216 -0
- package/src/services/ipa/connection.ts +57 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/groups.ts +722 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/posix.ts +19 -0
- package/src/services/ipa/profile.ts +132 -0
- package/src/services/ipa/runtime.ts +28 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.ts +100 -0
- package/src/services/ipa/sync.ts +862 -0
- package/src/services/ipa/users.ts +806 -0
- package/src/services/legal-consent.ts +76 -0
- package/src/services/logging/index.ts +477 -0
- package/src/services/logging/redaction.ts +20 -0
- package/src/services/logging/trace.ts +1231 -0
- package/src/services/mandates/index.ts +855 -0
- package/src/services/mandates/policy.ts +85 -0
- package/src/services/network-security.ts +31 -0
- package/src/services/notifications/batches.ts +722 -0
- package/src/services/notifications/browser.ts +252 -0
- package/src/services/notifications/catalog.ts +113 -0
- package/src/services/notifications/channels.ts +104 -0
- package/src/services/notifications/dispatcher.ts +139 -0
- package/src/services/notifications/email.ts +118 -0
- package/src/services/notifications/index.ts +633 -0
- package/src/services/notifications/live.ts +50 -0
- package/src/services/notifications/observability.ts +407 -0
- package/src/services/notifications/platform.ts +421 -0
- package/src/services/notifications/runtime.ts +120 -0
- package/src/services/notifications/user.ts +248 -0
- package/src/services/notifications/web-push-transport.ts +135 -0
- package/src/services/oauth-tokens.ts +209 -0
- package/src/services/outbox.ts +187 -0
- package/src/services/pdf/gotenberg.ts +290 -0
- package/src/services/pdf/index.ts +43 -0
- package/src/services/pdf/markdown.ts +209 -0
- package/src/services/pdf/template-preview.ts +122 -0
- package/src/services/postgres.ts +76 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +51 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +263 -0
- package/src/services/rail-preferences.ts +27 -0
- package/src/services/runtime-lifecycle.ts +191 -0
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.ts +891 -0
- package/src/services/service-accounts.ts +184 -0
- package/src/services/session/index.ts +236 -0
- package/src/services/session/test-fixture.ts +18 -0
- package/src/services/session/user.ts +144 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +86 -0
- package/src/services/settings/core-settings.ts +796 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +374 -0
- package/src/services/settings/index.ts +215 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +241 -0
- package/src/services/settings/templates.ts +4 -0
- package/src/services/sync-ops.ts +403 -0
- package/src/services/topic-cursor.ts +22 -0
- package/src/services/weather/forecast.ts +323 -0
- package/src/services/weather/geo.ts +119 -0
- package/src/services/weather/index.ts +67 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +133 -0
- package/src/services/weather/migrate.ts +45 -0
- package/src/services/weather/settings.ts +66 -0
- package/src/services/weather/short-id.ts +57 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/services/webauthn.ts +385 -0
- package/src/shared/account-action-notice.ts +70 -0
- package/src/shared/account-display.ts +15 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/ai-platform-prompt.ts +134 -0
- package/src/shared/ai-usage.ts +49 -0
- package/src/shared/app-presentation.ts +48 -0
- package/src/shared/app-url.ts +12 -0
- package/src/shared/branding.ts +14 -0
- package/src/shared/capability-messages.ts +70 -0
- package/src/shared/email-html.ts +54 -0
- package/src/shared/format.ts +91 -0
- package/src/shared/help.ts +68 -0
- package/src/shared/icons.ts +400 -0
- package/src/shared/index.ts +54 -0
- package/src/shared/locale.ts +48 -0
- package/src/shared/login-method.ts +44 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +105 -0
- package/src/shared/markdown/extensions/guided-help.ts +100 -0
- package/src/shared/markdown/extensions/images.ts +79 -0
- package/src/shared/markdown/extensions/info-blocks.ts +97 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +43 -0
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +109 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +193 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/network-address.ts +106 -0
- package/src/shared/redirect.ts +56 -0
- package/src/shared/template-rendering.ts +140 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +53 -0
- package/src/ssr/AdminSidebar.tsx +50 -0
- package/src/ssr/AppLaunchpad.island.tsx +236 -0
- package/src/ssr/AppWorkspaceController.island.tsx +21 -0
- package/src/ssr/BrowserNotifications.island.tsx +135 -0
- package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
- package/src/ssr/GlobalSearchDialog.tsx +35 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
- package/src/ssr/Layout.render.test.tsx +148 -0
- package/src/ssr/Layout.tsx +292 -0
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutFooter.tsx +22 -0
- package/src/ssr/LayoutHeader.tsx +81 -0
- package/src/ssr/LayoutHelp.tsx +869 -0
- package/src/ssr/LayoutPreferences.island.tsx +36 -0
- package/src/ssr/LayoutRail.tsx +58 -0
- package/src/ssr/MinimalLayout.tsx +46 -0
- package/src/ssr/PageError.tsx +29 -0
- package/src/ssr/ProfilePreferences.island.tsx +92 -0
- package/src/ssr/RailApps.island.tsx +61 -0
- package/src/ssr/RailEditor.tsx +293 -0
- package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/admin-active-link.ts +47 -0
- package/src/ssr/admin-navigation.ts +78 -0
- package/src/ssr/app-appearance.ts +23 -0
- package/src/ssr/app-navigation.ts +41 -0
- package/src/ssr/help-messages.ts +104 -0
- package/src/ssr/index.ts +14 -0
- package/src/ssr/islands/SearchBar.island.tsx +80 -0
- package/src/ssr/islands/index.ts +16 -0
- package/src/ssr/layout-context.ts +17 -0
- package/src/ssr/layout-help-markdown.ts +9 -0
- package/src/ssr/layout-help-navigation.ts +24 -0
- package/src/ssr/layout-help-url.ts +5 -0
- package/src/ssr/layout-runtime.ts +22 -0
- package/src/ssr/page-error-messages.ts +25 -0
- package/src/ssr/platform-messages.ts +135 -0
- package/src/ssr/preference-controller.ts +44 -0
- package/src/ssr/profile-preferences-messages.ts +29 -0
- package/src/ssr/rail-context.ts +21 -0
- package/src/ssr/rail-messages.ts +66 -0
- package/src/ssr/rail-navigation.ts +60 -0
- package/src/ssr/runtime.ts +31 -0
- package/src/ssr/url-filter.ts +127 -0
- package/src/styles/base-popover.css +36 -0
- package/src/styles/effects.css +851 -0
- package/src/styles/global.css +268 -0
- package/src/styles/input.css +58 -0
- package/src/styles/tokens.css +230 -0
- package/src/styles/utilities-buttons.css +405 -0
- package/src/styles/utilities-data.css +33 -0
- package/src/styles/utilities-feedback.css +232 -0
- package/src/styles/utilities-layout.css +603 -0
- package/src/styles/utilities-markdown-editor.css +525 -0
- package/src/styles/utilities-markdown-table.css +234 -0
- package/src/styles/utilities-navigation.css +1054 -0
- package/src/types/ambient.d.ts +24 -0
- package/src/workflows/ai/index.ts +17 -0
- package/src/workflows/ai/runtime.ts +286 -0
- package/src/workflows/ai/store.ts +314 -0
- package/src/workflows/ai/types.ts +111 -0
- package/src/workflows/ai-actions.ts +269 -0
- package/src/workflows/authoring.ts +88 -0
- package/src/workflows/builtins.ts +193 -0
- package/src/workflows/contracts.ts +280 -0
- package/src/workflows/definition.ts +334 -0
- package/src/workflows/editor.ts +113 -0
- package/src/workflows/index.ts +71 -0
- package/src/workflows/language/binder.ts +45 -0
- package/src/workflows/language/canonical.ts +29 -0
- package/src/workflows/language/compiler.ts +396 -0
- package/src/workflows/language/expressions.ts +34 -0
- package/src/workflows/language/index.ts +20 -0
- package/src/workflows/language/references.ts +55 -0
- package/src/workflows/language/schema.ts +134 -0
- package/src/workflows/language/strict-yaml.ts +131 -0
- package/src/workflows/manifest.ts +24 -0
- package/src/workflows/module.ts +12 -0
- package/src/workflows/runtime/coordinator.ts +115 -0
- package/src/workflows/runtime/executor.ts +1003 -0
- package/src/workflows/runtime/index.ts +60 -0
- package/src/workflows/runtime/instant.ts +8 -0
- package/src/workflows/runtime/ports.ts +213 -0
- package/src/workflows/runtime/schedule.ts +203 -0
- package/src/workflows/runtime/trigger.ts +37 -0
- package/src/workflows/runtime/wake.ts +13 -0
- package/src/workflows/store/actions.ts +438 -0
- package/src/workflows/store/budget.ts +145 -0
- package/src/workflows/store/definitions.ts +345 -0
- package/src/workflows/store/events.ts +383 -0
- package/src/workflows/store/index.ts +107 -0
- package/src/workflows/store/observability.ts +594 -0
- package/src/workflows/store/runs.ts +975 -0
- package/src/workflows/store/transaction.ts +23 -0
- package/src/workflows/store/worker.ts +439 -0
- package/src/workflows/testing/conformance.ts +72 -0
- package/src/workflows/testing/index.ts +11 -0
- package/src/workflows/testing/process-fixtures.ts +275 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { Tool, ToolContext, ToolResolver } from "@k2b/nessi";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
createHelpCatalog,
|
|
6
|
+
HELP_READ_MAX_CHARS,
|
|
7
|
+
HELP_SEARCH_MAX_LIMIT,
|
|
8
|
+
readHelpCatalog,
|
|
9
|
+
searchHelpCatalog,
|
|
10
|
+
} from "../_internal/help-catalog";
|
|
11
|
+
import {
|
|
12
|
+
type CapabilityActionManifest,
|
|
13
|
+
type CapabilityActionReview,
|
|
14
|
+
type CapabilityQueryManifest,
|
|
15
|
+
CloudResourceRefSchema,
|
|
16
|
+
cloudResourceRefAppId,
|
|
17
|
+
resolveCapabilityResourceReader,
|
|
18
|
+
} from "../contracts/capabilities";
|
|
19
|
+
import type { CapabilityRegistryEntry, HelpRegistryEntry } from "../contracts/registry";
|
|
20
|
+
import type { RequestActor } from "../server";
|
|
21
|
+
import { CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES } from "./default-tools";
|
|
22
|
+
import { type AiToolPreparationContext, defineAiTool, type PreparedAiTools, prepareAiTools } from "./tools";
|
|
23
|
+
import type { AiConversationService, AiRuntimeTool, AiToolPresentation } from "./types";
|
|
24
|
+
|
|
25
|
+
export type AiCapabilityKind = "query" | "action";
|
|
26
|
+
|
|
27
|
+
export type AiCapabilityCatalogItem = {
|
|
28
|
+
/** Stable public identity used by Skills, discovery, loading, and persistence. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Provider-safe function name used only for model tool definitions and calls. */
|
|
31
|
+
providerName: string;
|
|
32
|
+
appId: string;
|
|
33
|
+
appName: string;
|
|
34
|
+
appDescription: string;
|
|
35
|
+
kind: AiCapabilityKind;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type AiCapabilityAppCatalogItem = {
|
|
41
|
+
appId: string;
|
|
42
|
+
appName: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type AiCapabilityCatalogEntry = AiCapabilityCatalogItem & {
|
|
47
|
+
app: CapabilityRegistryEntry;
|
|
48
|
+
operation: CapabilityQueryManifest | CapabilityActionManifest;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type AiToolKind = "builtin" | AiCapabilityKind;
|
|
52
|
+
|
|
53
|
+
export type AiToolCatalogItem = {
|
|
54
|
+
name: string;
|
|
55
|
+
title: string;
|
|
56
|
+
description: string;
|
|
57
|
+
kind: AiToolKind;
|
|
58
|
+
appId?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type AiToolCatalogEntry = AiToolCatalogItem & {
|
|
62
|
+
searchText: string;
|
|
63
|
+
runtimeTool?: AiRuntimeTool;
|
|
64
|
+
capability?: AiCapabilityCatalogEntry;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type AiRememberableCapabilityApprovals = ReadonlyMap<string, string>;
|
|
68
|
+
|
|
69
|
+
const DEFAULT_SEARCH_LIMIT = 10;
|
|
70
|
+
const DEFAULT_APP_LIST_LIMIT = 20;
|
|
71
|
+
const MAX_APP_LIST_LIMIT = 25;
|
|
72
|
+
const MAX_APP_DIRECTORY_DESCRIPTION_CHARS = 2_000;
|
|
73
|
+
const MAX_UNAVAILABLE_LOADED_NAMES = 10;
|
|
74
|
+
|
|
75
|
+
const providerSafeSegment = (value: string): string =>
|
|
76
|
+
[...value]
|
|
77
|
+
.map((character) => {
|
|
78
|
+
if (/^[a-zA-Z0-9-]$/.test(character)) return character;
|
|
79
|
+
if (character === "_") return "__";
|
|
80
|
+
if (character === ".") return "_dot_";
|
|
81
|
+
return `_u${character.codePointAt(0)!.toString(16)}_`;
|
|
82
|
+
})
|
|
83
|
+
.join("");
|
|
84
|
+
|
|
85
|
+
/** Readable, collision-safe name within the strictest common provider limit. */
|
|
86
|
+
export const aiCapabilityToolName = (appId: string, kind: AiCapabilityKind, localId: string): string => {
|
|
87
|
+
const full = `${providerSafeSegment(appId)}__${kind}__${providerSafeSegment(localId)}`;
|
|
88
|
+
if (full.length <= 64) return full;
|
|
89
|
+
const suffix = createHash("sha256").update(full).digest("hex").slice(0, 12);
|
|
90
|
+
return `${full.slice(0, 50)}__${suffix}`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Stable qualified identity shared by capability consumers outside provider transports. */
|
|
94
|
+
export const aiCapabilityId = (appId: string, localId: string): string => `${appId}.${localId}`;
|
|
95
|
+
|
|
96
|
+
/** Build the compact, deterministic directory of apps in the current live registry. */
|
|
97
|
+
export const buildAiCapabilityAppCatalog = (apps: readonly CapabilityRegistryEntry[]): AiCapabilityAppCatalogItem[] => {
|
|
98
|
+
const seen = new Set<string>();
|
|
99
|
+
return [...apps]
|
|
100
|
+
.sort(
|
|
101
|
+
(left, right) =>
|
|
102
|
+
left.appId.localeCompare(right.appId) ||
|
|
103
|
+
left.appName.localeCompare(right.appName) ||
|
|
104
|
+
left.appDescription.localeCompare(right.appDescription) ||
|
|
105
|
+
left.endpoint.localeCompare(right.endpoint),
|
|
106
|
+
)
|
|
107
|
+
.flatMap((app) => {
|
|
108
|
+
if (seen.has(app.appId)) return [];
|
|
109
|
+
seen.add(app.appId);
|
|
110
|
+
return [{ appId: app.appId, appName: app.appName, description: app.appDescription }];
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Build one deterministic, immutable view of the current live registry. */
|
|
115
|
+
export const buildAiCapabilityCatalog = (apps: CapabilityRegistryEntry[]): AiCapabilityCatalogEntry[] => {
|
|
116
|
+
const entries = [...apps]
|
|
117
|
+
.sort(
|
|
118
|
+
(left, right) =>
|
|
119
|
+
left.appId.localeCompare(right.appId) ||
|
|
120
|
+
left.manifest.manifestHash.localeCompare(right.manifest.manifestHash) ||
|
|
121
|
+
left.appName.localeCompare(right.appName) ||
|
|
122
|
+
left.endpoint.localeCompare(right.endpoint),
|
|
123
|
+
)
|
|
124
|
+
.flatMap((app) => [
|
|
125
|
+
...app.manifest.actions.map((operation) => ({ app, operation, kind: "action" as const })),
|
|
126
|
+
...app.manifest.queries.map((operation) => ({ app, operation, kind: "query" as const })),
|
|
127
|
+
])
|
|
128
|
+
.map(
|
|
129
|
+
({ app, operation, kind }): AiCapabilityCatalogEntry => ({
|
|
130
|
+
name: aiCapabilityId(app.appId, operation.localId),
|
|
131
|
+
providerName: aiCapabilityToolName(app.appId, kind, operation.localId),
|
|
132
|
+
appId: app.appId,
|
|
133
|
+
appName: app.appName,
|
|
134
|
+
appDescription: app.appDescription,
|
|
135
|
+
kind,
|
|
136
|
+
title: operation.title,
|
|
137
|
+
description: operation.description,
|
|
138
|
+
app,
|
|
139
|
+
operation,
|
|
140
|
+
}),
|
|
141
|
+
)
|
|
142
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
143
|
+
|
|
144
|
+
const seen = new Set<string>();
|
|
145
|
+
return entries.filter((entry) => {
|
|
146
|
+
if (seen.has(entry.name)) return false;
|
|
147
|
+
seen.add(entry.name);
|
|
148
|
+
return true;
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const boundedLimit = (value: number | undefined, fallback: number, maximum: number): number => {
|
|
153
|
+
if (!Number.isInteger(value) || Number(value) <= 0) return fallback;
|
|
154
|
+
return Math.min(Number(value), maximum);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const listAiCapabilityApps = (
|
|
158
|
+
apps: readonly AiCapabilityAppCatalogItem[],
|
|
159
|
+
input: { cursor?: string; limit?: number },
|
|
160
|
+
): { apps: AiCapabilityAppCatalogItem[]; page: { hasMore: boolean; nextCursor?: string } } => {
|
|
161
|
+
const start = input.cursor ? apps.findIndex((app) => app.appId > input.cursor!) : 0;
|
|
162
|
+
const offset = start < 0 ? apps.length : start;
|
|
163
|
+
const limit = boundedLimit(input.limit, DEFAULT_APP_LIST_LIMIT, MAX_APP_LIST_LIMIT);
|
|
164
|
+
const page = apps.slice(offset, offset + limit);
|
|
165
|
+
const hasMore = offset + page.length < apps.length;
|
|
166
|
+
return {
|
|
167
|
+
apps: [...page],
|
|
168
|
+
page: {
|
|
169
|
+
hasMore,
|
|
170
|
+
...(hasMore && page.length > 0 ? { nextCursor: page.at(-1)!.appId } : {}),
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const normalizeSearchText = (value: string): string =>
|
|
176
|
+
value
|
|
177
|
+
.normalize("NFKD")
|
|
178
|
+
.replace(/\p{M}/gu, "")
|
|
179
|
+
.toLocaleLowerCase()
|
|
180
|
+
.replace(/[^\p{L}\p{N}]+/gu, " ")
|
|
181
|
+
.trim();
|
|
182
|
+
|
|
183
|
+
const searchTerms = (value: string): string[] => {
|
|
184
|
+
const terms = normalizeSearchText(value).split(" ").filter(Boolean);
|
|
185
|
+
const meaningful = terms.filter((term) => term.length >= 2);
|
|
186
|
+
return [...new Set(meaningful.length > 0 ? meaningful : terms)];
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const searchTermForms = (term: string): string[] => {
|
|
190
|
+
const forms = new Set([term]);
|
|
191
|
+
if (term.length > 3 && term.endsWith("s") && !term.endsWith("ss")) forms.add(term.slice(0, -1));
|
|
192
|
+
if (term.length > 4 && term.endsWith("es")) forms.add(term.slice(0, -2));
|
|
193
|
+
if (term.length > 4 && term.endsWith("ies")) forms.add(`${term.slice(0, -3)}y`);
|
|
194
|
+
return [...forms];
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const searchWordForms = (value: string): Set<string> => new Set(searchTerms(value).flatMap(searchTermForms));
|
|
198
|
+
|
|
199
|
+
const includesSearchTerm = (words: ReadonlySet<string>, term: string): boolean => searchTermForms(term).some((form) => words.has(form));
|
|
200
|
+
|
|
201
|
+
const includesSearchPhrase = (text: string, phrase: string): boolean => ` ${text} `.includes(` ${phrase} `);
|
|
202
|
+
|
|
203
|
+
const toolTitle = (name: string): string =>
|
|
204
|
+
name
|
|
205
|
+
.split("_")
|
|
206
|
+
.filter(Boolean)
|
|
207
|
+
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
|
|
208
|
+
.join(" ");
|
|
209
|
+
|
|
210
|
+
export const buildAiToolCatalog = (
|
|
211
|
+
builtIns: readonly AiRuntimeTool[],
|
|
212
|
+
capabilities: readonly AiCapabilityCatalogEntry[],
|
|
213
|
+
): AiToolCatalogEntry[] =>
|
|
214
|
+
[
|
|
215
|
+
...builtIns.map(
|
|
216
|
+
(runtimeTool): AiToolCatalogEntry => ({
|
|
217
|
+
name: runtimeTool.def.name,
|
|
218
|
+
title: toolTitle(runtimeTool.def.name),
|
|
219
|
+
description: runtimeTool.def.description,
|
|
220
|
+
kind: "builtin",
|
|
221
|
+
searchText: "",
|
|
222
|
+
runtimeTool,
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
...capabilities.map(
|
|
226
|
+
(capability): AiToolCatalogEntry => ({
|
|
227
|
+
name: capability.name,
|
|
228
|
+
title: capability.title,
|
|
229
|
+
description: capability.description,
|
|
230
|
+
kind: capability.kind,
|
|
231
|
+
appId: capability.appId,
|
|
232
|
+
searchText: `${capability.appName} ${capability.appDescription}`,
|
|
233
|
+
capability,
|
|
234
|
+
}),
|
|
235
|
+
),
|
|
236
|
+
].sort((left, right) => left.name.localeCompare(right.name));
|
|
237
|
+
|
|
238
|
+
export const searchAiTools = (
|
|
239
|
+
catalog: readonly AiToolCatalogEntry[],
|
|
240
|
+
input: { query: string; appId?: string },
|
|
241
|
+
): { tools: AiToolCatalogItem[] } => {
|
|
242
|
+
const phrase = normalizeSearchText(input.query);
|
|
243
|
+
const terms = searchTerms(input.query);
|
|
244
|
+
if (!phrase || terms.length === 0) return { tools: [] };
|
|
245
|
+
const matches = catalog
|
|
246
|
+
.filter((entry) => !input.appId || entry.appId === input.appId)
|
|
247
|
+
.flatMap((entry) => {
|
|
248
|
+
const name = normalizeSearchText(entry.name);
|
|
249
|
+
const title = normalizeSearchText(entry.title);
|
|
250
|
+
const identity = `${name} ${title}`;
|
|
251
|
+
const app = normalizeSearchText(`${entry.appId ?? ""} ${entry.searchText}`);
|
|
252
|
+
const description = normalizeSearchText(entry.description);
|
|
253
|
+
const identityWords = searchWordForms(identity);
|
|
254
|
+
const appWords = input.appId ? new Set<string>() : searchWordForms(app);
|
|
255
|
+
const descriptionWords = searchWordForms(description);
|
|
256
|
+
let matchedTerms = 0;
|
|
257
|
+
let score = 0;
|
|
258
|
+
if (name === phrase || title === phrase) score += 100;
|
|
259
|
+
else if (includesSearchPhrase(identity, phrase)) score += 50;
|
|
260
|
+
if (!input.appId && app === phrase) score += 40;
|
|
261
|
+
else if (!input.appId && includesSearchPhrase(app, phrase)) score += 20;
|
|
262
|
+
if (includesSearchPhrase(description, phrase)) score += 15;
|
|
263
|
+
for (const term of terms) {
|
|
264
|
+
const identityMatch = includesSearchTerm(identityWords, term);
|
|
265
|
+
const appMatch = includesSearchTerm(appWords, term);
|
|
266
|
+
const descriptionMatch = includesSearchTerm(descriptionWords, term);
|
|
267
|
+
if (identityMatch || appMatch || descriptionMatch) matchedTerms += 1;
|
|
268
|
+
if (identityMatch) score += 8;
|
|
269
|
+
if (appMatch) score += 6;
|
|
270
|
+
if (descriptionMatch) score += 4;
|
|
271
|
+
}
|
|
272
|
+
return matchedTerms > 0 ? [{ entry, matchedTerms, score }] : [];
|
|
273
|
+
})
|
|
274
|
+
.sort(
|
|
275
|
+
(left, right) =>
|
|
276
|
+
right.matchedTerms - left.matchedTerms || right.score - left.score || left.entry.name.localeCompare(right.entry.name),
|
|
277
|
+
)
|
|
278
|
+
.slice(0, DEFAULT_SEARCH_LIMIT);
|
|
279
|
+
return {
|
|
280
|
+
tools: matches.map(({ entry }) => ({
|
|
281
|
+
name: entry.name,
|
|
282
|
+
title: entry.title,
|
|
283
|
+
description: entry.description,
|
|
284
|
+
kind: entry.kind,
|
|
285
|
+
...(entry.appId ? { appId: entry.appId } : {}),
|
|
286
|
+
})),
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const AiHelpCatalogItemSchema = z
|
|
291
|
+
.object({
|
|
292
|
+
appId: z.string(),
|
|
293
|
+
appName: z.string(),
|
|
294
|
+
kind: z.literal("help"),
|
|
295
|
+
locale: z.string(),
|
|
296
|
+
documentId: z.string(),
|
|
297
|
+
title: z.string(),
|
|
298
|
+
description: z.string().optional(),
|
|
299
|
+
})
|
|
300
|
+
.strict();
|
|
301
|
+
|
|
302
|
+
const AiHelpDocumentSchema = AiHelpCatalogItemSchema.extend({
|
|
303
|
+
markdown: z.string().max(HELP_READ_MAX_CHARS),
|
|
304
|
+
truncated: z.boolean(),
|
|
305
|
+
}).strict();
|
|
306
|
+
|
|
307
|
+
/** Search and read the live Help snapshot without loading one tool per article. */
|
|
308
|
+
export const createAiHelpTools = (registry: readonly HelpRegistryEntry[], locale = "en"): AiRuntimeTool[] => {
|
|
309
|
+
const documents = createHelpCatalog(registry, locale);
|
|
310
|
+
|
|
311
|
+
const search = defineAiTool({
|
|
312
|
+
name: "search_help",
|
|
313
|
+
description:
|
|
314
|
+
"Search installed Cloud app Help when product behavior, settings, workflows, permissions, or app errors are unclear. Use 1-3 concise English product terms and scope appId when known. Returns compact document ids for read_help; skip this tool for straightforward live-data requests.",
|
|
315
|
+
inputSchema: z
|
|
316
|
+
.object({
|
|
317
|
+
query: z.string().trim().min(1).max(200).describe("Product task or concept to find."),
|
|
318
|
+
appId: z.string().trim().min(1).optional().describe("Optional exact Cloud app id."),
|
|
319
|
+
limit: z.number().int().min(1).max(HELP_SEARCH_MAX_LIMIT).optional(),
|
|
320
|
+
})
|
|
321
|
+
.strict(),
|
|
322
|
+
outputSchema: z.object({ documents: z.array(AiHelpCatalogItemSchema).max(HELP_SEARCH_MAX_LIMIT) }).strict(),
|
|
323
|
+
approval: "never",
|
|
324
|
+
}).server(async ({ query, appId, limit }) => ({
|
|
325
|
+
documents: searchHelpCatalog(documents, { query, appId, limit: boundedLimit(limit, DEFAULT_SEARCH_LIMIT, HELP_SEARCH_MAX_LIMIT) }),
|
|
326
|
+
}));
|
|
327
|
+
|
|
328
|
+
const read = defineAiTool({
|
|
329
|
+
name: "read_help",
|
|
330
|
+
description:
|
|
331
|
+
"Read the best matching Cloud app Help article returned by search_help. Pass the same concise search terms so long articles return the relevant bounded sections. Product Help guides behavior but never proves live access or action success.",
|
|
332
|
+
inputSchema: z
|
|
333
|
+
.object({
|
|
334
|
+
appId: z.string().trim().min(1).describe("Exact Cloud app id."),
|
|
335
|
+
documentId: z.string().trim().min(1).describe("Exact Help document id."),
|
|
336
|
+
query: z.string().trim().min(1).max(200).optional().describe("The concise terms used to find the article."),
|
|
337
|
+
})
|
|
338
|
+
.strict(),
|
|
339
|
+
outputSchema: z.object({ document: AiHelpDocumentSchema.nullable() }).strict(),
|
|
340
|
+
approval: "never",
|
|
341
|
+
}).server(async ({ appId, documentId, query }) => ({
|
|
342
|
+
document: readHelpCatalog(documents, { appId, documentId, query }),
|
|
343
|
+
}));
|
|
344
|
+
|
|
345
|
+
return [search, read];
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const resolveHelpRegistry = async (
|
|
349
|
+
listRegistry: () => Promise<HelpRegistryEntry[]>,
|
|
350
|
+
onError?: (error: unknown) => void,
|
|
351
|
+
): Promise<HelpRegistryEntry[]> => {
|
|
352
|
+
try {
|
|
353
|
+
return await listRegistry();
|
|
354
|
+
} catch (error) {
|
|
355
|
+
onError?.(error);
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const resolveCapabilityRegistry = async (
|
|
361
|
+
listRegistry: () => Promise<CapabilityRegistryEntry[]>,
|
|
362
|
+
onError?: (error: unknown) => void,
|
|
363
|
+
): Promise<CapabilityRegistryEntry[]> => {
|
|
364
|
+
try {
|
|
365
|
+
return await listRegistry();
|
|
366
|
+
} catch (error) {
|
|
367
|
+
onError?.(error);
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const SCHEMA_KEYS = new Set([
|
|
373
|
+
"$ref",
|
|
374
|
+
"type",
|
|
375
|
+
"description",
|
|
376
|
+
"format",
|
|
377
|
+
"default",
|
|
378
|
+
"enum",
|
|
379
|
+
"const",
|
|
380
|
+
"minimum",
|
|
381
|
+
"maximum",
|
|
382
|
+
"exclusiveMinimum",
|
|
383
|
+
"exclusiveMaximum",
|
|
384
|
+
"multipleOf",
|
|
385
|
+
"minLength",
|
|
386
|
+
"maxLength",
|
|
387
|
+
"pattern",
|
|
388
|
+
"minItems",
|
|
389
|
+
"maxItems",
|
|
390
|
+
"uniqueItems",
|
|
391
|
+
"minProperties",
|
|
392
|
+
"maxProperties",
|
|
393
|
+
"properties",
|
|
394
|
+
"required",
|
|
395
|
+
"items",
|
|
396
|
+
"prefixItems",
|
|
397
|
+
"additionalProperties",
|
|
398
|
+
"anyOf",
|
|
399
|
+
"oneOf",
|
|
400
|
+
"allOf",
|
|
401
|
+
"not",
|
|
402
|
+
"nullable",
|
|
403
|
+
"$defs",
|
|
404
|
+
"definitions",
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
const reduceSchemaValue = (value: unknown, key?: string): unknown => {
|
|
408
|
+
if (key === "const") return structuredClone(value);
|
|
409
|
+
if (Array.isArray(value)) {
|
|
410
|
+
if (key === "required" || key === "enum" || key === "type") return structuredClone(value);
|
|
411
|
+
return value.map((item) => reduceSchemaValue(item));
|
|
412
|
+
}
|
|
413
|
+
if (!value || typeof value !== "object") return value;
|
|
414
|
+
|
|
415
|
+
const output: Record<string, unknown> = {};
|
|
416
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
417
|
+
if (key === "properties" || key === "$defs" || key === "definitions") {
|
|
418
|
+
output[childKey] = reduceSchemaValue(childValue);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (!SCHEMA_KEYS.has(childKey)) continue;
|
|
422
|
+
output[childKey] = reduceSchemaValue(childValue, childKey);
|
|
423
|
+
}
|
|
424
|
+
return output;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/** Keep the provider-useful shape while leaving authoritative validation in the target app. */
|
|
428
|
+
export const reduceAiCapabilityInputSchema = (schema: Record<string, unknown>): Record<string, unknown> =>
|
|
429
|
+
reduceSchemaValue(schema) as Record<string, unknown>;
|
|
430
|
+
|
|
431
|
+
export const aiCapabilityInputSchema = (schema: Record<string, unknown>): z.ZodType =>
|
|
432
|
+
z.fromJSONSchema(reduceAiCapabilityInputSchema(schema));
|
|
433
|
+
|
|
434
|
+
const ToolCatalogItemSchema = z
|
|
435
|
+
.object({
|
|
436
|
+
name: z.string(),
|
|
437
|
+
title: z.string(),
|
|
438
|
+
description: z.string(),
|
|
439
|
+
kind: z.enum(["builtin", "query", "action"]),
|
|
440
|
+
appId: z.string().optional(),
|
|
441
|
+
})
|
|
442
|
+
.strict();
|
|
443
|
+
|
|
444
|
+
type ToolStateStore = Pick<AiConversationService, "loadTools">;
|
|
445
|
+
|
|
446
|
+
export const createAiToolMetaTools = (input: {
|
|
447
|
+
apps: readonly CapabilityRegistryEntry[];
|
|
448
|
+
catalog: readonly AiToolCatalogEntry[];
|
|
449
|
+
eagerNames: ReadonlySet<string>;
|
|
450
|
+
conversationId: string;
|
|
451
|
+
store: ToolStateStore;
|
|
452
|
+
maxLoadedTools?: number;
|
|
453
|
+
unavailableLoadedNames?: readonly string[];
|
|
454
|
+
}): AiRuntimeTool[] => {
|
|
455
|
+
const apps = buildAiCapabilityAppCatalog(input.apps);
|
|
456
|
+
const directoryEntries: string[] = [];
|
|
457
|
+
let directoryLength = 0;
|
|
458
|
+
for (const app of apps) {
|
|
459
|
+
const entry = `${app.appId} (${app.appName})`;
|
|
460
|
+
const addedLength = entry.length + (directoryEntries.length > 0 ? 2 : 0);
|
|
461
|
+
if (directoryLength + addedLength > MAX_APP_DIRECTORY_DESCRIPTION_CHARS) break;
|
|
462
|
+
directoryEntries.push(entry);
|
|
463
|
+
directoryLength += addedLength;
|
|
464
|
+
}
|
|
465
|
+
const hiddenAppCount = apps.length - directoryEntries.length;
|
|
466
|
+
const liveAppDirectory =
|
|
467
|
+
directoryEntries.length > 0
|
|
468
|
+
? ` Live capability apps: ${directoryEntries.join(", ")}${hiddenAppCount > 0 ? `, and ${hiddenAppCount} more` : ""}.`
|
|
469
|
+
: " No live capability apps are visible in this provider turn; retry discovery later instead of claiming a permanent product limitation.";
|
|
470
|
+
const unavailableLoadedNames = input.unavailableLoadedNames ?? [];
|
|
471
|
+
const unavailableLoadedNotice =
|
|
472
|
+
unavailableLoadedNames.length > 0
|
|
473
|
+
? ` Previously loaded tools currently absent from the live catalog: ${unavailableLoadedNames
|
|
474
|
+
.slice(-MAX_UNAVAILABLE_LOADED_NAMES)
|
|
475
|
+
.join(", ")}${
|
|
476
|
+
unavailableLoadedNames.length > MAX_UNAVAILABLE_LOADED_NAMES
|
|
477
|
+
? `, and ${unavailableLoadedNames.length - MAX_UNAVAILABLE_LOADED_NAMES} more`
|
|
478
|
+
: ""
|
|
479
|
+
}. Treat them as temporarily unavailable; do not infer a permanent product limitation or search repeatedly.`
|
|
480
|
+
: "";
|
|
481
|
+
const search = defineAiTool({
|
|
482
|
+
name: "search_tools",
|
|
483
|
+
description: `Search available Cloud tools and installed app operations by concise task terms.${liveAppDirectory} When the app is known, set its exact appId on the first attempt. Use list_apps only when the owning app is unclear. This only discovers tools; call load_tools with the exact returned names before using deferred tools.${unavailableLoadedNotice}`,
|
|
484
|
+
inputSchema: z
|
|
485
|
+
.object({
|
|
486
|
+
query: z.string().trim().min(1).max(200).describe("What the tool should do."),
|
|
487
|
+
appId: z.string().trim().min(1).optional().describe("Optional exact Cloud app id."),
|
|
488
|
+
})
|
|
489
|
+
.strict(),
|
|
490
|
+
outputSchema: z.object({ tools: z.array(ToolCatalogItemSchema).max(DEFAULT_SEARCH_LIMIT) }).strict(),
|
|
491
|
+
approval: "never",
|
|
492
|
+
}).server(async (args) => searchAiTools(input.catalog, args));
|
|
493
|
+
|
|
494
|
+
const listApps = defineAiTool({
|
|
495
|
+
name: "list_apps",
|
|
496
|
+
description:
|
|
497
|
+
"List live Cloud apps that currently publish AI-accessible operations. Returns exact app ids and concise app descriptions; use an app id to scope search_tools.",
|
|
498
|
+
inputSchema: z
|
|
499
|
+
.object({
|
|
500
|
+
cursor: z.string().max(80).optional(),
|
|
501
|
+
limit: z.number().int().min(1).max(MAX_APP_LIST_LIMIT).optional(),
|
|
502
|
+
})
|
|
503
|
+
.strict(),
|
|
504
|
+
outputSchema: z.object({ apps: z.record(z.string(), z.string()), nextCursor: z.string().optional() }).strict(),
|
|
505
|
+
approval: "never",
|
|
506
|
+
}).server(async (args) => {
|
|
507
|
+
const result = listAiCapabilityApps(apps, args);
|
|
508
|
+
return {
|
|
509
|
+
apps: Object.fromEntries(result.apps.map((app) => [app.appId, app.description])),
|
|
510
|
+
...(result.page.nextCursor ? { nextCursor: result.page.nextCursor } : {}),
|
|
511
|
+
};
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const load = defineAiTool({
|
|
515
|
+
name: "load_tools",
|
|
516
|
+
description:
|
|
517
|
+
"Load stable capability ids such as mail.conversation.list, or exact built-in names returned by search_tools, as ordinary tools for the next model turn. Skills may name capability ids directly, so load them without searching first.",
|
|
518
|
+
inputSchema: z.object({ names: z.array(z.string().trim().min(1)).min(1).max(25) }).strict(),
|
|
519
|
+
outputSchema: z
|
|
520
|
+
.object({
|
|
521
|
+
loaded: z.array(z.string()),
|
|
522
|
+
alreadyLoaded: z.array(z.string()),
|
|
523
|
+
missing: z.array(z.string()),
|
|
524
|
+
evicted: z.array(z.string()),
|
|
525
|
+
titles: z.record(z.string(), z.string()),
|
|
526
|
+
})
|
|
527
|
+
.strict(),
|
|
528
|
+
approval: "never",
|
|
529
|
+
}).server(async ({ names }) => {
|
|
530
|
+
const available = new Set(input.catalog.map((entry) => entry.name));
|
|
531
|
+
const requested = [...new Set(names)];
|
|
532
|
+
const eager = requested.filter((name) => input.eagerNames.has(name));
|
|
533
|
+
const valid = requested.filter((name) => available.has(name) && !input.eagerNames.has(name));
|
|
534
|
+
const missing = requested.filter((name) => !available.has(name));
|
|
535
|
+
const updated = await input.store.loadTools({
|
|
536
|
+
conversationId: input.conversationId,
|
|
537
|
+
names: valid,
|
|
538
|
+
maxLoadedTools: input.maxLoadedTools,
|
|
539
|
+
});
|
|
540
|
+
const alreadyLoaded = [...new Set([...eager, ...updated.alreadyLoaded])];
|
|
541
|
+
const catalogByName = new Map(input.catalog.map((entry) => [entry.name, entry]));
|
|
542
|
+
const titles = Object.fromEntries(
|
|
543
|
+
[...new Set([...updated.loaded, ...alreadyLoaded, ...updated.evicted])].flatMap((name) => {
|
|
544
|
+
const entry = catalogByName.get(name);
|
|
545
|
+
return entry ? [[name, entry.title]] : [];
|
|
546
|
+
}),
|
|
547
|
+
);
|
|
548
|
+
return { ...updated, alreadyLoaded, missing, titles };
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
return [search, load, listApps];
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export const createLoadedAiCapabilityTools = (input: {
|
|
555
|
+
catalog: readonly AiCapabilityCatalogEntry[];
|
|
556
|
+
loadedNames: readonly string[];
|
|
557
|
+
review?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<CapabilityActionReview | null>;
|
|
558
|
+
onReview?: (callId: string, review: CapabilityActionReview) => void;
|
|
559
|
+
execute: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
|
|
560
|
+
}): AiRuntimeTool[] => {
|
|
561
|
+
const byName = new Map(input.catalog.map((entry) => [entry.name, entry]));
|
|
562
|
+
return input.loadedNames.flatMap((name) => {
|
|
563
|
+
const entry = byName.get(name);
|
|
564
|
+
if (!entry) return [];
|
|
565
|
+
return [
|
|
566
|
+
defineAiTool({
|
|
567
|
+
name: entry.providerName,
|
|
568
|
+
canonicalName: entry.name,
|
|
569
|
+
description: `${entry.title}. ${entry.description} Never retry ACTION_OUTCOME_UNKNOWN. Do not retry unchanged after INTERNAL or INVALID_APP_RESPONSE; report the provider error.`,
|
|
570
|
+
inputSchema: aiCapabilityInputSchema(entry.operation.inputSchema),
|
|
571
|
+
outputSchema: z.unknown(),
|
|
572
|
+
// Capability Actions request a custom approval after their optional
|
|
573
|
+
// live review has resolved. The review may supply an app-owned scope.
|
|
574
|
+
approval: "never",
|
|
575
|
+
}).server(async (args, context) => {
|
|
576
|
+
if (entry.kind === "action") {
|
|
577
|
+
const review = (await input.review?.(entry, args, context)) ?? null;
|
|
578
|
+
if (review && context.callId) input.onReview?.(context.callId, review);
|
|
579
|
+
const message =
|
|
580
|
+
review?.message ?? `${entry.appName}: ${entry.title}\nReview the validated arguments below before running this Action.`;
|
|
581
|
+
if (!(await context.requestApproval(message))) throw new Error("Capability Action was rejected by the user.");
|
|
582
|
+
}
|
|
583
|
+
return input.execute(entry, args, context);
|
|
584
|
+
}),
|
|
585
|
+
];
|
|
586
|
+
});
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
export const createAiResourceReaderTool = (input: {
|
|
590
|
+
apps: readonly CapabilityRegistryEntry[];
|
|
591
|
+
catalog: readonly AiCapabilityCatalogEntry[];
|
|
592
|
+
execute: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
|
|
593
|
+
}): AiRuntimeTool =>
|
|
594
|
+
defineAiTool({
|
|
595
|
+
name: "read_cloud_resource",
|
|
596
|
+
description:
|
|
597
|
+
"Read a Cloud resource from its structured reference using the resource type's current canonical reader. Pass refs returned by search, Projects, or other capabilities unchanged; never substitute an id from another resource type.",
|
|
598
|
+
inputSchema: CloudResourceRefSchema,
|
|
599
|
+
outputSchema: z.unknown(),
|
|
600
|
+
approval: "never",
|
|
601
|
+
}).server(async (ref, context) => {
|
|
602
|
+
const appId = cloudResourceRefAppId(ref);
|
|
603
|
+
const app = input.apps.find((candidate) => candidate.appId === appId);
|
|
604
|
+
const reader = app ? resolveCapabilityResourceReader(app.manifest, ref) : null;
|
|
605
|
+
if (!reader) throw new Error(`Cloud resource type ${ref.type} is unknown or has no reader.`);
|
|
606
|
+
const entry = input.catalog.find(
|
|
607
|
+
(candidate) => candidate.appId === appId && candidate.kind === "query" && candidate.operation.localId === reader.localId,
|
|
608
|
+
);
|
|
609
|
+
if (!entry) throw new Error(`Cloud resource reader ${appId}.${reader.localId} is unavailable.`);
|
|
610
|
+
return input.execute(entry, { id: ref.id }, context);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
/** Nessi resolver: one registry/load-state snapshot drives discovery, loading, schemas, and execution per model turn. */
|
|
614
|
+
export const createAiToolResolver =
|
|
615
|
+
(input: {
|
|
616
|
+
conversationId: string;
|
|
617
|
+
actor: RequestActor;
|
|
618
|
+
staticTools: AiRuntimeTool[];
|
|
619
|
+
allowedTools?: readonly string[] | null;
|
|
620
|
+
runtimeContext?: Omit<AiToolPreparationContext, "actor" | "conversationId">;
|
|
621
|
+
store: Pick<AiConversationService, "getLoadedTools" | "loadTools">;
|
|
622
|
+
listRegistry?: () => Promise<CapabilityRegistryEntry[]>;
|
|
623
|
+
onCapabilityRegistryError?: (error: unknown) => void;
|
|
624
|
+
listHelpRegistry?: () => Promise<HelpRegistryEntry[]>;
|
|
625
|
+
onHelpRegistryError?: (error: unknown) => void;
|
|
626
|
+
locale?: string;
|
|
627
|
+
maxLoadedTools?: number;
|
|
628
|
+
execute?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
|
|
629
|
+
review?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<CapabilityActionReview | null>;
|
|
630
|
+
onReview?: (callId: string, review: CapabilityActionReview) => void;
|
|
631
|
+
onPrepared?: (snapshot: {
|
|
632
|
+
prepared: PreparedAiTools;
|
|
633
|
+
presentations: Map<string, AiToolPresentation>;
|
|
634
|
+
rememberableApprovals: AiRememberableCapabilityApprovals;
|
|
635
|
+
}) => void;
|
|
636
|
+
}): ToolResolver =>
|
|
637
|
+
async (): Promise<Tool[]> => {
|
|
638
|
+
const [registry, persistedLoadedNames, helpRegistry] = await Promise.all([
|
|
639
|
+
input.listRegistry ? resolveCapabilityRegistry(input.listRegistry, input.onCapabilityRegistryError) : [],
|
|
640
|
+
input.store.getLoadedTools({ conversationId: input.conversationId }),
|
|
641
|
+
input.listHelpRegistry ? resolveHelpRegistry(input.listHelpRegistry, input.onHelpRegistryError) : [],
|
|
642
|
+
]);
|
|
643
|
+
const configuredLimit = Math.floor(input.maxLoadedTools ?? 0);
|
|
644
|
+
const loadedNames = configuredLimit > 0 ? persistedLoadedNames.slice(-configuredLimit) : persistedLoadedNames;
|
|
645
|
+
if (loadedNames.length !== persistedLoadedNames.length) {
|
|
646
|
+
await input.store.loadTools({
|
|
647
|
+
conversationId: input.conversationId,
|
|
648
|
+
names: [],
|
|
649
|
+
maxLoadedTools: configuredLimit,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
const allowed = input.allowedTools == null ? null : new Set(input.allowedTools);
|
|
653
|
+
const capabilityCatalog = buildAiCapabilityCatalog(registry).filter((entry) => !allowed || allowed.has(entry.name));
|
|
654
|
+
const helpTools = input.listHelpRegistry ? createAiHelpTools(helpRegistry, input.locale) : [];
|
|
655
|
+
const resourceTool =
|
|
656
|
+
input.execute && capabilityCatalog.length > 0
|
|
657
|
+
? createAiResourceReaderTool({ apps: registry, catalog: capabilityCatalog, execute: input.execute })
|
|
658
|
+
: null;
|
|
659
|
+
const builtIns = [...input.staticTools, ...helpTools, ...(resourceTool ? [resourceTool] : [])].filter(
|
|
660
|
+
(tool) => !allowed || allowed.has(tool.def.name),
|
|
661
|
+
);
|
|
662
|
+
const catalog = buildAiToolCatalog(builtIns, capabilityCatalog);
|
|
663
|
+
const catalogNames = new Set(catalog.map((entry) => entry.name));
|
|
664
|
+
const unavailableLoadedNames = loadedNames.filter((name) => !catalogNames.has(name));
|
|
665
|
+
const eagerNames = new Set(builtIns.map((tool) => tool.def.name).filter((name) => !CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES.has(name)));
|
|
666
|
+
const activeBuiltIns = builtIns.filter((tool) => eagerNames.has(tool.def.name) || loadedNames.includes(tool.def.name));
|
|
667
|
+
const runtimeTools = [
|
|
668
|
+
...createAiToolMetaTools({
|
|
669
|
+
apps: allowed ? registry.filter((app) => capabilityCatalog.some((entry) => entry.appId === app.appId)) : registry,
|
|
670
|
+
catalog,
|
|
671
|
+
eagerNames,
|
|
672
|
+
conversationId: input.conversationId,
|
|
673
|
+
store: input.store,
|
|
674
|
+
maxLoadedTools: input.maxLoadedTools,
|
|
675
|
+
unavailableLoadedNames,
|
|
676
|
+
}),
|
|
677
|
+
...activeBuiltIns,
|
|
678
|
+
...(input.execute
|
|
679
|
+
? createLoadedAiCapabilityTools({
|
|
680
|
+
catalog: capabilityCatalog,
|
|
681
|
+
loadedNames,
|
|
682
|
+
review: input.review,
|
|
683
|
+
onReview: input.onReview,
|
|
684
|
+
execute: input.execute,
|
|
685
|
+
})
|
|
686
|
+
: []),
|
|
687
|
+
];
|
|
688
|
+
const prepared = prepareAiTools({
|
|
689
|
+
tools: runtimeTools,
|
|
690
|
+
...input.runtimeContext,
|
|
691
|
+
actor: input.actor,
|
|
692
|
+
conversationId: input.conversationId,
|
|
693
|
+
});
|
|
694
|
+
const catalogByName = new Map(capabilityCatalog.map((entry) => [entry.name, entry]));
|
|
695
|
+
const presentations = new Map<string, AiToolPresentation>();
|
|
696
|
+
const rememberableApprovals = new Map<string, string>();
|
|
697
|
+
for (const name of loadedNames) {
|
|
698
|
+
const entry = catalogByName.get(name);
|
|
699
|
+
if (!entry) continue;
|
|
700
|
+
presentations.set(entry.providerName, {
|
|
701
|
+
kind: "capability",
|
|
702
|
+
appId: entry.appId,
|
|
703
|
+
appName: entry.appName,
|
|
704
|
+
appIcon: entry.app.appIcon,
|
|
705
|
+
appAccent: entry.app.appAccent,
|
|
706
|
+
title: entry.title,
|
|
707
|
+
capabilityKind: entry.kind,
|
|
708
|
+
});
|
|
709
|
+
// Rememberable scopes are resolved by the owning app for each concrete
|
|
710
|
+
// call and attached when its live review completes.
|
|
711
|
+
}
|
|
712
|
+
input.onPrepared?.({ prepared, presentations, rememberableApprovals });
|
|
713
|
+
return prepared.tools;
|
|
714
|
+
};
|