@bastani/atomic 0.8.23 → 0.8.24-alpha.2
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/CHANGELOG.md +26 -0
- package/dist/builtin/intercom/CHANGELOG.md +13 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +13 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +28 -0
- package/dist/builtin/subagents/README.md +16 -0
- package/dist/builtin/subagents/agents/code-simplifier.md +2 -3
- package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -3
- package/dist/builtin/subagents/agents/codebase-locator.md +2 -3
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +2 -3
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -3
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -3
- package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -3
- package/dist/builtin/subagents/agents/debugger.md +2 -3
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +6 -0
- package/dist/builtin/subagents/src/agents/agent-serializer.ts +3 -0
- package/dist/builtin/subagents/src/agents/agents.ts +20 -1
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +1 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +3 -1
- package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +7 -7
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +5 -1
- package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +9 -10
- package/dist/builtin/subagents/src/shared/types.ts +1 -0
- package/dist/builtin/web-access/CHANGELOG.md +13 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +35 -0
- package/dist/builtin/workflows/README.md +38 -41
- package/dist/builtin/workflows/ambient.d.ts +36 -0
- package/dist/builtin/workflows/builtin/deep-research-codebase.d.ts +35 -0
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +11 -14
- package/dist/builtin/workflows/builtin/goal.d.ts +46 -0
- package/dist/builtin/workflows/builtin/goal.ts +10 -12
- package/dist/builtin/workflows/builtin/index.d.ts +136 -0
- package/dist/builtin/workflows/builtin/open-claude-design.d.ts +44 -0
- package/dist/builtin/workflows/builtin/open-claude-design.ts +19 -20
- package/dist/builtin/workflows/builtin/ralph.d.ts +36 -0
- package/dist/builtin/workflows/builtin/ralph.ts +20 -24
- package/dist/builtin/workflows/package.json +15 -5
- package/dist/builtin/workflows/src/authoring.d.ts +197 -0
- package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +6 -12
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -2
- package/dist/builtin/workflows/src/index.ts +0 -5
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +23 -9
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +33 -5
- package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +72 -11
- package/dist/builtin/workflows/src/sdk-surface.ts +12 -2
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +523 -0
- package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +1 -1
- package/dist/builtin/workflows/src/shared/types.ts +65 -350
- package/dist/builtin/workflows/src/workflows/define-workflow.ts +59 -44
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +1 -1
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +13 -1
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +23 -16
- package/dist/utils/changelog.js.map +1 -1
- package/docs/extensions.md +2 -2
- package/docs/packages.md +8 -4
- package/docs/subagents.md +30 -0
- package/docs/workflows.md +62 -21
- package/package.json +20 -1
- 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
|
-
|
|
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
|
|
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("
|
|
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 {
|
|
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
|
|
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,46 @@ 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
|
|
105
|
+
return makeCandidate(baseModel, baseModel, level);
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
const models = uniqueByFullId(availableModels);
|
|
81
|
-
const explicit = models.find((model) => model.fullId ===
|
|
109
|
+
const explicit = models.find((model) => model.fullId === baseModel);
|
|
82
110
|
if (explicit !== undefined) {
|
|
83
|
-
return
|
|
111
|
+
return makeCandidate(explicit.fullId, explicit.model ?? explicit.fullId, level);
|
|
84
112
|
}
|
|
85
113
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
114
|
+
if (baseModel.includes("/")) {
|
|
115
|
+
// Trust an explicit provider/model id even when the live catalog does not
|
|
116
|
+
// list it, mirroring the subagent resolver (resolveModelCandidate's
|
|
117
|
+
// `if (model.includes("/")) return model;`). The workflow catalog
|
|
118
|
+
// (ctx.modelRegistry.getAvailable()) can legitimately be a partial view
|
|
119
|
+
// (auth/provider gating, freshly added models), so treating an absent
|
|
120
|
+
// fully-qualified id as a hard failure made buildModelCandidates throw and
|
|
121
|
+
// collapse the whole ordered candidate list down to just the user's
|
|
122
|
+
// currentModel — discarding the workflow's defined primary and fallbacks.
|
|
123
|
+
// Pass it through with the reasoning suffix split off; the runtime fallback
|
|
124
|
+
// loop skips it only if the SDK genuinely cannot create a session for it.
|
|
125
|
+
return makeCandidate(baseModel, baseModel, level);
|
|
88
126
|
}
|
|
89
127
|
|
|
90
|
-
const byBareId = models.filter((model) => model.id ===
|
|
128
|
+
const byBareId = models.filter((model) => model.id === baseModel);
|
|
91
129
|
if (byBareId.length === 0) {
|
|
92
130
|
return { input, reason: "not available" };
|
|
93
131
|
}
|
|
94
132
|
if (byBareId.length === 1) {
|
|
95
133
|
const only = byBareId[0]!;
|
|
96
|
-
return
|
|
134
|
+
return makeCandidate(only.fullId, only.model ?? only.fullId, level);
|
|
97
135
|
}
|
|
98
136
|
|
|
99
137
|
const preferred = preferredProvider === undefined
|
|
100
138
|
? undefined
|
|
101
139
|
: byBareId.find((model) => model.provider === preferredProvider);
|
|
102
140
|
if (preferred !== undefined) {
|
|
103
|
-
return
|
|
141
|
+
return makeCandidate(preferred.fullId, preferred.model ?? preferred.fullId, level);
|
|
104
142
|
}
|
|
105
143
|
|
|
106
144
|
return {
|
|
@@ -127,13 +165,30 @@ function isFailure(value: WorkflowResolvedModelCandidate | ModelResolutionFailur
|
|
|
127
165
|
export function buildModelCandidates(input: {
|
|
128
166
|
readonly primaryModel?: WorkflowModelValue;
|
|
129
167
|
readonly fallbackModels?: readonly string[];
|
|
168
|
+
readonly fallbackThinkingLevels?: readonly string[];
|
|
130
169
|
readonly currentModel?: WorkflowModelValue;
|
|
131
170
|
readonly availableModels?: readonly WorkflowModelInfo[];
|
|
132
171
|
readonly preferredProvider?: string;
|
|
133
172
|
}): WorkflowResolvedModelCandidate[] {
|
|
134
173
|
const rawValues: WorkflowModelValue[] = [];
|
|
135
174
|
if (input.primaryModel !== undefined) rawValues.push(input.primaryModel);
|
|
136
|
-
|
|
175
|
+
for (const [index, fallback] of (input.fallbackModels ?? []).entries()) {
|
|
176
|
+
// Trim once up front so the suffix split, the validation error input, and the
|
|
177
|
+
// compat concatenation all operate on the same value. Concatenating the raw
|
|
178
|
+
// (untrimmed) fallback would push trailing whitespace into the interior of
|
|
179
|
+
// `id:level`, which `resolveStringModel` can no longer trim away.
|
|
180
|
+
const trimmedFallback = fallback.trim();
|
|
181
|
+
const split = splitReasoningSuffix(trimmedFallback);
|
|
182
|
+
const compatLevel = input.fallbackThinkingLevels?.[index];
|
|
183
|
+
if (split.level === undefined && compatLevel !== undefined) {
|
|
184
|
+
if (!WORKFLOW_THINKING_LEVEL_SET.has(compatLevel)) {
|
|
185
|
+
throw new WorkflowModelValidationError([{ input: trimmedFallback, reason: `invalid fallbackThinkingLevels[${index}] "${compatLevel}"; expected one of ${WORKFLOW_THINKING_LEVELS.join(", ")}` }]);
|
|
186
|
+
}
|
|
187
|
+
rawValues.push(`${trimmedFallback}:${compatLevel}`);
|
|
188
|
+
} else {
|
|
189
|
+
rawValues.push(trimmedFallback);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
137
192
|
if (input.currentModel !== undefined) rawValues.push(input.currentModel);
|
|
138
193
|
|
|
139
194
|
const failures: ModelResolutionFailure[] = [];
|
|
@@ -145,8 +200,9 @@ export function buildModelCandidates(input: {
|
|
|
145
200
|
failures.push(resolved);
|
|
146
201
|
continue;
|
|
147
202
|
}
|
|
148
|
-
|
|
149
|
-
seen.
|
|
203
|
+
const key = candidateKey(resolved);
|
|
204
|
+
if (seen.has(key)) continue;
|
|
205
|
+
seen.add(key);
|
|
150
206
|
candidates.push(resolved);
|
|
151
207
|
}
|
|
152
208
|
|
|
@@ -165,6 +221,7 @@ function catalogUnavailableWarning(): string {
|
|
|
165
221
|
export async function buildModelCandidatesFromCatalog(input: {
|
|
166
222
|
readonly primaryModel?: WorkflowModelValue;
|
|
167
223
|
readonly fallbackModels?: readonly string[];
|
|
224
|
+
readonly fallbackThinkingLevels?: readonly string[];
|
|
168
225
|
readonly catalog?: WorkflowModelCatalogPort;
|
|
169
226
|
}): Promise<WorkflowResolvedModelCandidate[]> {
|
|
170
227
|
const hasExplicitModel = input.primaryModel !== undefined || (input.fallbackModels?.length ?? 0) > 0;
|
|
@@ -174,6 +231,7 @@ export async function buildModelCandidatesFromCatalog(input: {
|
|
|
174
231
|
return buildModelCandidates({
|
|
175
232
|
primaryModel: input.primaryModel,
|
|
176
233
|
fallbackModels: input.fallbackModels,
|
|
234
|
+
fallbackThinkingLevels: input.fallbackThinkingLevels,
|
|
177
235
|
});
|
|
178
236
|
}
|
|
179
237
|
|
|
@@ -182,6 +240,7 @@ export async function buildModelCandidatesFromCatalog(input: {
|
|
|
182
240
|
return buildModelCandidates({
|
|
183
241
|
primaryModel: input.primaryModel,
|
|
184
242
|
fallbackModels: input.fallbackModels,
|
|
243
|
+
fallbackThinkingLevels: input.fallbackThinkingLevels,
|
|
185
244
|
currentModel: input.catalog.currentModel,
|
|
186
245
|
availableModels,
|
|
187
246
|
preferredProvider: input.catalog.preferredProvider,
|
|
@@ -199,6 +258,7 @@ export async function validateWorkflowModels(input: {
|
|
|
199
258
|
readonly requests: readonly {
|
|
200
259
|
readonly model?: WorkflowModelValue;
|
|
201
260
|
readonly fallbackModels?: readonly string[];
|
|
261
|
+
readonly fallbackThinkingLevels?: readonly string[];
|
|
202
262
|
}[];
|
|
203
263
|
readonly catalog?: WorkflowModelCatalogPort;
|
|
204
264
|
}): Promise<readonly string[]> {
|
|
@@ -230,6 +290,7 @@ export async function validateWorkflowModels(input: {
|
|
|
230
290
|
buildModelCandidates({
|
|
231
291
|
primaryModel: request.model,
|
|
232
292
|
fallbackModels: request.fallbackModels,
|
|
293
|
+
fallbackThinkingLevels: request.fallbackThinkingLevels,
|
|
233
294
|
currentModel: input.catalog?.currentModel,
|
|
234
295
|
availableModels,
|
|
235
296
|
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
|
|
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";
|