@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,205 @@
1
+ /**
2
+ * CLI Logger - Single-line progress updates like Claude Code CLI
3
+ *
4
+ * Usage:
5
+ * cliLogger.progress('Processing...') - Updates same line
6
+ * cliLogger.success('Done!') - New line with result
7
+ * cliLogger.error('Failed!') - New line with error
8
+ * cliLogger.clear() - Clear current line
9
+ */
10
+
11
+ class CLILogger {
12
+ constructor() {
13
+ this.currentLine = '';
14
+ this.isProgress = false;
15
+ this.animationInterval = null;
16
+ this.animationDots = 0;
17
+ this.isAnimating = false; // Track if we're in animation mode
18
+ this.indentStack = []; // Stack of messages showing delegation hierarchy
19
+ this.indentLevel = 0;
20
+
21
+ // Intercept console methods to auto-clear progress
22
+ this.setupConsoleIntercept();
23
+ }
24
+
25
+ setupConsoleIntercept() {
26
+ const originalLog = console.log;
27
+ const originalError = console.error;
28
+ const originalWarn = console.warn;
29
+ const originalInfo = console.info;
30
+ const self = this;
31
+
32
+ console.log = function(...args) {
33
+ self.clearProgress();
34
+ originalLog.apply(console, args);
35
+ };
36
+
37
+ console.error = function(...args) {
38
+ self.clearProgress();
39
+ originalError.apply(console, args);
40
+ };
41
+
42
+ console.warn = function(...args) {
43
+ self.clearProgress();
44
+ originalWarn.apply(console, args);
45
+ };
46
+
47
+ console.info = function(...args) {
48
+ self.clearProgress();
49
+ originalInfo.apply(console, args);
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Push a message to the delegation stack (indented)
55
+ */
56
+ pushIndent(message) {
57
+ this.indentLevel++;
58
+ const indent = ' '.repeat(this.indentLevel);
59
+ this.indentStack.push({ message, indent, level: this.indentLevel });
60
+ this.progress(`${indent}→ ${message}`);
61
+ }
62
+
63
+ /**
64
+ * Pop the last delegation from stack and restore parent context
65
+ */
66
+ popIndent() {
67
+ if (this.indentStack.length > 0) {
68
+ this.indentStack.pop();
69
+ this.indentLevel = Math.max(0, this.indentLevel - 1);
70
+
71
+ // Clear and restore parent context
72
+ this.clear();
73
+
74
+ // Re-render parent if exists
75
+ if (this.indentStack.length > 0) {
76
+ const parent = this.indentStack[this.indentStack.length - 1];
77
+ this.progress(`${parent.indent}→ ${parent.message}`);
78
+ }
79
+ } else {
80
+ this.indentLevel = 0;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Clear all indentation stack
86
+ */
87
+ clearStack() {
88
+ this.indentStack = [];
89
+ this.indentLevel = 0;
90
+ this.clear();
91
+ }
92
+
93
+ /**
94
+ * Get current indent string
95
+ */
96
+ getIndent() {
97
+ return ' '.repeat(this.indentLevel);
98
+ }
99
+
100
+ /**
101
+ * Show planning state with animated spinning stick
102
+ */
103
+ planning(prefix) {
104
+ // Stop any existing animation
105
+ this.stopAnimation();
106
+
107
+ const baseMessage = prefix || 'Thinking';
108
+ this.animationDots = 0;
109
+ this.isAnimating = true;
110
+ const spinnerChars = ['|', '/', '-', '\\'];
111
+
112
+ // Initial render
113
+ this.progress(`${baseMessage} ${spinnerChars[0]}`);
114
+
115
+ // Start animation
116
+ this.animationInterval = setInterval(() => {
117
+ this.animationDots = (this.animationDots + 1) % spinnerChars.length;
118
+ const spinner = spinnerChars[this.animationDots];
119
+ this.progress(`${baseMessage} ${spinner}`);
120
+ }, 150);
121
+ }
122
+
123
+ /**
124
+ * Stop animation if running
125
+ */
126
+ stopAnimation() {
127
+ if (this.animationInterval) {
128
+ clearInterval(this.animationInterval);
129
+ this.animationInterval = null;
130
+ this.animationDots = 0;
131
+ this.isAnimating = false;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Update the same line with progress (no newline)
137
+ */
138
+ progress(message) {
139
+ // Stop animation if it's running and we're not in animation mode
140
+ if (this.animationInterval && !this.isAnimating && !message.includes('...')) {
141
+ this.stopAnimation();
142
+ }
143
+
144
+ // Clear previous line
145
+ if (this.isProgress) {
146
+ process.stdout.write('\r\x1b[K');
147
+ }
148
+
149
+ // Write new message
150
+ process.stdout.write(message);
151
+ this.currentLine = message;
152
+ this.isProgress = true;
153
+ }
154
+
155
+ /**
156
+ * Complete progress and print result on new line
157
+ */
158
+ success(message) {
159
+ this.clearProgress();
160
+ console.log(message);
161
+ }
162
+
163
+ /**
164
+ * Print error on new line
165
+ */
166
+ error(message) {
167
+ this.clearProgress();
168
+ console.error(message);
169
+ }
170
+
171
+ /**
172
+ * Print info on new line
173
+ */
174
+ info(message) {
175
+ this.clearProgress();
176
+ console.log(message);
177
+ }
178
+
179
+ /**
180
+ * Clear current progress line
181
+ */
182
+ clearProgress() {
183
+ this.stopAnimation();
184
+ if (this.isProgress) {
185
+ process.stdout.write('\r\x1b[K');
186
+ this.isProgress = false;
187
+ this.currentLine = '';
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Just clear, no new line
193
+ */
194
+ clear() {
195
+ this.stopAnimation();
196
+ if (this.isProgress) {
197
+ process.stdout.write('\r\x1b[K');
198
+ this.isProgress = false;
199
+ this.currentLine = '';
200
+ }
201
+ }
202
+ }
203
+
204
+ // Singleton instance
205
+ export const cliLogger = new CLILogger();
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Incremental JSON Parser for Streaming LLM Responses
3
+ *
4
+ * Parses JSON stream incrementally and yields complete action objects
5
+ * as soon as they're fully received, without waiting for the entire response.
6
+ */
7
+
8
+ export class IncrementalJSONParser {
9
+ constructor() {
10
+ this.buffer = '';
11
+ this.actionsStartIndex = -1; // Position where "actions":[ was found
12
+ this.lastParsedIndex = 0; // Last position we successfully parsed up to
13
+ this.parsedActions = 0; // Count of actions we've parsed
14
+ }
15
+
16
+ /**
17
+ * Feed more content from the stream
18
+ * Returns array of complete actions found in this chunk
19
+ */
20
+ feed(chunk) {
21
+ const actions = [];
22
+ const oldBufferLength = this.buffer.length;
23
+ this.buffer += chunk;
24
+
25
+ // First, find where the actions array starts (if we haven't found it yet)
26
+ if (this.actionsStartIndex === -1) {
27
+ // Look for "actions" : [ with flexible whitespace
28
+ const match = this.buffer.match(/"actions"\s*:\s*\[/);
29
+ if (match) {
30
+ const actionsIndex = match.index;
31
+ this.actionsStartIndex = actionsIndex + match[0].length; // Position right after "actions": [
32
+ this.lastParsedIndex = this.actionsStartIndex;
33
+
34
+ if (process.env.KOI_DEBUG_LLM) {
35
+ console.error(`[IncrementalParser] 📍 Found "actions" array at position ${actionsIndex}, starting to parse from ${this.actionsStartIndex}`);
36
+ }
37
+ } else {
38
+ // Haven't found actions array yet, wait for more data
39
+ if (process.env.KOI_DEBUG_LLM && this.buffer.length > 50) {
40
+ // Show first 100 chars of buffer to debug what LLM is generating
41
+ const preview = this.buffer.substring(0, 100).replace(/\n/g, '\\n');
42
+ console.error(`[IncrementalParser] ⏳ Waiting for "actions" array (buffer: ${this.buffer.length} chars, preview: "${preview}...")`);
43
+ }
44
+ return actions;
45
+ }
46
+ }
47
+
48
+ // Try to parse actions from the buffer
49
+ // We'll try to extract complete JSON objects from the actions array
50
+ let currentPos = this.lastParsedIndex;
51
+ let depth = 0;
52
+ let inString = false;
53
+ let escapeNext = false;
54
+ let objectStart = -1;
55
+
56
+ for (let i = currentPos; i < this.buffer.length; i++) {
57
+ const char = this.buffer[i];
58
+
59
+ // Handle string escaping
60
+ if (escapeNext) {
61
+ escapeNext = false;
62
+ continue;
63
+ }
64
+
65
+ if (char === '\\' && inString) {
66
+ escapeNext = true;
67
+ continue;
68
+ }
69
+
70
+ // Track if we're inside a string
71
+ if (char === '"') {
72
+ inString = !inString;
73
+ continue;
74
+ }
75
+
76
+ // Skip processing inside strings
77
+ if (inString) {
78
+ continue;
79
+ }
80
+
81
+ // Track object depth
82
+ if (char === '{') {
83
+ if (depth === 0) {
84
+ objectStart = i; // Start of new action object
85
+ }
86
+ depth++;
87
+ } else if (char === '}') {
88
+ depth--;
89
+ if (depth === 0 && objectStart !== -1) {
90
+ // Complete action object found!
91
+ const actionJSON = this.buffer.substring(objectStart, i + 1);
92
+ try {
93
+ const action = JSON.parse(actionJSON);
94
+ actions.push(action);
95
+ this.parsedActions++;
96
+ this.lastParsedIndex = i + 1;
97
+
98
+ if (process.env.KOI_DEBUG_LLM) {
99
+ console.error(`[IncrementalParser] ✅ Parsed action #${this.parsedActions}: ${action.intent || action.type || 'unknown'}`);
100
+ }
101
+ } catch (e) {
102
+ // Failed to parse - might be incomplete, wait for more data
103
+ if (process.env.KOI_DEBUG_LLM) {
104
+ console.error(`[IncrementalParser] ⚠️ Failed to parse object at ${objectStart}: ${e.message}`);
105
+ }
106
+ }
107
+ objectStart = -1;
108
+ }
109
+ } else if (char === ']' && depth === 0) {
110
+ // End of actions array
111
+ if (process.env.KOI_DEBUG_LLM) {
112
+ console.error(`[IncrementalParser] 🏁 End of actions array reached`);
113
+ }
114
+ break;
115
+ }
116
+ }
117
+
118
+ return actions;
119
+ }
120
+
121
+ /**
122
+ * Signal end of stream - parse any remaining content
123
+ */
124
+ finalize() {
125
+ const actions = [];
126
+
127
+ // If we have unparsed content, try one final parse
128
+ if (this.lastParsedIndex < this.buffer.length) {
129
+ if (process.env.KOI_DEBUG_LLM) {
130
+ console.error(`[IncrementalParser] Finalizing - ${this.buffer.length - this.lastParsedIndex} chars remaining`);
131
+ }
132
+
133
+ // Try to find any remaining complete objects
134
+ const remaining = this.buffer.substring(this.lastParsedIndex);
135
+ const objects = this.extractObjects(remaining);
136
+ actions.push(...objects);
137
+ }
138
+
139
+ return actions;
140
+ }
141
+
142
+ /**
143
+ * Extract complete JSON objects from a string
144
+ */
145
+ extractObjects(str) {
146
+ const objects = [];
147
+ let depth = 0;
148
+ let inString = false;
149
+ let escapeNext = false;
150
+ let objectStart = -1;
151
+
152
+ for (let i = 0; i < str.length; i++) {
153
+ const char = str[i];
154
+
155
+ if (escapeNext) {
156
+ escapeNext = false;
157
+ continue;
158
+ }
159
+
160
+ if (char === '\\' && inString) {
161
+ escapeNext = true;
162
+ continue;
163
+ }
164
+
165
+ if (char === '"') {
166
+ inString = !inString;
167
+ continue;
168
+ }
169
+
170
+ if (inString) continue;
171
+
172
+ if (char === '{') {
173
+ if (depth === 0) objectStart = i;
174
+ depth++;
175
+ } else if (char === '}') {
176
+ depth--;
177
+ if (depth === 0 && objectStart !== -1) {
178
+ try {
179
+ const obj = JSON.parse(str.substring(objectStart, i + 1));
180
+ objects.push(obj);
181
+ } catch (e) {
182
+ // Ignore parse errors in finalize
183
+ }
184
+ objectStart = -1;
185
+ }
186
+ }
187
+ }
188
+
189
+ return objects;
190
+ }
191
+
192
+ /**
193
+ * Reset parser state for new stream
194
+ */
195
+ reset() {
196
+ this.buffer = '';
197
+ this.actionsStartIndex = -1;
198
+ this.lastParsedIndex = 0;
199
+ this.parsedActions = 0;
200
+ }
201
+ }
@@ -0,0 +1,33 @@
1
+ export { Agent } from './agent.js';
2
+ export { Team } from './team.js';
3
+ export { Skill } from './skill.js';
4
+ export { Role } from './role.js';
5
+ export { Runtime } from './runtime.js';
6
+ export { MCPClient, mcpClient } from './mcp-client.js';
7
+ export { Planner, PlanningAgent } from './planner.js';
8
+ export { SkillSelector, skillSelector } from './skill-selector.js';
9
+ export { registry, getRegistry } from './registry.js';
10
+
11
+ // Global registry for skill functions (for tool calling)
12
+ export const SkillRegistry = {
13
+ _functions: {},
14
+
15
+ register(skillName, functionName, fn, metadata = {}) {
16
+ if (!this._functions[skillName]) {
17
+ this._functions[skillName] = {};
18
+ }
19
+ this._functions[skillName][functionName] = { fn, metadata };
20
+ },
21
+
22
+ get(skillName, functionName) {
23
+ return this._functions[skillName]?.[functionName];
24
+ },
25
+
26
+ getAll(skillName) {
27
+ return this._functions[skillName] || {};
28
+ },
29
+
30
+ getAllSkills() {
31
+ return this._functions;
32
+ }
33
+ };