future-lang 0.3.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/ARCHITECTURE.md +424 -0
- package/MIGRATION.md +365 -0
- package/README.md +370 -0
- package/ROADMAP.md +263 -0
- package/examples/adult.future +8 -0
- package/examples/api.future +11 -0
- package/examples/assistant.future +8 -0
- package/examples/browser-demo.html +164 -0
- package/examples/greet.future +7 -0
- package/examples/hello.future +1 -0
- package/examples/math.future +8 -0
- package/examples/mini-app.html +301 -0
- package/examples/smarthome.future +10 -0
- package/future-browser.js +102 -0
- package/future-playground.html +650 -0
- package/package.json +27 -0
- package/runtime/ai.js +92 -0
- package/runtime/browser.js +458 -0
- package/runtime/device.js +36 -0
- package/runtime/home.js +19 -0
- package/runtime/http.js +32 -0
- package/runtime/index.js +403 -0
- package/runtime/lsp-metadata.js +104 -0
- package/runtime/math.js +16 -0
- package/runtime/memory.js +61 -0
- package/runtime/mqtt.js +49 -0
- package/runtime/providers/anthropic.js +59 -0
- package/runtime/providers/index.js +93 -0
- package/runtime/providers/openai-compat.js +85 -0
- package/runtime/providers/util.js +70 -0
- package/runtime/rag/chunker.js +65 -0
- package/runtime/rag/pipeline.js +86 -0
- package/runtime/rag/vector-store.js +119 -0
- package/runtime/rag.js +94 -0
- package/runtime/schedule.js +77 -0
- package/runtime/system.js +101 -0
- package/runtime/tts.js +38 -0
- package/runtime/vision.js +85 -0
- package/server.js +42 -0
- package/src/ast.js +202 -0
- package/src/cli.js +391 -0
- package/src/errors.js +21 -0
- package/src/formatter.js +48 -0
- package/src/generator.js +457 -0
- package/src/index.js +48 -0
- package/src/lexer.js +248 -0
- package/src/parser.js +469 -0
package/runtime/index.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// runtime/index.js
|
|
2
|
+
// Aggregates every capability namespace into a single `runtime` object that the
|
|
3
|
+
// generated JavaScript imports as `__rt`. Add a module here and it instantly
|
|
4
|
+
// becomes callable from Future as `<name>.<method>(...)`.
|
|
5
|
+
|
|
6
|
+
import * as ai from './ai.js';
|
|
7
|
+
import * as http from './http.js';
|
|
8
|
+
import * as mqtt from './mqtt.js';
|
|
9
|
+
import * as tts from './tts.js';
|
|
10
|
+
import * as rag from './rag.js';
|
|
11
|
+
import * as vision from './vision.js';
|
|
12
|
+
import * as home from './home.js';
|
|
13
|
+
// Optional modules — fully backward-compatible additions.
|
|
14
|
+
import * as memory from './memory.js';
|
|
15
|
+
import * as schedule from './schedule.js';
|
|
16
|
+
import * as system from './system.js';
|
|
17
|
+
import * as device from './device.js';
|
|
18
|
+
import * as math from './math.js';
|
|
19
|
+
import readline from 'node:readline';
|
|
20
|
+
|
|
21
|
+
// Canonical ordered list of capability module names.
|
|
22
|
+
const MODULE_NAMES = [
|
|
23
|
+
'ai', 'http', 'mqtt', 'tts', 'rag', 'vision', 'home',
|
|
24
|
+
'memory', 'schedule', 'system', 'device', 'math',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const runtime = { ai, http, mqtt, tts, rag, vision, home, memory, schedule, system, device, math };
|
|
28
|
+
|
|
29
|
+
// input(prompt) — reads a line from stdin (CLI programs).
|
|
30
|
+
runtime.input = async (prompt = '') => {
|
|
31
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
rl.question(String(prompt), (answer) => { rl.close(); resolve(answer); });
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// --- Structured manifest ---
|
|
38
|
+
// Each function entry carries enough metadata for docs, editor tooling, REPL, and AI agents.
|
|
39
|
+
// Shape: { description, params: [{ name, type, optional? }], returns, async }
|
|
40
|
+
|
|
41
|
+
export const manifest = {
|
|
42
|
+
ai: {
|
|
43
|
+
configure: {
|
|
44
|
+
description: 'Set the AI provider — accepts a named provider ("openai", "anthropic", "ollama", "gemini", …) or a custom OpenAI-compatible base URL',
|
|
45
|
+
params: [
|
|
46
|
+
{ name: 'providerOrUrl', type: 'string' },
|
|
47
|
+
{ name: 'apiKey', type: 'string' },
|
|
48
|
+
{ name: 'model', type: 'string', optional: true },
|
|
49
|
+
],
|
|
50
|
+
returns: 'void',
|
|
51
|
+
async: false,
|
|
52
|
+
},
|
|
53
|
+
ask: {
|
|
54
|
+
description: 'Ask an AI model a question and get a text response',
|
|
55
|
+
params: [{ name: 'prompt', type: 'string' }],
|
|
56
|
+
returns: 'string',
|
|
57
|
+
async: true,
|
|
58
|
+
},
|
|
59
|
+
chat: {
|
|
60
|
+
description: 'Send a multi-turn message list to an AI model',
|
|
61
|
+
params: [{ name: 'messages', type: 'array' }],
|
|
62
|
+
returns: 'string',
|
|
63
|
+
async: true,
|
|
64
|
+
},
|
|
65
|
+
stream: {
|
|
66
|
+
description: 'Stream a response chunk by chunk via a callback function',
|
|
67
|
+
params: [
|
|
68
|
+
{ name: 'prompt', type: 'string' },
|
|
69
|
+
{ name: 'onChunk', type: 'function' },
|
|
70
|
+
],
|
|
71
|
+
returns: 'void',
|
|
72
|
+
async: true,
|
|
73
|
+
},
|
|
74
|
+
embed: {
|
|
75
|
+
description: 'Generate a vector embedding for a piece of text',
|
|
76
|
+
params: [{ name: 'text', type: 'string' }],
|
|
77
|
+
returns: 'array',
|
|
78
|
+
async: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
http: {
|
|
83
|
+
get: {
|
|
84
|
+
description: 'Perform an HTTP GET request and return the parsed response',
|
|
85
|
+
params: [
|
|
86
|
+
{ name: 'url', type: 'string' },
|
|
87
|
+
{ name: 'headers', type: 'object', optional: true },
|
|
88
|
+
],
|
|
89
|
+
returns: 'any',
|
|
90
|
+
async: true,
|
|
91
|
+
},
|
|
92
|
+
post: {
|
|
93
|
+
description: 'Perform an HTTP POST request with a JSON body',
|
|
94
|
+
params: [
|
|
95
|
+
{ name: 'url', type: 'string' },
|
|
96
|
+
{ name: 'body', type: 'any' },
|
|
97
|
+
{ name: 'headers', type: 'object', optional: true },
|
|
98
|
+
],
|
|
99
|
+
returns: 'any',
|
|
100
|
+
async: true,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
mqtt: {
|
|
105
|
+
publish: {
|
|
106
|
+
description: 'Publish a message to an MQTT topic',
|
|
107
|
+
params: [
|
|
108
|
+
{ name: 'topic', type: 'string' },
|
|
109
|
+
{ name: 'message', type: 'string' },
|
|
110
|
+
],
|
|
111
|
+
returns: 'string',
|
|
112
|
+
async: true,
|
|
113
|
+
},
|
|
114
|
+
subscribe: {
|
|
115
|
+
description: 'Subscribe to an MQTT topic; handler is called on every message',
|
|
116
|
+
params: [
|
|
117
|
+
{ name: 'topic', type: 'string' },
|
|
118
|
+
{ name: 'handler', type: 'function' },
|
|
119
|
+
],
|
|
120
|
+
returns: 'string',
|
|
121
|
+
async: true,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
tts: {
|
|
126
|
+
speak: {
|
|
127
|
+
description: 'Speak text aloud using the system text-to-speech engine',
|
|
128
|
+
params: [{ name: 'text', type: 'string' }],
|
|
129
|
+
returns: 'void',
|
|
130
|
+
async: true,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
rag: {
|
|
135
|
+
index: {
|
|
136
|
+
description: 'Index documents into the default knowledge base (chunks, embeds, stores)',
|
|
137
|
+
params: [{ name: 'docs', type: 'array|string' }],
|
|
138
|
+
returns: 'object',
|
|
139
|
+
async: true,
|
|
140
|
+
},
|
|
141
|
+
query: {
|
|
142
|
+
description: 'Query the default knowledge base and get an LLM-generated answer',
|
|
143
|
+
params: [{ name: 'question', type: 'string' }],
|
|
144
|
+
returns: 'string',
|
|
145
|
+
async: true,
|
|
146
|
+
},
|
|
147
|
+
create: {
|
|
148
|
+
description: 'Create a named Knowledge Base with its own isolated vector store',
|
|
149
|
+
params: [
|
|
150
|
+
{ name: 'name', type: 'string' },
|
|
151
|
+
{ name: 'opts', type: 'object', optional: true },
|
|
152
|
+
],
|
|
153
|
+
returns: 'KnowledgeBase',
|
|
154
|
+
async: false,
|
|
155
|
+
},
|
|
156
|
+
indexFile: {
|
|
157
|
+
description: 'Read a local file and index its content into the default knowledge base',
|
|
158
|
+
params: [{ name: 'filePath', type: 'string' }],
|
|
159
|
+
returns: 'object',
|
|
160
|
+
async: true,
|
|
161
|
+
},
|
|
162
|
+
indexUrl: {
|
|
163
|
+
description: 'Fetch a URL and index its text content into the default knowledge base',
|
|
164
|
+
params: [{ name: 'url', type: 'string' }],
|
|
165
|
+
returns: 'object',
|
|
166
|
+
async: true,
|
|
167
|
+
},
|
|
168
|
+
stats: {
|
|
169
|
+
description: 'Return stats for the default knowledge base (chunk count, vector count)',
|
|
170
|
+
params: [],
|
|
171
|
+
returns: 'object',
|
|
172
|
+
async: false,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
vision: {
|
|
177
|
+
describe: {
|
|
178
|
+
description: 'Describe the contents of an image using a vision AI model',
|
|
179
|
+
params: [{ name: 'image', type: 'string' }],
|
|
180
|
+
returns: 'string',
|
|
181
|
+
async: true,
|
|
182
|
+
},
|
|
183
|
+
detect: {
|
|
184
|
+
description: 'Detect and list objects, people, or labels visible in an image',
|
|
185
|
+
params: [{ name: 'image', type: 'string' }],
|
|
186
|
+
returns: 'string',
|
|
187
|
+
async: true,
|
|
188
|
+
},
|
|
189
|
+
ocr: {
|
|
190
|
+
description: 'Extract all readable text from an image (OCR)',
|
|
191
|
+
params: [{ name: 'image', type: 'string' }],
|
|
192
|
+
returns: 'string',
|
|
193
|
+
async: true,
|
|
194
|
+
},
|
|
195
|
+
classify: {
|
|
196
|
+
description: 'Classify an image into a primary category',
|
|
197
|
+
params: [{ name: 'image', type: 'string' }],
|
|
198
|
+
returns: 'string',
|
|
199
|
+
async: true,
|
|
200
|
+
},
|
|
201
|
+
compare: {
|
|
202
|
+
description: 'Compare two images and describe their differences',
|
|
203
|
+
params: [
|
|
204
|
+
{ name: 'imageA', type: 'string' },
|
|
205
|
+
{ name: 'imageB', type: 'string' },
|
|
206
|
+
],
|
|
207
|
+
returns: 'string',
|
|
208
|
+
async: true,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
home: {
|
|
213
|
+
turnOn: {
|
|
214
|
+
description: 'Turn a home automation device on',
|
|
215
|
+
params: [{ name: 'device', type: 'string' }],
|
|
216
|
+
returns: 'string',
|
|
217
|
+
async: true,
|
|
218
|
+
},
|
|
219
|
+
turnOff: {
|
|
220
|
+
description: 'Turn a home automation device off',
|
|
221
|
+
params: [{ name: 'device', type: 'string' }],
|
|
222
|
+
returns: 'string',
|
|
223
|
+
async: true,
|
|
224
|
+
},
|
|
225
|
+
set: {
|
|
226
|
+
description: 'Set a home automation device to an arbitrary value',
|
|
227
|
+
params: [
|
|
228
|
+
{ name: 'device', type: 'string' },
|
|
229
|
+
{ name: 'value', type: 'any' },
|
|
230
|
+
],
|
|
231
|
+
returns: 'string',
|
|
232
|
+
async: true,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
memory: {
|
|
237
|
+
forget: {
|
|
238
|
+
description: 'Delete all keys matching a pattern, or clear everything if no pattern given',
|
|
239
|
+
params: [{ name: 'pattern', type: 'string|RegExp', optional: true }],
|
|
240
|
+
returns: 'number',
|
|
241
|
+
async: false,
|
|
242
|
+
},
|
|
243
|
+
set: {
|
|
244
|
+
description: 'Store a value in the in-process memory store',
|
|
245
|
+
params: [
|
|
246
|
+
{ name: 'key', type: 'string' },
|
|
247
|
+
{ name: 'value', type: 'any' },
|
|
248
|
+
],
|
|
249
|
+
returns: 'any',
|
|
250
|
+
async: false,
|
|
251
|
+
},
|
|
252
|
+
get: {
|
|
253
|
+
description: 'Retrieve a value from the memory store',
|
|
254
|
+
params: [{ name: 'key', type: 'string' }],
|
|
255
|
+
returns: 'any',
|
|
256
|
+
async: false,
|
|
257
|
+
},
|
|
258
|
+
delete: {
|
|
259
|
+
description: 'Delete a key from the memory store',
|
|
260
|
+
params: [{ name: 'key', type: 'string' }],
|
|
261
|
+
returns: 'boolean',
|
|
262
|
+
async: false,
|
|
263
|
+
},
|
|
264
|
+
search: {
|
|
265
|
+
description: 'Search memory entries whose key or value contains the query string',
|
|
266
|
+
params: [{ name: 'query', type: 'string' }],
|
|
267
|
+
returns: 'array',
|
|
268
|
+
async: false,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
schedule: {
|
|
273
|
+
every: {
|
|
274
|
+
description: 'Run a callback repeatedly at a fixed interval (e.g. "30m", "5s")',
|
|
275
|
+
params: [
|
|
276
|
+
{ name: 'interval', type: 'string|number' },
|
|
277
|
+
{ name: 'callback', type: 'function' },
|
|
278
|
+
],
|
|
279
|
+
returns: 'Timeout',
|
|
280
|
+
async: true,
|
|
281
|
+
},
|
|
282
|
+
once: {
|
|
283
|
+
description: 'Run a callback once after a delay (e.g. "10s", 5000)',
|
|
284
|
+
params: [
|
|
285
|
+
{ name: 'delay', type: 'string|number' },
|
|
286
|
+
{ name: 'callback', type: 'function' },
|
|
287
|
+
],
|
|
288
|
+
returns: 'any',
|
|
289
|
+
async: true,
|
|
290
|
+
},
|
|
291
|
+
cron: {
|
|
292
|
+
description: 'Run a callback on a cron schedule (requires node-cron)',
|
|
293
|
+
params: [
|
|
294
|
+
{ name: 'expression', type: 'string' },
|
|
295
|
+
{ name: 'callback', type: 'function' },
|
|
296
|
+
],
|
|
297
|
+
returns: 'any',
|
|
298
|
+
async: true,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
system: {
|
|
303
|
+
env: {
|
|
304
|
+
description: 'Read an environment variable; returns null if not set',
|
|
305
|
+
params: [{ name: 'name', type: 'string' }],
|
|
306
|
+
returns: 'string|null',
|
|
307
|
+
async: false,
|
|
308
|
+
},
|
|
309
|
+
exec: {
|
|
310
|
+
description: 'Execute a shell command and return its stdout',
|
|
311
|
+
params: [{ name: 'command', type: 'string|array' }],
|
|
312
|
+
returns: 'string',
|
|
313
|
+
async: true,
|
|
314
|
+
},
|
|
315
|
+
open: {
|
|
316
|
+
description: 'Open a file path or URL with the OS default application',
|
|
317
|
+
params: [{ name: 'target', type: 'string' }],
|
|
318
|
+
returns: 'string',
|
|
319
|
+
async: true,
|
|
320
|
+
},
|
|
321
|
+
notify: {
|
|
322
|
+
description: 'Send a desktop notification with a message',
|
|
323
|
+
params: [{ name: 'message', type: 'string' }],
|
|
324
|
+
returns: 'string',
|
|
325
|
+
async: true,
|
|
326
|
+
},
|
|
327
|
+
read: {
|
|
328
|
+
description: 'Read a file and return its content as a string',
|
|
329
|
+
params: [{ name: 'path', type: 'string' }],
|
|
330
|
+
returns: 'string',
|
|
331
|
+
async: true,
|
|
332
|
+
},
|
|
333
|
+
write: {
|
|
334
|
+
description: 'Write a string to a file (creates or overwrites)',
|
|
335
|
+
params: [
|
|
336
|
+
{ name: 'path', type: 'string' },
|
|
337
|
+
{ name: 'content', type: 'string' },
|
|
338
|
+
],
|
|
339
|
+
returns: 'string',
|
|
340
|
+
async: true,
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
device: {
|
|
345
|
+
register: {
|
|
346
|
+
description: 'Register an IoT device with a configuration object (name is required)',
|
|
347
|
+
params: [{ name: 'config', type: 'object' }],
|
|
348
|
+
returns: 'object',
|
|
349
|
+
async: false,
|
|
350
|
+
},
|
|
351
|
+
get: {
|
|
352
|
+
description: 'Look up a registered device by name',
|
|
353
|
+
params: [{ name: 'name', type: 'string' }],
|
|
354
|
+
returns: 'object|null',
|
|
355
|
+
async: false,
|
|
356
|
+
},
|
|
357
|
+
list: {
|
|
358
|
+
description: 'List all registered devices',
|
|
359
|
+
params: [],
|
|
360
|
+
returns: 'array',
|
|
361
|
+
async: false,
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
math: {
|
|
366
|
+
round: { description: 'Round to nearest integer', params: [{ name: 'x', type: 'number' }], returns: 'number', async: false },
|
|
367
|
+
floor: { description: 'Round down to nearest integer', params: [{ name: 'x', type: 'number' }], returns: 'number', async: false },
|
|
368
|
+
ceil: { description: 'Round up to nearest integer', params: [{ name: 'x', type: 'number' }], returns: 'number', async: false },
|
|
369
|
+
abs: { description: 'Absolute value', params: [{ name: 'x', type: 'number' }], returns: 'number', async: false },
|
|
370
|
+
sqrt: { description: 'Square root', params: [{ name: 'x', type: 'number' }], returns: 'number', async: false },
|
|
371
|
+
pow: { description: 'x raised to the power y', params: [{ name: 'x', type: 'number' }, { name: 'y', type: 'number' }], returns: 'number', async: false },
|
|
372
|
+
log: { description: 'Natural logarithm', params: [{ name: 'x', type: 'number' }], returns: 'number', async: false },
|
|
373
|
+
random: { description: 'Random float between 0 and 1', params: [], returns: 'number', async: false },
|
|
374
|
+
min: { description: 'Smallest of the given values', params: [{ name: '...values', type: 'number' }], returns: 'number', async: false },
|
|
375
|
+
max: { description: 'Largest of the given values', params: [{ name: '...values', type: 'number' }], returns: 'number', async: false },
|
|
376
|
+
pi: { description: 'The mathematical constant π', params: [], returns: 'number', async: false },
|
|
377
|
+
e: { description: "Euler's number", params: [], returns: 'number', async: false },
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// --- Introspection API ---
|
|
382
|
+
// These methods allow programs, REPLs, and AI agents to discover the runtime at run time.
|
|
383
|
+
|
|
384
|
+
/** List all available module names. */
|
|
385
|
+
runtime.listModules = () => [...MODULE_NAMES];
|
|
386
|
+
|
|
387
|
+
/** List all function names exported by a module. */
|
|
388
|
+
runtime.listFunctions = (mod) => {
|
|
389
|
+
const m = manifest[mod];
|
|
390
|
+
return m ? Object.keys(m) : [];
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Full description of the runtime: version, module list, and complete manifest.
|
|
395
|
+
* Suitable for AI agent discovery or documentation generation.
|
|
396
|
+
*/
|
|
397
|
+
runtime.describe = () => ({
|
|
398
|
+
version: '0.2.0',
|
|
399
|
+
modules: [...MODULE_NAMES],
|
|
400
|
+
manifest,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
export default runtime;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// runtime/lsp-metadata.js
|
|
2
|
+
// Generates VSCode / Language Server Protocol compatible metadata from the
|
|
3
|
+
// runtime manifest. Consume this in a Future LSP server or VSCode extension to
|
|
4
|
+
// provide completion items, hover documentation, and signature help.
|
|
5
|
+
|
|
6
|
+
import { manifest } from './index.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate a flat list of completion items for all capability methods.
|
|
10
|
+
* Each item follows the LSP CompletionItem shape (kind names, not numeric codes).
|
|
11
|
+
* @returns {object[]}
|
|
12
|
+
*/
|
|
13
|
+
export function generateCompletions() {
|
|
14
|
+
const items = [];
|
|
15
|
+
for (const [module, methods] of Object.entries(manifest)) {
|
|
16
|
+
for (const [name, meta] of Object.entries(methods)) {
|
|
17
|
+
const required = meta.params.filter((p) => !p.optional);
|
|
18
|
+
const optional = meta.params.filter((p) => p.optional);
|
|
19
|
+
const paramList = [
|
|
20
|
+
...required.map((p) => p.name),
|
|
21
|
+
...optional.map((p) => `${p.name}?`),
|
|
22
|
+
].join(', ');
|
|
23
|
+
const snippet = `${module}.${name}(${
|
|
24
|
+
meta.params.map((p, i) => `\${${i + 1}:${p.name}}`).join(', ')
|
|
25
|
+
})`;
|
|
26
|
+
items.push({
|
|
27
|
+
label: `${module}.${name}`,
|
|
28
|
+
kind: 'Function',
|
|
29
|
+
detail: `${module}.${name}(${paramList}) → ${meta.async ? `Promise<${meta.returns}>` : meta.returns}`,
|
|
30
|
+
documentation: meta.description,
|
|
31
|
+
insertText: snippet,
|
|
32
|
+
insertTextFormat: 'Snippet',
|
|
33
|
+
module,
|
|
34
|
+
method: name,
|
|
35
|
+
async: meta.async,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return items;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate hover documentation for a specific module.method pair.
|
|
44
|
+
* @param {string} module
|
|
45
|
+
* @param {string} method
|
|
46
|
+
* @returns {{ signature: string, description: string, params: object[] } | null}
|
|
47
|
+
*/
|
|
48
|
+
export function generateHoverInfo(module, method) {
|
|
49
|
+
const meta = manifest[module]?.[method];
|
|
50
|
+
if (!meta) return null;
|
|
51
|
+
const paramList = meta.params
|
|
52
|
+
.map((p) => `${p.name}${p.optional ? '?' : ''}: ${p.type}`)
|
|
53
|
+
.join(', ');
|
|
54
|
+
const ret = meta.async ? `Promise<${meta.returns}>` : meta.returns;
|
|
55
|
+
return {
|
|
56
|
+
signature: `${module}.${method}(${paramList}): ${ret}`,
|
|
57
|
+
description: meta.description,
|
|
58
|
+
params: meta.params,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Signature help for a specific module.method — used when the user types `(`.
|
|
64
|
+
* @returns {{ label: string, parameters: object[] } | null}
|
|
65
|
+
*/
|
|
66
|
+
export function generateSignatureHelp(module, method) {
|
|
67
|
+
const meta = manifest[module]?.[method];
|
|
68
|
+
if (!meta) return null;
|
|
69
|
+
const paramStrings = meta.params.map((p) => `${p.name}${p.optional ? '?' : ''}: ${p.type}`);
|
|
70
|
+
const label = `${module}.${method}(${paramStrings.join(', ')})`;
|
|
71
|
+
return {
|
|
72
|
+
label,
|
|
73
|
+
documentation: meta.description,
|
|
74
|
+
parameters: meta.params.map((p, i) => ({
|
|
75
|
+
label: paramStrings[i],
|
|
76
|
+
documentation: p.optional ? `Optional. Type: ${p.type}` : `Type: ${p.type}`,
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* All reserved keywords in the Future language.
|
|
83
|
+
* Used by syntax highlighters, LSP semantic tokens, and editor grammar files.
|
|
84
|
+
* @returns {string[]}
|
|
85
|
+
*/
|
|
86
|
+
export function generateLanguageKeywords() {
|
|
87
|
+
return ['print', 'if', 'else', 'end', 'function', 'return', 'true', 'false', 'and', 'or', 'not', 'on', 'every'];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Full LSP-ready metadata bundle.
|
|
92
|
+
* Suitable for initialising a Language Server or generating a VSCode extension's language contribution.
|
|
93
|
+
* @returns {object}
|
|
94
|
+
*/
|
|
95
|
+
export function generateLanguageMetadata() {
|
|
96
|
+
return {
|
|
97
|
+
languageId: 'future',
|
|
98
|
+
fileExtensions: ['.future'],
|
|
99
|
+
keywords: generateLanguageKeywords(),
|
|
100
|
+
completions: generateCompletions(),
|
|
101
|
+
modules: Object.keys(manifest),
|
|
102
|
+
manifest,
|
|
103
|
+
};
|
|
104
|
+
}
|
package/runtime/math.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// runtime/math.js — Math module for the Future language.
|
|
2
|
+
// All functions mirror JavaScript's Math object so Future programs can do
|
|
3
|
+
// numeric work without importing anything explicitly.
|
|
4
|
+
|
|
5
|
+
export const round = (x) => Math.round(Number(x));
|
|
6
|
+
export const floor = (x) => Math.floor(Number(x));
|
|
7
|
+
export const ceil = (x) => Math.ceil(Number(x));
|
|
8
|
+
export const abs = (x) => Math.abs(Number(x));
|
|
9
|
+
export const sqrt = (x) => Math.sqrt(Number(x));
|
|
10
|
+
export const pow = (x, y) => Math.pow(Number(x), Number(y));
|
|
11
|
+
export const log = (x) => Math.log(Number(x));
|
|
12
|
+
export const random = () => Math.random();
|
|
13
|
+
export const min = (...args) => Math.min(...args.map(Number));
|
|
14
|
+
export const max = (...args) => Math.max(...args.map(Number));
|
|
15
|
+
export const pi = Math.PI;
|
|
16
|
+
export const e = Math.E;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// runtime/memory.js — In-process key-value store with fuzzy search.
|
|
2
|
+
// Suitable for session state, conversation context, and lightweight RAG-style retrieval.
|
|
3
|
+
// Swap the `store` Map for a Redis/SQLite/vector-DB client to go persistent.
|
|
4
|
+
|
|
5
|
+
const store = new Map();
|
|
6
|
+
|
|
7
|
+
/** Store a value under a key. @returns the stored value. */
|
|
8
|
+
export function set(key, value) {
|
|
9
|
+
store.set(String(key), value);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Retrieve a value by key. @returns the value or null. */
|
|
14
|
+
export function get(key) {
|
|
15
|
+
return store.has(String(key)) ? store.get(String(key)) : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Remove a key. @returns true if the key existed. */
|
|
19
|
+
function memDelete(key) {
|
|
20
|
+
return store.delete(String(key));
|
|
21
|
+
}
|
|
22
|
+
export { memDelete as delete };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Search all stored entries whose key or stringified value contains the query.
|
|
26
|
+
* @returns {Array<{ key: string, value: any }>}
|
|
27
|
+
*/
|
|
28
|
+
export function search(query) {
|
|
29
|
+
const q = String(query).toLowerCase();
|
|
30
|
+
const results = [];
|
|
31
|
+
for (const [key, value] of store) {
|
|
32
|
+
const v = typeof value === 'string' ? value : JSON.stringify(value);
|
|
33
|
+
if (key.toLowerCase().includes(q) || v.toLowerCase().includes(q)) {
|
|
34
|
+
results.push({ key, value });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Forget (delete) all keys that match a pattern.
|
|
42
|
+
* Pattern can be an exact key, a substring, or a RegExp.
|
|
43
|
+
* With no argument, clears everything.
|
|
44
|
+
* @param {string|RegExp} [pattern]
|
|
45
|
+
* @returns {number} number of keys removed.
|
|
46
|
+
*/
|
|
47
|
+
export function forget(pattern) {
|
|
48
|
+
if (pattern === undefined) {
|
|
49
|
+
const count = store.size;
|
|
50
|
+
store.clear();
|
|
51
|
+
return count;
|
|
52
|
+
}
|
|
53
|
+
const test = pattern instanceof RegExp
|
|
54
|
+
? (k) => pattern.test(k)
|
|
55
|
+
: (k) => k.includes(String(pattern));
|
|
56
|
+
let removed = 0;
|
|
57
|
+
for (const key of [...store.keys()]) {
|
|
58
|
+
if (test(key)) { store.delete(key); removed++; }
|
|
59
|
+
}
|
|
60
|
+
return removed;
|
|
61
|
+
}
|
package/runtime/mqtt.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// runtime/mqtt.js — MQTT publish/subscribe.
|
|
2
|
+
// Uses the `mqtt` npm package + MQTT_URL when available. With no broker it falls
|
|
3
|
+
// back to an in-process loopback so publish/subscribe still work for demos/tests.
|
|
4
|
+
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
let client = null; // real client, or false for offline mode
|
|
8
|
+
const localSubs = new Map(); // topic -> handler[] (offline fallback)
|
|
9
|
+
|
|
10
|
+
async function getClient() {
|
|
11
|
+
if (client !== null) return client;
|
|
12
|
+
const url = process.env.MQTT_URL;
|
|
13
|
+
if (!url) { client = false; return client; }
|
|
14
|
+
try {
|
|
15
|
+
const mod = await import('mqtt');
|
|
16
|
+
const c = mod.default.connect(url);
|
|
17
|
+
await new Promise((res, rej) => { c.once('connect', res); c.once('error', rej); });
|
|
18
|
+
client = c;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.warn(`[mqtt] broker/package unavailable (${e.message}); using local loopback.`);
|
|
21
|
+
client = false;
|
|
22
|
+
}
|
|
23
|
+
return client;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Publish a message to a topic. @returns {Promise<string>} the payload sent. */
|
|
27
|
+
export async function publish(topic, message) {
|
|
28
|
+
const c = await getClient();
|
|
29
|
+
const payload = typeof message === 'string' ? message : JSON.stringify(message);
|
|
30
|
+
if (c) {
|
|
31
|
+
await new Promise((res) => c.publish(topic, payload, res));
|
|
32
|
+
} else {
|
|
33
|
+
for (const h of localSubs.get(topic) || []) h(payload, topic);
|
|
34
|
+
}
|
|
35
|
+
return payload;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Subscribe to a topic. `handler(message, topic)` runs on each message. */
|
|
39
|
+
export async function subscribe(topic, handler) {
|
|
40
|
+
const c = await getClient();
|
|
41
|
+
if (c) {
|
|
42
|
+
c.subscribe(topic);
|
|
43
|
+
c.on('message', (t, buf) => { if (t === topic) handler(buf.toString(), t); });
|
|
44
|
+
} else {
|
|
45
|
+
if (!localSubs.has(topic)) localSubs.set(topic, []);
|
|
46
|
+
localSubs.get(topic).push(handler);
|
|
47
|
+
}
|
|
48
|
+
return topic;
|
|
49
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// runtime/providers/anthropic.js — Anthropic native Messages API.
|
|
2
|
+
// Supports: ask, chat, stream. embed() falls back to keyword vectors
|
|
3
|
+
// because Anthropic doesn't have a public embeddings endpoint yet.
|
|
4
|
+
|
|
5
|
+
import { parseSSE, keywordVector } from './util.js';
|
|
6
|
+
|
|
7
|
+
const BASE = 'https://api.anthropic.com/v1';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create an Anthropic provider instance.
|
|
11
|
+
* @param {{ apiKey: string, model?: string }} config
|
|
12
|
+
*/
|
|
13
|
+
export function create(config) {
|
|
14
|
+
const key = config.apiKey;
|
|
15
|
+
const model = config.model ?? 'claude-sonnet-4-6';
|
|
16
|
+
|
|
17
|
+
const headers = {
|
|
18
|
+
'content-type': 'application/json',
|
|
19
|
+
'x-api-key': key,
|
|
20
|
+
'anthropic-version': '2023-06-01',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
async function chat(messages) {
|
|
24
|
+
const res = await fetch(`${BASE}/messages`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers,
|
|
27
|
+
body: JSON.stringify({ model, max_tokens: 1024, messages }),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) throw new Error(`[anthropic] HTTP ${res.status}: ${await res.text()}`);
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
return (data.content ?? []).map((b) => b.text ?? '').join('').trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function ask(prompt) {
|
|
35
|
+
return chat([{ role: 'user', content: String(prompt) }]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function stream(messages, onChunk) {
|
|
39
|
+
const res = await fetch(`${BASE}/messages`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers,
|
|
42
|
+
body: JSON.stringify({ model, max_tokens: 1024, messages, stream: true }),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) throw new Error(`[anthropic] stream HTTP ${res.status}`);
|
|
45
|
+
for await (const { event, data } of parseSSE(res.body)) {
|
|
46
|
+
if (event === 'content_block_delta' || data?.type === 'content_block_delta') {
|
|
47
|
+
onChunk(data.delta?.text ?? '');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function embed(text) {
|
|
53
|
+
// Anthropic has no public embeddings endpoint — use keyword vector fallback.
|
|
54
|
+
// For production semantic search, configure an OpenAI-compatible provider with an embed model.
|
|
55
|
+
return keywordVector(String(text));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { name: 'anthropic', ask, chat, stream, embed };
|
|
59
|
+
}
|