@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,369 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { TypeScriptTranspiler } from './typescript-transpiler.js';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves imports recursively and merges imported Skills and Agents into the main AST
|
|
11
|
+
*/
|
|
12
|
+
export class ImportResolver {
|
|
13
|
+
constructor(parse) {
|
|
14
|
+
this.parse = parse;
|
|
15
|
+
this.importedFiles = new Set(); // Track imported files to avoid circular imports
|
|
16
|
+
this.importedSkills = []; // Collect all imported Skills
|
|
17
|
+
this.importedAgents = []; // Collect all imported Agents
|
|
18
|
+
this.importedRoles = []; // Collect all imported Roles
|
|
19
|
+
this.importedTeams = []; // Collect all imported Teams
|
|
20
|
+
this.externalImports = []; // Track TypeScript/JavaScript imports
|
|
21
|
+
this.tsTranspiler = new TypeScriptTranspiler(); // TypeScript transpiler
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolve all imports in an AST recursively
|
|
26
|
+
* @param {object} ast - The AST to process
|
|
27
|
+
* @param {string} sourceFile - The source file path (for resolving relative paths)
|
|
28
|
+
* @returns {object} - AST with imports resolved and all declarations merged
|
|
29
|
+
*/
|
|
30
|
+
async resolveImports(ast, sourceFile) {
|
|
31
|
+
const sourceDir = path.dirname(sourceFile);
|
|
32
|
+
const imports = [];
|
|
33
|
+
const nonImportDecls = [];
|
|
34
|
+
|
|
35
|
+
// Separate imports from other declarations
|
|
36
|
+
for (const decl of ast.declarations) {
|
|
37
|
+
if (decl.type === 'ImportDecl') {
|
|
38
|
+
imports.push(decl);
|
|
39
|
+
} else {
|
|
40
|
+
nonImportDecls.push(decl);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Process each import
|
|
45
|
+
for (const importDecl of imports) {
|
|
46
|
+
const importPath = importDecl.name.value;
|
|
47
|
+
const resolvedPath = this.resolveImportPath(importPath, sourceDir, sourceFile);
|
|
48
|
+
|
|
49
|
+
// Check if this is an external (TypeScript/JavaScript) import
|
|
50
|
+
const ext = path.extname(resolvedPath);
|
|
51
|
+
const isExternalImport = ['.ts', '.tsx', '.js', '.jsx', '.mjs'].includes(ext);
|
|
52
|
+
|
|
53
|
+
if (isExternalImport) {
|
|
54
|
+
// Handle TypeScript/JavaScript imports
|
|
55
|
+
let finalPath = resolvedPath;
|
|
56
|
+
|
|
57
|
+
// Transpile TypeScript files to JavaScript
|
|
58
|
+
if (ext === '.ts' || ext === '.tsx') {
|
|
59
|
+
try {
|
|
60
|
+
finalPath = this.tsTranspiler.transpile(resolvedPath);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(`Failed to transpile TypeScript file "${importPath}": ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!this.externalImports.find(e => e.resolvedPath === finalPath)) {
|
|
67
|
+
this.externalImports.push({
|
|
68
|
+
originalPath: importPath,
|
|
69
|
+
resolvedPath: finalPath,
|
|
70
|
+
sourceFile: sourceFile,
|
|
71
|
+
isTypeScript: ext === '.ts' || ext === '.tsx'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for circular imports
|
|
78
|
+
if (this.importedFiles.has(resolvedPath)) {
|
|
79
|
+
console.warn(`⚠️ Skipping circular import: ${importPath}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mark as imported
|
|
84
|
+
this.importedFiles.add(resolvedPath);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Load and parse the imported file
|
|
88
|
+
const importedSource = fs.readFileSync(resolvedPath, 'utf-8');
|
|
89
|
+
const importedAst = this.parse(importedSource);
|
|
90
|
+
|
|
91
|
+
// Recursively resolve imports in the imported file
|
|
92
|
+
const resolvedImportedAst = await this.resolveImports(importedAst, resolvedPath);
|
|
93
|
+
|
|
94
|
+
// Extract all exportable declarations from imported file
|
|
95
|
+
for (const decl of resolvedImportedAst.declarations) {
|
|
96
|
+
if (decl.type === 'SkillDecl') {
|
|
97
|
+
const existingSkill = this.importedSkills.find(s => s.name.name === decl.name.name);
|
|
98
|
+
if (!existingSkill) {
|
|
99
|
+
this.importedSkills.push(decl);
|
|
100
|
+
} else {
|
|
101
|
+
console.warn(`⚠️ Duplicate Skill "${decl.name.name}" found in ${importPath}, skipping`);
|
|
102
|
+
}
|
|
103
|
+
} else if (decl.type === 'AgentDecl') {
|
|
104
|
+
const existingAgent = this.importedAgents.find(a => a.name.name === decl.name.name);
|
|
105
|
+
if (!existingAgent) {
|
|
106
|
+
this.importedAgents.push(decl);
|
|
107
|
+
} else {
|
|
108
|
+
console.warn(`⚠️ Duplicate Agent "${decl.name.name}" found in ${importPath}, skipping`);
|
|
109
|
+
}
|
|
110
|
+
} else if (decl.type === 'RoleDecl') {
|
|
111
|
+
const existingRole = this.importedRoles.find(r => r.name.name === decl.name.name);
|
|
112
|
+
if (!existingRole) {
|
|
113
|
+
this.importedRoles.push(decl);
|
|
114
|
+
} else {
|
|
115
|
+
console.warn(`⚠️ Duplicate Role "${decl.name.name}" found in ${importPath}, skipping`);
|
|
116
|
+
}
|
|
117
|
+
} else if (decl.type === 'TeamDecl') {
|
|
118
|
+
const existingTeam = this.importedTeams.find(t => t.name.name === decl.name.name);
|
|
119
|
+
if (!existingTeam) {
|
|
120
|
+
this.importedTeams.push(decl);
|
|
121
|
+
} else {
|
|
122
|
+
console.warn(`⚠️ Duplicate Team "${decl.name.name}" found in ${importPath}, skipping`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new Error(`Failed to import from "${importPath}": ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Merge imported declarations with current declarations
|
|
132
|
+
// Order: Roles -> Skills -> Agents -> Teams -> Other
|
|
133
|
+
const mergedDeclarations = [
|
|
134
|
+
...this.importedRoles,
|
|
135
|
+
...this.importedSkills,
|
|
136
|
+
...this.importedAgents,
|
|
137
|
+
...this.importedTeams,
|
|
138
|
+
...nonImportDecls
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
...ast,
|
|
143
|
+
declarations: mergedDeclarations
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Resolve import path - supports relative paths, absolute paths, and node_modules
|
|
149
|
+
* @param {string} importPath - The import path from the import statement
|
|
150
|
+
* @param {string} sourceDir - The directory of the source file
|
|
151
|
+
* @param {string} sourceFile - The full source file path
|
|
152
|
+
* @returns {string} - Absolute path to the imported file
|
|
153
|
+
*/
|
|
154
|
+
resolveImportPath(importPath, sourceDir, sourceFile) {
|
|
155
|
+
// 1. Relative paths (./ or ../)
|
|
156
|
+
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
157
|
+
const resolved = this.resolveRelativePath(importPath, sourceDir);
|
|
158
|
+
if (fs.existsSync(resolved)) {
|
|
159
|
+
return resolved;
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`Import file not found: ${resolved}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 2. Absolute paths
|
|
165
|
+
if (path.isAbsolute(importPath)) {
|
|
166
|
+
if (fs.existsSync(importPath)) {
|
|
167
|
+
return importPath;
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`Import file not found: ${importPath}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 3. Package imports (from node_modules)
|
|
173
|
+
const packageResolved = this.resolveFromNodeModules(importPath, sourceDir);
|
|
174
|
+
if (packageResolved) {
|
|
175
|
+
return packageResolved;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
throw new Error(`Cannot resolve import: ${importPath}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolve a relative path, trying different extensions and directory index
|
|
183
|
+
*/
|
|
184
|
+
resolveRelativePath(importPath, sourceDir) {
|
|
185
|
+
const basePath = path.resolve(sourceDir, importPath);
|
|
186
|
+
|
|
187
|
+
// 1. Try exact path first
|
|
188
|
+
if (fs.existsSync(basePath)) {
|
|
189
|
+
// If it's a file, return it
|
|
190
|
+
if (fs.statSync(basePath).isFile()) {
|
|
191
|
+
return basePath;
|
|
192
|
+
}
|
|
193
|
+
// If it's a directory, try index files
|
|
194
|
+
if (fs.statSync(basePath).isDirectory()) {
|
|
195
|
+
// Try index.koi first
|
|
196
|
+
const indexKoi = path.join(basePath, 'index.koi');
|
|
197
|
+
if (fs.existsSync(indexKoi)) {
|
|
198
|
+
return indexKoi;
|
|
199
|
+
}
|
|
200
|
+
// Try index.ts
|
|
201
|
+
const indexTs = path.join(basePath, 'index.ts');
|
|
202
|
+
if (fs.existsSync(indexTs)) {
|
|
203
|
+
return indexTs;
|
|
204
|
+
}
|
|
205
|
+
// Try index.js
|
|
206
|
+
const indexJs = path.join(basePath, 'index.js');
|
|
207
|
+
if (fs.existsSync(indexJs)) {
|
|
208
|
+
return indexJs;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 2. Try with different extensions if no extension provided
|
|
214
|
+
if (!path.extname(basePath)) {
|
|
215
|
+
const extensions = ['.koi', '.ts', '.tsx', '.js', '.jsx', '.mjs'];
|
|
216
|
+
for (const ext of extensions) {
|
|
217
|
+
const withExt = basePath + ext;
|
|
218
|
+
if (fs.existsSync(withExt)) {
|
|
219
|
+
return withExt;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 3. Try as directory with index files
|
|
225
|
+
const indexKoi = path.join(basePath, 'index.koi');
|
|
226
|
+
if (fs.existsSync(indexKoi)) {
|
|
227
|
+
return indexKoi;
|
|
228
|
+
}
|
|
229
|
+
const indexTs = path.join(basePath, 'index.ts');
|
|
230
|
+
if (fs.existsSync(indexTs)) {
|
|
231
|
+
return indexTs;
|
|
232
|
+
}
|
|
233
|
+
const indexJs = path.join(basePath, 'index.js');
|
|
234
|
+
if (fs.existsSync(indexJs)) {
|
|
235
|
+
return indexJs;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return basePath;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Resolve from node_modules, checking each parent directory
|
|
243
|
+
*/
|
|
244
|
+
resolveFromNodeModules(packageName, sourceDir) {
|
|
245
|
+
let currentDir = sourceDir;
|
|
246
|
+
|
|
247
|
+
// Walk up the directory tree looking for node_modules
|
|
248
|
+
while (true) {
|
|
249
|
+
const nodeModulesPath = path.join(currentDir, 'node_modules', packageName);
|
|
250
|
+
|
|
251
|
+
// Try to resolve the package
|
|
252
|
+
const resolved = this.resolvePackage(nodeModulesPath, packageName);
|
|
253
|
+
if (resolved) {
|
|
254
|
+
return resolved;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Move to parent directory
|
|
258
|
+
const parentDir = path.dirname(currentDir);
|
|
259
|
+
if (parentDir === currentDir) {
|
|
260
|
+
// Reached root, stop
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
currentDir = parentDir;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Resolve a package, checking package.json for main/exports
|
|
271
|
+
*/
|
|
272
|
+
resolvePackage(packagePath, packageName) {
|
|
273
|
+
// Check if package directory exists
|
|
274
|
+
if (!fs.existsSync(packagePath)) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Try package.json
|
|
279
|
+
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
280
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
281
|
+
try {
|
|
282
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
283
|
+
|
|
284
|
+
// Check "koi" field (custom field for Koi packages)
|
|
285
|
+
if (packageJson.koi) {
|
|
286
|
+
const koiEntry = path.join(packagePath, packageJson.koi);
|
|
287
|
+
if (fs.existsSync(koiEntry)) {
|
|
288
|
+
return koiEntry;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check "main" field
|
|
293
|
+
if (packageJson.main) {
|
|
294
|
+
const mainPath = path.join(packagePath, packageJson.main);
|
|
295
|
+
if (fs.existsSync(mainPath)) {
|
|
296
|
+
return mainPath;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check "exports" field (modern Node.js)
|
|
301
|
+
if (packageJson.exports) {
|
|
302
|
+
const exports = packageJson.exports;
|
|
303
|
+
if (typeof exports === 'string') {
|
|
304
|
+
const exportPath = path.join(packagePath, exports);
|
|
305
|
+
if (fs.existsSync(exportPath)) {
|
|
306
|
+
return exportPath;
|
|
307
|
+
}
|
|
308
|
+
} else if (exports['.']) {
|
|
309
|
+
const defaultExport = typeof exports['.'] === 'string' ? exports['.'] : exports['.'].default;
|
|
310
|
+
if (defaultExport) {
|
|
311
|
+
const exportPath = path.join(packagePath, defaultExport);
|
|
312
|
+
if (fs.existsSync(exportPath)) {
|
|
313
|
+
return exportPath;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.warn(`⚠️ Failed to parse package.json for ${packageName}: ${error.message}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Try index files in order: .koi, .ts, .js
|
|
324
|
+
const indexKoi = path.join(packagePath, 'index.koi');
|
|
325
|
+
if (fs.existsSync(indexKoi)) {
|
|
326
|
+
return indexKoi;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const indexTs = path.join(packagePath, 'index.ts');
|
|
330
|
+
if (fs.existsSync(indexTs)) {
|
|
331
|
+
return indexTs;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const indexJs = path.join(packagePath, 'index.js');
|
|
335
|
+
if (fs.existsSync(indexJs)) {
|
|
336
|
+
return indexJs;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Try packageName with extensions
|
|
340
|
+
const namedKoi = path.join(packagePath, `${packageName}.koi`);
|
|
341
|
+
if (fs.existsSync(namedKoi)) {
|
|
342
|
+
return namedKoi;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const namedTs = path.join(packagePath, `${packageName}.ts`);
|
|
346
|
+
if (fs.existsSync(namedTs)) {
|
|
347
|
+
return namedTs;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const namedJs = path.join(packagePath, `${packageName}.js`);
|
|
351
|
+
if (fs.existsSync(namedJs)) {
|
|
352
|
+
return namedJs;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Reset the resolver state
|
|
360
|
+
*/
|
|
361
|
+
reset() {
|
|
362
|
+
this.importedFiles.clear();
|
|
363
|
+
this.importedSkills = [];
|
|
364
|
+
this.importedAgents = [];
|
|
365
|
+
this.importedRoles = [];
|
|
366
|
+
this.importedTeams = [];
|
|
367
|
+
this.externalImports = [];
|
|
368
|
+
}
|
|
369
|
+
}
|