@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.
Files changed (85) hide show
  1. package/QUICKSTART.md +89 -0
  2. package/README.md +545 -0
  3. package/examples/actions-demo.koi +177 -0
  4. package/examples/cache-test.koi +29 -0
  5. package/examples/calculator.koi +61 -0
  6. package/examples/clear-registry.js +33 -0
  7. package/examples/clear-registry.koi +30 -0
  8. package/examples/code-introspection-test.koi +149 -0
  9. package/examples/counter.koi +132 -0
  10. package/examples/delegation-test.koi +52 -0
  11. package/examples/directory-import-test.koi +84 -0
  12. package/examples/hello-world-claude.koi +52 -0
  13. package/examples/hello-world.koi +52 -0
  14. package/examples/hello.koi +24 -0
  15. package/examples/mcp-example.koi +70 -0
  16. package/examples/multi-event-handler-test.koi +144 -0
  17. package/examples/new-import-test.koi +89 -0
  18. package/examples/pipeline.koi +162 -0
  19. package/examples/registry-demo.koi +184 -0
  20. package/examples/registry-playbook-demo.koi +162 -0
  21. package/examples/registry-playbook-email-compositor-2.koi +140 -0
  22. package/examples/registry-playbook-email-compositor.koi +140 -0
  23. package/examples/sentiment.koi +90 -0
  24. package/examples/simple.koi +48 -0
  25. package/examples/skill-import-test.koi +76 -0
  26. package/examples/skills/advanced/index.koi +95 -0
  27. package/examples/skills/math-operations.koi +69 -0
  28. package/examples/skills/string-operations.koi +56 -0
  29. package/examples/task-chaining-demo.koi +244 -0
  30. package/examples/test-await.koi +22 -0
  31. package/examples/test-crypto-sha256.koi +196 -0
  32. package/examples/test-delegation.koi +41 -0
  33. package/examples/test-multi-team-routing.koi +258 -0
  34. package/examples/test-no-handler.koi +35 -0
  35. package/examples/test-npm-import.koi +67 -0
  36. package/examples/test-parse.koi +10 -0
  37. package/examples/test-peers-with-team.koi +59 -0
  38. package/examples/test-permissions-fail.koi +20 -0
  39. package/examples/test-permissions.koi +36 -0
  40. package/examples/test-simple-registry.koi +31 -0
  41. package/examples/test-typescript-import.koi +64 -0
  42. package/examples/test-uses-team-syntax.koi +25 -0
  43. package/examples/test-uses-team.koi +31 -0
  44. package/examples/utils/calculator.test.ts +144 -0
  45. package/examples/utils/calculator.ts +56 -0
  46. package/examples/utils/math-helpers.js +50 -0
  47. package/examples/utils/math-helpers.ts +55 -0
  48. package/examples/web-delegation-demo.koi +165 -0
  49. package/package.json +78 -0
  50. package/src/cli/koi.js +793 -0
  51. package/src/compiler/build-optimizer.js +447 -0
  52. package/src/compiler/cache-manager.js +274 -0
  53. package/src/compiler/import-resolver.js +369 -0
  54. package/src/compiler/parser.js +7542 -0
  55. package/src/compiler/transpiler.js +1105 -0
  56. package/src/compiler/typescript-transpiler.js +148 -0
  57. package/src/grammar/koi.pegjs +767 -0
  58. package/src/runtime/action-registry.js +172 -0
  59. package/src/runtime/actions/call-skill.js +45 -0
  60. package/src/runtime/actions/format.js +115 -0
  61. package/src/runtime/actions/print.js +42 -0
  62. package/src/runtime/actions/registry-delete.js +37 -0
  63. package/src/runtime/actions/registry-get.js +37 -0
  64. package/src/runtime/actions/registry-keys.js +33 -0
  65. package/src/runtime/actions/registry-search.js +34 -0
  66. package/src/runtime/actions/registry-set.js +50 -0
  67. package/src/runtime/actions/return.js +31 -0
  68. package/src/runtime/actions/send-message.js +58 -0
  69. package/src/runtime/actions/update-state.js +36 -0
  70. package/src/runtime/agent.js +1368 -0
  71. package/src/runtime/cli-logger.js +205 -0
  72. package/src/runtime/incremental-json-parser.js +201 -0
  73. package/src/runtime/index.js +33 -0
  74. package/src/runtime/llm-provider.js +1372 -0
  75. package/src/runtime/mcp-client.js +1171 -0
  76. package/src/runtime/planner.js +273 -0
  77. package/src/runtime/registry-backends/keyv-sqlite.js +215 -0
  78. package/src/runtime/registry-backends/local.js +260 -0
  79. package/src/runtime/registry.js +162 -0
  80. package/src/runtime/role.js +14 -0
  81. package/src/runtime/router.js +395 -0
  82. package/src/runtime/runtime.js +113 -0
  83. package/src/runtime/skill-selector.js +173 -0
  84. package/src/runtime/skill.js +25 -0
  85. 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
+ }