@k2b/cloud 0.7.0 → 0.8.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.
Files changed (182) hide show
  1. package/package.json +5 -4
  2. package/scripts/README.md +10 -0
  3. package/scripts/build.ts +6 -1
  4. package/scripts/preload.ts +7 -0
  5. package/scripts/sync-recovery-smoke.ts +21 -6
  6. package/src/_internal/build-metadata.ts +8 -1
  7. package/src/_internal/capabilities.ts +35 -12
  8. package/src/_internal/define-app.ts +28 -11
  9. package/src/_internal/page-responses.ts +1 -1
  10. package/src/_internal/readiness.ts +4 -1
  11. package/src/_internal/runtime-context.ts +1 -1
  12. package/src/access/PermissionEditor.tsx +15 -9
  13. package/src/access/PrincipalPicker.tsx +9 -3
  14. package/src/access/ResourceApiKeys.tsx +1 -1
  15. package/src/access/ui.ts +1 -2
  16. package/src/ai/admin.ts +2 -2
  17. package/src/ai/assistant-models.ts +1 -1
  18. package/src/ai/audio-tool.ts +1 -1
  19. package/src/ai/browser-code-contracts.ts +174 -52
  20. package/src/ai/browser.ts +4 -2
  21. package/src/ai/capabilities.ts +3 -3
  22. package/src/ai/chat/blocks.tsx +127 -50
  23. package/src/ai/chat/builtin-tools.tsx +2 -4
  24. package/src/ai/chat/capability-table.tsx +1 -1
  25. package/src/ai/chat/composer-adapter.ts +48 -13
  26. package/src/ai/chat/message-utils.ts +18 -5
  27. package/src/ai/chat/presentation.tsx +24 -11
  28. package/src/ai/chat/tool-disclosure.tsx +6 -2
  29. package/src/ai/chat/tool-groups.ts +8 -2
  30. package/src/ai/chat-quotas.ts +2 -2
  31. package/src/ai/chat-task-contracts.ts +9 -13
  32. package/src/ai/chat-tasks.ts +39 -18
  33. package/src/ai/client/controller.ts +87 -38
  34. package/src/ai/client/projection.ts +13 -6
  35. package/src/ai/client/transport.ts +4 -2
  36. package/src/ai/code-capability-routes.ts +22 -27
  37. package/src/ai/code-capability-transport.ts +2 -1
  38. package/src/ai/code-execution.ts +53 -0
  39. package/src/ai/code-mode-skill.ts +9 -5
  40. package/src/ai/code-runtime-tools.ts +16 -3
  41. package/src/ai/code-source-contracts.ts +100 -32
  42. package/src/ai/code-source-tools.ts +49 -6
  43. package/src/ai/compaction.ts +3 -3
  44. package/src/ai/default-tools.ts +87 -14
  45. package/src/ai/dictation-runtime.ts +1 -1
  46. package/src/ai/draft-content.ts +27 -12
  47. package/src/ai/enrich.ts +1 -1
  48. package/src/ai/executor.ts +8 -12
  49. package/src/ai/fetch-file-tool.ts +2 -2
  50. package/src/ai/file-content-version.ts +6 -1
  51. package/src/ai/file-tools.ts +8 -2
  52. package/src/ai/files-store.ts +55 -17
  53. package/src/ai/grids-skill.ts +2 -0
  54. package/src/ai/http.ts +18 -7
  55. package/src/ai/index.ts +24 -25
  56. package/src/ai/inference-calls.ts +3 -4
  57. package/src/ai/live.ts +1 -1
  58. package/src/ai/markdown-pdf-tool.ts +2 -2
  59. package/src/ai/memories.ts +30 -27
  60. package/src/ai/memory-learning-runs.ts +1 -1
  61. package/src/ai/memory-learning.ts +5 -5
  62. package/src/ai/memory-tool.ts +1 -5
  63. package/src/ai/memory-workflow-evidence.ts +9 -4
  64. package/src/ai/message-queue.ts +23 -12
  65. package/src/ai/migrate.ts +6 -4
  66. package/src/ai/model-pricing.ts +4 -4
  67. package/src/ai/pdf-render-worker.ts +3 -2
  68. package/src/ai/projects-routes.ts +15 -40
  69. package/src/ai/projects.ts +18 -13
  70. package/src/ai/quota-provider.ts +6 -5
  71. package/src/ai/quota-report.ts +5 -5
  72. package/src/ai/quotas.ts +3 -3
  73. package/src/ai/routes.ts +60 -34
  74. package/src/ai/run-timeout.ts +3 -1
  75. package/src/ai/runtime-tools.ts +6 -7
  76. package/src/ai/runtime.ts +38 -20
  77. package/src/ai/settings.ts +1 -1
  78. package/src/ai/short-id.ts +4 -5
  79. package/src/ai/skill-search.ts +21 -10
  80. package/src/ai/skill-seeds.ts +16 -7
  81. package/src/ai/skill-tool.ts +6 -1
  82. package/src/ai/skills-routes.ts +32 -15
  83. package/src/ai/skills.ts +54 -28
  84. package/src/ai/store.ts +80 -47
  85. package/src/ai/structured.ts +6 -2
  86. package/src/ai/system-prompt.ts +7 -5
  87. package/src/ai/task-contracts.ts +84 -0
  88. package/src/ai/task-execution.ts +92 -0
  89. package/src/ai/todo-contracts.ts +24 -12
  90. package/src/ai/todo-tool.ts +16 -13
  91. package/src/ai/tools.ts +1 -1
  92. package/src/ai/transcription.ts +4 -4
  93. package/src/ai/turn-timing.ts +1 -1
  94. package/src/ai/ui.tsx +2 -1
  95. package/src/ai/usage.ts +1 -1
  96. package/src/ai/vision-tool.ts +38 -17
  97. package/src/api/admin-ai-quotas.ts +14 -8
  98. package/src/api/admin-ai-skills.ts +3 -3
  99. package/src/api/admin-linux-identities.ts +10 -4
  100. package/src/api/admin-rail.ts +2 -2
  101. package/src/api/app-approval.ts +2 -2
  102. package/src/api/auth.ts +1 -1
  103. package/src/api/capabilities.ts +45 -13
  104. package/src/api/index.ts +5 -5
  105. package/src/api/mcp.ts +5 -12
  106. package/src/browser/CloudResourceSearch.tsx +1 -1
  107. package/src/browser/command-bridge.ts +1 -1
  108. package/src/browser/command-shortcuts.ts +1 -1
  109. package/src/browser/commands.ts +8 -7
  110. package/src/browser/testing.ts +2 -1
  111. package/src/capabilities/client.ts +1 -1
  112. package/src/capabilities/command-link.ts +1 -1
  113. package/src/capabilities/server.ts +2 -1
  114. package/src/cli/admin/ai-quotas.ts +5 -5
  115. package/src/cli/admin/app-credentials.ts +7 -9
  116. package/src/cli/admin/index.ts +3 -3
  117. package/src/cli/admin/instance.ts +2 -2
  118. package/src/cli/admin/linux.ts +4 -2
  119. package/src/cli/admin/sync.ts +1 -1
  120. package/src/cli/capabilities.ts +42 -29
  121. package/src/config/define-env.ts +97 -0
  122. package/src/config/env.ts +147 -54
  123. package/src/config/index.ts +3 -1
  124. package/src/contracts/capabilities.ts +8 -1
  125. package/src/contracts/index.ts +5 -7
  126. package/src/contracts/registry.ts +2 -0
  127. package/src/contracts/shared.ts +12 -3
  128. package/src/desktop/index.ts +1 -0
  129. package/src/desktop/solid.tsx +1 -1
  130. package/src/index.ts +8 -2
  131. package/src/server/index.ts +2 -2
  132. package/src/server/ratelimit.ts +1 -1
  133. package/src/server/services/access-revision.ts +3 -3
  134. package/src/server/services/access.ts +3 -1
  135. package/src/services/accounts/notification-sender.ts +12 -2
  136. package/src/services/announcements/index.ts +4 -4
  137. package/src/services/app-approval.ts +11 -10
  138. package/src/services/identity/key-config.ts +6 -4
  139. package/src/services/identity/runtime-config.ts +6 -5
  140. package/src/services/ipa/posix.ts +1 -1
  141. package/src/services/ipa/sync.ts +1 -1
  142. package/src/services/logging/index.ts +1 -1
  143. package/src/services/logging/trace.ts +1 -1
  144. package/src/services/mandates/index.ts +5 -2
  145. package/src/services/mandates/policy.ts +106 -6
  146. package/src/services/notifications/batches.ts +1 -0
  147. package/src/services/pdf/gotenberg.ts +50 -14
  148. package/src/services/pdf/index.ts +11 -11
  149. package/src/services/public-http.ts +1 -1
  150. package/src/services/rail-shortcuts.ts +1 -1
  151. package/src/services/rail-snapshot.ts +1 -1
  152. package/src/services/request-cache-redis.ts +2 -1
  153. package/src/services/session/user.ts +1 -1
  154. package/src/services/settings/app.ts +5 -1
  155. package/src/services/settings/core-settings.ts +6 -8
  156. package/src/services/settings/index.ts +3 -3
  157. package/src/services/settings/store.ts +3 -3
  158. package/src/services/webauthn.ts +1 -1
  159. package/src/shared/index.ts +3 -6
  160. package/src/shared/markdown/extensions/info-blocks.ts +1 -1
  161. package/src/ssr/AdminSidebar.tsx +2 -2
  162. package/src/ssr/AppLaunchpad.island.tsx +12 -2
  163. package/src/ssr/AppLaunchpadPanel.tsx +4 -4
  164. package/src/ssr/GlobalSearchDialog.tsx +6 -6
  165. package/src/ssr/GlobalSearchTrigger.island.tsx +2 -2
  166. package/src/ssr/Layout.tsx +2 -2
  167. package/src/ssr/LayoutHeader.tsx +1 -1
  168. package/src/ssr/LayoutHelp.tsx +4 -6
  169. package/src/ssr/LayoutHelpTrigger.island.tsx +2 -2
  170. package/src/ssr/MinimalLayout.tsx +1 -1
  171. package/src/ssr/MobileNavigation.tsx +2 -2
  172. package/src/ssr/ProfilePreferences.island.tsx +1 -1
  173. package/src/ssr/admin-navigation.ts +6 -2
  174. package/src/ssr/islands/index.ts +2 -2
  175. package/src/ssr/mobile-menu-history.ts +1 -1
  176. package/src/workflows/ai/index.ts +2 -2
  177. package/src/workflows/ai/runtime.ts +16 -91
  178. package/src/workflows/ai/store.ts +1 -1
  179. package/src/workflows/ai/types.ts +6 -82
  180. package/src/workflows/store/index.ts +2 -2
  181. package/src/server/services/freeipa/test-certificates.ts +0 -44
  182. package/src/services/session/test-fixture.ts +0 -18
@@ -4,8 +4,8 @@ import type { CloudTheme } from "../shared/theme";
4
4
  import AppLaunchpad, { type AppLaunchpadApp } from "./AppLaunchpad.island";
5
5
  import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
6
6
  import GlobalSearchTrigger from "./GlobalSearchTrigger.island";
7
- import LayoutHelpTrigger from "./LayoutHelpTrigger.island";
8
7
  import LayoutBreadcrumbs from "./LayoutBreadcrumbs.island";
8
+ import LayoutHelpTrigger from "./LayoutHelpTrigger.island";
9
9
  import LayoutPreferences from "./LayoutPreferences.island";
10
10
  import type { LayoutBreadcrumb } from "./layout-runtime";
11
11
  import ProfilePreferences from "./ProfilePreferences.island";
@@ -1,14 +1,14 @@
1
- import { COMMANDS_CHANGED, contextCommandsWithShortcuts, shortcutLabel } from "../browser/command-shortcuts";
2
- import { searchHelpTopics } from "./layout-help-search";
1
+ import type { HelpDocumentManifest, HelpDocumentPayload, HelpSearchPayload } from "@k2b/cloud/shared";
3
2
  import { clipboard } from "@k2b/stdlib/solid";
4
3
  import { Button, IconButton, IconButtonLink, MarkdownView, NoticeCard, Placeholder, prompts, ScrollArea, useLocale } from "@k2b/ui";
5
- import type { HelpDocumentManifest, HelpDocumentPayload, HelpSearchPayload } from "@k2b/cloud/shared";
6
4
  import { createEffect, createMemo, createSignal, For, type JSX, onCleanup, onMount, Show } from "solid-js";
5
+ import { COMMANDS_CHANGED, contextCommandsWithShortcuts, shortcutLabel } from "../browser/command-shortcuts";
7
6
  import { appAccentStyle } from "./app-appearance";
8
7
  import { type GlobalSearchHelpApp, openGlobalSearchHelpDialog } from "./GlobalSearchHelpDialog";
9
8
  import { helpMessages } from "./help-messages";
10
9
  import { formatHelpBundleMarkdown, formatHelpDocumentMarkdown } from "./layout-help-markdown";
11
10
  import { adjacentHelpDocuments, focusHelpArticleHeading, resetHelpArticleScroll } from "./layout-help-navigation";
11
+ import { searchHelpTopics } from "./layout-help-search";
12
12
  import { layoutHelpTopicHref } from "./layout-help-url";
13
13
 
14
14
  type HelpTopicBase = {
@@ -114,9 +114,7 @@ const Shortcuts = (props: { openSearchHelp: () => void }) => {
114
114
  window.addEventListener(COMMANDS_CHANGED, update);
115
115
  onCleanup(() => window.removeEventListener(COMMANDS_CHANGED, update));
116
116
  });
117
- const entries = createMemo(() =>
118
- commands().filter((command) => command.shortcut),
119
- );
117
+ const entries = createMemo(() => commands().filter((command) => command.shortcut));
120
118
  return (
121
119
  <div class="flex flex-col gap-3">
122
120
  <p class="text-sm leading-relaxed text-dimmed">{t().shortcutsIntro}</p>
@@ -1,6 +1,6 @@
1
- import { registerContextAwareCommand } from "../browser/commands";
2
- import { createEffect, onCleanup } from "solid-js";
3
1
  import { IconButton, Tooltip, useLocale } from "@k2b/ui";
2
+ import { createEffect, onCleanup } from "solid-js";
3
+ import { registerContextAwareCommand } from "../browser/commands";
4
4
  import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
5
5
  import { openLayoutHelpDialog } from "./LayoutHelp";
6
6
  import { platformMessages } from "./platform-messages";
@@ -2,8 +2,8 @@ import { LocaleProvider } from "@k2b/ui";
2
2
  import type { JSX } from "solid-js/jsx-runtime";
3
3
  import { getLocale } from "../server/locale";
4
4
  import { readThemeFromCookieHeader } from "../shared/theme";
5
- import type { MinimalLayoutContext } from "./layout-context";
6
5
  import LayoutPreferences from "./LayoutPreferences.island";
6
+ import type { MinimalLayoutContext } from "./layout-context";
7
7
  import TimezoneCookie from "./TimezoneCookie.island";
8
8
 
9
9
  export type MinimalLayoutPreferencePosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
@@ -1,9 +1,9 @@
1
1
  import { BottomSheet, Navigation, SegmentedControl, useLocale } from "@k2b/ui";
2
2
  import { createSignal, onCleanup, Show } from "solid-js";
3
- import { AppLaunchpadPanel, type AppLaunchpadContext } from "./AppLaunchpadPanel";
3
+ import { type AppLaunchpadContext, AppLaunchpadPanel } from "./AppLaunchpadPanel";
4
4
  import { openMobileMenu } from "./mobile-menu-history";
5
- import { observeWorkspaceNavigation, readWorkspaceNavigation } from "./workspace-navigation";
6
5
  import { platformMessages } from "./platform-messages";
6
+ import { observeWorkspaceNavigation, readWorkspaceNavigation } from "./workspace-navigation";
7
7
 
8
8
  export function openCloudMobileMenu(apps: AppLaunchpadContext) {
9
9
  return openMobileMenu((beforeSelect) => (close, context) => (
@@ -1,6 +1,6 @@
1
- import { createProfileActions } from "./profile-actions";
2
1
  import { Avatar, Dropdown } from "@k2b/ui";
3
2
  import type { CloudTheme } from "../shared/theme";
3
+ import { createProfileActions } from "./profile-actions";
4
4
 
5
5
  type ProfilePreferencesProps = {
6
6
  avatarSrc?: string;
@@ -45,7 +45,9 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
45
45
  }))
46
46
  .sort((a, b) => a.app.name.localeCompare(b.app.name));
47
47
 
48
- aiLinks.push(...contributedApps.flatMap(({groups}) => groups.filter(group => group.section === "ai").flatMap(group => group.links)));
48
+ aiLinks.push(
49
+ ...contributedApps.flatMap(({ groups }) => groups.filter((group) => group.section === "ai").flatMap((group) => group.links)),
50
+ );
49
51
 
50
52
  const appsWithGroups = new Set(contributedApps.filter(({ groups }) => groups.length > 0).map(({ app }) => app.id));
51
53
  const appLinks = apps
@@ -66,7 +68,9 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
66
68
  { href: "/admin/announcements", icon: "ti-speakerphone", label: t.announcements },
67
69
  ],
68
70
  },
69
- ...contributedApps.flatMap(({ groups }) => groups.filter(group => group.section !== "ai").map(({label,links}) => ({label,links}))),
71
+ ...contributedApps.flatMap(({ groups }) =>
72
+ groups.filter((group) => group.section !== "ai").map(({ label, links }) => ({ label, links })),
73
+ ),
70
74
  {
71
75
  label: t.userManagement,
72
76
  links: [
@@ -1,6 +1,6 @@
1
- export { default as SearchBar } from "./SearchBar";
2
- export { provideWorkspaceNavigation } from "../workspace-navigation";
3
1
  export { default as WorkspaceNavigationProvider } from "../WorkspaceNavigationProvider";
2
+ export { provideWorkspaceNavigation } from "../workspace-navigation";
3
+ export { default as SearchBar } from "./SearchBar";
4
4
 
5
5
  import { AppLaunchpadButton, AppLaunchpadProvider } from "../AppLaunchpad.island";
6
6
  import { LayoutHelpDocuments, LayoutHelpPage } from "../LayoutHelp";
@@ -1,4 +1,4 @@
1
- import { bottomSheetOptions, dialogCore, type DialogRender } from "@k2b/ui";
1
+ import { bottomSheetOptions, type DialogRender, dialogCore } from "@k2b/ui";
2
2
 
3
3
  const marker = "cloudMobileMenu";
4
4
  let running: Promise<void> | undefined;
@@ -1,13 +1,13 @@
1
+ export { startWorkflowAiRuntime, stopWorkflowAiRuntime, submitWorkflowAiTask, type WorkflowAiRuntimeOptions } from "./runtime";
1
2
  export {
2
- WORKFLOW_AI_DEPENDENCY_KIND,
3
3
  createWorkflowAiTask,
4
4
  getWorkflowAiTask,
5
5
  getWorkflowAiTaskByEffectKey,
6
6
  migrateWorkflowAi,
7
+ WORKFLOW_AI_DEPENDENCY_KIND,
7
8
  wakeWorkflowAiTask,
8
9
  workflowAiTaskExists,
9
10
  } from "./store";
10
- export { startWorkflowAiRuntime, stopWorkflowAiRuntime, submitWorkflowAiTask, type WorkflowAiRuntimeOptions } from "./runtime";
11
11
  export {
12
12
  type WorkflowAiRequest,
13
13
  type WorkflowAiRequestInput,
@@ -1,11 +1,10 @@
1
- import { AiBackgroundAdmissionError, AiBackgroundCostError } from "../../ai/inference-calls";
2
1
  import { StructuredOutputError } from "@k2b/nessi";
3
2
  import type { Job, Worker } from "@k2b/sync";
4
- import { z } from "zod";
5
3
  import { lazySync } from "../../_internal/process-sync";
6
- import { isAiSettingsError, type RunAiStructuredInput, runAiStructured } from "../../ai";
4
+ import { isAiSettingsError, runAiStructured } from "../../ai";
5
+ import { AiBackgroundAdmissionError, AiBackgroundCostError } from "../../ai/inference-calls";
6
+ import { executeAiTask } from "../../ai/task-execution";
7
7
 
8
- import type { WorkflowJsonValue } from "../contracts";
9
8
  import {
10
9
  claimWorkflowAiTask,
11
10
  completeWorkflowAiTask,
@@ -17,7 +16,7 @@ import {
17
16
  wakeWorkflowAiTask,
18
17
  workflowAiTaskCancellationRequested,
19
18
  } from "./store";
20
- import type { WorkflowAiRequest, WorkflowAiTask } from "./types";
19
+ import type { WorkflowAiTask } from "./types";
21
20
 
22
21
  const JOB_ID = "cloud.workflow-ai";
23
22
  const MAX_ATTEMPTS = 3;
@@ -72,92 +71,18 @@ export const settleWorkflowAiAttemptFailure = async (
72
71
  return "failed";
73
72
  };
74
73
 
75
- const asJson = (value: unknown): WorkflowJsonValue => JSON.parse(JSON.stringify(value)) as WorkflowJsonValue;
76
- const choiceEnum = (values: string[]) => z.enum(values as [string, ...string[]]);
77
- const structuredOutputSchema = (fields: Extract<WorkflowAiRequest, { kind: "extract_data" }>["fields"]) => {
78
- const shape: Record<string, z.ZodType> = {};
79
- for (const field of fields) {
80
- let schema: z.ZodType =
81
- field.type === "text"
82
- ? z.string().max(field.maxLength ?? 20_000)
83
- : field.type === "number"
84
- ? z.number()
85
- : field.type === "boolean"
86
- ? z.boolean()
87
- : field.type === "date_time"
88
- ? z.iso.datetime({ offset: true })
89
- : choiceEnum(field.choices!);
90
- if (!field.required) schema = schema.optional();
91
- shape[field.name] = schema.describe(field.description);
92
- }
93
- return z.object(shape).strict();
94
- };
95
-
96
- export const executeWorkflowAiRequest = async (task: WorkflowAiTask, runStructured: StructuredRunner, signal: AbortSignal) => {
97
- const request = task.request;
98
- const common = {
99
- appId: task.appId,
100
- attribution: { workflowRunId: task.runId, stepKey: task.stepKey, userId: task.usageUserId },
101
- requestedModelId: task.modelProfileId,
102
- signal,
103
- temperature: 0,
104
- } satisfies Partial<RunAiStructuredInput<z.ZodType>>;
105
-
106
- if (request.kind === "generate_text") {
107
- const result = await runStructured({
108
- ...common,
109
- task: "workflow-generate-text",
110
- systemPrompt: request.prompt,
111
- input: request.input === undefined ? "Create the requested text." : JSON.stringify(request.input),
112
- outputName: "generated_text",
113
- output: z.object({ text: z.string().max(request.maxOutputChars) }),
114
- maxOutputTokens: Math.min(8_192, Math.max(64, Math.ceil(request.maxOutputChars / 2))),
115
- });
116
- return { output: result.output.text as WorkflowJsonValue, usage: result.usage ? asJson(result.usage) : null };
117
- }
118
-
119
- if (request.kind === "classify") {
120
- const result = await runStructured({
121
- ...common,
122
- task: "workflow-classify",
123
- systemPrompt: `${request.prompt}\nReturn exactly one of the declared choices.`,
124
- input: JSON.stringify(request.input),
125
- outputName: "classification",
126
- output: z.object({ choice: choiceEnum(request.choices) }),
127
- maxOutputTokens: 200,
128
- });
129
- return { output: result.output.choice as WorkflowJsonValue, usage: result.usage ? asJson(result.usage) : null };
130
- }
131
-
132
- if (request.kind === "extract_data") {
133
- const result = await runStructured({
134
- ...common,
135
- task: "workflow-extract-data",
136
- systemPrompt: `${request.prompt}\nReturn only the declared fields. Do not invent values that are not supported by the input.`,
137
- input: JSON.stringify(request.input),
138
- outputName: "structured_data",
139
- output: structuredOutputSchema(request.fields),
140
- maxOutputTokens: 4_096,
141
- });
142
- return { output: asJson(result.output), usage: result.usage ? asJson(result.usage) : null };
143
- }
144
-
145
- const maximum = request.maxChoices ?? request.choices.length;
146
- const result = await runStructured({
147
- ...common,
148
- task: "workflow-classify-many",
149
- systemPrompt: `${request.prompt}\nReturn only unique values from the declared choices.`,
150
- input: JSON.stringify(request.input),
151
- outputName: "classifications",
152
- output: z.object({ choices: z.array(choiceEnum(request.choices)).min(request.minChoices).max(maximum) }),
153
- maxOutputTokens: 500,
154
- });
155
- const selected = new Set(result.output.choices);
156
- return {
157
- output: request.choices.filter((choice) => selected.has(choice)) as WorkflowJsonValue,
158
- usage: result.usage ? asJson(result.usage) : null,
159
- };
160
- };
74
+ export const executeWorkflowAiRequest = async (task: WorkflowAiTask, runStructured: StructuredRunner, signal: AbortSignal) =>
75
+ executeAiTask(
76
+ task.request,
77
+ {
78
+ appId: task.appId,
79
+ attribution: { workflowRunId: task.runId, stepKey: task.stepKey, userId: task.usageUserId },
80
+ requestedModelId: task.modelProfileId,
81
+ signal,
82
+ taskPrefix: "workflow",
83
+ },
84
+ runStructured,
85
+ );
161
86
 
162
87
  const retryableError = (error: unknown): boolean => {
163
88
  if (error instanceof AiBackgroundAdmissionError) return error.retryable;
@@ -1,7 +1,7 @@
1
- import { notifyWorkflowWorker } from "../store/worker-runtime";
2
1
  import { type SQL, sql } from "bun";
3
2
  import type { WorkflowJsonValue } from "../contracts";
4
3
  import { wakeWorkflowRunsWaitingOn } from "../store/runs";
4
+ import { notifyWorkflowWorker } from "../store/worker-runtime";
5
5
  import {
6
6
  type WorkflowAiRequest,
7
7
  type WorkflowAiRequestInput,
@@ -1,88 +1,12 @@
1
- import { z } from "zod";
1
+ import type { AiTaskRequest as WorkflowAiRequest } from "../../ai/task-contracts";
2
2
  import type { WorkflowJsonValue } from "../contracts";
3
3
 
4
- const prompt = z.string().trim().min(1).max(20_000);
5
- const modelProfileId = z.string().trim().min(1).max(120).optional();
6
- const choice = z.string().trim().min(1).max(200);
7
- const choices = z
8
- .array(choice)
9
- .min(2)
10
- .max(50)
11
- .refine((values) => new Set(values).size === values.length, "Choices must be unique.");
4
+ export {
5
+ type AiTaskRequest as WorkflowAiRequest,
6
+ type AiTaskRequestInput as WorkflowAiRequestInput,
7
+ AiTaskRequestSchema as workflowAiRequestSchema,
8
+ } from "../../ai/task-contracts";
12
9
 
13
- const structuredField = z
14
- .object({
15
- name: z
16
- .string()
17
- .trim()
18
- .regex(/^[A-Za-z][A-Za-z0-9_]{0,79}$/),
19
- type: z.enum(["text", "number", "boolean", "date_time", "enum"]),
20
- description: z.string().trim().min(1).max(500),
21
- required: z.boolean().default(true),
22
- choices: z.array(choice).min(1).max(50).optional(),
23
- maxLength: z.number().int().min(1).max(20_000).optional(),
24
- })
25
- .strict()
26
- .superRefine((field, ctx) => {
27
- if (field.type === "enum" && !field.choices)
28
- ctx.addIssue({ code: "custom", path: ["choices"], message: "Enum fields require choices." });
29
- if (field.type !== "enum" && field.choices)
30
- ctx.addIssue({ code: "custom", path: ["choices"], message: "Choices are only valid for enum fields." });
31
- if (field.type !== "text" && field.maxLength)
32
- ctx.addIssue({ code: "custom", path: ["maxLength"], message: "maxLength is only valid for text fields." });
33
- });
34
-
35
- const structuredFields = z
36
- .array(structuredField)
37
- .min(1)
38
- .max(40)
39
- .refine((fields) => new Set(fields.map((field) => field.name)).size === fields.length, "Structured field names must be unique.");
40
-
41
- export const workflowAiRequestSchema = z.discriminatedUnion("kind", [
42
- z.object({
43
- kind: z.literal("generate_text"),
44
- prompt,
45
- input: z.unknown().optional(),
46
- modelProfileId,
47
- maxOutputChars: z.number().int().min(1).max(20_000).default(4_000),
48
- }),
49
- z.object({
50
- kind: z.literal("classify"),
51
- prompt,
52
- input: z.unknown(),
53
- choices,
54
- modelProfileId,
55
- }),
56
- z
57
- .object({
58
- kind: z.literal("classify_many"),
59
- prompt,
60
- input: z.unknown(),
61
- choices,
62
- minChoices: z.number().int().min(0).max(50).default(0),
63
- maxChoices: z.number().int().min(0).max(50).optional(),
64
- modelProfileId,
65
- })
66
- .superRefine((value, ctx) => {
67
- const maximum = value.maxChoices ?? value.choices.length;
68
- if (maximum > value.choices.length) {
69
- ctx.addIssue({ code: "custom", path: ["maxChoices"], message: "maxChoices cannot exceed the number of choices." });
70
- }
71
- if (value.minChoices > maximum) {
72
- ctx.addIssue({ code: "custom", path: ["minChoices"], message: "minChoices cannot exceed maxChoices." });
73
- }
74
- }),
75
- z.object({
76
- kind: z.literal("extract_data"),
77
- prompt,
78
- input: z.unknown(),
79
- fields: structuredFields,
80
- modelProfileId,
81
- }),
82
- ]);
83
-
84
- export type WorkflowAiRequest = z.infer<typeof workflowAiRequestSchema>;
85
- export type WorkflowAiRequestInput = z.input<typeof workflowAiRequestSchema>;
86
10
  export type WorkflowAiTaskStatus = "queued" | "running" | "succeeded" | "failed" | "canceled";
87
11
 
88
12
  export type WorkflowAiTask = {
@@ -50,11 +50,11 @@ export {
50
50
  } from "./events";
51
51
  export {
52
52
  getWorkflowRun,
53
- listWorkflowFamilies,
54
- listWorkflowRunTimeline,
55
53
  listStrandedWorkflowEffects,
54
+ listWorkflowFamilies,
56
55
  listWorkflowRunSteps,
57
56
  listWorkflowRuns,
57
+ listWorkflowRunTimeline,
58
58
  type StrandedWorkflowEffect,
59
59
  type WorkflowAppHealth,
60
60
  type WorkflowFamilyFilter,
@@ -1,44 +0,0 @@
1
- export const TEST_CA_CERT = `-----BEGIN CERTIFICATE-----
2
- MIIBljCCATugAwIBAgIUaD/zikxyQaxGreCVzb9YufuusxIwCgYIKoZIzj0EAwIw
3
- IDEeMBwGA1UEAwwVQ2xvdWQgRnJlZUlQQSBUZXN0IENBMB4XDTI2MDcyOTE1NTgx
4
- MFoXDTM2MDcyNjE1NTgxMFowIDEeMBwGA1UEAwwVQ2xvdWQgRnJlZUlQQSBUZXN0
5
- IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYZ7eU7/C71ilbpKa9k/FXObw
6
- Vq4u/3rtpW/kSjsJaIVNPFp5oFBdHceL0UPgoxwpaJiJNHiwQHF/BBxaJ7HOAKNT
7
- MFEwHQYDVR0OBBYEFOflw/p5xNlS0am+HytNLyXVEUndMB8GA1UdIwQYMBaAFOfl
8
- w/p5xNlS0am+HytNLyXVEUndMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
9
- SQAwRgIhAJmRoNV5dxKvNOd2KoytvK/6ckRWN+Uu0ljc5y/g4izqAiEAsLCjxWvz
10
- mgt1Z3uk+QkT9D1vHCuKSvcqv3aVn0DClxA=
11
- -----END CERTIFICATE-----`;
12
-
13
- export const TEST_SERVER_CERT = `-----BEGIN CERTIFICATE-----
14
- MIIBpDCCAUmgAwIBAgIUNGe5IovnDTTKxjxwHBGVcnOaXaQwCgYIKoZIzj0EAwIw
15
- IDEeMBwGA1UEAwwVQ2xvdWQgRnJlZUlQQSBUZXN0IENBMB4XDTI2MDcyOTE1NTgx
16
- MFoXDTM2MDcyNjE1NTgxMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZI
17
- zj0CAQYIKoZIzj0DAQcDQgAEolKc1EyyTH6MKX0GR7FhbXCN0O3G/D2riPJ045K+
18
- qCL6m0Fso38eNxKbNENSkE1tX7j4uv6tcLmtHU/cKxjUg6NtMGswFAYDVR0RBA0w
19
- C4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBT/iu3f
20
- aYfd6r4xoPTkZujj/0rwWjAfBgNVHSMEGDAWgBTn5cP6ecTZUtGpvh8rTS8l1RFJ
21
- 3TAKBggqhkjOPQQDAgNJADBGAiEAgsXqgbBm2RJaUZIs0IR2rbI+6S+1CtugVMHl
22
- oxuRagcCIQCtoMZh0rZFlmUkH19BxrDvQe3JqCuQpd2HiZdvZTJhyQ==
23
- -----END CERTIFICATE-----`;
24
-
25
- export const TEST_SERVER_KEY = `-----BEGIN EC PARAMETERS-----
26
- BggqhkjOPQMBBw==
27
- -----END EC PARAMETERS-----
28
- -----BEGIN EC PRIVATE KEY-----
29
- MHcCAQEEIKFSLmqZO5KgG9hGbNhhJa0V2IMl39N2UHUj5VUh6DsmoAoGCCqGSM49
30
- AwEHoUQDQgAEolKc1EyyTH6MKX0GR7FhbXCN0O3G/D2riPJ045K+qCL6m0Fso38e
31
- NxKbNENSkE1tX7j4uv6tcLmtHU/cKxjUgw==
32
- -----END EC PRIVATE KEY-----`;
33
-
34
- export const TEST_WRONG_CA_CERT = `-----BEGIN CERTIFICATE-----
35
- MIIBhDCCASugAwIBAgIUWM3XX235lciHJ/UKoajhrSx2dPwwCgYIKoZIzj0EAwIw
36
- GDEWMBQGA1UEAwwNV3JvbmcgVGVzdCBDQTAeFw0yNjA3MjkxNTU4MTBaFw0zNjA3
37
- MjYxNTU4MTBaMBgxFjAUBgNVBAMMDVdyb25nIFRlc3QgQ0EwWTATBgcqhkjOPQIB
38
- BggqhkjOPQMBBwNCAAREOwLQCQYQI7zEa7GaSZag98eOF5KMckBPrDsDxS0Bb5lx
39
- uIa3iWu9CI6Xkw+v+avpPF+Oz3QB7GXgCboKHFico1MwUTAdBgNVHQ4EFgQU1PRs
40
- qLXtaHVxaKu0e0le1KpD6GYwHwYDVR0jBBgwFoAU1PRsqLXtaHVxaKu0e0le1KpD
41
- 6GYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiA9vA9jI66Un7zQ
42
- ycf0+6Ze0y5aSM9FLojGcVuzZb0iyAIgIC6ooFag9BuAZqcj/k07hjARi6UVOpEy
43
- sIJIkokji38=
44
- -----END CERTIFICATE-----`;
@@ -1,18 +0,0 @@
1
- import { Hono } from "hono";
2
- import { sql } from "bun";
3
- import { session } from "./index";
4
-
5
- /** Integration fixture: run with an isolated Core authority and its public JWKS endpoint. */
6
- export const createTestSession = async (userId: string): Promise<string> => {
7
- // Most session fixtures represent established users, not first-use onboarding.
8
- await sql`INSERT INTO auth.legal_acceptances (user_id, session_id, document_version, documents)
9
- VALUES (${userId}::uuid, ${crypto.randomUUID()}::uuid, 'test-fixture', '[]'::jsonb) ON CONFLICT DO NOTHING`;
10
- const app = new Hono().post("/login", async (c) => c.json({ token: await session.create(c, userId) }));
11
- const response = await app.request("/login", { method: "POST" });
12
- if (response.status !== 200) throw new Error(`Session fixture login failed: ${response.status}`);
13
- const body: unknown = await response.json();
14
- if (!body || typeof body !== "object" || !("token" in body) || typeof body.token !== "string") {
15
- throw new Error("Session fixture did not return a token");
16
- }
17
- return body.token;
18
- };