@swizzy_ai/kit 1.0.0

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/dist/index.js ADDED
@@ -0,0 +1,1451 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var zod = require('zod');
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+ var http = require('http');
9
+ var WebSocket = require('ws');
10
+ var openai = require('@ai-sdk/openai');
11
+ var anthropic = require('@ai-sdk/anthropic');
12
+ var google = require('@ai-sdk/google');
13
+ var xai = require('@ai-sdk/xai');
14
+ var ai = require('ai');
15
+
16
+ function _interopNamespaceDefault(e) {
17
+ var n = Object.create(null);
18
+ if (e) {
19
+ Object.keys(e).forEach(function (k) {
20
+ if (k !== 'default') {
21
+ var d = Object.getOwnPropertyDescriptor(e, k);
22
+ Object.defineProperty(n, k, d.get ? d : {
23
+ enumerable: true,
24
+ get: function () { return e[k]; }
25
+ });
26
+ }
27
+ });
28
+ }
29
+ n.default = e;
30
+ return Object.freeze(n);
31
+ }
32
+
33
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
34
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
35
+ var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
36
+ var WebSocket__namespace = /*#__PURE__*/_interopNamespaceDefault(WebSocket);
37
+
38
+ class Step {
39
+ constructor(config) {
40
+ this.id = config.id;
41
+ this.instruction = config.instruction;
42
+ this.schema = config.schema;
43
+ this.update = config.update;
44
+ this.contextFunction = config.contextFunction;
45
+ this.contextType = config.contextType || 'xml'; // Default to xml
46
+ this.beforeRun = config.beforeRun;
47
+ this.afterRun = config.afterRun;
48
+ this.model = config.model;
49
+ }
50
+ validate(data) {
51
+ const result = this.schema.safeParse(data);
52
+ if (!result.success) {
53
+ throw new Error(`Step "${this.id}" validation failed: ${result.error.message}`);
54
+ }
55
+ return result.data;
56
+ }
57
+ getContext(workflowContext) {
58
+ return this.contextFunction ? this.contextFunction(workflowContext) : workflowContext;
59
+ }
60
+ }
61
+
62
+ class TextStep extends Step {
63
+ constructor(config) {
64
+ // Create a dummy schema for string
65
+ const dummySchema = zod.z.string();
66
+ super({
67
+ ...config,
68
+ schema: dummySchema,
69
+ });
70
+ }
71
+ validate(data) {
72
+ // For text steps, just return the data as string
73
+ return typeof data === 'string' ? data : String(data);
74
+ }
75
+ }
76
+
77
+ class ComputeStep extends Step {
78
+ constructor(config) {
79
+ // Use a permissive schema that accepts any data
80
+ const permissiveSchema = zod.z.any();
81
+ super({
82
+ ...config,
83
+ schema: permissiveSchema,
84
+ model: '', // No model needed for compute steps
85
+ });
86
+ this.isComputeStep = true;
87
+ }
88
+ validate(data) {
89
+ // For compute steps, accept any data without validation
90
+ return data;
91
+ }
92
+ }
93
+
94
+ exports.Models = void 0;
95
+ (function (Models) {
96
+ Models["GPT4"] = "gpt-4";
97
+ Models["GPT35_TURBO"] = "gpt-3.5-turbo";
98
+ Models["CLAUDE3_SONNET"] = "claude-3-sonnet-20240229";
99
+ Models["CLAUDE3_HAIKU"] = "claude-3-haiku-20240307";
100
+ Models["GEMINI_PRO"] = "gemini-pro";
101
+ Models["GEMINI_PRO_VISION"] = "gemini-pro-vision";
102
+ Models["GROK_BETA"] = "grok-beta";
103
+ Models["SWIZZY_DEFAULT"] = "swizzy-default";
104
+ })(exports.Models || (exports.Models = {}));
105
+ class Model {
106
+ constructor(modelEnum) {
107
+ this.model = modelEnum;
108
+ this.provider = this.getProviderFromModel(modelEnum);
109
+ }
110
+ getProviderFromModel(model) {
111
+ switch (model) {
112
+ case exports.Models.GPT4:
113
+ case exports.Models.GPT35_TURBO:
114
+ return 'openai';
115
+ case exports.Models.CLAUDE3_SONNET:
116
+ case exports.Models.CLAUDE3_HAIKU:
117
+ return 'anthropic';
118
+ case exports.Models.GEMINI_PRO:
119
+ case exports.Models.GEMINI_PRO_VISION:
120
+ return 'google';
121
+ case exports.Models.GROK_BETA:
122
+ return 'grok';
123
+ case exports.Models.SWIZZY_DEFAULT:
124
+ return 'swizzy';
125
+ default:
126
+ console.error("Unknown error defaulting to SWIZZY");
127
+ return 'swizzy';
128
+ }
129
+ }
130
+ }
131
+
132
+ class BaseProvider {
133
+ }
134
+ class SwizzyProvider extends BaseProvider {
135
+ constructor(config) {
136
+ super();
137
+ const baseURL = config.baseURL || process.env.SWIZZY_BASE_URL || 'https://swizzy-kit.hello-ad4.workers.dev';
138
+ const apiKey = config.apiKey || process.env.SWIZZY_API_KEY;
139
+ if (!apiKey) {
140
+ throw new Error('SWIZZY_API_KEY is required. Set it via environment variable SWIZZY_API_KEY or pass apiKey directly.');
141
+ }
142
+ this.config = { baseURL, apiKey };
143
+ }
144
+ async complete(options) {
145
+ const endpoint = options.stream ? '/completions/stream' : '/completions';
146
+ const response = await fetch(`${this.config.baseURL}${endpoint}`, {
147
+ method: 'POST',
148
+ headers: {
149
+ 'Content-Type': 'application/json',
150
+ 'x-api-key': this.config.apiKey,
151
+ },
152
+ body: JSON.stringify({
153
+ prompt: options.prompt,
154
+ max_tokens: options.maxTokens || 1000,
155
+ temperature: options.temperature || 0.7,
156
+ }),
157
+ });
158
+ if (!response.ok) {
159
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
160
+ try {
161
+ const errorData = await response.json();
162
+ errorMessage = errorData.error || errorData.message || errorMessage;
163
+ }
164
+ catch (e) {
165
+ const text = await response.text().catch(() => '');
166
+ if (text)
167
+ errorMessage = text;
168
+ }
169
+ throw new Error(`LLM API error: ${errorMessage}`);
170
+ }
171
+ if (options.stream) {
172
+ const reader = response.body?.getReader();
173
+ if (!reader)
174
+ throw new Error('No response body for streaming');
175
+ let fullText = '';
176
+ const decoder = new TextDecoder();
177
+ while (true) {
178
+ const { done, value } = await reader.read();
179
+ if (done)
180
+ break;
181
+ const chunk = decoder.decode(value);
182
+ fullText += chunk;
183
+ }
184
+ return { text: fullText };
185
+ }
186
+ else {
187
+ const data = await response.json();
188
+ let transformedUsage;
189
+ const usageData = data.fullResult?.usage || data.usage;
190
+ if (usageData) {
191
+ transformedUsage = {
192
+ promptTokens: usageData.prompt_tokens || 0,
193
+ completionTokens: usageData.completion_tokens || 0,
194
+ totalTokens: usageData.total_tokens || 0
195
+ };
196
+ }
197
+ if (options.onUsage && transformedUsage) {
198
+ options.onUsage(transformedUsage, this.getProviderName());
199
+ }
200
+ return {
201
+ text: data.completion,
202
+ usage: transformedUsage,
203
+ };
204
+ }
205
+ }
206
+ supportsModel(model) {
207
+ return model === exports.Models.SWIZZY_DEFAULT;
208
+ }
209
+ getProviderName() {
210
+ return 'swizzy';
211
+ }
212
+ }
213
+ class MultiProvider extends BaseProvider {
214
+ constructor() {
215
+ super();
216
+ this.openaiKey = process.env.OPENAI_API_KEY;
217
+ this.anthropicKey = process.env.ANTHROPIC_API_KEY;
218
+ this.googleKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY;
219
+ this.xaiKey = process.env.XAI_API_KEY;
220
+ }
221
+ async complete(options) {
222
+ const model = options.model || exports.Models.GPT4;
223
+ let provider;
224
+ switch (model) {
225
+ case exports.Models.GPT4:
226
+ case exports.Models.GPT35_TURBO:
227
+ if (!this.openaiKey)
228
+ throw new Error('OpenAI API key not available');
229
+ provider = openai.openai(model);
230
+ break;
231
+ case exports.Models.CLAUDE3_SONNET:
232
+ case exports.Models.CLAUDE3_HAIKU:
233
+ if (!this.anthropicKey)
234
+ throw new Error('Anthropic API key not available');
235
+ provider = anthropic.anthropic(model);
236
+ break;
237
+ case exports.Models.GEMINI_PRO:
238
+ case exports.Models.GEMINI_PRO_VISION:
239
+ if (!this.googleKey)
240
+ throw new Error('Google API key not available');
241
+ provider = google.google(model);
242
+ break;
243
+ case exports.Models.GROK_BETA:
244
+ if (!this.xaiKey)
245
+ throw new Error('xAI API key not available');
246
+ provider = xai.xai(model);
247
+ break;
248
+ default:
249
+ throw new Error(`Unsupported model: ${model}`);
250
+ }
251
+ const result = await ai.generateText({
252
+ model: provider,
253
+ prompt: options.prompt,
254
+ maxTokens: options.maxTokens,
255
+ temperature: options.temperature,
256
+ });
257
+ const usage = {
258
+ promptTokens: result.usage.promptTokens,
259
+ completionTokens: result.usage.completionTokens,
260
+ totalTokens: result.usage.totalTokens,
261
+ };
262
+ if (options.onUsage) {
263
+ options.onUsage(usage, this.getProviderName());
264
+ }
265
+ return {
266
+ text: result.text,
267
+ usage,
268
+ };
269
+ }
270
+ supportsModel(model) {
271
+ const m = model;
272
+ switch (m) {
273
+ case exports.Models.GPT4:
274
+ case exports.Models.GPT35_TURBO:
275
+ return !!this.openaiKey;
276
+ case exports.Models.CLAUDE3_SONNET:
277
+ case exports.Models.CLAUDE3_HAIKU:
278
+ return !!this.anthropicKey;
279
+ case exports.Models.GEMINI_PRO:
280
+ case exports.Models.GEMINI_PRO_VISION:
281
+ return !!this.googleKey;
282
+ case exports.Models.GROK_BETA:
283
+ return !!this.xaiKey;
284
+ default:
285
+ return false;
286
+ }
287
+ }
288
+ getProviderName() {
289
+ return 'multi';
290
+ }
291
+ }
292
+
293
+ class ProviderRegistry {
294
+ constructor() {
295
+ this.providers = new Map();
296
+ this.registerDefaultProviders();
297
+ }
298
+ registerDefaultProviders() {
299
+ const hasAnyMultiKey = !!(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.XAI_API_KEY);
300
+ if (hasAnyMultiKey) {
301
+ this.providers.set('multi', new MultiProvider());
302
+ }
303
+ if (process.env.SWIZZY_API_KEY) {
304
+ this.providers.set('swizzy', new SwizzyProvider({ apiKey: process.env.SWIZZY_API_KEY }));
305
+ }
306
+ }
307
+ registerProvider(name, provider) {
308
+ this.providers.set(name, provider);
309
+ }
310
+ getProviderForModel(model) {
311
+ for (const provider of this.providers.values()) {
312
+ if (provider.supportsModel(model)) {
313
+ return provider;
314
+ }
315
+ }
316
+ throw new Error(`No provider registered for model: ${model}`);
317
+ }
318
+ getAllProviders() {
319
+ return Array.from(this.providers.values());
320
+ }
321
+ hasProviderForModel(model) {
322
+ try {
323
+ this.getProviderForModel(model);
324
+ return true;
325
+ }
326
+ catch {
327
+ return false;
328
+ }
329
+ }
330
+ }
331
+
332
+ class LLMClient {
333
+ constructor(registry) {
334
+ this.registry = registry;
335
+ }
336
+ async complete(options) {
337
+ const model = options.model;
338
+ if (!model) {
339
+ throw new Error('Model must be specified in CompletionOptions');
340
+ }
341
+ const provider = this.registry.getProviderForModel(model);
342
+ return provider.complete(options);
343
+ }
344
+ }
345
+
346
+ class BungeeBuilder {
347
+ constructor(currentStepId) {
348
+ /**
349
+ * Add a single step execution.
350
+ */
351
+ this.add = (stepId) => {
352
+ this._plan.destinations.push({ type: 'step', targetId: stepId });
353
+ return this;
354
+ };
355
+ /**
356
+ * Add multiple executions based on count with config function.
357
+ */
358
+ this.batch = (stepId, count, configFn) => {
359
+ for (let i = 0; i < count; i++) {
360
+ this._plan.destinations.push({ type: 'step', targetId: stepId });
361
+ }
362
+ this._plan.configFn = configFn;
363
+ return this;
364
+ };
365
+ /**
366
+ * Configure execution settings.
367
+ */
368
+ this.config = (options) => {
369
+ if (options.concurrency !== undefined) {
370
+ this._plan.concurrency = options.concurrency;
371
+ }
372
+ return this;
373
+ };
374
+ /**
375
+ * Trigger the Jump.
376
+ */
377
+ this.jump = () => {
378
+ return { type: 'BUNGEE_JUMP', plan: this._plan };
379
+ };
380
+ this._plan = {
381
+ id: `bungee_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
382
+ anchorId: currentStepId,
383
+ destinations: [],
384
+ concurrency: 5
385
+ };
386
+ }
387
+ }
388
+
389
+ class Wizard {
390
+ constructor(config) {
391
+ this.steps = [];
392
+ this.workflowContext = {};
393
+ // Bungee state tracking
394
+ this.bungeeWorkers = new Map();
395
+ this.pendingReentry = new Set();
396
+ // Performance optimizations
397
+ this.stepIndexMap = new Map();
398
+ this.schemaDescriptions = new Map();
399
+ this.maxCacheSize = 100;
400
+ this.connectedClients = new Set();
401
+ this.maxWebSocketConnections = 10;
402
+ this.wsIntervals = new WeakMap();
403
+ // WebSocket messages sent immediately for real-time UI updates
404
+ // Token tracking
405
+ this.totalTokens = 0;
406
+ this.stepTokens = 0;
407
+ this.currentStepIndex = 0;
408
+ this.isPaused = false;
409
+ this.isRunning = false;
410
+ this.isStepMode = false;
411
+ this.id = config.id;
412
+ const registry = new ProviderRegistry();
413
+ this.llmClient = new LLMClient(registry);
414
+ this.systemPrompt = config.systemPrompt;
415
+ // Set up token tracking
416
+ if (config.onUsage) {
417
+ const originalOnUsage = config.onUsage;
418
+ config.onUsage = (usage, provider) => {
419
+ this.totalTokens += usage.totalTokens;
420
+ this.stepTokens = usage.totalTokens; // Last step tokens
421
+ this.sendToClients({
422
+ type: 'token_update',
423
+ totalTokens: this.totalTokens,
424
+ stepTokens: this.stepTokens
425
+ });
426
+ originalOnUsage(usage, provider);
427
+ };
428
+ }
429
+ // Create logs directory if it doesn't exist
430
+ const logsDir = path__namespace.join(process.cwd(), '.wizard');
431
+ if (!fs__namespace.existsSync(logsDir)) {
432
+ fs__namespace.mkdirSync(logsDir, { recursive: true });
433
+ }
434
+ this.logFilePath = path__namespace.join(logsDir, `${this.id}.log`);
435
+ }
436
+ log(messageOrFn) {
437
+ if (!this.logFilePath)
438
+ return; // Early exit if logging disabled
439
+ const message = typeof messageOrFn === 'function' ? messageOrFn() : messageOrFn;
440
+ const content = `${new Date().toISOString()}: ${message}\n`;
441
+ this.appendToFile(content);
442
+ }
443
+ appendToFile(content) {
444
+ if (!this.logFilePath)
445
+ return;
446
+ try {
447
+ fs__namespace.appendFileSync(this.logFilePath, content, 'utf8');
448
+ }
449
+ catch (error) {
450
+ console.log('Wizard log:', content.trim());
451
+ }
452
+ }
453
+ addStep(config) {
454
+ const step = new Step(config);
455
+ const index = this.steps.length;
456
+ this.steps.push(step);
457
+ this.stepIndexMap.set(step.id, index);
458
+ return this;
459
+ }
460
+ addParallelSteps(callback) {
461
+ const configs = [];
462
+ const addStep = (config) => configs.push(config);
463
+ callback(addStep);
464
+ const parallelSteps = configs.map(c => new Step(c));
465
+ const index = this.steps.length;
466
+ this.steps.push(parallelSteps);
467
+ // Add all parallel steps to index with same position
468
+ parallelSteps.forEach(step => this.stepIndexMap.set(step.id, index));
469
+ return this;
470
+ }
471
+ addTextStep(config) {
472
+ const step = new TextStep(config);
473
+ const index = this.steps.length;
474
+ this.steps.push(step);
475
+ this.stepIndexMap.set(step.id, index);
476
+ return this;
477
+ }
478
+ addComputeStep(config) {
479
+ const step = new ComputeStep(config);
480
+ const index = this.steps.length;
481
+ this.steps.push(step);
482
+ this.stepIndexMap.set(step.id, index);
483
+ return this;
484
+ }
485
+ goto(stepId) { return `GOTO ${stepId}`; }
486
+ next() { return 'NEXT'; }
487
+ stop() { return 'STOP'; }
488
+ retry() { return 'RETRY'; }
489
+ wait() { return 'WAIT'; }
490
+ clearStepError(stepId) {
491
+ delete this.workflowContext[`${stepId}_error`];
492
+ delete this.workflowContext[`${stepId}_retryCount`];
493
+ }
494
+ isStringSignal(signal) {
495
+ return typeof signal === 'string';
496
+ }
497
+ isBungeeJumpSignal(signal) {
498
+ return typeof signal === 'object' && signal !== null && signal.type === 'BUNGEE_JUMP';
499
+ }
500
+ async executeBungeePlan(plan) {
501
+ console.log(`πŸͺ‚ Executing Bungee plan ${plan.id} with ${plan.destinations.length} destinations`);
502
+ // Track active workers for this plan
503
+ const activeWorkers = new Set();
504
+ for (let i = 0; i < plan.destinations.length; i++) {
505
+ // Launch worker
506
+ const workerPromise = this.launchBungeeWorker(plan, i);
507
+ activeWorkers.add(workerPromise);
508
+ // Respect concurrency limit
509
+ if (activeWorkers.size >= plan.concurrency) {
510
+ await Promise.race(activeWorkers);
511
+ // Clean up completed workers
512
+ for (const promise of activeWorkers) {
513
+ if (promise !== workerPromise) {
514
+ activeWorkers.delete(promise);
515
+ }
516
+ }
517
+ }
518
+ }
519
+ // Wait for all workers to complete
520
+ await Promise.all(activeWorkers);
521
+ console.log(`βœ… Bungee plan ${plan.id} completed, returning to anchor ${plan.anchorId}`);
522
+ }
523
+ async launchBungeeWorker(plan, index) {
524
+ const destination = plan.destinations[index];
525
+ const telescope = plan.configFn ? plan.configFn(index) : {};
526
+ const workerId = `${plan.id}_${destination.targetId}_${index}_${Date.now()}`;
527
+ const telescopeContext = this.createTelescopeContext(this.workflowContext, telescope);
528
+ const promise = this.executeWorkerStep(destination.targetId, telescopeContext);
529
+ // Track this worker
530
+ if (!this.bungeeWorkers.has(plan.id)) {
531
+ this.bungeeWorkers.set(plan.id, new Map());
532
+ }
533
+ this.bungeeWorkers.get(plan.id).set(workerId, {
534
+ planId: plan.id,
535
+ workerId,
536
+ promise,
537
+ telescope
538
+ });
539
+ try {
540
+ await promise;
541
+ }
542
+ catch (error) {
543
+ console.error(`Bungee worker ${workerId} failed:`, error);
544
+ this.workflowContext[`${workerId}_error`] = error.message;
545
+ }
546
+ finally {
547
+ // Clean up
548
+ const planWorkers = this.bungeeWorkers.get(plan.id);
549
+ if (planWorkers) {
550
+ planWorkers.delete(workerId);
551
+ if (planWorkers.size === 0) {
552
+ this.bungeeWorkers.delete(plan.id);
553
+ // Trigger reentry to anchor
554
+ this.pendingReentry.add(plan.anchorId);
555
+ }
556
+ }
557
+ }
558
+ }
559
+ createWizardActions(anchorStepId = '') {
560
+ return {
561
+ updateContext: (updates) => this.updateContext(updates),
562
+ llmClient: this.llmClient,
563
+ goto: (stepId) => this.goto(stepId),
564
+ next: () => this.next(),
565
+ stop: () => this.stop(),
566
+ retry: () => this.retry(),
567
+ wait: () => this.wait(),
568
+ bungee: {
569
+ init: () => new BungeeBuilder(anchorStepId)
570
+ }
571
+ };
572
+ }
573
+ createWorkerActions(telescope) {
574
+ return {
575
+ updateContext: (updates) => {
576
+ this.mergeWorkerResults(updates, telescope);
577
+ },
578
+ llmClient: this.llmClient,
579
+ goto: () => 'STOP',
580
+ next: () => 'STOP',
581
+ stop: () => 'STOP',
582
+ retry: () => 'STOP',
583
+ wait: () => 'STOP',
584
+ bungee: {
585
+ init: () => {
586
+ throw new Error('Bungee not allowed in worker context');
587
+ }
588
+ }
589
+ };
590
+ }
591
+ createTelescopeContext(baseContext, telescope) {
592
+ return {
593
+ ...baseContext,
594
+ ...telescope,
595
+ _telescope: telescope,
596
+ _anchorId: null
597
+ };
598
+ }
599
+ async executeWorkerStep(stepId, telescopeContext) {
600
+ const step = this.findStep(stepId);
601
+ if (!step)
602
+ return;
603
+ const stepContext = step.getContext(telescopeContext);
604
+ const stepData = await this.generateStepData(step, stepContext);
605
+ const actions = this.createWorkerActions(telescopeContext._telescope);
606
+ return await step.update(stepData, telescopeContext, actions);
607
+ }
608
+ mergeWorkerResults(updates, telescope) {
609
+ Object.entries(updates).forEach(([key, value]) => {
610
+ this.workflowContext[key] = value;
611
+ });
612
+ }
613
+ async retriggerAnchor(anchorId) {
614
+ const anchorStep = this.findStep(anchorId);
615
+ if (anchorStep) {
616
+ await this.executeStep(anchorStep);
617
+ }
618
+ }
619
+ async processReentries() {
620
+ const anchorsToRetrigger = Array.from(this.pendingReentry);
621
+ this.pendingReentry.clear();
622
+ for (const anchorId of anchorsToRetrigger) {
623
+ await this.retriggerAnchor(anchorId);
624
+ }
625
+ }
626
+ findStep(stepId) {
627
+ for (const item of this.steps) {
628
+ if (Array.isArray(item)) {
629
+ const found = item.find(s => s.id === stepId);
630
+ if (found)
631
+ return found;
632
+ }
633
+ else {
634
+ if (item.id === stepId)
635
+ return item;
636
+ }
637
+ }
638
+ return null;
639
+ }
640
+ findStepIndex(stepId) {
641
+ return this.stepIndexMap.get(stepId) ?? -1;
642
+ }
643
+ async executeStep(step) {
644
+ console.log(`Starting step ${step.id}`);
645
+ this.log(`Starting step ${step.id}`);
646
+ const stepContext = step.getContext(this.workflowContext);
647
+ let processedInstruction = step.instruction;
648
+ if (step.contextType === 'template' || step.contextType === 'both') {
649
+ processedInstruction = this.applyTemplate(step.instruction, stepContext);
650
+ }
651
+ this.sendToClients({
652
+ type: 'step_update',
653
+ stepId: step.id,
654
+ status: 'current',
655
+ instruction: processedInstruction,
656
+ context: stepContext,
657
+ fields: this.extractSchemaFields(step.schema)
658
+ });
659
+ this.sendToClients({
660
+ type: 'context_update',
661
+ context: this.workflowContext
662
+ });
663
+ try {
664
+ if (step.beforeRun) {
665
+ await step.beforeRun();
666
+ }
667
+ this.log(() => `Context for step ${step.id}: ${JSON.stringify(stepContext)}`);
668
+ // Skip LLM data generation for compute steps
669
+ const stepData = step.isComputeStep ? null : await this.generateStepData(step, stepContext);
670
+ if (this.isPaused) {
671
+ console.log('⏸️ Paused before LLM call, waiting for user input...');
672
+ await this.waitForResume();
673
+ console.log('▢️ Resumed, checking for user override...');
674
+ if (this.userOverrideData) {
675
+ console.log('πŸ“ Using user override data');
676
+ try {
677
+ const validatedData = step.validate(this.userOverrideData);
678
+ this.sendToClients({
679
+ type: 'step_update',
680
+ stepId: step.id,
681
+ status: 'completed',
682
+ data: validatedData
683
+ });
684
+ this.userOverrideData = undefined;
685
+ return await this.processStepResult(step, validatedData);
686
+ }
687
+ catch (validationError) {
688
+ console.error('User override validation failed:', validationError.message);
689
+ this.userOverrideData = undefined;
690
+ }
691
+ }
692
+ }
693
+ if (stepData && stepData.__validationFailed) {
694
+ console.log(`πŸ”„ Validation failed for step ${step.id}, retrying...`, stepData);
695
+ return 'RETRY';
696
+ }
697
+ const actions = this.createWizardActions(step.id);
698
+ const signal = await step.update(stepData, this.workflowContext, actions);
699
+ this.sendToClients({
700
+ type: 'step_update',
701
+ stepId: step.id,
702
+ status: 'completed',
703
+ data: stepData
704
+ });
705
+ if (this.workflowContext[`${step.id}_error`]) {
706
+ this.clearStepError(step.id);
707
+ }
708
+ if (step.afterRun) {
709
+ await step.afterRun(stepData);
710
+ }
711
+ return signal;
712
+ }
713
+ catch (error) {
714
+ console.log('Processing error', error);
715
+ this.updateContext({
716
+ [`${step.id}_error`]: error.message,
717
+ [`${step.id}_retryCount`]: (this.workflowContext[`${step.id}_retryCount`] || 0) + 1
718
+ });
719
+ return 'RETRY';
720
+ }
721
+ }
722
+ async processStepResult(step, stepData) {
723
+ const actions = this.createWizardActions(step.id);
724
+ const signal = await step.update(stepData, this.workflowContext, actions);
725
+ if (this.workflowContext[`${step.id}_error`]) {
726
+ this.clearStepError(step.id);
727
+ }
728
+ if (step.afterRun) {
729
+ await step.afterRun(stepData);
730
+ }
731
+ return signal;
732
+ }
733
+ setContext(context) {
734
+ this.workflowContext = { ...this.workflowContext, ...context };
735
+ return this;
736
+ }
737
+ getContext() {
738
+ return this.workflowContext;
739
+ }
740
+ updateContext(updates) {
741
+ this.workflowContext = { ...this.workflowContext, ...updates };
742
+ return this;
743
+ }
744
+ async run() {
745
+ const startTime = Date.now();
746
+ if (this.visualizationServer) {
747
+ console.log('🎯 Waiting for UI to start wizard execution...');
748
+ this.sendToClients({ type: 'status_update', status: { waitingForStart: true, isStepMode: false } });
749
+ await this.waitForRunCommand();
750
+ console.log('πŸš€ Starting wizard execution from UI command');
751
+ // Send all steps info
752
+ const stepsInfo = this.steps.map(item => {
753
+ if (Array.isArray(item)) {
754
+ return item.map(step => ({
755
+ id: step.id,
756
+ instruction: step.instruction,
757
+ fields: this.extractSchemaFields(step.schema),
758
+ status: 'pending'
759
+ }));
760
+ }
761
+ else {
762
+ return {
763
+ id: item.id,
764
+ instruction: item.instruction,
765
+ fields: this.extractSchemaFields(item.schema),
766
+ status: 'pending'
767
+ };
768
+ }
769
+ }).flat();
770
+ this.sendToClients({ type: 'wizard_start', steps: stepsInfo });
771
+ }
772
+ this.log('Wizard session started');
773
+ this.currentStepIndex = 0;
774
+ this.isRunning = true;
775
+ while (this.currentStepIndex < this.steps.length && this.isRunning) {
776
+ const item = this.steps[this.currentStepIndex];
777
+ if (!item)
778
+ break;
779
+ if (Array.isArray(item)) {
780
+ const parallelSteps = item;
781
+ console.log(`Starting parallel steps: ${parallelSteps.map(s => s.id).join(', ')}`);
782
+ this.log(`Starting parallel steps: ${parallelSteps.map(s => s.id).join(', ')}`);
783
+ const promises = parallelSteps.map(step => this.executeStep(step));
784
+ const signals = await Promise.all(promises);
785
+ let nextSignal = 'NEXT';
786
+ for (const signal of signals) {
787
+ if (signal === 'STOP') {
788
+ return;
789
+ }
790
+ if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
791
+ nextSignal = signal;
792
+ break;
793
+ }
794
+ if (signal === 'RETRY') {
795
+ nextSignal = 'RETRY';
796
+ }
797
+ }
798
+ if (nextSignal === 'NEXT') {
799
+ this.currentStepIndex++;
800
+ }
801
+ else if (nextSignal === 'RETRY') ;
802
+ else if (this.isStringSignal(nextSignal) && nextSignal.startsWith('GOTO ')) {
803
+ const targetStepId = nextSignal.substring(5);
804
+ const targetIndex = this.findStepIndex(targetStepId);
805
+ if (targetIndex !== -1) {
806
+ this.currentStepIndex = targetIndex;
807
+ }
808
+ else {
809
+ throw new Error(`Unknown step ID for GOTO: ${targetStepId}`);
810
+ }
811
+ }
812
+ }
813
+ else {
814
+ const signal = await this.executeStep(item);
815
+ switch (signal) {
816
+ case 'NEXT':
817
+ this.currentStepIndex++;
818
+ break;
819
+ case 'STOP':
820
+ return;
821
+ case 'RETRY':
822
+ break;
823
+ case 'WAIT':
824
+ await new Promise(resolve => setTimeout(resolve, 10 * 1000));
825
+ this.currentStepIndex++;
826
+ break;
827
+ default:
828
+ if (this.isBungeeJumpSignal(signal)) {
829
+ await this.executeBungeePlan(signal.plan);
830
+ }
831
+ else if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
832
+ const targetStepId = signal.substring(5);
833
+ const targetIndex = this.findStepIndex(targetStepId);
834
+ if (targetIndex !== -1) {
835
+ this.currentStepIndex = targetIndex;
836
+ }
837
+ else {
838
+ throw new Error(`Unknown step ID for GOTO: ${targetStepId}`);
839
+ }
840
+ }
841
+ }
842
+ }
843
+ if (this.isStepMode) {
844
+ this.isPaused = true;
845
+ await this.waitForResume();
846
+ }
847
+ await this.processReentries();
848
+ }
849
+ const endTime = Date.now();
850
+ const duration = endTime - startTime;
851
+ this.isRunning = false;
852
+ this.sendToClients({ type: 'status_update', status: { completed: true } });
853
+ console.log(`βœ… Wizard completed in ${duration}ms`);
854
+ }
855
+ async waitForRunCommand() {
856
+ return new Promise(resolve => {
857
+ this.runResolver = resolve;
858
+ });
859
+ }
860
+ async generateStepData(step, stepContext) {
861
+ const systemContext = this.systemPrompt ? `${this.systemPrompt}\n\n` : '';
862
+ const errorContext = this.workflowContext[`${step.id}_error`] ?
863
+ `\n\nPREVIOUS ERROR (attempt ${this.workflowContext[`${step.id}_retryCount`] || 1}):\n${this.workflowContext[`${step.id}_error`]}\nPlease fix this.` : '';
864
+ let processedInstruction = step.instruction;
865
+ if (step.contextType === 'template' || step.contextType === 'both') {
866
+ processedInstruction = this.applyTemplate(step.instruction, stepContext);
867
+ }
868
+ this.log(() => `Processed instruction for step ${step.id}: ${processedInstruction}`);
869
+ let contextSection = '';
870
+ if (step.contextType === 'xml' || step.contextType === 'both' || !step.contextType) {
871
+ contextSection = `\n\nSTEP CONTEXT:\n${this.objectToXml(stepContext)}`;
872
+ }
873
+ this.log(() => `Context section for step ${step.id}: ${contextSection}`);
874
+ if (step instanceof TextStep) {
875
+ const prompt = `${systemContext}You are executing a wizard step. Generate text for this step.
876
+
877
+ STEP: ${step.id}
878
+ INSTRUCTION: ${processedInstruction}${errorContext}${contextSection}
879
+
880
+ Generate the text response now.`;
881
+ this.log(() => `Full prompt for step ${step.id}: ${prompt}`);
882
+ const llmResult = await this.llmClient.complete({
883
+ prompt,
884
+ model: step.model,
885
+ maxTokens: 1000,
886
+ temperature: 0.3,
887
+ });
888
+ this.log(() => `LLM response for step ${step.id}: ${llmResult.text}`);
889
+ console.log(`LLM response for step ${step.id}:`, llmResult.text);
890
+ return llmResult.text;
891
+ }
892
+ const schemaDescription = this.describeSchema(step.schema, step.id);
893
+ const prompt = `${systemContext}You are executing a wizard step. Generate data for this step.
894
+
895
+ STEP: ${step.id}
896
+ INSTRUCTION: ${processedInstruction}${errorContext}${contextSection}
897
+
898
+ SCHEMA REQUIREMENTS:
899
+ ${schemaDescription}
900
+
901
+ REQUIRED OUTPUT FORMAT:
902
+ Return a plain XML response with a root <response> tag.
903
+ CRITICAL: Every field MUST include tag-category="wizard" attribute. This is MANDATORY.
904
+ Every field MUST also include a type attribute (e.g., type="string", type="number", type="boolean", type="array").
905
+
906
+ IMPORTANT PARSING RULES:
907
+ - Fields with tag-category="wizard" do NOT need closing tags
908
+ - Content ends when the next tag with tag-category="wizard" begins, OR when </response> is reached
909
+ - This means you can include ANY content (including code with <>, XML snippets, etc.) without worrying about breaking the parser
910
+ - Only fields marked with tag-category="wizard" will be parsed
911
+
912
+ Example:
913
+ <response>
914
+ <name tag-category="wizard" type="string">John Smith
915
+ <age tag-category="wizard" type="number">25
916
+ <code tag-category="wizard" type="string">
917
+ function example() {
918
+ const x = <div>Hello</div>;
919
+ return x;
920
+ }
921
+ <tags tag-category="wizard" type="array">["a", "b", "c"]
922
+ </response>
923
+
924
+ Notice: No closing tags needed for wizard fields! Content naturally ends at the next wizard field or </response>.
925
+
926
+ Generate the XML response now.`;
927
+ this.log(() => `Full prompt for step ${step.id}: ${prompt}`);
928
+ const llmResult = await this.llmClient.complete({
929
+ prompt,
930
+ model: step.model,
931
+ maxTokens: 1000,
932
+ temperature: 0.3,
933
+ });
934
+ this.log(() => `LLM response for step ${step.id}: ${llmResult.text}`);
935
+ console.log(`LLM response for step ${step.id}:`, llmResult.text);
936
+ const jsonData = this.parseXmlToJson(llmResult.text);
937
+ this.log(() => `Parsed JSON data for step ${step.id}: ${JSON.stringify(jsonData)}`);
938
+ try {
939
+ return step.validate(jsonData);
940
+ }
941
+ catch (validationError) {
942
+ this.log(() => `Validation failed for step ${step.id}: ${validationError.message}`);
943
+ try {
944
+ const repairedData = await this.repairSchemaData(jsonData, step.schema, validationError.message, step.id);
945
+ this.log(() => `Repaired data for step ${step.id}: ${JSON.stringify(repairedData)}`);
946
+ return step.validate(repairedData);
947
+ }
948
+ catch (repairError) {
949
+ this.log(() => `Repair failed for step ${step.id}: ${repairError.message}`);
950
+ return { __validationFailed: true, error: validationError.message };
951
+ }
952
+ }
953
+ }
954
+ async repairSchemaData(invalidData, schema, validationError, stepId) {
955
+ const step = this.findStep(stepId);
956
+ if (!step)
957
+ throw new Error(`Step ${stepId} not found`);
958
+ const schemaDescription = this.describeSchema(schema, stepId);
959
+ const prompt = `You are repairing invalid data for a wizard step. The data failed validation and needs to be fixed to match the schema.
960
+
961
+ INVALID DATA: ${JSON.stringify(invalidData, null, 2)}
962
+ VALIDATION ERROR: ${validationError}
963
+
964
+ SCHEMA REQUIREMENTS:
965
+ ${schemaDescription}
966
+
967
+ REQUIRED OUTPUT FORMAT:
968
+ Return a plain XML response with a root <response> tag.
969
+ CRITICAL: Every field MUST include tag-category="wizard" attribute. This is MANDATORY.
970
+ Every field MUST also include a type attribute (e.g., type="string", type="number", type="boolean", type="array").
971
+
972
+ IMPORTANT: Fields with tag-category="wizard" do NOT need closing tags. Content ends at the next wizard field or </response>.
973
+
974
+ Example:
975
+ <response>
976
+ <name tag-category="wizard" type="string">John
977
+ <age tag-category="wizard" type="number">25
978
+ <tags tag-category="wizard" type="array">["a", "b"]
979
+ </response>
980
+
981
+ Fix the data to match the schema and generate the XML response now.`;
982
+ const llmResult = await this.llmClient.complete({
983
+ prompt,
984
+ model: step.model,
985
+ maxTokens: 10000,
986
+ temperature: 0.3,
987
+ });
988
+ const repairedJsonData = this.parseXmlToJson(llmResult.text);
989
+ return repairedJsonData;
990
+ }
991
+ describeSchema(schema, stepId) {
992
+ if (stepId && this.schemaDescriptions.has(stepId)) {
993
+ return this.schemaDescriptions.get(stepId);
994
+ }
995
+ let description;
996
+ if (schema instanceof zod.z.ZodObject) {
997
+ const shape = schema._def.shape();
998
+ const fields = Object.entries(shape).map(([key, fieldSchema]) => {
999
+ const type = this.getSchemaType(fieldSchema);
1000
+ const xmlExample = this.getXmlExample(key, type);
1001
+ return `${key}: ${type} - ${xmlExample}`;
1002
+ });
1003
+ description = `Object with fields:\n${fields.join('\n')}`;
1004
+ }
1005
+ else {
1006
+ description = 'Unknown schema type';
1007
+ }
1008
+ if (stepId) {
1009
+ // Implement simple cache eviction if needed
1010
+ if (this.schemaDescriptions.size >= this.maxCacheSize) {
1011
+ const firstKey = this.schemaDescriptions.keys().next().value;
1012
+ this.schemaDescriptions.delete(firstKey || '');
1013
+ }
1014
+ this.schemaDescriptions.set(stepId, description);
1015
+ }
1016
+ return description;
1017
+ }
1018
+ getXmlExample(key, type) {
1019
+ switch (type) {
1020
+ case 'string': return `<${key} tag-category="wizard" type="string">example`;
1021
+ case 'number': return `<${key} tag-category="wizard" type="number">123`;
1022
+ case 'boolean': return `<${key} tag-category="wizard" type="boolean">true`;
1023
+ case 'array': return `<${key} tag-category="wizard" type="array">["item1", "item2"]`;
1024
+ default:
1025
+ if (type.startsWith('enum:')) {
1026
+ const values = type.split(': ')[1].split(', ');
1027
+ return `<${key} tag-category="wizard" type="string">${values[0]}`;
1028
+ }
1029
+ return `<${key} tag-category="wizard" type="object"><subfield type="string">value</subfield>`;
1030
+ }
1031
+ }
1032
+ getSchemaType(schema) {
1033
+ if (schema instanceof zod.z.ZodOptional)
1034
+ return this.getSchemaType(schema._def.innerType);
1035
+ if (schema instanceof zod.z.ZodString)
1036
+ return 'string';
1037
+ if (schema instanceof zod.z.ZodNumber)
1038
+ return 'number';
1039
+ if (schema instanceof zod.z.ZodBoolean)
1040
+ return 'boolean';
1041
+ if (schema instanceof zod.z.ZodArray)
1042
+ return 'array';
1043
+ if (schema instanceof zod.z.ZodEnum)
1044
+ return `enum: ${schema._def.values.join(', ')}`;
1045
+ return 'object';
1046
+ }
1047
+ extractSchemaFields(schema) {
1048
+ if (!(schema instanceof zod.z.ZodObject))
1049
+ return [];
1050
+ const shape = schema._def.shape();
1051
+ const fields = [];
1052
+ for (const [key, fieldSchema] of Object.entries(shape)) {
1053
+ const type = this.getSchemaType(fieldSchema);
1054
+ const field = { key, type };
1055
+ if (type.startsWith('enum:')) {
1056
+ field.type = 'enum';
1057
+ field.enumValues = type.substring(5).split(', ');
1058
+ }
1059
+ fields.push(field);
1060
+ }
1061
+ return fields;
1062
+ }
1063
+ parseXmlToJson(xml) {
1064
+ const responseMatch = xml.match(/<response\s*>([\s\S]*?)(?:<\/response\s*>|$)/i);
1065
+ if (!responseMatch) {
1066
+ throw new Error('Invalid XML response: missing <response> tag');
1067
+ }
1068
+ return this.parseXmlElementWithTagCategory(responseMatch[1]);
1069
+ }
1070
+ parseXmlElementWithTagCategory(xmlContent) {
1071
+ const result = {};
1072
+ const matches = [];
1073
+ let match;
1074
+ const pattern = new RegExp(Wizard.WIZARD_TAG_PATTERN);
1075
+ while ((match = pattern.exec(xmlContent)) !== null) {
1076
+ matches.push({
1077
+ tagName: match[1],
1078
+ attributes: match[2],
1079
+ index: match.index,
1080
+ fullMatch: match[0]
1081
+ });
1082
+ }
1083
+ this.log(() => `Found ${matches.length} wizard-tagged fields`);
1084
+ for (let i = 0; i < matches.length; i++) {
1085
+ const current = matches[i];
1086
+ const next = matches[i + 1];
1087
+ const typeMatch = current.attributes.match(/type=["']([^"']+)["']/);
1088
+ const typeHint = typeMatch ? typeMatch[1].toLowerCase() : null;
1089
+ const contentStart = current.index + current.fullMatch.length;
1090
+ let contentEnd;
1091
+ if (next) {
1092
+ contentEnd = next.index;
1093
+ }
1094
+ else {
1095
+ const responseCloseIndex = xmlContent.indexOf('</response', contentStart);
1096
+ contentEnd = responseCloseIndex !== -1 ? responseCloseIndex : xmlContent.length;
1097
+ }
1098
+ let rawContent = xmlContent.slice(contentStart, contentEnd);
1099
+ // Optimize: avoid double trimEnd
1100
+ const trimmed = rawContent.trimEnd();
1101
+ const closingTag = `</${current.tagName}>`;
1102
+ if (trimmed.endsWith(closingTag)) {
1103
+ rawContent = trimmed.slice(0, -closingTag.length);
1104
+ }
1105
+ else {
1106
+ rawContent = trimmed;
1107
+ }
1108
+ this.log(() => `Parsing field "${current.tagName}" with type="${typeHint}"`);
1109
+ this.log(() => `Raw content (first 200 chars): ${rawContent.substring(0, 200)}`);
1110
+ let value;
1111
+ if (typeHint === 'string') {
1112
+ value = rawContent;
1113
+ }
1114
+ else if (typeHint === 'number') {
1115
+ value = this.parseNumber(rawContent.trim());
1116
+ }
1117
+ else if (typeHint === 'boolean') {
1118
+ value = this.parseBoolean(rawContent.trim());
1119
+ }
1120
+ else if (typeHint === 'array') {
1121
+ value = this.parseArray(rawContent.trim());
1122
+ }
1123
+ else if (typeHint === 'object') {
1124
+ value = this.parseXmlElementWithTagCategory(rawContent);
1125
+ }
1126
+ else if (typeHint === 'null') {
1127
+ value = null;
1128
+ }
1129
+ else {
1130
+ value = this.inferAndParseValue(rawContent.trim());
1131
+ }
1132
+ if (result[current.tagName] !== undefined) {
1133
+ if (!Array.isArray(result[current.tagName])) {
1134
+ result[current.tagName] = [result[current.tagName]];
1135
+ }
1136
+ result[current.tagName].push(value);
1137
+ }
1138
+ else {
1139
+ result[current.tagName] = value;
1140
+ }
1141
+ this.log(() => `Parsed "${current.tagName}" = ${JSON.stringify(value).substring(0, 200)}`);
1142
+ }
1143
+ return result;
1144
+ }
1145
+ parseNumber(value) {
1146
+ const num = Number(value);
1147
+ if (isNaN(num)) {
1148
+ throw new Error(`Invalid number value: "${value}"`);
1149
+ }
1150
+ return num;
1151
+ }
1152
+ parseBoolean(value) {
1153
+ const normalized = value.toLowerCase();
1154
+ if (normalized === 'true')
1155
+ return true;
1156
+ if (normalized === 'false')
1157
+ return false;
1158
+ throw new Error(`Invalid boolean value: "${value}" (expected "true" or "false")`);
1159
+ }
1160
+ parseArray(value) {
1161
+ try {
1162
+ const parsed = JSON.parse(value);
1163
+ if (!Array.isArray(parsed)) {
1164
+ throw new Error('Parsed value is not an array');
1165
+ }
1166
+ return parsed;
1167
+ }
1168
+ catch (error) {
1169
+ throw new Error(`Invalid array JSON: "${value}"`);
1170
+ }
1171
+ }
1172
+ inferAndParseValue(content) {
1173
+ const trimmed = content.trim();
1174
+ if (trimmed === '')
1175
+ return '';
1176
+ if (trimmed === 'null')
1177
+ return null;
1178
+ if (trimmed === 'true')
1179
+ return true;
1180
+ if (trimmed === 'false')
1181
+ return false;
1182
+ if (!isNaN(Number(trimmed)) && trimmed !== '') {
1183
+ return Number(trimmed);
1184
+ }
1185
+ if (/<\w+[^>]*tag-category=["']wizard["']/.test(trimmed)) {
1186
+ return this.parseXmlElementWithTagCategory(trimmed);
1187
+ }
1188
+ if ((trimmed.startsWith('[') && trimmed.endsWith(']')) ||
1189
+ (trimmed.startsWith('{') && trimmed.endsWith('}'))) {
1190
+ try {
1191
+ return JSON.parse(trimmed);
1192
+ }
1193
+ catch {
1194
+ return trimmed;
1195
+ }
1196
+ }
1197
+ return trimmed;
1198
+ }
1199
+ objectToXml(obj, rootName = 'context') {
1200
+ const buildXml = (data, tagName) => {
1201
+ let type = typeof data;
1202
+ if (data === null)
1203
+ type = 'null';
1204
+ else if (Array.isArray(data))
1205
+ type = 'array';
1206
+ const attr = ` type="${type}"`;
1207
+ if (data === null || data === undefined)
1208
+ return `<${tagName}${attr}></${tagName}>`;
1209
+ if (type === 'string')
1210
+ return `<${tagName}${attr}>${this.escapeXml(data)}</${tagName}>`;
1211
+ if (type === 'number' || type === 'boolean')
1212
+ return `<${tagName}${attr}>${data}</${tagName}>`;
1213
+ if (type === 'array')
1214
+ return `<${tagName}${attr}>${JSON.stringify(data)}</${tagName}>`;
1215
+ if (type === 'object') {
1216
+ const children = Object.entries(data).map(([k, v]) => buildXml(v, k)).join('');
1217
+ return `<${tagName}${attr}>${children}</${tagName}>`;
1218
+ }
1219
+ return `<${tagName}${attr}>${String(data)}</${tagName}>`;
1220
+ };
1221
+ return buildXml(obj, rootName);
1222
+ }
1223
+ escapeXml(unsafe) {
1224
+ return unsafe
1225
+ .replace(/&/g, '&amp;')
1226
+ .replace(/</g, '&lt;')
1227
+ .replace(/>/g, '&gt;')
1228
+ .replace(/"/g, '&quot;')
1229
+ .replace(/'/g, '&apos;');
1230
+ }
1231
+ applyTemplate(instruction, context) {
1232
+ return instruction.replace(Wizard.TEMPLATE_REGEX, (match, path) => {
1233
+ const keys = path.split('.');
1234
+ let value = context;
1235
+ for (const key of keys) {
1236
+ if (value && typeof value === 'object' && key in value) {
1237
+ value = value[key];
1238
+ }
1239
+ else {
1240
+ return match;
1241
+ }
1242
+ }
1243
+ return typeof value === 'string' ? value : JSON.stringify(value);
1244
+ });
1245
+ }
1246
+ async visualize(port = 3000) {
1247
+ return new Promise((resolve, reject) => {
1248
+ const server = http__namespace.createServer((req, res) => {
1249
+ if (req.url === '/') {
1250
+ res.writeHead(200, { 'Content-Type': 'text/html' });
1251
+ res.end(this.getVisualizationHtml());
1252
+ }
1253
+ else {
1254
+ res.writeHead(404);
1255
+ res.end('Not found');
1256
+ }
1257
+ });
1258
+ this.wss = new WebSocket__namespace.Server({ server });
1259
+ this.setupWebSocketHandlers();
1260
+ server.listen(port, 'localhost', () => {
1261
+ this.visualizationPort = port;
1262
+ this.visualizationServer = server;
1263
+ const url = `http://localhost:${port}`;
1264
+ console.log(`🎯 Wizard visualization available at: ${url}`);
1265
+ resolve({ server, url });
1266
+ });
1267
+ server.on('error', (err) => {
1268
+ if (err.code === 'EADDRINUSE') {
1269
+ this.visualize(port + 1).then(resolve).catch(reject);
1270
+ }
1271
+ else {
1272
+ reject(err);
1273
+ }
1274
+ });
1275
+ });
1276
+ }
1277
+ setupWebSocketHandlers() {
1278
+ if (!this.wss)
1279
+ return;
1280
+ this.wss.on('connection', (ws) => {
1281
+ if (this.connectedClients.size >= this.maxWebSocketConnections) {
1282
+ console.log('πŸ”— WebSocket connection rejected: max connections reached');
1283
+ ws.close(1008, 'Max connections reached');
1284
+ return;
1285
+ }
1286
+ console.log('πŸ”— WebSocket client connected');
1287
+ this.connectedClients.add(ws);
1288
+ const pingInterval = setInterval(() => {
1289
+ if (ws.readyState === WebSocket__namespace.OPEN) {
1290
+ ws.ping();
1291
+ }
1292
+ else {
1293
+ clearInterval(pingInterval);
1294
+ }
1295
+ }, 30000);
1296
+ this.wsIntervals.set(ws, pingInterval);
1297
+ ws.on('message', (message) => {
1298
+ try {
1299
+ const data = JSON.parse(message.toString());
1300
+ this.handleWebSocketMessage(data, ws);
1301
+ }
1302
+ catch (error) {
1303
+ console.error('Invalid WebSocket message:', error);
1304
+ }
1305
+ });
1306
+ ws.on('close', () => {
1307
+ console.log('πŸ”Œ WebSocket client disconnected');
1308
+ this.connectedClients.delete(ws);
1309
+ const interval = this.wsIntervals.get(ws);
1310
+ if (interval) {
1311
+ clearInterval(interval);
1312
+ this.wsIntervals.delete(ws);
1313
+ }
1314
+ });
1315
+ ws.on('error', () => {
1316
+ this.connectedClients.delete(ws);
1317
+ const interval = this.wsIntervals.get(ws);
1318
+ if (interval) {
1319
+ clearInterval(interval);
1320
+ this.wsIntervals.delete(ws);
1321
+ }
1322
+ });
1323
+ this.sendToClients({ type: 'status_update', status: { waitingForStart: true, isRunning: false, isPaused: false } });
1324
+ });
1325
+ }
1326
+ handleWebSocketMessage(data, ws) {
1327
+ switch (data.type) {
1328
+ case 'control':
1329
+ switch (data.action) {
1330
+ case 'start':
1331
+ console.log('πŸš€ Starting wizard execution from UI');
1332
+ this.isRunning = true;
1333
+ this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false, isStepMode: false } });
1334
+ if (this.runResolver) {
1335
+ this.runResolver();
1336
+ this.runResolver = undefined;
1337
+ }
1338
+ break;
1339
+ case 'pause':
1340
+ this.isPaused = true;
1341
+ console.log('⏸️ Wizard execution paused');
1342
+ this.sendToClients({ type: 'status_update', status: { isPaused: true, isStepMode: this.isStepMode } });
1343
+ break;
1344
+ case 'resume':
1345
+ this.isPaused = false;
1346
+ this.isStepMode = false;
1347
+ console.log('▢️ Wizard execution resumed');
1348
+ if (this.pauseResolver) {
1349
+ this.pauseResolver();
1350
+ this.pauseResolver = undefined;
1351
+ }
1352
+ this.sendToClients({ type: 'status_update', status: { isPaused: false, isStepMode: false } });
1353
+ break;
1354
+ case 'step_forward':
1355
+ if (this.isPaused) {
1356
+ this.isStepMode = true;
1357
+ console.log('⏭️ Stepping forward');
1358
+ if (this.pauseResolver) {
1359
+ this.pauseResolver();
1360
+ this.pauseResolver = undefined;
1361
+ }
1362
+ this.sendToClients({ type: 'status_update', status: { isPaused: true, isStepMode: true } });
1363
+ }
1364
+ break;
1365
+ case 'stop':
1366
+ console.log('πŸ›‘ Stopping wizard execution');
1367
+ this.isRunning = false;
1368
+ this.isPaused = false;
1369
+ this.isStepMode = false;
1370
+ this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, isStepMode: false } });
1371
+ break;
1372
+ case 'replay':
1373
+ console.log('πŸ”„ Replaying wizard - resetting state');
1374
+ this.isRunning = false;
1375
+ this.isPaused = false;
1376
+ this.workflowContext = {};
1377
+ this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, waitingForStart: true } });
1378
+ break;
1379
+ }
1380
+ break;
1381
+ case 'run':
1382
+ console.log('πŸš€ Starting wizard execution from UI (run command)');
1383
+ this.isRunning = true;
1384
+ this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false } });
1385
+ if (this.runResolver) {
1386
+ this.runResolver();
1387
+ this.runResolver = undefined;
1388
+ }
1389
+ break;
1390
+ case 'form_submit':
1391
+ this.userOverrideData = data.data;
1392
+ console.log('πŸ“ User override data received:', data.data);
1393
+ if (this.pauseResolver) {
1394
+ this.pauseResolver();
1395
+ this.pauseResolver = undefined;
1396
+ }
1397
+ break;
1398
+ case 'update_step_data':
1399
+ this.updateContext(data.data);
1400
+ console.log('πŸ“ Step data updated:', data.data);
1401
+ break;
1402
+ case 'goto':
1403
+ const index = this.findStepIndex(data.stepId);
1404
+ if (index !== -1) {
1405
+ this.currentStepIndex = index;
1406
+ this.isRunning = true;
1407
+ this.isPaused = false;
1408
+ this.isStepMode = false;
1409
+ console.log(`πŸ”„ Going to step ${data.stepId}`);
1410
+ this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false, isStepMode: false } });
1411
+ }
1412
+ break;
1413
+ }
1414
+ }
1415
+ sendToClients(message) {
1416
+ const messageStr = JSON.stringify(message);
1417
+ this.connectedClients.forEach(client => {
1418
+ if (client.readyState === WebSocket__namespace.OPEN) {
1419
+ client.send(messageStr);
1420
+ }
1421
+ });
1422
+ }
1423
+ async waitForResume() {
1424
+ return new Promise(resolve => {
1425
+ this.pauseResolver = resolve;
1426
+ });
1427
+ }
1428
+ getVisualizationHtml() {
1429
+ const fs = require('fs');
1430
+ const path = require('path');
1431
+ // Read the HTML file (now self-contained with inline CSS and JS)
1432
+ const htmlPath = path.join(__dirname, 'ui/wizard-visualizer.html');
1433
+ return fs.readFileSync(htmlPath, 'utf-8');
1434
+ }
1435
+ }
1436
+ Wizard.TEMPLATE_REGEX = /\{\{(\w+(?:\.\w+)*)\}\}/g;
1437
+ Wizard.WIZARD_TAG_PATTERN = /<(\w+)\s+([^>]*tag-category=["']wizard["'][^>]*)>/gi;
1438
+
1439
+ exports.BaseProvider = BaseProvider;
1440
+ exports.BungeeBuilder = BungeeBuilder;
1441
+ exports.ComputeStep = ComputeStep;
1442
+ exports.LLMClient = LLMClient;
1443
+ exports.Model = Model;
1444
+ exports.MultiProvider = MultiProvider;
1445
+ exports.ProviderRegistry = ProviderRegistry;
1446
+ exports.Step = Step;
1447
+ exports.SwizzyProvider = SwizzyProvider;
1448
+ exports.TextStep = TextStep;
1449
+ exports.Wizard = Wizard;
1450
+ exports.default = Wizard;
1451
+ //# sourceMappingURL=index.js.map