@multiplier-labs/stepflow 0.3.1 → 0.3.4
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/.codekin/reports/code-review/2026-04-11_code-review-daily.md +146 -0
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +17 -5
- package/.github/workflows/security.yml +29 -0
- package/CHANGELOG.md +34 -0
- package/README.md +24 -0
- package/SECURITY.md +40 -0
- package/dist/{chunk-FQCT3I2A.js → chunk-5XJBAPXF.js} +527 -551
- package/dist/chunk-5XJBAPXF.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-FMPJBK2N.js} +67 -55
- package/dist/chunk-FMPJBK2N.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-JPXPB7U5.js} +52 -2
- package/dist/chunk-JPXPB7U5.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-U3PCVK6O.js} +276 -61
- package/dist/chunk-U3PCVK6O.js.map +1 -0
- package/dist/events/index.d.ts +60 -7
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Cd0IZ40L.d.ts} +56 -17
- package/dist/index.d.ts +15 -23
- package/dist/index.js +784 -547
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +4 -4
- package/dist/scheduler/index.js +2 -2
- package/dist/storage/index.d.ts +322 -56
- package/dist/storage/index.js +4 -2
- package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
- package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
- package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
- package/docs/stepflow-api-reference.md +5 -4
- package/package.json +12 -8
- package/src/core/engine/concurrency.ts +229 -0
- package/src/core/engine/lifecycle.ts +176 -0
- package/src/core/engine.test.ts +536 -522
- package/src/core/engine.ts +116 -235
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator/persistence.ts +165 -0
- package/src/core/orchestrator/plan-resolution.ts +140 -0
- package/src/core/orchestrator/state-transitions.ts +109 -0
- package/src/core/orchestrator/step-runner.ts +427 -0
- package/src/core/orchestrator.ts +57 -395
- package/src/core/types.ts +7 -29
- package/src/events/index.ts +4 -4
- package/src/events/memory.test.ts +39 -41
- package/src/events/memory.ts +7 -10
- package/src/events/socketio.test.ts +112 -162
- package/src/events/socketio.ts +20 -27
- package/src/events/types.ts +16 -19
- package/src/events/webhook.test.ts +489 -235
- package/src/events/webhook.ts +355 -139
- package/src/index.ts +19 -19
- package/src/planning/evaluate-condition.test.ts +153 -0
- package/src/planning/evaluate-condition.ts +105 -0
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +45 -152
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +20 -106
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +149 -150
- package/src/scheduler/cron.ts +58 -69
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +89 -105
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +40 -22
- package/src/scheduler/types.ts +4 -9
- package/src/storage/index.ts +5 -5
- package/src/storage/memory.test.ts +133 -183
- package/src/storage/memory.ts +21 -42
- package/src/storage/postgres-core.ts +550 -0
- package/src/storage/postgres-migration.test.ts +69 -0
- package/src/storage/postgres-transaction-pagination.test.ts +107 -0
- package/src/storage/postgres.ts +222 -937
- package/src/storage/sqlite-core.ts +151 -0
- package/src/storage/sqlite.test.ts +330 -281
- package/src/storage/sqlite.ts +225 -170
- package/src/storage/types.ts +33 -58
- package/src/utils/errors.test.ts +78 -80
- package/src/utils/errors.ts +27 -57
- package/src/utils/id.ts +3 -3
- package/src/utils/index.ts +6 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/pinned-fetch.test.ts +84 -0
- package/src/utils/pinned-fetch.ts +118 -0
- package/src/utils/postgres-deps.ts +20 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- package/src/utils/safe-json.test.ts +166 -0
- package/src/utils/safe-json.ts +107 -0
- package/src/utils/webhook-signature.test.ts +111 -0
- package/src/utils/webhook-signature.ts +74 -0
- package/dist/chunk-FQCT3I2A.js.map +0 -1
- package/dist/chunk-GXY66V7Q.js.map +0 -1
- package/dist/chunk-U5QGN3RT.js.map +0 -1
- package/dist/chunk-XCF4PLN7.js.map +0 -1
- package/src/scheduler/postgres-persistence.test.ts +0 -906
package/src/planning/planner.ts
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* Selects recipes and generates plans based on conditions and input analysis.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { generateId } from '../utils/id';
|
|
7
|
+
import { evaluateCondition, getNestedValue } from './evaluate-condition';
|
|
8
8
|
import type {
|
|
9
|
-
ConditionOperator,
|
|
10
9
|
Plan,
|
|
11
10
|
PlannedStep,
|
|
12
11
|
Planner,
|
|
@@ -20,102 +19,7 @@ import type {
|
|
|
20
19
|
ResourceEstimate,
|
|
21
20
|
StepHandlerRegistry,
|
|
22
21
|
WorkflowKind,
|
|
23
|
-
} from
|
|
24
|
-
|
|
25
|
-
// ============================================================================
|
|
26
|
-
// Condition Evaluation
|
|
27
|
-
// ============================================================================
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get a nested value from an object using dot notation.
|
|
31
|
-
*/
|
|
32
|
-
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
33
|
-
return path.split(".").reduce<unknown>((current, key) => {
|
|
34
|
-
if (current && typeof current === "object" && key in current) {
|
|
35
|
-
return (current as Record<string, unknown>)[key];
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
}, obj);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Evaluate a single condition against a value.
|
|
43
|
-
*/
|
|
44
|
-
function evaluateCondition(
|
|
45
|
-
operator: ConditionOperator,
|
|
46
|
-
fieldValue: unknown,
|
|
47
|
-
conditionValue: unknown,
|
|
48
|
-
): boolean {
|
|
49
|
-
switch (operator) {
|
|
50
|
-
case "eq":
|
|
51
|
-
return fieldValue === conditionValue;
|
|
52
|
-
|
|
53
|
-
case "neq":
|
|
54
|
-
return fieldValue !== conditionValue;
|
|
55
|
-
|
|
56
|
-
case "gt":
|
|
57
|
-
return typeof fieldValue === "number" &&
|
|
58
|
-
typeof conditionValue === "number"
|
|
59
|
-
? fieldValue > conditionValue
|
|
60
|
-
: false;
|
|
61
|
-
|
|
62
|
-
case "gte":
|
|
63
|
-
return typeof fieldValue === "number" &&
|
|
64
|
-
typeof conditionValue === "number"
|
|
65
|
-
? fieldValue >= conditionValue
|
|
66
|
-
: false;
|
|
67
|
-
|
|
68
|
-
case "lt":
|
|
69
|
-
return typeof fieldValue === "number" &&
|
|
70
|
-
typeof conditionValue === "number"
|
|
71
|
-
? fieldValue < conditionValue
|
|
72
|
-
: false;
|
|
73
|
-
|
|
74
|
-
case "lte":
|
|
75
|
-
return typeof fieldValue === "number" &&
|
|
76
|
-
typeof conditionValue === "number"
|
|
77
|
-
? fieldValue <= conditionValue
|
|
78
|
-
: false;
|
|
79
|
-
|
|
80
|
-
case "contains":
|
|
81
|
-
if (
|
|
82
|
-
typeof fieldValue === "string" &&
|
|
83
|
-
typeof conditionValue === "string"
|
|
84
|
-
) {
|
|
85
|
-
return fieldValue.includes(conditionValue);
|
|
86
|
-
}
|
|
87
|
-
if (Array.isArray(fieldValue)) {
|
|
88
|
-
return fieldValue.includes(conditionValue);
|
|
89
|
-
}
|
|
90
|
-
return false;
|
|
91
|
-
|
|
92
|
-
case "matches":
|
|
93
|
-
if (
|
|
94
|
-
typeof fieldValue === "string" &&
|
|
95
|
-
typeof conditionValue === "string"
|
|
96
|
-
) {
|
|
97
|
-
try {
|
|
98
|
-
return new RE2(conditionValue).test(fieldValue);
|
|
99
|
-
} catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
|
|
105
|
-
case "exists":
|
|
106
|
-
return (
|
|
107
|
-
fieldValue !== undefined && fieldValue !== null && fieldValue !== ""
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
case "notExists":
|
|
111
|
-
return (
|
|
112
|
-
fieldValue === undefined || fieldValue === null || fieldValue === ""
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
default:
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
22
|
+
} from './types';
|
|
119
23
|
|
|
120
24
|
/**
|
|
121
25
|
* Score a recipe's conditions against input.
|
|
@@ -123,7 +27,7 @@ function evaluateCondition(
|
|
|
123
27
|
*/
|
|
124
28
|
function scoreConditions(
|
|
125
29
|
conditions: RecipeCondition[] | undefined,
|
|
126
|
-
input: Record<string, unknown
|
|
30
|
+
input: Record<string, unknown>
|
|
127
31
|
): number {
|
|
128
32
|
// No conditions = default match with low score
|
|
129
33
|
if (!conditions || conditions.length === 0) {
|
|
@@ -185,9 +89,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
185
89
|
private recipeRegistry: RecipeRegistry;
|
|
186
90
|
private handlerRegistry?: StepHandlerRegistry;
|
|
187
91
|
private validateHandlers: boolean;
|
|
188
|
-
private resourceEstimates: Required<
|
|
189
|
-
NonNullable<RuleBasedPlannerConfig["resourceEstimates"]>
|
|
190
|
-
>;
|
|
92
|
+
private resourceEstimates: Required<NonNullable<RuleBasedPlannerConfig['resourceEstimates']>>;
|
|
191
93
|
|
|
192
94
|
constructor(config: RuleBasedPlannerConfig) {
|
|
193
95
|
this.recipeRegistry = config.recipeRegistry;
|
|
@@ -207,7 +109,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
207
109
|
async selectRecipe(
|
|
208
110
|
workflowKind: WorkflowKind,
|
|
209
111
|
input: Record<string, unknown>,
|
|
210
|
-
context?: PlanningContext
|
|
112
|
+
context?: PlanningContext
|
|
211
113
|
): Promise<RecipeSelectionResult> {
|
|
212
114
|
// Check for forced recipe in hints
|
|
213
115
|
if (context?.hints?.forceRecipeId) {
|
|
@@ -225,7 +127,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
225
127
|
if (context?.hints?.preferredVariant) {
|
|
226
128
|
const preferred = this.recipeRegistry.getVariant(
|
|
227
129
|
workflowKind,
|
|
228
|
-
context.hints.preferredVariant
|
|
130
|
+
context.hints.preferredVariant
|
|
229
131
|
);
|
|
230
132
|
if (preferred) {
|
|
231
133
|
return {
|
|
@@ -244,7 +146,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
244
146
|
}
|
|
245
147
|
|
|
246
148
|
// Score each recipe
|
|
247
|
-
const scored = recipes.map(
|
|
149
|
+
const scored = recipes.map(recipe => ({
|
|
248
150
|
recipe,
|
|
249
151
|
conditionScore: scoreConditions(recipe.conditions, input),
|
|
250
152
|
priorityScore: recipe.priority ?? 0,
|
|
@@ -259,7 +161,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
259
161
|
});
|
|
260
162
|
|
|
261
163
|
// Find best matching recipe
|
|
262
|
-
const best = scored.find(
|
|
164
|
+
const best = scored.find(s => s.conditionScore > 0);
|
|
263
165
|
|
|
264
166
|
if (best) {
|
|
265
167
|
const reason = this.buildSelectionReason(best.recipe, input);
|
|
@@ -294,12 +196,12 @@ export class RuleBasedPlanner implements Planner {
|
|
|
294
196
|
async generatePlan(
|
|
295
197
|
recipe: Recipe,
|
|
296
198
|
input: Record<string, unknown>,
|
|
297
|
-
context?: PlanningContext
|
|
199
|
+
context?: PlanningContext
|
|
298
200
|
): Promise<Plan> {
|
|
299
201
|
const modifications: PlanModification[] = [];
|
|
300
202
|
|
|
301
203
|
// Start with recipe steps
|
|
302
|
-
let steps = recipe.steps.map(
|
|
204
|
+
let steps = recipe.steps.map(step => this.recipeStepToPlannedStep(step));
|
|
303
205
|
|
|
304
206
|
// Apply hint-based modifications
|
|
305
207
|
if (context?.hints) {
|
|
@@ -308,11 +210,11 @@ export class RuleBasedPlanner implements Planner {
|
|
|
308
210
|
// Mark steps to skip
|
|
309
211
|
if (skipSteps && skipSteps.length > 0) {
|
|
310
212
|
for (const stepKey of skipSteps) {
|
|
311
|
-
const stepIndex = steps.findIndex(
|
|
213
|
+
const stepIndex = steps.findIndex(s => s.key === stepKey);
|
|
312
214
|
if (stepIndex >= 0) {
|
|
313
|
-
steps = steps.filter(
|
|
215
|
+
steps = steps.filter(s => s.key !== stepKey);
|
|
314
216
|
modifications.push({
|
|
315
|
-
type:
|
|
217
|
+
type: 'remove_step',
|
|
316
218
|
stepKey,
|
|
317
219
|
value: null,
|
|
318
220
|
reason: `Skipped via planning hints`,
|
|
@@ -327,9 +229,9 @@ export class RuleBasedPlanner implements Planner {
|
|
|
327
229
|
step.config = { ...step.config, ...additionalConfig };
|
|
328
230
|
}
|
|
329
231
|
modifications.push({
|
|
330
|
-
type:
|
|
232
|
+
type: 'modify_step',
|
|
331
233
|
value: additionalConfig,
|
|
332
|
-
reason:
|
|
234
|
+
reason: 'Applied additional config from planning hints',
|
|
333
235
|
});
|
|
334
236
|
}
|
|
335
237
|
}
|
|
@@ -365,13 +267,13 @@ export class RuleBasedPlanner implements Planner {
|
|
|
365
267
|
async plan(
|
|
366
268
|
workflowKind: WorkflowKind,
|
|
367
269
|
input: Record<string, unknown>,
|
|
368
|
-
context?: PlanningContext
|
|
270
|
+
context?: PlanningContext
|
|
369
271
|
): Promise<Plan> {
|
|
370
272
|
const selection = await this.selectRecipe(workflowKind, input, context);
|
|
371
273
|
const plan = await this.generatePlan(selection.recipe, input, context);
|
|
372
274
|
|
|
373
275
|
// Add selection reasoning to plan
|
|
374
|
-
plan.reasoning = `${selection.reason}. ${plan.reasoning ??
|
|
276
|
+
plan.reasoning = `${selection.reason}. ${plan.reasoning ?? ''}`.trim();
|
|
375
277
|
|
|
376
278
|
return plan;
|
|
377
279
|
}
|
|
@@ -385,7 +287,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
385
287
|
|
|
386
288
|
// Check plan has steps
|
|
387
289
|
if (!plan.steps || plan.steps.length === 0) {
|
|
388
|
-
errors.push(
|
|
290
|
+
errors.push('Plan has no steps');
|
|
389
291
|
}
|
|
390
292
|
|
|
391
293
|
// Check for duplicate step keys
|
|
@@ -400,7 +302,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
400
302
|
// Validate each step
|
|
401
303
|
for (const step of plan.steps) {
|
|
402
304
|
if (!step.key) {
|
|
403
|
-
errors.push(
|
|
305
|
+
errors.push('Step missing key');
|
|
404
306
|
}
|
|
405
307
|
if (!step.name) {
|
|
406
308
|
warnings.push(`Step '${step.key}' missing name`);
|
|
@@ -412,9 +314,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
412
314
|
// Validate handler exists (if registry available)
|
|
413
315
|
if (this.validateHandlers && this.handlerRegistry) {
|
|
414
316
|
if (!this.handlerRegistry.has(step.handlerRef)) {
|
|
415
|
-
errors.push(
|
|
416
|
-
`Step '${step.key}' references unknown handler: ${step.handlerRef}`,
|
|
417
|
-
);
|
|
317
|
+
errors.push(`Step '${step.key}' references unknown handler: ${step.handlerRef}`);
|
|
418
318
|
}
|
|
419
319
|
}
|
|
420
320
|
}
|
|
@@ -423,7 +323,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
423
323
|
if (plan.childWorkflows) {
|
|
424
324
|
for (const child of plan.childWorkflows) {
|
|
425
325
|
if (!child.kind) {
|
|
426
|
-
errors.push(
|
|
326
|
+
errors.push('Child workflow missing kind');
|
|
427
327
|
}
|
|
428
328
|
}
|
|
429
329
|
}
|
|
@@ -439,12 +339,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
439
339
|
* Estimate resources required for a plan.
|
|
440
340
|
*/
|
|
441
341
|
estimateResources(plan: Plan): ResourceEstimate {
|
|
442
|
-
const {
|
|
443
|
-
apiCallsPerStep,
|
|
444
|
-
tokensPerStep,
|
|
445
|
-
durationPerStep,
|
|
446
|
-
apiCallsPerChild,
|
|
447
|
-
} = this.resourceEstimates;
|
|
342
|
+
const { apiCallsPerStep, tokensPerStep, durationPerStep, apiCallsPerChild } = this.resourceEstimates;
|
|
448
343
|
|
|
449
344
|
const stepCount = plan.steps.length;
|
|
450
345
|
const childCount = plan.childWorkflows?.length ?? 0;
|
|
@@ -463,7 +358,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
463
358
|
/**
|
|
464
359
|
* Convert a recipe step to a planned step.
|
|
465
360
|
*/
|
|
466
|
-
private recipeStepToPlannedStep(step: Recipe[
|
|
361
|
+
private recipeStepToPlannedStep(step: Recipe['steps'][0]): PlannedStep {
|
|
467
362
|
return {
|
|
468
363
|
key: step.key,
|
|
469
364
|
name: step.name,
|
|
@@ -483,7 +378,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
483
378
|
*/
|
|
484
379
|
private applyConstraints(
|
|
485
380
|
steps: PlannedStep[],
|
|
486
|
-
constraints: NonNullable<PlanningContext[
|
|
381
|
+
constraints: NonNullable<PlanningContext['constraints']>
|
|
487
382
|
): { steps: PlannedStep[]; modifications: PlanModification[] } {
|
|
488
383
|
const modifications: PlanModification[] = [];
|
|
489
384
|
const perStepTimeout = constraints.maxDuration
|
|
@@ -491,17 +386,15 @@ export class RuleBasedPlanner implements Planner {
|
|
|
491
386
|
: undefined;
|
|
492
387
|
|
|
493
388
|
// Apply all constraint mutations in a single pass
|
|
494
|
-
const modifiedSteps = steps.map(
|
|
389
|
+
const modifiedSteps = steps.map(step => {
|
|
495
390
|
const modified = { ...step };
|
|
496
391
|
|
|
497
|
-
if (constraints.priority ===
|
|
498
|
-
modified.timeout = modified.timeout
|
|
499
|
-
? Math.min(modified.timeout, 30000)
|
|
500
|
-
: 30000;
|
|
392
|
+
if (constraints.priority === 'speed') {
|
|
393
|
+
modified.timeout = modified.timeout ? Math.min(modified.timeout, 30000) : 30000;
|
|
501
394
|
modified.maxRetries = Math.min(modified.maxRetries ?? 3, 1);
|
|
502
395
|
}
|
|
503
396
|
|
|
504
|
-
if (constraints.priority ===
|
|
397
|
+
if (constraints.priority === 'cost') {
|
|
505
398
|
modified.maxRetries = 0;
|
|
506
399
|
}
|
|
507
400
|
|
|
@@ -515,23 +408,23 @@ export class RuleBasedPlanner implements Planner {
|
|
|
515
408
|
});
|
|
516
409
|
|
|
517
410
|
// Record what was applied
|
|
518
|
-
if (constraints.priority ===
|
|
411
|
+
if (constraints.priority === 'speed') {
|
|
519
412
|
modifications.push({
|
|
520
|
-
type:
|
|
521
|
-
value: { priority:
|
|
522
|
-
reason:
|
|
413
|
+
type: 'set_default',
|
|
414
|
+
value: { priority: 'speed' },
|
|
415
|
+
reason: 'Optimized for speed: reduced timeouts and retries',
|
|
523
416
|
});
|
|
524
417
|
}
|
|
525
|
-
if (constraints.priority ===
|
|
418
|
+
if (constraints.priority === 'cost') {
|
|
526
419
|
modifications.push({
|
|
527
|
-
type:
|
|
528
|
-
value: { priority:
|
|
529
|
-
reason:
|
|
420
|
+
type: 'set_default',
|
|
421
|
+
value: { priority: 'cost' },
|
|
422
|
+
reason: 'Optimized for cost: disabled retries',
|
|
530
423
|
});
|
|
531
424
|
}
|
|
532
425
|
if (constraints.maxDuration) {
|
|
533
426
|
modifications.push({
|
|
534
|
-
type:
|
|
427
|
+
type: 'set_default',
|
|
535
428
|
value: { maxDuration: constraints.maxDuration },
|
|
536
429
|
reason: `Applied duration constraint: ${constraints.maxDuration}ms total`,
|
|
537
430
|
});
|
|
@@ -545,23 +438,23 @@ export class RuleBasedPlanner implements Planner {
|
|
|
545
438
|
*/
|
|
546
439
|
private buildSelectionReason(
|
|
547
440
|
recipe: Recipe,
|
|
548
|
-
input: Record<string, unknown
|
|
441
|
+
input: Record<string, unknown>
|
|
549
442
|
): string {
|
|
550
443
|
const parts: string[] = [`Selected recipe: ${recipe.id}`];
|
|
551
444
|
|
|
552
445
|
if (recipe.conditions && recipe.conditions.length > 0) {
|
|
553
|
-
const conditionDescriptions = recipe.conditions.map(
|
|
446
|
+
const conditionDescriptions = recipe.conditions.map(c => {
|
|
554
447
|
const value = getNestedValue(input, c.field);
|
|
555
448
|
return `${c.field} ${c.operator} ${JSON.stringify(c.value)} (actual: ${JSON.stringify(value)})`;
|
|
556
449
|
});
|
|
557
|
-
parts.push(`Matched conditions: ${conditionDescriptions.join(
|
|
450
|
+
parts.push(`Matched conditions: ${conditionDescriptions.join(', ')}`);
|
|
558
451
|
}
|
|
559
452
|
|
|
560
453
|
if (recipe.priority !== undefined && recipe.priority > 0) {
|
|
561
454
|
parts.push(`Priority: ${recipe.priority}`);
|
|
562
455
|
}
|
|
563
456
|
|
|
564
|
-
return parts.join(
|
|
457
|
+
return parts.join('. ');
|
|
565
458
|
}
|
|
566
459
|
|
|
567
460
|
/**
|
|
@@ -570,7 +463,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
570
463
|
private buildPlanReasoning(
|
|
571
464
|
recipe: Recipe,
|
|
572
465
|
modifications: PlanModification[],
|
|
573
|
-
context?: PlanningContext
|
|
466
|
+
context?: PlanningContext
|
|
574
467
|
): string {
|
|
575
468
|
const parts: string[] = [];
|
|
576
469
|
|
|
@@ -592,10 +485,10 @@ export class RuleBasedPlanner implements Planner {
|
|
|
592
485
|
constraintList.push(`maxApiCalls=${context.constraints.maxApiCalls}`);
|
|
593
486
|
}
|
|
594
487
|
if (constraintList.length > 0) {
|
|
595
|
-
parts.push(`Constraints: ${constraintList.join(
|
|
488
|
+
parts.push(`Constraints: ${constraintList.join(', ')}`);
|
|
596
489
|
}
|
|
597
490
|
}
|
|
598
491
|
|
|
599
|
-
return parts.join(
|
|
492
|
+
return parts.join('. ') + '.';
|
|
600
493
|
}
|
|
601
494
|
}
|