octaflow 0.25.0 → 0.27.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 CHANGED
@@ -200,6 +200,7 @@ next to what your steps actually do.
200
200
  | 🔁 | **Retry & timeout** — per-step `maxAttempts`, fixed/exponential backoff, wall-clock timeout |
201
201
  | 💤 | **Durable sleep** — hold a step in the queue for N ms (survives restarts) |
202
202
  | 🔀 | **Conditional branching** — `when` guards skip a step and its branch; `join: 'any'` converges |
203
+ | 🎯 | **Decisions** — typed choices with a confidence from a swappable decider (a function, a model, your own); below a threshold a person settles it |
203
204
  | ⏳ | **Deadlines** — a budget on a suspended step (fail, or continue with a stand-in answer) and on a whole run |
204
205
  | ♻️ | **Retry a failed run** — `retryWorkflow` resumes from the failure point; completed steps keep their output |
205
206
  | 🚦 | **Concurrency & rate limiting** — per-step-type caps and token buckets via a pluggable gate |
@@ -295,6 +296,10 @@ export const flowStepLease = pgTable('flow_step_lease', flowStepLeaseColumns, (t
295
296
  ]);
296
297
  ```
297
298
 
299
+ The names are yours: the store's `ON CONFLICT` clauses target column lists, never a
300
+ constraint by name, so what must match is each index's columns and its partial `WHERE`,
301
+ not what you call it.
302
+
298
303
  The store stays plain SQL, bound to the `schema` you hand `createPgWorkflowStore`,
299
304
  `createPgStepGate` and `createPgEventSink` — and, for the gate, to the `tables` names — so
300
305
  declare the tables under the same schema and names. A test in the package holds every column
@@ -317,6 +322,7 @@ Full docs — concepts, every feature, production wiring and the API reference
317
322
  | [Retry & timeout](https://octaflow.octabits.io/core/retry-and-timeout/) | attempt budgets, backoff, and how a failure is classified |
318
323
  | [Fan-out & map](https://octaflow.octabits.io/core/fan-out-and-map/) | one child step per item of a runtime list |
319
324
  | [Branching](https://octaflow.octabits.io/core/branching/) | `when` guards and join rules — if/else over a static DAG |
325
+ | [Decisions](https://octaflow.octabits.io/core/decisions/) | typed choices with a confidence, a swappable decider, a person below the threshold |
320
326
  | [Signals](https://octaflow.octabits.io/core/signals/) · [Sub-workflows](https://octaflow.octabits.io/core/sub-workflows/) · [Saga](https://octaflow.octabits.io/core/saga-compensation/) | suspend, nest, and roll back |
321
327
  | [Deadlines](https://octaflow.octabits.io/core/deadlines/) | budgets for a suspended step and for a whole run |
322
328
  | [Heartbeats](https://octaflow.octabits.io/core/heartbeats/) | liveness for long steps, and interrupting one that was cancelled |
@@ -351,8 +357,9 @@ Runnable, focused examples live in [`examples/`](./examples) — see [`examples/
351
357
  | 14 | `14-live-progress.ts` | `FlowObserver` → SSE fan-out (build your own dashboard) |
352
358
  | 15 | `15-conditional-branching.ts` | `when` guards + a `join: 'any'` convergence |
353
359
  | 16 | `16-deadlines-and-retry.ts` | wait deadlines, run deadlines, `retryWorkflow`, heartbeats |
360
+ | 17 | `17-decisions.ts` | a decision step, a review below the threshold, branches on the outcome |
354
361
 
355
- The in-memory examples (01–11, 14–16) share a small driver, [`examples/runtime.ts`](./examples/runtime.ts),
362
+ The in-memory examples (01–11, 14–17) share a small driver, [`examples/runtime.ts`](./examples/runtime.ts),
356
363
  that builds an engine over the in-memory store and an in-process queue you drain.
357
364
 
358
365
  ---
@@ -1,6 +1,6 @@
1
- import { E as RetryPolicy, g as TypedWorkflow, h as TypedStepContext, m as TypedStep, nt as Result, tt as FlowErrorShape } from "../index-BURAYZui.js";
1
+ import { A as TypedStep, H as RetryPolicy, M as TypedWorkflow, _t as FlowErrorShape, f as Decider, j as TypedStepContext, vt as Result } from "../index-BGmJYvTt.js";
2
2
  import { n as Logger } from "../logger-BCus3AOa.js";
3
- import { i as BuildStepContextArgs, o as WorkflowHooks } from "../hooks-BdxIcc92.js";
3
+ import { i as BuildStepContextArgs, o as WorkflowHooks } from "../hooks-BGOHvR37.js";
4
4
  import { C as isSubprocessorResidency, S as embeddingDimensionsFor, _ as EmbeddingProviderMeta, a as AiProcessingRegion, b as OpenAiCompatibleProviderId, c as AiProviderModelOption, d as AiVendorResidency, f as DEFAULT_AI_PROVIDER, g as EmbeddingProviderId, h as EMBEDDING_PROVIDER_IDS, i as AiEndpointResidency, l as AiResidencyError, m as EMBEDDING_PROVIDERS, n as AI_PROVIDER_IDS, o as AiProviderId, p as DEFAULT_EMBEDDING_PROVIDER, r as AiEndpointRegion, s as AiProviderMeta, t as AI_PROVIDERS, u as AiResidencyResult, v as OPENAI_COMPATIBLE_PROVIDER, w as resolveEndpointResidency, x as OpenAiCompatibleProviderMeta, y as OPENAI_COMPATIBLE_PROVIDER_ID } from "../provider-registry-BvXBqJDR.js";
5
5
  import { z } from "zod";
6
6
  import { EmbeddingModelV4, LanguageModelV4, SharedV4ProviderMetadata } from "@ai-sdk/provider";
@@ -148,6 +148,31 @@ export declare function buildAiWorkflow<TInput extends Record<string, unknown>,
148
148
  steps: TSteps;
149
149
  }): TypedWorkflow<TInput, AiContext<THost>>;
150
150
  //#endregion
151
+ //#region src/ai/model-decider.d.ts
152
+ export interface ModelDeciderOptions {
153
+ /**
154
+ * The model to ask. Omitted: the step's `ctx.context.model`, which the AI
155
+ * hooks instrument for token usage and cost. Pass a model wrapped by your
156
+ * disclosure gate either way — the decision input leaves the process.
157
+ */
158
+ model?: LanguageModelV4;
159
+ /** Recorded on every decision. Default `model:<modelId>` of the model that ran. */
160
+ id?: string;
161
+ /** Prepended to the request's own instructions. */
162
+ system?: string;
163
+ /** Passed to the AI SDK. Default 0 — the step's retry policy owns retries. */
164
+ maxRetries?: number;
165
+ }
166
+ /**
167
+ * A decider over a language model with structured output: the options become
168
+ * an enum the model must choose from, and the model states its confidence.
169
+ *
170
+ * `calibrated` is `false` — a language model's stated confidence is not a
171
+ * probability you can set a threshold against with any precision. Treat its
172
+ * threshold as a policy knob and check it against what reviewers overrule.
173
+ */
174
+ export declare function createModelDecider<THost = unknown>(options?: ModelDeciderOptions): Decider<AiContext<THost>>;
175
+ //#endregion
151
176
  //#region src/ai/hooks.d.ts
152
177
  export interface AiModelResolver<THost = unknown> {
153
178
  /** Resolve the base (uninstrumented) model for a step. */
package/dist/ai/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { S as ok, b as noopLogger, f as defineStep, l as buildWorkflow, x as err } from "../core-DNWPwFaE.js";
1
+ import { D as ok, E as err, T as noopLogger, _ as defineStep, m as buildWorkflow } from "../core-C9h3-KFr.js";
2
2
  import { a as readRefusalDetails, i as isAiRefusalError, n as REFUSAL_FINISH_REASON, r as createRefusalAwareModel, t as AiRefusalError } from "../refusal-BsL2HPDb.js";
3
3
  import { a as EMBEDDING_PROVIDERS, c as OPENAI_COMPATIBLE_PROVIDER_ID, d as resolveEndpointResidency, i as DEFAULT_EMBEDDING_PROVIDER, l as embeddingDimensionsFor, n as AI_PROVIDER_IDS, o as EMBEDDING_PROVIDER_IDS, r as DEFAULT_AI_PROVIDER, s as OPENAI_COMPATIBLE_PROVIDER, t as AI_PROVIDERS, u as isSubprocessorResidency } from "../provider-registry-BRwjC6_3.js";
4
- import { wrapEmbeddingModel, wrapLanguageModel } from "ai";
4
+ import { z } from "zod";
5
+ import { Output, generateText, wrapEmbeddingModel, wrapLanguageModel } from "ai";
5
6
  //#region src/ai/instrumented-model.ts
6
7
  function createUsageAccumulator() {
7
8
  const total = {
@@ -318,6 +319,53 @@ function buildAiWorkflow(config) {
318
319
  return buildWorkflow(config);
319
320
  }
320
321
  //#endregion
322
+ //#region src/ai/model-decider.ts
323
+ const DEFAULT_SYSTEM = [
324
+ "You classify the input by answering each question with exactly one of its options.",
325
+ "For each answer give a confidence between 0 and 1: the probability that the option is right.",
326
+ "Use a low confidence when the input is ambiguous or the options do not fit it well. Keep reasons to one sentence."
327
+ ].join(" ");
328
+ function responseSchema(questions) {
329
+ return z.object(Object.fromEntries(Object.entries(questions).map(([key, options]) => [key, z.object({
330
+ choice: z.enum(options),
331
+ confidence: z.number().min(0).max(1),
332
+ reason: z.string()
333
+ })])));
334
+ }
335
+ function prompt(request) {
336
+ return `Questions:\n${Object.entries(request.questions).map(([key, options]) => `- ${key}: ${options.join(" | ")}`).join("\n")}\n\nInput:\n${JSON.stringify(request.input, null, 2)}`;
337
+ }
338
+ /**
339
+ * A decider over a language model with structured output: the options become
340
+ * an enum the model must choose from, and the model states its confidence.
341
+ *
342
+ * `calibrated` is `false` — a language model's stated confidence is not a
343
+ * probability you can set a threshold against with any precision. Treat its
344
+ * threshold as a policy knob and check it against what reviewers overrule.
345
+ */
346
+ function createModelDecider(options = {}) {
347
+ return {
348
+ id: options.id ?? (options.model ? `model:${options.model.modelId}` : "model"),
349
+ calibrated: false,
350
+ async decide(request, context) {
351
+ const model = options.model ?? context.model;
352
+ const system = [options.system ?? DEFAULT_SYSTEM, request.instructions].filter(Boolean).join("\n\n");
353
+ const { output } = await generateText({
354
+ model,
355
+ system,
356
+ prompt: prompt(request),
357
+ output: Output.object({ schema: responseSchema(request.questions) }),
358
+ maxRetries: options.maxRetries ?? 0,
359
+ abortSignal: request.signal
360
+ });
361
+ return {
362
+ answers: output,
363
+ decider: options.id ?? `model:${model.modelId}`
364
+ };
365
+ }
366
+ };
367
+ }
368
+ //#endregion
321
369
  //#region src/ai/hooks.ts
322
370
  /**
323
371
  * Wire the AI behaviors (model instrumentation, token/cost capture, quota,
@@ -662,4 +710,4 @@ function thinkingSettingsFor(providerId, modelId, opts) {
662
710
  return { temperature: opts.temperature };
663
711
  }
664
712
  //#endregion
665
- export { AI_PROVIDERS, AI_PROVIDER_IDS, AiRefusalError, DEFAULT_AI_PROVIDER, DEFAULT_AI_QUOTA, DEFAULT_EMBEDDING_PROVIDER, DEFAULT_MODEL_PRICING, EMBEDDING_PROVIDERS, EMBEDDING_PROVIDER_IDS, OPENAI_COMPATIBLE_PROVIDER, OPENAI_COMPATIBLE_PROVIDER_ID, REFUSAL_FINISH_REASON, ZERO_MODEL_PRICING, anthropicThinkingSettings, buildAiWorkflow, createAiQuotaService, createAiUsageAggregationService, createAiWorkflowHooks, createCostEstimator, createEmbeddingUsageAccumulator, createInstrumentedEmbeddingModel, createInstrumentedModel, createRefusalAwareModel, createUsageAccumulator, defineAiStep, embeddingDimensionsFor, estimateCostMicros, isAiRefusalError, isSubprocessorResidency, readRefusalDetails, resolveEndpointResidency, supportsAdaptiveThinking, supportsToolSearch, thinkingSettingsFor };
713
+ export { AI_PROVIDERS, AI_PROVIDER_IDS, AiRefusalError, DEFAULT_AI_PROVIDER, DEFAULT_AI_QUOTA, DEFAULT_EMBEDDING_PROVIDER, DEFAULT_MODEL_PRICING, EMBEDDING_PROVIDERS, EMBEDDING_PROVIDER_IDS, OPENAI_COMPATIBLE_PROVIDER, OPENAI_COMPATIBLE_PROVIDER_ID, REFUSAL_FINISH_REASON, ZERO_MODEL_PRICING, anthropicThinkingSettings, buildAiWorkflow, createAiQuotaService, createAiUsageAggregationService, createAiWorkflowHooks, createCostEstimator, createEmbeddingUsageAccumulator, createInstrumentedEmbeddingModel, createInstrumentedModel, createModelDecider, createRefusalAwareModel, createUsageAccumulator, defineAiStep, embeddingDimensionsFor, estimateCostMicros, isAiRefusalError, isSubprocessorResidency, readRefusalDetails, resolveEndpointResidency, supportsAdaptiveThinking, supportsToolSearch, thinkingSettingsFor };
@@ -66,7 +66,7 @@ export interface LazyEmbeddingModelMeta {
66
66
  * next call rather than cached.
67
67
  */
68
68
  export declare function createLazyEmbeddingModel(meta: LazyEmbeddingModelMeta, load: () => Promise<EmbeddingModelV4>): EmbeddingModelV4;
69
- export type AiProviderConfigErrorKey = 'ai_residency_undeclared' | 'ai_residency_invalid' | 'ai_base_url_invalid' | 'ai_model_required' | 'ai_embedding_dimensions_invalid';
69
+ export type AiProviderConfigErrorKey = 'ai_residency_undeclared' | 'ai_residency_invalid' | 'ai_base_url_invalid' | 'ai_model_required' | 'ai_model_undeclared' | 'ai_embedding_dimensions_invalid';
70
70
  /**
71
71
  * A provider configuration that cannot work. Thrown before any SDK is loaded
72
72
  * or any request is made: it is a deployment mistake, found at wiring time.
@@ -143,4 +143,66 @@ export interface OpenAiCompatibleEmbeddingProvider {
143
143
  export declare function createOpenAiCompatibleModelProvider(config: OpenAiCompatibleModelProviderConfig): Promise<OpenAiCompatibleModelProvider>;
144
144
  /** An embedding model on an OpenAI-compatible endpoint, with its declared width. */
145
145
  export declare function createOpenAiCompatibleEmbeddingProvider(config: OpenAiCompatibleEmbeddingProviderConfig): Promise<OpenAiCompatibleEmbeddingProvider>;
146
+ export interface OpenAiCompatibleGatewayModel {
147
+ /** Where the gateway sends this alias. Validated like an endpoint's residency. */
148
+ residency: AiEndpointResidency;
149
+ }
150
+ /**
151
+ * `residency` is where the gateway itself runs, usually `{ region: 'self-hosted' }`.
152
+ * It decides whether plain http is acceptable for the hop to the gateway; where a
153
+ * call ends up is the alias's residency in `models`.
154
+ */
155
+ export interface OpenAiCompatibleGatewayConfig extends OpenAiCompatibleEndpointConfig {
156
+ /** Every alias this deployment may call, keyed by the model name the gateway knows. */
157
+ models: Record<string, OpenAiCompatibleGatewayModel>;
158
+ /** As for {@link OpenAiCompatibleModelProviderConfig.structuredOutputs}. */
159
+ structuredOutputs?: boolean;
160
+ /** As for {@link OpenAiCompatibleModelProviderConfig.includeUsage}. */
161
+ includeUsage?: boolean;
162
+ }
163
+ export interface GatewayCallOptions {
164
+ /**
165
+ * Sent as the request body's `metadata`, which LiteLLM keeps in its spend log,
166
+ * so a call there can be matched to the host's own record: a workflow id, a
167
+ * scope key. It leaves the house as written and is never redacted: put ids
168
+ * here, never content. Added innermost, so a disclosure gate wrapped around the
169
+ * model neither digests nor redacts it.
170
+ */
171
+ metadata?: Record<string, string>;
172
+ }
173
+ export interface OpenAiCompatibleGatewayProvider {
174
+ providerId: OpenAiCompatibleProviderId;
175
+ /** Where the gateway itself runs. */
176
+ residency: AiEndpointResidency;
177
+ /** Every declared alias with its validated residency: what a settings page or a subprocessor annex lists. */
178
+ models: Readonly<Record<string, AiEndpointResidency>>;
179
+ /** The residency of one alias. Throws `ai_model_undeclared` for an alias the declaration does not name. */
180
+ residencyOf(alias: string): AiEndpointResidency;
181
+ /** A Chat Completions model for a declared alias, wrapped for refusal detection. */
182
+ languageModel(alias: string, options?: GatewayCallOptions): LanguageModelV4;
183
+ }
184
+ /**
185
+ * A model gateway — LiteLLM, or any router with the OpenAI-compatible API — as
186
+ * refusal-aware models, one per declared alias, each with the residency of
187
+ * where the gateway sends it.
188
+ *
189
+ * ```ts
190
+ * const gateway = await createOpenAiCompatibleGatewayProvider({
191
+ * baseUrl: 'http://litellm.ai.svc:4000/v1',
192
+ * apiKey: process.env.LITELLM_KEY,
193
+ * residency: { region: 'self-hosted' },
194
+ * models: {
195
+ * 'approval-confidential': { residency: { region: 'self-hosted' } },
196
+ * 'approval-public': { residency: { region: 'eu', legalName: 'Example Inference GmbH', country: 'DE' } },
197
+ * },
198
+ * });
199
+ * const model = gateway.languageModel('approval-confidential', { metadata: { workflowId } });
200
+ * ```
201
+ *
202
+ * The declaration is what the gateway is configured to do, stated by the
203
+ * deployment: this provider cannot see the gateway's routing, and a fallback
204
+ * the gateway adds on its own breaks the declaration silently. Keep the two
205
+ * in the same review.
206
+ */
207
+ export declare function createOpenAiCompatibleGatewayProvider(config: OpenAiCompatibleGatewayConfig): Promise<OpenAiCompatibleGatewayProvider>;
146
208
  //#endregion
@@ -1,5 +1,6 @@
1
1
  import { r as createRefusalAwareModel } from "../../refusal-BsL2HPDb.js";
2
2
  import { a as EMBEDDING_PROVIDERS, c as OPENAI_COMPATIBLE_PROVIDER_ID, d as resolveEndpointResidency, t as AI_PROVIDERS } from "../../provider-registry-BRwjC6_3.js";
3
+ import { wrapLanguageModel } from "ai";
3
4
  //#region src/ai/providers/index.ts
4
5
  const LLM_LOADERS = {
5
6
  anthropic: async (apiKey, baseURL) => {
@@ -186,5 +187,90 @@ async function createOpenAiCompatibleEmbeddingProvider(config) {
186
187
  embeddingModel: () => provider.embeddingModel(modelId)
187
188
  };
188
189
  }
190
+ /** Puts `metadata` into the request body under the provider's own options key, merged over any the call carries. */
191
+ function withGatewayMetadata(model, optionsKey, metadata) {
192
+ const fixed = { ...metadata };
193
+ return wrapLanguageModel({
194
+ model,
195
+ middleware: {
196
+ specificationVersion: "v4",
197
+ transformParams: async ({ params }) => {
198
+ const own = params.providerOptions?.[optionsKey] ?? {};
199
+ const existing = own.metadata !== null && typeof own.metadata === "object" && !Array.isArray(own.metadata) ? own.metadata : {};
200
+ const merged = {
201
+ ...own,
202
+ metadata: {
203
+ ...existing,
204
+ ...fixed
205
+ }
206
+ };
207
+ return {
208
+ ...params,
209
+ providerOptions: {
210
+ ...params.providerOptions,
211
+ [optionsKey]: merged
212
+ }
213
+ };
214
+ }
215
+ }
216
+ });
217
+ }
218
+ /**
219
+ * A model gateway — LiteLLM, or any router with the OpenAI-compatible API — as
220
+ * refusal-aware models, one per declared alias, each with the residency of
221
+ * where the gateway sends it.
222
+ *
223
+ * ```ts
224
+ * const gateway = await createOpenAiCompatibleGatewayProvider({
225
+ * baseUrl: 'http://litellm.ai.svc:4000/v1',
226
+ * apiKey: process.env.LITELLM_KEY,
227
+ * residency: { region: 'self-hosted' },
228
+ * models: {
229
+ * 'approval-confidential': { residency: { region: 'self-hosted' } },
230
+ * 'approval-public': { residency: { region: 'eu', legalName: 'Example Inference GmbH', country: 'DE' } },
231
+ * },
232
+ * });
233
+ * const model = gateway.languageModel('approval-confidential', { metadata: { workflowId } });
234
+ * ```
235
+ *
236
+ * The declaration is what the gateway is configured to do, stated by the
237
+ * deployment: this provider cannot see the gateway's routing, and a fallback
238
+ * the gateway adds on its own breaks the declaration silently. Keep the two
239
+ * in the same review.
240
+ */
241
+ async function createOpenAiCompatibleGatewayProvider(config) {
242
+ const residency = checkEndpoint(config);
243
+ const declared = config.models;
244
+ if (declared === null || typeof declared !== "object" || Object.keys(declared).length === 0) throw new AiProviderConfigError("ai_model_required", "a gateway needs at least one declared model alias in models");
245
+ const models = {};
246
+ for (const [alias, model] of Object.entries(declared)) {
247
+ requireModel(alias, "a model alias");
248
+ const resolved = resolveEndpointResidency(model?.residency);
249
+ if (!resolved.ok) throw new AiProviderConfigError(resolved.error.key, `model ${JSON.stringify(alias)}: ${resolved.error.message}`);
250
+ models[alias] = resolved.value;
251
+ }
252
+ Object.freeze(models);
253
+ const provider = await loadOpenAiCompatible(config, {
254
+ structuredOutputs: config.structuredOutputs,
255
+ includeUsage: config.includeUsage
256
+ });
257
+ const optionsKey = config.name ?? "openai-compatible";
258
+ const residencyOf = (alias) => {
259
+ const found = Object.hasOwn(models, alias) ? models[alias] : void 0;
260
+ if (found === void 0) throw new AiProviderConfigError("ai_model_undeclared", `model ${JSON.stringify(alias)} is not declared for this gateway; declared: ${Object.keys(models).join(", ")}`);
261
+ return found;
262
+ };
263
+ return {
264
+ providerId: OPENAI_COMPATIBLE_PROVIDER_ID,
265
+ residency,
266
+ models,
267
+ residencyOf,
268
+ languageModel: (alias, options) => {
269
+ residencyOf(alias);
270
+ const chat = provider.chatModel(alias);
271
+ return createRefusalAwareModel(options?.metadata ? withGatewayMetadata(chat, optionsKey, options.metadata) : chat);
272
+ }
273
+ };
274
+ }
189
275
  //#endregion
190
- export { AiProviderConfigError, createEmbeddingModelFactory, createEmbeddingModelProvider, createLanguageModelFactory, createLanguageModelProvider, createLazyEmbeddingModel, createOpenAiCompatibleEmbeddingProvider, createOpenAiCompatibleModelProvider };
276
+ export { AiProviderConfigError, createEmbeddingModelFactory, createEmbeddingModelProvider, createLanguageModelFactory, createLanguageModelProvider, createLazyEmbeddingModel, createOpenAiCompatibleEmbeddingProvider, createOpenAiCompatibleGatewayProvider, createOpenAiCompatibleModelProvider };
@@ -515,6 +515,222 @@ function buildWorkflow(config) {
515
515
  };
516
516
  }
517
517
  //#endregion
518
+ //#region src/core/decision.ts
519
+ /**
520
+ * A decider over a plain function: every answer has confidence 1, so it never
521
+ * asks for review. For rules you can state, and for tests.
522
+ */
523
+ function functionDecider(id, decide) {
524
+ return {
525
+ id: `function:${id}`,
526
+ calibrated: true,
527
+ async decide(request, context) {
528
+ const choices = await decide(request, context);
529
+ return { answers: Object.fromEntries(Object.entries(choices).map(([key, choice]) => [key, {
530
+ choice,
531
+ confidence: 1
532
+ }])) };
533
+ }
534
+ };
535
+ }
536
+ function answerSchema(options) {
537
+ const option = z.enum(options);
538
+ return z.object({
539
+ choice: option,
540
+ confidence: z.number().min(0).max(1),
541
+ distribution: z.partialRecord(option, z.number().min(0).max(1)).optional(),
542
+ reason: z.string().optional()
543
+ });
544
+ }
545
+ function decisionOutputSchema(questions) {
546
+ const keys = Object.keys(questions);
547
+ return z.object({
548
+ answers: z.object(Object.fromEntries(Object.entries(questions).map(([key, options]) => [key, answerSchema(options)]))),
549
+ decider: z.string(),
550
+ calibrated: z.boolean(),
551
+ review: z.array(z.enum(keys))
552
+ });
553
+ }
554
+ function decisionReviewSchema(questions) {
555
+ return z.object({
556
+ answers: z.object(Object.fromEntries(Object.entries(questions).map(([key, options]) => [key, z.enum(options).optional()]))).strict(),
557
+ reviewer: z.string().optional()
558
+ });
559
+ }
560
+ function decisionOutcomeSchema(questions) {
561
+ const entries = Object.entries(questions);
562
+ return z.object({
563
+ choices: z.object(Object.fromEntries(entries.map(([key, options]) => [key, z.enum(options)]))),
564
+ decidedBy: z.object(Object.fromEntries(entries.map(([key]) => [key, z.enum(["decider", "person"])]))),
565
+ decider: z.string(),
566
+ reviewer: z.string().optional()
567
+ });
568
+ }
569
+ const emptyInputSchema = z.object({});
570
+ /**
571
+ * Hold a decider's response to the questions: every question answered, every
572
+ * choice one of its options. A decider that breaks this is broken, not flaky —
573
+ * the error is not retried.
574
+ */
575
+ function checkResponse(type, questions, decider, response) {
576
+ const answers = {};
577
+ for (const [key, options] of Object.entries(questions)) {
578
+ const answer = response.answers[key];
579
+ if (!answer) throw nonRetryableError(`[${type}] decider '${decider}' gave no answer to '${key}'`);
580
+ if (!options.includes(answer.choice)) throw nonRetryableError(`[${type}] decider '${decider}' answered '${key}' with '${answer.choice}', not one of: ${options.join(", ")}`);
581
+ if (!(answer.confidence >= 0 && answer.confidence <= 1)) throw nonRetryableError(`[${type}] decider '${decider}' gave '${key}' a confidence of ${answer.confidence}, outside [0, 1]`);
582
+ answers[key] = answer;
583
+ }
584
+ return answers;
585
+ }
586
+ /**
587
+ * Define a **decision** step: the decider answers each question with one of
588
+ * its options and a confidence; the output is typed by the options and
589
+ * validated against them, so a dependent can branch on it with a plain `when`.
590
+ *
591
+ * ```ts
592
+ * const triage = defineDecisionStep({
593
+ * type: 'mail:triage',
594
+ * workflowInputSchema: input,
595
+ * dependencies: { parseMail },
596
+ * questions: {
597
+ * tone: ['aggressive', 'friendly'],
598
+ * audience: ['invoicing', 'customer-support', 'tech-support'],
599
+ * },
600
+ * input: (ctx) => ctx.deps.parseMail,
601
+ * decider: createModelDecider(),
602
+ * threshold: 0.8,
603
+ * });
604
+ * ```
605
+ *
606
+ * Questions under `threshold` land in `output.review`; pair the step with
607
+ * {@link defineDecisionReviewStep} and {@link defineDecisionOutcomeStep} to have
608
+ * a person settle them.
609
+ */
610
+ function defineDecisionStep(config) {
611
+ const { type, questions, decider, threshold } = config;
612
+ const keys = Object.keys(questions);
613
+ if (keys.length === 0) throw new Error(`[${type}] a decision needs at least one question`);
614
+ for (const [key, options] of Object.entries(questions)) if (new Set(options).size !== options.length) throw new Error(`[${type}] question '${key}' repeats an option`);
615
+ const thresholdOf = (key) => typeof threshold === "number" ? threshold : threshold?.[key];
616
+ return {
617
+ ...defineStep({
618
+ type,
619
+ workflowInputSchema: config.workflowInputSchema,
620
+ outputSchema: decisionOutputSchema(questions),
621
+ dependencies: config.dependencies,
622
+ when: config.when,
623
+ join: config.join,
624
+ retry: config.retry,
625
+ isRetryable: config.isRetryable,
626
+ timeoutMs: config.timeoutMs,
627
+ handler: async (ctx) => {
628
+ const response = await decider.decide({
629
+ questions,
630
+ input: await config.input(ctx),
631
+ instructions: config.instructions,
632
+ signal: ctx.signal
633
+ }, ctx.context);
634
+ const deciderId = response.decider ?? decider.id;
635
+ const answers = checkResponse(type, questions, deciderId, response);
636
+ const review = keys.filter((key) => {
637
+ const t = thresholdOf(key);
638
+ return t !== void 0 && answers[key].confidence < t;
639
+ });
640
+ return {
641
+ answers,
642
+ decider: deciderId,
643
+ calibrated: decider.calibrated,
644
+ review
645
+ };
646
+ }
647
+ }),
648
+ questions
649
+ };
650
+ }
651
+ /** The single entry of a one-step dependency map, or a definition-time error. */
652
+ function soleEntry(type, role, map) {
653
+ const entries = Object.entries(map);
654
+ if (entries.length !== 1) throw new Error(`[${type}] '${role}' takes exactly one step, keyed by its step key — got ${entries.length}`);
655
+ return entries[0];
656
+ }
657
+ /**
658
+ * Define the **review** of a decision: a wait step that suspends only when the
659
+ * decision flagged a question, until `engine.resumeStep` delivers a
660
+ * {@link DecisionReview} — the options a person chose. When nothing was
661
+ * flagged the step is skipped.
662
+ *
663
+ * `onTimeout: 'accept'` settles an expired review by keeping the decider's
664
+ * answers; the default `'fail'` ends the run.
665
+ */
666
+ function defineDecisionReviewStep(config) {
667
+ const [key, decision] = soleEntry(config.type, "decision", config.decision);
668
+ return defineStep({
669
+ type: config.type,
670
+ workflowInputSchema: emptyInputSchema,
671
+ outputSchema: decisionReviewSchema(decision.questions),
672
+ dependencies: config.decision,
673
+ waitForEvent: true,
674
+ timeoutMs: config.timeoutMs,
675
+ onTimeout: config.onTimeout === "accept" ? { output: { answers: {} } } : "fail",
676
+ when: (ctx) => ctx.deps[key].review.length > 0,
677
+ handler: async () => ({ answers: {} })
678
+ });
679
+ }
680
+ /**
681
+ * Merge a decision with its review: a person's choice wins where they made
682
+ * one, the decider's stands everywhere else. Also what
683
+ * {@link defineDecisionOutcomeStep} runs.
684
+ */
685
+ function resolveDecision(decision, review) {
686
+ const choices = {};
687
+ const decidedBy = {};
688
+ for (const [key, answer] of Object.entries(decision.answers)) {
689
+ const chosen = (review?.answers)?.[key];
690
+ choices[key] = chosen ?? answer.choice;
691
+ decidedBy[key] = chosen === void 0 ? "decider" : "person";
692
+ }
693
+ return {
694
+ choices,
695
+ decidedBy,
696
+ decider: decision.decider,
697
+ ...review?.reviewer !== void 0 ? { reviewer: review.reviewer } : {}
698
+ };
699
+ }
700
+ /**
701
+ * Define the **outcome** of a decision: one step that waits for the review
702
+ * when there is one, runs straight after the decision when there is not, and
703
+ * outputs the settled {@link DecisionOutcome}. Branch on it downstream:
704
+ *
705
+ * ```ts
706
+ * const invoicing = defineStep({
707
+ * dependencies: { outcome },
708
+ * when: (ctx) => ctx.deps.outcome.choices.audience === 'invoicing',
709
+ * // …
710
+ * });
711
+ * ```
712
+ */
713
+ function defineDecisionOutcomeStep(config) {
714
+ const [decisionKey, decision] = soleEntry(config.type, "decision", config.decision);
715
+ const [reviewKey] = soleEntry(config.type, "review", config.review);
716
+ return defineStep({
717
+ type: config.type,
718
+ workflowInputSchema: emptyInputSchema,
719
+ outputSchema: decisionOutcomeSchema(decision.questions),
720
+ dependencies: {
721
+ ...config.decision,
722
+ ...config.review
723
+ },
724
+ join: "any",
725
+ handler: async (ctx) => {
726
+ const deps = ctx.deps;
727
+ const decided = deps[decisionKey];
728
+ if (!decided) throw nonRetryableError(`[${config.type}] decision '${decisionKey}' has no output`);
729
+ return resolveDecision(decided, deps[reviewKey]);
730
+ }
731
+ });
732
+ }
733
+ //#endregion
518
734
  //#region src/core/public-view.ts
519
735
  /**
520
736
  * Public wire view of a workflow — the projection every HTTP consumer was
@@ -653,4 +869,4 @@ function toPublicWorkflow(workflow) {
653
869
  };
654
870
  }
655
871
  //#endregion
656
- export { ok as S, markRetryable as _, WORKFLOW_STATUS_SCHEMA as a, noopLogger as b, toPublicWorkflow as c, defineSleepStep as d, defineStep as f, isRetryableError as g, explicitRetryability as h, STEP_DISPLAY_STATUS_SCHEMA as i, buildWorkflow as l, defineWaitStep as m, PUBLIC_WORKFLOW_STEP_SCHEMA as n, toDisplayStepStatus as o, defineSubWorkflowStep as p, STEP_DISPLAY_STATUS as r, toPublicStep as s, PUBLIC_WORKFLOW_SCHEMA as t, defineMapStep as u, nonRetryableError as v, err as x, retryableError as y };
872
+ export { nonRetryableError as C, ok as D, err as E, markRetryable as S, noopLogger as T, defineStep as _, WORKFLOW_STATUS_SCHEMA as a, explicitRetryability as b, toPublicWorkflow as c, defineDecisionStep as d, functionDecider as f, defineSleepStep as g, defineMapStep as h, STEP_DISPLAY_STATUS_SCHEMA as i, defineDecisionOutcomeStep as l, buildWorkflow as m, PUBLIC_WORKFLOW_STEP_SCHEMA as n, toDisplayStepStatus as o, resolveDecision as p, STEP_DISPLAY_STATUS as r, toPublicStep as s, PUBLIC_WORKFLOW_SCHEMA as t, defineDecisionReviewStep as u, defineSubWorkflowStep as v, retryableError as w, isRetryableError as x, defineWaitStep as y };
@@ -1,4 +1,4 @@
1
- import { L as StepId, W as WorkflowId, nt as Result, tt as FlowErrorShape } from "./index-BURAYZui.js";
1
+ import { $ as StepId, _t as FlowErrorShape, ot as WorkflowId, vt as Result } from "./index-BGmJYvTt.js";
2
2
  //#region src/core/dispatcher.d.ts
3
3
  /**
4
4
  * What a step job is for. A queue carries two kinds of work for a step, and
@@ -1,4 +1,4 @@
1
- import { D as StartOptions, L as StepId, R as StepRecord, U as WorkflowDefinition, W as WorkflowId, nt as Result, q as WorkflowRecord, tt as FlowErrorShape } from "./index-BURAYZui.js";
1
+ import { $ as StepId, U as StartOptions, _t as FlowErrorShape, at as WorkflowDefinition, et as StepRecord, lt as WorkflowRecord, ot as WorkflowId, vt as Result } from "./index-BGmJYvTt.js";
2
2
  //#region src/core/hooks.d.ts
3
3
  interface BeforeStartArgs {
4
4
  definition: WorkflowDefinition;
@@ -695,6 +695,176 @@ interface BuildWorkflowConfig<TInput extends Record<string, unknown>, TSteps ext
695
695
  */
696
696
  declare function buildWorkflow<TInput extends Record<string, unknown>, TContext = unknown, TSteps extends Record<string, TypedStep<any, any, any, TContext>> = {}>(config: BuildWorkflowConfig<TInput, TSteps, TContext>): TypedWorkflow<TInput, TContext>;
697
697
  //#endregion
698
+ //#region src/core/decision.d.ts
699
+ /** The questions a decision answers: each key names a question, each value its options. */
700
+ type DecisionQuestions = Readonly<Record<string, readonly [string, ...string[]]>>;
701
+ /** One answer: the chosen option, how sure the decider is, and optionally why. */
702
+ interface DecisionAnswer<TOption extends string = string> {
703
+ choice: TOption;
704
+ /** Probability in [0, 1] that `choice` is right. Only meaningful as a probability when the decider is `calibrated`. */
705
+ confidence: number;
706
+ /** The probability per option, where the decider gives one. */
707
+ distribution?: Partial<Record<TOption, number>>;
708
+ /** A short reason, where the decider gives one — shown to a reviewer, never parsed. */
709
+ reason?: string;
710
+ }
711
+ /** The answers to a set of questions, typed by their options. */
712
+ type DecisionAnswers<Q extends DecisionQuestions> = { [K in keyof Q]: DecisionAnswer<Q[K][number]>; };
713
+ /** The chosen option per question. */
714
+ type DecisionChoices<Q extends DecisionQuestions> = { [K in keyof Q]: Q[K][number]; };
715
+ /** What a decider is asked. */
716
+ interface DecisionRequest {
717
+ questions: DecisionQuestions;
718
+ /** The state to decide on — a data structure, not a prompt. */
719
+ input: unknown;
720
+ /** Optional guidance for the decider; a function decider may ignore it. */
721
+ instructions?: string;
722
+ signal?: AbortSignal;
723
+ }
724
+ /** What a decider answers: one answer per question. */
725
+ interface DecisionResponse {
726
+ answers: Record<string, DecisionAnswer>;
727
+ /** Overrides the decider's `id` for this answer — e.g. the model that actually ran. */
728
+ decider?: string;
729
+ }
730
+ /**
731
+ * Anything that can answer a decision. `calibrated` is the decider's claim
732
+ * that its confidences are probabilities — true for a deterministic function
733
+ * (always 1) or a model trained for it, false for a language model asked to
734
+ * state its own confidence. The step records it on every output, so a reader
735
+ * of the run knows how much a threshold meant.
736
+ */
737
+ interface Decider<TContext = unknown> {
738
+ /** Recorded on every output — e.g. `function:rules` or `model:mistral-large`. */
739
+ readonly id: string;
740
+ readonly calibrated: boolean;
741
+ decide(request: DecisionRequest, context: TContext): Promise<DecisionResponse>;
742
+ }
743
+ /**
744
+ * A decider over a plain function: every answer has confidence 1, so it never
745
+ * asks for review. For rules you can state, and for tests.
746
+ */
747
+ declare function functionDecider<TContext = unknown>(id: string, decide: (request: DecisionRequest, context: TContext) => Record<string, string> | Promise<Record<string, string>>): Decider<TContext>;
748
+ /** What a decision step outputs. */
749
+ type DecisionOutput<Q extends DecisionQuestions> = {
750
+ answers: DecisionAnswers<Q>;
751
+ /** The decider's `id`. */
752
+ decider: string;
753
+ /** The decider's `calibrated` claim. */
754
+ calibrated: boolean;
755
+ /** The questions answered below their threshold — empty when none need a person. */
756
+ review: Array<keyof Q & string>;
757
+ };
758
+ /** A decision step: a `TypedStep` that also carries its questions, for the review and outcome steps. */
759
+ type DecisionStep<Q extends DecisionQuestions, TInput extends Record<string, unknown> = Record<string, unknown>, TDeps extends Record<string, TypedStep<any, any, any, TContext>> = {}, TContext = unknown> = TypedStep<TInput, DecisionOutput<Q>, TDeps, TContext> & {
760
+ readonly questions: Q;
761
+ };
762
+ /** Reviewing a decision: the options a person chose, for any subset of its questions. */
763
+ type DecisionReview<Q extends DecisionQuestions> = {
764
+ answers: Partial<DecisionChoices<Q>>;
765
+ /** Who reviewed — recorded on the outcome. */
766
+ reviewer?: string;
767
+ };
768
+ /** The settled decision: one option per question, and who chose it. */
769
+ type DecisionOutcome<Q extends DecisionQuestions> = {
770
+ choices: DecisionChoices<Q>;
771
+ decidedBy: { [K in keyof Q]: 'decider' | 'person'; };
772
+ decider: string;
773
+ reviewer?: string;
774
+ };
775
+ interface DefineDecisionStepConfig<Q extends DecisionQuestions, TInput extends Record<string, unknown>, TContext, TDeps extends Record<string, TypedStep<any, any, any, TContext>>, TJoin extends JoinRule> {
776
+ type: string;
777
+ workflowInputSchema: z.ZodType<TInput>;
778
+ dependencies?: TDeps;
779
+ /** The questions, each with its options — write them inline so their literals are kept. */
780
+ questions: Q;
781
+ /** The state the decider sees, built from the validated input and dependencies. */
782
+ input: (ctx: TypedStepContext<TInput, TDeps, TContext, TJoin>) => unknown;
783
+ /** Guidance passed to the decider. */
784
+ instructions?: string;
785
+ decider: Decider<TContext>;
786
+ /**
787
+ * Below this confidence a question is flagged for review — one number for
788
+ * every question, or one per question (a question left out is never
789
+ * flagged). Omitted: nothing is flagged.
790
+ */
791
+ threshold?: number | Partial<Record<keyof Q, number>>;
792
+ /** Guard — see `defineStep`. */
793
+ when?: (ctx: TypedStepContext<TInput, TDeps, TContext, TJoin>) => boolean | Promise<boolean>;
794
+ /** Join rule — see `defineStep`. */
795
+ join?: TJoin;
796
+ retry?: RetryPolicy;
797
+ isRetryable?: (error: unknown) => boolean;
798
+ timeoutMs?: number;
799
+ }
800
+ /**
801
+ * Define a **decision** step: the decider answers each question with one of
802
+ * its options and a confidence; the output is typed by the options and
803
+ * validated against them, so a dependent can branch on it with a plain `when`.
804
+ *
805
+ * ```ts
806
+ * const triage = defineDecisionStep({
807
+ * type: 'mail:triage',
808
+ * workflowInputSchema: input,
809
+ * dependencies: { parseMail },
810
+ * questions: {
811
+ * tone: ['aggressive', 'friendly'],
812
+ * audience: ['invoicing', 'customer-support', 'tech-support'],
813
+ * },
814
+ * input: (ctx) => ctx.deps.parseMail,
815
+ * decider: createModelDecider(),
816
+ * threshold: 0.8,
817
+ * });
818
+ * ```
819
+ *
820
+ * Questions under `threshold` land in `output.review`; pair the step with
821
+ * {@link defineDecisionReviewStep} and {@link defineDecisionOutcomeStep} to have
822
+ * a person settle them.
823
+ */
824
+ declare function defineDecisionStep<const Q extends DecisionQuestions, TInput extends Record<string, unknown>, TContext = unknown, TDeps extends Record<string, TypedStep<any, any, any, TContext>> = {}, TJoin extends JoinRule = 'all'>(config: DefineDecisionStepConfig<Q, TInput, TContext, TDeps, TJoin>): DecisionStep<Q, TInput, TDeps, TContext>;
825
+ /**
826
+ * Define the **review** of a decision: a wait step that suspends only when the
827
+ * decision flagged a question, until `engine.resumeStep` delivers a
828
+ * {@link DecisionReview} — the options a person chose. When nothing was
829
+ * flagged the step is skipped.
830
+ *
831
+ * `onTimeout: 'accept'` settles an expired review by keeping the decider's
832
+ * answers; the default `'fail'` ends the run.
833
+ */
834
+ declare function defineDecisionReviewStep<Q extends DecisionQuestions, TKey extends string, TContext = unknown>(config: {
835
+ type: string;
836
+ /** The decision under review, keyed by its step key: `{ triage }`. */
837
+ decision: { [K in TKey]: DecisionStep<Q, any, any, TContext>; };
838
+ timeoutMs?: number;
839
+ onTimeout?: 'fail' | 'accept';
840
+ }): TypedStep<Record<string, never>, DecisionReview<Q>, { [K in TKey]: DecisionStep<Q, any, any, TContext>; }, TContext>;
841
+ /**
842
+ * Merge a decision with its review: a person's choice wins where they made
843
+ * one, the decider's stands everywhere else. Also what
844
+ * {@link defineDecisionOutcomeStep} runs.
845
+ */
846
+ declare function resolveDecision<Q extends DecisionQuestions>(decision: DecisionOutput<Q>, review?: DecisionReview<Q>): DecisionOutcome<Q>;
847
+ /**
848
+ * Define the **outcome** of a decision: one step that waits for the review
849
+ * when there is one, runs straight after the decision when there is not, and
850
+ * outputs the settled {@link DecisionOutcome}. Branch on it downstream:
851
+ *
852
+ * ```ts
853
+ * const invoicing = defineStep({
854
+ * dependencies: { outcome },
855
+ * when: (ctx) => ctx.deps.outcome.choices.audience === 'invoicing',
856
+ * // …
857
+ * });
858
+ * ```
859
+ */
860
+ declare function defineDecisionOutcomeStep<Q extends DecisionQuestions, TDecisionKey extends string, TReviewKey extends string, TContext = unknown>(config: {
861
+ type: string;
862
+ /** The decision, keyed by its step key: `{ triage }`. */
863
+ decision: { [K in TDecisionKey]: DecisionStep<Q, any, any, TContext>; };
864
+ /** Its review, keyed by its step key: `{ triageReview }`. */
865
+ review: { [K in TReviewKey]: TypedStep<any, DecisionReview<Q>, any, TContext>; };
866
+ }): TypedStep<Record<string, never>, DecisionOutcome<Q>, { [K in TDecisionKey]: DecisionStep<Q, any, any, TContext>; } & { [K in TReviewKey]: TypedStep<any, DecisionReview<Q>, any, TContext>; }, TContext>;
867
+ //#endregion
698
868
  //#region src/core/public-view.d.ts
699
869
  /** The five states a UI renders. Suspensions display as in-flight. */
700
870
  type StepDisplayStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
@@ -823,4 +993,4 @@ declare function toPublicStep(step: StepRecord): PublicWorkflowStep;
823
993
  */
824
994
  declare function toPublicWorkflow(workflow: WorkflowWithSteps): PublicWorkflow;
825
995
  //#endregion
826
- export { computeReadiness as $, StepConditionHandler as A, StepStatus as B, defineWaitStep as C, StartOptions as D, RetryPolicy as E, StepHandlerNotFoundError as F, WorkflowNotFoundError as G, WorkflowCreatedResult as H, StepHandlerRegistry as I, WorkflowStatus as J, WorkflowNotRetryableError as K, StepId as L, StepError as M, StepExecutionContext as N, StepCompensateHandler as O, StepHandler as P, ReadinessPlan as Q, StepRecord as R, defineSubWorkflowStep as S, InvalidWorkflowDefinitionError as T, WorkflowDefinition as U, WaitTimeoutPolicy as V, WorkflowId as W, JoinRule as X, WorkflowWithSteps as Y, PlannedSkip as Z, WorkflowOutput as _, STEP_DISPLAY_STATUS as a, defineSleepStep as b, WORKFLOW_STATUS_SCHEMA as c, toPublicWorkflow as d, isTerminalStepStatus as et, StepDeps as f, TypedWorkflow as g, TypedStepContext as h, PublicWorkflowStep as i, ok as it, StepDefinition as j, StepCompensationContext as k, toDisplayStepStatus as l, TypedStep as m, PUBLIC_WORKFLOW_STEP_SCHEMA as n, Result as nt, STEP_DISPLAY_STATUS_SCHEMA as o, StepOutput as p, WorkflowRecord as q, PublicWorkflow as r, err as rt, StepDisplayStatus as s, PUBLIC_WORKFLOW_SCHEMA as t, FlowErrorShape as tt, toPublicStep as u, buildWorkflow as v, FlowError as w, defineStep as x, defineMapStep as y, StepRegistration as z };
996
+ export { StepId as $, TypedStep as A, FlowError as B, defineDecisionOutcomeStep as C, resolveDecision as D, functionDecider as E, defineMapStep as F, StepCompensationContext as G, RetryPolicy as H, defineSleepStep as I, StepError as J, StepConditionHandler as K, defineStep as L, TypedWorkflow as M, WorkflowOutput as N, StepDeps as O, buildWorkflow as P, StepHandlerRegistry as Q, defineSubWorkflowStep as R, DecisionStep as S, defineDecisionStep as T, StartOptions as U, InvalidWorkflowDefinitionError as V, StepCompensateHandler as W, StepHandler as X, StepExecutionContext as Y, StepHandlerNotFoundError as Z, DecisionOutput as _, FlowErrorShape as _t, STEP_DISPLAY_STATUS as a, WorkflowDefinition as at, DecisionResponse as b, ok as bt, WORKFLOW_STATUS_SCHEMA as c, WorkflowNotRetryableError as ct, toPublicWorkflow as d, WorkflowWithSteps as dt, StepRecord as et, Decider as f, JoinRule as ft, DecisionOutcome as g, isTerminalStepStatus as gt, DecisionChoices as h, computeReadiness as ht, PublicWorkflowStep as i, WorkflowCreatedResult as it, TypedStepContext as j, StepOutput as k, toDisplayStepStatus as l, WorkflowRecord as lt, DecisionAnswers as m, ReadinessPlan as mt, PUBLIC_WORKFLOW_STEP_SCHEMA as n, StepStatus as nt, STEP_DISPLAY_STATUS_SCHEMA as o, WorkflowId as ot, DecisionAnswer as p, PlannedSkip as pt, StepDefinition as q, PublicWorkflow as r, WaitTimeoutPolicy as rt, StepDisplayStatus as s, WorkflowNotFoundError as st, PUBLIC_WORKFLOW_SCHEMA as t, StepRegistration as tt, toPublicStep as u, WorkflowStatus as ut, DecisionQuestions as v, Result as vt, defineDecisionReviewStep as w, DecisionReview as x, DecisionRequest as y, err as yt, defineWaitStep as z };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { $ as computeReadiness, A as StepConditionHandler, B as StepStatus, C as defineWaitStep, D as StartOptions, E as RetryPolicy, F as StepHandlerNotFoundError, G as WorkflowNotFoundError, H as WorkflowCreatedResult, I as StepHandlerRegistry, J as WorkflowStatus, K as WorkflowNotRetryableError, L as StepId, M as StepError, N as StepExecutionContext, O as StepCompensateHandler, P as StepHandler, Q as ReadinessPlan, R as StepRecord, S as defineSubWorkflowStep, T as InvalidWorkflowDefinitionError, U as WorkflowDefinition, V as WaitTimeoutPolicy, W as WorkflowId, X as JoinRule, Y as WorkflowWithSteps, Z as PlannedSkip, _ as WorkflowOutput, a as STEP_DISPLAY_STATUS, b as defineSleepStep, c as WORKFLOW_STATUS_SCHEMA, d as toPublicWorkflow, et as isTerminalStepStatus, f as StepDeps, g as TypedWorkflow, h as TypedStepContext, i as PublicWorkflowStep, it as ok, j as StepDefinition, k as StepCompensationContext, l as toDisplayStepStatus, m as TypedStep, n as PUBLIC_WORKFLOW_STEP_SCHEMA, nt as Result, o as STEP_DISPLAY_STATUS_SCHEMA, p as StepOutput, q as WorkflowRecord, r as PublicWorkflow, rt as err, s as StepDisplayStatus, t as PUBLIC_WORKFLOW_SCHEMA, tt as FlowErrorShape, u as toPublicStep, v as buildWorkflow, w as FlowError, x as defineStep, y as defineMapStep, z as StepRegistration } from "./index-BURAYZui.js";
1
+ import { $ as StepId, A as TypedStep, B as FlowError, C as defineDecisionOutcomeStep, D as resolveDecision, E as functionDecider, F as defineMapStep, G as StepCompensationContext, H as RetryPolicy, I as defineSleepStep, J as StepError, K as StepConditionHandler, L as defineStep, M as TypedWorkflow, N as WorkflowOutput, O as StepDeps, P as buildWorkflow, Q as StepHandlerRegistry, R as defineSubWorkflowStep, S as DecisionStep, T as defineDecisionStep, U as StartOptions, V as InvalidWorkflowDefinitionError, W as StepCompensateHandler, X as StepHandler, Y as StepExecutionContext, Z as StepHandlerNotFoundError, _ as DecisionOutput, _t as FlowErrorShape, a as STEP_DISPLAY_STATUS, at as WorkflowDefinition, b as DecisionResponse, bt as ok, c as WORKFLOW_STATUS_SCHEMA, ct as WorkflowNotRetryableError, d as toPublicWorkflow, dt as WorkflowWithSteps, et as StepRecord, f as Decider, ft as JoinRule, g as DecisionOutcome, gt as isTerminalStepStatus, h as DecisionChoices, ht as computeReadiness, i as PublicWorkflowStep, it as WorkflowCreatedResult, j as TypedStepContext, k as StepOutput, l as toDisplayStepStatus, lt as WorkflowRecord, m as DecisionAnswers, mt as ReadinessPlan, n as PUBLIC_WORKFLOW_STEP_SCHEMA, nt as StepStatus, o as STEP_DISPLAY_STATUS_SCHEMA, ot as WorkflowId, p as DecisionAnswer, pt as PlannedSkip, q as StepDefinition, r as PublicWorkflow, rt as WaitTimeoutPolicy, s as StepDisplayStatus, st as WorkflowNotFoundError, t as PUBLIC_WORKFLOW_SCHEMA, tt as StepRegistration, u as toPublicStep, ut as WorkflowStatus, v as DecisionQuestions, vt as Result, w as defineDecisionReviewStep, x as DecisionReview, y as DecisionRequest, yt as err, z as defineWaitStep } from "./index-BGmJYvTt.js";
2
2
  import { n as Logger, r as noopLogger, t as LogAttributes } from "./logger-BCus3AOa.js";
3
- import { A as ListWorkflowsFilters, C as AddChildStep, D as CreatedWorkflow, E as CreateWorkflowStep, M as TransactionalScope, N as WorkflowStore, O as FailStepParams, S as createInMemoryStepGate, T as CreateWorkflowParams, _ as InMemoryStepGateConfig, a as FlowSpan, b as StepGateDecision, c as RecordingObserver, d as createMeterObserver, f as createRecordingObserver, g as ConcurrencyRule, h as noopTracer, i as FlowObserver, j as ReopenWorkflowParams, k as FinishWorkflowParams, l as RecordingTracer, m as noopObserver, n as FlowEventType, o as FlowTracer, p as createRecordingTracer, r as FlowMeter, s as RecordedSpan, t as FlowEvent, u as combineObservers, v as RateRule, w as CompleteStepParams, x as StepGateRequest, y as StepGate } from "./observability-CR9dEdLW.js";
4
- import { i as EnqueueOptions, n as DispatchStepPayload, r as Dispatcher, t as DispatchKind } from "./dispatcher-g7Ct0J6w.js";
5
- import { a as WorkflowCompletedArgs, i as BuildStepContextArgs, n as BeforeStartArgs, o as WorkflowHooks, r as BeforeStartResult, t as AfterStepArgs } from "./hooks-BdxIcc92.js";
3
+ import { A as ListWorkflowsFilters, C as AddChildStep, D as CreatedWorkflow, E as CreateWorkflowStep, M as TransactionalScope, N as WorkflowStore, O as FailStepParams, S as createInMemoryStepGate, T as CreateWorkflowParams, _ as InMemoryStepGateConfig, a as FlowSpan, b as StepGateDecision, c as RecordingObserver, d as createMeterObserver, f as createRecordingObserver, g as ConcurrencyRule, h as noopTracer, i as FlowObserver, j as ReopenWorkflowParams, k as FinishWorkflowParams, l as RecordingTracer, m as noopObserver, n as FlowEventType, o as FlowTracer, p as createRecordingTracer, r as FlowMeter, s as RecordedSpan, t as FlowEvent, u as combineObservers, v as RateRule, w as CompleteStepParams, x as StepGateRequest, y as StepGate } from "./observability-C-jfi2DX.js";
4
+ import { i as EnqueueOptions, n as DispatchStepPayload, r as Dispatcher, t as DispatchKind } from "./dispatcher-Bzkqq0Ax.js";
5
+ import { a as WorkflowCompletedArgs, i as BuildStepContextArgs, n as BeforeStartArgs, o as WorkflowHooks, r as BeforeStartResult, t as AfterStepArgs } from "./hooks-BGOHvR37.js";
6
6
  //#region src/core/registry.d.ts
7
7
  /**
8
8
  * Creates an in-memory step handler registry.
@@ -162,4 +162,4 @@ export declare function createInMemoryWorkflowStore(partitionKey?: string): Work
162
162
  };
163
163
  };
164
164
  //#endregion
165
- export { AddChildStep, AfterStepArgs, BeforeStartArgs, BeforeStartResult, BuildStepContextArgs, CompleteStepParams, ConcurrencyRule, CreateWorkflowParams, CreateWorkflowStep, CreatedWorkflow, DispatchKind, DispatchStepPayload, Dispatcher, EnqueueOptions, FailStepParams, FinishWorkflowParams, FlowError, FlowErrorShape, FlowEvent, FlowEventType, FlowMeter, FlowObserver, FlowSpan, FlowTracer, InMemoryStepGateConfig, InvalidWorkflowDefinitionError, JoinRule, ListWorkflowsFilters, LogAttributes, Logger, PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, PlannedSkip, PublicWorkflow, PublicWorkflowStep, RateRule, ReadinessPlan, RecordedSpan, RecordingObserver, RecordingTracer, ReopenWorkflowParams, Result, RetryPolicy, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, StartOptions, StepCompensateHandler, StepCompensationContext, StepConditionHandler, StepDefinition, StepDeps, StepDisplayStatus, StepError, StepExecutionContext, StepGate, StepGateDecision, StepGateRequest, StepHandler, StepHandlerNotFoundError, StepHandlerRegistry, StepId, StepOutput, StepRecord, StepRegistration, StepStatus, TransactionalScope, TypedStep, TypedStepContext, TypedWorkflow, WORKFLOW_STATUS_SCHEMA, WaitTimeoutPolicy, WorkflowCompletedArgs, WorkflowCreatedResult, WorkflowDefinition, WorkflowHooks, WorkflowId, WorkflowNotFoundError, WorkflowNotRetryableError, WorkflowOutput, WorkflowRecord, WorkflowStatus, WorkflowStore, WorkflowWithSteps, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createMeterObserver, createRecordingObserver, createRecordingTracer, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, isTerminalStepStatus, noopLogger, noopObserver, noopTracer, ok, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
165
+ export { AddChildStep, AfterStepArgs, BeforeStartArgs, BeforeStartResult, BuildStepContextArgs, CompleteStepParams, ConcurrencyRule, CreateWorkflowParams, CreateWorkflowStep, CreatedWorkflow, Decider, DecisionAnswer, DecisionAnswers, DecisionChoices, DecisionOutcome, DecisionOutput, DecisionQuestions, DecisionRequest, DecisionResponse, DecisionReview, DecisionStep, DispatchKind, DispatchStepPayload, Dispatcher, EnqueueOptions, FailStepParams, FinishWorkflowParams, FlowError, FlowErrorShape, FlowEvent, FlowEventType, FlowMeter, FlowObserver, FlowSpan, FlowTracer, InMemoryStepGateConfig, InvalidWorkflowDefinitionError, JoinRule, ListWorkflowsFilters, LogAttributes, Logger, PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, PlannedSkip, PublicWorkflow, PublicWorkflowStep, RateRule, ReadinessPlan, RecordedSpan, RecordingObserver, RecordingTracer, ReopenWorkflowParams, Result, RetryPolicy, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, StartOptions, StepCompensateHandler, StepCompensationContext, StepConditionHandler, StepDefinition, StepDeps, StepDisplayStatus, StepError, StepExecutionContext, StepGate, StepGateDecision, StepGateRequest, StepHandler, StepHandlerNotFoundError, StepHandlerRegistry, StepId, StepOutput, StepRecord, StepRegistration, StepStatus, TransactionalScope, TypedStep, TypedStepContext, TypedWorkflow, WORKFLOW_STATUS_SCHEMA, WaitTimeoutPolicy, WorkflowCompletedArgs, WorkflowCreatedResult, WorkflowDefinition, WorkflowHooks, WorkflowId, WorkflowNotFoundError, WorkflowNotRetryableError, WorkflowOutput, WorkflowRecord, WorkflowStatus, WorkflowStore, WorkflowWithSteps, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createMeterObserver, createRecordingObserver, createRecordingTracer, defineDecisionOutcomeStep, defineDecisionReviewStep, defineDecisionStep, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, functionDecider, isTerminalStepStatus, noopLogger, noopObserver, noopTracer, ok, resolveDecision, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { S as ok, _ as markRetryable, a as WORKFLOW_STATUS_SCHEMA, b as noopLogger, c as toPublicWorkflow, d as defineSleepStep, f as defineStep, g as isRetryableError, h as explicitRetryability, i as STEP_DISPLAY_STATUS_SCHEMA, l as buildWorkflow, m as defineWaitStep, n as PUBLIC_WORKFLOW_STEP_SCHEMA, o as toDisplayStepStatus, p as defineSubWorkflowStep, r as STEP_DISPLAY_STATUS, s as toPublicStep, t as PUBLIC_WORKFLOW_SCHEMA, u as defineMapStep, v as nonRetryableError, x as err, y as retryableError } from "./core-DNWPwFaE.js";
1
+ import { C as nonRetryableError, D as ok, E as err, S as markRetryable, T as noopLogger, _ as defineStep, a as WORKFLOW_STATUS_SCHEMA, b as explicitRetryability, c as toPublicWorkflow, d as defineDecisionStep, f as functionDecider, g as defineSleepStep, h as defineMapStep, i as STEP_DISPLAY_STATUS_SCHEMA, l as defineDecisionOutcomeStep, m as buildWorkflow, n as PUBLIC_WORKFLOW_STEP_SCHEMA, o as toDisplayStepStatus, p as resolveDecision, r as STEP_DISPLAY_STATUS, s as toPublicStep, t as PUBLIC_WORKFLOW_SCHEMA, u as defineDecisionReviewStep, v as defineSubWorkflowStep, w as retryableError, x as isRetryableError, y as defineWaitStep } from "./core-C9h3-KFr.js";
2
2
  //#region src/core/readiness.ts
3
3
  /** Statuses a step can no longer move out of on its own. */
4
4
  const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
@@ -2233,4 +2233,4 @@ function createInMemoryWorkflowStore(partitionKey = "default") {
2233
2233
  };
2234
2234
  }
2235
2235
  //#endregion
2236
- export { PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, WORKFLOW_STATUS_SCHEMA, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createInMemoryWorkflowStore, createMeterObserver, createRecordingObserver, createRecordingTracer, createStepHandlerRegistry, createWorkflowEngine, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, explicitRetryability, isRetryableError, isTerminalStepStatus, markRetryable, nonRetryableError, noopLogger, noopObserver, noopTracer, ok, retryableError, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
2236
+ export { PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, WORKFLOW_STATUS_SCHEMA, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createInMemoryWorkflowStore, createMeterObserver, createRecordingObserver, createRecordingTracer, createStepHandlerRegistry, createWorkflowEngine, defineDecisionOutcomeStep, defineDecisionReviewStep, defineDecisionStep, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, explicitRetryability, functionDecider, isRetryableError, isTerminalStepStatus, markRetryable, nonRetryableError, noopLogger, noopObserver, noopTracer, ok, resolveDecision, retryableError, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
@@ -1,4 +1,4 @@
1
- import { J as WorkflowStatus, L as StepId, R as StepRecord, W as WorkflowId, Y as WorkflowWithSteps, q as WorkflowRecord } from "./index-BURAYZui.js";
1
+ import { $ as StepId, dt as WorkflowWithSteps, et as StepRecord, lt as WorkflowRecord, ot as WorkflowId, ut as WorkflowStatus } from "./index-BGmJYvTt.js";
2
2
  //#region src/core/store.d.ts
3
3
  interface CreateWorkflowStep {
4
4
  key: string;
@@ -320,7 +320,7 @@ declare function createRecordingObserver(): RecordingObserver;
320
320
  * The slice of a metrics API the engine's meter observer needs. Structural:
321
321
  * `@octabits-io/telemetry`'s meter and an OpenTelemetry `Meter`
322
322
  * both fit (the SDK's `createCounter`/`createHistogram` need a two-line
323
- * adapter, the framework's meter none).
323
+ * adapter).
324
324
  */
325
325
  interface FlowMeter {
326
326
  counter(name: string, options?: {
@@ -1,6 +1,6 @@
1
- import "../index-BURAYZui.js";
1
+ import "../index-BGmJYvTt.js";
2
2
  import { n as Logger } from "../logger-BCus3AOa.js";
3
- import { r as Dispatcher } from "../dispatcher-g7Ct0J6w.js";
3
+ import { r as Dispatcher } from "../dispatcher-Bzkqq0Ax.js";
4
4
  import { z } from "zod";
5
5
  import { PgBoss } from "pg-boss";
6
6
  //#region src/pg-boss/payload.d.ts
@@ -1,5 +1,5 @@
1
- import { W as WorkflowId } from "../index-BURAYZui.js";
2
- import { N as WorkflowStore, g as ConcurrencyRule, i as FlowObserver, t as FlowEvent, v as RateRule, y as StepGate } from "../observability-CR9dEdLW.js";
1
+ import { ot as WorkflowId } from "../index-BGmJYvTt.js";
2
+ import { N as WorkflowStore, g as ConcurrencyRule, i as FlowObserver, t as FlowEvent, v as RateRule, y as StepGate } from "../observability-C-jfi2DX.js";
3
3
  import { Pool } from "pg";
4
4
  //#region src/postgres/ddl.d.ts
5
5
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octaflow",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss, with an optional AI add-on (token/cost/quota). Layered single package.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -116,23 +116,23 @@
116
116
  }
117
117
  },
118
118
  "devDependencies": {
119
- "@ai-sdk/anthropic": "^4.0.52",
120
- "@ai-sdk/mistral": "^4.0.42",
121
- "@ai-sdk/openai": "^4.0.65",
122
- "@ai-sdk/openai-compatible": "^3.0.48",
123
- "@ai-sdk/provider": "^4.0.13",
124
- "@testcontainers/postgresql": "^12.1.0",
125
- "@types/node": "^26.5.1",
119
+ "@ai-sdk/anthropic": "^4.0.71",
120
+ "@ai-sdk/mistral": "^4.0.56",
121
+ "@ai-sdk/openai": "^4.0.83",
122
+ "@ai-sdk/openai-compatible": "^3.0.62",
123
+ "@ai-sdk/provider": "^4.0.21",
124
+ "@testcontainers/postgresql": "^12.2.0",
125
+ "@types/node": "^26.6.4",
126
126
  "@types/pg": "^8.23.1",
127
- "ai": "^7.0.97",
128
- "drizzle-orm": "^0.45.2",
129
- "pg": "^8.23.0",
130
- "pg-boss": "^12.30.0",
131
- "testcontainers": "^12.1.0",
127
+ "ai": "^7.0.127",
128
+ "drizzle-orm": "^0.45.3",
129
+ "pg": "^8.23.1",
130
+ "pg-boss": "^12.35.1",
131
+ "testcontainers": "^12.2.0",
132
132
  "tsdown": "^0.23.0",
133
133
  "typescript": "^7.0.2",
134
- "vitest": "^5.0.0",
135
- "zod": "^4.6.0"
134
+ "vitest": "^5.0.3",
135
+ "zod": "^4.6.5"
136
136
  },
137
137
  "scripts": {
138
138
  "build": "tsdown",