@swizzy_ai/kit 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -46
- package/dist/core/wizard/bungee/builder.d.ts +13 -2
- package/dist/core/wizard/bungee/builder.d.ts.map +1 -1
- package/dist/core/wizard/bungee/executor.d.ts +1 -1
- package/dist/core/wizard/bungee/executor.d.ts.map +1 -1
- package/dist/core/wizard/bungee/types.d.ts +6 -1
- package/dist/core/wizard/bungee/types.d.ts.map +1 -1
- package/dist/core/wizard/state-manager.d.ts +15 -0
- package/dist/core/wizard/state-manager.d.ts.map +1 -0
- package/dist/core/wizard/steps/base.d.ts +5 -2
- package/dist/core/wizard/steps/base.d.ts.map +1 -1
- package/dist/core/wizard/steps/text.d.ts +1 -0
- package/dist/core/wizard/steps/text.d.ts.map +1 -1
- package/dist/core/wizard/visualization-manager.d.ts.map +1 -1
- package/dist/core/wizard/wizard.d.ts +14 -9
- package/dist/core/wizard/wizard.d.ts.map +1 -1
- package/dist/index.d.ts +39 -14
- package/dist/index.js +346 -191
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/core/wizard/context-manager.d.ts +0 -9
- package/dist/core/wizard/context-manager.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@ class Step {
|
|
|
47
47
|
this.beforeRun = config.beforeRun;
|
|
48
48
|
this.afterRun = config.afterRun;
|
|
49
49
|
this.model = config.model;
|
|
50
|
+
this.stream = config.stream ?? true; // Default to streaming enabled
|
|
50
51
|
}
|
|
51
52
|
validate(data) {
|
|
52
53
|
const result = this.schema.safeParse(data);
|
|
@@ -55,8 +56,8 @@ class Step {
|
|
|
55
56
|
}
|
|
56
57
|
return result.data;
|
|
57
58
|
}
|
|
58
|
-
getContext(workflowContext) {
|
|
59
|
-
return this.context ? this.context(workflowContext) : workflowContext;
|
|
59
|
+
async getContext(workflowContext) {
|
|
60
|
+
return this.context ? await this.context(workflowContext) : workflowContext;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -380,18 +381,27 @@ class BungeeBuilder {
|
|
|
380
381
|
/**
|
|
381
382
|
* Add a single step execution.
|
|
382
383
|
*/
|
|
383
|
-
this.add = (stepId) => {
|
|
384
|
-
this._plan.destinations.push({ type: 'step', targetId: stepId });
|
|
384
|
+
this.add = (stepId, config) => {
|
|
385
|
+
this._plan.destinations.push({ type: 'step', targetId: stepId, config });
|
|
385
386
|
return this;
|
|
386
387
|
};
|
|
387
388
|
/**
|
|
388
389
|
* Add multiple executions based on count with config function.
|
|
389
390
|
*/
|
|
390
|
-
this.batch = (stepId, count, configFn) => {
|
|
391
|
+
this.batch = (stepId, count, configFn, options) => {
|
|
391
392
|
for (let i = 0; i < count; i++) {
|
|
392
|
-
this._plan.destinations.push({ type: 'step', targetId: stepId });
|
|
393
|
+
this._plan.destinations.push({ type: 'step', targetId: stepId, config: configFn(i) });
|
|
394
|
+
}
|
|
395
|
+
// Apply batch-specific options
|
|
396
|
+
if (options?.optimistic !== undefined) {
|
|
397
|
+
this._plan.optimistic = options.optimistic;
|
|
398
|
+
}
|
|
399
|
+
if (options?.returnToAnchor !== undefined) {
|
|
400
|
+
this._plan.returnToAnchor = options.returnToAnchor;
|
|
401
|
+
}
|
|
402
|
+
if (options?.failWizardOnFailure !== undefined) {
|
|
403
|
+
this._plan.failWizardOnFailure = options.failWizardOnFailure;
|
|
393
404
|
}
|
|
394
|
-
this._plan.configFn = configFn;
|
|
395
405
|
return this;
|
|
396
406
|
};
|
|
397
407
|
/**
|
|
@@ -401,6 +411,22 @@ class BungeeBuilder {
|
|
|
401
411
|
if (options.concurrency !== undefined) {
|
|
402
412
|
this._plan.concurrency = options.concurrency;
|
|
403
413
|
}
|
|
414
|
+
if (options.optimistic !== undefined) {
|
|
415
|
+
this._plan.optimistic = options.optimistic;
|
|
416
|
+
}
|
|
417
|
+
if (options.returnToAnchor !== undefined) {
|
|
418
|
+
this._plan.returnToAnchor = options.returnToAnchor;
|
|
419
|
+
}
|
|
420
|
+
if (options.failWizardOnFailure !== undefined) {
|
|
421
|
+
this._plan.failWizardOnFailure = options.failWizardOnFailure;
|
|
422
|
+
}
|
|
423
|
+
return this;
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* Set completion callback.
|
|
427
|
+
*/
|
|
428
|
+
this.onComplete = (callback) => {
|
|
429
|
+
this._plan.onComplete = callback;
|
|
404
430
|
return this;
|
|
405
431
|
};
|
|
406
432
|
/**
|
|
@@ -428,8 +454,8 @@ class SchemaUtils {
|
|
|
428
454
|
const shape = schema._def.shape();
|
|
429
455
|
const fields = Object.entries(shape).map(([key, fieldSchema]) => {
|
|
430
456
|
const type = this.getSchemaType(fieldSchema);
|
|
431
|
-
|
|
432
|
-
return `${key}: ${type}
|
|
457
|
+
this.getXmlExample(key, type);
|
|
458
|
+
return `${key}: ${type}`;
|
|
433
459
|
});
|
|
434
460
|
description = `Object with fields:\n${fields.join('\n')}`;
|
|
435
461
|
}
|
|
@@ -479,8 +505,8 @@ class SchemaUtils {
|
|
|
479
505
|
}
|
|
480
506
|
static getXmlExample(key, type) {
|
|
481
507
|
switch (type) {
|
|
482
|
-
case 'string': return `<${key} tag-category="wizard" type="string">
|
|
483
|
-
case 'number': return `<${key} tag-category="wizard" type="number">
|
|
508
|
+
case 'string': return `<${key} tag-category="wizard" type="string">[your text should be here]`;
|
|
509
|
+
case 'number': return `<${key} tag-category="wizard" type="number">[number should be here]`;
|
|
484
510
|
case 'boolean': return `<${key} tag-category="wizard" type="boolean">true`;
|
|
485
511
|
case 'array': return `<${key} tag-category="wizard" type="array">["item1", "item2"]`;
|
|
486
512
|
default:
|
|
@@ -488,7 +514,7 @@ class SchemaUtils {
|
|
|
488
514
|
const values = type.split(': ')[1].split(', ');
|
|
489
515
|
return `<${key} tag-category="wizard" type="string">${values[0]}`;
|
|
490
516
|
}
|
|
491
|
-
return `<${key} tag-category="wizard" type="object"><subfield type="string">value</subfield>`;
|
|
517
|
+
return `<${key} tag-category="wizard" type="object"><subfield type="string">[text value should be here]</subfield>`;
|
|
492
518
|
}
|
|
493
519
|
}
|
|
494
520
|
static objectToXml(obj, rootName = 'context') {
|
|
@@ -533,6 +559,20 @@ class VisualizationManager {
|
|
|
533
559
|
this.connectedClients = new Set();
|
|
534
560
|
this.maxWebSocketConnections = 10;
|
|
535
561
|
this.wsIntervals = new WeakMap();
|
|
562
|
+
// Listen for state update events
|
|
563
|
+
this.wizard.on('state:update', (data) => {
|
|
564
|
+
this.sendToClients({
|
|
565
|
+
type: 'state_update',
|
|
566
|
+
...data
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
// Listen for wizard stop events
|
|
570
|
+
this.wizard.on('wizard:stop', (data) => {
|
|
571
|
+
this.sendToClients({
|
|
572
|
+
type: 'wizard_stop',
|
|
573
|
+
...data
|
|
574
|
+
});
|
|
575
|
+
});
|
|
536
576
|
} // Wizard instance for callbacks
|
|
537
577
|
getStepsInfo() {
|
|
538
578
|
return this.wizard.steps.map((item) => {
|
|
@@ -696,6 +736,12 @@ class VisualizationManager {
|
|
|
696
736
|
this.wizard.isPaused = false;
|
|
697
737
|
this.wizard.isStepMode = false;
|
|
698
738
|
this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, isStepMode: false } });
|
|
739
|
+
// Emit wizard:stop event for manual stops
|
|
740
|
+
this.wizard.events.emit('wizard:stop', {
|
|
741
|
+
reason: 'manual_stop',
|
|
742
|
+
finalState: this.wizard.stateManager.getState(),
|
|
743
|
+
timestamp: Date.now()
|
|
744
|
+
});
|
|
699
745
|
break;
|
|
700
746
|
case 'replay':
|
|
701
747
|
console.log('🔄 Replaying wizard - resetting state');
|
|
@@ -803,24 +849,33 @@ class BungeeExecutor {
|
|
|
803
849
|
// Launch worker
|
|
804
850
|
const workerPromise = this.launchBungeeWorker(plan, i);
|
|
805
851
|
activeWorkers.add(workerPromise);
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
852
|
+
}
|
|
853
|
+
// Wait for all workers to complete unless optimistic
|
|
854
|
+
if (!plan.optimistic) {
|
|
855
|
+
try {
|
|
856
|
+
await Promise.all(activeWorkers);
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
if (plan.failWizardOnFailure !== false) { // Default true
|
|
860
|
+
throw error; // Re-throw to stop wizard
|
|
814
861
|
}
|
|
862
|
+
// If failWizardOnFailure is false, just log and continue
|
|
863
|
+
console.error(`Bungee plan ${plan.id} had failures but continuing:`, error);
|
|
815
864
|
}
|
|
816
865
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
866
|
+
if (plan.onComplete) {
|
|
867
|
+
return plan.onComplete(this.wizard);
|
|
868
|
+
}
|
|
869
|
+
if (plan.returnToAnchor !== false) { // Default true
|
|
870
|
+
console.log(`✅ Bungee plan ${plan.id} completed, returning to anchor ${plan.anchorId}`);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
console.log(`✅ Bungee plan ${plan.id} completed, proceeding to next step`);
|
|
874
|
+
}
|
|
820
875
|
}
|
|
821
876
|
async launchBungeeWorker(plan, index) {
|
|
822
877
|
const destination = plan.destinations[index];
|
|
823
|
-
const telescope =
|
|
878
|
+
const telescope = destination.config || {};
|
|
824
879
|
const workerId = `${plan.id}_${destination.targetId}_${index}_${Date.now()}`;
|
|
825
880
|
const telescopeContext = this.createTelescopeContext(this.wizard.workflowContext, telescope);
|
|
826
881
|
const promise = this.executeWorkerStep(destination.targetId, telescopeContext);
|
|
@@ -837,10 +892,6 @@ class BungeeExecutor {
|
|
|
837
892
|
try {
|
|
838
893
|
await promise;
|
|
839
894
|
}
|
|
840
|
-
catch (error) {
|
|
841
|
-
console.error(`Bungee worker ${workerId} failed:`, error);
|
|
842
|
-
this.wizard.workflowContext[`${workerId}_error`] = error.message;
|
|
843
|
-
}
|
|
844
895
|
finally {
|
|
845
896
|
// Clean up
|
|
846
897
|
const planWorkers = this.bungeeWorkers.get(plan.id);
|
|
@@ -848,8 +899,10 @@ class BungeeExecutor {
|
|
|
848
899
|
planWorkers.delete(workerId);
|
|
849
900
|
if (planWorkers.size === 0) {
|
|
850
901
|
this.bungeeWorkers.delete(plan.id);
|
|
851
|
-
// Trigger reentry to anchor
|
|
852
|
-
|
|
902
|
+
// Trigger reentry to anchor if configured
|
|
903
|
+
if (plan.returnToAnchor !== false) {
|
|
904
|
+
this.pendingReentry.add(plan.anchorId);
|
|
905
|
+
}
|
|
853
906
|
}
|
|
854
907
|
}
|
|
855
908
|
}
|
|
@@ -872,9 +925,7 @@ class BungeeExecutor {
|
|
|
872
925
|
return await step.update(stepData, telescopeContext, actions);
|
|
873
926
|
}
|
|
874
927
|
mergeWorkerResults(updates, telescope) {
|
|
875
|
-
|
|
876
|
-
this.wizard.workflowContext[key] = value;
|
|
877
|
-
});
|
|
928
|
+
this.wizard.updateContext(updates);
|
|
878
929
|
}
|
|
879
930
|
async retriggerAnchor(anchorId) {
|
|
880
931
|
const anchorStep = this.wizard.findStep(anchorId);
|
|
@@ -993,24 +1044,49 @@ class UsageTracker {
|
|
|
993
1044
|
}
|
|
994
1045
|
}
|
|
995
1046
|
|
|
996
|
-
class
|
|
997
|
-
constructor() {
|
|
998
|
-
this.
|
|
1047
|
+
class StateManager {
|
|
1048
|
+
constructor(events) {
|
|
1049
|
+
this.events = events;
|
|
1050
|
+
this.state = {};
|
|
1051
|
+
}
|
|
1052
|
+
setState(updates) {
|
|
1053
|
+
let newUpdates;
|
|
1054
|
+
if (typeof updates === 'function') {
|
|
1055
|
+
// Higher-order function pattern like React setState
|
|
1056
|
+
newUpdates = updates(this.state);
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
// Object pattern
|
|
1060
|
+
newUpdates = updates;
|
|
1061
|
+
}
|
|
1062
|
+
const previousState = { ...this.state };
|
|
1063
|
+
this.state = { ...this.state, ...newUpdates };
|
|
1064
|
+
// Emit state update event
|
|
1065
|
+
this.events.emit('state:update', {
|
|
1066
|
+
previousState,
|
|
1067
|
+
newState: this.state,
|
|
1068
|
+
updates: newUpdates,
|
|
1069
|
+
timestamp: Date.now()
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
getState() {
|
|
1073
|
+
return this.state;
|
|
999
1074
|
}
|
|
1075
|
+
// Legacy methods for backward compatibility
|
|
1000
1076
|
setContext(context) {
|
|
1001
|
-
this.
|
|
1077
|
+
this.setState(context);
|
|
1002
1078
|
}
|
|
1003
1079
|
getContext() {
|
|
1004
|
-
return this.
|
|
1080
|
+
return this.getState();
|
|
1005
1081
|
}
|
|
1006
1082
|
updateContext(updates) {
|
|
1007
|
-
this.
|
|
1083
|
+
this.setState(updates);
|
|
1008
1084
|
}
|
|
1009
1085
|
getWorkflowContext() {
|
|
1010
|
-
return this.
|
|
1086
|
+
return this.getState();
|
|
1011
1087
|
}
|
|
1012
|
-
setWorkflowContext(
|
|
1013
|
-
this.
|
|
1088
|
+
setWorkflowContext(state) {
|
|
1089
|
+
this.state = state;
|
|
1014
1090
|
}
|
|
1015
1091
|
}
|
|
1016
1092
|
|
|
@@ -1042,10 +1118,10 @@ class Wizard {
|
|
|
1042
1118
|
}
|
|
1043
1119
|
// Getters for manager methods
|
|
1044
1120
|
get workflowContext() {
|
|
1045
|
-
return this.
|
|
1121
|
+
return this.stateManager.getContext();
|
|
1046
1122
|
}
|
|
1047
1123
|
set workflowContext(value) {
|
|
1048
|
-
this.
|
|
1124
|
+
this.stateManager.setWorkflowContext(value);
|
|
1049
1125
|
}
|
|
1050
1126
|
get log() {
|
|
1051
1127
|
return this.isLoggingEnabled ? this.logger.log.bind(this.logger) : () => { };
|
|
@@ -1065,7 +1141,7 @@ class Wizard {
|
|
|
1065
1141
|
this.isStepMode = false;
|
|
1066
1142
|
this.skipStartWait = false;
|
|
1067
1143
|
this.debouncedSendContextUpdate = this.debounce(() => {
|
|
1068
|
-
this.visualizationManager.sendContextUpdate(this.
|
|
1144
|
+
this.visualizationManager.sendContextUpdate(this.stateManager.getContext());
|
|
1069
1145
|
}, 100);
|
|
1070
1146
|
this.id = config.id;
|
|
1071
1147
|
const registry = new ProviderRegistry();
|
|
@@ -1076,13 +1152,13 @@ class Wizard {
|
|
|
1076
1152
|
this.isLoggingEnabled = config.logging ?? (process.env.NODE_ENV === 'development' || !process.env.NODE_ENV);
|
|
1077
1153
|
// Initialize managers
|
|
1078
1154
|
this.logger = new Logger(this.id);
|
|
1079
|
-
this.
|
|
1155
|
+
this.events = new EventEmitter();
|
|
1156
|
+
this.stateManager = new StateManager(this.events);
|
|
1080
1157
|
this.visualizationManager = new VisualizationManager(this);
|
|
1081
1158
|
this.usageTracker = new UsageTracker(config.onUsage, (totalTokens, rate) => {
|
|
1082
1159
|
this.visualizationManager.sendTokenUpdate(totalTokens, rate);
|
|
1083
1160
|
});
|
|
1084
1161
|
this.bungeeExecutor = new BungeeExecutor(this);
|
|
1085
|
-
this.events = new EventEmitter();
|
|
1086
1162
|
}
|
|
1087
1163
|
addStep(config) {
|
|
1088
1164
|
const step = new Step(config);
|
|
@@ -1126,10 +1202,12 @@ class Wizard {
|
|
|
1126
1202
|
return this;
|
|
1127
1203
|
}
|
|
1128
1204
|
clearStepError(stepId) {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1205
|
+
this.stateManager.setState((prevState) => {
|
|
1206
|
+
const newState = { ...prevState };
|
|
1207
|
+
delete newState[`${stepId}_error`];
|
|
1208
|
+
delete newState[`${stepId}_retryCount`];
|
|
1209
|
+
return newState;
|
|
1210
|
+
});
|
|
1133
1211
|
}
|
|
1134
1212
|
isStringSignal(signal) {
|
|
1135
1213
|
return typeof signal === 'string';
|
|
@@ -1152,11 +1230,7 @@ class Wizard {
|
|
|
1152
1230
|
this.currentStepIndex++;
|
|
1153
1231
|
return true;
|
|
1154
1232
|
default:
|
|
1155
|
-
if (this.
|
|
1156
|
-
await this.bungeeExecutor.executeBungeePlan(signal.plan);
|
|
1157
|
-
return true;
|
|
1158
|
-
}
|
|
1159
|
-
else if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
|
|
1233
|
+
if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
|
|
1160
1234
|
const targetStepId = signal.substring(5);
|
|
1161
1235
|
const targetIndex = this.findStepIndex(targetStepId);
|
|
1162
1236
|
if (targetIndex !== -1) {
|
|
@@ -1167,6 +1241,28 @@ class Wizard {
|
|
|
1167
1241
|
}
|
|
1168
1242
|
return true;
|
|
1169
1243
|
}
|
|
1244
|
+
else if (this.isBungeeJumpSignal(signal)) {
|
|
1245
|
+
try {
|
|
1246
|
+
const result = await this.bungeeExecutor.executeBungeePlan(signal.plan);
|
|
1247
|
+
if (result) {
|
|
1248
|
+
return await this.handleFlowControlSignal(result);
|
|
1249
|
+
}
|
|
1250
|
+
if (signal.plan.returnToAnchor === false) {
|
|
1251
|
+
this.currentStepIndex++; // Proceed to next step when not returning to anchor
|
|
1252
|
+
}
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
catch (error) {
|
|
1256
|
+
console.error('Bungee plan failed:', error);
|
|
1257
|
+
this.workflowContext[`bungee_error`] = error.message;
|
|
1258
|
+
if (signal.plan.failWizardOnFailure !== false) { // Default true
|
|
1259
|
+
this.isRunning = false; // Stop the wizard on bungee failure
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
// If failWizardOnFailure is false, continue
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1170
1266
|
}
|
|
1171
1267
|
return true;
|
|
1172
1268
|
}
|
|
@@ -1261,10 +1357,17 @@ class Wizard {
|
|
|
1261
1357
|
totalSteps: this.steps.length,
|
|
1262
1358
|
timestamp: endTime
|
|
1263
1359
|
});
|
|
1360
|
+
// Emit wizard:stop event
|
|
1361
|
+
this.events.emit('wizard:stop', {
|
|
1362
|
+
reason: 'completed',
|
|
1363
|
+
finalState: this.stateManager.getState(),
|
|
1364
|
+
timestamp: endTime
|
|
1365
|
+
});
|
|
1264
1366
|
}
|
|
1265
1367
|
createBaseActions() {
|
|
1266
1368
|
return {
|
|
1267
1369
|
updateContext: (updates) => this.updateContext(updates),
|
|
1370
|
+
setState: (updates) => this.stateManager.setState(updates),
|
|
1268
1371
|
llmClient: this.llmClient,
|
|
1269
1372
|
goto: (stepId) => this.goto(stepId),
|
|
1270
1373
|
next: () => this.next(),
|
|
@@ -1276,6 +1379,7 @@ class Wizard {
|
|
|
1276
1379
|
createWizardActions(anchorStepId = '') {
|
|
1277
1380
|
return {
|
|
1278
1381
|
...this.createBaseActions(),
|
|
1382
|
+
setState: (updates) => this.stateManager.setState(updates),
|
|
1279
1383
|
bungee: {
|
|
1280
1384
|
init: () => new BungeeBuilder(anchorStepId)
|
|
1281
1385
|
}
|
|
@@ -1286,6 +1390,9 @@ class Wizard {
|
|
|
1286
1390
|
updateContext: (updates) => {
|
|
1287
1391
|
this.bungeeExecutor.mergeWorkerResults(updates, telescope);
|
|
1288
1392
|
},
|
|
1393
|
+
setState: (updates) => {
|
|
1394
|
+
this.bungeeExecutor.mergeWorkerResults(updates, telescope);
|
|
1395
|
+
},
|
|
1289
1396
|
llmClient: this.llmClient,
|
|
1290
1397
|
goto: () => Wizard.STOP,
|
|
1291
1398
|
next: () => Wizard.STOP,
|
|
@@ -1324,7 +1431,7 @@ class Wizard {
|
|
|
1324
1431
|
instruction: step.instruction,
|
|
1325
1432
|
timestamp: stepStartTime
|
|
1326
1433
|
});
|
|
1327
|
-
const stepContext = step.getContext(this.
|
|
1434
|
+
const stepContext = await step.getContext(this.stateManager.getContext());
|
|
1328
1435
|
let processedInstruction = step.instruction;
|
|
1329
1436
|
if (step.contextType === 'template' || step.contextType === 'both') {
|
|
1330
1437
|
processedInstruction = this.applyTemplate(step.instruction, stepContext);
|
|
@@ -1519,124 +1626,98 @@ class Wizard {
|
|
|
1519
1626
|
* Uses streaming for regular steps to provide real-time parsing and UI updates.
|
|
1520
1627
|
* TextStep and ComputeStep use different approaches (non-streaming).
|
|
1521
1628
|
*/
|
|
1629
|
+
/**
|
|
1630
|
+
* Generates data for a wizard step by calling the LLM.
|
|
1631
|
+
* CLEANED VERSION: Separates formatting rules from logic to prevent hallucination.
|
|
1632
|
+
*/
|
|
1522
1633
|
async generateStepData(step, stepContext) {
|
|
1523
1634
|
const systemContext = this.systemPrompt ? `${this.systemPrompt}\n\n` : '';
|
|
1635
|
+
// Build context strings
|
|
1524
1636
|
const errorContext = this.workflowContext[`${step.id}_error`] ?
|
|
1525
|
-
`\n\
|
|
1637
|
+
`\n\n!!! PREVIOUS ERROR (Attempt ${this.workflowContext[`${step.id}_retryCount`] || 1}) !!!\nThe previous output caused this error: ${this.workflowContext[`${step.id}_error`]}\nYOU MUST FIX THIS.` : '';
|
|
1526
1638
|
let processedInstruction = step.instruction;
|
|
1527
1639
|
if (step.contextType === 'template' || step.contextType === 'both') {
|
|
1528
1640
|
processedInstruction = this.applyTemplate(step.instruction, stepContext);
|
|
1529
1641
|
}
|
|
1530
|
-
this.log(() => `Processed instruction for step ${step.id}: ${processedInstruction}`);
|
|
1531
1642
|
let contextSection = '';
|
|
1532
1643
|
if (step.contextType === 'xml' || step.contextType === 'both' || !step.contextType) {
|
|
1533
|
-
contextSection = `\n\
|
|
1644
|
+
contextSection = `\n\n### CURRENT CONTEXT ###\n${this.objectToXml(stepContext)}`;
|
|
1534
1645
|
}
|
|
1535
|
-
|
|
1646
|
+
// --- Text Step Handling ---
|
|
1536
1647
|
if (step instanceof TextStep) {
|
|
1537
|
-
const prompt = `${systemContext}
|
|
1648
|
+
const prompt = `${systemContext}
|
|
1649
|
+
TASK: Generate content for step "${step.id}".
|
|
1538
1650
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1651
|
+
INSTRUCTION:
|
|
1652
|
+
${processedInstruction}
|
|
1653
|
+
${contextSection}
|
|
1654
|
+
${errorContext}
|
|
1541
1655
|
|
|
1656
|
+
OUTPUT:
|
|
1542
1657
|
Generate the text response now.`;
|
|
1543
|
-
this.log(() => `Full prompt for step ${step.id}: ${prompt}`);
|
|
1544
1658
|
let fullText = '';
|
|
1545
|
-
|
|
1659
|
+
console.log("full prompt", prompt);
|
|
1660
|
+
const result = await this.llmClient.complete({
|
|
1546
1661
|
prompt,
|
|
1547
1662
|
model: step.model,
|
|
1548
1663
|
maxTokens: 1000,
|
|
1549
1664
|
temperature: 0.3,
|
|
1550
|
-
stream:
|
|
1665
|
+
stream: step.stream,
|
|
1551
1666
|
onChunk: (chunk) => {
|
|
1552
1667
|
fullText += chunk;
|
|
1553
|
-
|
|
1554
|
-
this.events.emit('step:chunk', {
|
|
1555
|
-
stepId: step.id,
|
|
1556
|
-
chunk: chunk,
|
|
1557
|
-
timestamp: Date.now()
|
|
1558
|
-
});
|
|
1668
|
+
this.events.emit('step:chunk', { stepId: step.id, chunk, timestamp: Date.now() });
|
|
1559
1669
|
},
|
|
1560
1670
|
onUsage: this.usageTracker.updateUsage.bind(this.usageTracker)
|
|
1561
1671
|
});
|
|
1562
|
-
|
|
1563
|
-
|
|
1672
|
+
if (result.text) {
|
|
1673
|
+
fullText = result.text;
|
|
1674
|
+
}
|
|
1564
1675
|
return fullText;
|
|
1565
1676
|
}
|
|
1566
|
-
//
|
|
1567
|
-
// This allows real-time processing of LLM responses as they arrive
|
|
1677
|
+
// --- Regular XML Step Handling ---
|
|
1568
1678
|
const parser = this.createStreamingXmlParser();
|
|
1569
1679
|
let latestResult = {};
|
|
1570
1680
|
const schemaDescription = SchemaUtils.describeSchema(step.schema, step.id);
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
SCHEMA REQUIREMENTS:
|
|
1577
|
-
${schemaDescription}
|
|
1578
|
-
|
|
1579
|
-
REQUIRED OUTPUT FORMAT:
|
|
1580
|
-
Return a plain XML response with a root <response> tag.
|
|
1581
|
-
CRITICAL: Every field MUST include tag-category="wizard" attribute. This is MANDATORY.
|
|
1582
|
-
Every field MUST also include a type attribute (e.g., type="string", type="number", type="boolean", type="array").
|
|
1681
|
+
// CLEANER PROMPT STRUCTURE
|
|
1682
|
+
const prompt = `${systemContext}
|
|
1683
|
+
=== GOAL ===
|
|
1684
|
+
You are an intelligent agent executing step: "${step.id}".
|
|
1685
|
+
Your task is to generate data that satisfies the INSTRUCTION below based on the CONTEXT.
|
|
1583
1686
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
- NO line breaks inside arrays
|
|
1589
|
-
- Example: <items tag-category="wizard" type="array">["apple", "banana", "orange"]
|
|
1687
|
+
=== INSTRUCTION ===
|
|
1688
|
+
${processedInstruction}
|
|
1689
|
+
${contextSection}
|
|
1690
|
+
${errorContext}
|
|
1590
1691
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
- Only fields marked with tag-category="wizard" will be parsed
|
|
1692
|
+
=== RESPONSE FORMAT ===
|
|
1693
|
+
You must output a VALID XML object inside a <response> tag.
|
|
1694
|
+
1. Every field must have: tag-category="wizard" and a type attribute (string, number, boolean, array).
|
|
1695
|
+
2. Arrays must be single-line JSON: <tags tag-category="wizard" type="array">["a", "b"]
|
|
1596
1696
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
<name tag-category="wizard" type="string">John Smith
|
|
1600
|
-
<age tag-category="wizard" type="number">25
|
|
1601
|
-
<tags tag-category="wizard" type="array">["a", "b", "c"]
|
|
1602
|
-
</response>
|
|
1697
|
+
=== SCHEMA DEFINITION ===
|
|
1698
|
+
${schemaDescription}
|
|
1603
1699
|
|
|
1604
|
-
|
|
1700
|
+
*** CRITICAL RULES ***
|
|
1701
|
+
1. Do NOT copy values from the schema definition or examples above.
|
|
1702
|
+
2. Generate NEW values based strictly on the "INSTRUCTION" and "CURRENT CONTEXT".
|
|
1703
|
+
3. If the instruction implies a selection (like an ID), ensure the ID exists in the Context.
|
|
1605
1704
|
|
|
1606
|
-
Generate the XML response now.`;
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
await this.llmClient.complete({
|
|
1705
|
+
Generate the XML <response> now.`;
|
|
1706
|
+
// console.log(prompt); // Uncomment to debug the cleaner prompt
|
|
1707
|
+
step.stream !== false;
|
|
1708
|
+
const result = await this.llmClient.complete({
|
|
1611
1709
|
prompt,
|
|
1612
1710
|
model: step.model,
|
|
1613
1711
|
maxTokens: 1000,
|
|
1614
|
-
temperature: 0.3,
|
|
1615
|
-
stream:
|
|
1712
|
+
temperature: 0.3, // Lower temp for precision
|
|
1713
|
+
stream: step.stream,
|
|
1616
1714
|
onChunk: (chunk) => {
|
|
1617
|
-
|
|
1618
|
-
this.events.emit('step:chunk', {
|
|
1619
|
-
stepId: step.id,
|
|
1620
|
-
chunk: chunk,
|
|
1621
|
-
timestamp: Date.now()
|
|
1622
|
-
});
|
|
1623
|
-
// Process each incoming chunk through the streaming parser
|
|
1715
|
+
this.events.emit('step:chunk', { stepId: step.id, chunk, timestamp: Date.now() });
|
|
1624
1716
|
const parseResult = parser.push(chunk);
|
|
1625
1717
|
if (parseResult && !parseResult.done) {
|
|
1626
1718
|
latestResult = parseResult.result;
|
|
1627
|
-
|
|
1628
|
-
this.
|
|
1629
|
-
stepId: step.id,
|
|
1630
|
-
data: latestResult,
|
|
1631
|
-
timestamp: Date.now()
|
|
1632
|
-
});
|
|
1633
|
-
// Stream partial results out to connected clients via WebSocket
|
|
1634
|
-
// This broadcasts real-time updates to UI and external consumers
|
|
1635
|
-
this.visualizationManager.sendStepUpdate({
|
|
1636
|
-
stepId: step.id,
|
|
1637
|
-
status: 'streaming',
|
|
1638
|
-
data: latestResult
|
|
1639
|
-
});
|
|
1719
|
+
this.events.emit('step:streaming', { stepId: step.id, data: latestResult, timestamp: Date.now() });
|
|
1720
|
+
this.visualizationManager.sendStepUpdate({ stepId: step.id, status: 'streaming', data: latestResult });
|
|
1640
1721
|
}
|
|
1641
1722
|
else if (parseResult?.done) {
|
|
1642
1723
|
latestResult = parseResult.result;
|
|
@@ -1644,20 +1725,19 @@ Generate the XML response now.`;
|
|
|
1644
1725
|
},
|
|
1645
1726
|
onUsage: this.usageTracker.updateUsage.bind(this.usageTracker)
|
|
1646
1727
|
});
|
|
1647
|
-
|
|
1648
|
-
|
|
1728
|
+
if (result.text) {
|
|
1729
|
+
latestResult = result.text;
|
|
1730
|
+
}
|
|
1649
1731
|
try {
|
|
1650
1732
|
return step.validate(latestResult);
|
|
1651
1733
|
}
|
|
1652
1734
|
catch (validationError) {
|
|
1653
|
-
|
|
1735
|
+
// Logic for repair remains the same...
|
|
1654
1736
|
try {
|
|
1655
1737
|
const repairedData = await this.repairSchemaData(latestResult, step.schema, validationError.message, step.id);
|
|
1656
|
-
this.log(() => `Repaired data for step ${step.id}: ${JSON.stringify(repairedData)}`);
|
|
1657
1738
|
return step.validate(repairedData);
|
|
1658
1739
|
}
|
|
1659
1740
|
catch (repairError) {
|
|
1660
|
-
this.log(() => `Repair failed for step ${step.id}: ${repairError.message}`);
|
|
1661
1741
|
return { __validationFailed: true, error: validationError.message };
|
|
1662
1742
|
}
|
|
1663
1743
|
}
|
|
@@ -1700,16 +1780,17 @@ Fix the data to match the schema and generate the XML response now.`;
|
|
|
1700
1780
|
return repairedJsonData;
|
|
1701
1781
|
}
|
|
1702
1782
|
/**
|
|
1703
|
-
* Creates
|
|
1783
|
+
* Creates an improved streaming XML parser for incremental processing of LLM responses.
|
|
1704
1784
|
*
|
|
1705
|
-
* This parser
|
|
1706
|
-
* fields marked with tag-category="wizard"
|
|
1785
|
+
* This parser is designed to handle partial chunks robustly and provides better error recovery.
|
|
1786
|
+
* It processes XML chunks as they arrive, extracting fields marked with tag-category="wizard".
|
|
1707
1787
|
*
|
|
1708
|
-
* Key
|
|
1709
|
-
* -
|
|
1710
|
-
* -
|
|
1711
|
-
* -
|
|
1712
|
-
* -
|
|
1788
|
+
* Key improvements:
|
|
1789
|
+
* - Better partial tag handling
|
|
1790
|
+
* - More robust regex matching
|
|
1791
|
+
* - Improved buffer management
|
|
1792
|
+
* - Better error recovery for malformed chunks
|
|
1793
|
+
* - State machine approach for parsing
|
|
1713
1794
|
*
|
|
1714
1795
|
* @returns An object with a push method that accepts text chunks and returns parse results
|
|
1715
1796
|
*/
|
|
@@ -1718,57 +1799,131 @@ Fix the data to match the schema and generate the XML response now.`;
|
|
|
1718
1799
|
let inResponse = false; // Tracks if we've entered the <response> tag
|
|
1719
1800
|
const result = {}; // The final parsed JSON object
|
|
1720
1801
|
let currentField = null; // Currently parsing field
|
|
1802
|
+
let parseErrors = 0; // Track consecutive parse errors
|
|
1721
1803
|
return {
|
|
1722
1804
|
push: (chunk) => {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
inResponse
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (currentField) {
|
|
1736
|
-
result[currentField.name] = this.parseValueByType(currentField.content.trim(), currentField.type);
|
|
1805
|
+
try {
|
|
1806
|
+
buffer += chunk;
|
|
1807
|
+
// Wait for <response> tag to start parsing
|
|
1808
|
+
if (!inResponse) {
|
|
1809
|
+
const responseStart = buffer.indexOf('<response>');
|
|
1810
|
+
if (responseStart !== -1) {
|
|
1811
|
+
inResponse = true;
|
|
1812
|
+
buffer = buffer.slice(responseStart + 10); // Remove <response> from buffer
|
|
1813
|
+
}
|
|
1814
|
+
else {
|
|
1815
|
+
return null; // Still waiting for response start
|
|
1816
|
+
}
|
|
1737
1817
|
}
|
|
1738
|
-
//
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1818
|
+
// Process buffer for wizard tags
|
|
1819
|
+
let processedSomething = false;
|
|
1820
|
+
// Continue processing while we have data
|
|
1821
|
+
while (buffer.length > 0) {
|
|
1822
|
+
// If we have a current field, try to accumulate content
|
|
1823
|
+
if (currentField) {
|
|
1824
|
+
// Look for the next wizard tag or end of response
|
|
1825
|
+
const nextWizardTag = buffer.match(/<\w+\s+[^>]*tag-category=["']wizard["'][^>]*>/);
|
|
1826
|
+
const responseEnd = buffer.indexOf('</response>');
|
|
1827
|
+
if (nextWizardTag && nextWizardTag.index !== undefined) {
|
|
1828
|
+
// Found next tag, finalize current field
|
|
1829
|
+
const contentEnd = nextWizardTag.index;
|
|
1830
|
+
currentField.content += buffer.slice(0, contentEnd);
|
|
1831
|
+
result[currentField.name] = this.parseValueByType(currentField.content.trim(), currentField.type);
|
|
1832
|
+
currentField = null;
|
|
1833
|
+
buffer = buffer.slice(contentEnd);
|
|
1834
|
+
processedSomething = true;
|
|
1835
|
+
}
|
|
1836
|
+
else if (responseEnd !== -1) {
|
|
1837
|
+
// End of response, finalize current field
|
|
1838
|
+
currentField.content += buffer.slice(0, responseEnd);
|
|
1839
|
+
result[currentField.name] = this.parseValueByType(currentField.content.trim(), currentField.type);
|
|
1840
|
+
return { done: true, result }; // Parsing complete
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
// No complete field yet, but we might have a partial tag at the end
|
|
1844
|
+
// Check if buffer ends with a partial wizard tag
|
|
1845
|
+
const partialTagMatch = buffer.match(/<\w+\s+[^>]*tag-category=["']wizard["'][^>]*$/);
|
|
1846
|
+
if (partialTagMatch) {
|
|
1847
|
+
// Buffer ends with partial tag, keep it for next chunk
|
|
1848
|
+
break;
|
|
1849
|
+
}
|
|
1850
|
+
else {
|
|
1851
|
+
// Safe to accumulate entire buffer
|
|
1852
|
+
currentField.content += buffer;
|
|
1853
|
+
buffer = '';
|
|
1854
|
+
processedSomething = true;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
else {
|
|
1859
|
+
// No current field, look for a new wizard tag
|
|
1860
|
+
const tagMatch = buffer.match(Wizard.WIZARD_TAG_PATTERN);
|
|
1861
|
+
if (tagMatch && tagMatch.index === 0) {
|
|
1862
|
+
// Tag starts at beginning of buffer
|
|
1863
|
+
const typeMatch = tagMatch[2].match(/type=["']([^"']+)["']/);
|
|
1864
|
+
currentField = {
|
|
1865
|
+
name: tagMatch[1],
|
|
1866
|
+
type: typeMatch?.[1]?.toLowerCase() || 'string',
|
|
1867
|
+
content: ''
|
|
1868
|
+
};
|
|
1869
|
+
buffer = buffer.slice(tagMatch[0].length);
|
|
1870
|
+
processedSomething = true;
|
|
1871
|
+
}
|
|
1872
|
+
else if (tagMatch && tagMatch.index !== undefined && tagMatch.index > 0) {
|
|
1873
|
+
// Tag exists but not at start - might be partial
|
|
1874
|
+
const partialTagMatch = buffer.match(/<\w+\s+[^>]*tag-category=["']wizard["'][^>]*$/);
|
|
1875
|
+
if (partialTagMatch) {
|
|
1876
|
+
// Buffer ends with partial tag, wait for more data
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1879
|
+
else {
|
|
1880
|
+
// Tag is in middle, process up to it
|
|
1881
|
+
// This shouldn't happen in well-formed XML, but handle gracefully
|
|
1882
|
+
buffer = buffer.slice(tagMatch.index);
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
// No wizard tag found
|
|
1888
|
+
if (buffer.includes('</response>')) {
|
|
1889
|
+
// End of response without finalizing a field
|
|
1890
|
+
return { done: true, result };
|
|
1891
|
+
}
|
|
1892
|
+
// Check for partial tag at end
|
|
1893
|
+
const partialTagMatch = buffer.match(/<\w+\s+[^>]*tag-category=["']wizard["'][^>]*$/);
|
|
1894
|
+
if (partialTagMatch) {
|
|
1895
|
+
break; // Wait for more data
|
|
1896
|
+
}
|
|
1897
|
+
// No partial tag, buffer might contain non-wizard content
|
|
1898
|
+
// This is unusual but we'll keep it for now
|
|
1899
|
+
break;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1756
1902
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
currentField.content += buffer.slice(0, endIndex);
|
|
1761
|
-
result[currentField.name] = this.parseValueByType(currentField.content.trim(), currentField.type);
|
|
1762
|
-
return { done: true, result }; // Parsing complete
|
|
1903
|
+
// Reset error counter on successful processing
|
|
1904
|
+
if (processedSomething) {
|
|
1905
|
+
parseErrors = 0;
|
|
1763
1906
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1907
|
+
// Return partial result for UI updates
|
|
1908
|
+
return { done: false, result: { ...result } };
|
|
1909
|
+
}
|
|
1910
|
+
catch (error) {
|
|
1911
|
+
parseErrors++;
|
|
1912
|
+
console.warn(`Streaming XML parse error (attempt ${parseErrors}):`, error instanceof Error ? error.message : String(error));
|
|
1913
|
+
// If we have too many consecutive errors, try to recover
|
|
1914
|
+
if (parseErrors > 3) {
|
|
1915
|
+
console.error('Too many parse errors, attempting recovery');
|
|
1916
|
+
// Try to find next valid wizard tag and restart from there
|
|
1917
|
+
const recoveryMatch = buffer.match(/<\w+\s+[^>]*tag-category=["']wizard["'][^>]*>/);
|
|
1918
|
+
if (recoveryMatch && recoveryMatch.index !== undefined && recoveryMatch.index > 0) {
|
|
1919
|
+
buffer = buffer.slice(recoveryMatch.index);
|
|
1920
|
+
parseErrors = 0; // Reset error counter
|
|
1921
|
+
return { done: false, result: { ...result } };
|
|
1922
|
+
}
|
|
1768
1923
|
}
|
|
1924
|
+
// Return current result even with errors
|
|
1925
|
+
return { done: false, result: { ...result } };
|
|
1769
1926
|
}
|
|
1770
|
-
// Return partial result for UI updates
|
|
1771
|
-
return { done: false, result: { ...result } };
|
|
1772
1927
|
}
|
|
1773
1928
|
};
|
|
1774
1929
|
}
|