@koi-language/koi 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/QUICKSTART.md +89 -0
- package/README.md +545 -0
- package/examples/actions-demo.koi +177 -0
- package/examples/cache-test.koi +29 -0
- package/examples/calculator.koi +61 -0
- package/examples/clear-registry.js +33 -0
- package/examples/clear-registry.koi +30 -0
- package/examples/code-introspection-test.koi +149 -0
- package/examples/counter.koi +132 -0
- package/examples/delegation-test.koi +52 -0
- package/examples/directory-import-test.koi +84 -0
- package/examples/hello-world-claude.koi +52 -0
- package/examples/hello-world.koi +52 -0
- package/examples/hello.koi +24 -0
- package/examples/mcp-example.koi +70 -0
- package/examples/multi-event-handler-test.koi +144 -0
- package/examples/new-import-test.koi +89 -0
- package/examples/pipeline.koi +162 -0
- package/examples/registry-demo.koi +184 -0
- package/examples/registry-playbook-demo.koi +162 -0
- package/examples/registry-playbook-email-compositor-2.koi +140 -0
- package/examples/registry-playbook-email-compositor.koi +140 -0
- package/examples/sentiment.koi +90 -0
- package/examples/simple.koi +48 -0
- package/examples/skill-import-test.koi +76 -0
- package/examples/skills/advanced/index.koi +95 -0
- package/examples/skills/math-operations.koi +69 -0
- package/examples/skills/string-operations.koi +56 -0
- package/examples/task-chaining-demo.koi +244 -0
- package/examples/test-await.koi +22 -0
- package/examples/test-crypto-sha256.koi +196 -0
- package/examples/test-delegation.koi +41 -0
- package/examples/test-multi-team-routing.koi +258 -0
- package/examples/test-no-handler.koi +35 -0
- package/examples/test-npm-import.koi +67 -0
- package/examples/test-parse.koi +10 -0
- package/examples/test-peers-with-team.koi +59 -0
- package/examples/test-permissions-fail.koi +20 -0
- package/examples/test-permissions.koi +36 -0
- package/examples/test-simple-registry.koi +31 -0
- package/examples/test-typescript-import.koi +64 -0
- package/examples/test-uses-team-syntax.koi +25 -0
- package/examples/test-uses-team.koi +31 -0
- package/examples/utils/calculator.test.ts +144 -0
- package/examples/utils/calculator.ts +56 -0
- package/examples/utils/math-helpers.js +50 -0
- package/examples/utils/math-helpers.ts +55 -0
- package/examples/web-delegation-demo.koi +165 -0
- package/package.json +78 -0
- package/src/cli/koi.js +793 -0
- package/src/compiler/build-optimizer.js +447 -0
- package/src/compiler/cache-manager.js +274 -0
- package/src/compiler/import-resolver.js +369 -0
- package/src/compiler/parser.js +7542 -0
- package/src/compiler/transpiler.js +1105 -0
- package/src/compiler/typescript-transpiler.js +148 -0
- package/src/grammar/koi.pegjs +767 -0
- package/src/runtime/action-registry.js +172 -0
- package/src/runtime/actions/call-skill.js +45 -0
- package/src/runtime/actions/format.js +115 -0
- package/src/runtime/actions/print.js +42 -0
- package/src/runtime/actions/registry-delete.js +37 -0
- package/src/runtime/actions/registry-get.js +37 -0
- package/src/runtime/actions/registry-keys.js +33 -0
- package/src/runtime/actions/registry-search.js +34 -0
- package/src/runtime/actions/registry-set.js +50 -0
- package/src/runtime/actions/return.js +31 -0
- package/src/runtime/actions/send-message.js +58 -0
- package/src/runtime/actions/update-state.js +36 -0
- package/src/runtime/agent.js +1368 -0
- package/src/runtime/cli-logger.js +205 -0
- package/src/runtime/incremental-json-parser.js +201 -0
- package/src/runtime/index.js +33 -0
- package/src/runtime/llm-provider.js +1372 -0
- package/src/runtime/mcp-client.js +1171 -0
- package/src/runtime/planner.js +273 -0
- package/src/runtime/registry-backends/keyv-sqlite.js +215 -0
- package/src/runtime/registry-backends/local.js +260 -0
- package/src/runtime/registry.js +162 -0
- package/src/runtime/role.js +14 -0
- package/src/runtime/router.js +395 -0
- package/src/runtime/runtime.js +113 -0
- package/src/runtime/skill-selector.js +173 -0
- package/src/runtime/skill.js +25 -0
- package/src/runtime/team.js +162 -0
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
import { SourceMapGenerator } from 'source-map';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
export class KoiTranspiler {
|
|
6
|
+
constructor(sourceFile = 'source.zs', options = {}) {
|
|
7
|
+
this.sourceFile = sourceFile;
|
|
8
|
+
this.sourceMap = new SourceMapGenerator({ file: sourceFile + '.js' });
|
|
9
|
+
this.currentLine = 1;
|
|
10
|
+
this.currentColumn = 0;
|
|
11
|
+
this.indent = 0;
|
|
12
|
+
this.inEventHandler = false;
|
|
13
|
+
this.cacheData = options.cacheData || null; // Build-time optimizations
|
|
14
|
+
this.outputPath = options.outputPath || null;
|
|
15
|
+
this.runtimePath = options.runtimePath || null;
|
|
16
|
+
this.externalImports = options.externalImports || []; // TypeScript/JavaScript imports
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
transpile(ast) {
|
|
20
|
+
let code = this.generateProgram(ast);
|
|
21
|
+
return {
|
|
22
|
+
code,
|
|
23
|
+
map: this.sourceMap.toString()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addMapping(node) {
|
|
28
|
+
if (node?.location) {
|
|
29
|
+
this.sourceMap.addMapping({
|
|
30
|
+
generated: { line: this.currentLine, column: this.currentColumn },
|
|
31
|
+
source: this.sourceFile,
|
|
32
|
+
original: { line: node.location.start.line, column: node.location.start.column - 1 }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
emit(code, node = null) {
|
|
38
|
+
if (node) this.addMapping(node);
|
|
39
|
+
|
|
40
|
+
for (const char of code) {
|
|
41
|
+
if (char === '\n') {
|
|
42
|
+
this.currentLine++;
|
|
43
|
+
this.currentColumn = 0;
|
|
44
|
+
} else {
|
|
45
|
+
this.currentColumn++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return code;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getIndent() {
|
|
52
|
+
return ' '.repeat(this.indent);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate a safe JavaScript identifier from an import path
|
|
57
|
+
* e.g., "./utils/helpers" -> "utils_helpers"
|
|
58
|
+
* "lodash" -> "lodash"
|
|
59
|
+
* "@types/node" -> "types_node"
|
|
60
|
+
*/
|
|
61
|
+
generateSafeImportName(importPath) {
|
|
62
|
+
// Remove file extension
|
|
63
|
+
let name = importPath.replace(/\.(ts|tsx|js|jsx|mjs)$/, '');
|
|
64
|
+
|
|
65
|
+
// Remove leading ./ and ../
|
|
66
|
+
name = name.replace(/^\.\.?\//g, '');
|
|
67
|
+
|
|
68
|
+
// Replace special characters with underscores
|
|
69
|
+
name = name.replace(/[^a-zA-Z0-9_$]/g, '_');
|
|
70
|
+
|
|
71
|
+
// Remove leading underscores
|
|
72
|
+
name = name.replace(/^_+/, '');
|
|
73
|
+
|
|
74
|
+
// If starts with a number, prepend with underscore
|
|
75
|
+
if (/^\d/.test(name)) {
|
|
76
|
+
name = '_' + name;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// If empty after sanitization, use a default
|
|
80
|
+
if (!name) {
|
|
81
|
+
name = 'external_module';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return name;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ============================================================
|
|
88
|
+
// Program
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
generateProgram(node) {
|
|
92
|
+
let code = this.emit(`// Generated from ${this.sourceFile}\n`);
|
|
93
|
+
|
|
94
|
+
// Support KOI_RUNTIME_PATH for local development
|
|
95
|
+
// This allows developers to work on Koi itself without reinstalling
|
|
96
|
+
const koiRuntimePath = process.env.KOI_RUNTIME_PATH;
|
|
97
|
+
|
|
98
|
+
let runtimeImportPath;
|
|
99
|
+
let routerImportPath;
|
|
100
|
+
|
|
101
|
+
if (koiRuntimePath) {
|
|
102
|
+
// Development mode: use local runtime
|
|
103
|
+
const runtimeIndexPath = path.join(koiRuntimePath, 'index.js');
|
|
104
|
+
const routerPath = path.join(koiRuntimePath, 'router.js');
|
|
105
|
+
|
|
106
|
+
runtimeImportPath = 'file://' + path.resolve(runtimeIndexPath);
|
|
107
|
+
routerImportPath = 'file://' + path.resolve(routerPath);
|
|
108
|
+
|
|
109
|
+
code += this.emit(`// Using local runtime from KOI_RUNTIME_PATH: ${koiRuntimePath}\n`);
|
|
110
|
+
} else {
|
|
111
|
+
// Production mode: use package imports
|
|
112
|
+
runtimeImportPath = 'koi-lang';
|
|
113
|
+
routerImportPath = 'koi-lang/router';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Store routerImportPath for later use
|
|
117
|
+
this.routerImportPath = routerImportPath;
|
|
118
|
+
|
|
119
|
+
code += this.emit(`import { Agent, Team, Skill, Role, Runtime, SkillRegistry, skillSelector, registry } from '${runtimeImportPath}';\n\n`);
|
|
120
|
+
|
|
121
|
+
// Generate imports for external TypeScript/JavaScript modules
|
|
122
|
+
if (this.externalImports && this.externalImports.length > 0) {
|
|
123
|
+
code += this.emit(`// External TypeScript/JavaScript imports\n`);
|
|
124
|
+
|
|
125
|
+
for (const extImport of this.externalImports) {
|
|
126
|
+
// Use the resolved path (which points to transpiled .js for TypeScript files)
|
|
127
|
+
let importPath = extImport.originalPath;
|
|
128
|
+
|
|
129
|
+
// Check if this is a node_modules package (resolved path contains node_modules)
|
|
130
|
+
const isNodeModule = extImport.resolvedPath.includes('node_modules');
|
|
131
|
+
|
|
132
|
+
// For relative imports, recalculate path from output location
|
|
133
|
+
if (extImport.originalPath.startsWith('./') || extImport.originalPath.startsWith('../')) {
|
|
134
|
+
if (this.outputPath) {
|
|
135
|
+
const outputDir = path.dirname(this.outputPath);
|
|
136
|
+
const relPath = path.relative(outputDir, extImport.resolvedPath);
|
|
137
|
+
importPath = relPath.split(path.sep).join('/');
|
|
138
|
+
if (!importPath.startsWith('.')) {
|
|
139
|
+
importPath = './' + importPath;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else if (isNodeModule) {
|
|
143
|
+
// For node_modules packages, keep original package name
|
|
144
|
+
importPath = extImport.originalPath;
|
|
145
|
+
} else {
|
|
146
|
+
// For other absolute imports, use resolved path
|
|
147
|
+
importPath = extImport.resolvedPath;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Generate a safe identifier from the original import path (not resolved)
|
|
151
|
+
const safeName = this.generateSafeImportName(extImport.originalPath);
|
|
152
|
+
|
|
153
|
+
// For node_modules packages, use default import first, then also get named exports
|
|
154
|
+
if (isNodeModule) {
|
|
155
|
+
code += this.emit(`import ${safeName}_default from '${importPath}';\n`);
|
|
156
|
+
code += this.emit(`import * as ${safeName}_named from '${importPath}';\n`);
|
|
157
|
+
// Prefer default export if it exists, otherwise use named exports
|
|
158
|
+
code += this.emit(`const ${safeName} = ${safeName}_default || ${safeName}_named;\n`);
|
|
159
|
+
} else {
|
|
160
|
+
// For local files, use namespace import
|
|
161
|
+
code += this.emit(`import * as ${safeName} from '${importPath}';\n`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Make it available globally
|
|
165
|
+
code += this.emit(`globalThis.${safeName} = ${safeName};\n`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
code += this.emit(`\n`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Make SkillRegistry and registry available globally
|
|
172
|
+
code += this.emit(`globalThis.SkillRegistry = SkillRegistry;\n`);
|
|
173
|
+
code += this.emit(`globalThis.skillSelector = skillSelector;\n`);
|
|
174
|
+
code += this.emit(`globalThis.registry = registry;\n\n`);
|
|
175
|
+
|
|
176
|
+
// Inject build-time cache if available
|
|
177
|
+
if (this.cacheData && this.cacheData.affordances) {
|
|
178
|
+
code += this.emit(this.generateCacheCode());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Separate run statements from other declarations
|
|
182
|
+
const agentDecls = [];
|
|
183
|
+
const skillDecls = [];
|
|
184
|
+
const runStatements = [];
|
|
185
|
+
const otherDecls = [];
|
|
186
|
+
|
|
187
|
+
for (const decl of node.declarations) {
|
|
188
|
+
if (decl.type === 'RunStatement') {
|
|
189
|
+
runStatements.push(decl);
|
|
190
|
+
} else if (decl.type === 'AgentDecl') {
|
|
191
|
+
agentDecls.push(decl);
|
|
192
|
+
} else if (decl.type === 'SkillDecl') {
|
|
193
|
+
skillDecls.push(decl);
|
|
194
|
+
} else {
|
|
195
|
+
otherDecls.push(decl);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Generate all declarations in original order (except RunStatements)
|
|
200
|
+
// This preserves dependencies between roles, teams, and agents
|
|
201
|
+
this.skipAgentRegistration = true;
|
|
202
|
+
for (const decl of node.declarations) {
|
|
203
|
+
if (decl.type !== 'RunStatement') {
|
|
204
|
+
code += this.generateDeclaration(decl);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
this.skipAgentRegistration = false;
|
|
208
|
+
|
|
209
|
+
// Generate main async function that coordinates everything
|
|
210
|
+
if (agentDecls.length > 0 || skillDecls.length > 0 || runStatements.length > 0) {
|
|
211
|
+
code += this.emit(`\n// Main execution function\n`);
|
|
212
|
+
code += this.emit(`(async () => {\n`);
|
|
213
|
+
this.indent++;
|
|
214
|
+
|
|
215
|
+
// Register all agents
|
|
216
|
+
if (agentDecls.length > 0) {
|
|
217
|
+
code += this.emit(`${this.getIndent()}// Register agents with router\n`);
|
|
218
|
+
code += this.emit(`${this.getIndent()}const { agentRouter } = await import('${this.routerImportPath}');\n\n`);
|
|
219
|
+
|
|
220
|
+
for (const decl of agentDecls) {
|
|
221
|
+
const agentName = decl.name.name;
|
|
222
|
+
const hasCachedAffordances = this.cacheData && this.cacheData.affordances && this.cacheData.affordances[agentName];
|
|
223
|
+
|
|
224
|
+
if (hasCachedAffordances) {
|
|
225
|
+
code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName}, CACHED_AFFORDANCES['${agentName}']);\n`);
|
|
226
|
+
} else {
|
|
227
|
+
code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName});\n`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
code += this.emit(`\n`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Register all skills with skillSelector
|
|
235
|
+
if (skillDecls.length > 0) {
|
|
236
|
+
code += this.emit(`${this.getIndent()}// Register skills with skillSelector\n`);
|
|
237
|
+
|
|
238
|
+
for (const decl of skillDecls) {
|
|
239
|
+
const skillName = decl.name.name;
|
|
240
|
+
const hasCachedAffordance = this.cacheData && this.cacheData.skillAffordances && this.cacheData.skillAffordances[skillName];
|
|
241
|
+
|
|
242
|
+
// Get function names for this skill
|
|
243
|
+
const functionNames = decl.functions
|
|
244
|
+
? decl.functions.filter(f => f.isExport).map(f => f.name.name)
|
|
245
|
+
: [];
|
|
246
|
+
|
|
247
|
+
if (functionNames.length > 0) {
|
|
248
|
+
// Build functions array
|
|
249
|
+
code += this.emit(`${this.getIndent()}const ${skillName}Functions = [${functionNames.map(fn => `{ name: '${fn}', fn: ${fn}, description: SkillRegistry.get('${skillName}', '${fn}')?.metadata?.affordance || 'Function from ${skillName}' }`).join(', ')}];\n`);
|
|
250
|
+
|
|
251
|
+
// Register with cached affordance if available
|
|
252
|
+
if (hasCachedAffordance) {
|
|
253
|
+
code += this.emit(`${this.getIndent()}await skillSelector.register('${skillName}', ${skillName}Functions, CACHED_SKILL_AFFORDANCES['${skillName}']);\n`);
|
|
254
|
+
} else {
|
|
255
|
+
code += this.emit(`${this.getIndent()}await skillSelector.register('${skillName}', ${skillName}Functions);\n`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
code += this.emit(`\n`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Execute run statements
|
|
264
|
+
for (const runStmt of runStatements) {
|
|
265
|
+
code += this.generateRunBody(runStmt);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Clean exit
|
|
269
|
+
code += this.emit(`${this.getIndent()}process.exit(0);\n`);
|
|
270
|
+
|
|
271
|
+
this.indent--;
|
|
272
|
+
code += this.emit(`})().catch(err => {\n`);
|
|
273
|
+
this.indent++;
|
|
274
|
+
code += this.emit(`${this.getIndent()}console.error('Error:', err.message);\n`);
|
|
275
|
+
code += this.emit(`${this.getIndent()}process.exit(1);\n`);
|
|
276
|
+
this.indent--;
|
|
277
|
+
code += this.emit(`});\n`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return code;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
generateCacheCode() {
|
|
284
|
+
const meta = this.cacheData.metadata;
|
|
285
|
+
let code = this.emit('// ============================================================\n');
|
|
286
|
+
code += this.emit('// Pre-computed Affordances (Build-time Cache)\n');
|
|
287
|
+
code += this.emit(`// Generated at: ${new Date(meta.generatedAt).toISOString()}\n`);
|
|
288
|
+
code += this.emit(`// Total agents: ${meta.totalAgents || 0}\n`);
|
|
289
|
+
code += this.emit(`// Total agent affordances: ${meta.totalAffordances || 0}\n`);
|
|
290
|
+
code += this.emit(`// Total skills: ${meta.totalSkills || 0}\n`);
|
|
291
|
+
code += this.emit(`// Total skill affordances: ${meta.totalSkillAffordances || 0}\n`);
|
|
292
|
+
code += this.emit('// This avoids embedding API calls at runtime\n');
|
|
293
|
+
code += this.emit('// ============================================================\n\n');
|
|
294
|
+
code += this.emit(`const CACHED_AFFORDANCES = ${JSON.stringify(this.cacheData.affordances || {}, null, 2)};\n\n`);
|
|
295
|
+
code += this.emit(`const CACHED_SKILL_AFFORDANCES = ${JSON.stringify(this.cacheData.skillAffordances || {}, null, 2)};\n\n`);
|
|
296
|
+
return code;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ============================================================
|
|
300
|
+
// Declarations
|
|
301
|
+
// ============================================================
|
|
302
|
+
|
|
303
|
+
generateDeclaration(node) {
|
|
304
|
+
switch (node.type) {
|
|
305
|
+
case 'PackageDecl':
|
|
306
|
+
return this.emit(`// Package: ${node.name.value}\n\n`, node);
|
|
307
|
+
case 'ImportDecl':
|
|
308
|
+
return this.generateImport(node);
|
|
309
|
+
case 'RoleDecl':
|
|
310
|
+
return this.generateRole(node);
|
|
311
|
+
case 'TeamDecl':
|
|
312
|
+
return this.generateTeam(node);
|
|
313
|
+
case 'AgentDecl':
|
|
314
|
+
return this.generateAgent(node);
|
|
315
|
+
case 'SkillDecl':
|
|
316
|
+
return this.generateSkill(node);
|
|
317
|
+
case 'RunStatement':
|
|
318
|
+
return this.generateRun(node);
|
|
319
|
+
default:
|
|
320
|
+
return this.emit(`/* Unknown declaration: ${node.type} */\n`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
generateImport(node) {
|
|
325
|
+
return this.emit(`// Import ${node.what}: ${node.name.value}\n`, node);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
generateRole(node) {
|
|
329
|
+
const caps = node.capabilities.map(c => `'${c.name.name}'`).join(', ');
|
|
330
|
+
return this.emit(
|
|
331
|
+
`const ${node.name.name} = new Role('${node.name.name}', [${caps}]);\n\n`,
|
|
332
|
+
node
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
generateTeam(node) {
|
|
337
|
+
let code = this.emit(`const ${node.name.name} = new Team('${node.name.name}', {\n`, node);
|
|
338
|
+
this.indent++;
|
|
339
|
+
for (const member of node.members) {
|
|
340
|
+
let value;
|
|
341
|
+
|
|
342
|
+
// Handle MCP addresses
|
|
343
|
+
if (member.value.type === 'MCPAddress') {
|
|
344
|
+
value = `'${member.value.address}'`;
|
|
345
|
+
}
|
|
346
|
+
// Handle AgentReference
|
|
347
|
+
else if (member.value.type === 'AgentReference') {
|
|
348
|
+
value = member.value.agent.name;
|
|
349
|
+
}
|
|
350
|
+
// Handle Identifier
|
|
351
|
+
else if (member.value.type === 'Identifier') {
|
|
352
|
+
value = member.value.name;
|
|
353
|
+
}
|
|
354
|
+
// Handle regular values
|
|
355
|
+
else if (typeof member.value === 'string') {
|
|
356
|
+
value = `'${member.value}'`;
|
|
357
|
+
}
|
|
358
|
+
else if (member.value.value) {
|
|
359
|
+
value = `'${member.value.value}'`;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
value = member.value.name || 'undefined';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
code += this.emit(`${this.getIndent()}${member.name.name}: ${value},\n`);
|
|
366
|
+
}
|
|
367
|
+
this.indent--;
|
|
368
|
+
code += this.emit(`});\n\n`);
|
|
369
|
+
return code;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
generateAgent(node) {
|
|
373
|
+
let code = this.emit(`const ${node.name.name} = new Agent({\n`, node);
|
|
374
|
+
this.indent++;
|
|
375
|
+
code += this.emit(`${this.getIndent()}name: '${node.name.name}',\n`);
|
|
376
|
+
code += this.emit(`${this.getIndent()}role: ${node.role.name},\n`);
|
|
377
|
+
|
|
378
|
+
// Extract body items
|
|
379
|
+
const skills = node.body.filter(b => b.type === 'UsesSkill');
|
|
380
|
+
const usesTeams = node.body.filter(b => b.type === 'UsesTeam');
|
|
381
|
+
const llmConfig = node.body.find(b => b.type === 'LLMConfig');
|
|
382
|
+
const eventHandlers = node.body.filter(b => b.type === 'EventHandler');
|
|
383
|
+
const state = node.body.find(b => b.type === 'StateDecl');
|
|
384
|
+
const playbooks = node.body.filter(b => b.type === 'PlaybookDecl');
|
|
385
|
+
const resilience = node.body.find(b => b.type === 'ResilienceDecl');
|
|
386
|
+
const peers = node.body.find(b => b.type === 'PeersDecl');
|
|
387
|
+
|
|
388
|
+
// Track if agent has handlers for auto-registration
|
|
389
|
+
this.agentHasHandlers = eventHandlers.length > 0;
|
|
390
|
+
|
|
391
|
+
if (skills.length > 0) {
|
|
392
|
+
code += this.emit(`${this.getIndent()}skills: [${skills.map(s => `'${s.skill.name}'`).join(', ')}],\n`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (usesTeams.length > 0) {
|
|
396
|
+
code += this.emit(`${this.getIndent()}usesTeams: [${usesTeams.map(t => t.team.name).join(', ')}],\n`);
|
|
397
|
+
|
|
398
|
+
// For backward compatibility: if no explicit peers and usesTeams exists,
|
|
399
|
+
// set peers to the first team for 'peers.event()' syntax to work
|
|
400
|
+
if (!peers && usesTeams.length > 0) {
|
|
401
|
+
code += this.emit(`${this.getIndent()}peers: ${usesTeams[0].team.name}, // Auto-assigned from uses Team\n`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (llmConfig) {
|
|
406
|
+
code += this.emit(`${this.getIndent()}llm: ${this.generateExpression(llmConfig.config)},\n`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (state) {
|
|
410
|
+
code += this.emit(`${this.getIndent()}state: {\n`);
|
|
411
|
+
this.indent++;
|
|
412
|
+
for (const field of state.fields) {
|
|
413
|
+
const init = field.init ? this.generateExpression(field.init) : 'null';
|
|
414
|
+
code += this.emit(`${this.getIndent()}${field.name.name}: ${init},\n`);
|
|
415
|
+
}
|
|
416
|
+
this.indent--;
|
|
417
|
+
code += this.emit(`${this.getIndent()}},\n`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (playbooks.length > 0) {
|
|
421
|
+
code += this.emit(`${this.getIndent()}playbooks: {\n`);
|
|
422
|
+
this.indent++;
|
|
423
|
+
for (const pb of playbooks) {
|
|
424
|
+
code += this.emit(`${this.getIndent()}${pb.name.value}: ${this.generateExpression(pb.content)},\n`);
|
|
425
|
+
}
|
|
426
|
+
this.indent--;
|
|
427
|
+
code += this.emit(`${this.getIndent()}},\n`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (resilience) {
|
|
431
|
+
code += this.emit(`${this.getIndent()}resilience: {\n`);
|
|
432
|
+
this.indent++;
|
|
433
|
+
code += this.emit(`${this.getIndent()}name: ${resilience.name.value},\n`);
|
|
434
|
+
for (const prop of resilience.properties) {
|
|
435
|
+
const val = this.generateExpression(prop.value);
|
|
436
|
+
code += this.emit(`${this.getIndent()}${prop.name.name}: ${val},\n`);
|
|
437
|
+
}
|
|
438
|
+
this.indent--;
|
|
439
|
+
code += this.emit(`${this.getIndent()}},\n`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (peers) {
|
|
443
|
+
let teamName;
|
|
444
|
+
if (peers.team && typeof peers.team === 'object') {
|
|
445
|
+
// Handle TeamReference with override
|
|
446
|
+
teamName = peers.team.name ? peers.team.name.name : peers.team;
|
|
447
|
+
} else {
|
|
448
|
+
teamName = peers.team;
|
|
449
|
+
}
|
|
450
|
+
code += this.emit(`${this.getIndent()}peers: ${teamName},\n`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (eventHandlers.length > 0) {
|
|
454
|
+
code += this.emit(`${this.getIndent()}handlers: {\n`);
|
|
455
|
+
this.indent++;
|
|
456
|
+
for (const handler of eventHandlers) {
|
|
457
|
+
code += this.generateEventHandler(handler);
|
|
458
|
+
}
|
|
459
|
+
this.indent--;
|
|
460
|
+
code += this.emit(`${this.getIndent()}}\n`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
this.indent--;
|
|
464
|
+
code += this.emit(`});\n`);
|
|
465
|
+
|
|
466
|
+
// Auto-register agent with router if it has handlers (only if not skipping)
|
|
467
|
+
if (this.agentHasHandlers && !this.skipAgentRegistration) {
|
|
468
|
+
const agentName = node.name.name;
|
|
469
|
+
const hasCachedAffordances = this.cacheData && this.cacheData.affordances && this.cacheData.affordances[agentName];
|
|
470
|
+
|
|
471
|
+
code += this.emit(`\n// Auto-register agent with router for dynamic discovery\n`);
|
|
472
|
+
code += this.emit(`(async () => {\n`);
|
|
473
|
+
this.indent++;
|
|
474
|
+
code += this.emit(`${this.getIndent()}const { agentRouter } = await import('${this.routerImportPath}');\n`);
|
|
475
|
+
|
|
476
|
+
if (hasCachedAffordances) {
|
|
477
|
+
// Use cached affordances (no embedding generation needed)
|
|
478
|
+
code += this.emit(`${this.getIndent()}// Using pre-computed embeddings from build cache\n`);
|
|
479
|
+
code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName}, CACHED_AFFORDANCES['${agentName}']);\n`);
|
|
480
|
+
} else {
|
|
481
|
+
// No cache, generate at runtime
|
|
482
|
+
code += this.emit(`${this.getIndent()}await agentRouter.register(${agentName});\n`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.indent--;
|
|
486
|
+
code += this.emit(`})();\n`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
code += this.emit(`\n`);
|
|
490
|
+
return code;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
generateEventHandler(node) {
|
|
494
|
+
const params = node.params.map(p => p.name.name).join(', ');
|
|
495
|
+
|
|
496
|
+
// Check if this is a playbook-only handler (only has PlaybookStatement, no other code)
|
|
497
|
+
const hasOnlyPlaybook = node.body.length === 1 && node.body[0].type === 'PlaybookStatement';
|
|
498
|
+
|
|
499
|
+
if (hasOnlyPlaybook) {
|
|
500
|
+
// Generate playbook-only handler
|
|
501
|
+
const playbook = node.body[0].content.value;
|
|
502
|
+
const escapedPlaybook = JSON.stringify(playbook);
|
|
503
|
+
|
|
504
|
+
let code = this.emit(`${this.getIndent()}${node.event.name}: (() => {\n`);
|
|
505
|
+
this.indent++;
|
|
506
|
+
code += this.emit(`${this.getIndent()}const handler = async function(${params}) {\n`);
|
|
507
|
+
this.indent++;
|
|
508
|
+
code += this.emit(`${this.getIndent()}// This should not be called - playbook will be executed by LLM\n`);
|
|
509
|
+
code += this.emit(`${this.getIndent()}throw new Error('Playbook-only handler called directly');\n`);
|
|
510
|
+
this.indent--;
|
|
511
|
+
code += this.emit(`${this.getIndent()}};\n`);
|
|
512
|
+
code += this.emit(`${this.getIndent()}handler.__playbookOnly__ = true;\n`);
|
|
513
|
+
code += this.emit(`${this.getIndent()}handler.__playbook__ = ${escapedPlaybook};\n`);
|
|
514
|
+
code += this.emit(`${this.getIndent()}return handler;\n`);
|
|
515
|
+
this.indent--;
|
|
516
|
+
code += this.emit(`${this.getIndent()}})(),\n`);
|
|
517
|
+
return code;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Regular handler with code
|
|
521
|
+
let code = this.emit(`${this.getIndent()}${node.event.name}: async function(${params}) {\n`);
|
|
522
|
+
this.indent++;
|
|
523
|
+
this.inEventHandler = true;
|
|
524
|
+
for (const stmt of node.body) {
|
|
525
|
+
code += this.generateStatement(stmt);
|
|
526
|
+
}
|
|
527
|
+
this.inEventHandler = false;
|
|
528
|
+
this.indent--;
|
|
529
|
+
code += this.emit(`${this.getIndent()}},\n`);
|
|
530
|
+
return code;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
generateSkill(node) {
|
|
534
|
+
let code = '';
|
|
535
|
+
const skillName = node.name.name;
|
|
536
|
+
|
|
537
|
+
// Generate skill comment header
|
|
538
|
+
code += this.emit(`// ============================================================\n`);
|
|
539
|
+
code += this.emit(`// Skill: ${skillName}\n`);
|
|
540
|
+
if (node.affordance) {
|
|
541
|
+
code += this.emit(`// ${node.affordance.replace(/\n/g, '\\n// ')}\n`);
|
|
542
|
+
}
|
|
543
|
+
code += this.emit(`// ============================================================\n\n`);
|
|
544
|
+
|
|
545
|
+
// Generate internal agents
|
|
546
|
+
if (node.agents && node.agents.length > 0) {
|
|
547
|
+
for (const agent of node.agents) {
|
|
548
|
+
code += this.generateAgent(agent);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Generate internal teams
|
|
553
|
+
if (node.teams && node.teams.length > 0) {
|
|
554
|
+
for (const team of node.teams) {
|
|
555
|
+
code += this.generateTeam(team);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Generate exported functions
|
|
560
|
+
const functionNames = [];
|
|
561
|
+
if (node.functions && node.functions.length > 0) {
|
|
562
|
+
for (const func of node.functions) {
|
|
563
|
+
code += this.generateFunction(func);
|
|
564
|
+
if (func.isExport) {
|
|
565
|
+
functionNames.push(func.name.name);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Register exported functions in SkillRegistry
|
|
571
|
+
if (functionNames.length > 0) {
|
|
572
|
+
code += this.emit(`// Register skill functions\n`);
|
|
573
|
+
for (const funcName of functionNames) {
|
|
574
|
+
code += this.emit(`SkillRegistry.register('${skillName}', '${funcName}', ${funcName}, { affordance: ${JSON.stringify(node.affordance || '')} });\n`);
|
|
575
|
+
}
|
|
576
|
+
code += this.emit(`\n`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
code += this.emit(`\n`);
|
|
580
|
+
return code;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
generateFunction(node) {
|
|
584
|
+
let code = '';
|
|
585
|
+
|
|
586
|
+
// Function signature (strip TypeScript type annotations for JavaScript output)
|
|
587
|
+
const exportKeyword = node.isExport ? 'export ' : '';
|
|
588
|
+
const asyncKeyword = node.isAsync ? 'async ' : '';
|
|
589
|
+
const params = node.params ? node.params.map(p => p.name.name).join(', ') : '';
|
|
590
|
+
|
|
591
|
+
code += this.emit(`${exportKeyword}${asyncKeyword}function ${node.name.name}(${params}) {\n`, node);
|
|
592
|
+
|
|
593
|
+
// Function body - emit as raw code
|
|
594
|
+
if (node.body && node.body.code) {
|
|
595
|
+
this.indent++;
|
|
596
|
+
const bodyLines = node.body.code.split('\n');
|
|
597
|
+
for (const line of bodyLines) {
|
|
598
|
+
if (line.trim()) {
|
|
599
|
+
code += this.emit(`${this.getIndent()}${line}\n`);
|
|
600
|
+
} else {
|
|
601
|
+
code += this.emit(`\n`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
this.indent--;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
code += this.emit(`}\n\n`);
|
|
608
|
+
return code;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
generateTypeExpression(typeNode) {
|
|
612
|
+
if (!typeNode) return 'any';
|
|
613
|
+
|
|
614
|
+
switch (typeNode.type) {
|
|
615
|
+
case 'AnyType': return 'any';
|
|
616
|
+
case 'StringType': return 'string';
|
|
617
|
+
case 'NumberType': return 'number';
|
|
618
|
+
case 'BooleanType': return 'boolean';
|
|
619
|
+
case 'JsonType': return 'any';
|
|
620
|
+
case 'PromiseType':
|
|
621
|
+
return `Promise<${this.generateTypeExpression(typeNode.inner)}>`;
|
|
622
|
+
case 'CustomType':
|
|
623
|
+
return typeNode.name;
|
|
624
|
+
default:
|
|
625
|
+
return 'any';
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
generateRun(node) {
|
|
630
|
+
// Check if target is MemberExpression (Agent.event)
|
|
631
|
+
if (node.target.type === 'MemberExpression') {
|
|
632
|
+
const agent = this.generateExpression(node.target.object);
|
|
633
|
+
const event = typeof node.target.property === 'string'
|
|
634
|
+
? node.target.property
|
|
635
|
+
: node.target.property.name;
|
|
636
|
+
const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
|
|
637
|
+
|
|
638
|
+
return this.emit(
|
|
639
|
+
`\n// Run\n(async () => {\n const result = await ${agent}.handle('${event}', ${args});\n // Result handled by actions\n})();\n`,
|
|
640
|
+
node
|
|
641
|
+
);
|
|
642
|
+
} else {
|
|
643
|
+
// Direct function call
|
|
644
|
+
const target = this.generateExpression(node.target);
|
|
645
|
+
const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
|
|
646
|
+
return this.emit(`\n// Run\n(async () => {\n const result = await ${target}(${args});\n // Result handled by actions\n})();\n`, node);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
generateRunBody(node) {
|
|
651
|
+
// Generate just the body of a run statement (without IIFE wrapper)
|
|
652
|
+
// Used when generating coordinated main function
|
|
653
|
+
let code = '';
|
|
654
|
+
|
|
655
|
+
if (node.target.type === 'MemberExpression') {
|
|
656
|
+
const agent = this.generateExpression(node.target.object);
|
|
657
|
+
const event = typeof node.target.property === 'string'
|
|
658
|
+
? node.target.property
|
|
659
|
+
: node.target.property.name;
|
|
660
|
+
const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
|
|
661
|
+
|
|
662
|
+
code += this.emit(`${this.getIndent()}// Execute\n`);
|
|
663
|
+
code += this.emit(`${this.getIndent()}const result = await ${agent}.handle('${event}', ${args});\n`);
|
|
664
|
+
code += this.emit(`${this.getIndent()}// Result handled by actions\n\n`);
|
|
665
|
+
} else {
|
|
666
|
+
// Direct function call
|
|
667
|
+
const target = this.generateExpression(node.target);
|
|
668
|
+
const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
|
|
669
|
+
|
|
670
|
+
code += this.emit(`${this.getIndent()}// Execute\n`);
|
|
671
|
+
code += this.emit(`${this.getIndent()}const result = await ${target}(${args});\n`);
|
|
672
|
+
code += this.emit(`${this.getIndent()}// Result handled by actions\n\n`);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return code;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ============================================================
|
|
679
|
+
// Statements
|
|
680
|
+
// ============================================================
|
|
681
|
+
|
|
682
|
+
generateStatement(node) {
|
|
683
|
+
switch (node.type) {
|
|
684
|
+
case 'PlaybookStatement':
|
|
685
|
+
// Replace newlines with spaces and truncate safely
|
|
686
|
+
const playbookText = node.content.value.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
|
|
687
|
+
const truncated = playbookText.length > 80 ? playbookText.substring(0, 80) + '...' : playbookText;
|
|
688
|
+
return this.emit(`${this.getIndent()}// Playbook: ${truncated}\n`, node);
|
|
689
|
+
case 'VariableDeclaration':
|
|
690
|
+
return this.generateVarDecl(node);
|
|
691
|
+
case 'ConstDeclaration':
|
|
692
|
+
return this.generateConstDecl(node);
|
|
693
|
+
case 'IfStatement':
|
|
694
|
+
return this.generateIf(node);
|
|
695
|
+
case 'ForStatement':
|
|
696
|
+
return this.generateFor(node);
|
|
697
|
+
case 'ForOfStatement':
|
|
698
|
+
return this.generateForOf(node);
|
|
699
|
+
case 'ForInStatement':
|
|
700
|
+
return this.generateForIn(node);
|
|
701
|
+
case 'WhileStatement':
|
|
702
|
+
return this.generateWhile(node);
|
|
703
|
+
case 'ReturnStatement':
|
|
704
|
+
return this.generateReturn(node);
|
|
705
|
+
case 'SendStatement':
|
|
706
|
+
return this.generateSend(node);
|
|
707
|
+
case 'UsePlaybookStatement':
|
|
708
|
+
return this.emit(`${this.getIndent()}// Use playbook: ${node.name.name || node.name.value}\n`, node);
|
|
709
|
+
case 'ExpressionStatement':
|
|
710
|
+
return this.emit(`${this.getIndent()}${this.generateExpression(node.expression)};\n`, node);
|
|
711
|
+
case 'CodeBlockStatement':
|
|
712
|
+
case 'RawCodeBlock':
|
|
713
|
+
// Emit raw code block with proper indentation
|
|
714
|
+
// Transform await send expressions to Runtime.send calls
|
|
715
|
+
const rawCode = this.transformSendExpressions(node.code);
|
|
716
|
+
const lines = rawCode.split('\n');
|
|
717
|
+
let code = '';
|
|
718
|
+
for (const line of lines) {
|
|
719
|
+
if (line.trim()) {
|
|
720
|
+
code += this.emit(`${this.getIndent()}${line}\n`);
|
|
721
|
+
} else {
|
|
722
|
+
code += this.emit('\n');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return code;
|
|
726
|
+
default:
|
|
727
|
+
return this.emit(`${this.getIndent()}/* Unknown statement: ${node.type} */\n`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
generateVarDecl(node) {
|
|
732
|
+
const init = node.init ? ` = ${this.generateExpression(node.init)}` : '';
|
|
733
|
+
return this.emit(`${this.getIndent()}let ${node.name.name}${init};\n`, node);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
generateConstDecl(node) {
|
|
737
|
+
return this.emit(`${this.getIndent()}const ${node.name.name} = ${this.generateExpression(node.value)};\n`, node);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
generateIf(node) {
|
|
741
|
+
let code = this.emit(`${this.getIndent()}if (${this.generateExpression(node.condition)}) {\n`, node);
|
|
742
|
+
this.indent++;
|
|
743
|
+
for (const stmt of node.then) {
|
|
744
|
+
code += this.generateStatement(stmt);
|
|
745
|
+
}
|
|
746
|
+
this.indent--;
|
|
747
|
+
code += this.emit(`${this.getIndent()}}`);
|
|
748
|
+
|
|
749
|
+
if (node.else && node.else.length > 0) {
|
|
750
|
+
code += this.emit(` else {\n`);
|
|
751
|
+
this.indent++;
|
|
752
|
+
for (const stmt of node.else) {
|
|
753
|
+
code += this.generateStatement(stmt);
|
|
754
|
+
}
|
|
755
|
+
this.indent--;
|
|
756
|
+
code += this.emit(`${this.getIndent()}}`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
code += this.emit(`\n`);
|
|
760
|
+
return code;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
generateFor(node) {
|
|
764
|
+
const init = node.init ? this.generateExpression(node.init) : '';
|
|
765
|
+
const condition = node.condition ? this.generateExpression(node.condition) : '';
|
|
766
|
+
const update = node.update ? this.generateExpression(node.update) : '';
|
|
767
|
+
let code = this.emit(`${this.getIndent()}for (${init}; ${condition}; ${update}) {\n`, node);
|
|
768
|
+
this.indent++;
|
|
769
|
+
for (const stmt of node.body) {
|
|
770
|
+
code += this.generateStatement(stmt);
|
|
771
|
+
}
|
|
772
|
+
this.indent--;
|
|
773
|
+
code += this.emit(`${this.getIndent()}}\n`);
|
|
774
|
+
return code;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
generateForOf(node) {
|
|
778
|
+
const decl = node.declaration || 'const';
|
|
779
|
+
const id = node.id.name || node.id;
|
|
780
|
+
const expr = this.generateExpression(node.expression);
|
|
781
|
+
let code = this.emit(`${this.getIndent()}for (${decl} ${id} of ${expr}) {\n`, node);
|
|
782
|
+
this.indent++;
|
|
783
|
+
for (const stmt of node.body) {
|
|
784
|
+
code += this.generateStatement(stmt);
|
|
785
|
+
}
|
|
786
|
+
this.indent--;
|
|
787
|
+
code += this.emit(`${this.getIndent()}}\n`);
|
|
788
|
+
return code;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
generateForIn(node) {
|
|
792
|
+
const decl = node.declaration || 'const';
|
|
793
|
+
const id = node.id.name || node.id;
|
|
794
|
+
const expr = this.generateExpression(node.expression);
|
|
795
|
+
let code = this.emit(`${this.getIndent()}for (${decl} ${id} in ${expr}) {\n`, node);
|
|
796
|
+
this.indent++;
|
|
797
|
+
for (const stmt of node.body) {
|
|
798
|
+
code += this.generateStatement(stmt);
|
|
799
|
+
}
|
|
800
|
+
this.indent--;
|
|
801
|
+
code += this.emit(`${this.getIndent()}}\n`);
|
|
802
|
+
return code;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
generateWhile(node) {
|
|
806
|
+
let code = this.emit(`${this.getIndent()}while (${this.generateExpression(node.condition)}) {\n`, node);
|
|
807
|
+
this.indent++;
|
|
808
|
+
for (const stmt of node.body) {
|
|
809
|
+
code += this.generateStatement(stmt);
|
|
810
|
+
}
|
|
811
|
+
this.indent--;
|
|
812
|
+
code += this.emit(`${this.getIndent()}}\n`);
|
|
813
|
+
return code;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
generateReturn(node) {
|
|
817
|
+
const value = node.value ? ` ${this.generateExpression(node.value)}` : '';
|
|
818
|
+
return this.emit(`${this.getIndent()}return${value};\n`, node);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
generateSend(node) {
|
|
822
|
+
let code = `await Runtime.send({\n`;
|
|
823
|
+
this.indent++;
|
|
824
|
+
code += `${this.getIndent()}base: ${this.generateExpression(node.target.base)},\n`;
|
|
825
|
+
|
|
826
|
+
if (node.target.filters.length > 0) {
|
|
827
|
+
code += `${this.getIndent()}filters: [\n`;
|
|
828
|
+
this.indent++;
|
|
829
|
+
for (const filter of node.target.filters) {
|
|
830
|
+
if (filter.type === 'EventFilter') {
|
|
831
|
+
code += `${this.getIndent()}{ type: 'event', name: ${this.generateExpression(filter.event)} },\n`;
|
|
832
|
+
} else if (filter.type === 'RoleFilter') {
|
|
833
|
+
code += `${this.getIndent()}{ type: 'role', role: ${filter.role.name} },\n`;
|
|
834
|
+
} else if (filter.type === 'SelectionFilter') {
|
|
835
|
+
code += `${this.getIndent()}{ type: 'select', mode: '${filter.mode}' },\n`;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
this.indent--;
|
|
839
|
+
code += `${this.getIndent()}],\n`;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Pass arguments: single argument directly, multiple as array
|
|
843
|
+
if (node.arguments.length === 1) {
|
|
844
|
+
code += `${this.getIndent()}args: ${this.generateExpression(node.arguments[0])},\n`;
|
|
845
|
+
} else if (node.arguments.length > 1) {
|
|
846
|
+
code += `${this.getIndent()}args: [${node.arguments.map(arg => this.generateExpression(arg)).join(', ')}],\n`;
|
|
847
|
+
} else {
|
|
848
|
+
code += `${this.getIndent()}args: {},\n`;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (node.timeout) {
|
|
852
|
+
code += `${this.getIndent()}timeout: ${node.timeout.value}${node.timeout.unit === 's' ? '000' : ''}\n`;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
this.indent--;
|
|
856
|
+
code += `${this.getIndent()}})`;
|
|
857
|
+
return this.emit(`${this.getIndent()}${code};\n`, node);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// ============================================================
|
|
861
|
+
// Expressions
|
|
862
|
+
// ============================================================
|
|
863
|
+
|
|
864
|
+
generateExpression(node) {
|
|
865
|
+
if (!node) return 'null';
|
|
866
|
+
|
|
867
|
+
switch (node.type) {
|
|
868
|
+
case 'BinaryExpression':
|
|
869
|
+
return `(${this.generateExpression(node.left)} ${node.operator} ${this.generateExpression(node.right)})`;
|
|
870
|
+
case 'UnaryExpression':
|
|
871
|
+
return `${node.operator}${this.generateExpression(node.argument)}`;
|
|
872
|
+
case 'NewExpression':
|
|
873
|
+
return this.generateNewExpression(node);
|
|
874
|
+
case 'CallExpression':
|
|
875
|
+
return this.generateCall(node);
|
|
876
|
+
case 'MemberExpression':
|
|
877
|
+
return this.generateMember(node);
|
|
878
|
+
case 'AwaitExpression':
|
|
879
|
+
return this.generateAwaitExpression(node);
|
|
880
|
+
case 'Identifier':
|
|
881
|
+
// Add 'this.' prefix for agent properties when inside event handler
|
|
882
|
+
if (this.inEventHandler && (node.name === 'peers' || node.name === 'state')) {
|
|
883
|
+
return `this.${node.name}`;
|
|
884
|
+
}
|
|
885
|
+
return node.name;
|
|
886
|
+
case 'StringLiteral':
|
|
887
|
+
return JSON.stringify(node.value);
|
|
888
|
+
case 'NumberLiteral':
|
|
889
|
+
return String(node.value);
|
|
890
|
+
case 'BooleanLiteral':
|
|
891
|
+
return String(node.value);
|
|
892
|
+
case 'NullLiteral':
|
|
893
|
+
return 'null';
|
|
894
|
+
case 'ObjectLiteral':
|
|
895
|
+
return this.generateObject(node);
|
|
896
|
+
case 'ArrayLiteral':
|
|
897
|
+
return this.generateArray(node);
|
|
898
|
+
case 'ArrowFunction':
|
|
899
|
+
return this.generateArrowFunction(node);
|
|
900
|
+
case 'TemplateLiteral':
|
|
901
|
+
return this.generateTemplateLiteral(node);
|
|
902
|
+
case 'AssignmentExpression':
|
|
903
|
+
return this.generateAssignment(node);
|
|
904
|
+
default:
|
|
905
|
+
return `/* Unknown expr: ${node.type} */`;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
transformSendExpressions(code) {
|
|
910
|
+
// Transform: await send target.event("name").role(Role).any()(args) timeout Xs
|
|
911
|
+
// To: await Runtime.send({ base: this.target, filters: [...], args: args, timeout: X000 })
|
|
912
|
+
|
|
913
|
+
const sendRegex = /await\s+send\s+(\w+)\.event\("([^"]+)"\)((?:\.\w+\([^)]*\))*)\s*\(([^)]*)\)(?:\s+timeout\s+(\d+)(s|ms))?/g;
|
|
914
|
+
|
|
915
|
+
return code.replace(sendRegex, (match, target, eventName, chain, args, timeoutVal, timeoutUnit) => {
|
|
916
|
+
const filters = [`{ type: 'event', name: "${eventName}" }`];
|
|
917
|
+
|
|
918
|
+
// Parse the chain (.role(X).any())
|
|
919
|
+
if (chain) {
|
|
920
|
+
const roleMatch = chain.match(/\.role\((\w+)\)/);
|
|
921
|
+
if (roleMatch) {
|
|
922
|
+
filters.push(`{ type: 'role', role: ${roleMatch[1]} }`);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const selectMatch = chain.match(/\.(any|all|first)\(\)/);
|
|
926
|
+
if (selectMatch) {
|
|
927
|
+
filters.push(`{ type: 'select', mode: '${selectMatch[1]}' }`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// If target is 'peers', it refers to this.peers in the agent context
|
|
932
|
+
const targetExpr = target === 'peers' ? 'this.peers' : target;
|
|
933
|
+
|
|
934
|
+
let result = `await Runtime.send({ base: ${targetExpr}, filters: [${filters.join(', ')}], args: ${args || '{}'}`;
|
|
935
|
+
|
|
936
|
+
if (timeoutVal) {
|
|
937
|
+
const timeout = timeoutUnit === 's' ? parseInt(timeoutVal) * 1000 : parseInt(timeoutVal);
|
|
938
|
+
result += `, timeout: ${timeout}`;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
result += ' })';
|
|
942
|
+
return result;
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
generateAwaitExpression(node) {
|
|
947
|
+
// Check if this is a regular await expression (await someFunction())
|
|
948
|
+
// or a send expression (await send ...)
|
|
949
|
+
if (node.argument) {
|
|
950
|
+
// Regular await expression - just generate: await <expression>
|
|
951
|
+
return `await ${this.generateExpression(node.argument)}`;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Send expression - generate Runtime.send(...)
|
|
955
|
+
let code = `await Runtime.send({\n`;
|
|
956
|
+
this.indent++;
|
|
957
|
+
code += `${this.getIndent()}base: ${this.generateExpression(node.target.base)},\n`;
|
|
958
|
+
|
|
959
|
+
if (node.target.filters.length > 0) {
|
|
960
|
+
code += `${this.getIndent()}filters: [\n`;
|
|
961
|
+
this.indent++;
|
|
962
|
+
for (const filter of node.target.filters) {
|
|
963
|
+
if (filter.type === 'EventFilter') {
|
|
964
|
+
code += `${this.getIndent()}{ type: 'event', name: ${this.generateExpression(filter.event)} },\n`;
|
|
965
|
+
} else if (filter.type === 'RoleFilter') {
|
|
966
|
+
code += `${this.getIndent()}{ type: 'role', role: ${filter.role.name} },\n`;
|
|
967
|
+
} else if (filter.type === 'SelectionFilter') {
|
|
968
|
+
code += `${this.getIndent()}{ type: 'select', mode: '${filter.mode}' },\n`;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
this.indent--;
|
|
972
|
+
code += `${this.getIndent()}],\n`;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Pass arguments: single argument directly, multiple as array
|
|
976
|
+
if (node.arguments.length === 1) {
|
|
977
|
+
code += `${this.getIndent()}args: ${this.generateExpression(node.arguments[0])},\n`;
|
|
978
|
+
} else if (node.arguments.length > 1) {
|
|
979
|
+
code += `${this.getIndent()}args: [${node.arguments.map(arg => this.generateExpression(arg)).join(', ')}],\n`;
|
|
980
|
+
} else {
|
|
981
|
+
code += `${this.getIndent()}args: {},\n`;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (node.timeout) {
|
|
985
|
+
code += `${this.getIndent()}timeout: ${node.timeout.value}${node.timeout.unit === 's' ? '000' : ''}\n`;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
this.indent--;
|
|
989
|
+
code += `${this.getIndent()}})`;
|
|
990
|
+
return code;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
generateNewExpression(node) {
|
|
994
|
+
const callee = this.generateExpression(node.callee);
|
|
995
|
+
const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
|
|
996
|
+
return `new ${callee}(${args})`;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
generateCall(node) {
|
|
1000
|
+
// Special handling for peers(TeamName) - access specific team
|
|
1001
|
+
if (this.inEventHandler &&
|
|
1002
|
+
node.callee.type === 'Identifier' &&
|
|
1003
|
+
node.callee.name === 'peers' &&
|
|
1004
|
+
node.arguments.length === 1) {
|
|
1005
|
+
// peers(TeamName) → this._getTeam(TeamName)
|
|
1006
|
+
const teamName = this.generateExpression(node.arguments[0]);
|
|
1007
|
+
return `this._getTeam(${teamName})`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const callee = this.generateExpression(node.callee);
|
|
1011
|
+
const args = node.arguments.map(arg => this.generateExpression(arg)).join(', ');
|
|
1012
|
+
return `${callee}(${args})`;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
generateMember(node) {
|
|
1016
|
+
const obj = this.generateExpression(node.object);
|
|
1017
|
+
|
|
1018
|
+
// Check if property is computed (array access with brackets)
|
|
1019
|
+
if (node.computed || node.property.type === 'NumberLiteral') {
|
|
1020
|
+
const prop = this.generateExpression(node.property);
|
|
1021
|
+
return `${obj}[${prop}]`;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Regular property access with dot notation
|
|
1025
|
+
const prop = typeof node.property === 'string'
|
|
1026
|
+
? node.property
|
|
1027
|
+
: node.property.name || this.generateExpression(node.property);
|
|
1028
|
+
return `${obj}.${prop}`;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
generateObject(node) {
|
|
1032
|
+
if (node.properties.length === 0) return '{}';
|
|
1033
|
+
|
|
1034
|
+
const props = node.properties.map(prop => {
|
|
1035
|
+
// Handle spread properties (...expr)
|
|
1036
|
+
if (prop.type === 'SpreadProperty') {
|
|
1037
|
+
return `...${this.generateExpression(prop.argument)}`;
|
|
1038
|
+
}
|
|
1039
|
+
// Regular key: value properties
|
|
1040
|
+
const key = prop.key.name || prop.key.value;
|
|
1041
|
+
const value = this.generateExpression(prop.value);
|
|
1042
|
+
return `${key}: ${value}`;
|
|
1043
|
+
}).join(', ');
|
|
1044
|
+
|
|
1045
|
+
return `{ ${props} }`;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
generateArray(node) {
|
|
1049
|
+
const elements = node.elements.map(el => this.generateExpression(el)).join(', ');
|
|
1050
|
+
return `[${elements}]`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
generateArrowFunction(node) {
|
|
1054
|
+
// Generate parameters
|
|
1055
|
+
const params = node.params.map(p => p.name || p).join(', ');
|
|
1056
|
+
|
|
1057
|
+
// Generate body
|
|
1058
|
+
if (node.body.type === 'BlockStatement') {
|
|
1059
|
+
// Multi-statement body with {}
|
|
1060
|
+
let body = '{\n';
|
|
1061
|
+
this.indent++;
|
|
1062
|
+
for (const stmt of node.body.statements) {
|
|
1063
|
+
body += this.generateStatement(stmt);
|
|
1064
|
+
}
|
|
1065
|
+
this.indent--;
|
|
1066
|
+
body += `${this.getIndent()}}`;
|
|
1067
|
+
return `(${params}) => ${body}`;
|
|
1068
|
+
} else {
|
|
1069
|
+
// Expression body (implicit return)
|
|
1070
|
+
const body = this.generateExpression(node.body);
|
|
1071
|
+
|
|
1072
|
+
// If body is an object literal, wrap it in parentheses to avoid ambiguity
|
|
1073
|
+
if (node.body.type === 'ObjectLiteral') {
|
|
1074
|
+
return `(${params}) => (${body})`;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return `(${params}) => ${body}`;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
generateTemplateLiteral(node) {
|
|
1082
|
+
if (node.parts.length === 0) {
|
|
1083
|
+
return '``';
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const parts = node.parts.map(part => {
|
|
1087
|
+
if (part.type === 'TemplateString') {
|
|
1088
|
+
// Raw string part - keep as-is but escape backticks
|
|
1089
|
+
return part.value.replace(/`/g, '\\`');
|
|
1090
|
+
} else if (part.type === 'TemplateExpression') {
|
|
1091
|
+
// Expression part ${...}
|
|
1092
|
+
return '${' + this.generateExpression(part.expression) + '}';
|
|
1093
|
+
}
|
|
1094
|
+
return '';
|
|
1095
|
+
}).join('');
|
|
1096
|
+
|
|
1097
|
+
return `\`${parts}\``;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
generateAssignment(node) {
|
|
1101
|
+
const left = this.generateExpression(node.left);
|
|
1102
|
+
const right = this.generateExpression(node.right);
|
|
1103
|
+
return `${left} ${node.operator} ${right}`;
|
|
1104
|
+
}
|
|
1105
|
+
}
|