@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
package/src/ai/stream.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { lazySync } from "../_internal/process-sync";
|
|
2
|
+
import { logger } from "../services/logging";
|
|
3
|
+
import { latestTopicCursor } from "../services/topic-cursor";
|
|
4
|
+
import {
|
|
5
|
+
type AiStreamEvent,
|
|
6
|
+
type AiTurnSnapshot,
|
|
7
|
+
type AiWireEvent,
|
|
8
|
+
isNewerWireEvent,
|
|
9
|
+
reconcileResolvedTurnActions,
|
|
10
|
+
steerAppliedBlockId,
|
|
11
|
+
steerMessageBlockId,
|
|
12
|
+
} from "./protocol";
|
|
13
|
+
import { projectPublicAiStoredMessages, publicAiStoredMessages } from "./public-projection";
|
|
14
|
+
import { aiConversations } from "./store";
|
|
15
|
+
import type { AiConversation } from "./types";
|
|
16
|
+
|
|
17
|
+
const log = logger("ai:stream");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Live fanout for wire events. Events carry their full payload so the SSE hot
|
|
21
|
+
* path never touches Postgres; durable state lives in ai.messages plus the
|
|
22
|
+
* throttled ai.turns.live_blocks snapshot.
|
|
23
|
+
*/
|
|
24
|
+
export const aiStreamTopic = lazySync((sync) =>
|
|
25
|
+
sync.topic<AiWireEvent>({
|
|
26
|
+
id: "cloud-ai-stream",
|
|
27
|
+
owner: "cloud",
|
|
28
|
+
retention: { maxAgeMs: 15 * 60 * 1000, maxBytes: 256 * 1024 * 1024 },
|
|
29
|
+
maxPayloadBytes: 257 * 1024,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export type AiTurnControlEvent = { type: "abort"; conversationId: string; turnId: string };
|
|
34
|
+
|
|
35
|
+
export const aiTurnControlsTopic = lazySync((sync) =>
|
|
36
|
+
sync.topic<AiTurnControlEvent>({
|
|
37
|
+
id: "cloud-ai-turn-controls",
|
|
38
|
+
owner: "cloud",
|
|
39
|
+
// Abort signals are rare and tiny (5 KiB cap): 4 MiB keeps ~800 of them
|
|
40
|
+
// across the 15-minute window.
|
|
41
|
+
retention: { maxAgeMs: 15 * 60 * 1000, maxBytes: 4 * 1024 * 1024 },
|
|
42
|
+
maxPayloadBytes: 5 * 1024,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const publishAiWireEvent = async (event: AiWireEvent): Promise<void> => {
|
|
47
|
+
await aiStreamTopic().publish({
|
|
48
|
+
tenantId: event.conversationId,
|
|
49
|
+
orderingKey: event.turnId,
|
|
50
|
+
data: event,
|
|
51
|
+
idempotencyKey: `wire:${event.turnId}:${event.attempt}:${event.seq}`,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const publishAiTurnAbort = async (input: { conversationId: string; turnId: string }): Promise<void> => {
|
|
56
|
+
await aiTurnControlsTopic().publish({
|
|
57
|
+
tenantId: input.conversationId,
|
|
58
|
+
orderingKey: input.turnId,
|
|
59
|
+
data: { type: "abort", conversationId: input.conversationId, turnId: input.turnId },
|
|
60
|
+
idempotencyKey: `turn-abort:${input.turnId}`,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const encoder = new TextEncoder();
|
|
65
|
+
const DEFAULT_HEARTBEAT_MS = 5_000;
|
|
66
|
+
|
|
67
|
+
export const sseHeaders = {
|
|
68
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
69
|
+
"Cache-Control": "no-cache, no-transform",
|
|
70
|
+
Connection: "keep-alive",
|
|
71
|
+
"X-Accel-Buffering": "no",
|
|
72
|
+
} as const;
|
|
73
|
+
|
|
74
|
+
export const encodeSseEvent = (event: AiStreamEvent): Uint8Array =>
|
|
75
|
+
encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
76
|
+
|
|
77
|
+
export const encodeSseHeartbeat = (): Uint8Array => encoder.encode(": heartbeat\n\n");
|
|
78
|
+
|
|
79
|
+
const turnSnapshotFromActive = (active: NonNullable<Awaited<ReturnType<typeof aiConversations.getActiveTurn>>>): AiTurnSnapshot => ({
|
|
80
|
+
turnId: active.turn.shortId,
|
|
81
|
+
attempt: active.turn.attempt,
|
|
82
|
+
status: active.turn.status,
|
|
83
|
+
seq: active.liveSeq,
|
|
84
|
+
blocks: [...active.liveBlocks],
|
|
85
|
+
modelProfileId: active.turn.modelProfileId,
|
|
86
|
+
createdAt: active.turn.createdAt,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/** Initial history window; older messages load on demand while scrolling up. */
|
|
90
|
+
export const AI_STREAM_INITIAL_MESSAGE_LIMIT = 100;
|
|
91
|
+
|
|
92
|
+
export const loadAiStreamState = async (conversation: AiConversation): Promise<Extract<AiStreamEvent, { type: "state" }>> => {
|
|
93
|
+
const [page, active] = await Promise.all([
|
|
94
|
+
aiConversations.listMessagesPage({ conversationId: conversation.id, limit: AI_STREAM_INITIAL_MESSAGE_LIMIT }),
|
|
95
|
+
aiConversations.getActiveTurn({ conversationId: conversation.id }),
|
|
96
|
+
]);
|
|
97
|
+
const snapshot = active ? turnSnapshotFromActive(active) : null;
|
|
98
|
+
if (snapshot) {
|
|
99
|
+
const [steers, resolvedActions] = await Promise.all([
|
|
100
|
+
aiConversations.listTurnSteers({ conversationId: conversation.id, turnId: active!.turn.id }),
|
|
101
|
+
aiConversations.listResolvedPendingActions({ conversationId: conversation.id, turnId: active!.turn.id }),
|
|
102
|
+
]);
|
|
103
|
+
snapshot.blocks = reconcileResolvedTurnActions(snapshot.blocks, resolvedActions);
|
|
104
|
+
const known = new Set(snapshot.blocks.map((block) => block.id));
|
|
105
|
+
for (const steer of steers) {
|
|
106
|
+
if (steer.status === "discarded" || known.has(steerMessageBlockId(steer.id))) continue;
|
|
107
|
+
snapshot.blocks.push({
|
|
108
|
+
id: steerMessageBlockId(steer.id),
|
|
109
|
+
kind: "steer_message",
|
|
110
|
+
steerId: steer.id,
|
|
111
|
+
text: steer.text,
|
|
112
|
+
status: steer.status === "pending" ? "pending" : "consumed",
|
|
113
|
+
});
|
|
114
|
+
if (steer.status === "consumed" && !known.has(steerAppliedBlockId(steer.id))) {
|
|
115
|
+
snapshot.blocks.push({ id: steerAppliedBlockId(steer.id), kind: "steer_applied", steerId: steer.id });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
type: "state",
|
|
121
|
+
conversation: { ...conversation, id: conversation.shortId },
|
|
122
|
+
messages: await publicAiStoredMessages(page.messages, conversation),
|
|
123
|
+
hasMoreMessages: page.hasMore,
|
|
124
|
+
activeTurn: snapshot,
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type SnapshotTailItem<TSnapshot, TEvent> = { kind: "snapshot"; value: TSnapshot } | { kind: "event"; value: TEvent };
|
|
129
|
+
|
|
130
|
+
async function* streamSnapshotThenTail<TCursor, TSnapshot, TEvent>(input: {
|
|
131
|
+
captureCursor: () => Promise<TCursor>;
|
|
132
|
+
loadSnapshot: () => Promise<TSnapshot>;
|
|
133
|
+
tail: (cursor: TCursor) => AsyncIterable<TEvent>;
|
|
134
|
+
}): AsyncGenerator<SnapshotTailItem<TSnapshot, TEvent>> {
|
|
135
|
+
const cursor = await input.captureCursor();
|
|
136
|
+
yield { kind: "snapshot", value: await input.loadSnapshot() };
|
|
137
|
+
for await (const event of input.tail(cursor)) yield { kind: "event", value: event };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Transport-neutral conversation stream: one `state` snapshot, then the live
|
|
142
|
+
* tail. SSE and WebSocket adapters must both consume this feed.
|
|
143
|
+
*
|
|
144
|
+
* The topic cursor is grabbed before the snapshot is loaded, so every event
|
|
145
|
+
* that races the snapshot is replayed from the tail and deduplicated via
|
|
146
|
+
* (attempt, seq). Events of unknown turns are dropped until their
|
|
147
|
+
* `turn_started` arrives, which makes stale retention entries harmless.
|
|
148
|
+
* A memoized per-conversation hub preserves this snapshot race guarantee. While
|
|
149
|
+
* a conversation has subscribers it costs one full-topic follower per process
|
|
150
|
+
* (Sync filters tenants locally); the hub retires when the last subscriber
|
|
151
|
+
* leaves. live() has no subscription-ready barrier.
|
|
152
|
+
*/
|
|
153
|
+
export async function* streamAiConversationEvents(input: {
|
|
154
|
+
conversation: AiConversation;
|
|
155
|
+
signal: AbortSignal;
|
|
156
|
+
}): AsyncGenerator<AiStreamEvent> {
|
|
157
|
+
let current: { turnId: string; publicTurnId: string; attempt: number; seq: number } | null = null;
|
|
158
|
+
for await (const item of streamSnapshotThenTail({
|
|
159
|
+
captureCursor: () => latestTopicCursor({ topic: aiStreamTopic(), resourceId: "cloud-ai-stream", tenantId: input.conversation.id }),
|
|
160
|
+
loadSnapshot: () => loadAiStreamState(input.conversation),
|
|
161
|
+
tail: (after) => aiStreamTopic().hub({ tenantId: input.conversation.id }).subscribe({ after, signal: input.signal }),
|
|
162
|
+
})) {
|
|
163
|
+
if (item.kind === "snapshot") {
|
|
164
|
+
const state = item.value;
|
|
165
|
+
yield state;
|
|
166
|
+
const activeTurn = state.activeTurn
|
|
167
|
+
? await aiConversations.getTurnByShortId({
|
|
168
|
+
conversationId: input.conversation.id,
|
|
169
|
+
shortId: state.activeTurn.turnId,
|
|
170
|
+
})
|
|
171
|
+
: null;
|
|
172
|
+
current =
|
|
173
|
+
state.activeTurn && activeTurn
|
|
174
|
+
? {
|
|
175
|
+
turnId: activeTurn.id,
|
|
176
|
+
publicTurnId: state.activeTurn.turnId,
|
|
177
|
+
attempt: state.activeTurn.attempt,
|
|
178
|
+
seq: state.activeTurn.seq,
|
|
179
|
+
}
|
|
180
|
+
: null;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const received = item.value;
|
|
185
|
+
const event = received.data;
|
|
186
|
+
if (current?.turnId === event.turnId) {
|
|
187
|
+
if (!isNewerWireEvent(event, current)) continue;
|
|
188
|
+
} else if (event.type !== "turn_started") {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const publicTurnId =
|
|
192
|
+
current?.turnId === event.turnId
|
|
193
|
+
? current.publicTurnId
|
|
194
|
+
: (await aiConversations.getTurn({ conversationId: input.conversation.id, turnId: event.turnId }))?.shortId;
|
|
195
|
+
if (!publicTurnId) continue;
|
|
196
|
+
current = { turnId: event.turnId, publicTurnId, attempt: event.attempt, seq: event.seq };
|
|
197
|
+
if (event.type === "turn_finished") {
|
|
198
|
+
const messages = await aiConversations
|
|
199
|
+
.listTurnMessages({ conversationId: event.conversationId, loopId: event.turnId })
|
|
200
|
+
.catch(() => []);
|
|
201
|
+
yield {
|
|
202
|
+
...event,
|
|
203
|
+
conversationId: input.conversation.shortId,
|
|
204
|
+
turnId: publicTurnId,
|
|
205
|
+
messages: projectPublicAiStoredMessages(messages, input.conversation.shortId, new Map([[event.turnId, publicTurnId]])),
|
|
206
|
+
};
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
yield { ...event, conversationId: input.conversation.shortId, turnId: publicTurnId };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const __aiStreamTest = { streamSnapshotThenTail };
|
|
215
|
+
|
|
216
|
+
/** Conversation-scoped SSE adapter for the shared event feed. */
|
|
217
|
+
export const createAiConversationStreamResponse = (input: {
|
|
218
|
+
conversation: AiConversation;
|
|
219
|
+
signal?: AbortSignal;
|
|
220
|
+
heartbeatMs?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Re-checked on every heartbeat. A stream can outlive the grant that opened
|
|
223
|
+
* it by hours, and authorizing once at connect time means a withdrawn grant
|
|
224
|
+
* keeps delivering model output until the client happens to disconnect.
|
|
225
|
+
* Returning false closes the stream; the client's reconnect then meets the
|
|
226
|
+
* ordinary 403.
|
|
227
|
+
*/
|
|
228
|
+
revalidate?: () => Promise<boolean>;
|
|
229
|
+
}): Response => {
|
|
230
|
+
const heartbeatMs = input.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
|
|
231
|
+
const liveAbort = new AbortController();
|
|
232
|
+
let heartbeat: ReturnType<typeof setInterval> | undefined;
|
|
233
|
+
let closed = false;
|
|
234
|
+
|
|
235
|
+
const abortLive = () => liveAbort.abort();
|
|
236
|
+
if (input.signal?.aborted) abortLive();
|
|
237
|
+
else input.signal?.addEventListener("abort", abortLive, { once: true });
|
|
238
|
+
|
|
239
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
240
|
+
async start(controller) {
|
|
241
|
+
const enqueue = (chunk: Uint8Array): boolean => {
|
|
242
|
+
if (closed) return false;
|
|
243
|
+
try {
|
|
244
|
+
controller.enqueue(chunk);
|
|
245
|
+
return true;
|
|
246
|
+
} catch {
|
|
247
|
+
close();
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const close = () => {
|
|
252
|
+
if (closed) return;
|
|
253
|
+
closed = true;
|
|
254
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
255
|
+
heartbeat = undefined;
|
|
256
|
+
input.signal?.removeEventListener("abort", abortLive);
|
|
257
|
+
abortLive();
|
|
258
|
+
try {
|
|
259
|
+
controller.close();
|
|
260
|
+
} catch {
|
|
261
|
+
// The client may already have cancelled the stream.
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
if (heartbeatMs > 0) {
|
|
266
|
+
heartbeat = setInterval(() => {
|
|
267
|
+
if (!enqueue(encodeSseHeartbeat())) return;
|
|
268
|
+
if (!input.revalidate) return;
|
|
269
|
+
// Fail closed: a revalidation that throws is not a pass.
|
|
270
|
+
void input
|
|
271
|
+
.revalidate()
|
|
272
|
+
.catch((error) => {
|
|
273
|
+
log.warn("AI conversation stream revalidation failed", {
|
|
274
|
+
conversationId: input.conversation.id,
|
|
275
|
+
error: error instanceof Error ? error.message : "revalidation failed",
|
|
276
|
+
});
|
|
277
|
+
return false;
|
|
278
|
+
})
|
|
279
|
+
.then((allowed) => {
|
|
280
|
+
if (!allowed) close();
|
|
281
|
+
});
|
|
282
|
+
}, heartbeatMs);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
for await (const event of streamAiConversationEvents({ conversation: input.conversation, signal: liveAbort.signal })) {
|
|
287
|
+
if (!enqueue(encodeSseEvent(event))) return;
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (!liveAbort.signal.aborted) {
|
|
291
|
+
log.warn("AI conversation stream failed", {
|
|
292
|
+
conversationId: input.conversation.id,
|
|
293
|
+
error: error instanceof Error ? error.message : "AI conversation stream failed",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
} finally {
|
|
297
|
+
close();
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
cancel() {
|
|
301
|
+
closed = true;
|
|
302
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
303
|
+
abortLive();
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return new Response(stream, { headers: sseHeaders });
|
|
308
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Usage } from "@k2b/nessi";
|
|
2
|
+
import { sql } from "bun";
|
|
3
|
+
import { logger } from "../services/logging";
|
|
4
|
+
|
|
5
|
+
export type AiUsageAttribution = {
|
|
6
|
+
userId?: string;
|
|
7
|
+
conversationId?: string;
|
|
8
|
+
turnId?: string;
|
|
9
|
+
workflowRunId?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type AiStructuredRunRecord = {
|
|
13
|
+
attribution?: AiUsageAttribution;
|
|
14
|
+
traceId?: string;
|
|
15
|
+
task: string;
|
|
16
|
+
appId?: string;
|
|
17
|
+
modelProfileId?: string;
|
|
18
|
+
providerModel?: string;
|
|
19
|
+
status: "ok" | "failed";
|
|
20
|
+
durationMs: number;
|
|
21
|
+
usage?: Usage;
|
|
22
|
+
mode?: string;
|
|
23
|
+
repaired?: boolean;
|
|
24
|
+
attempts?: number;
|
|
25
|
+
errorCode?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const usageNumber = (usage: Usage | undefined, key: "input" | "output" | "total" | "creditsUsed") => {
|
|
30
|
+
const value = (usage as Partial<Record<typeof key, unknown>> | undefined)?.[key];
|
|
31
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Metadata-only durable accounting. Prompt, input, and output content never enter this ledger. */
|
|
35
|
+
export const recordAiStructuredRun = async (record: AiStructuredRunRecord): Promise<void> => {
|
|
36
|
+
await sql`
|
|
37
|
+
INSERT INTO ai.structured_runs (
|
|
38
|
+
task, app_id, model_profile_id, provider_model, status, duration_ms,
|
|
39
|
+
input_tokens, output_tokens, total_tokens, credits_used,
|
|
40
|
+
mode, repaired, attempts, error_code, error, user_id, conversation_id, turn_id, workflow_run_id, trace_id
|
|
41
|
+
) VALUES (
|
|
42
|
+
${record.task}, ${record.appId ?? null}, ${record.modelProfileId ?? null}, ${record.providerModel ?? null},
|
|
43
|
+
${record.status}, ${Math.max(0, Math.round(record.durationMs))},
|
|
44
|
+
${usageNumber(record.usage, "input")}, ${usageNumber(record.usage, "output")},
|
|
45
|
+
${usageNumber(record.usage, "total")}, ${usageNumber(record.usage, "creditsUsed")},
|
|
46
|
+
${record.mode ?? null}, ${record.repaired ?? null}, ${record.attempts ?? null},
|
|
47
|
+
${record.errorCode ?? null}, ${record.error?.slice(0, 2_000) ?? null},
|
|
48
|
+
COALESCE(${record.attribution?.userId ?? null}::uuid,
|
|
49
|
+
(SELECT created_by_user_id FROM ai.conversations WHERE id = ${record.attribution?.conversationId ?? null}::uuid)),
|
|
50
|
+
${record.attribution?.conversationId ?? null}::uuid, ${record.attribution?.turnId ?? null}::uuid,
|
|
51
|
+
${record.attribution?.workflowRunId ?? null}::uuid, ${record.traceId ?? null}
|
|
52
|
+
)
|
|
53
|
+
`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Accounting failure must not turn a completed inference into a retry. */
|
|
57
|
+
export const safelyRecordStructuredRun = async (record: AiStructuredRunRecord): Promise<void> => {
|
|
58
|
+
try {
|
|
59
|
+
await recordAiStructuredRun(record);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger("ai.structured").error("Could not persist AI structured-run usage", {
|
|
62
|
+
task: record.task,
|
|
63
|
+
error: error instanceof Error ? error.message : String(error),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Input, LoopAggregate, StructuredMeta, Usage } from "@k2b/nessi";
|
|
2
|
+
import { nessi, StructuredOutputError } from "@k2b/nessi";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import { coreSettings } from "../services";
|
|
5
|
+
import type { TraceContext } from "../services/logging";
|
|
6
|
+
import { trace } from "../services/logging";
|
|
7
|
+
import { resolveAiModel } from "./settings";
|
|
8
|
+
import { type AiUsageAttribution, safelyRecordStructuredRun } from "./structured-runs";
|
|
9
|
+
import type { AiResolvedModel } from "./types";
|
|
10
|
+
|
|
11
|
+
export const AI_BACKGROUND_MODEL_SETTING_KEY = "ai.background_model_id";
|
|
12
|
+
export const AI_WORKFLOW_MODEL_SETTING_KEY = "ai.workflow_model_id";
|
|
13
|
+
|
|
14
|
+
export const selectAiWorkflowModelId = (input: {
|
|
15
|
+
requestedModelId?: string;
|
|
16
|
+
workflowModelId?: string;
|
|
17
|
+
backgroundModelId?: string;
|
|
18
|
+
}): string | undefined => input.requestedModelId?.trim() || input.workflowModelId?.trim() || input.backgroundModelId?.trim() || undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the model for background inference: explicit request →
|
|
22
|
+
* `ai.background_model_id` setting → platform default. Throws when AI is
|
|
23
|
+
* disabled or the model is unavailable — callers skip their work then.
|
|
24
|
+
*/
|
|
25
|
+
export const resolveAiBackgroundModel = async (requestedModelId?: string): Promise<AiResolvedModel> => {
|
|
26
|
+
const backgroundModelId = String((await coreSettings.get<string>(AI_BACKGROUND_MODEL_SETTING_KEY)) ?? "").trim();
|
|
27
|
+
return resolveAiModel({ kind: "selectable" }, requestedModelId?.trim() || backgroundModelId || undefined);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Resolve a workflow model: action override → workflow setting → background setting → platform default. */
|
|
31
|
+
export const resolveAiWorkflowModel = async (requestedModelId?: string): Promise<AiResolvedModel> => {
|
|
32
|
+
const [workflowModelId, backgroundModelId] = await Promise.all([
|
|
33
|
+
coreSettings.get<string>(AI_WORKFLOW_MODEL_SETTING_KEY),
|
|
34
|
+
coreSettings.get<string>(AI_BACKGROUND_MODEL_SETTING_KEY),
|
|
35
|
+
]);
|
|
36
|
+
return resolveAiModel(
|
|
37
|
+
{ kind: "selectable" },
|
|
38
|
+
selectAiWorkflowModelId({
|
|
39
|
+
requestedModelId,
|
|
40
|
+
workflowModelId: String(workflowModelId ?? ""),
|
|
41
|
+
backgroundModelId: String(backgroundModelId ?? ""),
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type RunAiStructuredInput<TOutput extends z.ZodType> = {
|
|
47
|
+
/** Short machine name for tracing, e.g. "chat-enrich". */
|
|
48
|
+
task: string;
|
|
49
|
+
input: Input;
|
|
50
|
+
output: TOutput;
|
|
51
|
+
outputName?: string;
|
|
52
|
+
systemPrompt?: string;
|
|
53
|
+
requestedModelId?: string;
|
|
54
|
+
temperature?: number;
|
|
55
|
+
maxOutputTokens?: number;
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
/** Parent trace span when the caller already runs inside one (e.g. a sync job). */
|
|
58
|
+
traceParent?: TraceContext;
|
|
59
|
+
appId?: string;
|
|
60
|
+
/** Metadata-only attribution, supplied by the authorized caller. */
|
|
61
|
+
attribution?: AiUsageAttribution;
|
|
62
|
+
/** Model resolution seam — tests inject a fake so they never touch shared settings. */
|
|
63
|
+
resolveModel?: (requestedModelId?: string) => Promise<AiResolvedModel>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type RunAiStructuredResult<TOutput extends z.ZodType> = {
|
|
67
|
+
output: z.infer<TOutput>;
|
|
68
|
+
modelProfileId: string;
|
|
69
|
+
usage?: Usage;
|
|
70
|
+
structuredMeta: StructuredMeta;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* One schema-valid background inference via nessi.structured, wrapped in a
|
|
75
|
+
* trace span (events: model.resolved, llm.completed — metadata only, never
|
|
76
|
+
* prompt or output content).
|
|
77
|
+
*/
|
|
78
|
+
export const runAiStructured = async <TOutput extends z.ZodType>(
|
|
79
|
+
input: RunAiStructuredInput<TOutput>,
|
|
80
|
+
): Promise<RunAiStructuredResult<TOutput>> => {
|
|
81
|
+
return trace.withSpan(
|
|
82
|
+
{
|
|
83
|
+
name: `ai.structured.${input.task}`,
|
|
84
|
+
source: `ai:structured:${input.task}`,
|
|
85
|
+
appId: input.appId,
|
|
86
|
+
category: "ai",
|
|
87
|
+
parent: input.traceParent,
|
|
88
|
+
},
|
|
89
|
+
async (span) => {
|
|
90
|
+
const startedAt = Date.now();
|
|
91
|
+
let resolved: AiResolvedModel | undefined;
|
|
92
|
+
try {
|
|
93
|
+
resolved = await (input.resolveModel ?? resolveAiBackgroundModel)(input.requestedModelId);
|
|
94
|
+
await trace.record({
|
|
95
|
+
context: span,
|
|
96
|
+
event: "model.resolved",
|
|
97
|
+
attributes: { model: resolved.profile.id, providerModel: resolved.profile.model, provider: resolved.profile.provider },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const result = await nessi.structured({
|
|
101
|
+
agentId: "cloud-bg",
|
|
102
|
+
provider: resolved.provider,
|
|
103
|
+
systemPrompt: input.systemPrompt,
|
|
104
|
+
input: input.input,
|
|
105
|
+
output: input.output,
|
|
106
|
+
outputName: input.outputName,
|
|
107
|
+
temperature: input.temperature ?? 0,
|
|
108
|
+
maxOutputTokens: input.maxOutputTokens ?? resolved.profile.maxOutputTokens,
|
|
109
|
+
disableReasoning: true,
|
|
110
|
+
signal: input.signal,
|
|
111
|
+
});
|
|
112
|
+
const durationMs = Date.now() - startedAt;
|
|
113
|
+
|
|
114
|
+
await trace.record({
|
|
115
|
+
context: span,
|
|
116
|
+
event: "llm.completed",
|
|
117
|
+
attributes: {
|
|
118
|
+
model: resolved.profile.id,
|
|
119
|
+
durationMs,
|
|
120
|
+
mode: result.structuredMeta.mode,
|
|
121
|
+
repaired: result.structuredMeta.repaired,
|
|
122
|
+
attempts: result.structuredMeta.attempts,
|
|
123
|
+
inputTokens: result.usage?.input,
|
|
124
|
+
outputTokens: result.usage?.output,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
await safelyRecordStructuredRun({
|
|
128
|
+
task: input.task,
|
|
129
|
+
attribution: input.attribution,
|
|
130
|
+
traceId: span.traceId,
|
|
131
|
+
appId: input.appId,
|
|
132
|
+
modelProfileId: resolved.profile.id,
|
|
133
|
+
providerModel: resolved.profile.model,
|
|
134
|
+
status: "ok",
|
|
135
|
+
durationMs,
|
|
136
|
+
usage: result.usage,
|
|
137
|
+
mode: result.structuredMeta.mode,
|
|
138
|
+
repaired: result.structuredMeta.repaired,
|
|
139
|
+
attempts: result.structuredMeta.attempts,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
output: result.output,
|
|
144
|
+
modelProfileId: resolved.profile.id,
|
|
145
|
+
usage: result.usage,
|
|
146
|
+
structuredMeta: result.structuredMeta,
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const details =
|
|
150
|
+
error instanceof StructuredOutputError
|
|
151
|
+
? (error.details as { attempts?: number; aggregate?: LoopAggregate } | undefined)
|
|
152
|
+
: undefined;
|
|
153
|
+
await safelyRecordStructuredRun({
|
|
154
|
+
task: input.task,
|
|
155
|
+
attribution: input.attribution,
|
|
156
|
+
traceId: span.traceId,
|
|
157
|
+
appId: input.appId,
|
|
158
|
+
modelProfileId: resolved?.profile.id,
|
|
159
|
+
providerModel: resolved?.profile.model,
|
|
160
|
+
status: "failed",
|
|
161
|
+
durationMs: Date.now() - startedAt,
|
|
162
|
+
usage: details?.aggregate?.usage,
|
|
163
|
+
attempts: details?.attempts,
|
|
164
|
+
errorCode: error instanceof StructuredOutputError ? error.code : error instanceof Error ? error.name : "unknown",
|
|
165
|
+
error: error instanceof Error ? error.message : String(error),
|
|
166
|
+
});
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
summarize: (result) => ({ model: result.modelProfileId, mode: result.structuredMeta.mode, repaired: result.structuredMeta.repaired }),
|
|
172
|
+
onError: (error) => (error instanceof StructuredOutputError ? structuredFailureSummary(error) : undefined),
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/** Metadata-only failure diagnostics: error code, attempts, and per-attempt stop reasons (catches max_tokens truncation). */
|
|
178
|
+
const structuredFailureSummary = (error: StructuredOutputError): Record<string, unknown> => {
|
|
179
|
+
const details = error.details as { attempts?: number; aggregate?: LoopAggregate } | undefined;
|
|
180
|
+
return {
|
|
181
|
+
code: error.code,
|
|
182
|
+
attempts: details?.attempts,
|
|
183
|
+
stopReasons: details?.aggregate?.turns?.map((turn) => turn.stopReason ?? "unknown").join(","),
|
|
184
|
+
outputTokens: details?.aggregate?.usage?.output,
|
|
185
|
+
};
|
|
186
|
+
};
|