@k2b/cloud 0.43.0 → 0.44.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 (267) hide show
  1. package/package.json +7 -2
  2. package/src/_internal/app-access-request.ts +13 -0
  3. package/src/_internal/app-access-resources.ts +141 -0
  4. package/src/_internal/app-event-catalog.ts +40 -0
  5. package/src/_internal/app-event-schemas.ts +127 -0
  6. package/src/_internal/application-package.ts +40 -0
  7. package/src/_internal/built-in-app-ids.json +27 -0
  8. package/src/_internal/capabilities.ts +158 -12
  9. package/src/_internal/capability-origin.ts +4 -0
  10. package/src/_internal/capability-streams.ts +7 -5
  11. package/src/_internal/define-app.ts +57 -6
  12. package/src/_internal/object-store-area.ts +9 -0
  13. package/src/_internal/object-store-config.ts +101 -0
  14. package/src/_internal/object-store-key.ts +14 -0
  15. package/src/_internal/process-identity.ts +7 -2
  16. package/src/_internal/registry-validation.ts +11 -1
  17. package/src/_internal/registry.ts +8 -1
  18. package/src/_internal/runtime-context.ts +8 -1
  19. package/src/access/AppAccessEditor.tsx +2 -0
  20. package/src/access/PermissionEditor.tsx +9 -3
  21. package/src/access/PrincipalPicker.tsx +14 -4
  22. package/src/access/ResourceApiKeys.tsx +3 -4
  23. package/src/access/app-access-messages.ts +6 -0
  24. package/src/account/ProfilePictureDialog.tsx +699 -0
  25. package/src/account/profile-picture-messages.ts +269 -0
  26. package/src/account/ui.ts +7 -0
  27. package/src/ai/admin.ts +3 -0
  28. package/src/ai/app-frame.ts +12 -0
  29. package/src/ai/application-tasks.ts +101 -0
  30. package/src/ai/assistant-models.ts +126 -64
  31. package/src/ai/capabilities.ts +19 -4
  32. package/src/ai/chart-block.ts +63 -30
  33. package/src/ai/chat/chart-block.tsx +12 -3
  34. package/src/ai/chat/composer-adapter.ts +8 -7
  35. package/src/ai/chat/message-actions.tsx +1 -1
  36. package/src/ai/chat/message-utils.ts +2 -2
  37. package/src/ai/chat/presentation.tsx +22 -1
  38. package/src/ai/chat-quotas.ts +5 -4
  39. package/src/ai/chat-tasks.ts +2 -2
  40. package/src/ai/client/controller.ts +11 -10
  41. package/src/ai/client/projection.ts +3 -3
  42. package/src/ai/compaction.ts +3 -3
  43. package/src/ai/credentials.ts +13 -114
  44. package/src/ai/default-tool-contracts.ts +4 -3
  45. package/src/ai/dictation-runtime.ts +4 -4
  46. package/src/ai/enrich.ts +2 -2
  47. package/src/ai/executor.ts +31 -22
  48. package/src/ai/http.ts +17 -29
  49. package/src/ai/index.ts +11 -5
  50. package/src/ai/inference-calls.ts +5 -4
  51. package/src/ai/maintenance.ts +1 -1
  52. package/src/ai/memory-learning-runs.ts +5 -5
  53. package/src/ai/memory-learning.ts +3 -3
  54. package/src/ai/message-queue.ts +1 -2
  55. package/src/ai/migrate.ts +75 -42
  56. package/src/ai/model-access-migrate.ts +3 -17
  57. package/src/ai/model-access.ts +44 -85
  58. package/src/ai/model-card.tsx +134 -0
  59. package/src/ai/model-catalog.ts +359 -0
  60. package/src/ai/model-chooser-messages.ts +88 -0
  61. package/src/ai/model-chooser.tsx +577 -0
  62. package/src/ai/model-delete.ts +234 -0
  63. package/src/ai/model-fixture.ts +39 -0
  64. package/src/ai/model-icon.tsx +36 -0
  65. package/src/ai/model-overview.ts +53 -0
  66. package/src/ai/model-usage.ts +45 -0
  67. package/src/ai/openai-api-messages.ts +0 -4
  68. package/src/ai/openai-api.ts +26 -9
  69. package/src/ai/personal-agent.ts +1 -1
  70. package/src/ai/prefs.ts +12 -10
  71. package/src/ai/projects-routes.ts +2 -1
  72. package/src/ai/projects.ts +13 -12
  73. package/src/ai/protocol.ts +2 -2
  74. package/src/ai/provider-connections.ts +161 -0
  75. package/src/ai/provider-errors.ts +55 -6
  76. package/src/ai/provider-fetch.ts +4 -0
  77. package/src/ai/provider.ts +1 -1
  78. package/src/ai/quota-limits.ts +36 -0
  79. package/src/ai/quota-provider.ts +4 -2
  80. package/src/ai/quota-report.ts +7 -4
  81. package/src/ai/quotas.ts +19 -8
  82. package/src/ai/recent-resources.ts +6 -0
  83. package/src/ai/request-headers.ts +14 -87
  84. package/src/ai/resource-refs.ts +6 -2
  85. package/src/ai/routes.ts +17 -18
  86. package/src/ai/runtime.ts +50 -22
  87. package/src/ai/settings.ts +93 -580
  88. package/src/ai/store.ts +45 -23
  89. package/src/ai/stream.ts +1 -1
  90. package/src/ai/structured-runs.ts +2 -2
  91. package/src/ai/structured.ts +21 -44
  92. package/src/ai/task-contracts.ts +25 -5
  93. package/src/ai/task-execution.ts +15 -6
  94. package/src/ai/task-model.ts +9 -0
  95. package/src/ai/transcription.ts +27 -22
  96. package/src/ai/types.ts +50 -62
  97. package/src/ai/ui.tsx +13 -0
  98. package/src/ai/usage.ts +8 -8
  99. package/src/ai/validate.ts +8 -7
  100. package/src/ai/view-block.ts +2 -2
  101. package/src/api/accounts-entities.ts +16 -0
  102. package/src/api/admin-ai-quotas.ts +34 -56
  103. package/src/api/admin-ai-usage.ts +1 -1
  104. package/src/api/admin-ai.ts +173 -0
  105. package/src/api/admin-core-settings.ts +68 -244
  106. package/src/api/app-approval.ts +3 -1
  107. package/src/api/app-event-delivery.ts +51 -0
  108. package/src/api/app-events.ts +123 -0
  109. package/src/api/app-resource-access.ts +48 -0
  110. package/src/api/auth/account-mismatch.ts +24 -0
  111. package/src/api/auth.ts +26 -3
  112. package/src/api/capabilities.ts +28 -4
  113. package/src/api/index.ts +7 -1
  114. package/src/api/mcp.ts +175 -2
  115. package/src/api/me.ts +6 -4
  116. package/src/api/pwa-phone.ts +75 -26
  117. package/src/api/pwa.ts +34 -8
  118. package/src/api/resource-links.ts +83 -6
  119. package/src/api/resource-resolve.ts +50 -0
  120. package/src/api/search/schemas.ts +3 -1
  121. package/src/api/search.ts +9 -7
  122. package/src/app-pwa/contracts.ts +4 -0
  123. package/src/app-pwa/core-query-error.ts +20 -0
  124. package/src/app-pwa/index.ts +1 -0
  125. package/src/app-pwa/service.ts +91 -0
  126. package/src/app-pwa/transport.ts +22 -0
  127. package/src/approvals/messages.ts +2 -2
  128. package/src/browser/cloud-id.ts +86 -0
  129. package/src/browser/resource-clipboard.ts +4 -0
  130. package/src/browser/resource-links.ts +52 -0
  131. package/src/capabilities/server-invoke.ts +2 -11
  132. package/src/capabilities/testing.ts +5 -2
  133. package/src/cli/access.ts +6 -5
  134. package/src/cli/admin/ai-models.ts +199 -0
  135. package/src/cli/admin/ai-quotas.ts +6 -102
  136. package/src/cli/admin/ai-usage.ts +4 -4
  137. package/src/cli/admin/app-credentials.ts +5 -3
  138. package/src/cli/admin/index.ts +5 -2
  139. package/src/cli/apps.ts +89 -26
  140. package/src/cli/capabilities.ts +22 -1
  141. package/src/cli/events.ts +320 -0
  142. package/src/config/env.ts +40 -0
  143. package/src/contracts/app-access.ts +26 -2
  144. package/src/contracts/app-details.ts +89 -0
  145. package/src/contracts/app-events.ts +51 -0
  146. package/src/contracts/app.ts +21 -5
  147. package/src/contracts/capabilities.ts +143 -3
  148. package/src/contracts/index.ts +2 -0
  149. package/src/contracts/notification-types.ts +36 -9
  150. package/src/contracts/pwa.ts +17 -1
  151. package/src/contracts/registry.ts +6 -0
  152. package/src/contracts/resource-clipboard.ts +35 -0
  153. package/src/contracts/resource-pages.ts +140 -0
  154. package/src/contracts/resource-resolution.ts +13 -0
  155. package/src/contracts/shared.ts +4 -0
  156. package/src/contracts/user-notifications.ts +2 -0
  157. package/src/events/app-event-delivery.ts +100 -0
  158. package/src/events/app-events.testing.ts +65 -0
  159. package/src/events/app-events.ts +134 -0
  160. package/src/events/core-user-created.ts +24 -0
  161. package/src/events/index.ts +9 -0
  162. package/src/events/testing.ts +2 -0
  163. package/src/index.ts +1 -1
  164. package/src/server/api/respond.ts +8 -3
  165. package/src/server/index.ts +1 -0
  166. package/src/server/middleware/app-access.ts +1 -1
  167. package/src/server/middleware/auth.ts +21 -7
  168. package/src/server/middleware/index.ts +1 -0
  169. package/src/server/middleware/invocation.ts +2 -0
  170. package/src/server/public-storage.ts +106 -0
  171. package/src/server/services/access.ts +8 -0
  172. package/src/services/accounts/app.ts +6 -0
  173. package/src/services/accounts/entities.ts +14 -3
  174. package/src/services/accounts/groups.ts +3 -1
  175. package/src/services/accounts/identities.ts +62 -10
  176. package/src/services/accounts/users.ts +7 -6
  177. package/src/services/app-access.ts +25 -7
  178. package/src/services/app-details.ts +224 -0
  179. package/src/services/app-discovery.ts +2 -1
  180. package/src/services/app-event-shapes.ts +139 -0
  181. package/src/services/app-events/accept.ts +116 -0
  182. package/src/services/app-events/admin.ts +250 -0
  183. package/src/services/app-events/declarations.ts +187 -0
  184. package/src/services/app-events/deliver.ts +218 -0
  185. package/src/services/app-events.ts +92 -0
  186. package/src/services/config-conflict.ts +11 -0
  187. package/src/services/core-user-events.ts +1 -0
  188. package/src/services/freeipa-config.ts +9 -0
  189. package/src/services/geo/ledger.ts +28 -10
  190. package/src/services/geo/service.ts +43 -24
  191. package/src/services/identity/core-origin.ts +12 -0
  192. package/src/services/identity/invocation-actor.ts +20 -2
  193. package/src/services/identity/invocation-authority.ts +4 -0
  194. package/src/services/identity/invocation-token.ts +238 -56
  195. package/src/services/identity/issue-invocation.ts +6 -1
  196. package/src/services/image-metadata.ts +65 -1
  197. package/src/services/index.ts +13 -1
  198. package/src/services/ipa/sync.ts +6 -2
  199. package/src/services/ipa/users.ts +4 -1
  200. package/src/services/mandates/policy.ts +19 -12
  201. package/src/services/notifications/browser.ts +4 -0
  202. package/src/services/notifications/catalog.ts +5 -2
  203. package/src/services/notifications/platform.ts +7 -2
  204. package/src/services/notifications/user.ts +18 -7
  205. package/src/services/oauth-tokens.ts +28 -2
  206. package/src/services/object-store.ts +217 -0
  207. package/src/services/providers/local/users.ts +5 -0
  208. package/src/services/pwa-devices.ts +147 -37
  209. package/src/services/session/index.ts +14 -0
  210. package/src/services/settings/core-settings.ts +71 -43
  211. package/src/services/settings/index.ts +8 -0
  212. package/src/services/settings/versions.ts +26 -0
  213. package/src/shared/ai-models.ts +274 -0
  214. package/src/shared/ai-quotas.ts +2 -2
  215. package/src/shared/ai-usage.ts +2 -2
  216. package/src/shared/index.ts +2 -0
  217. package/src/shared-text/documents.ts +25 -8
  218. package/src/shared-text/log.ts +1 -1
  219. package/src/shared-text/text-diff.ts +75 -0
  220. package/src/ssr/AdminArea.tsx +131 -0
  221. package/src/ssr/AdminLayout.tsx +9 -3
  222. package/src/ssr/AdminSidebar.tsx +7 -7
  223. package/src/ssr/AppAccessDenied.tsx +2 -0
  224. package/src/ssr/AppBadge.tsx +14 -0
  225. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  226. package/src/ssr/CloudShell.island.tsx +25 -4
  227. package/src/ssr/Layout.tsx +8 -6
  228. package/src/ssr/PwaLayout.tsx +16 -1
  229. package/src/ssr/PwaPartCount.island.tsx +23 -0
  230. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  231. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  232. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  233. package/src/ssr/admin-active-link.ts +16 -0
  234. package/src/ssr/admin-navigation.ts +25 -20
  235. package/src/ssr/app-badges.ts +31 -7
  236. package/src/ssr/index.ts +8 -0
  237. package/src/ssr/page-trail-runtime.ts +3 -1
  238. package/src/ssr/platform-messages.ts +9 -1
  239. package/src/ssr/pwa-messages.ts +2 -2
  240. package/src/ssr/rail-navigation.ts +5 -1
  241. package/src/ssr/settings-draft.ts +153 -0
  242. package/src/ssr/shell-data.tsx +2 -0
  243. package/src/styles/global.css +1 -0
  244. package/src/styles/profile-picture.css +270 -0
  245. package/src/styles/utilities-navigation.css +6 -0
  246. package/src/workflows/ai/policy.ts +49 -0
  247. package/src/workflows/ai/runtime.ts +131 -21
  248. package/src/workflows/ai/store.ts +283 -35
  249. package/src/workflows/ai/types.ts +8 -2
  250. package/src/workflows/ai-actions.ts +148 -120
  251. package/src/workflows/authoring.ts +31 -0
  252. package/src/workflows/contracts.ts +1 -0
  253. package/src/workflows/definition.ts +2 -0
  254. package/src/workflows/editor.ts +15 -0
  255. package/src/workflows/index.ts +2 -1
  256. package/src/workflows/language/compiler.ts +34 -3
  257. package/src/workflows/runtime/executor.ts +132 -33
  258. package/src/workflows/runtime/index.ts +2 -0
  259. package/src/workflows/runtime/ports.ts +5 -1
  260. package/src/workflows/store/actions.ts +1 -1
  261. package/src/workflows/store/definitions.ts +5 -0
  262. package/src/workflows/store/runs.ts +5 -3
  263. package/src/workflows/store/worker.ts +7 -2
  264. package/src/ai/model-pricing.ts +0 -43
  265. package/src/ai/model-request-settings.ts +0 -98
  266. package/src/ai/settings-messages.ts +0 -129
  267. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -1,4 +1,7 @@
1
+ import { assertAiTaskRequestSize } from "../ai/task-contracts";
1
2
  import type { AiResolvedModel } from "../ai/types";
3
+ import { type AiModelRequest, AiModelRequestSchema, type AiResolvedModelRef } from "../shared/ai-models";
4
+ import { WorkflowAiAdmissionError, type WorkflowAiScope, workflowAiModelHash } from "./ai/policy";
2
5
  import type { WorkflowAiRequestInput, WorkflowAiTask } from "./ai/types";
3
6
  import type { WorkflowFieldSchema, WorkflowJsonValue } from "./contracts";
4
7
  import type { WorkflowActionContext, WorkflowActionResult, WorkflowPlannedEffect } from "./definition";
@@ -7,7 +10,14 @@ import { workflowAction } from "./definition";
7
10
  const text = (description: string, optional = false, maxLength = 20_000) =>
8
11
  ({ kind: "string", minLength: 1, maxLength, optional, description }) as const;
9
12
  const identifier = (description: string) => ({ kind: "string", format: "identifier", minLength: 1, maxLength: 120, description }) as const;
10
- const model = text("Optional enabled AI model profile ID.", true, 120);
13
+ const model = {
14
+ kind: "union",
15
+ optional: true,
16
+ variants: [
17
+ { kind: "object", properties: { class: { kind: "string", enum: ["fast", "balanced", "thorough", "maximum"] } } },
18
+ { kind: "object", properties: { id: text("Explicit model variant ID.", false, 241) } },
19
+ ],
20
+ } as const satisfies WorkflowFieldSchema;
11
21
  const prompt = text("Instructions for the AI task.");
12
22
  const choices = {
13
23
  kind: "array",
@@ -58,10 +68,13 @@ type WorkflowAiActionDependencies = {
58
68
  stepKey: string;
59
69
  effectKey: string;
60
70
  request: WorkflowAiRequestInput;
61
- modelProfileId: string;
71
+ modelId: string;
72
+ modelRef?: AiResolvedModelRef;
73
+ modelConfigHash?: string;
74
+ scope?: WorkflowAiScope;
62
75
  }): Promise<{ task: WorkflowAiTask; created: boolean }>;
63
76
  exists(effectKey: string): Promise<boolean>;
64
- resolveModel(requestedModelId?: string): Promise<AiResolvedModel>;
77
+ resolveModel(model?: AiModelRequest): Promise<AiResolvedModel>;
65
78
  submit(taskId: string): Promise<void>;
66
79
  };
67
80
 
@@ -83,7 +96,11 @@ const dependencies = async (): Promise<WorkflowAiActionDependencies> => {
83
96
  const failureCode = (error: unknown): string => {
84
97
  const code =
85
98
  (error as { aiError?: { code?: unknown }; code?: unknown } | null)?.aiError?.code ?? (error as { code?: unknown } | null)?.code;
86
- return typeof code === "string" ? `WORKFLOW_AI_${code.toUpperCase()}` : "WORKFLOW_AI_UNAVAILABLE";
99
+ return typeof code === "string"
100
+ ? code.startsWith("WORKFLOW_AI_")
101
+ ? code
102
+ : `WORKFLOW_AI_${code.toUpperCase()}`
103
+ : "WORKFLOW_AI_UNAVAILABLE";
87
104
  };
88
105
 
89
106
  const taskResult = (task: WorkflowAiTask): WorkflowActionResult<WorkflowJsonValue> | null => {
@@ -105,6 +122,7 @@ export const runWorkflowAiAction = async (
105
122
  ctx: Pick<WorkflowActionContext, "runId" | "stepKey" | "effectKey">,
106
123
  request: WorkflowAiRequestInput,
107
124
  injected?: WorkflowAiActionDependencies,
125
+ scope?: () => Promise<WorkflowAiScope>,
108
126
  ): Promise<WorkflowActionResult<WorkflowJsonValue>> => {
109
127
  const deps = injected ?? (await dependencies());
110
128
  try {
@@ -113,14 +131,17 @@ export const runWorkflowAiAction = async (
113
131
  if (terminal) return terminal;
114
132
 
115
133
  if (!task) {
116
- const resolved = await deps.resolveModel(request.modelProfileId?.trim() || undefined);
134
+ assertAiTaskRequestSize(request);
135
+ const resolved = await deps.resolveModel(request.model);
117
136
  task = (
118
137
  await deps.create({
119
138
  runId: ctx.runId,
120
139
  stepKey: ctx.stepKey,
121
140
  effectKey: ctx.effectKey,
122
141
  request,
123
- modelProfileId: resolved.profile.id,
142
+ modelId: resolved.profile.id,
143
+ modelRef: resolved.model,
144
+ ...(scope ? { scope: await scope(), modelConfigHash: workflowAiModelHash(resolved.profile) } : {}),
124
145
  })
125
146
  ).task;
126
147
  const racedTerminal = taskResult(task);
@@ -134,7 +155,7 @@ export const runWorkflowAiAction = async (
134
155
  state: "failed",
135
156
  code: failureCode(error),
136
157
  message: error instanceof Error ? error.message : "Workflow AI task is unavailable.",
137
- retryable: failureCode(error) === "WORKFLOW_AI_UNAVAILABLE",
158
+ retryable: error instanceof WorkflowAiAdmissionError ? error.retryable : failureCode(error) === "WORKFLOW_AI_UNAVAILABLE",
138
159
  };
139
160
  }
140
161
  };
@@ -149,124 +170,131 @@ export const workflowAiActionCost = async (
149
170
 
150
171
  const planWorkflowAiAction = async (summary: string): Promise<WorkflowPlannedEffect> => ({
151
172
  summary,
173
+ outputUnavailable: true,
152
174
  issues: ["AI output is not available during a dry run."],
153
175
  });
154
176
 
155
- export const AI_WORKFLOW_ACTIONS = {
156
- aiExtractData: workflowAction.idempotent<typeof extractDataConfig, WorkflowJsonValue>({
157
- label: "AI extract structured data",
158
- description: "Extracts one bounded, validated object from supplied input.",
159
- outputType: "core.value",
160
- config: extractDataConfig,
161
- cost: (ctx) => workflowAiActionCost(ctx),
162
- plan: () => planWorkflowAiAction("Extract structured data with AI."),
163
- run: (ctx, values) =>
164
- runWorkflowAiAction(ctx, {
165
- kind: "extract_data",
166
- input: values.input,
167
- prompt: values.prompt,
168
- fields: values.fields,
169
- ...(values.model ? { modelProfileId: values.model } : {}),
170
- }),
171
- }),
172
- aiGenerateText: workflowAction.idempotent({
173
- label: "AI generate text",
174
- description: "Generates one bounded text value without performing a domain action.",
175
- outputType: "core.text",
176
- config: {
177
- kind: "object",
178
- properties: {
179
- prompt,
180
- input: { kind: "value", optional: true, description: "Optional JSON input supplied separately from the prompt." },
181
- model,
182
- maxOutputChars: {
183
- kind: "number",
184
- integer: true,
185
- minimum: 1,
186
- maximum: 20_000,
187
- optional: true,
188
- description: "Maximum generated text length. Defaults to 4000 characters.",
177
+ export const createWorkflowAiActions = (options?: { scope(ctx: WorkflowActionContext): Promise<WorkflowAiScope> }) => {
178
+ const run = (ctx: WorkflowActionContext, request: WorkflowAiRequestInput) =>
179
+ runWorkflowAiAction(ctx, request, undefined, options ? () => options.scope(ctx) : undefined);
180
+ return {
181
+ aiExtractData: workflowAction.idempotent<typeof extractDataConfig, WorkflowJsonValue>({
182
+ label: "AI extract structured data",
183
+ description: "Extracts one bounded, validated object from supplied input.",
184
+ outputType: "core.value",
185
+ config: extractDataConfig,
186
+ cost: (ctx) => (ctx.invocation.mode === "dryRun" ? { maxAiCalls: 1 } : workflowAiActionCost(ctx)),
187
+ plan: () => planWorkflowAiAction("Extract structured data with AI."),
188
+ run: (ctx, values) =>
189
+ run(ctx, {
190
+ kind: "extract_data",
191
+ input: values.input,
192
+ prompt: values.prompt,
193
+ fields: values.fields,
194
+ ...(values.model ? { model: AiModelRequestSchema.parse(values.model) } : {}),
195
+ }),
196
+ }),
197
+ aiGenerateText: workflowAction.idempotent({
198
+ label: "AI generate text",
199
+ description: "Generates one bounded text value without performing a domain action.",
200
+ outputType: "core.text",
201
+ config: {
202
+ kind: "object",
203
+ properties: {
204
+ prompt,
205
+ input: { kind: "value", optional: true, description: "Optional JSON input supplied separately from the prompt." },
206
+ model,
207
+ maxOutputChars: {
208
+ kind: "number",
209
+ integer: true,
210
+ minimum: 1,
211
+ maximum: 20_000,
212
+ optional: true,
213
+ description: "Maximum generated text length. Defaults to 4000 characters.",
214
+ },
215
+ saveAs: identifier("Variable name for the generated text."),
189
216
  },
190
- saveAs: identifier("Variable name for the generated text."),
191
217
  },
192
- },
193
- cost: (ctx) => workflowAiActionCost(ctx),
194
- plan: () => planWorkflowAiAction("Generate text with AI."),
195
- run: (ctx, values) =>
196
- runWorkflowAiAction(ctx, {
197
- kind: "generate_text",
198
- prompt: values.prompt,
199
- ...(values.input === undefined ? {} : { input: values.input }),
200
- ...(values.model ? { modelProfileId: values.model } : {}),
201
- ...(values.maxOutputChars === undefined ? {} : { maxOutputChars: values.maxOutputChars }),
202
- }),
203
- }),
204
- aiClassify: workflowAction.idempotent({
205
- label: "AI classify",
206
- description: "Returns exactly one value from the declared choices.",
207
- outputType: "core.text",
208
- config: {
209
- kind: "object",
210
- properties: {
211
- input: { kind: "value", description: "Bounded JSON value to classify." },
212
- prompt,
213
- choices,
214
- model,
215
- saveAs: identifier("Variable name for the selected choice."),
216
- },
217
- },
218
- cost: (ctx) => workflowAiActionCost(ctx),
219
- plan: () => planWorkflowAiAction("Classify one value with AI."),
220
- run: (ctx, values) =>
221
- runWorkflowAiAction(ctx, {
222
- kind: "classify",
223
- input: values.input,
224
- prompt: values.prompt,
225
- choices: values.choices,
226
- ...(values.model ? { modelProfileId: values.model } : {}),
227
- }),
228
- }),
229
- aiClassifyMany: workflowAction.idempotent({
230
- label: "AI classify many",
231
- description: "Returns a unique subset of the declared choices in their declared order.",
232
- outputType: "core.textArray",
233
- config: {
234
- kind: "object",
235
- properties: {
236
- input: { kind: "value", description: "Bounded JSON value to classify." },
237
- prompt,
238
- choices,
239
- minChoices: {
240
- kind: "number",
241
- integer: true,
242
- minimum: 0,
243
- maximum: 50,
244
- optional: true,
245
- description: "Minimum number of choices. Defaults to zero.",
218
+ cost: (ctx) => (ctx.invocation.mode === "dryRun" ? { maxAiCalls: 1 } : workflowAiActionCost(ctx)),
219
+ plan: () => planWorkflowAiAction("Generate text with AI."),
220
+ run: (ctx, values) =>
221
+ run(ctx, {
222
+ kind: "generate_text",
223
+ prompt: values.prompt,
224
+ ...(values.input === undefined ? {} : { input: values.input }),
225
+ ...(values.model ? { model: AiModelRequestSchema.parse(values.model) } : {}),
226
+ ...(values.maxOutputChars === undefined ? {} : { maxOutputChars: values.maxOutputChars }),
227
+ }),
228
+ }),
229
+ aiClassify: workflowAction.idempotent({
230
+ label: "AI classify",
231
+ description: "Returns exactly one value from the declared choices.",
232
+ outputType: "core.text",
233
+ config: {
234
+ kind: "object",
235
+ properties: {
236
+ input: { kind: "value", description: "Bounded JSON value to classify." },
237
+ prompt,
238
+ choices,
239
+ model,
240
+ saveAs: identifier("Variable name for the selected choice."),
246
241
  },
247
- maxChoices: {
248
- kind: "number",
249
- integer: true,
250
- minimum: 0,
251
- maximum: 50,
252
- optional: true,
253
- description: "Maximum number of choices. Defaults to all declared choices.",
242
+ },
243
+ cost: (ctx) => (ctx.invocation.mode === "dryRun" ? { maxAiCalls: 1 } : workflowAiActionCost(ctx)),
244
+ plan: () => planWorkflowAiAction("Classify one value with AI."),
245
+ run: (ctx, values) =>
246
+ run(ctx, {
247
+ kind: "classify",
248
+ input: values.input,
249
+ prompt: values.prompt,
250
+ choices: values.choices,
251
+ ...(values.model ? { model: AiModelRequestSchema.parse(values.model) } : {}),
252
+ }),
253
+ }),
254
+ aiClassifyMany: workflowAction.idempotent({
255
+ label: "AI classify many",
256
+ description: "Returns a unique subset of the declared choices in their declared order.",
257
+ outputType: "core.textArray",
258
+ config: {
259
+ kind: "object",
260
+ properties: {
261
+ input: { kind: "value", description: "Bounded JSON value to classify." },
262
+ prompt,
263
+ choices,
264
+ minChoices: {
265
+ kind: "number",
266
+ integer: true,
267
+ minimum: 0,
268
+ maximum: 50,
269
+ optional: true,
270
+ description: "Minimum number of choices. Defaults to zero.",
271
+ },
272
+ maxChoices: {
273
+ kind: "number",
274
+ integer: true,
275
+ minimum: 0,
276
+ maximum: 50,
277
+ optional: true,
278
+ description: "Maximum number of choices. Defaults to all declared choices.",
279
+ },
280
+ model,
281
+ saveAs: identifier("Variable name for the selected choices."),
254
282
  },
255
- model,
256
- saveAs: identifier("Variable name for the selected choices."),
257
283
  },
258
- },
259
- cost: (ctx) => workflowAiActionCost(ctx),
260
- plan: () => planWorkflowAiAction("Classify multiple values with AI."),
261
- run: (ctx, values) =>
262
- runWorkflowAiAction(ctx, {
263
- kind: "classify_many",
264
- input: values.input,
265
- prompt: values.prompt,
266
- choices: values.choices,
267
- ...(values.minChoices === undefined ? {} : { minChoices: values.minChoices }),
268
- ...(values.maxChoices === undefined ? {} : { maxChoices: values.maxChoices }),
269
- ...(values.model ? { modelProfileId: values.model } : {}),
270
- }),
271
- }),
272
- } as const;
284
+ cost: (ctx) => (ctx.invocation.mode === "dryRun" ? { maxAiCalls: 1 } : workflowAiActionCost(ctx)),
285
+ plan: () => planWorkflowAiAction("Classify multiple values with AI."),
286
+ run: (ctx, values) =>
287
+ run(ctx, {
288
+ kind: "classify_many",
289
+ input: values.input,
290
+ prompt: values.prompt,
291
+ choices: values.choices,
292
+ ...(values.minChoices === undefined ? {} : { minChoices: values.minChoices }),
293
+ ...(values.maxChoices === undefined ? {} : { maxChoices: values.maxChoices }),
294
+ ...(values.model ? { model: AiModelRequestSchema.parse(values.model) } : {}),
295
+ }),
296
+ }),
297
+ } as const;
298
+ };
299
+
300
+ export const AI_WORKFLOW_ACTIONS = createWorkflowAiActions();
@@ -64,6 +64,37 @@ export const buildWorkflowManifestCompletions = (
64
64
  ): WorkflowCompletionItem[] => {
65
65
  const manifest = workflows.manifest;
66
66
  const context = workflowCompletionContext(source, caret);
67
+ const indentation = context.line.match(/^\s*/)?.[0].length ?? 0;
68
+ const preceding = source
69
+ .slice(0, context.caret - context.line.length)
70
+ .trimEnd()
71
+ .split("\n");
72
+ const parent = preceding.findLast((line) => line.trim() && (line.match(/^\s*/)?.[0].length ?? 0) < indentation);
73
+ const inlineCondition = /^\s*(?:-\s*)?(?:if|not):\s*[A-Za-z]*$/.test(context.line);
74
+ const parentCondition = /^(\s*(?:-\s*)?)(?:if|all|any|not):\s*$/.exec(parent ?? "");
75
+ const blockCondition =
76
+ !context.key && /^\s*(?:-\s*)?[A-Za-z]*$/.test(context.line) && parentCondition && indentation > parentCondition[1]!.length;
77
+ if (inlineCondition || blockCondition) {
78
+ return [
79
+ "equals",
80
+ "notEquals",
81
+ "includes",
82
+ "textEquals",
83
+ "contains",
84
+ "startsWith",
85
+ "endsWith",
86
+ "greaterThan",
87
+ "lessThan",
88
+ "atLeast",
89
+ "atMost",
90
+ "exists",
91
+ "all",
92
+ "any",
93
+ "not",
94
+ ].map((operator) =>
95
+ workflowCompletionItem(context, "keyword", operator, inlineCondition ? `{ ${operator}: [] }` : `${operator}: `, "Workflow condition"),
96
+ );
97
+ }
67
98
  if (context.key === "type") {
68
99
  return manifest.inputs.map((input) => workflowCompletionItem(context, "literal", input.kind, input.kind, input.description));
69
100
  }
@@ -107,6 +107,7 @@ export type WorkflowIrTrigger = {
107
107
 
108
108
  export type WorkflowCondition =
109
109
  | { operator: "equals" | "notEquals"; operands: [WorkflowJsonValue, WorkflowJsonValue] }
110
+ | { operator: "greaterThan" | "lessThan" | "atLeast" | "atMost"; operands: [WorkflowJsonValue, WorkflowJsonValue] }
110
111
  | { operator: "includes"; operands: [WorkflowJsonValue, WorkflowJsonValue] }
111
112
  | { operator: "textEquals" | "contains" | "startsWith" | "endsWith"; operands: [WorkflowJsonValue, WorkflowJsonValue] }
112
113
  | { operator: "exists"; reference: string }
@@ -144,6 +144,8 @@ export type WorkflowPlannedEffect = {
144
144
  * that cannot tell a planned record from a real one will act on a fiction.
145
145
  */
146
146
  output?: WorkflowJsonValue;
147
+ /** Omit the planned output when it cannot be predicted; explain why in `issues`. */
148
+ outputUnavailable?: true;
147
149
  /** Why the plan is not fully determined — surfaced in the dry-run view. */
148
150
  issues?: string[];
149
151
  };
@@ -77,6 +77,21 @@ export const createWorkflowYamlHighlighter = (workflows?: DefinedWorkflowModule)
77
77
  "required",
78
78
  "with",
79
79
  "if",
80
+ "equals",
81
+ "notEquals",
82
+ "includes",
83
+ "textEquals",
84
+ "contains",
85
+ "startsWith",
86
+ "endsWith",
87
+ "greaterThan",
88
+ "lessThan",
89
+ "atLeast",
90
+ "atMost",
91
+ "exists",
92
+ "all",
93
+ "any",
94
+ "not",
80
95
  "then",
81
96
  "else",
82
97
  "switch",
@@ -1,4 +1,5 @@
1
- export { AI_WORKFLOW_ACTIONS } from "./ai-actions";
1
+ export type { WorkflowAiBudget, WorkflowAiScope } from "./ai/policy";
2
+ export { AI_WORKFLOW_ACTIONS, createWorkflowAiActions } from "./ai-actions";
2
3
  export {
3
4
  buildWorkflowManifestCompletions,
4
5
  type WorkflowCompletionContext,
@@ -14,6 +14,7 @@ import type {
14
14
  } from "../contracts";
15
15
  import type { DefinedWorkflowModule } from "../module";
16
16
  import { hashWorkflowJson, hashWorkflowSource, normalizeWorkflowJson } from "./canonical";
17
+ import { parseWorkflowValueString } from "./expressions";
17
18
  import { validateWorkflowField, workflowDiagnostic, workflowRecord } from "./schema";
18
19
  import { parseWorkflowYaml } from "./strict-yaml";
19
20
 
@@ -212,9 +213,23 @@ const compileCondition = (
212
213
  const operators = Object.keys(value);
213
214
  if (
214
215
  operators.length !== 1 ||
215
- !["equals", "notEquals", "includes", "textEquals", "contains", "startsWith", "endsWith", "exists", "all", "any", "not"].includes(
216
- operators[0]!,
217
- )
216
+ ![
217
+ "equals",
218
+ "notEquals",
219
+ "includes",
220
+ "textEquals",
221
+ "contains",
222
+ "startsWith",
223
+ "endsWith",
224
+ "greaterThan",
225
+ "lessThan",
226
+ "atLeast",
227
+ "atMost",
228
+ "exists",
229
+ "all",
230
+ "any",
231
+ "not",
232
+ ].includes(operators[0]!)
218
233
  ) {
219
234
  addDiagnostic(context, "condition.operator", "Condition must contain exactly one supported operator", path);
220
235
  return undefined;
@@ -247,6 +262,22 @@ const compileCondition = (
247
262
  addDiagnostic(context, "condition.operands", `${operator} requires exactly two operands`, [...path, operator]);
248
263
  return undefined;
249
264
  }
265
+ if (operator === "greaterThan" || operator === "lessThan" || operator === "atLeast" || operator === "atMost") {
266
+ operand.forEach((value, index) => {
267
+ const expression = typeof value === "string" ? parseWorkflowValueString(value) : undefined;
268
+ if (
269
+ (typeof value !== "number" || !Number.isFinite(value)) &&
270
+ (expression?.kind !== "expression" || expression.expression.kind !== "reference")
271
+ ) {
272
+ addDiagnostic(context, "condition.type", `${operator} operand ${index + 1} must be a finite number or reference`, [
273
+ ...path,
274
+ operator,
275
+ index,
276
+ ]);
277
+ }
278
+ });
279
+ return { operator, operands: [normalizeWorkflowJson(operand[0]!), normalizeWorkflowJson(operand[1]!)] };
280
+ }
250
281
  return {
251
282
  operator: operator as "equals" | "notEquals" | "includes" | "textEquals" | "contains" | "startsWith" | "endsWith",
252
283
  operands: [normalizeWorkflowJson(operand[0]!), normalizeWorkflowJson(operand[1]!)],