blokctl 0.6.21 → 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/__tests__/modular-observability.capstone.e2e.test.js +72 -0
- package/dist/commands/create/node.js +46 -66
- package/dist/commands/create/project.js +60 -9
- package/dist/commands/create/utils/Examples.d.ts +11 -23
- package/dist/commands/create/utils/Examples.js +206 -421
- package/dist/commands/dev/index.js +40 -1
- package/dist/commands/generate/NodeGenerator.d.ts +0 -2
- package/dist/commands/generate/NodeGenerator.js +0 -20
- package/dist/commands/generate/RuntimeGenerator.d.ts +0 -2
- package/dist/commands/generate/RuntimeGenerator.js +0 -19
- package/dist/commands/generate/RuntimeGenerator.test.js +0 -29
- package/dist/commands/generate/TriggerGenerator.d.ts +0 -2
- package/dist/commands/generate/TriggerGenerator.js +0 -19
- package/dist/commands/generate/WorkflowGenerator.d.ts +0 -2
- package/dist/commands/generate/WorkflowGenerator.js +4 -23
- package/dist/commands/generate/WorkflowGenerator.test.js +2 -2
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +0 -12
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +0 -12
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +0 -14
- package/dist/commands/generate/prompts/create-fn-node.system.js +10 -17
- package/dist/commands/generate/prompts/create-workflow.system.js +114 -375
- package/dist/commands/generate/validators/WorkflowValidator.js +80 -5
- package/dist/commands/generate/validators/WorkflowValidator.test.js +66 -0
- package/dist/commands/monitor/monitor-component.js +5 -5
- package/dist/commands/observability/add.d.ts +2 -0
- package/dist/commands/observability/add.js +113 -0
- package/dist/commands/observability/alerting-module.test.js +43 -0
- package/dist/commands/observability/apply.d.ts +10 -0
- package/dist/commands/observability/apply.js +11 -0
- package/dist/commands/observability/descriptor.d.ts +37 -0
- package/dist/commands/observability/descriptor.js +203 -0
- package/dist/commands/observability/descriptor.test.d.ts +1 -0
- package/dist/commands/observability/descriptor.test.js +40 -0
- package/dist/commands/observability/index.d.ts +1 -0
- package/dist/commands/observability/index.js +53 -0
- package/dist/commands/observability/list.d.ts +2 -0
- package/dist/commands/observability/list.js +45 -0
- package/dist/commands/observability/logging-module.test.d.ts +1 -0
- package/dist/commands/observability/logging-module.test.js +43 -0
- package/dist/commands/observability/obs-stack-module.test.d.ts +1 -0
- package/dist/commands/observability/obs-stack-module.test.js +33 -0
- package/dist/commands/observability/remove.d.ts +2 -0
- package/dist/commands/observability/remove.js +62 -0
- package/dist/commands/observability/shared.d.ts +6 -0
- package/dist/commands/observability/shared.js +23 -0
- package/dist/commands/observability/status.d.ts +2 -0
- package/dist/commands/observability/status.js +36 -0
- package/dist/commands/observability/tracing-module.test.d.ts +1 -0
- package/dist/commands/observability/tracing-module.test.js +42 -0
- package/dist/commands/profile/index.js +7 -10
- package/dist/commands/watch/format.d.ts +23 -0
- package/dist/commands/watch/format.js +60 -0
- package/dist/commands/watch/index.d.ts +1 -0
- package/dist/commands/watch/index.js +53 -0
- package/dist/commands/watch/sse.d.ts +16 -0
- package/dist/commands/watch/sse.js +82 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/services/obs-setup.d.ts +5 -0
- package/dist/services/obs-setup.js +68 -0
- package/dist/services/obs-setup.test.d.ts +1 -0
- package/dist/services/obs-setup.test.js +71 -0
- package/dist/services/obs-tiers.d.ts +9 -0
- package/dist/services/obs-tiers.js +16 -0
- package/dist/services/observability-mutations.d.ts +4 -0
- package/dist/services/observability-mutations.js +46 -0
- package/dist/services/observability-mutations.test.d.ts +1 -0
- package/dist/services/observability-mutations.test.js +57 -0
- package/dist/services/runtime-setup.d.ts +12 -1
- package/dist/services/runtime-setup.js +274 -14
- package/dist/studio-dist/assets/{index-BD8_9YPN.js → index-CnFqCRQe.js} +17 -17
- package/dist/studio-dist/index.html +1 -1
- package/package.json +3 -3
- package/dist/commands/generate/GenerationAnalytics.d.ts +0 -61
- package/dist/commands/generate/GenerationAnalytics.js +0 -163
- package/dist/commands/generate/GenerationAnalytics.test.js +0 -407
- package/dist/commands/generate/PromptVersioning.d.ts +0 -25
- package/dist/commands/generate/PromptVersioning.js +0 -71
- package/dist/commands/generate/PromptVersioning.test.js +0 -120
- /package/dist/{commands/generate/GenerationAnalytics.test.d.ts → __tests__/modular-observability.capstone.e2e.test.d.ts} +0 -0
- /package/dist/commands/{generate/PromptVersioning.test.d.ts → observability/alerting-module.test.d.ts} +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import child_process from "node:child_process";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import util from "node:util";
|
|
3
5
|
import fsExtra from "fs-extra";
|
|
4
6
|
import { waitForGrpcPort } from "../../services/health-probe.js";
|
|
5
7
|
import { detectRr } from "../../services/runtime-detector.js";
|
|
6
|
-
import { readProjectConfig, validateProjectRuntimes } from "../../services/runtime-setup.js";
|
|
8
|
+
import { generateCSharpNodeRegistry, generateGoNodeRegistry, generateJavaNodeRegistry, generateRustNodeRegistry, readProjectConfig, validateProjectRuntimes, } from "../../services/runtime-setup.js";
|
|
9
|
+
const exec = util.promisify(child_process.exec);
|
|
7
10
|
const runningProcesses = [];
|
|
8
11
|
function spawnProcess(cmd, args, name, currentPath, cwd, env) {
|
|
9
12
|
const child = spawn(cmd, args, {
|
|
@@ -98,6 +101,42 @@ export async function devProject(opts) {
|
|
|
98
101
|
HOST: "0.0.0.0",
|
|
99
102
|
BLOK_TRANSPORT: "grpc",
|
|
100
103
|
};
|
|
104
|
+
if (rt.kind === "python3" || rt.kind === "ruby" || rt.kind === "php") {
|
|
105
|
+
env.BLOK_NODES_DIR = path.resolve(currentPath, "runtimes", rt.kind, "nodes");
|
|
106
|
+
}
|
|
107
|
+
if (rt.kind === "go") {
|
|
108
|
+
try {
|
|
109
|
+
generateGoNodeRegistry(currentPath);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.log(` Warning: Go user-node codegen failed: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (rt.kind === "rust") {
|
|
116
|
+
try {
|
|
117
|
+
generateRustNodeRegistry(currentPath);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
console.log(` Warning: Rust user-node codegen failed: ${err.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (rt.kind === "csharp") {
|
|
124
|
+
try {
|
|
125
|
+
generateCSharpNodeRegistry(currentPath);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.log(` Warning: C# user-node codegen failed: ${err.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (rt.kind === "java") {
|
|
132
|
+
try {
|
|
133
|
+
generateJavaNodeRegistry(currentPath);
|
|
134
|
+
await exec("mvn package -q -DskipTests", { cwd: runtimeCwd, timeout: 300000 });
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.log(` Warning: Java user-node codegen/build failed: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
101
140
|
runtimeDefs.push({
|
|
102
141
|
cmd,
|
|
103
142
|
args,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
3
|
import { generateText } from "ai";
|
|
4
|
-
import { GenerationAnalytics } from "./GenerationAnalytics.js";
|
|
5
|
-
import { getVersionStamp, registerPromptContent } from "./PromptVersioning.js";
|
|
6
4
|
import createFnNodeSystemPrompt from "./prompts/create-fn-node.system.js";
|
|
7
5
|
import createNodeSystemPrompt from "./prompts/create-node.system.js";
|
|
8
6
|
import * as CompilationValidator from "./validators/CompilationValidator.js";
|
|
@@ -10,10 +8,6 @@ import * as NodeValidator from "./validators/NodeValidator.js";
|
|
|
10
8
|
export default class NodeGenerator {
|
|
11
9
|
MAX_VALIDATION_ATTEMPTS = 3;
|
|
12
10
|
async generateNode(nodeName, userPrompt, apiKey, update = false, nodeStyle = "function") {
|
|
13
|
-
const analytics = GenerationAnalytics.getInstance();
|
|
14
|
-
const getElapsed = analytics.startTimer();
|
|
15
|
-
const promptId = nodeStyle === "function" ? "create-fn-node" : "create-node";
|
|
16
|
-
const promptVersion = getVersionStamp(promptId);
|
|
17
11
|
const openai = createOpenAI({
|
|
18
12
|
compatibility: "strict",
|
|
19
13
|
apiKey: apiKey,
|
|
@@ -21,7 +15,6 @@ export default class NodeGenerator {
|
|
|
21
15
|
const promptTemplate = nodeStyle === "function" ? createFnNodeSystemPrompt : createNodeSystemPrompt;
|
|
22
16
|
let prompt = promptTemplate.prompt;
|
|
23
17
|
let existingCode = null;
|
|
24
|
-
registerPromptContent(promptId, prompt);
|
|
25
18
|
if (update) {
|
|
26
19
|
const dirName = nodeName.toLowerCase().replace(/\s+/g, "-");
|
|
27
20
|
const dirPath = process.cwd();
|
|
@@ -72,17 +65,6 @@ export default class NodeGenerator {
|
|
|
72
65
|
console.log(`⚠️ Validation failed (attempt ${attempts}/${this.MAX_VALIDATION_ATTEMPTS}). Retrying with feedback...`);
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
|
-
const durationMs = getElapsed();
|
|
76
|
-
analytics.recordEvent({
|
|
77
|
-
type: "node",
|
|
78
|
-
subtype: nodeStyle,
|
|
79
|
-
name: nodeName,
|
|
80
|
-
success: isValid,
|
|
81
|
-
attempts,
|
|
82
|
-
durationMs,
|
|
83
|
-
errors: allErrors,
|
|
84
|
-
promptVersion,
|
|
85
|
-
});
|
|
86
68
|
return {
|
|
87
69
|
nodeName,
|
|
88
70
|
userPrompt,
|
|
@@ -92,8 +74,6 @@ export default class NodeGenerator {
|
|
|
92
74
|
errors: validationErrors,
|
|
93
75
|
warnings: validationWarnings,
|
|
94
76
|
attempts,
|
|
95
|
-
promptVersion,
|
|
96
|
-
durationMs,
|
|
97
77
|
},
|
|
98
78
|
};
|
|
99
79
|
}
|
|
@@ -11,8 +11,6 @@ export type RuntimeInformation = {
|
|
|
11
11
|
errors: string[];
|
|
12
12
|
warnings: string[];
|
|
13
13
|
attempts: number;
|
|
14
|
-
promptVersion?: string;
|
|
15
|
-
durationMs?: number;
|
|
16
14
|
};
|
|
17
15
|
};
|
|
18
16
|
declare const SUPPORTED_LANGUAGES: readonly ["go", "java", "rust", "python", "csharp", "php", "ruby"];
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
3
|
import { generateText } from "ai";
|
|
4
|
-
import { GenerationAnalytics } from "./GenerationAnalytics.js";
|
|
5
|
-
import { getVersionStamp, registerPromptContent } from "./PromptVersioning.js";
|
|
6
4
|
import createRuntimeSystemPrompt from "./prompts/create-runtime.system.js";
|
|
7
5
|
const SUPPORTED_LANGUAGES = ["go", "java", "rust", "python", "csharp", "php", "ruby"];
|
|
8
6
|
export function isSupportedLanguage(lang) {
|
|
@@ -11,15 +9,11 @@ export function isSupportedLanguage(lang) {
|
|
|
11
9
|
export default class RuntimeGenerator {
|
|
12
10
|
MAX_VALIDATION_ATTEMPTS = 3;
|
|
13
11
|
async generateRuntime(language, userPrompt, apiKey, update = false, existingPath) {
|
|
14
|
-
const analytics = GenerationAnalytics.getInstance();
|
|
15
|
-
const getElapsed = analytics.startTimer();
|
|
16
|
-
const promptVersion = getVersionStamp("create-runtime");
|
|
17
12
|
const openai = createOpenAI({
|
|
18
13
|
compatibility: "strict",
|
|
19
14
|
apiKey: apiKey,
|
|
20
15
|
});
|
|
21
16
|
let prompt = createRuntimeSystemPrompt.prompt;
|
|
22
|
-
registerPromptContent("create-runtime", prompt);
|
|
23
17
|
if (update && existingPath) {
|
|
24
18
|
const existingContent = this.readExistingRuntime(existingPath);
|
|
25
19
|
prompt = `${createRuntimeSystemPrompt.updatePrompt}\n\n${existingContent}`;
|
|
@@ -54,17 +48,6 @@ export default class RuntimeGenerator {
|
|
|
54
48
|
}
|
|
55
49
|
}
|
|
56
50
|
const files = this.parseFiles(generatedCode, language);
|
|
57
|
-
const durationMs = getElapsed();
|
|
58
|
-
analytics.recordEvent({
|
|
59
|
-
type: "node",
|
|
60
|
-
subtype: `runtime-${language}`,
|
|
61
|
-
name: `runtime-${language}`,
|
|
62
|
-
success: isValid,
|
|
63
|
-
attempts,
|
|
64
|
-
durationMs,
|
|
65
|
-
errors: allErrors,
|
|
66
|
-
promptVersion,
|
|
67
|
-
});
|
|
68
51
|
return {
|
|
69
52
|
language,
|
|
70
53
|
userPrompt,
|
|
@@ -75,8 +58,6 @@ export default class RuntimeGenerator {
|
|
|
75
58
|
errors: validationErrors,
|
|
76
59
|
warnings: validationWarnings,
|
|
77
60
|
attempts,
|
|
78
|
-
promptVersion,
|
|
79
|
-
durationMs,
|
|
80
61
|
},
|
|
81
62
|
};
|
|
82
63
|
}
|
|
@@ -6,7 +6,6 @@ vi.mock("ai", () => ({
|
|
|
6
6
|
generateText: vi.fn(),
|
|
7
7
|
}));
|
|
8
8
|
import { generateText } from "ai";
|
|
9
|
-
import { GenerationAnalytics } from "./GenerationAnalytics.js";
|
|
10
9
|
import RuntimeGenerator, { isSupportedLanguage } from "./RuntimeGenerator.js";
|
|
11
10
|
const mockedGenerateText = vi.mocked(generateText);
|
|
12
11
|
describe("RuntimeGenerator", () => {
|
|
@@ -15,7 +14,6 @@ describe("RuntimeGenerator", () => {
|
|
|
15
14
|
generator = new RuntimeGenerator();
|
|
16
15
|
vi.clearAllMocks();
|
|
17
16
|
vi.spyOn(console, "log").mockImplementation(() => { });
|
|
18
|
-
GenerationAnalytics.resetInstance();
|
|
19
17
|
});
|
|
20
18
|
afterEach(() => {
|
|
21
19
|
vi.restoreAllMocks();
|
|
@@ -433,39 +431,12 @@ func main() {
|
|
|
433
431
|
expect(result.validationResult?.attempts).toBe(3);
|
|
434
432
|
expect(result.validationResult?.errors.length).toBeGreaterThan(0);
|
|
435
433
|
});
|
|
436
|
-
it("should record analytics event", async () => {
|
|
437
|
-
mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
|
|
438
|
-
await generator.generateRuntime("go", "test", "test-key");
|
|
439
|
-
const analytics = GenerationAnalytics.getInstance();
|
|
440
|
-
const stats = analytics.getStats();
|
|
441
|
-
expect(stats.totalGenerations).toBe(1);
|
|
442
|
-
expect(stats.successCount).toBe(1);
|
|
443
|
-
});
|
|
444
|
-
it("should record failed analytics event", async () => {
|
|
445
|
-
mockedGenerateText.mockResolvedValue({ text: "invalid" });
|
|
446
|
-
await generator.generateRuntime("java", "test", "test-key");
|
|
447
|
-
const analytics = GenerationAnalytics.getInstance();
|
|
448
|
-
const stats = analytics.getStats();
|
|
449
|
-
expect(stats.totalGenerations).toBe(1);
|
|
450
|
-
expect(stats.failureCount).toBe(1);
|
|
451
|
-
});
|
|
452
434
|
it("should strip markdown fences from LLM output", async () => {
|
|
453
435
|
const wrappedCode = `\`\`\`go\n${validGoRuntime}\n\`\`\``;
|
|
454
436
|
mockedGenerateText.mockResolvedValueOnce({ text: wrappedCode });
|
|
455
437
|
const result = await generator.generateRuntime("go", "test", "test-key");
|
|
456
438
|
expect(result.validationResult?.valid).toBe(true);
|
|
457
439
|
});
|
|
458
|
-
it("should include prompt version in validation result", async () => {
|
|
459
|
-
mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
|
|
460
|
-
const result = await generator.generateRuntime("go", "test", "test-key");
|
|
461
|
-
expect(result.validationResult?.promptVersion).toBe("create-runtime@1.0.0");
|
|
462
|
-
});
|
|
463
|
-
it("should include duration in validation result", async () => {
|
|
464
|
-
mockedGenerateText.mockResolvedValueOnce({ text: validGoRuntime });
|
|
465
|
-
const result = await generator.generateRuntime("go", "test", "test-key");
|
|
466
|
-
expect(result.validationResult?.durationMs).toBeDefined();
|
|
467
|
-
expect(typeof result.validationResult?.durationMs).toBe("number");
|
|
468
|
-
});
|
|
469
440
|
});
|
|
470
441
|
describe("language-specific validation", () => {
|
|
471
442
|
it("should warn Go code without go.mod", () => {
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
3
|
import { generateText } from "ai";
|
|
4
|
-
import { GenerationAnalytics } from "./GenerationAnalytics.js";
|
|
5
|
-
import { getVersionStamp, registerPromptContent } from "./PromptVersioning.js";
|
|
6
4
|
import createTriggerSystemPrompt from "./prompts/create-trigger.system.js";
|
|
7
5
|
import * as CompilationValidator from "./validators/CompilationValidator.js";
|
|
8
6
|
export default class TriggerGenerator {
|
|
9
7
|
MAX_VALIDATION_ATTEMPTS = 3;
|
|
10
8
|
async generateTrigger(triggerName, triggerType, userPrompt, apiKey, update = false, existingTriggerPath) {
|
|
11
|
-
const analytics = GenerationAnalytics.getInstance();
|
|
12
|
-
const getElapsed = analytics.startTimer();
|
|
13
|
-
const promptVersion = getVersionStamp("create-trigger");
|
|
14
9
|
const openai = createOpenAI({
|
|
15
10
|
compatibility: "strict",
|
|
16
11
|
apiKey: apiKey,
|
|
17
12
|
});
|
|
18
13
|
let prompt = createTriggerSystemPrompt.prompt;
|
|
19
|
-
registerPromptContent("create-trigger", prompt);
|
|
20
14
|
if (update && existingTriggerPath) {
|
|
21
15
|
const existingContent = fs.readFileSync(existingTriggerPath, "utf8");
|
|
22
16
|
prompt = `${createTriggerSystemPrompt.updatePrompt}\n\n${existingContent}`;
|
|
@@ -58,17 +52,6 @@ export default class TriggerGenerator {
|
|
|
58
52
|
console.log(`⚠️ Trigger validation failed (attempt ${attempts}/${this.MAX_VALIDATION_ATTEMPTS}). Retrying with feedback...`);
|
|
59
53
|
}
|
|
60
54
|
}
|
|
61
|
-
const durationMs = getElapsed();
|
|
62
|
-
analytics.recordEvent({
|
|
63
|
-
type: "trigger",
|
|
64
|
-
subtype: triggerType,
|
|
65
|
-
name: triggerName,
|
|
66
|
-
success: isValid,
|
|
67
|
-
attempts,
|
|
68
|
-
durationMs,
|
|
69
|
-
errors: allErrors,
|
|
70
|
-
promptVersion,
|
|
71
|
-
});
|
|
72
55
|
return {
|
|
73
56
|
triggerName,
|
|
74
57
|
triggerType,
|
|
@@ -79,8 +62,6 @@ export default class TriggerGenerator {
|
|
|
79
62
|
errors: validationErrors,
|
|
80
63
|
warnings: validationWarnings,
|
|
81
64
|
attempts,
|
|
82
|
-
promptVersion,
|
|
83
|
-
durationMs,
|
|
84
65
|
},
|
|
85
66
|
};
|
|
86
67
|
}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
3
|
import { generateText } from "ai";
|
|
4
|
-
import { GenerationAnalytics } from "./GenerationAnalytics.js";
|
|
5
|
-
import { getVersionStamp, registerPromptContent } from "./PromptVersioning.js";
|
|
6
4
|
import createWorkflowSystemPrompt from "./prompts/create-workflow.system.js";
|
|
7
5
|
import * as WorkflowValidator from "./validators/WorkflowValidator.js";
|
|
8
6
|
export default class WorkflowGenerator {
|
|
9
7
|
MAX_VALIDATION_ATTEMPTS = 3;
|
|
10
8
|
async generateWorkflow(workflowName, userPrompt, apiKey, triggerType, update = false, existingWorkflowPath) {
|
|
11
|
-
const analytics = GenerationAnalytics.getInstance();
|
|
12
|
-
const getElapsed = analytics.startTimer();
|
|
13
|
-
const promptVersion = getVersionStamp("create-workflow");
|
|
14
9
|
const openai = createOpenAI({
|
|
15
10
|
compatibility: "strict",
|
|
16
11
|
apiKey: apiKey,
|
|
17
12
|
});
|
|
18
13
|
let prompt = createWorkflowSystemPrompt.prompt;
|
|
19
|
-
registerPromptContent("create-workflow", prompt);
|
|
20
14
|
if (update && existingWorkflowPath) {
|
|
21
15
|
const existingContent = fs.readFileSync(existingWorkflowPath, "utf8");
|
|
22
16
|
prompt = `${createWorkflowSystemPrompt.updatePrompt}\n\n${existingContent}`;
|
|
@@ -50,17 +44,6 @@ export default class WorkflowGenerator {
|
|
|
50
44
|
console.log(`⚠️ Workflow validation failed (attempt ${attempts}/${this.MAX_VALIDATION_ATTEMPTS}). Retrying with feedback...`);
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
|
-
const durationMs = getElapsed();
|
|
54
|
-
analytics.recordEvent({
|
|
55
|
-
type: "workflow",
|
|
56
|
-
subtype: triggerType,
|
|
57
|
-
name: workflowName,
|
|
58
|
-
success: isValid,
|
|
59
|
-
attempts,
|
|
60
|
-
durationMs,
|
|
61
|
-
errors: allErrors,
|
|
62
|
-
promptVersion,
|
|
63
|
-
});
|
|
64
47
|
return {
|
|
65
48
|
workflowName,
|
|
66
49
|
userPrompt,
|
|
@@ -71,8 +54,6 @@ export default class WorkflowGenerator {
|
|
|
71
54
|
errors: validationErrors,
|
|
72
55
|
warnings: validationWarnings,
|
|
73
56
|
attempts,
|
|
74
|
-
promptVersion,
|
|
75
|
-
durationMs,
|
|
76
57
|
},
|
|
77
58
|
};
|
|
78
59
|
}
|
|
@@ -120,11 +101,11 @@ export default class WorkflowGenerator {
|
|
|
120
101
|
"```",
|
|
121
102
|
"",
|
|
122
103
|
"Please fix these errors and regenerate the workflow JSON. Common fixes:",
|
|
123
|
-
"- Ensure every step
|
|
104
|
+
"- Ensure every step has a unique `id` and a `use` (or a `branch`) — inputs live inline; there is NO `nodes` map",
|
|
124
105
|
"- Ensure the trigger has exactly one trigger type with valid configuration",
|
|
125
|
-
"- Ensure
|
|
126
|
-
"-
|
|
127
|
-
"-
|
|
106
|
+
"- Ensure branch `when` is a raw `ctx.*` expression (never `js/` or `$.`)",
|
|
107
|
+
"- Reference earlier outputs with `$.state.<id>` and request data with `$.req.*`",
|
|
108
|
+
"- Use a single step with `branch: { when, then, else }` for conditionals (not a `conditions` array)",
|
|
128
109
|
].join("\n");
|
|
129
110
|
return feedback;
|
|
130
111
|
}
|
|
@@ -70,8 +70,8 @@ describe("WorkflowGenerator", () => {
|
|
|
70
70
|
});
|
|
71
71
|
it("should include common fix suggestions", () => {
|
|
72
72
|
const result = createFeedback("Test", "{}", ["Missing node entry"]);
|
|
73
|
-
expect(result).toContain("
|
|
74
|
-
expect(result).toContain("else
|
|
73
|
+
expect(result).toContain("unique `id`");
|
|
74
|
+
expect(result).toContain("branch: { when, then, else }");
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
});
|
|
@@ -115,18 +115,6 @@ describe("NodeGenerator E2E", () => {
|
|
|
115
115
|
const callArgs = mockedGenerateText.mock.calls[0][0];
|
|
116
116
|
expect(callArgs.temperature).toBe(0.2);
|
|
117
117
|
});
|
|
118
|
-
it("should include prompt version in validation result", async () => {
|
|
119
|
-
mockedGenerateText.mockResolvedValueOnce({ text: VALID_FUNCTION_FIRST_NODE });
|
|
120
|
-
mockValidPass();
|
|
121
|
-
const result = await generator.generateNode("test-node", "Create a test node", "test-api-key", false, "function");
|
|
122
|
-
expect(result.validationResult.promptVersion).toContain("create-fn-node@");
|
|
123
|
-
});
|
|
124
|
-
it("should include duration in validation result", async () => {
|
|
125
|
-
mockedGenerateText.mockResolvedValueOnce({ text: VALID_FUNCTION_FIRST_NODE });
|
|
126
|
-
mockValidPass();
|
|
127
|
-
const result = await generator.generateNode("test-node", "Create a test node", "test-api-key", false, "function");
|
|
128
|
-
expect(result.validationResult.durationMs).toBeGreaterThanOrEqual(0);
|
|
129
|
-
});
|
|
130
118
|
});
|
|
131
119
|
describe("validation feedback loop", () => {
|
|
132
120
|
it("should retry with feedback on first failure and succeed on second attempt", async () => {
|
|
@@ -645,18 +645,6 @@ describe("RuntimeGenerator E2E", () => {
|
|
|
645
645
|
expect(mockedGenerateText).toHaveBeenCalledTimes(1);
|
|
646
646
|
});
|
|
647
647
|
});
|
|
648
|
-
describe("analytics integration", () => {
|
|
649
|
-
it("should include prompt version in validation result", async () => {
|
|
650
|
-
mockedGenerateText.mockResolvedValueOnce({ text: VALID_GO_RUNTIME });
|
|
651
|
-
const result = await generator.generateRuntime("go", "Create a runtime", "test-api-key");
|
|
652
|
-
expect(result.validationResult.promptVersion).toContain("create-runtime@");
|
|
653
|
-
});
|
|
654
|
-
it("should include duration in validation result", async () => {
|
|
655
|
-
mockedGenerateText.mockResolvedValueOnce({ text: VALID_GO_RUNTIME });
|
|
656
|
-
const result = await generator.generateRuntime("go", "Create a runtime", "test-api-key");
|
|
657
|
-
expect(result.validationResult.durationMs).toBeGreaterThanOrEqual(0);
|
|
658
|
-
});
|
|
659
|
-
});
|
|
660
648
|
describe("multi-file parsing", () => {
|
|
661
649
|
it("should parse files separated by // FILE: markers", async () => {
|
|
662
650
|
const code = [
|
|
@@ -278,18 +278,4 @@ describe("TriggerGenerator E2E", () => {
|
|
|
278
278
|
expect(system).toContain("loadNodes");
|
|
279
279
|
});
|
|
280
280
|
});
|
|
281
|
-
describe("analytics integration", () => {
|
|
282
|
-
it("should include prompt version in validation result", async () => {
|
|
283
|
-
mockedGenerateText.mockResolvedValueOnce({ text: VALID_QUEUE_TRIGGER });
|
|
284
|
-
mockedValidateCode.mockReturnValue({ success: true, errors: [], warnings: [] });
|
|
285
|
-
const result = await generator.generateTrigger("test-trigger", "queue", "Create a test trigger", "test-api-key");
|
|
286
|
-
expect(result.validationResult.promptVersion).toContain("create-trigger@");
|
|
287
|
-
});
|
|
288
|
-
it("should include duration in validation result", async () => {
|
|
289
|
-
mockedGenerateText.mockResolvedValueOnce({ text: VALID_QUEUE_TRIGGER });
|
|
290
|
-
mockedValidateCode.mockReturnValue({ success: true, errors: [], warnings: [] });
|
|
291
|
-
const result = await generator.generateTrigger("test-trigger", "queue", "Create a test trigger", "test-api-key");
|
|
292
|
-
expect(result.validationResult.durationMs).toBeGreaterThanOrEqual(0);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
281
|
});
|
|
@@ -9,7 +9,7 @@ What to return:
|
|
|
9
9
|
1. Proper imports:
|
|
10
10
|
* \`z\` from \`zod\`
|
|
11
11
|
* \`Context\` from \`@blokjs/shared\`
|
|
12
|
-
* \`defineNode\` from \`@blokjs/
|
|
12
|
+
* \`defineNode\` from \`@blokjs/core\`
|
|
13
13
|
2. A clear and structured \`input\` schema using Zod (z.object with proper types).
|
|
14
14
|
3. A matching \`output\` schema using Zod.
|
|
15
15
|
4. A single exported node instance created via \`defineNode\` with:
|
|
@@ -26,11 +26,11 @@ Constraints:
|
|
|
26
26
|
* The Zod \`output\` schema must fully describe the object returned by \`execute\`.
|
|
27
27
|
* Inside \`execute(ctx, input)\`:
|
|
28
28
|
* Use the strongly-typed \`input\`, which is automatically inferred from the Zod schema.
|
|
29
|
-
* Use \`ctx\` to access request data, configuration, and
|
|
29
|
+
* Use \`ctx\` to access request data, configuration, and logging when needed:
|
|
30
30
|
* \`ctx.request.body\`, \`ctx.request.query\`, \`ctx.request.params\` for HTTP data
|
|
31
|
-
* \`ctx.vars\` for reading/writing values shared between nodes
|
|
32
31
|
* \`ctx.logger\` for logging
|
|
33
32
|
* \`ctx.env\` for environment variables
|
|
33
|
+
* Do **not** write to \`ctx.state\`/\`ctx.vars\` from a node — just RETURN your output; the runner auto-persists it to \`ctx.state[<step-id>]\` for downstream steps. Upstream values arrive as \`input.*\` (mapped in the workflow), not by reading \`ctx.vars\`.
|
|
34
34
|
* Do **not** construct or return \`BlokResponse\` here; just return a plain object matching the output schema. The wrapper created by \`defineNode\` will call \`setSuccess\` / \`setError\` and handle \`GlobalError\`.
|
|
35
35
|
* On validation errors or runtime errors, you do NOT manually throw \`GlobalError\`; throw/rethrow normal errors. The \`defineNode\` wrapper will catch them and map them to \`GlobalError\` consistently with proper error codes:
|
|
36
36
|
* Zod validation errors → 400 Bad Request
|
|
@@ -52,7 +52,7 @@ Real-World Examples to Guide You:
|
|
|
52
52
|
\`\`\`typescript
|
|
53
53
|
import type { Context } from "@blokjs/shared";
|
|
54
54
|
import { z } from "zod";
|
|
55
|
-
import { defineNode } from "@blokjs/
|
|
55
|
+
import { defineNode } from "@blokjs/core";
|
|
56
56
|
|
|
57
57
|
export default defineNode({
|
|
58
58
|
name: "api-call",
|
|
@@ -102,10 +102,6 @@ export default defineNode({
|
|
|
102
102
|
|
|
103
103
|
ctx.logger.log(\`Request completed in \${duration.toFixed(2)}ms with status \${response.status}\`);
|
|
104
104
|
|
|
105
|
-
if (ctx.vars) {
|
|
106
|
-
ctx.vars["api-response"] = { status: response.status, data };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
105
|
return {
|
|
110
106
|
status: response.status,
|
|
111
107
|
statusText: response.statusText,
|
|
@@ -122,7 +118,7 @@ export default defineNode({
|
|
|
122
118
|
\`\`\`typescript
|
|
123
119
|
import type { Context } from "@blokjs/shared";
|
|
124
120
|
import { z } from "zod";
|
|
125
|
-
import { defineNode } from "@blokjs/
|
|
121
|
+
import { defineNode } from "@blokjs/core";
|
|
126
122
|
|
|
127
123
|
export default defineNode({
|
|
128
124
|
name: "fetch-user",
|
|
@@ -149,11 +145,8 @@ export default defineNode({
|
|
|
149
145
|
// Simulate database fetch
|
|
150
146
|
const user = await fetchUserFromDatabase(input.userId, input.includeMetadata);
|
|
151
147
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
ctx.vars["current-user"] = user;
|
|
155
|
-
}
|
|
156
|
-
|
|
148
|
+
// Just return — the runner persists this to ctx.state["fetch-user"]
|
|
149
|
+
// for any downstream step that maps it in as an input.
|
|
157
150
|
return { user };
|
|
158
151
|
},
|
|
159
152
|
});
|
|
@@ -176,7 +169,7 @@ Template to follow (adapt and fill based on the user's request):
|
|
|
176
169
|
|
|
177
170
|
import type { Context } from "@blokjs/shared";
|
|
178
171
|
import { z } from "zod";
|
|
179
|
-
import { defineNode } from "@blokjs/
|
|
172
|
+
import { defineNode } from "@blokjs/core";
|
|
180
173
|
|
|
181
174
|
/**
|
|
182
175
|
* [Brief description of what this node does]
|
|
@@ -210,11 +203,11 @@ export default defineNode({
|
|
|
210
203
|
//
|
|
211
204
|
// Common patterns:
|
|
212
205
|
// - Read HTTP params: const id = ctx.request.params.id;
|
|
213
|
-
// - Read
|
|
214
|
-
// - Write for future nodes: ctx.vars["this-node-key"] = someValue;
|
|
206
|
+
// - Read upstream values via input.* (mapped from prior steps in the workflow) — NOT ctx.vars
|
|
215
207
|
// - Use input.* fields that match the input schema (TypeScript infers the type automatically)
|
|
216
208
|
// - Log messages: ctx.logger.log("Processing request");
|
|
217
209
|
// - Access environment: const apiKey = ctx.env.API_KEY;
|
|
210
|
+
// - Do NOT write ctx.state/ctx.vars here — just return; the runner persists your output.
|
|
218
211
|
|
|
219
212
|
// TODO: Implement business logic here
|
|
220
213
|
|