blokctl 0.2.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/commands/build/index.d.ts +2 -0
- package/dist/commands/build/index.js +210 -0
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.js +46 -0
- package/dist/commands/cost/index.d.ts +1 -0
- package/dist/commands/cost/index.js +74 -0
- package/dist/commands/create/node.d.ts +2 -0
- package/dist/commands/create/node.js +541 -0
- package/dist/commands/create/project.d.ts +2 -0
- package/dist/commands/create/project.js +941 -0
- package/dist/commands/create/utils/Examples.d.ts +39 -0
- package/dist/commands/create/utils/Examples.js +983 -0
- package/dist/commands/create/workflow.d.ts +2 -0
- package/dist/commands/create/workflow.js +109 -0
- package/dist/commands/deploy/index.d.ts +2 -0
- package/dist/commands/deploy/index.js +176 -0
- package/dist/commands/dev/index.d.ts +2 -0
- package/dist/commands/dev/index.js +190 -0
- package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
- package/dist/commands/generate/GenerationAnalytics.js +162 -0
- package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
- package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
- package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
- package/dist/commands/generate/NodeFileWriter.js +240 -0
- package/dist/commands/generate/NodeGenerator.d.ts +20 -0
- package/dist/commands/generate/NodeGenerator.js +181 -0
- package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
- package/dist/commands/generate/NodeGenerator.test.js +101 -0
- package/dist/commands/generate/PromptVersioning.d.ts +25 -0
- package/dist/commands/generate/PromptVersioning.js +71 -0
- package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
- package/dist/commands/generate/PromptVersioning.test.js +120 -0
- package/dist/commands/generate/RegisterNode.d.ts +3 -0
- package/dist/commands/generate/RegisterNode.js +37 -0
- package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
- package/dist/commands/generate/RuntimeGenerator.js +369 -0
- package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
- package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
- package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
- package/dist/commands/generate/TriggerGenerator.js +220 -0
- package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
- package/dist/commands/generate/TriggerGenerator.test.js +209 -0
- package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
- package/dist/commands/generate/WorkflowGenerator.js +131 -0
- package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
- package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
- package/dist/commands/generate/index.d.ts +1 -0
- package/dist/commands/generate/index.js +418 -0
- package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
- package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
- package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
- package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-node.system.js +114 -0
- package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
- package/dist/commands/generate/prompts/create-readme.system.js +83 -0
- package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
- package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
- package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
- package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
- package/dist/commands/generate/prompts/register-node.system.js +26 -0
- package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
- package/dist/commands/generate/validators/CompilationValidator.js +86 -0
- package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
- package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
- package/dist/commands/generate/validators/NodeValidator.js +217 -0
- package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
- package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
- package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
- package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
- package/dist/commands/generate/validators/index.d.ts +4 -0
- package/dist/commands/generate/validators/index.js +2 -0
- package/dist/commands/graph/index.d.ts +1 -0
- package/dist/commands/graph/index.js +69 -0
- package/dist/commands/install/index.d.ts +1 -0
- package/dist/commands/install/index.js +4 -0
- package/dist/commands/install/node.d.ts +4 -0
- package/dist/commands/install/node.js +136 -0
- package/dist/commands/install/workflow.d.ts +4 -0
- package/dist/commands/install/workflow.js +62 -0
- package/dist/commands/login/index.d.ts +2 -0
- package/dist/commands/login/index.js +77 -0
- package/dist/commands/logout/index.d.ts +2 -0
- package/dist/commands/logout/index.js +20 -0
- package/dist/commands/marketplace/runtime.d.ts +54 -0
- package/dist/commands/marketplace/runtime.js +350 -0
- package/dist/commands/migrate/index.d.ts +1 -0
- package/dist/commands/migrate/index.js +14 -0
- package/dist/commands/migrate/node.d.ts +2 -0
- package/dist/commands/migrate/node.js +110 -0
- package/dist/commands/monitor/index.d.ts +1 -0
- package/dist/commands/monitor/index.js +28 -0
- package/dist/commands/monitor/monitor-component.d.ts +1 -0
- package/dist/commands/monitor/monitor-component.js +271 -0
- package/dist/commands/monitor/static/index.html +2124 -0
- package/dist/commands/monitor/static-web-server.d.ts +1 -0
- package/dist/commands/monitor/static-web-server.js +89 -0
- package/dist/commands/profile/index.d.ts +1 -0
- package/dist/commands/profile/index.js +112 -0
- package/dist/commands/publish/index.d.ts +1 -0
- package/dist/commands/publish/index.js +4 -0
- package/dist/commands/publish/node.d.ts +4 -0
- package/dist/commands/publish/node.js +231 -0
- package/dist/commands/publish/workflow.d.ts +4 -0
- package/dist/commands/publish/workflow.js +165 -0
- package/dist/commands/search/docs.d.ts +17 -0
- package/dist/commands/search/docs.js +179 -0
- package/dist/commands/search/index.d.ts +1 -0
- package/dist/commands/search/index.js +5 -0
- package/dist/commands/search/indexer.d.ts +10 -0
- package/dist/commands/search/indexer.js +265 -0
- package/dist/commands/search/nodes.d.ts +4 -0
- package/dist/commands/search/nodes.js +101 -0
- package/dist/commands/search/workflow.d.ts +4 -0
- package/dist/commands/search/workflow.js +100 -0
- package/dist/commands/trace/index.d.ts +1 -0
- package/dist/commands/trace/index.js +26 -0
- package/dist/commands/trace/startStudio.d.ts +8 -0
- package/dist/commands/trace/startStudio.js +116 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +186 -0
- package/dist/services/commander.d.ts +9 -0
- package/dist/services/commander.js +20 -0
- package/dist/services/constants.d.ts +1 -0
- package/dist/services/constants.js +3 -0
- package/dist/services/local-token-manager.d.ts +14 -0
- package/dist/services/local-token-manager.js +99 -0
- package/dist/services/non-interactive.d.ts +5 -0
- package/dist/services/non-interactive.js +30 -0
- package/dist/services/package-manager.d.ts +35 -0
- package/dist/services/package-manager.js +111 -0
- package/dist/services/posthog.d.ts +31 -0
- package/dist/services/posthog.js +159 -0
- package/dist/services/registry-manager.d.ts +9 -0
- package/dist/services/registry-manager.js +26 -0
- package/dist/services/runtime-detector.d.ts +23 -0
- package/dist/services/runtime-detector.js +181 -0
- package/dist/services/runtime-setup.d.ts +36 -0
- package/dist/services/runtime-setup.js +250 -0
- package/dist/services/utils.d.ts +2 -0
- package/dist/services/utils.js +29 -0
- package/dist/services/workflow-loader.d.ts +30 -0
- package/dist/services/workflow-loader.js +46 -0
- package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
- package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
- package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
- package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
- package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
- package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
- package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
- package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
- package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
- package/dist/studio-dist/favicon.svg +5 -0
- package/dist/studio-dist/index.html +21 -0
- package/package.json +75 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PROMPT_VERSIONS, computeContentHash, getAllPromptVersions, getPromptVersion, getVersionStamp, registerPromptContent, } from "./PromptVersioning.js";
|
|
3
|
+
describe("PromptVersioning", () => {
|
|
4
|
+
describe("PROMPT_VERSIONS registry", () => {
|
|
5
|
+
it("should have entries for all prompt types", () => {
|
|
6
|
+
expect(PROMPT_VERSIONS["create-fn-node"]).toBeDefined();
|
|
7
|
+
expect(PROMPT_VERSIONS["create-node"]).toBeDefined();
|
|
8
|
+
expect(PROMPT_VERSIONS["create-workflow"]).toBeDefined();
|
|
9
|
+
expect(PROMPT_VERSIONS["create-trigger"]).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
it("should have valid version strings for all entries", () => {
|
|
12
|
+
for (const [id, version] of Object.entries(PROMPT_VERSIONS)) {
|
|
13
|
+
expect(version.version).toMatch(/^\d+\.\d+\.\d+$/);
|
|
14
|
+
expect(version.id).toBe(id);
|
|
15
|
+
expect(version.changelog).toBeTruthy();
|
|
16
|
+
expect(version.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
it("should have function-first node at v2.0.0 with defineNode pattern", () => {
|
|
20
|
+
const fnNode = PROMPT_VERSIONS["create-fn-node"];
|
|
21
|
+
expect(fnNode.version).toBe("2.0.0");
|
|
22
|
+
expect(fnNode.changelog).toContain("defineNode");
|
|
23
|
+
});
|
|
24
|
+
it("should have workflow prompt at v2.0.0 with trigger types", () => {
|
|
25
|
+
const workflow = PROMPT_VERSIONS["create-workflow"];
|
|
26
|
+
expect(workflow.version).toBe("2.0.0");
|
|
27
|
+
expect(workflow.changelog).toContain("trigger");
|
|
28
|
+
});
|
|
29
|
+
it("should have trigger prompt at v2.0.0 with TriggerBase", () => {
|
|
30
|
+
const trigger = PROMPT_VERSIONS["create-trigger"];
|
|
31
|
+
expect(trigger.version).toBe("2.0.0");
|
|
32
|
+
expect(trigger.changelog).toContain("TriggerBase");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe("getPromptVersion", () => {
|
|
36
|
+
it("should return version for known prompt ID", () => {
|
|
37
|
+
const version = getPromptVersion("create-fn-node");
|
|
38
|
+
expect(version).toBeDefined();
|
|
39
|
+
expect(version.id).toBe("create-fn-node");
|
|
40
|
+
});
|
|
41
|
+
it("should return undefined for unknown prompt ID", () => {
|
|
42
|
+
const version = getPromptVersion("nonexistent-prompt");
|
|
43
|
+
expect(version).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("getAllPromptVersions", () => {
|
|
47
|
+
it("should return all registered prompt versions", () => {
|
|
48
|
+
const versions = getAllPromptVersions();
|
|
49
|
+
expect(versions.length).toBeGreaterThanOrEqual(4);
|
|
50
|
+
});
|
|
51
|
+
it("should include all known prompt IDs", () => {
|
|
52
|
+
const versions = getAllPromptVersions();
|
|
53
|
+
const ids = versions.map((v) => v.id);
|
|
54
|
+
expect(ids).toContain("create-fn-node");
|
|
55
|
+
expect(ids).toContain("create-node");
|
|
56
|
+
expect(ids).toContain("create-workflow");
|
|
57
|
+
expect(ids).toContain("create-trigger");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("getVersionStamp", () => {
|
|
61
|
+
it("should return formatted version stamp for known prompt", () => {
|
|
62
|
+
const stamp = getVersionStamp("create-fn-node");
|
|
63
|
+
expect(stamp).toBe("create-fn-node@2.0.0");
|
|
64
|
+
});
|
|
65
|
+
it("should return unknown stamp for unregistered prompt", () => {
|
|
66
|
+
const stamp = getVersionStamp("unknown-prompt");
|
|
67
|
+
expect(stamp).toBe("unknown-prompt@unknown");
|
|
68
|
+
});
|
|
69
|
+
it("should return stamps in id@version format", () => {
|
|
70
|
+
for (const [id, version] of Object.entries(PROMPT_VERSIONS)) {
|
|
71
|
+
const stamp = getVersionStamp(id);
|
|
72
|
+
expect(stamp).toBe(`${id}@${version.version}`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("computeContentHash", () => {
|
|
77
|
+
it("should return a non-empty string", () => {
|
|
78
|
+
const hash = computeContentHash("test content");
|
|
79
|
+
expect(hash).toBeTruthy();
|
|
80
|
+
expect(typeof hash).toBe("string");
|
|
81
|
+
});
|
|
82
|
+
it("should return consistent hash for same content", () => {
|
|
83
|
+
const hash1 = computeContentHash("hello world");
|
|
84
|
+
const hash2 = computeContentHash("hello world");
|
|
85
|
+
expect(hash1).toBe(hash2);
|
|
86
|
+
});
|
|
87
|
+
it("should return different hashes for different content", () => {
|
|
88
|
+
const hash1 = computeContentHash("content A");
|
|
89
|
+
const hash2 = computeContentHash("content B");
|
|
90
|
+
expect(hash1).not.toBe(hash2);
|
|
91
|
+
});
|
|
92
|
+
it("should handle empty string", () => {
|
|
93
|
+
const hash = computeContentHash("");
|
|
94
|
+
expect(hash).toBe("0");
|
|
95
|
+
});
|
|
96
|
+
it("should handle long content", () => {
|
|
97
|
+
const longContent = "a".repeat(10000);
|
|
98
|
+
const hash = computeContentHash(longContent);
|
|
99
|
+
expect(hash).toBeTruthy();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("registerPromptContent", () => {
|
|
103
|
+
it("should compute and store content hash for known prompt", () => {
|
|
104
|
+
registerPromptContent("create-fn-node", "test prompt content");
|
|
105
|
+
const version = getPromptVersion("create-fn-node");
|
|
106
|
+
expect(version.contentHash).toBeTruthy();
|
|
107
|
+
expect(version.contentHash).toBe(computeContentHash("test prompt content"));
|
|
108
|
+
});
|
|
109
|
+
it("should not throw for unknown prompt ID", () => {
|
|
110
|
+
expect(() => registerPromptContent("nonexistent", "content")).not.toThrow();
|
|
111
|
+
});
|
|
112
|
+
it("should update hash when content changes", () => {
|
|
113
|
+
registerPromptContent("create-node", "content v1");
|
|
114
|
+
const hash1 = getPromptVersion("create-node").contentHash;
|
|
115
|
+
registerPromptContent("create-node", "content v2");
|
|
116
|
+
const hash2 = getPromptVersion("create-node").contentHash;
|
|
117
|
+
expect(hash1).not.toBe(hash2);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
|
+
import { generateText } from "ai";
|
|
4
|
+
import registerNodeSystemPrompt from "./prompts/register-node.system.js";
|
|
5
|
+
export default class RegisterNode {
|
|
6
|
+
async generateNodesFile(nodeName, importPath, code, apiKey) {
|
|
7
|
+
const dirPath = process.cwd();
|
|
8
|
+
const nodesFile = `${dirPath}/src/Nodes.ts`;
|
|
9
|
+
if (!fs.existsSync(nodesFile)) {
|
|
10
|
+
throw new Error("The Nodes.ts file does not exist. Please ensure you are in the correct project directory.");
|
|
11
|
+
}
|
|
12
|
+
const fileContent = fs.readFileSync(nodesFile, "utf8");
|
|
13
|
+
if (fileContent.includes(`"${nodeName}"`)) {
|
|
14
|
+
console.log(`\nNode "${nodeName}" is already registered in Nodes.ts.`);
|
|
15
|
+
}
|
|
16
|
+
const openai = createOpenAI({
|
|
17
|
+
compatibility: "strict",
|
|
18
|
+
apiKey: apiKey,
|
|
19
|
+
});
|
|
20
|
+
const { text } = await generateText({
|
|
21
|
+
model: openai("gpt-4o"),
|
|
22
|
+
system: `${registerNodeSystemPrompt.prompt} \n${fileContent}`,
|
|
23
|
+
prompt: `Node information:
|
|
24
|
+
|
|
25
|
+
Name: ${nodeName} (This is the key in the nodes object)
|
|
26
|
+
Import Path: ${importPath}
|
|
27
|
+
Source Code:
|
|
28
|
+
${code}
|
|
29
|
+
|
|
30
|
+
Take the class name from the source code and use it to register the node in Nodes.ts.`,
|
|
31
|
+
temperature: 0.2,
|
|
32
|
+
});
|
|
33
|
+
const cleaned = text.replace(/^```typescript\s*([\s\S]*?)\s*```$/gm, "$1");
|
|
34
|
+
fs.writeFileSync(nodesFile, cleaned, "utf8");
|
|
35
|
+
return nodesFile;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type RuntimeInformation = {
|
|
2
|
+
language: string;
|
|
3
|
+
userPrompt: string;
|
|
4
|
+
files: Array<{
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}>;
|
|
8
|
+
rawCode: string;
|
|
9
|
+
validationResult?: {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
errors: string[];
|
|
12
|
+
warnings: string[];
|
|
13
|
+
attempts: number;
|
|
14
|
+
promptVersion?: string;
|
|
15
|
+
durationMs?: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
declare const SUPPORTED_LANGUAGES: readonly ["go", "java", "rust", "python", "csharp", "php", "ruby"];
|
|
19
|
+
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
20
|
+
export declare function isSupportedLanguage(lang: string): lang is SupportedLanguage;
|
|
21
|
+
export default class RuntimeGenerator {
|
|
22
|
+
private readonly MAX_VALIDATION_ATTEMPTS;
|
|
23
|
+
generateRuntime(language: string, userPrompt: string, apiKey: string, update?: boolean, existingPath?: string): Promise<RuntimeInformation>;
|
|
24
|
+
validateRuntimeStructure(code: string, language: string): {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
errors: string[];
|
|
27
|
+
warnings: string[];
|
|
28
|
+
};
|
|
29
|
+
parseFiles(code: string, language: string): Array<{
|
|
30
|
+
path: string;
|
|
31
|
+
content: string;
|
|
32
|
+
}>;
|
|
33
|
+
private buildEnhancedPrompt;
|
|
34
|
+
private createFeedbackPrompt;
|
|
35
|
+
private getSemanticGuidance;
|
|
36
|
+
private validateLanguageSpecific;
|
|
37
|
+
private readExistingRuntime;
|
|
38
|
+
private getFileExtension;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
|
+
import { generateText } from "ai";
|
|
4
|
+
import { GenerationAnalytics } from "./GenerationAnalytics.js";
|
|
5
|
+
import { getVersionStamp, registerPromptContent } from "./PromptVersioning.js";
|
|
6
|
+
import createRuntimeSystemPrompt from "./prompts/create-runtime.system.js";
|
|
7
|
+
const SUPPORTED_LANGUAGES = ["go", "java", "rust", "python", "csharp", "php", "ruby"];
|
|
8
|
+
export function isSupportedLanguage(lang) {
|
|
9
|
+
return SUPPORTED_LANGUAGES.includes(lang.toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
export default class RuntimeGenerator {
|
|
12
|
+
MAX_VALIDATION_ATTEMPTS = 3;
|
|
13
|
+
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
|
+
const openai = createOpenAI({
|
|
18
|
+
compatibility: "strict",
|
|
19
|
+
apiKey: apiKey,
|
|
20
|
+
});
|
|
21
|
+
let prompt = createRuntimeSystemPrompt.prompt;
|
|
22
|
+
registerPromptContent("create-runtime", prompt);
|
|
23
|
+
if (update && existingPath) {
|
|
24
|
+
const existingContent = this.readExistingRuntime(existingPath);
|
|
25
|
+
prompt = `${createRuntimeSystemPrompt.updatePrompt}\n\n${existingContent}`;
|
|
26
|
+
}
|
|
27
|
+
const enhancedPrompt = this.buildEnhancedPrompt(userPrompt, language);
|
|
28
|
+
let attempts = 0;
|
|
29
|
+
let generatedCode = "";
|
|
30
|
+
let validationErrors = [];
|
|
31
|
+
let validationWarnings = [];
|
|
32
|
+
let isValid = false;
|
|
33
|
+
const allErrors = [];
|
|
34
|
+
while (attempts < this.MAX_VALIDATION_ATTEMPTS && !isValid) {
|
|
35
|
+
attempts++;
|
|
36
|
+
let finalPrompt = enhancedPrompt;
|
|
37
|
+
if (attempts > 1 && validationErrors.length > 0) {
|
|
38
|
+
finalPrompt = this.createFeedbackPrompt(enhancedPrompt, generatedCode, validationErrors);
|
|
39
|
+
}
|
|
40
|
+
const { text } = await generateText({
|
|
41
|
+
model: openai("gpt-4o"),
|
|
42
|
+
system: prompt,
|
|
43
|
+
prompt: finalPrompt,
|
|
44
|
+
temperature: 0.2,
|
|
45
|
+
});
|
|
46
|
+
generatedCode = text.replace(/^```(?:\w+)?\s*([\s\S]*?)\s*```$/gm, "$1").trim();
|
|
47
|
+
const structureResult = this.validateRuntimeStructure(generatedCode, language);
|
|
48
|
+
validationErrors = structureResult.errors;
|
|
49
|
+
validationWarnings = structureResult.warnings;
|
|
50
|
+
isValid = structureResult.valid;
|
|
51
|
+
allErrors.push(...validationErrors);
|
|
52
|
+
if (!isValid && attempts < this.MAX_VALIDATION_ATTEMPTS) {
|
|
53
|
+
console.log(`\u26a0\ufe0f Runtime validation failed (attempt ${attempts}/${this.MAX_VALIDATION_ATTEMPTS}). Retrying with feedback...`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
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
|
+
return {
|
|
69
|
+
language,
|
|
70
|
+
userPrompt,
|
|
71
|
+
files,
|
|
72
|
+
rawCode: generatedCode,
|
|
73
|
+
validationResult: {
|
|
74
|
+
valid: isValid,
|
|
75
|
+
errors: validationErrors,
|
|
76
|
+
warnings: validationWarnings,
|
|
77
|
+
attempts,
|
|
78
|
+
promptVersion,
|
|
79
|
+
durationMs,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
validateRuntimeStructure(code, language) {
|
|
84
|
+
const errors = [];
|
|
85
|
+
const warnings = [];
|
|
86
|
+
const fileCount = (code.match(/\/\/ FILE:/gi) || []).length;
|
|
87
|
+
if (fileCount < 2) {
|
|
88
|
+
errors.push("Runtime must contain multiple files (use // FILE: <path> markers). Expected at least SDK core + server + Dockerfile.");
|
|
89
|
+
}
|
|
90
|
+
if (!code.includes("/execute")) {
|
|
91
|
+
errors.push("Missing POST /execute endpoint - required by the Blok Runtime Protocol");
|
|
92
|
+
}
|
|
93
|
+
if (!code.includes("/health")) {
|
|
94
|
+
errors.push("Missing GET /health endpoint - required by the Blok Runtime Protocol");
|
|
95
|
+
}
|
|
96
|
+
if (!code.toLowerCase().includes("context") && !code.toLowerCase().includes("ctx")) {
|
|
97
|
+
errors.push("Missing Context type definition - must map to Blok workflow context");
|
|
98
|
+
}
|
|
99
|
+
const handlerPatterns = ["NodeHandler", "node_handler", "Handler", "execute", "Execute"];
|
|
100
|
+
const hasHandler = handlerPatterns.some((p) => code.includes(p));
|
|
101
|
+
if (!hasHandler) {
|
|
102
|
+
errors.push("Missing NodeHandler interface/trait - nodes must implement an execute method");
|
|
103
|
+
}
|
|
104
|
+
const registryPatterns = ["Registry", "registry", "register", "Register"];
|
|
105
|
+
const hasRegistry = registryPatterns.some((p) => code.includes(p));
|
|
106
|
+
if (!hasRegistry) {
|
|
107
|
+
errors.push("Missing NodeRegistry - must provide node registration and dispatch");
|
|
108
|
+
}
|
|
109
|
+
const hasDockerfile = code.toLowerCase().includes("dockerfile") || code.toLowerCase().includes("docker");
|
|
110
|
+
if (!hasDockerfile) {
|
|
111
|
+
warnings.push("Missing Dockerfile - recommended for container deployment");
|
|
112
|
+
}
|
|
113
|
+
if (!code.includes("success") || !code.includes("data") || !code.includes("errors")) {
|
|
114
|
+
warnings.push("ExecutionResult should include success, data, and errors fields");
|
|
115
|
+
}
|
|
116
|
+
this.validateLanguageSpecific(code, language, errors, warnings);
|
|
117
|
+
return {
|
|
118
|
+
valid: errors.length === 0,
|
|
119
|
+
errors,
|
|
120
|
+
warnings,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
parseFiles(code, language) {
|
|
124
|
+
const files = [];
|
|
125
|
+
const fileRegex = /\/\/\s*FILE:\s*(.+?)(?:\n|\r\n)/gi;
|
|
126
|
+
const parts = code.split(fileRegex);
|
|
127
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
128
|
+
const filePath = parts[i].trim();
|
|
129
|
+
const content = (parts[i + 1] || "").trim();
|
|
130
|
+
if (filePath && content) {
|
|
131
|
+
files.push({ path: filePath, content });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (files.length === 0 && code.trim()) {
|
|
135
|
+
const ext = this.getFileExtension(language);
|
|
136
|
+
files.push({ path: `runtime.${ext}`, content: code.trim() });
|
|
137
|
+
}
|
|
138
|
+
return files;
|
|
139
|
+
}
|
|
140
|
+
buildEnhancedPrompt(userPrompt, language) {
|
|
141
|
+
const parts = [
|
|
142
|
+
`Generate a complete Blok Runtime SDK for the "${language}" programming language.`,
|
|
143
|
+
"",
|
|
144
|
+
userPrompt,
|
|
145
|
+
"",
|
|
146
|
+
`IMPORTANT: Generate a complete, runnable ${language} runtime SDK with:`,
|
|
147
|
+
"1. Core types (Context, Request, Response, ExecutionRequest, ExecutionResult)",
|
|
148
|
+
"2. NodeHandler interface/trait",
|
|
149
|
+
"3. NodeRegistry for node management",
|
|
150
|
+
"4. HTTP server with POST /execute and GET /health endpoints",
|
|
151
|
+
'5. Example "hello-world" node',
|
|
152
|
+
"6. Dockerfile for containerized deployment",
|
|
153
|
+
"7. Build configuration file (go.mod, pom.xml, Cargo.toml, etc.)",
|
|
154
|
+
"",
|
|
155
|
+
"Use // FILE: <relative-path> to separate each file.",
|
|
156
|
+
];
|
|
157
|
+
switch (language.toLowerCase()) {
|
|
158
|
+
case "go":
|
|
159
|
+
parts.push("\nGenerate a Go module with:");
|
|
160
|
+
parts.push("- sdk/blok.go (core types + registry)");
|
|
161
|
+
parts.push("- server/main.go (HTTP server)");
|
|
162
|
+
parts.push("- nodes/hello-world/main.go (example node)");
|
|
163
|
+
parts.push("- go.mod (module definition)");
|
|
164
|
+
parts.push("- Dockerfile (multi-stage build)");
|
|
165
|
+
break;
|
|
166
|
+
case "java":
|
|
167
|
+
parts.push("\nGenerate a Maven project with:");
|
|
168
|
+
parts.push("- src/main/java/com/blok/runtime/Blok.java (core types)");
|
|
169
|
+
parts.push("- src/main/java/com/blok/runtime/NodeRegistry.java (registry)");
|
|
170
|
+
parts.push("- src/main/java/com/blok/server/RuntimeServer.java (HTTP server)");
|
|
171
|
+
parts.push("- src/main/java/com/blok/nodes/HelloWorldNode.java (example)");
|
|
172
|
+
parts.push("- pom.xml (Maven config)");
|
|
173
|
+
parts.push("- Dockerfile (multi-stage build)");
|
|
174
|
+
break;
|
|
175
|
+
case "rust":
|
|
176
|
+
parts.push("\nGenerate a Cargo project with:");
|
|
177
|
+
parts.push("- src/lib.rs (core types + NodeHandler trait)");
|
|
178
|
+
parts.push("- src/registry.rs (NodeRegistry)");
|
|
179
|
+
parts.push("- src/main.rs (HTTP server with axum or actix-web)");
|
|
180
|
+
parts.push("- src/nodes/hello_world.rs (example node)");
|
|
181
|
+
parts.push("- Cargo.toml (dependencies)");
|
|
182
|
+
parts.push("- Dockerfile (multi-stage build)");
|
|
183
|
+
break;
|
|
184
|
+
case "python":
|
|
185
|
+
parts.push("\nGenerate a Python package with:");
|
|
186
|
+
parts.push("- blok/__init__.py (core types)");
|
|
187
|
+
parts.push("- blok/registry.py (NodeRegistry)");
|
|
188
|
+
parts.push("- server.py (HTTP server with Flask or FastAPI)");
|
|
189
|
+
parts.push("- nodes/hello_world.py (example node)");
|
|
190
|
+
parts.push("- requirements.txt or pyproject.toml");
|
|
191
|
+
parts.push("- Dockerfile");
|
|
192
|
+
break;
|
|
193
|
+
case "csharp":
|
|
194
|
+
parts.push("\nGenerate a .NET project with:");
|
|
195
|
+
parts.push("- Runtime/BlokContext.cs (core types)");
|
|
196
|
+
parts.push("- Runtime/NodeRegistry.cs (registry)");
|
|
197
|
+
parts.push("- Program.cs (minimal API server)");
|
|
198
|
+
parts.push("- Nodes/HelloWorldNode.cs (example)");
|
|
199
|
+
parts.push("- BlokRuntime.csproj");
|
|
200
|
+
parts.push("- Dockerfile");
|
|
201
|
+
break;
|
|
202
|
+
case "php":
|
|
203
|
+
parts.push("\nGenerate a PHP project with:");
|
|
204
|
+
parts.push("- src/Runtime/Context.php (core types)");
|
|
205
|
+
parts.push("- src/Runtime/NodeRegistry.php (registry)");
|
|
206
|
+
parts.push("- src/Nodes/HelloWorldNode.php (example)");
|
|
207
|
+
parts.push("- server.php (HTTP server)");
|
|
208
|
+
parts.push("- composer.json");
|
|
209
|
+
parts.push("- Dockerfile");
|
|
210
|
+
break;
|
|
211
|
+
case "ruby":
|
|
212
|
+
parts.push("\nGenerate a Ruby project with:");
|
|
213
|
+
parts.push("- lib/blok/context.rb (core types)");
|
|
214
|
+
parts.push("- lib/blok/registry.rb (NodeRegistry)");
|
|
215
|
+
parts.push("- lib/blok/nodes/hello_world.rb (example)");
|
|
216
|
+
parts.push("- server.rb (Sinatra or Rack HTTP server)");
|
|
217
|
+
parts.push("- Gemfile");
|
|
218
|
+
parts.push("- Dockerfile");
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
return parts.join("\n");
|
|
222
|
+
}
|
|
223
|
+
createFeedbackPrompt(originalPrompt, previousCode, errors) {
|
|
224
|
+
const analyzedErrors = errors.map((err, i) => {
|
|
225
|
+
const guidance = this.getSemanticGuidance(err);
|
|
226
|
+
return `${i + 1}. ${err}${guidance ? `\n Fix: ${guidance}` : ""}`;
|
|
227
|
+
});
|
|
228
|
+
return [
|
|
229
|
+
originalPrompt,
|
|
230
|
+
"",
|
|
231
|
+
"\u274c The previous generation had validation errors:",
|
|
232
|
+
"",
|
|
233
|
+
...analyzedErrors,
|
|
234
|
+
"",
|
|
235
|
+
"Previous code:",
|
|
236
|
+
"```",
|
|
237
|
+
previousCode.substring(0, 2000),
|
|
238
|
+
previousCode.length > 2000 ? "\n... (truncated)" : "",
|
|
239
|
+
"```",
|
|
240
|
+
"",
|
|
241
|
+
"Please fix ALL errors and regenerate the complete runtime SDK.",
|
|
242
|
+
"Make sure to:",
|
|
243
|
+
"- Use // FILE: <path> markers to separate each file",
|
|
244
|
+
"- Include POST /execute and GET /health HTTP endpoints",
|
|
245
|
+
"- Define Context, Request, Response, ExecutionRequest, ExecutionResult types",
|
|
246
|
+
"- Implement NodeHandler interface and NodeRegistry",
|
|
247
|
+
"- Include a hello-world example node",
|
|
248
|
+
"- Include a Dockerfile",
|
|
249
|
+
].join("\n");
|
|
250
|
+
}
|
|
251
|
+
getSemanticGuidance(error) {
|
|
252
|
+
const errorLower = error.toLowerCase();
|
|
253
|
+
if (errorLower.includes("file") && errorLower.includes("marker")) {
|
|
254
|
+
return "Separate files with '// FILE: <relative-path>' on its own line before each file's content";
|
|
255
|
+
}
|
|
256
|
+
if (errorLower.includes("/execute")) {
|
|
257
|
+
return "Add an HTTP POST /execute endpoint that receives ExecutionRequest JSON and returns ExecutionResult JSON";
|
|
258
|
+
}
|
|
259
|
+
if (errorLower.includes("/health")) {
|
|
260
|
+
return "Add an HTTP GET /health endpoint that returns {status, version, runtime, nodes[]}";
|
|
261
|
+
}
|
|
262
|
+
if (errorLower.includes("context")) {
|
|
263
|
+
return "Define a Context struct/class with: id, workflow_name, workflow_path, request, response, vars, env";
|
|
264
|
+
}
|
|
265
|
+
if (errorLower.includes("nodehandler") || errorLower.includes("handler")) {
|
|
266
|
+
return "Define an interface/trait with an execute method: execute(context, config) -> result/error";
|
|
267
|
+
}
|
|
268
|
+
if (errorLower.includes("registry")) {
|
|
269
|
+
return "Implement a NodeRegistry with register(name, handler), get(name), execute(request) methods";
|
|
270
|
+
}
|
|
271
|
+
if (errorLower.includes("dockerfile")) {
|
|
272
|
+
return "Add a multi-stage Dockerfile that builds and runs the runtime on port 8080";
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
validateLanguageSpecific(code, language, errors, warnings) {
|
|
277
|
+
switch (language.toLowerCase()) {
|
|
278
|
+
case "go":
|
|
279
|
+
if (!code.includes("go.mod") && !code.includes("module")) {
|
|
280
|
+
warnings.push("Missing go.mod - Go module definition recommended");
|
|
281
|
+
}
|
|
282
|
+
if (!code.includes("package")) {
|
|
283
|
+
errors.push("Go code must declare a package");
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
case "java":
|
|
287
|
+
if (!code.includes("pom.xml") && !code.includes("build.gradle")) {
|
|
288
|
+
warnings.push("Missing build configuration (pom.xml or build.gradle)");
|
|
289
|
+
}
|
|
290
|
+
if (!code.includes("class")) {
|
|
291
|
+
errors.push("Java code must define at least one class");
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
case "rust":
|
|
295
|
+
if (!code.includes("Cargo.toml") && !code.includes("[package]")) {
|
|
296
|
+
warnings.push("Missing Cargo.toml - Rust project configuration recommended");
|
|
297
|
+
}
|
|
298
|
+
if (!code.includes("fn ") && !code.includes("fn main")) {
|
|
299
|
+
errors.push("Rust code must define functions");
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
case "python":
|
|
303
|
+
if (!code.includes("requirements.txt") && !code.includes("pyproject.toml")) {
|
|
304
|
+
warnings.push("Missing dependency file (requirements.txt or pyproject.toml)");
|
|
305
|
+
}
|
|
306
|
+
if (!code.includes("def ")) {
|
|
307
|
+
errors.push("Python code must define functions");
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
case "csharp":
|
|
311
|
+
if (!code.includes(".csproj")) {
|
|
312
|
+
warnings.push("Missing .csproj project file");
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
315
|
+
case "php":
|
|
316
|
+
if (!code.includes("composer.json")) {
|
|
317
|
+
warnings.push("Missing composer.json");
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case "ruby":
|
|
321
|
+
if (!code.includes("Gemfile")) {
|
|
322
|
+
warnings.push("Missing Gemfile");
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
readExistingRuntime(dirPath) {
|
|
328
|
+
if (!fs.existsSync(dirPath)) {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
const parts = [];
|
|
332
|
+
const readDir = (dir) => {
|
|
333
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
334
|
+
for (const entry of entries) {
|
|
335
|
+
const fullPath = `${dir}/${entry.name}`;
|
|
336
|
+
if (entry.isDirectory() &&
|
|
337
|
+
!entry.name.startsWith(".") &&
|
|
338
|
+
entry.name !== "node_modules" &&
|
|
339
|
+
entry.name !== "target" &&
|
|
340
|
+
entry.name !== "build") {
|
|
341
|
+
readDir(fullPath);
|
|
342
|
+
}
|
|
343
|
+
else if (entry.isFile()) {
|
|
344
|
+
try {
|
|
345
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
346
|
+
const relativePath = fullPath.replace(dirPath, "").replace(/^\//, "");
|
|
347
|
+
parts.push(`// FILE: ${relativePath}\n${content}`);
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
readDir(dirPath);
|
|
355
|
+
return parts.join("\n\n");
|
|
356
|
+
}
|
|
357
|
+
getFileExtension(language) {
|
|
358
|
+
const extensions = {
|
|
359
|
+
go: "go",
|
|
360
|
+
java: "java",
|
|
361
|
+
rust: "rs",
|
|
362
|
+
python: "py",
|
|
363
|
+
csharp: "cs",
|
|
364
|
+
php: "php",
|
|
365
|
+
ruby: "rb",
|
|
366
|
+
};
|
|
367
|
+
return extensions[language.toLowerCase()] || "txt";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|