@bastani/atomic 0.8.23-0 → 0.8.24-alpha.1

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 (70) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +13 -0
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/CHANGELOG.md +13 -0
  5. package/dist/builtin/mcp/package.json +1 -1
  6. package/dist/builtin/subagents/CHANGELOG.md +28 -0
  7. package/dist/builtin/subagents/README.md +16 -0
  8. package/dist/builtin/subagents/agents/code-simplifier.md +2 -3
  9. package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -3
  10. package/dist/builtin/subagents/agents/codebase-locator.md +2 -3
  11. package/dist/builtin/subagents/agents/codebase-online-researcher.md +2 -3
  12. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -3
  13. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -3
  14. package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -3
  15. package/dist/builtin/subagents/agents/debugger.md +2 -3
  16. package/dist/builtin/subagents/package.json +1 -1
  17. package/dist/builtin/subagents/skills/subagent/SKILL.md +6 -0
  18. package/dist/builtin/subagents/src/agents/agent-serializer.ts +3 -0
  19. package/dist/builtin/subagents/src/agents/agents.ts +20 -1
  20. package/dist/builtin/subagents/src/runs/background/async-execution.ts +1 -1
  21. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +3 -1
  22. package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +7 -7
  23. package/dist/builtin/subagents/src/runs/foreground/execution.ts +5 -1
  24. package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +9 -10
  25. package/dist/builtin/subagents/src/shared/types.ts +1 -0
  26. package/dist/builtin/web-access/CHANGELOG.md +13 -0
  27. package/dist/builtin/web-access/package.json +1 -1
  28. package/dist/builtin/workflows/CHANGELOG.md +31 -0
  29. package/dist/builtin/workflows/README.md +38 -41
  30. package/dist/builtin/workflows/builtin/deep-research-codebase.d.ts +35 -0
  31. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +11 -14
  32. package/dist/builtin/workflows/builtin/goal.d.ts +46 -0
  33. package/dist/builtin/workflows/builtin/goal.ts +10 -12
  34. package/dist/builtin/workflows/builtin/index.d.ts +136 -0
  35. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +44 -0
  36. package/dist/builtin/workflows/builtin/open-claude-design.ts +19 -20
  37. package/dist/builtin/workflows/builtin/ralph.d.ts +36 -0
  38. package/dist/builtin/workflows/builtin/ralph.ts +20 -24
  39. package/dist/builtin/workflows/package.json +15 -5
  40. package/dist/builtin/workflows/src/authoring.ts +410 -0
  41. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +6 -12
  42. package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -2
  43. package/dist/builtin/workflows/src/index.ts +0 -5
  44. package/dist/builtin/workflows/src/runs/foreground/executor.ts +23 -9
  45. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +33 -5
  46. package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +61 -10
  47. package/dist/builtin/workflows/src/sdk-surface.ts +12 -2
  48. package/dist/builtin/workflows/src/shared/authoring-contract.ts +660 -0
  49. package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +1 -1
  50. package/dist/builtin/workflows/src/shared/types.ts +65 -350
  51. package/dist/builtin/workflows/src/workflows/define-workflow.ts +59 -44
  52. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  53. package/dist/core/atomic-guide-command.js +1 -1
  54. package/dist/core/atomic-guide-command.js.map +1 -1
  55. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
  56. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/chat-message-renderer.js +13 -1
  58. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  59. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  60. package/dist/modes/interactive/interactive-mode.js +1 -1
  61. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  62. package/dist/utils/changelog.d.ts.map +1 -1
  63. package/dist/utils/changelog.js +23 -16
  64. package/dist/utils/changelog.js.map +1 -1
  65. package/docs/extensions.md +2 -2
  66. package/docs/packages.md +8 -4
  67. package/docs/subagents.md +30 -0
  68. package/docs/workflows.md +39 -21
  69. package/package.json +1 -1
  70. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +0 -335
@@ -7,6 +7,7 @@ import { mkdir, writeFile } from "node:fs/promises";
7
7
  import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
8
8
  import { CONFIG_DIR_NAME, createAskUserQuestionToolDefinition, isCodexFastModeCandidateModelId } from "@bastani/atomic";
9
9
  import { stageUiBroker } from "../../shared/stage-ui-broker.js";
10
+ import { isBrandedWorkflowDefinition, stampWorkflowDefinition } from "../../workflows/define-workflow.js";
10
11
  import { buildStagePromptAdapter } from "../../shared/stage-prompt.js";
11
12
  import type {
12
13
  WorkflowDefinition,
@@ -43,6 +44,7 @@ import type {
43
44
  WorkflowSerializableValue,
44
45
  } from "../../shared/types.js";
45
46
  import type { InternalStageContext, StageAdapters } from "./stage-runner.js";
47
+ import type * as AuthoringContract from "../../shared/authoring-contract.js";
46
48
  import type {
47
49
  RunStatus,
48
50
  StageNotice,
@@ -102,7 +104,7 @@ export interface RunContinuationOpts {
102
104
  readonly resumeFromStageId: string;
103
105
  }
104
106
 
105
- export interface RunOpts {
107
+ export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "store" | "cancellation" | "overlay" | "registry" | "stageControlRegistry" | "continuation" | "onRunStart" | "onStageStart" | "onStageEnd" | "onRunEnd"> {
106
108
  adapters?: StageAdapters;
107
109
  /** Invocation working directory exposed to workflow definitions as ctx.cwd. */
108
110
  cwd?: string;
@@ -1152,7 +1154,7 @@ function defineDirectWorkflow(
1152
1154
  name: string,
1153
1155
  runFn: WorkflowDefinition["run"],
1154
1156
  ): WorkflowDefinition {
1155
- return Object.freeze({
1157
+ const definition = {
1156
1158
  __piWorkflow: true,
1157
1159
  name,
1158
1160
  normalizedName: name,
@@ -1160,7 +1162,10 @@ function defineDirectWorkflow(
1160
1162
  inputs: Object.freeze({}),
1161
1163
  outputs: DIRECT_WORKFLOW_OUTPUTS,
1162
1164
  run: runFn,
1163
- });
1165
+ } as WorkflowDefinition;
1166
+ // Stamp before freezing so the WeakSet brand can be attached.
1167
+ stampWorkflowDefinition(definition);
1168
+ return Object.freeze(definition);
1164
1169
  }
1165
1170
 
1166
1171
  /**
@@ -1805,19 +1810,24 @@ function workflowChildSerializationMessage(err: unknown): string {
1805
1810
  }
1806
1811
 
1807
1812
  function isWorkflowDefinition(value: unknown): value is WorkflowDefinition {
1808
- if (value === null || typeof value !== "object") return false;
1813
+ if (!isBrandedWorkflowDefinition(value)) return false;
1809
1814
  const record = value as Partial<WorkflowDefinition>;
1810
1815
  return record.__piWorkflow === true &&
1811
1816
  typeof record.name === "string" && record.name.trim().length > 0 &&
1812
1817
  typeof record.normalizedName === "string" && record.normalizedName.trim().length > 0 &&
1813
1818
  typeof record.run === "function" &&
1814
- // Compiled definitions always set `inputs: {}`; guard it so a handcrafted
1815
- // object that passes the sentinel still fails here with the clear "requires
1816
- // a compiled workflow definition" error rather than crashing later inside
1817
- // resolveAndValidateInputs(child.inputs, ...) on `Object.entries(undefined)`.
1818
1819
  typeof record.inputs === "object" && record.inputs !== null;
1819
1820
  }
1820
1821
 
1822
+ function workflowDefinitionRequirementMessage(callSite: string, value: unknown): string {
1823
+ // isWorkflowDefinition already failed; this extra sentinel check narrows the
1824
+ // diagnostic for forged legacy literals versus unrelated values.
1825
+ if (value !== null && typeof value === "object" && (value as { __piWorkflow?: unknown }).__piWorkflow === true) {
1826
+ return `atomic-workflows: ${callSite} requires a compiled workflow definition produced by defineWorkflow(...).compile(); hand-rolled __piWorkflow objects are not supported`;
1827
+ }
1828
+ return `atomic-workflows: ${callSite} requires a compiled workflow definition`;
1829
+ }
1830
+
1821
1831
  function cloneWorkflowChildReplaySnapshot(snapshot: WorkflowChildReplaySnapshot): WorkflowChildReplaySnapshot {
1822
1832
  return {
1823
1833
  alias: snapshot.alias,
@@ -1859,6 +1869,10 @@ export async function run<TInputs extends WorkflowInputValues>(
1859
1869
  inputs: Readonly<Record<string, unknown>>,
1860
1870
  opts: RunOpts = {},
1861
1871
  ): Promise<RunResult> {
1872
+ if (!isWorkflowDefinition(def)) {
1873
+ throw new Error(workflowDefinitionRequirementMessage("run(definition, inputs)", def));
1874
+ }
1875
+
1862
1876
  const activeStore = opts.store ?? defaultStore;
1863
1877
  const adapters = opts.adapters ?? {};
1864
1878
  if (opts.usePromptNodesForUi === true && opts.ui !== undefined) {
@@ -3221,7 +3235,7 @@ export async function run<TInputs extends WorkflowInputValues>(
3221
3235
  // declared output contract is validated dynamically by the child run and
3222
3236
  // selectWorkflowOutputs, so the typed result is reconstructed via casts.
3223
3237
  if (!isWorkflowDefinition(child)) {
3224
- throw new Error("atomic-workflows: ctx.workflow(definition) requires a compiled workflow definition");
3238
+ throw new Error(workflowDefinitionRequirementMessage("ctx.workflow(definition)", child));
3225
3239
  }
3226
3240
  const childName = child.normalizedName;
3227
3241
  const boundaryName = options.stageName ?? `workflow:${childName}`;
@@ -64,7 +64,7 @@ export interface StageSessionRuntime {
64
64
  getLastAssistantText?: () => string | undefined;
65
65
  }
66
66
 
67
- export type StageSessionCreateOptions = CreateAgentSessionOptions & Pick<StageOptions, "mcp" | "fallbackModels">;
67
+ export type StageSessionCreateOptions = CreateAgentSessionOptions & Pick<StageOptions, "mcp" | "fallbackModels" | "fallbackThinkingLevels">;
68
68
 
69
69
  type WorkflowFastModeSettings = {
70
70
  readonly chat: boolean;
@@ -169,6 +169,7 @@ function stripWorkflowOnlyOptions(options: StageOptions | undefined): CreateAgen
169
169
  const {
170
170
  mcp: _mcp,
171
171
  fallbackModels: _fallbackModels,
172
+ fallbackThinkingLevels: _fallbackThinkingLevels,
172
173
  context,
173
174
  forkFromSessionFile,
174
175
  sessionDir,
@@ -549,6 +550,7 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
549
550
  candidatesPromise = buildModelCandidatesFromCatalog({
550
551
  primaryModel: stageOptions?.model,
551
552
  fallbackModels: stageOptions?.fallbackModels,
553
+ fallbackThinkingLevels: stageOptions?.fallbackThinkingLevels,
552
554
  catalog: modelCatalog,
553
555
  });
554
556
  }
@@ -557,7 +559,13 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
557
559
 
558
560
  function stageOptionsForCandidate(candidate: WorkflowResolvedModelCandidate | undefined): StageOptions | undefined {
559
561
  if (candidate === undefined) return stageOptions;
560
- return { ...(stageOptions ?? {}), model: candidate.value, fallbackModels: undefined };
562
+ return {
563
+ ...(stageOptions ?? {}),
564
+ model: candidate.value,
565
+ ...(candidate.reasoningLevel !== undefined ? { thinkingLevel: candidate.reasoningLevel } : {}),
566
+ fallbackModels: undefined,
567
+ fallbackThinkingLevels: undefined,
568
+ };
561
569
  }
562
570
 
563
571
  let sessionSettingsManager: WorkflowFastModeSettingsManager | undefined;
@@ -591,6 +599,25 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
591
599
  return { session: created };
592
600
  }
593
601
 
602
+ function effectiveCandidateReasoning(candidate: WorkflowResolvedModelCandidate): StageOptions["thinkingLevel"] | undefined {
603
+ return candidate.reasoningLevel ?? stageOptions?.thinkingLevel;
604
+ }
605
+
606
+ function modelAttemptReasoning(candidate: WorkflowResolvedModelCandidate): Pick<WorkflowModelAttempt, "reasoningLevel"> {
607
+ const reasoningLevel = effectiveCandidateReasoning(candidate);
608
+ return reasoningLevel !== undefined ? { reasoningLevel } : {};
609
+ }
610
+
611
+ function applyCandidateThinking(candidate: WorkflowResolvedModelCandidate | undefined): void {
612
+ pendingThinkingLevel = candidate === undefined
613
+ ? stageOptions?.thinkingLevel
614
+ : effectiveCandidateReasoning(candidate);
615
+ }
616
+
617
+ function candidateLabel(candidate: WorkflowResolvedModelCandidate): string {
618
+ return candidate.reasoningLevel !== undefined ? `${candidate.id}:${candidate.reasoningLevel}` : candidate.id;
619
+ }
620
+
594
621
  function attachSession(created: StageSessionRuntime | StageSessionCreateResult): StageSessionRuntime {
595
622
  const result = normalizeSessionCreateResult(created);
596
623
  session = result.session;
@@ -612,6 +639,7 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
612
639
  candidate: WorkflowResolvedModelCandidate | undefined,
613
640
  consumer: AgentSessionConsumer,
614
641
  ): Promise<StageSessionRuntime> {
642
+ applyCandidateThinking(candidate);
615
643
  const created = adapters.agentSession
616
644
  ? await adapters.agentSession.create(stripWorkflowOnlyOptions(stageOptionsForCandidate(candidate)) as StageSessionCreateOptions, {
617
645
  ...meta,
@@ -716,16 +744,16 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
716
744
  notifyModelFallbackMetaChange();
717
745
  try {
718
746
  await promptWithPauseResume(activeSession, text, sdkOptions);
719
- modelAttempts.push({ model: candidate.id, success: true });
747
+ modelAttempts.push({ model: candidate.id, success: true, ...modelAttemptReasoning(candidate) });
720
748
  return;
721
749
  } catch (err) {
722
750
  const message = errorMessage(err);
723
- modelAttempts.push({ model: candidate.id, success: false, error: message });
751
+ modelAttempts.push({ model: candidate.id, success: false, ...modelAttemptReasoning(candidate), error: message });
724
752
  if (signal?.aborted || !isRetryableModelFailure(message) || index === candidates.length - 1) {
725
753
  throw err;
726
754
  }
727
755
  const nextCandidate = candidates[index + 1]!;
728
- modelWarnings.push(`[fallback] ${candidate.id} failed: ${message}. Retrying with ${nextCandidate.id}.`);
756
+ modelWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${message}. Retrying with ${candidateLabel(nextCandidate)}.`);
729
757
  await disposeCurrentSession();
730
758
  index += 1;
731
759
  }
@@ -3,11 +3,38 @@ import type {
3
3
  WorkflowModelCatalogPort,
4
4
  WorkflowModelInfo,
5
5
  WorkflowModelValue,
6
+ WorkflowThinkingLevel,
6
7
  } from "../../shared/types.js";
7
8
 
8
9
  export interface WorkflowResolvedModelCandidate {
9
10
  readonly id: string;
10
11
  readonly value: WorkflowModelValue;
12
+ readonly reasoningLevel?: WorkflowThinkingLevel;
13
+ }
14
+
15
+ function makeCandidate(
16
+ id: string,
17
+ value: WorkflowModelValue,
18
+ level: WorkflowThinkingLevel | undefined,
19
+ ): WorkflowResolvedModelCandidate {
20
+ return level !== undefined ? { id, value, reasoningLevel: level } : { id, value };
21
+ }
22
+
23
+ const WORKFLOW_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const satisfies readonly WorkflowThinkingLevel[];
24
+ const WORKFLOW_THINKING_LEVEL_SET: ReadonlySet<string> = new Set(WORKFLOW_THINKING_LEVELS);
25
+
26
+ export function splitReasoningSuffix(model: string): { readonly baseModel: string; readonly level?: WorkflowThinkingLevel } {
27
+ const index = model.lastIndexOf(":");
28
+ if (index < 0) return { baseModel: model };
29
+ const suffix = model.slice(index + 1);
30
+ if (WORKFLOW_THINKING_LEVEL_SET.has(suffix)) {
31
+ return { baseModel: model.slice(0, index), level: suffix as WorkflowThinkingLevel };
32
+ }
33
+ return { baseModel: model };
34
+ }
35
+
36
+ function candidateKey(candidate: WorkflowResolvedModelCandidate): string {
37
+ return `${candidate.id}::${candidate.reasoningLevel ?? ""}`;
11
38
  }
12
39
 
13
40
  interface ModelResolutionFailure {
@@ -72,35 +99,36 @@ function resolveStringModel(
72
99
  ): WorkflowResolvedModelCandidate | ModelResolutionFailure {
73
100
  const input = rawInput.trim();
74
101
  if (!input) return { input: rawInput, reason: "empty model id" };
102
+ const { baseModel, level } = splitReasoningSuffix(input);
75
103
 
76
104
  if (availableModels === undefined) {
77
- return { id: input, value: input };
105
+ return makeCandidate(baseModel, baseModel, level);
78
106
  }
79
107
 
80
108
  const models = uniqueByFullId(availableModels);
81
- const explicit = models.find((model) => model.fullId === input);
109
+ const explicit = models.find((model) => model.fullId === baseModel);
82
110
  if (explicit !== undefined) {
83
- return { id: explicit.fullId, value: explicit.model ?? explicit.fullId };
111
+ return makeCandidate(explicit.fullId, explicit.model ?? explicit.fullId, level);
84
112
  }
85
113
 
86
- if (input.includes("/")) {
114
+ if (baseModel.includes("/")) {
87
115
  return { input, reason: "not available" };
88
116
  }
89
117
 
90
- const byBareId = models.filter((model) => model.id === input);
118
+ const byBareId = models.filter((model) => model.id === baseModel);
91
119
  if (byBareId.length === 0) {
92
120
  return { input, reason: "not available" };
93
121
  }
94
122
  if (byBareId.length === 1) {
95
123
  const only = byBareId[0]!;
96
- return { id: only.fullId, value: only.model ?? only.fullId };
124
+ return makeCandidate(only.fullId, only.model ?? only.fullId, level);
97
125
  }
98
126
 
99
127
  const preferred = preferredProvider === undefined
100
128
  ? undefined
101
129
  : byBareId.find((model) => model.provider === preferredProvider);
102
130
  if (preferred !== undefined) {
103
- return { id: preferred.fullId, value: preferred.model ?? preferred.fullId };
131
+ return makeCandidate(preferred.fullId, preferred.model ?? preferred.fullId, level);
104
132
  }
105
133
 
106
134
  return {
@@ -127,13 +155,30 @@ function isFailure(value: WorkflowResolvedModelCandidate | ModelResolutionFailur
127
155
  export function buildModelCandidates(input: {
128
156
  readonly primaryModel?: WorkflowModelValue;
129
157
  readonly fallbackModels?: readonly string[];
158
+ readonly fallbackThinkingLevels?: readonly string[];
130
159
  readonly currentModel?: WorkflowModelValue;
131
160
  readonly availableModels?: readonly WorkflowModelInfo[];
132
161
  readonly preferredProvider?: string;
133
162
  }): WorkflowResolvedModelCandidate[] {
134
163
  const rawValues: WorkflowModelValue[] = [];
135
164
  if (input.primaryModel !== undefined) rawValues.push(input.primaryModel);
136
- rawValues.push(...(input.fallbackModels ?? []));
165
+ for (const [index, fallback] of (input.fallbackModels ?? []).entries()) {
166
+ // Trim once up front so the suffix split, the validation error input, and the
167
+ // compat concatenation all operate on the same value. Concatenating the raw
168
+ // (untrimmed) fallback would push trailing whitespace into the interior of
169
+ // `id:level`, which `resolveStringModel` can no longer trim away.
170
+ const trimmedFallback = fallback.trim();
171
+ const split = splitReasoningSuffix(trimmedFallback);
172
+ const compatLevel = input.fallbackThinkingLevels?.[index];
173
+ if (split.level === undefined && compatLevel !== undefined) {
174
+ if (!WORKFLOW_THINKING_LEVEL_SET.has(compatLevel)) {
175
+ throw new WorkflowModelValidationError([{ input: trimmedFallback, reason: `invalid fallbackThinkingLevels[${index}] "${compatLevel}"; expected one of ${WORKFLOW_THINKING_LEVELS.join(", ")}` }]);
176
+ }
177
+ rawValues.push(`${trimmedFallback}:${compatLevel}`);
178
+ } else {
179
+ rawValues.push(trimmedFallback);
180
+ }
181
+ }
137
182
  if (input.currentModel !== undefined) rawValues.push(input.currentModel);
138
183
 
139
184
  const failures: ModelResolutionFailure[] = [];
@@ -145,8 +190,9 @@ export function buildModelCandidates(input: {
145
190
  failures.push(resolved);
146
191
  continue;
147
192
  }
148
- if (seen.has(resolved.id)) continue;
149
- seen.add(resolved.id);
193
+ const key = candidateKey(resolved);
194
+ if (seen.has(key)) continue;
195
+ seen.add(key);
150
196
  candidates.push(resolved);
151
197
  }
152
198
 
@@ -165,6 +211,7 @@ function catalogUnavailableWarning(): string {
165
211
  export async function buildModelCandidatesFromCatalog(input: {
166
212
  readonly primaryModel?: WorkflowModelValue;
167
213
  readonly fallbackModels?: readonly string[];
214
+ readonly fallbackThinkingLevels?: readonly string[];
168
215
  readonly catalog?: WorkflowModelCatalogPort;
169
216
  }): Promise<WorkflowResolvedModelCandidate[]> {
170
217
  const hasExplicitModel = input.primaryModel !== undefined || (input.fallbackModels?.length ?? 0) > 0;
@@ -174,6 +221,7 @@ export async function buildModelCandidatesFromCatalog(input: {
174
221
  return buildModelCandidates({
175
222
  primaryModel: input.primaryModel,
176
223
  fallbackModels: input.fallbackModels,
224
+ fallbackThinkingLevels: input.fallbackThinkingLevels,
177
225
  });
178
226
  }
179
227
 
@@ -182,6 +230,7 @@ export async function buildModelCandidatesFromCatalog(input: {
182
230
  return buildModelCandidates({
183
231
  primaryModel: input.primaryModel,
184
232
  fallbackModels: input.fallbackModels,
233
+ fallbackThinkingLevels: input.fallbackThinkingLevels,
185
234
  currentModel: input.catalog.currentModel,
186
235
  availableModels,
187
236
  preferredProvider: input.catalog.preferredProvider,
@@ -199,6 +248,7 @@ export async function validateWorkflowModels(input: {
199
248
  readonly requests: readonly {
200
249
  readonly model?: WorkflowModelValue;
201
250
  readonly fallbackModels?: readonly string[];
251
+ readonly fallbackThinkingLevels?: readonly string[];
202
252
  }[];
203
253
  readonly catalog?: WorkflowModelCatalogPort;
204
254
  }): Promise<readonly string[]> {
@@ -230,6 +280,7 @@ export async function validateWorkflowModels(input: {
230
280
  buildModelCandidates({
231
281
  primaryModel: request.model,
232
282
  fallbackModels: request.fallbackModels,
283
+ fallbackThinkingLevels: request.fallbackThinkingLevels,
233
284
  currentModel: input.catalog?.currentModel,
234
285
  availableModels,
235
286
  preferredProvider: input.catalog?.preferredProvider,
@@ -2,12 +2,22 @@
2
2
  * Non-cyclic public SDK surface for @bastani/workflows.
3
3
  *
4
4
  * Keep public runtime exports here when they are safe to load during workflow
5
- * discovery. The package root re-exports this module and adds runWorkflow,
6
- * which is intentionally excluded because workflow-runner imports discovery.ts.
5
+ * discovery. The package root re-exports this module directly.
7
6
  */
8
7
 
9
8
  export { defineWorkflow } from "./workflows/define-workflow.js";
10
9
 
10
+ const REMOVED_RUN_WORKFLOW_MESSAGE =
11
+ "@bastani/workflows no longer exports runWorkflow; author workflows with defineWorkflow(...).compile()";
12
+
13
+ /**
14
+ * @deprecated Removed imperative workflow API. Kept as a runtime migration
15
+ * stub so older workflow modules fail at the call site with a clear message.
16
+ */
17
+ export const runWorkflow: never = (() => {
18
+ throw new Error(REMOVED_RUN_WORKFLOW_MESSAGE);
19
+ }) as never;
20
+
11
21
  // TypeBox authoring surface so jiti-loaded workflows can `import { Type } from
12
22
  // "@bastani/workflows"` (the virtual module is built from this file).
13
23
  export { Type } from "typebox";