@multiplier-labs/stepflow 0.3.1 → 0.3.3
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/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +11 -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-4VOKMGP6.js} +23 -42
- package/dist/chunk-4VOKMGP6.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-GYPNJBDP.js} +12 -16
- package/dist/chunk-GYPNJBDP.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-KHTUWLVZ.js} +14 -47
- package/dist/chunk-KHTUWLVZ.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-ML35PIHX.js} +1 -1
- package/dist/chunk-ML35PIHX.js.map +1 -0
- package/dist/events/index.d.ts +4 -4
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Dzr437iW.d.ts} +6 -6
- package/dist/index.d.ts +14 -12
- package/dist/index.js +40 -82
- 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 +13 -13
- package/dist/storage/index.js +2 -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 +2 -3
- package/package.json +7 -6
- package/src/core/engine.test.ts +466 -522
- package/src/core/engine.ts +42 -67
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator.ts +64 -83
- 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 +197 -235
- package/src/events/webhook.ts +44 -71
- package/src/index.ts +19 -19
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +66 -91
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +39 -43
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +141 -150
- package/src/scheduler/cron.ts +32 -67
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +71 -99
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +12 -19
- 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.ts +197 -393
- package/src/storage/sqlite.test.ts +154 -281
- package/src/storage/sqlite.ts +50 -85
- 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 +4 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/postgres-deps.ts +4 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- 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,8 +3,8 @@
|
|
|
3
3
|
* Selects recipes and generates plans based on conditions and input analysis.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import RE2 from
|
|
7
|
-
import { generateId } from
|
|
6
|
+
import RE2 from 're2';
|
|
7
|
+
import { generateId } from '../utils/id';
|
|
8
8
|
import type {
|
|
9
9
|
ConditionOperator,
|
|
10
10
|
Plan,
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
ResourceEstimate,
|
|
21
21
|
StepHandlerRegistry,
|
|
22
22
|
WorkflowKind,
|
|
23
|
-
} from
|
|
23
|
+
} from './types';
|
|
24
24
|
|
|
25
25
|
// ============================================================================
|
|
26
26
|
// Condition Evaluation
|
|
@@ -30,8 +30,8 @@ import type {
|
|
|
30
30
|
* Get a nested value from an object using dot notation.
|
|
31
31
|
*/
|
|
32
32
|
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
33
|
-
return path.split(
|
|
34
|
-
if (current && typeof current ===
|
|
33
|
+
return path.split('.').reduce<unknown>((current, key) => {
|
|
34
|
+
if (current && typeof current === 'object' && key in current) {
|
|
35
35
|
return (current as Record<string, unknown>)[key];
|
|
36
36
|
}
|
|
37
37
|
return undefined;
|
|
@@ -44,44 +44,37 @@ function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
|
44
44
|
function evaluateCondition(
|
|
45
45
|
operator: ConditionOperator,
|
|
46
46
|
fieldValue: unknown,
|
|
47
|
-
conditionValue: unknown
|
|
47
|
+
conditionValue: unknown
|
|
48
48
|
): boolean {
|
|
49
49
|
switch (operator) {
|
|
50
|
-
case
|
|
50
|
+
case 'eq':
|
|
51
51
|
return fieldValue === conditionValue;
|
|
52
52
|
|
|
53
|
-
case
|
|
53
|
+
case 'neq':
|
|
54
54
|
return fieldValue !== conditionValue;
|
|
55
55
|
|
|
56
|
-
case
|
|
57
|
-
return typeof fieldValue ===
|
|
58
|
-
typeof conditionValue === "number"
|
|
56
|
+
case 'gt':
|
|
57
|
+
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
59
58
|
? fieldValue > conditionValue
|
|
60
59
|
: false;
|
|
61
60
|
|
|
62
|
-
case
|
|
63
|
-
return typeof fieldValue ===
|
|
64
|
-
typeof conditionValue === "number"
|
|
61
|
+
case 'gte':
|
|
62
|
+
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
65
63
|
? fieldValue >= conditionValue
|
|
66
64
|
: false;
|
|
67
65
|
|
|
68
|
-
case
|
|
69
|
-
return typeof fieldValue ===
|
|
70
|
-
typeof conditionValue === "number"
|
|
66
|
+
case 'lt':
|
|
67
|
+
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
71
68
|
? fieldValue < conditionValue
|
|
72
69
|
: false;
|
|
73
70
|
|
|
74
|
-
case
|
|
75
|
-
return typeof fieldValue ===
|
|
76
|
-
typeof conditionValue === "number"
|
|
71
|
+
case 'lte':
|
|
72
|
+
return typeof fieldValue === 'number' && typeof conditionValue === 'number'
|
|
77
73
|
? fieldValue <= conditionValue
|
|
78
74
|
: false;
|
|
79
75
|
|
|
80
|
-
case
|
|
81
|
-
if (
|
|
82
|
-
typeof fieldValue === "string" &&
|
|
83
|
-
typeof conditionValue === "string"
|
|
84
|
-
) {
|
|
76
|
+
case 'contains':
|
|
77
|
+
if (typeof fieldValue === 'string' && typeof conditionValue === 'string') {
|
|
85
78
|
return fieldValue.includes(conditionValue);
|
|
86
79
|
}
|
|
87
80
|
if (Array.isArray(fieldValue)) {
|
|
@@ -89,11 +82,8 @@ function evaluateCondition(
|
|
|
89
82
|
}
|
|
90
83
|
return false;
|
|
91
84
|
|
|
92
|
-
case
|
|
93
|
-
if (
|
|
94
|
-
typeof fieldValue === "string" &&
|
|
95
|
-
typeof conditionValue === "string"
|
|
96
|
-
) {
|
|
85
|
+
case 'matches':
|
|
86
|
+
if (typeof fieldValue === 'string' && typeof conditionValue === 'string') {
|
|
97
87
|
try {
|
|
98
88
|
return new RE2(conditionValue).test(fieldValue);
|
|
99
89
|
} catch {
|
|
@@ -102,15 +92,11 @@ function evaluateCondition(
|
|
|
102
92
|
}
|
|
103
93
|
return false;
|
|
104
94
|
|
|
105
|
-
case
|
|
106
|
-
return
|
|
107
|
-
fieldValue !== undefined && fieldValue !== null && fieldValue !== ""
|
|
108
|
-
);
|
|
95
|
+
case 'exists':
|
|
96
|
+
return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
|
|
109
97
|
|
|
110
|
-
case
|
|
111
|
-
return
|
|
112
|
-
fieldValue === undefined || fieldValue === null || fieldValue === ""
|
|
113
|
-
);
|
|
98
|
+
case 'notExists':
|
|
99
|
+
return fieldValue === undefined || fieldValue === null || fieldValue === '';
|
|
114
100
|
|
|
115
101
|
default:
|
|
116
102
|
return false;
|
|
@@ -123,7 +109,7 @@ function evaluateCondition(
|
|
|
123
109
|
*/
|
|
124
110
|
function scoreConditions(
|
|
125
111
|
conditions: RecipeCondition[] | undefined,
|
|
126
|
-
input: Record<string, unknown
|
|
112
|
+
input: Record<string, unknown>
|
|
127
113
|
): number {
|
|
128
114
|
// No conditions = default match with low score
|
|
129
115
|
if (!conditions || conditions.length === 0) {
|
|
@@ -185,9 +171,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
185
171
|
private recipeRegistry: RecipeRegistry;
|
|
186
172
|
private handlerRegistry?: StepHandlerRegistry;
|
|
187
173
|
private validateHandlers: boolean;
|
|
188
|
-
private resourceEstimates: Required<
|
|
189
|
-
NonNullable<RuleBasedPlannerConfig["resourceEstimates"]>
|
|
190
|
-
>;
|
|
174
|
+
private resourceEstimates: Required<NonNullable<RuleBasedPlannerConfig['resourceEstimates']>>;
|
|
191
175
|
|
|
192
176
|
constructor(config: RuleBasedPlannerConfig) {
|
|
193
177
|
this.recipeRegistry = config.recipeRegistry;
|
|
@@ -207,7 +191,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
207
191
|
async selectRecipe(
|
|
208
192
|
workflowKind: WorkflowKind,
|
|
209
193
|
input: Record<string, unknown>,
|
|
210
|
-
context?: PlanningContext
|
|
194
|
+
context?: PlanningContext
|
|
211
195
|
): Promise<RecipeSelectionResult> {
|
|
212
196
|
// Check for forced recipe in hints
|
|
213
197
|
if (context?.hints?.forceRecipeId) {
|
|
@@ -225,7 +209,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
225
209
|
if (context?.hints?.preferredVariant) {
|
|
226
210
|
const preferred = this.recipeRegistry.getVariant(
|
|
227
211
|
workflowKind,
|
|
228
|
-
context.hints.preferredVariant
|
|
212
|
+
context.hints.preferredVariant
|
|
229
213
|
);
|
|
230
214
|
if (preferred) {
|
|
231
215
|
return {
|
|
@@ -244,7 +228,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
244
228
|
}
|
|
245
229
|
|
|
246
230
|
// Score each recipe
|
|
247
|
-
const scored = recipes.map(
|
|
231
|
+
const scored = recipes.map(recipe => ({
|
|
248
232
|
recipe,
|
|
249
233
|
conditionScore: scoreConditions(recipe.conditions, input),
|
|
250
234
|
priorityScore: recipe.priority ?? 0,
|
|
@@ -259,7 +243,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
259
243
|
});
|
|
260
244
|
|
|
261
245
|
// Find best matching recipe
|
|
262
|
-
const best = scored.find(
|
|
246
|
+
const best = scored.find(s => s.conditionScore > 0);
|
|
263
247
|
|
|
264
248
|
if (best) {
|
|
265
249
|
const reason = this.buildSelectionReason(best.recipe, input);
|
|
@@ -294,12 +278,12 @@ export class RuleBasedPlanner implements Planner {
|
|
|
294
278
|
async generatePlan(
|
|
295
279
|
recipe: Recipe,
|
|
296
280
|
input: Record<string, unknown>,
|
|
297
|
-
context?: PlanningContext
|
|
281
|
+
context?: PlanningContext
|
|
298
282
|
): Promise<Plan> {
|
|
299
283
|
const modifications: PlanModification[] = [];
|
|
300
284
|
|
|
301
285
|
// Start with recipe steps
|
|
302
|
-
let steps = recipe.steps.map(
|
|
286
|
+
let steps = recipe.steps.map(step => this.recipeStepToPlannedStep(step));
|
|
303
287
|
|
|
304
288
|
// Apply hint-based modifications
|
|
305
289
|
if (context?.hints) {
|
|
@@ -308,11 +292,11 @@ export class RuleBasedPlanner implements Planner {
|
|
|
308
292
|
// Mark steps to skip
|
|
309
293
|
if (skipSteps && skipSteps.length > 0) {
|
|
310
294
|
for (const stepKey of skipSteps) {
|
|
311
|
-
const stepIndex = steps.findIndex(
|
|
295
|
+
const stepIndex = steps.findIndex(s => s.key === stepKey);
|
|
312
296
|
if (stepIndex >= 0) {
|
|
313
|
-
steps = steps.filter(
|
|
297
|
+
steps = steps.filter(s => s.key !== stepKey);
|
|
314
298
|
modifications.push({
|
|
315
|
-
type:
|
|
299
|
+
type: 'remove_step',
|
|
316
300
|
stepKey,
|
|
317
301
|
value: null,
|
|
318
302
|
reason: `Skipped via planning hints`,
|
|
@@ -327,9 +311,9 @@ export class RuleBasedPlanner implements Planner {
|
|
|
327
311
|
step.config = { ...step.config, ...additionalConfig };
|
|
328
312
|
}
|
|
329
313
|
modifications.push({
|
|
330
|
-
type:
|
|
314
|
+
type: 'modify_step',
|
|
331
315
|
value: additionalConfig,
|
|
332
|
-
reason:
|
|
316
|
+
reason: 'Applied additional config from planning hints',
|
|
333
317
|
});
|
|
334
318
|
}
|
|
335
319
|
}
|
|
@@ -365,13 +349,13 @@ export class RuleBasedPlanner implements Planner {
|
|
|
365
349
|
async plan(
|
|
366
350
|
workflowKind: WorkflowKind,
|
|
367
351
|
input: Record<string, unknown>,
|
|
368
|
-
context?: PlanningContext
|
|
352
|
+
context?: PlanningContext
|
|
369
353
|
): Promise<Plan> {
|
|
370
354
|
const selection = await this.selectRecipe(workflowKind, input, context);
|
|
371
355
|
const plan = await this.generatePlan(selection.recipe, input, context);
|
|
372
356
|
|
|
373
357
|
// Add selection reasoning to plan
|
|
374
|
-
plan.reasoning = `${selection.reason}. ${plan.reasoning ??
|
|
358
|
+
plan.reasoning = `${selection.reason}. ${plan.reasoning ?? ''}`.trim();
|
|
375
359
|
|
|
376
360
|
return plan;
|
|
377
361
|
}
|
|
@@ -385,7 +369,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
385
369
|
|
|
386
370
|
// Check plan has steps
|
|
387
371
|
if (!plan.steps || plan.steps.length === 0) {
|
|
388
|
-
errors.push(
|
|
372
|
+
errors.push('Plan has no steps');
|
|
389
373
|
}
|
|
390
374
|
|
|
391
375
|
// Check for duplicate step keys
|
|
@@ -400,7 +384,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
400
384
|
// Validate each step
|
|
401
385
|
for (const step of plan.steps) {
|
|
402
386
|
if (!step.key) {
|
|
403
|
-
errors.push(
|
|
387
|
+
errors.push('Step missing key');
|
|
404
388
|
}
|
|
405
389
|
if (!step.name) {
|
|
406
390
|
warnings.push(`Step '${step.key}' missing name`);
|
|
@@ -412,9 +396,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
412
396
|
// Validate handler exists (if registry available)
|
|
413
397
|
if (this.validateHandlers && this.handlerRegistry) {
|
|
414
398
|
if (!this.handlerRegistry.has(step.handlerRef)) {
|
|
415
|
-
errors.push(
|
|
416
|
-
`Step '${step.key}' references unknown handler: ${step.handlerRef}`,
|
|
417
|
-
);
|
|
399
|
+
errors.push(`Step '${step.key}' references unknown handler: ${step.handlerRef}`);
|
|
418
400
|
}
|
|
419
401
|
}
|
|
420
402
|
}
|
|
@@ -423,7 +405,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
423
405
|
if (plan.childWorkflows) {
|
|
424
406
|
for (const child of plan.childWorkflows) {
|
|
425
407
|
if (!child.kind) {
|
|
426
|
-
errors.push(
|
|
408
|
+
errors.push('Child workflow missing kind');
|
|
427
409
|
}
|
|
428
410
|
}
|
|
429
411
|
}
|
|
@@ -439,12 +421,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
439
421
|
* Estimate resources required for a plan.
|
|
440
422
|
*/
|
|
441
423
|
estimateResources(plan: Plan): ResourceEstimate {
|
|
442
|
-
const {
|
|
443
|
-
apiCallsPerStep,
|
|
444
|
-
tokensPerStep,
|
|
445
|
-
durationPerStep,
|
|
446
|
-
apiCallsPerChild,
|
|
447
|
-
} = this.resourceEstimates;
|
|
424
|
+
const { apiCallsPerStep, tokensPerStep, durationPerStep, apiCallsPerChild } = this.resourceEstimates;
|
|
448
425
|
|
|
449
426
|
const stepCount = plan.steps.length;
|
|
450
427
|
const childCount = plan.childWorkflows?.length ?? 0;
|
|
@@ -463,7 +440,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
463
440
|
/**
|
|
464
441
|
* Convert a recipe step to a planned step.
|
|
465
442
|
*/
|
|
466
|
-
private recipeStepToPlannedStep(step: Recipe[
|
|
443
|
+
private recipeStepToPlannedStep(step: Recipe['steps'][0]): PlannedStep {
|
|
467
444
|
return {
|
|
468
445
|
key: step.key,
|
|
469
446
|
name: step.name,
|
|
@@ -483,7 +460,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
483
460
|
*/
|
|
484
461
|
private applyConstraints(
|
|
485
462
|
steps: PlannedStep[],
|
|
486
|
-
constraints: NonNullable<PlanningContext[
|
|
463
|
+
constraints: NonNullable<PlanningContext['constraints']>
|
|
487
464
|
): { steps: PlannedStep[]; modifications: PlanModification[] } {
|
|
488
465
|
const modifications: PlanModification[] = [];
|
|
489
466
|
const perStepTimeout = constraints.maxDuration
|
|
@@ -491,17 +468,15 @@ export class RuleBasedPlanner implements Planner {
|
|
|
491
468
|
: undefined;
|
|
492
469
|
|
|
493
470
|
// Apply all constraint mutations in a single pass
|
|
494
|
-
const modifiedSteps = steps.map(
|
|
471
|
+
const modifiedSteps = steps.map(step => {
|
|
495
472
|
const modified = { ...step };
|
|
496
473
|
|
|
497
|
-
if (constraints.priority ===
|
|
498
|
-
modified.timeout = modified.timeout
|
|
499
|
-
? Math.min(modified.timeout, 30000)
|
|
500
|
-
: 30000;
|
|
474
|
+
if (constraints.priority === 'speed') {
|
|
475
|
+
modified.timeout = modified.timeout ? Math.min(modified.timeout, 30000) : 30000;
|
|
501
476
|
modified.maxRetries = Math.min(modified.maxRetries ?? 3, 1);
|
|
502
477
|
}
|
|
503
478
|
|
|
504
|
-
if (constraints.priority ===
|
|
479
|
+
if (constraints.priority === 'cost') {
|
|
505
480
|
modified.maxRetries = 0;
|
|
506
481
|
}
|
|
507
482
|
|
|
@@ -515,23 +490,23 @@ export class RuleBasedPlanner implements Planner {
|
|
|
515
490
|
});
|
|
516
491
|
|
|
517
492
|
// Record what was applied
|
|
518
|
-
if (constraints.priority ===
|
|
493
|
+
if (constraints.priority === 'speed') {
|
|
519
494
|
modifications.push({
|
|
520
|
-
type:
|
|
521
|
-
value: { priority:
|
|
522
|
-
reason:
|
|
495
|
+
type: 'set_default',
|
|
496
|
+
value: { priority: 'speed' },
|
|
497
|
+
reason: 'Optimized for speed: reduced timeouts and retries',
|
|
523
498
|
});
|
|
524
499
|
}
|
|
525
|
-
if (constraints.priority ===
|
|
500
|
+
if (constraints.priority === 'cost') {
|
|
526
501
|
modifications.push({
|
|
527
|
-
type:
|
|
528
|
-
value: { priority:
|
|
529
|
-
reason:
|
|
502
|
+
type: 'set_default',
|
|
503
|
+
value: { priority: 'cost' },
|
|
504
|
+
reason: 'Optimized for cost: disabled retries',
|
|
530
505
|
});
|
|
531
506
|
}
|
|
532
507
|
if (constraints.maxDuration) {
|
|
533
508
|
modifications.push({
|
|
534
|
-
type:
|
|
509
|
+
type: 'set_default',
|
|
535
510
|
value: { maxDuration: constraints.maxDuration },
|
|
536
511
|
reason: `Applied duration constraint: ${constraints.maxDuration}ms total`,
|
|
537
512
|
});
|
|
@@ -545,23 +520,23 @@ export class RuleBasedPlanner implements Planner {
|
|
|
545
520
|
*/
|
|
546
521
|
private buildSelectionReason(
|
|
547
522
|
recipe: Recipe,
|
|
548
|
-
input: Record<string, unknown
|
|
523
|
+
input: Record<string, unknown>
|
|
549
524
|
): string {
|
|
550
525
|
const parts: string[] = [`Selected recipe: ${recipe.id}`];
|
|
551
526
|
|
|
552
527
|
if (recipe.conditions && recipe.conditions.length > 0) {
|
|
553
|
-
const conditionDescriptions = recipe.conditions.map(
|
|
528
|
+
const conditionDescriptions = recipe.conditions.map(c => {
|
|
554
529
|
const value = getNestedValue(input, c.field);
|
|
555
530
|
return `${c.field} ${c.operator} ${JSON.stringify(c.value)} (actual: ${JSON.stringify(value)})`;
|
|
556
531
|
});
|
|
557
|
-
parts.push(`Matched conditions: ${conditionDescriptions.join(
|
|
532
|
+
parts.push(`Matched conditions: ${conditionDescriptions.join(', ')}`);
|
|
558
533
|
}
|
|
559
534
|
|
|
560
535
|
if (recipe.priority !== undefined && recipe.priority > 0) {
|
|
561
536
|
parts.push(`Priority: ${recipe.priority}`);
|
|
562
537
|
}
|
|
563
538
|
|
|
564
|
-
return parts.join(
|
|
539
|
+
return parts.join('. ');
|
|
565
540
|
}
|
|
566
541
|
|
|
567
542
|
/**
|
|
@@ -570,7 +545,7 @@ export class RuleBasedPlanner implements Planner {
|
|
|
570
545
|
private buildPlanReasoning(
|
|
571
546
|
recipe: Recipe,
|
|
572
547
|
modifications: PlanModification[],
|
|
573
|
-
context?: PlanningContext
|
|
548
|
+
context?: PlanningContext
|
|
574
549
|
): string {
|
|
575
550
|
const parts: string[] = [];
|
|
576
551
|
|
|
@@ -592,10 +567,10 @@ export class RuleBasedPlanner implements Planner {
|
|
|
592
567
|
constraintList.push(`maxApiCalls=${context.constraints.maxApiCalls}`);
|
|
593
568
|
}
|
|
594
569
|
if (constraintList.length > 0) {
|
|
595
|
-
parts.push(`Constraints: ${constraintList.join(
|
|
570
|
+
parts.push(`Constraints: ${constraintList.join(', ')}`);
|
|
596
571
|
}
|
|
597
572
|
}
|
|
598
573
|
|
|
599
|
-
return parts.join(
|
|
574
|
+
return parts.join('. ') + '.';
|
|
600
575
|
}
|
|
601
576
|
}
|