openagentflow 0.1.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/LICENSE +21 -0
- package/README.md +262 -0
- package/adapters/langgraph/demo_template.js +104 -0
- package/adapters/langgraph/index.js +363 -0
- package/adapters/langgraph/templates.js +502 -0
- package/cli/env.js +193 -0
- package/cli/index.js +580 -0
- package/compiler/compiler.js +134 -0
- package/compiler/index.js +7 -0
- package/compiler/ir-generator.js +142 -0
- package/compiler/validator.js +594 -0
- package/examples/hello.oaf +14 -0
- package/examples/software-dev.oaf +60 -0
- package/examples/summarize-input.json +3 -0
- package/examples/summarize.oaf +28 -0
- package/examples/support-triage-input.json +3 -0
- package/examples/support-triage.oaf +49 -0
- package/package.json +44 -0
- package/parser/ast.js +240 -0
- package/parser/index.js +9 -0
- package/parser/lexer.js +376 -0
- package/parser/parser.js +505 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAgentFlow Compiler — Unified Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the full compilation pipeline:
|
|
5
|
+
* 1. Lexical Analysis (Lexer)
|
|
6
|
+
* 2. Parsing (Parser → AST)
|
|
7
|
+
* 3. Semantic Validation (Validator)
|
|
8
|
+
* 4. IR Generation (IRGenerator)
|
|
9
|
+
*
|
|
10
|
+
* Each stage can be invoked independently or the full pipeline can be run
|
|
11
|
+
* via the `compile()` method.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Lexer } from '../parser/lexer.js';
|
|
15
|
+
import { Parser } from '../parser/parser.js';
|
|
16
|
+
import { SemanticValidator } from './validator.js';
|
|
17
|
+
import { IRGenerator } from './ir-generator.js';
|
|
18
|
+
|
|
19
|
+
// ─── Compilation Result ────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export class CompilationResult {
|
|
22
|
+
constructor() {
|
|
23
|
+
/** @type {import('../parser/lexer.js').Token[]|null} */
|
|
24
|
+
this.tokens = null;
|
|
25
|
+
|
|
26
|
+
/** @type {import('../parser/ast.js').Program|null} */
|
|
27
|
+
this.ast = null;
|
|
28
|
+
|
|
29
|
+
/** @type {import('./validator.js').ValidationResult|null} */
|
|
30
|
+
this.validation = null;
|
|
31
|
+
|
|
32
|
+
/** @type {object|null} */
|
|
33
|
+
this.ir = null;
|
|
34
|
+
|
|
35
|
+
/** @type {Error|null} */
|
|
36
|
+
this.error = null;
|
|
37
|
+
|
|
38
|
+
/** @type {'success'|'lexer_error'|'parse_error'|'validation_error'|'error'} */
|
|
39
|
+
this.status = 'success';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Compiler ──────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export class Compiler {
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} source - The .oaf source text
|
|
48
|
+
* @param {string} [filename='<input>'] - Filename for diagnostics
|
|
49
|
+
*/
|
|
50
|
+
constructor(source, filename = '<input>') {
|
|
51
|
+
this.source = source;
|
|
52
|
+
this.filename = filename;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run the full compilation pipeline.
|
|
57
|
+
* @returns {CompilationResult}
|
|
58
|
+
*/
|
|
59
|
+
compile() {
|
|
60
|
+
const result = new CompilationResult();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Stage 1: Lexical Analysis
|
|
64
|
+
result.tokens = this.lex();
|
|
65
|
+
|
|
66
|
+
// Stage 2: Parsing
|
|
67
|
+
result.ast = this.parse(result.tokens);
|
|
68
|
+
|
|
69
|
+
// Stage 3: Semantic Validation
|
|
70
|
+
result.validation = this.validate(result.ast);
|
|
71
|
+
|
|
72
|
+
if (!result.validation.isValid) {
|
|
73
|
+
result.status = 'validation_error';
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Stage 4: IR Generation
|
|
78
|
+
result.ir = this.generateIR(result.ast);
|
|
79
|
+
|
|
80
|
+
result.status = 'success';
|
|
81
|
+
} catch (err) {
|
|
82
|
+
result.error = err;
|
|
83
|
+
|
|
84
|
+
if (err.name === 'LexerError') {
|
|
85
|
+
result.status = 'lexer_error';
|
|
86
|
+
} else if (err.name === 'ParseError') {
|
|
87
|
+
result.status = 'parse_error';
|
|
88
|
+
} else {
|
|
89
|
+
result.status = 'error';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Stage 1: Tokenize the source.
|
|
98
|
+
* @returns {import('../parser/lexer.js').Token[]}
|
|
99
|
+
*/
|
|
100
|
+
lex() {
|
|
101
|
+
const lexer = new Lexer(this.source, this.filename);
|
|
102
|
+
return lexer.tokenize();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stage 2: Parse tokens into an AST.
|
|
107
|
+
* @param {import('../parser/lexer.js').Token[]} tokens
|
|
108
|
+
* @returns {import('../parser/ast.js').Program}
|
|
109
|
+
*/
|
|
110
|
+
parse(tokens) {
|
|
111
|
+
const parser = new Parser(tokens);
|
|
112
|
+
return parser.parse();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Stage 3: Validate the AST semantically.
|
|
117
|
+
* @param {import('../parser/ast.js').Program} ast
|
|
118
|
+
* @returns {import('./validator.js').ValidationResult}
|
|
119
|
+
*/
|
|
120
|
+
validate(ast) {
|
|
121
|
+
const validator = new SemanticValidator(ast, this.filename);
|
|
122
|
+
return validator.validate();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Stage 4: Generate the IR from a validated AST.
|
|
127
|
+
* @param {import('../parser/ast.js').Program} ast
|
|
128
|
+
* @returns {object}
|
|
129
|
+
*/
|
|
130
|
+
generateIR(ast) {
|
|
131
|
+
const generator = new IRGenerator(ast);
|
|
132
|
+
return generator.generate();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAgentFlow IR Generator
|
|
3
|
+
*
|
|
4
|
+
* Transforms a validated AST into the Intermediate Representation (IR).
|
|
5
|
+
* The IR is a plain JSON-serializable object conforming to the IR spec.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: Validated AST → [IR Generator] → IR (JSON)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const IR_VERSION = '0.1.0';
|
|
11
|
+
|
|
12
|
+
export class IRGenerator {
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('../parser/ast.js').Program} ast - The validated AST
|
|
15
|
+
*/
|
|
16
|
+
constructor(ast) {
|
|
17
|
+
this.ast = ast;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate the IR from the AST.
|
|
22
|
+
* @returns {object} IR document
|
|
23
|
+
*/
|
|
24
|
+
generate() {
|
|
25
|
+
const workflow = this.ast.workflow;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
version: IR_VERSION,
|
|
29
|
+
workflow: this.buildWorkflowMeta(workflow),
|
|
30
|
+
state: this.buildState(workflow.state),
|
|
31
|
+
agents: this.buildAgents(workflow.agents),
|
|
32
|
+
graph: this.buildGraph(workflow.flow),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Workflow Metadata ───────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
buildWorkflowMeta(workflow) {
|
|
39
|
+
const config = {};
|
|
40
|
+
if (workflow.config) {
|
|
41
|
+
for (const entry of workflow.config.entries) {
|
|
42
|
+
config[entry.key] = entry.value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
name: workflow.name,
|
|
47
|
+
config,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── State ───────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
buildState(stateBlock) {
|
|
54
|
+
if (!stateBlock) {
|
|
55
|
+
return { variables: [] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
variables: stateBlock.fields.map(field => ({
|
|
60
|
+
name: field.name,
|
|
61
|
+
type: this.serializeType(field.typeExpr),
|
|
62
|
+
options: (field.options ?? []).map(opt => ({
|
|
63
|
+
name: opt.name,
|
|
64
|
+
args: opt.args ?? [],
|
|
65
|
+
})),
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert a TypeExpr AST node into an IR type descriptor string.
|
|
72
|
+
* @param {import('../parser/ast.js').TypeExpr} typeExpr
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
serializeType(typeExpr) {
|
|
76
|
+
switch (typeExpr.kind) {
|
|
77
|
+
case 'primitive':
|
|
78
|
+
return typeExpr.name;
|
|
79
|
+
case 'list':
|
|
80
|
+
return `list<${this.serializeType(typeExpr.elementType)}>`;
|
|
81
|
+
case 'map':
|
|
82
|
+
return `map<${this.serializeType(typeExpr.keyType)},${this.serializeType(typeExpr.valueType)}>`;
|
|
83
|
+
default:
|
|
84
|
+
return 'unknown';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Agents ──────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
buildAgents(agents) {
|
|
91
|
+
return agents.map(agent => {
|
|
92
|
+
let provider = agent.provider ?? null;
|
|
93
|
+
if (!provider && agent.model) {
|
|
94
|
+
if (agent.model.startsWith('claude-')) provider = 'anthropic';
|
|
95
|
+
else if (agent.model.startsWith('gpt-') || agent.model.startsWith('o1') || agent.model.startsWith('o3')) provider = 'openai';
|
|
96
|
+
else if (agent.model.startsWith('gemini-') || agent.model.startsWith('gemma-')) provider = 'gemini';
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
id: agent.id,
|
|
100
|
+
instructions: agent.instructions,
|
|
101
|
+
model: agent.model,
|
|
102
|
+
provider,
|
|
103
|
+
temperature: agent.temperature,
|
|
104
|
+
tools: agent.tools,
|
|
105
|
+
inputs: agent.inputs,
|
|
106
|
+
outputs: agent.outputs,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Graph ───────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
buildGraph(flowBlock) {
|
|
114
|
+
if (!flowBlock) {
|
|
115
|
+
return { edges: [], entrypoint: null, terminals: [] };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Find entrypoint: the target of the start edge
|
|
119
|
+
let entrypoint = null;
|
|
120
|
+
const terminals = [];
|
|
121
|
+
const agentEdges = [];
|
|
122
|
+
|
|
123
|
+
for (const edge of flowBlock.edges) {
|
|
124
|
+
if (edge.source === 'start') {
|
|
125
|
+
entrypoint = edge.target;
|
|
126
|
+
} else if (edge.target === 'end') {
|
|
127
|
+
terminals.push(edge.source);
|
|
128
|
+
} else {
|
|
129
|
+
agentEdges.push({
|
|
130
|
+
source: edge.source,
|
|
131
|
+
target: edge.target,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
edges: agentEdges,
|
|
138
|
+
entrypoint,
|
|
139
|
+
terminals,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|