@rainfall-devkit/sdk 0.1.8 → 0.2.1

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 (58) hide show
  1. package/README.md +51 -0
  2. package/dist/chunk-7MRE4ZVI.mjs +662 -0
  3. package/dist/chunk-AQFC7YAX.mjs +27 -0
  4. package/dist/chunk-EI7SJH5K.mjs +85 -0
  5. package/dist/chunk-NTTAVKRT.mjs +89 -0
  6. package/dist/chunk-RVKW5KBT.mjs +269 -0
  7. package/dist/chunk-V5QWJVLC.mjs +662 -0
  8. package/dist/chunk-VDPKDC3R.mjs +869 -0
  9. package/dist/chunk-WOITG5TG.mjs +84 -0
  10. package/dist/chunk-XAHJQRBJ.mjs +269 -0
  11. package/dist/chunk-XEQ6U3JQ.mjs +269 -0
  12. package/dist/cli/index.js +3797 -632
  13. package/dist/cli/index.mjs +453 -36
  14. package/dist/config-7UT7GYSN.mjs +16 -0
  15. package/dist/config-DDTQQBN7.mjs +14 -0
  16. package/dist/config-MD45VGWD.mjs +14 -0
  17. package/dist/config-ZKNHII2A.mjs +8 -0
  18. package/dist/daemon/index.d.mts +168 -0
  19. package/dist/daemon/index.d.ts +168 -0
  20. package/dist/daemon/index.js +3182 -0
  21. package/dist/daemon/index.mjs +1548 -0
  22. package/dist/errors-BMPseAnM.d.mts +47 -0
  23. package/dist/errors-BMPseAnM.d.ts +47 -0
  24. package/dist/errors-CZdRoYyw.d.ts +332 -0
  25. package/dist/errors-Chjq1Mev.d.mts +332 -0
  26. package/dist/index.d.mts +249 -2
  27. package/dist/index.d.ts +249 -2
  28. package/dist/index.js +1247 -3
  29. package/dist/index.mjs +227 -2
  30. package/dist/listeners-B5Vy9Ao5.d.ts +372 -0
  31. package/dist/listeners-BbYIaNCs.d.mts +372 -0
  32. package/dist/listeners-CP2A9J_2.d.ts +372 -0
  33. package/dist/listeners-CTRSofnm.d.mts +372 -0
  34. package/dist/listeners-CYI-YwIF.d.mts +372 -0
  35. package/dist/listeners-DRwITBW_.d.mts +372 -0
  36. package/dist/listeners-DrMrvFT5.d.ts +372 -0
  37. package/dist/listeners-MNAnpZj-.d.mts +372 -0
  38. package/dist/listeners-PZI7iT85.d.ts +372 -0
  39. package/dist/listeners-QJeEtLbV.d.ts +372 -0
  40. package/dist/listeners-hp0Ib2Ox.d.ts +372 -0
  41. package/dist/listeners-jLwetUnx.d.mts +372 -0
  42. package/dist/mcp.d.mts +7 -2
  43. package/dist/mcp.d.ts +7 -2
  44. package/dist/mcp.js +92 -1
  45. package/dist/mcp.mjs +1 -1
  46. package/dist/sdk-4OvXPr8E.d.mts +1054 -0
  47. package/dist/sdk-4OvXPr8E.d.ts +1054 -0
  48. package/dist/sdk-CJ9g5lFo.d.mts +772 -0
  49. package/dist/sdk-CJ9g5lFo.d.ts +772 -0
  50. package/dist/sdk-CN1ezZrI.d.mts +1054 -0
  51. package/dist/sdk-CN1ezZrI.d.ts +1054 -0
  52. package/dist/sdk-DD1OeGRJ.d.mts +871 -0
  53. package/dist/sdk-DD1OeGRJ.d.ts +871 -0
  54. package/dist/sdk-Xw0BjsLd.d.mts +1054 -0
  55. package/dist/sdk-Xw0BjsLd.d.ts +1054 -0
  56. package/dist/types-GnRAfH-h.d.mts +489 -0
  57. package/dist/types-GnRAfH-h.d.ts +489 -0
  58. package/package.json +17 -5
@@ -0,0 +1,3182 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // node_modules/tsup/assets/cjs_shims.js
34
+ var init_cjs_shims = __esm({
35
+ "node_modules/tsup/assets/cjs_shims.js"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
40
+ // src/cli/config.ts
41
+ var config_exports = {};
42
+ __export(config_exports, {
43
+ getConfigDir: () => getConfigDir,
44
+ getLLMConfig: () => getLLMConfig,
45
+ getProviderBaseUrl: () => getProviderBaseUrl,
46
+ isLocalProvider: () => isLocalProvider,
47
+ loadConfig: () => loadConfig,
48
+ saveConfig: () => saveConfig
49
+ });
50
+ function getConfigDir() {
51
+ return CONFIG_DIR;
52
+ }
53
+ function loadConfig() {
54
+ let config = {};
55
+ if ((0, import_fs.existsSync)(CONFIG_FILE)) {
56
+ try {
57
+ config = JSON.parse((0, import_fs.readFileSync)(CONFIG_FILE, "utf8"));
58
+ } catch {
59
+ config = {};
60
+ }
61
+ }
62
+ if (process.env.RAINFALL_API_KEY) {
63
+ config.apiKey = process.env.RAINFALL_API_KEY;
64
+ }
65
+ if (process.env.RAINFALL_BASE_URL) {
66
+ config.baseUrl = process.env.RAINFALL_BASE_URL;
67
+ }
68
+ if (!config.llm) {
69
+ config.llm = { provider: "rainfall" };
70
+ }
71
+ if (process.env.OPENAI_API_KEY) {
72
+ config.llm.provider = config.llm.provider || "openai";
73
+ config.llm.apiKey = process.env.OPENAI_API_KEY;
74
+ }
75
+ if (process.env.ANTHROPIC_API_KEY) {
76
+ config.llm.provider = "anthropic";
77
+ config.llm.apiKey = process.env.ANTHROPIC_API_KEY;
78
+ }
79
+ if (process.env.OLLAMA_HOST || process.env.OLLAMA_URL) {
80
+ config.llm.provider = "ollama";
81
+ config.llm.baseUrl = process.env.OLLAMA_HOST || process.env.OLLAMA_URL;
82
+ }
83
+ if (process.env.LLM_MODEL) {
84
+ config.llm.model = process.env.LLM_MODEL;
85
+ }
86
+ return config;
87
+ }
88
+ function saveConfig(config) {
89
+ if (!(0, import_fs.existsSync)(CONFIG_DIR)) {
90
+ (0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
91
+ }
92
+ (0, import_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2));
93
+ }
94
+ function getLLMConfig(config) {
95
+ const defaults = {
96
+ provider: "rainfall",
97
+ apiKey: config.apiKey || "",
98
+ baseUrl: config.baseUrl || "https://api.rainfall.com",
99
+ model: "llama-3.3-70b-versatile",
100
+ options: {}
101
+ };
102
+ return { ...defaults, ...config.llm };
103
+ }
104
+ function isLocalProvider(config) {
105
+ return config.llm?.provider === "ollama" || config.llm?.provider === "local";
106
+ }
107
+ function getProviderBaseUrl(config) {
108
+ const provider = config.llm?.provider || "rainfall";
109
+ switch (provider) {
110
+ case "openai":
111
+ return config.llm?.baseUrl || "https://api.openai.com/v1";
112
+ case "anthropic":
113
+ return config.llm?.baseUrl || "https://api.anthropic.com/v1";
114
+ case "ollama":
115
+ return config.llm?.baseUrl || "http://localhost:11434/v1";
116
+ case "local":
117
+ case "custom":
118
+ return config.llm?.baseUrl || "http://localhost:1234/v1";
119
+ case "rainfall":
120
+ default:
121
+ return config.baseUrl || "https://api.rainfall.com";
122
+ }
123
+ }
124
+ var import_fs, import_path, import_os, CONFIG_DIR, CONFIG_FILE;
125
+ var init_config = __esm({
126
+ "src/cli/config.ts"() {
127
+ "use strict";
128
+ init_cjs_shims();
129
+ import_fs = require("fs");
130
+ import_path = require("path");
131
+ import_os = require("os");
132
+ CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".rainfall");
133
+ CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
134
+ }
135
+ });
136
+
137
+ // src/daemon/index.ts
138
+ var daemon_exports = {};
139
+ __export(daemon_exports, {
140
+ MCPProxyHub: () => MCPProxyHub,
141
+ RainfallDaemon: () => RainfallDaemon,
142
+ getDaemonInstance: () => getDaemonInstance,
143
+ getDaemonStatus: () => getDaemonStatus,
144
+ startDaemon: () => startDaemon,
145
+ stopDaemon: () => stopDaemon
146
+ });
147
+ module.exports = __toCommonJS(daemon_exports);
148
+ init_cjs_shims();
149
+ var import_ws2 = require("ws");
150
+ var import_express = __toESM(require("express"));
151
+
152
+ // src/sdk.ts
153
+ init_cjs_shims();
154
+
155
+ // src/client.ts
156
+ init_cjs_shims();
157
+
158
+ // src/errors.ts
159
+ init_cjs_shims();
160
+ var RainfallError = class _RainfallError extends Error {
161
+ constructor(message, code, statusCode, details) {
162
+ super(message);
163
+ this.code = code;
164
+ this.statusCode = statusCode;
165
+ this.details = details;
166
+ this.name = "RainfallError";
167
+ Object.setPrototypeOf(this, _RainfallError.prototype);
168
+ }
169
+ toJSON() {
170
+ return {
171
+ name: this.name,
172
+ code: this.code,
173
+ message: this.message,
174
+ statusCode: this.statusCode,
175
+ details: this.details
176
+ };
177
+ }
178
+ };
179
+ var AuthenticationError = class _AuthenticationError extends RainfallError {
180
+ constructor(message = "Invalid API key", details) {
181
+ super(message, "AUTHENTICATION_ERROR", 401, details);
182
+ this.name = "AuthenticationError";
183
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
184
+ }
185
+ };
186
+ var RateLimitError = class _RateLimitError extends RainfallError {
187
+ retryAfter;
188
+ limit;
189
+ remaining;
190
+ resetAt;
191
+ constructor(message = "Rate limit exceeded", retryAfter = 60, limit = 0, remaining = 0, resetAt) {
192
+ super(message, "RATE_LIMIT_ERROR", 429, { retryAfter, limit, remaining });
193
+ this.name = "RateLimitError";
194
+ this.retryAfter = retryAfter;
195
+ this.limit = limit;
196
+ this.remaining = remaining;
197
+ this.resetAt = resetAt || new Date(Date.now() + retryAfter * 1e3);
198
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
199
+ }
200
+ };
201
+ var ValidationError = class _ValidationError extends RainfallError {
202
+ constructor(message, details) {
203
+ super(message, "VALIDATION_ERROR", 400, details);
204
+ this.name = "ValidationError";
205
+ Object.setPrototypeOf(this, _ValidationError.prototype);
206
+ }
207
+ };
208
+ var NotFoundError = class _NotFoundError extends RainfallError {
209
+ constructor(resource, identifier) {
210
+ super(
211
+ `${resource}${identifier ? ` '${identifier}'` : ""} not found`,
212
+ "NOT_FOUND_ERROR",
213
+ 404,
214
+ { resource, identifier }
215
+ );
216
+ this.name = "NotFoundError";
217
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
218
+ }
219
+ };
220
+ var ServerError = class _ServerError extends RainfallError {
221
+ constructor(message = "Internal server error", statusCode = 500) {
222
+ super(message, "SERVER_ERROR", statusCode);
223
+ this.name = "ServerError";
224
+ Object.setPrototypeOf(this, _ServerError.prototype);
225
+ }
226
+ };
227
+ var TimeoutError = class _TimeoutError extends RainfallError {
228
+ constructor(timeoutMs) {
229
+ super(`Request timed out after ${timeoutMs}ms`, "TIMEOUT_ERROR", 408);
230
+ this.name = "TimeoutError";
231
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
232
+ }
233
+ };
234
+ var NetworkError = class _NetworkError extends RainfallError {
235
+ constructor(message = "Network error", details) {
236
+ super(message, "NETWORK_ERROR", void 0, details);
237
+ this.name = "NetworkError";
238
+ Object.setPrototypeOf(this, _NetworkError.prototype);
239
+ }
240
+ };
241
+ function parseErrorResponse(response, data) {
242
+ const statusCode = response.status;
243
+ if (statusCode === 429) {
244
+ const retryAfter = parseInt(response.headers.get("retry-after") || "60", 10);
245
+ const limit = parseInt(response.headers.get("x-ratelimit-limit") || "0", 10);
246
+ const remaining = parseInt(response.headers.get("x-ratelimit-remaining") || "0", 10);
247
+ const resetHeader = response.headers.get("x-ratelimit-reset");
248
+ const resetAt = resetHeader ? new Date(parseInt(resetHeader, 10) * 1e3) : void 0;
249
+ return new RateLimitError(
250
+ typeof data === "object" && data && "message" in data ? String(data.message) : "Rate limit exceeded",
251
+ retryAfter,
252
+ limit,
253
+ remaining,
254
+ resetAt
255
+ );
256
+ }
257
+ switch (statusCode) {
258
+ case 401:
259
+ return new AuthenticationError(
260
+ typeof data === "object" && data && "message" in data ? String(data.message) : "Invalid API key"
261
+ );
262
+ case 404:
263
+ return new NotFoundError(
264
+ typeof data === "object" && data && "resource" in data ? String(data.resource) : "Resource",
265
+ typeof data === "object" && data && "identifier" in data ? String(data.identifier) : void 0
266
+ );
267
+ case 400:
268
+ return new ValidationError(
269
+ typeof data === "object" && data && "message" in data ? String(data.message) : "Invalid request",
270
+ typeof data === "object" && data && "details" in data ? data.details : void 0
271
+ );
272
+ case 500:
273
+ case 502:
274
+ case 503:
275
+ case 504:
276
+ return new ServerError(
277
+ typeof data === "object" && data && "message" in data ? String(data.message) : "Server error",
278
+ statusCode
279
+ );
280
+ default:
281
+ return new RainfallError(
282
+ typeof data === "object" && data && "message" in data ? String(data.message) : `HTTP ${statusCode}`,
283
+ "UNKNOWN_ERROR",
284
+ statusCode,
285
+ typeof data === "object" ? data : void 0
286
+ );
287
+ }
288
+ }
289
+
290
+ // src/client.ts
291
+ var DEFAULT_BASE_URL = "https://olympic-api.pragma-digital.org/v1";
292
+ var DEFAULT_TIMEOUT = 3e4;
293
+ var DEFAULT_RETRIES = 3;
294
+ var DEFAULT_RETRY_DELAY = 1e3;
295
+ var RainfallClient = class {
296
+ apiKey;
297
+ baseUrl;
298
+ defaultTimeout;
299
+ defaultRetries;
300
+ defaultRetryDelay;
301
+ lastRateLimitInfo;
302
+ subscriberId;
303
+ constructor(config) {
304
+ this.apiKey = config.apiKey;
305
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
306
+ this.defaultTimeout = config.timeout || DEFAULT_TIMEOUT;
307
+ this.defaultRetries = config.retries ?? DEFAULT_RETRIES;
308
+ this.defaultRetryDelay = config.retryDelay || DEFAULT_RETRY_DELAY;
309
+ }
310
+ /**
311
+ * Get the last rate limit info from the API
312
+ */
313
+ getRateLimitInfo() {
314
+ return this.lastRateLimitInfo;
315
+ }
316
+ /**
317
+ * Make an authenticated request to the Rainfall API
318
+ */
319
+ async request(path, options = {}, requestOptions) {
320
+ const timeout = requestOptions?.timeout ?? this.defaultTimeout;
321
+ const maxRetries = requestOptions?.retries ?? this.defaultRetries;
322
+ const retryDelay = requestOptions?.retryDelay ?? this.defaultRetryDelay;
323
+ const url = `${this.baseUrl}${path}`;
324
+ const method = options.method || "GET";
325
+ let lastError;
326
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
327
+ try {
328
+ const controller = new AbortController();
329
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
330
+ const response = await fetch(url, {
331
+ method,
332
+ headers: {
333
+ "x-api-key": this.apiKey,
334
+ "Content-Type": "application/json",
335
+ "Accept": "application/json",
336
+ "X-Rainfall-SDK-Version": "0.1.0",
337
+ ...options.headers
338
+ },
339
+ body: options.body ? JSON.stringify(options.body) : void 0,
340
+ signal: controller.signal
341
+ });
342
+ clearTimeout(timeoutId);
343
+ const limit = response.headers.get("x-ratelimit-limit");
344
+ const remaining = response.headers.get("x-ratelimit-remaining");
345
+ const reset = response.headers.get("x-ratelimit-reset");
346
+ if (limit && remaining && reset) {
347
+ this.lastRateLimitInfo = {
348
+ limit: parseInt(limit, 10),
349
+ remaining: parseInt(remaining, 10),
350
+ resetAt: new Date(parseInt(reset, 10) * 1e3)
351
+ };
352
+ }
353
+ let data;
354
+ const contentType = response.headers.get("content-type");
355
+ if (contentType?.includes("application/json")) {
356
+ data = await response.json();
357
+ } else {
358
+ data = await response.text();
359
+ }
360
+ if (!response.ok) {
361
+ throw parseErrorResponse(response, data);
362
+ }
363
+ return data;
364
+ } catch (error) {
365
+ if (error instanceof RainfallError) {
366
+ if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
367
+ throw error;
368
+ }
369
+ if (error.statusCode === 401) {
370
+ throw error;
371
+ }
372
+ }
373
+ if (error instanceof Error && error.name === "AbortError") {
374
+ lastError = new TimeoutError(timeout);
375
+ } else if (error instanceof TypeError) {
376
+ lastError = new NetworkError(error.message);
377
+ } else {
378
+ lastError = error instanceof Error ? error : new Error(String(error));
379
+ }
380
+ if (attempt >= maxRetries) {
381
+ break;
382
+ }
383
+ const delay = retryDelay * Math.pow(2, attempt) + Math.random() * 1e3;
384
+ await this.sleep(delay);
385
+ }
386
+ }
387
+ throw lastError || new RainfallError("Request failed", "REQUEST_FAILED");
388
+ }
389
+ /**
390
+ * Execute a tool/node by ID
391
+ */
392
+ async executeTool(toolId, params, options) {
393
+ const subscriberId = await this.ensureSubscriberId();
394
+ const response = await this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}`, {
395
+ method: "POST",
396
+ body: params || {}
397
+ }, options);
398
+ return response.result;
399
+ }
400
+ /**
401
+ * List all available tools
402
+ */
403
+ async listTools() {
404
+ const subscriberId = await this.ensureSubscriberId();
405
+ const result = await this.request(`/olympic/subscribers/${subscriberId}/nodes/_utils/node-descriptions`);
406
+ if (result.success && result.nodes) {
407
+ return Object.values(result.nodes);
408
+ }
409
+ const legacyResult = await this.request(`/olympic/subscribers/${subscriberId}/nodes/_utils/node-list`);
410
+ if (legacyResult.keys && Array.isArray(legacyResult.keys)) {
411
+ return legacyResult.keys.map((key) => ({
412
+ id: key,
413
+ name: key,
414
+ description: "",
415
+ category: "general"
416
+ }));
417
+ }
418
+ return legacyResult.nodes || [];
419
+ }
420
+ /**
421
+ * Get tool schema/parameters
422
+ */
423
+ async getToolSchema(toolId) {
424
+ const subscriberId = await this.ensureSubscriberId();
425
+ const response = await this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}/params`);
426
+ return response.params;
427
+ }
428
+ /**
429
+ * Get subscriber info
430
+ */
431
+ async getMe() {
432
+ const result = await this.request("/olympic/subscribers/me");
433
+ if (result.subscriber?.id) {
434
+ this.subscriberId = result.subscriber.id;
435
+ }
436
+ const subscriber = result.subscriber;
437
+ return {
438
+ id: subscriber.id,
439
+ name: subscriber.name,
440
+ email: subscriber.google_id,
441
+ billingStatus: subscriber.billing_status,
442
+ plan: subscriber.billing_status,
443
+ usage: {
444
+ callsThisMonth: subscriber.metadata?.usage?.callsThisMonth ?? 0,
445
+ callsLimit: subscriber.metadata?.usage?.callsLimit ?? 5e3
446
+ }
447
+ };
448
+ }
449
+ /**
450
+ * Ensure we have a subscriber ID, fetching it if necessary
451
+ */
452
+ async ensureSubscriberId() {
453
+ if (this.subscriberId) {
454
+ return this.subscriberId;
455
+ }
456
+ const me = await this.getMe();
457
+ if (!me.id) {
458
+ throw new RainfallError("Failed to get subscriber ID", "NO_SUBSCRIBER_ID");
459
+ }
460
+ return me.id;
461
+ }
462
+ sleep(ms) {
463
+ return new Promise((resolve) => setTimeout(resolve, ms));
464
+ }
465
+ /**
466
+ * OpenAI-compatible chat completions with tool support
467
+ */
468
+ async chatCompletions(params) {
469
+ const { subscriber_id, ...body } = params;
470
+ if (body.stream) {
471
+ const url = `${this.baseUrl}/olympic/subscribers/${subscriber_id}/v1/chat/completions`;
472
+ const response = await fetch(url, {
473
+ method: "POST",
474
+ headers: {
475
+ "x-api-key": this.apiKey,
476
+ "Content-Type": "application/json",
477
+ "Accept": "text/event-stream"
478
+ },
479
+ body: JSON.stringify(body)
480
+ });
481
+ if (!response.ok) {
482
+ const error = await response.text();
483
+ throw new RainfallError(`Chat completions failed: ${error}`, "CHAT_ERROR");
484
+ }
485
+ if (!response.body) {
486
+ throw new RainfallError("No response body", "CHAT_ERROR");
487
+ }
488
+ return response.body;
489
+ }
490
+ return this.request(
491
+ `/olympic/subscribers/${subscriber_id}/v1/chat/completions`,
492
+ {
493
+ method: "POST",
494
+ body
495
+ }
496
+ );
497
+ }
498
+ /**
499
+ * List available models (OpenAI-compatible format)
500
+ */
501
+ async listModels(subscriberId) {
502
+ const sid = subscriberId || this.subscriberId || await this.ensureSubscriberId();
503
+ const result = await this.request(
504
+ `/olympic/subscribers/${sid}/v1/models`
505
+ );
506
+ return result.data || [];
507
+ }
508
+ };
509
+
510
+ // src/namespaces/integrations.ts
511
+ init_cjs_shims();
512
+ function createIntegrations(client) {
513
+ return new IntegrationsNamespace(client);
514
+ }
515
+ var IntegrationsNamespace = class {
516
+ constructor(client) {
517
+ this.client = client;
518
+ }
519
+ get github() {
520
+ return {
521
+ issues: {
522
+ create: (params) => this.client.executeTool("github-create-issue", params),
523
+ list: (params) => this.client.executeTool("github-list-issues", params),
524
+ get: (params) => this.client.executeTool("github-get-issue", params),
525
+ update: (params) => this.client.executeTool("github-update-issue", params),
526
+ addComment: (params) => this.client.executeTool("github-add-issue-comment", params)
527
+ },
528
+ repos: {
529
+ get: (params) => this.client.executeTool("github-get-repository", params),
530
+ listBranches: (params) => this.client.executeTool("github-list-branches", params)
531
+ },
532
+ pullRequests: {
533
+ list: (params) => this.client.executeTool("github-list-pull-requests", params),
534
+ get: (params) => this.client.executeTool("github-get-pull-request", params)
535
+ }
536
+ };
537
+ }
538
+ get notion() {
539
+ return {
540
+ pages: {
541
+ create: (params) => this.client.executeTool("notion-pages-create", params),
542
+ retrieve: (params) => this.client.executeTool("notion-pages-retrieve", params),
543
+ update: (params) => this.client.executeTool("notion-pages-update", params)
544
+ },
545
+ databases: {
546
+ query: (params) => this.client.executeTool("notion-databases-query", params),
547
+ retrieve: (params) => this.client.executeTool("notion-databases-retrieve", params)
548
+ },
549
+ blocks: {
550
+ appendChildren: (params) => this.client.executeTool("notion-blocks-append-children", params),
551
+ retrieveChildren: (params) => this.client.executeTool("notion-blocks-retrieve-children", params)
552
+ }
553
+ };
554
+ }
555
+ get linear() {
556
+ return {
557
+ issues: {
558
+ create: (params) => this.client.executeTool("linear-core-issueCreate", params),
559
+ list: (params) => this.client.executeTool("linear-core-issues", params),
560
+ get: (params) => this.client.executeTool("linear-core-issue", params),
561
+ update: (params) => this.client.executeTool("linear-core-issueUpdate", params),
562
+ archive: (params) => this.client.executeTool("linear-core-issueArchive", params)
563
+ },
564
+ teams: {
565
+ list: () => this.client.executeTool("linear-core-teams", {})
566
+ }
567
+ };
568
+ }
569
+ get slack() {
570
+ return {
571
+ messages: {
572
+ send: (params) => this.client.executeTool("slack-core-postMessage", params),
573
+ list: (params) => this.client.executeTool("slack-core-listMessages", params)
574
+ },
575
+ channels: {
576
+ list: () => this.client.executeTool("slack-core-listChannels", {})
577
+ },
578
+ users: {
579
+ list: () => this.client.executeTool("slack-core-listUsers", {})
580
+ },
581
+ reactions: {
582
+ add: (params) => this.client.executeTool("slack-core-addReaction", params)
583
+ }
584
+ };
585
+ }
586
+ get figma() {
587
+ return {
588
+ files: {
589
+ get: (params) => this.client.executeTool("figma-files-getFile", { fileKey: params.fileKey }),
590
+ getNodes: (params) => this.client.executeTool("figma-files-getFileNodes", { fileKey: params.fileKey, nodeIds: params.nodeIds }),
591
+ getImages: (params) => this.client.executeTool("figma-files-getFileImage", { fileKey: params.fileKey, nodeIds: params.nodeIds, format: params.format }),
592
+ getComments: (params) => this.client.executeTool("figma-comments-getFileComments", { fileKey: params.fileKey }),
593
+ postComment: (params) => this.client.executeTool("figma-comments-postComment", { fileKey: params.fileKey, message: params.message, nodeId: params.nodeId })
594
+ },
595
+ projects: {
596
+ list: (params) => this.client.executeTool("figma-projects-getTeamProjects", { teamId: params.teamId }),
597
+ getFiles: (params) => this.client.executeTool("figma-projects-getProjectFiles", { projectId: params.projectId })
598
+ }
599
+ };
600
+ }
601
+ get stripe() {
602
+ return {
603
+ customers: {
604
+ create: (params) => this.client.executeTool("stripe-customers-create", params),
605
+ retrieve: (params) => this.client.executeTool("stripe-customers-retrieve", { customerId: params.customerId }),
606
+ update: (params) => this.client.executeTool("stripe-customers-update", params),
607
+ listPaymentMethods: (params) => this.client.executeTool("stripe-customers-list-payment-methods", { customerId: params.customerId })
608
+ },
609
+ paymentIntents: {
610
+ create: (params) => this.client.executeTool("stripe-payment-intents-create", params),
611
+ retrieve: (params) => this.client.executeTool("stripe-payment-intents-retrieve", { paymentIntentId: params.paymentIntentId }),
612
+ confirm: (params) => this.client.executeTool("stripe-payment-intents-confirm", { paymentIntentId: params.paymentIntentId })
613
+ },
614
+ subscriptions: {
615
+ create: (params) => this.client.executeTool("stripe-subscriptions-create", params),
616
+ retrieve: (params) => this.client.executeTool("stripe-subscriptions-retrieve", { subscriptionId: params.subscriptionId }),
617
+ cancel: (params) => this.client.executeTool("stripe-subscriptions-cancel", { subscriptionId: params.subscriptionId })
618
+ }
619
+ };
620
+ }
621
+ };
622
+
623
+ // src/namespaces/memory.ts
624
+ init_cjs_shims();
625
+ function createMemory(client) {
626
+ return {
627
+ create: (params) => client.executeTool("memory-create", params),
628
+ get: (params) => client.executeTool("memory-get", { memoryId: params.memoryId }),
629
+ recall: (params) => client.executeTool("memory-recall", params),
630
+ list: (params) => client.executeTool("memory-list", params ?? {}),
631
+ update: (params) => client.executeTool("memory-update", params),
632
+ delete: (params) => client.executeTool("memory-delete", { memoryId: params.memoryId })
633
+ };
634
+ }
635
+
636
+ // src/namespaces/articles.ts
637
+ init_cjs_shims();
638
+ function createArticles(client) {
639
+ return {
640
+ search: (params) => client.executeTool("article-search", params),
641
+ create: (params) => client.executeTool("article-create", params),
642
+ createFromUrl: (params) => client.executeTool("article-create-from-url", params),
643
+ fetch: (params) => client.executeTool("article-fetch", params),
644
+ recent: (params) => client.executeTool("article-recent", params ?? {}),
645
+ relevant: (params) => client.executeTool("article-relevant-news", params),
646
+ summarize: (params) => client.executeTool("article-summarize", params),
647
+ extractTopics: (params) => client.executeTool("article-topic-extractor", params)
648
+ };
649
+ }
650
+
651
+ // src/namespaces/web.ts
652
+ init_cjs_shims();
653
+ function createWeb(client) {
654
+ return {
655
+ search: {
656
+ exa: (params) => client.executeTool("exa-web-search", params),
657
+ perplexity: (params) => client.executeTool("perplexity-search", params)
658
+ },
659
+ fetch: (params) => client.executeTool("web-fetch", params),
660
+ htmlToMarkdown: (params) => client.executeTool("html-to-markdown-converter", params),
661
+ extractHtml: (params) => client.executeTool("extract-html-selector", params)
662
+ };
663
+ }
664
+
665
+ // src/namespaces/ai.ts
666
+ init_cjs_shims();
667
+ function createAI(client) {
668
+ return {
669
+ embeddings: {
670
+ document: (params) => client.executeTool("jina-document-embedding", params),
671
+ query: (params) => client.executeTool("jina-query-embedding", params),
672
+ image: (params) => client.executeTool("jina-image-embedding", { image: params.imageBase64 })
673
+ },
674
+ image: {
675
+ generate: (params) => client.executeTool("image-generation", params)
676
+ },
677
+ ocr: (params) => client.executeTool("ocr-text-extraction", { image: params.imageBase64 }),
678
+ vision: (params) => client.executeTool("llama-scout-vision", { image: params.imageBase64, prompt: params.prompt }),
679
+ chat: (params) => client.executeTool("xai-chat-completions", params),
680
+ complete: (params) => client.executeTool("fim", params),
681
+ classify: (params) => client.executeTool("jina-document-classifier", params),
682
+ segment: (params) => client.executeTool("jina-text-segmenter", params),
683
+ /**
684
+ * OpenAI-compatible chat completions with full tool support
685
+ * This is the recommended method for multi-turn conversations with tools
686
+ */
687
+ chatCompletions: (params) => client.chatCompletions(params)
688
+ };
689
+ }
690
+
691
+ // src/namespaces/data.ts
692
+ init_cjs_shims();
693
+ function createData(client) {
694
+ return {
695
+ csv: {
696
+ query: (params) => client.executeTool("query-csv", params),
697
+ convert: (params) => client.executeTool("csv-convert", params)
698
+ },
699
+ scripts: {
700
+ create: (params) => client.executeTool("create-saved-script", params),
701
+ execute: (params) => client.executeTool("execute-saved-script", params),
702
+ list: () => client.executeTool("list-saved-scripts", {}),
703
+ update: (params) => client.executeTool("update-saved-script", params),
704
+ delete: (params) => client.executeTool("delete-saved-script", params)
705
+ },
706
+ similarity: {
707
+ search: (params) => client.executeTool("duck-db-similarity-search", params),
708
+ duckDbSearch: (params) => client.executeTool("duck-db-similarity-search", params)
709
+ }
710
+ };
711
+ }
712
+
713
+ // src/namespaces/utils.ts
714
+ init_cjs_shims();
715
+ function createUtils(client) {
716
+ return {
717
+ mermaid: (params) => client.executeTool("mermaid-diagram-generator", { mermaid: params.diagram }),
718
+ documentConvert: (params) => client.executeTool("document-format-converter", {
719
+ base64: `data:${params.mimeType};base64,${Buffer.from(params.document).toString("base64")}`,
720
+ format: params.format
721
+ }),
722
+ regex: {
723
+ match: (params) => client.executeTool("regex-match", params),
724
+ replace: (params) => client.executeTool("regex-replace", params)
725
+ },
726
+ jsonExtract: (params) => client.executeTool("json-extract", params),
727
+ digest: (params) => client.executeTool("digest-generator", { text: params.data }),
728
+ monteCarlo: (params) => client.executeTool("monte-carlo-simulation", params)
729
+ };
730
+ }
731
+
732
+ // src/sdk.ts
733
+ var Rainfall = class {
734
+ client;
735
+ _integrations;
736
+ _memory;
737
+ _articles;
738
+ _web;
739
+ _ai;
740
+ _data;
741
+ _utils;
742
+ constructor(config) {
743
+ this.client = new RainfallClient(config);
744
+ }
745
+ /**
746
+ * Integrations namespace - GitHub, Notion, Linear, Slack, Figma, Stripe
747
+ *
748
+ * @example
749
+ * ```typescript
750
+ * // GitHub
751
+ * await rainfall.integrations.github.issues.create({
752
+ * owner: 'facebook',
753
+ * repo: 'react',
754
+ * title: 'Bug report'
755
+ * });
756
+ *
757
+ * // Slack
758
+ * await rainfall.integrations.slack.messages.send({
759
+ * channelId: 'C123456',
760
+ * text: 'Hello team!'
761
+ * });
762
+ *
763
+ * // Linear
764
+ * const issues = await rainfall.integrations.linear.issues.list();
765
+ * ```
766
+ */
767
+ get integrations() {
768
+ if (!this._integrations) {
769
+ this._integrations = createIntegrations(this.client);
770
+ }
771
+ return this._integrations;
772
+ }
773
+ /**
774
+ * Memory namespace - Semantic memory storage and retrieval
775
+ *
776
+ * @example
777
+ * ```typescript
778
+ * // Store a memory
779
+ * await rainfall.memory.create({
780
+ * content: 'User prefers dark mode',
781
+ * keywords: ['preference', 'ui']
782
+ * });
783
+ *
784
+ * // Recall similar memories
785
+ * const memories = await rainfall.memory.recall({
786
+ * query: 'user preferences',
787
+ * topK: 5
788
+ * });
789
+ * ```
790
+ */
791
+ get memory() {
792
+ if (!this._memory) {
793
+ this._memory = createMemory(this.client);
794
+ }
795
+ return this._memory;
796
+ }
797
+ /**
798
+ * Articles namespace - News aggregation and article management
799
+ *
800
+ * @example
801
+ * ```typescript
802
+ * // Search news
803
+ * const articles = await rainfall.articles.search({
804
+ * query: 'artificial intelligence'
805
+ * });
806
+ *
807
+ * // Create from URL
808
+ * const article = await rainfall.articles.createFromUrl({
809
+ * url: 'https://example.com/article'
810
+ * });
811
+ *
812
+ * // Summarize
813
+ * const summary = await rainfall.articles.summarize({
814
+ * text: article.content
815
+ * });
816
+ * ```
817
+ */
818
+ get articles() {
819
+ if (!this._articles) {
820
+ this._articles = createArticles(this.client);
821
+ }
822
+ return this._articles;
823
+ }
824
+ /**
825
+ * Web namespace - Web search, scraping, and content extraction
826
+ *
827
+ * @example
828
+ * ```typescript
829
+ * // Search with Exa
830
+ * const results = await rainfall.web.search.exa({
831
+ * query: 'latest AI research'
832
+ * });
833
+ *
834
+ * // Fetch and convert
835
+ * const html = await rainfall.web.fetch({ url: 'https://example.com' });
836
+ * const markdown = await rainfall.web.htmlToMarkdown({ html });
837
+ *
838
+ * // Extract specific elements
839
+ * const links = await rainfall.web.extractHtml({
840
+ * html,
841
+ * selector: 'a[href]'
842
+ * });
843
+ * ```
844
+ */
845
+ get web() {
846
+ if (!this._web) {
847
+ this._web = createWeb(this.client);
848
+ }
849
+ return this._web;
850
+ }
851
+ /**
852
+ * AI namespace - Embeddings, image generation, OCR, vision, chat
853
+ *
854
+ * @example
855
+ * ```typescript
856
+ * // Generate embeddings
857
+ * const embedding = await rainfall.ai.embeddings.document({
858
+ * text: 'Hello world'
859
+ * });
860
+ *
861
+ * // Generate image
862
+ * const image = await rainfall.ai.image.generate({
863
+ * prompt: 'A serene mountain landscape'
864
+ * });
865
+ *
866
+ * // OCR
867
+ * const text = await rainfall.ai.ocr({ imageBase64: '...' });
868
+ *
869
+ * // Chat
870
+ * const response = await rainfall.ai.chat({
871
+ * messages: [{ role: 'user', content: 'Hello!' }]
872
+ * });
873
+ * ```
874
+ */
875
+ get ai() {
876
+ if (!this._ai) {
877
+ this._ai = createAI(this.client);
878
+ }
879
+ return this._ai;
880
+ }
881
+ /**
882
+ * Data namespace - CSV processing, scripts, similarity search
883
+ *
884
+ * @example
885
+ * ```typescript
886
+ * // Query CSV with SQL
887
+ * const results = await rainfall.data.csv.query({
888
+ * sql: 'SELECT * FROM data WHERE value > 100'
889
+ * });
890
+ *
891
+ * // Execute saved script
892
+ * const result = await rainfall.data.scripts.execute({
893
+ * name: 'my-script',
894
+ * params: { input: 'data' }
895
+ * });
896
+ * ```
897
+ */
898
+ get data() {
899
+ if (!this._data) {
900
+ this._data = createData(this.client);
901
+ }
902
+ return this._data;
903
+ }
904
+ /**
905
+ * Utils namespace - Mermaid diagrams, document conversion, regex, JSON extraction
906
+ *
907
+ * @example
908
+ * ```typescript
909
+ * // Generate diagram
910
+ * const diagram = await rainfall.utils.mermaid({
911
+ * diagram: 'graph TD; A-->B;'
912
+ * });
913
+ *
914
+ * // Convert document
915
+ * const pdf = await rainfall.utils.documentConvert({
916
+ * document: markdownContent,
917
+ * mimeType: 'text/markdown',
918
+ * format: 'pdf'
919
+ * });
920
+ *
921
+ * // Extract JSON from text
922
+ * const json = await rainfall.utils.jsonExtract({
923
+ * text: 'Here is some data: {"key": "value"}'
924
+ * });
925
+ * ```
926
+ */
927
+ get utils() {
928
+ if (!this._utils) {
929
+ this._utils = createUtils(this.client);
930
+ }
931
+ return this._utils;
932
+ }
933
+ /**
934
+ * Get the underlying HTTP client for advanced usage
935
+ */
936
+ getClient() {
937
+ return this.client;
938
+ }
939
+ /**
940
+ * List all available tools
941
+ */
942
+ async listTools() {
943
+ return this.client.listTools();
944
+ }
945
+ /**
946
+ * Get schema for a specific tool
947
+ */
948
+ async getToolSchema(toolId) {
949
+ return this.client.getToolSchema(toolId);
950
+ }
951
+ /**
952
+ * Execute any tool by ID (low-level access)
953
+ */
954
+ async executeTool(toolId, params) {
955
+ return this.client.executeTool(toolId, params);
956
+ }
957
+ /**
958
+ * Get current subscriber info and usage
959
+ */
960
+ async getMe() {
961
+ return this.client.getMe();
962
+ }
963
+ /**
964
+ * Get current rate limit info
965
+ */
966
+ getRateLimitInfo() {
967
+ return this.client.getRateLimitInfo();
968
+ }
969
+ /**
970
+ * OpenAI-compatible chat completions with tool support
971
+ *
972
+ * @example
973
+ * ```typescript
974
+ * // Simple chat
975
+ * const response = await rainfall.chatCompletions({
976
+ * subscriber_id: 'my-subscriber',
977
+ * messages: [{ role: 'user', content: 'Hello!' }],
978
+ * model: 'llama-3.3-70b-versatile'
979
+ * });
980
+ *
981
+ * // With tools
982
+ * const response = await rainfall.chatCompletions({
983
+ * subscriber_id: 'my-subscriber',
984
+ * messages: [{ role: 'user', content: 'Search for AI news' }],
985
+ * tools: [{ type: 'function', function: { name: 'web-search' } }],
986
+ * enable_stacked: true
987
+ * });
988
+ *
989
+ * // Streaming
990
+ * const stream = await rainfall.chatCompletions({
991
+ * subscriber_id: 'my-subscriber',
992
+ * messages: [{ role: 'user', content: 'Tell me a story' }],
993
+ * stream: true
994
+ * });
995
+ * ```
996
+ */
997
+ async chatCompletions(params) {
998
+ return this.client.chatCompletions(params);
999
+ }
1000
+ /**
1001
+ * List available models (OpenAI-compatible format)
1002
+ *
1003
+ * @example
1004
+ * ```typescript
1005
+ * const models = await rainfall.listModels();
1006
+ * console.log(models); // [{ id: 'llama-3.3-70b-versatile', ... }]
1007
+ * ```
1008
+ */
1009
+ async listModels(subscriberId) {
1010
+ return this.client.listModels(subscriberId);
1011
+ }
1012
+ };
1013
+
1014
+ // src/services/networked.ts
1015
+ init_cjs_shims();
1016
+ var RainfallNetworkedExecutor = class {
1017
+ rainfall;
1018
+ options;
1019
+ edgeNodeId;
1020
+ jobCallbacks = /* @__PURE__ */ new Map();
1021
+ resultPollingInterval;
1022
+ constructor(rainfall, options = {}) {
1023
+ this.rainfall = rainfall;
1024
+ this.options = {
1025
+ wsPort: 8765,
1026
+ httpPort: 8787,
1027
+ hostname: process.env.HOSTNAME || "local-daemon",
1028
+ capabilities: {
1029
+ localExec: true,
1030
+ fileWatch: true,
1031
+ passiveListen: true
1032
+ },
1033
+ ...options
1034
+ };
1035
+ }
1036
+ /**
1037
+ * Register this edge node with the Rainfall backend
1038
+ */
1039
+ async registerEdgeNode() {
1040
+ const capabilities = this.buildCapabilitiesList();
1041
+ try {
1042
+ const result = await this.rainfall.executeTool("register-edge-node", {
1043
+ hostname: this.options.hostname,
1044
+ capabilities,
1045
+ wsPort: this.options.wsPort,
1046
+ httpPort: this.options.httpPort,
1047
+ version: "0.1.0"
1048
+ });
1049
+ this.edgeNodeId = result.edgeNodeId;
1050
+ console.log(`\u{1F310} Edge node registered with Rainfall as ${this.edgeNodeId}`);
1051
+ return this.edgeNodeId;
1052
+ } catch (error) {
1053
+ this.edgeNodeId = `edge-${this.options.hostname}-${Date.now()}`;
1054
+ console.log(`\u{1F310} Edge node running in local mode (ID: ${this.edgeNodeId})`);
1055
+ return this.edgeNodeId;
1056
+ }
1057
+ }
1058
+ /**
1059
+ * Unregister this edge node on shutdown
1060
+ */
1061
+ async unregisterEdgeNode() {
1062
+ if (!this.edgeNodeId) return;
1063
+ try {
1064
+ await this.rainfall.executeTool("unregister-edge-node", {
1065
+ edgeNodeId: this.edgeNodeId
1066
+ });
1067
+ console.log(`\u{1F310} Edge node ${this.edgeNodeId} unregistered`);
1068
+ } catch {
1069
+ }
1070
+ if (this.resultPollingInterval) {
1071
+ clearInterval(this.resultPollingInterval);
1072
+ }
1073
+ }
1074
+ /**
1075
+ * Queue a tool execution for distributed processing
1076
+ * Non-blocking - returns immediately with a job ID
1077
+ */
1078
+ async queueToolExecution(toolId, params, options = {}) {
1079
+ const executionMode = options.executionMode || "any";
1080
+ try {
1081
+ const result = await this.rainfall.executeTool("queue-job", {
1082
+ toolId,
1083
+ params,
1084
+ executionMode,
1085
+ requesterEdgeNodeId: this.edgeNodeId
1086
+ });
1087
+ if (options.callback) {
1088
+ this.jobCallbacks.set(result.jobId, options.callback);
1089
+ this.startResultPolling();
1090
+ }
1091
+ return result.jobId;
1092
+ } catch (error) {
1093
+ if (executionMode === "local-only" || executionMode === "any") {
1094
+ try {
1095
+ const result = await this.rainfall.executeTool(toolId, params);
1096
+ if (options.callback) {
1097
+ options.callback(result);
1098
+ }
1099
+ return `local-${Date.now()}`;
1100
+ } catch (execError) {
1101
+ if (options.callback) {
1102
+ options.callback(null, String(execError));
1103
+ }
1104
+ throw execError;
1105
+ }
1106
+ }
1107
+ throw error;
1108
+ }
1109
+ }
1110
+ /**
1111
+ * Get status of a queued job
1112
+ */
1113
+ async getJobStatus(jobId) {
1114
+ try {
1115
+ const result = await this.rainfall.executeTool("get-job-status", {
1116
+ jobId
1117
+ });
1118
+ return result.job;
1119
+ } catch {
1120
+ return null;
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Subscribe to job results via polling (WebSocket fallback)
1125
+ * In the future, this will use WebSocket push from ApresMoi
1126
+ */
1127
+ async subscribeToResults(callback) {
1128
+ console.log("\u{1F4E1} Subscribed to job results via Rainfall (polling mode)");
1129
+ this.onResultReceived = callback;
1130
+ }
1131
+ onResultReceived;
1132
+ /**
1133
+ * Start polling for job results (fallback until WebSocket push is ready)
1134
+ */
1135
+ startResultPolling() {
1136
+ if (this.resultPollingInterval) return;
1137
+ this.resultPollingInterval = setInterval(async () => {
1138
+ for (const [jobId, callback] of this.jobCallbacks) {
1139
+ try {
1140
+ const job = await this.getJobStatus(jobId);
1141
+ if (job?.status === "completed" || job?.status === "failed") {
1142
+ callback(job.result, job.error);
1143
+ this.jobCallbacks.delete(jobId);
1144
+ if (this.onResultReceived) {
1145
+ this.onResultReceived(jobId, job.result, job.error);
1146
+ }
1147
+ }
1148
+ } catch {
1149
+ }
1150
+ }
1151
+ if (this.jobCallbacks.size === 0 && this.resultPollingInterval) {
1152
+ clearInterval(this.resultPollingInterval);
1153
+ this.resultPollingInterval = void 0;
1154
+ }
1155
+ }, 2e3);
1156
+ }
1157
+ /**
1158
+ * Claim a job for execution on this edge node
1159
+ */
1160
+ async claimJob() {
1161
+ try {
1162
+ const result = await this.rainfall.executeTool("claim-job", {
1163
+ edgeNodeId: this.edgeNodeId,
1164
+ capabilities: this.buildCapabilitiesList()
1165
+ });
1166
+ return result.job;
1167
+ } catch {
1168
+ return null;
1169
+ }
1170
+ }
1171
+ /**
1172
+ * Submit job result after execution
1173
+ */
1174
+ async submitJobResult(jobId, result, error) {
1175
+ try {
1176
+ await this.rainfall.executeTool("submit-job-result", {
1177
+ jobId,
1178
+ edgeNodeId: this.edgeNodeId,
1179
+ result,
1180
+ error
1181
+ });
1182
+ } catch {
1183
+ }
1184
+ }
1185
+ /**
1186
+ * Get this edge node's ID
1187
+ */
1188
+ getEdgeNodeId() {
1189
+ return this.edgeNodeId;
1190
+ }
1191
+ /**
1192
+ * Build capabilities list from options
1193
+ */
1194
+ buildCapabilitiesList() {
1195
+ const caps = this.options.capabilities || {};
1196
+ const list = [];
1197
+ if (caps.localExec) list.push("local-exec");
1198
+ if (caps.fileWatch) list.push("file-watch");
1199
+ if (caps.passiveListen) list.push("passive-listen");
1200
+ if (caps.browser) list.push("browser");
1201
+ if (caps.custom) list.push(...caps.custom);
1202
+ return list;
1203
+ }
1204
+ };
1205
+
1206
+ // src/services/context.ts
1207
+ init_cjs_shims();
1208
+ var RainfallDaemonContext = class {
1209
+ rainfall;
1210
+ options;
1211
+ localMemories = /* @__PURE__ */ new Map();
1212
+ sessions = /* @__PURE__ */ new Map();
1213
+ executionHistory = [];
1214
+ currentSessionId;
1215
+ constructor(rainfall, options = {}) {
1216
+ this.rainfall = rainfall;
1217
+ this.options = {
1218
+ maxLocalMemories: 1e3,
1219
+ maxMessageHistory: 100,
1220
+ maxExecutionHistory: 500,
1221
+ sessionTtl: 24 * 60 * 60 * 1e3,
1222
+ // 24 hours
1223
+ ...options
1224
+ };
1225
+ }
1226
+ /**
1227
+ * Initialize the context - load recent memories from cloud
1228
+ */
1229
+ async initialize() {
1230
+ try {
1231
+ const recentMemories = await this.rainfall.memory.recall({
1232
+ query: "daemon:context",
1233
+ topK: this.options.maxLocalMemories
1234
+ });
1235
+ for (const memory of recentMemories) {
1236
+ this.localMemories.set(memory.id, {
1237
+ id: memory.id,
1238
+ content: memory.content,
1239
+ keywords: memory.keywords || [],
1240
+ timestamp: memory.timestamp,
1241
+ source: memory.source,
1242
+ metadata: memory.metadata
1243
+ });
1244
+ }
1245
+ console.log(`\u{1F9E0} Loaded ${this.localMemories.size} memories into context`);
1246
+ } catch (error) {
1247
+ console.warn("\u26A0\uFE0F Could not sync memories:", error instanceof Error ? error.message : error);
1248
+ }
1249
+ }
1250
+ /**
1251
+ * Create or get a session
1252
+ */
1253
+ getSession(sessionId) {
1254
+ if (sessionId && this.sessions.has(sessionId)) {
1255
+ const session = this.sessions.get(sessionId);
1256
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1257
+ return session;
1258
+ }
1259
+ const newSession = {
1260
+ id: sessionId || `session-${Date.now()}`,
1261
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1262
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
1263
+ variables: {},
1264
+ messageHistory: []
1265
+ };
1266
+ this.sessions.set(newSession.id, newSession);
1267
+ this.currentSessionId = newSession.id;
1268
+ return newSession;
1269
+ }
1270
+ /**
1271
+ * Get the current active session
1272
+ */
1273
+ getCurrentSession() {
1274
+ if (this.currentSessionId) {
1275
+ return this.sessions.get(this.currentSessionId);
1276
+ }
1277
+ return void 0;
1278
+ }
1279
+ /**
1280
+ * Set the current active session
1281
+ */
1282
+ setCurrentSession(sessionId) {
1283
+ if (this.sessions.has(sessionId)) {
1284
+ this.currentSessionId = sessionId;
1285
+ }
1286
+ }
1287
+ /**
1288
+ * Add a message to the current session history
1289
+ */
1290
+ addMessage(role, content) {
1291
+ const session = this.getCurrentSession();
1292
+ if (!session) return;
1293
+ session.messageHistory.push({
1294
+ role,
1295
+ content,
1296
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1297
+ });
1298
+ if (session.messageHistory.length > this.options.maxMessageHistory) {
1299
+ session.messageHistory = session.messageHistory.slice(-this.options.maxMessageHistory);
1300
+ }
1301
+ }
1302
+ /**
1303
+ * Store a memory (local + cloud sync)
1304
+ */
1305
+ async storeMemory(content, options = {}) {
1306
+ const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1307
+ const entry = {
1308
+ id,
1309
+ content,
1310
+ keywords: options.keywords || [],
1311
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1312
+ source: options.source || "daemon",
1313
+ metadata: options.metadata
1314
+ };
1315
+ this.localMemories.set(id, entry);
1316
+ try {
1317
+ await this.rainfall.memory.create({
1318
+ content,
1319
+ keywords: [...options.keywords || [], "daemon:context"],
1320
+ metadata: {
1321
+ ...options.metadata,
1322
+ daemonMemoryId: id,
1323
+ source: options.source || "daemon"
1324
+ }
1325
+ });
1326
+ } catch (error) {
1327
+ console.warn("\u26A0\uFE0F Could not sync memory to cloud:", error instanceof Error ? error.message : error);
1328
+ }
1329
+ this.trimLocalMemories();
1330
+ return id;
1331
+ }
1332
+ /**
1333
+ * Recall memories by query
1334
+ */
1335
+ async recallMemories(query, topK = 5) {
1336
+ const localResults = Array.from(this.localMemories.values()).filter(
1337
+ (m) => m.content.toLowerCase().includes(query.toLowerCase()) || m.keywords.some((k) => k.toLowerCase().includes(query.toLowerCase()))
1338
+ ).slice(0, topK);
1339
+ try {
1340
+ const cloudResults = await this.rainfall.memory.recall({ query, topK });
1341
+ const seen = new Set(localResults.map((r) => r.id));
1342
+ for (const mem of cloudResults) {
1343
+ if (!seen.has(mem.id)) {
1344
+ localResults.push({
1345
+ id: mem.id,
1346
+ content: mem.content,
1347
+ keywords: mem.keywords || [],
1348
+ timestamp: mem.timestamp,
1349
+ source: mem.source,
1350
+ metadata: mem.metadata
1351
+ });
1352
+ }
1353
+ }
1354
+ } catch {
1355
+ }
1356
+ return localResults.slice(0, topK);
1357
+ }
1358
+ /**
1359
+ * Set a session variable
1360
+ */
1361
+ setVariable(key, value) {
1362
+ const session = this.getCurrentSession();
1363
+ if (session) {
1364
+ session.variables[key] = value;
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Get a session variable
1369
+ */
1370
+ getVariable(key) {
1371
+ const session = this.getCurrentSession();
1372
+ return session?.variables[key];
1373
+ }
1374
+ /**
1375
+ * Record a tool execution
1376
+ */
1377
+ recordExecution(toolId, params, result, options = { duration: 0 }) {
1378
+ const record = {
1379
+ id: `exec-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1380
+ toolId,
1381
+ params,
1382
+ result,
1383
+ error: options.error,
1384
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1385
+ duration: options.duration,
1386
+ edgeNodeId: options.edgeNodeId
1387
+ };
1388
+ this.executionHistory.push(record);
1389
+ if (this.executionHistory.length > this.options.maxExecutionHistory) {
1390
+ this.executionHistory = this.executionHistory.slice(-this.options.maxExecutionHistory);
1391
+ }
1392
+ }
1393
+ /**
1394
+ * Get recent execution history
1395
+ */
1396
+ getExecutionHistory(limit = 10) {
1397
+ return this.executionHistory.slice(-limit).reverse();
1398
+ }
1399
+ /**
1400
+ * Get execution statistics
1401
+ */
1402
+ getExecutionStats() {
1403
+ const stats = {
1404
+ total: this.executionHistory.length,
1405
+ successful: 0,
1406
+ failed: 0,
1407
+ averageDuration: 0,
1408
+ byTool: {}
1409
+ };
1410
+ let totalDuration = 0;
1411
+ for (const exec of this.executionHistory) {
1412
+ if (exec.error) {
1413
+ stats.failed++;
1414
+ } else {
1415
+ stats.successful++;
1416
+ }
1417
+ totalDuration += exec.duration;
1418
+ stats.byTool[exec.toolId] = (stats.byTool[exec.toolId] || 0) + 1;
1419
+ }
1420
+ stats.averageDuration = stats.total > 0 ? totalDuration / stats.total : 0;
1421
+ return stats;
1422
+ }
1423
+ /**
1424
+ * Clear old sessions based on TTL
1425
+ */
1426
+ cleanupSessions() {
1427
+ const now = Date.now();
1428
+ const ttl = this.options.sessionTtl;
1429
+ for (const [id, session] of this.sessions) {
1430
+ const lastActivity = new Date(session.lastActivity).getTime();
1431
+ if (now - lastActivity > ttl) {
1432
+ this.sessions.delete(id);
1433
+ if (this.currentSessionId === id) {
1434
+ this.currentSessionId = void 0;
1435
+ }
1436
+ }
1437
+ }
1438
+ }
1439
+ /**
1440
+ * Get context summary for debugging
1441
+ */
1442
+ getStatus() {
1443
+ return {
1444
+ memoriesCached: this.localMemories.size,
1445
+ activeSessions: this.sessions.size,
1446
+ currentSession: this.currentSessionId,
1447
+ executionHistorySize: this.executionHistory.length
1448
+ };
1449
+ }
1450
+ trimLocalMemories() {
1451
+ if (this.localMemories.size <= this.options.maxLocalMemories) return;
1452
+ const entries = Array.from(this.localMemories.entries()).sort((a, b) => new Date(a[1].timestamp).getTime() - new Date(b[1].timestamp).getTime());
1453
+ const toRemove = entries.slice(0, entries.length - this.options.maxLocalMemories);
1454
+ for (const [id] of toRemove) {
1455
+ this.localMemories.delete(id);
1456
+ }
1457
+ }
1458
+ };
1459
+
1460
+ // src/services/listeners.ts
1461
+ init_cjs_shims();
1462
+ var RainfallListenerRegistry = class {
1463
+ rainfall;
1464
+ context;
1465
+ executor;
1466
+ watchers = /* @__PURE__ */ new Map();
1467
+ cronIntervals = /* @__PURE__ */ new Map();
1468
+ eventHistory = [];
1469
+ maxEventHistory = 100;
1470
+ constructor(rainfall, context, executor) {
1471
+ this.rainfall = rainfall;
1472
+ this.context = context;
1473
+ this.executor = executor;
1474
+ }
1475
+ /**
1476
+ * Register a file watcher
1477
+ * Note: Actual file watching requires fs.watch or chokidar
1478
+ * This is the registry - actual watching is done by the daemon
1479
+ */
1480
+ async registerFileWatcher(config) {
1481
+ console.log(`\u{1F441}\uFE0F Registering file watcher: ${config.name} (${config.watchPath})`);
1482
+ const existing = Array.from(this.watchers.keys());
1483
+ if (existing.includes(config.id)) {
1484
+ await this.unregisterFileWatcher(config.id);
1485
+ }
1486
+ this.watchers.set(config.id, {
1487
+ stop: () => {
1488
+ console.log(`\u{1F441}\uFE0F Stopped file watcher: ${config.name}`);
1489
+ }
1490
+ });
1491
+ await this.context.storeMemory(`File watcher registered: ${config.name}`, {
1492
+ keywords: ["listener", "file-watcher", config.name],
1493
+ metadata: { config }
1494
+ });
1495
+ }
1496
+ /**
1497
+ * Unregister a file watcher
1498
+ */
1499
+ async unregisterFileWatcher(id) {
1500
+ const watcher = this.watchers.get(id);
1501
+ if (watcher) {
1502
+ watcher.stop();
1503
+ this.watchers.delete(id);
1504
+ }
1505
+ }
1506
+ /**
1507
+ * Register a cron trigger
1508
+ */
1509
+ async registerCronTrigger(config) {
1510
+ console.log(`\u23F0 Registering cron trigger: ${config.name} (${config.cron})`);
1511
+ if (this.cronIntervals.has(config.id)) {
1512
+ clearInterval(this.cronIntervals.get(config.id));
1513
+ this.cronIntervals.delete(config.id);
1514
+ }
1515
+ const interval = this.parseCronToMs(config.cron);
1516
+ if (interval) {
1517
+ const intervalId = setInterval(async () => {
1518
+ await this.handleCronTick(config);
1519
+ }, interval);
1520
+ this.cronIntervals.set(config.id, intervalId);
1521
+ }
1522
+ await this.context.storeMemory(`Cron trigger registered: ${config.name}`, {
1523
+ keywords: ["listener", "cron", config.name],
1524
+ metadata: { config }
1525
+ });
1526
+ }
1527
+ /**
1528
+ * Unregister a cron trigger
1529
+ */
1530
+ unregisterCronTrigger(id) {
1531
+ const interval = this.cronIntervals.get(id);
1532
+ if (interval) {
1533
+ clearInterval(interval);
1534
+ this.cronIntervals.delete(id);
1535
+ }
1536
+ }
1537
+ /**
1538
+ * Handle a file event
1539
+ */
1540
+ async handleFileEvent(watcherId, eventType, filePath) {
1541
+ const event = {
1542
+ id: `evt-${Date.now()}`,
1543
+ type: "file",
1544
+ source: watcherId,
1545
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1546
+ data: { eventType, filePath }
1547
+ };
1548
+ this.recordEvent(event);
1549
+ console.log(`\u{1F4C1} File event: ${eventType} ${filePath}`);
1550
+ }
1551
+ /**
1552
+ * Handle a cron tick
1553
+ */
1554
+ async handleCronTick(config) {
1555
+ const event = {
1556
+ id: `evt-${Date.now()}`,
1557
+ type: "cron",
1558
+ source: config.id,
1559
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1560
+ data: { cron: config.cron }
1561
+ };
1562
+ this.recordEvent(event);
1563
+ console.log(`\u23F0 Cron tick: ${config.name}`);
1564
+ for (const step of config.workflow) {
1565
+ try {
1566
+ await this.executor.queueToolExecution(step.toolId, {
1567
+ ...step.params,
1568
+ _event: event
1569
+ });
1570
+ } catch (error) {
1571
+ console.error(`\u274C Workflow step failed: ${step.toolId}`, error);
1572
+ }
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Trigger a manual event (for testing or programmatic triggers)
1577
+ */
1578
+ async triggerManual(name, data = {}) {
1579
+ const event = {
1580
+ id: `evt-${Date.now()}`,
1581
+ type: "manual",
1582
+ source: name,
1583
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1584
+ data
1585
+ };
1586
+ this.recordEvent(event);
1587
+ console.log(`\u{1F446} Manual trigger: ${name}`);
1588
+ await this.context.storeMemory(`Manual trigger fired: ${name}`, {
1589
+ keywords: ["trigger", "manual", name],
1590
+ metadata: { event }
1591
+ });
1592
+ }
1593
+ /**
1594
+ * Get recent events
1595
+ */
1596
+ getRecentEvents(limit = 10) {
1597
+ return this.eventHistory.slice(-limit).reverse();
1598
+ }
1599
+ /**
1600
+ * Get active listeners status
1601
+ */
1602
+ getStatus() {
1603
+ return {
1604
+ fileWatchers: this.watchers.size,
1605
+ cronTriggers: this.cronIntervals.size,
1606
+ recentEvents: this.eventHistory.length
1607
+ };
1608
+ }
1609
+ /**
1610
+ * Stop all listeners
1611
+ */
1612
+ async stopAll() {
1613
+ for (const [id] of this.watchers) {
1614
+ await this.unregisterFileWatcher(id);
1615
+ }
1616
+ for (const [id] of this.cronIntervals) {
1617
+ this.unregisterCronTrigger(id);
1618
+ }
1619
+ console.log("\u{1F6D1} All listeners stopped");
1620
+ }
1621
+ recordEvent(event) {
1622
+ this.eventHistory.push(event);
1623
+ if (this.eventHistory.length > this.maxEventHistory) {
1624
+ this.eventHistory = this.eventHistory.slice(-this.maxEventHistory);
1625
+ }
1626
+ }
1627
+ /**
1628
+ * Simple cron parser - converts basic cron expressions to milliseconds
1629
+ * Supports: @hourly, @daily, @weekly, and simple intervals like every N minutes
1630
+ */
1631
+ parseCronToMs(cron) {
1632
+ switch (cron) {
1633
+ case "@hourly":
1634
+ return 60 * 60 * 1e3;
1635
+ case "@daily":
1636
+ return 24 * 60 * 60 * 1e3;
1637
+ case "@weekly":
1638
+ return 7 * 24 * 60 * 60 * 1e3;
1639
+ case "@minutely":
1640
+ return 60 * 1e3;
1641
+ }
1642
+ const match = cron.match(/^\*\/(\d+)\s/);
1643
+ if (match) {
1644
+ const minutes = parseInt(match[1], 10);
1645
+ if (minutes > 0 && minutes <= 60) {
1646
+ return minutes * 60 * 1e3;
1647
+ }
1648
+ }
1649
+ console.warn(`\u26A0\uFE0F Unrecognized cron pattern "${cron}", using 1 minute interval`);
1650
+ return 60 * 1e3;
1651
+ }
1652
+ };
1653
+
1654
+ // src/services/mcp-proxy.ts
1655
+ init_cjs_shims();
1656
+ var import_ws = require("ws");
1657
+ var import_client2 = require("@modelcontextprotocol/sdk/client/index.js");
1658
+ var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
1659
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
1660
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
1661
+ var MCPProxyHub = class {
1662
+ clients = /* @__PURE__ */ new Map();
1663
+ options;
1664
+ refreshTimer;
1665
+ reconnectTimeouts = /* @__PURE__ */ new Map();
1666
+ requestId = 0;
1667
+ constructor(options = {}) {
1668
+ this.options = {
1669
+ debug: options.debug ?? false,
1670
+ autoReconnect: options.autoReconnect ?? true,
1671
+ reconnectDelay: options.reconnectDelay ?? 5e3,
1672
+ toolTimeout: options.toolTimeout ?? 3e4,
1673
+ refreshInterval: options.refreshInterval ?? 3e4
1674
+ };
1675
+ }
1676
+ /**
1677
+ * Initialize the MCP proxy hub
1678
+ */
1679
+ async initialize() {
1680
+ this.log("\u{1F50C} Initializing MCP Proxy Hub...");
1681
+ this.startRefreshTimer();
1682
+ this.log("\u2705 MCP Proxy Hub initialized");
1683
+ }
1684
+ /**
1685
+ * Shutdown the MCP proxy hub and disconnect all clients
1686
+ */
1687
+ async shutdown() {
1688
+ this.log("\u{1F6D1} Shutting down MCP Proxy Hub...");
1689
+ if (this.refreshTimer) {
1690
+ clearInterval(this.refreshTimer);
1691
+ this.refreshTimer = void 0;
1692
+ }
1693
+ for (const timeout of this.reconnectTimeouts.values()) {
1694
+ clearTimeout(timeout);
1695
+ }
1696
+ this.reconnectTimeouts.clear();
1697
+ const disconnectPromises = Array.from(this.clients.entries()).map(
1698
+ async ([name, client]) => {
1699
+ try {
1700
+ await this.disconnectClient(name);
1701
+ } catch (error) {
1702
+ this.log(`Error disconnecting ${name}:`, error);
1703
+ }
1704
+ }
1705
+ );
1706
+ await Promise.allSettled(disconnectPromises);
1707
+ this.clients.clear();
1708
+ this.log("\u{1F44B} MCP Proxy Hub shut down");
1709
+ }
1710
+ /**
1711
+ * Connect to an MCP server
1712
+ */
1713
+ async connectClient(config) {
1714
+ const { name, transport } = config;
1715
+ if (this.clients.has(name)) {
1716
+ this.log(`Reconnecting client: ${name}`);
1717
+ await this.disconnectClient(name);
1718
+ }
1719
+ this.log(`Connecting to MCP server: ${name} (${transport})...`);
1720
+ try {
1721
+ const client = new import_client2.Client(
1722
+ {
1723
+ name: `rainfall-daemon-${name}`,
1724
+ version: "0.2.0"
1725
+ },
1726
+ {
1727
+ capabilities: {}
1728
+ }
1729
+ );
1730
+ let lastErrorTime = 0;
1731
+ client.onerror = (error) => {
1732
+ const now = Date.now();
1733
+ if (now - lastErrorTime > 5e3) {
1734
+ this.log(`MCP Server Error (${name}):`, error.message);
1735
+ lastErrorTime = now;
1736
+ }
1737
+ if (this.options.autoReconnect) {
1738
+ this.scheduleReconnect(name, config);
1739
+ }
1740
+ };
1741
+ let transportInstance;
1742
+ if (transport === "stdio" && config.command) {
1743
+ const env = {};
1744
+ for (const [key, value] of Object.entries({ ...process.env, ...config.env })) {
1745
+ if (value !== void 0) {
1746
+ env[key] = value;
1747
+ }
1748
+ }
1749
+ transportInstance = new import_stdio.StdioClientTransport({
1750
+ command: config.command,
1751
+ args: config.args,
1752
+ env
1753
+ });
1754
+ } else if (transport === "http" && config.url) {
1755
+ transportInstance = new import_streamableHttp.StreamableHTTPClientTransport(
1756
+ new URL(config.url),
1757
+ {
1758
+ requestInit: {
1759
+ headers: config.headers
1760
+ }
1761
+ }
1762
+ );
1763
+ } else if (transport === "websocket" && config.url) {
1764
+ transportInstance = new import_ws.WebSocket(config.url);
1765
+ await new Promise((resolve, reject) => {
1766
+ transportInstance.on("open", () => resolve());
1767
+ transportInstance.on("error", reject);
1768
+ setTimeout(() => reject(new Error("WebSocket connection timeout")), 1e4);
1769
+ });
1770
+ } else {
1771
+ throw new Error(`Invalid transport configuration for ${name}`);
1772
+ }
1773
+ await client.connect(transportInstance);
1774
+ const toolsResult = await client.request(
1775
+ {
1776
+ method: "tools/list",
1777
+ params: {}
1778
+ },
1779
+ import_types.ListToolsResultSchema
1780
+ );
1781
+ const tools = toolsResult.tools.map((tool) => ({
1782
+ name: tool.name,
1783
+ description: tool.description || "",
1784
+ inputSchema: tool.inputSchema,
1785
+ serverName: name
1786
+ }));
1787
+ const clientInfo = {
1788
+ name,
1789
+ client,
1790
+ transport: transportInstance,
1791
+ transportType: transport,
1792
+ tools,
1793
+ connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
1794
+ lastUsed: (/* @__PURE__ */ new Date()).toISOString(),
1795
+ config,
1796
+ status: "connected"
1797
+ };
1798
+ this.clients.set(name, clientInfo);
1799
+ this.log(`\u2705 Connected to ${name} (${tools.length} tools)`);
1800
+ this.printAvailableTools(name, tools);
1801
+ return name;
1802
+ } catch (error) {
1803
+ const errorMessage = error instanceof Error ? error.message : String(error);
1804
+ this.log(`\u274C Failed to connect to ${name}:`, errorMessage);
1805
+ if (this.options.autoReconnect) {
1806
+ this.scheduleReconnect(name, config);
1807
+ }
1808
+ throw error;
1809
+ }
1810
+ }
1811
+ /**
1812
+ * Disconnect a specific MCP client
1813
+ */
1814
+ async disconnectClient(name) {
1815
+ const client = this.clients.get(name);
1816
+ if (!client) return;
1817
+ const timeout = this.reconnectTimeouts.get(name);
1818
+ if (timeout) {
1819
+ clearTimeout(timeout);
1820
+ this.reconnectTimeouts.delete(name);
1821
+ }
1822
+ try {
1823
+ await client.client.close();
1824
+ if ("close" in client.transport && typeof client.transport.close === "function") {
1825
+ await client.transport.close();
1826
+ }
1827
+ } catch (error) {
1828
+ this.log(`Error closing client ${name}:`, error);
1829
+ }
1830
+ this.clients.delete(name);
1831
+ this.log(`Disconnected from ${name}`);
1832
+ }
1833
+ /**
1834
+ * Schedule a reconnection attempt
1835
+ */
1836
+ scheduleReconnect(name, config) {
1837
+ if (this.reconnectTimeouts.has(name)) return;
1838
+ const timeout = setTimeout(async () => {
1839
+ this.reconnectTimeouts.delete(name);
1840
+ this.log(`Attempting to reconnect to ${name}...`);
1841
+ try {
1842
+ await this.connectClient(config);
1843
+ } catch (error) {
1844
+ this.log(`Reconnection failed for ${name}`);
1845
+ }
1846
+ }, this.options.reconnectDelay);
1847
+ this.reconnectTimeouts.set(name, timeout);
1848
+ }
1849
+ /**
1850
+ * Call a tool on the appropriate MCP client
1851
+ */
1852
+ async callTool(toolName, args, options = {}) {
1853
+ const timeout = options.timeout ?? this.options.toolTimeout;
1854
+ let clientInfo;
1855
+ let actualToolName = toolName;
1856
+ if (options.namespace) {
1857
+ clientInfo = this.clients.get(options.namespace);
1858
+ if (!clientInfo) {
1859
+ throw new Error(`Namespace '${options.namespace}' not found`);
1860
+ }
1861
+ const prefix = `${options.namespace}-`;
1862
+ if (actualToolName.startsWith(prefix)) {
1863
+ actualToolName = actualToolName.slice(prefix.length);
1864
+ }
1865
+ if (!clientInfo.tools.some((t) => t.name === actualToolName)) {
1866
+ throw new Error(`Tool '${actualToolName}' not found in namespace '${options.namespace}'`);
1867
+ }
1868
+ } else {
1869
+ for (const [, info] of this.clients) {
1870
+ const tool = info.tools.find((t) => t.name === toolName);
1871
+ if (tool) {
1872
+ clientInfo = info;
1873
+ break;
1874
+ }
1875
+ }
1876
+ }
1877
+ if (!clientInfo) {
1878
+ throw new Error(`Tool '${toolName}' not found on any connected MCP server`);
1879
+ }
1880
+ const requestId = `req_${++this.requestId}`;
1881
+ clientInfo.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
1882
+ try {
1883
+ this.log(`[${requestId}] Calling '${actualToolName}' on '${clientInfo.name}'`);
1884
+ const result = await Promise.race([
1885
+ clientInfo.client.request(
1886
+ {
1887
+ method: "tools/call",
1888
+ params: {
1889
+ name: actualToolName,
1890
+ arguments: args
1891
+ }
1892
+ },
1893
+ import_types.CallToolResultSchema
1894
+ ),
1895
+ new Promise(
1896
+ (_, reject) => setTimeout(() => reject(new Error(`Tool call timeout after ${timeout}ms`)), timeout)
1897
+ )
1898
+ ]);
1899
+ this.log(`[${requestId}] Completed successfully`);
1900
+ return this.formatToolResult(result);
1901
+ } catch (error) {
1902
+ this.log(`[${requestId}] Failed:`, error instanceof Error ? error.message : error);
1903
+ if (error instanceof import_types.McpError) {
1904
+ throw new Error(`MCP Error (${toolName}): ${error.message} (code: ${error.code})`);
1905
+ }
1906
+ throw error;
1907
+ }
1908
+ }
1909
+ /**
1910
+ * Format MCP tool result for consistent output
1911
+ */
1912
+ formatToolResult(result) {
1913
+ if (!result || !result.content) {
1914
+ return "";
1915
+ }
1916
+ return result.content.map((item) => {
1917
+ if (item.type === "text") {
1918
+ return item.text || "";
1919
+ } else if (item.type === "resource") {
1920
+ return `[Resource: ${item.resource?.uri || "unknown"}]`;
1921
+ } else if (item.type === "image") {
1922
+ return `[Image: ${item.mimeType || "unknown"}]`;
1923
+ } else if (item.type === "audio") {
1924
+ return `[Audio: ${item.mimeType || "unknown"}]`;
1925
+ } else {
1926
+ return JSON.stringify(item);
1927
+ }
1928
+ }).join("\n");
1929
+ }
1930
+ /**
1931
+ * Get all tools from all connected MCP clients
1932
+ * Optionally with namespace prefix
1933
+ */
1934
+ getAllTools(options = {}) {
1935
+ const allTools = [];
1936
+ for (const [clientName, client] of this.clients) {
1937
+ for (const tool of client.tools) {
1938
+ if (options.namespacePrefix) {
1939
+ allTools.push({
1940
+ ...tool,
1941
+ name: `${clientName}-${tool.name}`
1942
+ });
1943
+ } else {
1944
+ allTools.push(tool);
1945
+ }
1946
+ }
1947
+ }
1948
+ return allTools;
1949
+ }
1950
+ /**
1951
+ * Get tools from a specific client
1952
+ */
1953
+ getClientTools(clientName) {
1954
+ const client = this.clients.get(clientName);
1955
+ return client?.tools || [];
1956
+ }
1957
+ /**
1958
+ * Get list of connected MCP clients
1959
+ */
1960
+ listClients() {
1961
+ return Array.from(this.clients.entries()).map(([name, info]) => ({
1962
+ name,
1963
+ status: info.status,
1964
+ toolCount: info.tools.length,
1965
+ connectedAt: info.connectedAt,
1966
+ lastUsed: info.lastUsed,
1967
+ transportType: info.transportType
1968
+ }));
1969
+ }
1970
+ /**
1971
+ * Get client info by name
1972
+ */
1973
+ getClient(name) {
1974
+ return this.clients.get(name);
1975
+ }
1976
+ /**
1977
+ * Refresh tool lists from all connected clients
1978
+ */
1979
+ async refreshTools() {
1980
+ for (const [name, client] of this.clients) {
1981
+ try {
1982
+ const toolsResult = await client.client.request(
1983
+ {
1984
+ method: "tools/list",
1985
+ params: {}
1986
+ },
1987
+ import_types.ListToolsResultSchema
1988
+ );
1989
+ client.tools = toolsResult.tools.map((tool) => ({
1990
+ name: tool.name,
1991
+ description: tool.description || "",
1992
+ inputSchema: tool.inputSchema,
1993
+ serverName: name
1994
+ }));
1995
+ this.log(`Refreshed ${name}: ${client.tools.length} tools`);
1996
+ } catch (error) {
1997
+ this.log(`Failed to refresh tools for ${name}:`, error);
1998
+ client.status = "error";
1999
+ client.error = error instanceof Error ? error.message : String(error);
2000
+ }
2001
+ }
2002
+ }
2003
+ /**
2004
+ * List resources from a specific client or all clients
2005
+ */
2006
+ async listResources(clientName) {
2007
+ const results = [];
2008
+ const clients = clientName ? [clientName] : Array.from(this.clients.keys());
2009
+ for (const name of clients) {
2010
+ const client = this.clients.get(name);
2011
+ if (!client) continue;
2012
+ try {
2013
+ const result = await client.client.request(
2014
+ {
2015
+ method: "resources/list",
2016
+ params: {}
2017
+ },
2018
+ import_types.ListResourcesResultSchema
2019
+ );
2020
+ results.push({
2021
+ clientName: name,
2022
+ resources: result.resources
2023
+ });
2024
+ } catch (error) {
2025
+ this.log(`Failed to list resources for ${name}:`, error);
2026
+ }
2027
+ }
2028
+ return results;
2029
+ }
2030
+ /**
2031
+ * Read a resource from a specific client
2032
+ */
2033
+ async readResource(uri, clientName) {
2034
+ if (clientName) {
2035
+ const client = this.clients.get(clientName);
2036
+ if (!client) {
2037
+ throw new Error(`Client '${clientName}' not found`);
2038
+ }
2039
+ const result = await client.client.request(
2040
+ {
2041
+ method: "resources/read",
2042
+ params: { uri }
2043
+ },
2044
+ import_types.ReadResourceResultSchema
2045
+ );
2046
+ return result;
2047
+ } else {
2048
+ for (const [name, client] of this.clients) {
2049
+ try {
2050
+ const result = await client.client.request(
2051
+ {
2052
+ method: "resources/read",
2053
+ params: { uri }
2054
+ },
2055
+ import_types.ReadResourceResultSchema
2056
+ );
2057
+ return { clientName: name, ...result };
2058
+ } catch {
2059
+ }
2060
+ }
2061
+ throw new Error(`Resource '${uri}' not found on any client`);
2062
+ }
2063
+ }
2064
+ /**
2065
+ * List prompts from a specific client or all clients
2066
+ */
2067
+ async listPrompts(clientName) {
2068
+ const results = [];
2069
+ const clients = clientName ? [clientName] : Array.from(this.clients.keys());
2070
+ for (const name of clients) {
2071
+ const client = this.clients.get(name);
2072
+ if (!client) continue;
2073
+ try {
2074
+ const result = await client.client.request(
2075
+ {
2076
+ method: "prompts/list",
2077
+ params: {}
2078
+ },
2079
+ import_types.ListPromptsResultSchema
2080
+ );
2081
+ results.push({
2082
+ clientName: name,
2083
+ prompts: result.prompts
2084
+ });
2085
+ } catch (error) {
2086
+ this.log(`Failed to list prompts for ${name}:`, error);
2087
+ }
2088
+ }
2089
+ return results;
2090
+ }
2091
+ /**
2092
+ * Get a prompt from a specific client
2093
+ */
2094
+ async getPrompt(name, args, clientName) {
2095
+ if (clientName) {
2096
+ const client = this.clients.get(clientName);
2097
+ if (!client) {
2098
+ throw new Error(`Client '${clientName}' not found`);
2099
+ }
2100
+ const result = await client.client.request(
2101
+ {
2102
+ method: "prompts/get",
2103
+ params: { name, arguments: args }
2104
+ },
2105
+ import_types.GetPromptResultSchema
2106
+ );
2107
+ return result;
2108
+ } else {
2109
+ for (const [cName, client] of this.clients) {
2110
+ try {
2111
+ const result = await client.client.request(
2112
+ {
2113
+ method: "prompts/get",
2114
+ params: { name, arguments: args }
2115
+ },
2116
+ import_types.GetPromptResultSchema
2117
+ );
2118
+ return { clientName: cName, ...result };
2119
+ } catch {
2120
+ }
2121
+ }
2122
+ throw new Error(`Prompt '${name}' not found on any client`);
2123
+ }
2124
+ }
2125
+ /**
2126
+ * Health check for all connected clients
2127
+ */
2128
+ async healthCheck() {
2129
+ const results = /* @__PURE__ */ new Map();
2130
+ for (const [name, client] of this.clients) {
2131
+ try {
2132
+ const startTime = Date.now();
2133
+ await client.client.request(
2134
+ {
2135
+ method: "tools/list",
2136
+ params: {}
2137
+ },
2138
+ import_types.ListToolsResultSchema
2139
+ );
2140
+ results.set(name, {
2141
+ status: "healthy",
2142
+ responseTime: Date.now() - startTime
2143
+ });
2144
+ } catch (error) {
2145
+ results.set(name, {
2146
+ status: "unhealthy",
2147
+ responseTime: 0,
2148
+ error: error instanceof Error ? error.message : String(error)
2149
+ });
2150
+ if (this.options.autoReconnect) {
2151
+ this.scheduleReconnect(name, client.config);
2152
+ }
2153
+ }
2154
+ }
2155
+ return results;
2156
+ }
2157
+ /**
2158
+ * Start the automatic refresh timer
2159
+ */
2160
+ startRefreshTimer() {
2161
+ if (this.refreshTimer) {
2162
+ clearInterval(this.refreshTimer);
2163
+ }
2164
+ if (this.options.refreshInterval > 0) {
2165
+ this.refreshTimer = setInterval(async () => {
2166
+ try {
2167
+ await this.refreshTools();
2168
+ } catch (error) {
2169
+ this.log("Auto-refresh failed:", error);
2170
+ }
2171
+ }, this.options.refreshInterval);
2172
+ }
2173
+ }
2174
+ /**
2175
+ * Print available tools for a client
2176
+ */
2177
+ printAvailableTools(clientName, tools) {
2178
+ if (tools.length === 0) {
2179
+ this.log(` No tools available from ${clientName}`);
2180
+ return;
2181
+ }
2182
+ this.log(`
2183
+ --- ${clientName} Tools (${tools.length}) ---`);
2184
+ for (const tool of tools) {
2185
+ this.log(` \u2022 ${tool.name}: ${tool.description.slice(0, 60)}${tool.description.length > 60 ? "..." : ""}`);
2186
+ }
2187
+ }
2188
+ /**
2189
+ * Debug logging
2190
+ */
2191
+ log(...args) {
2192
+ if (this.options.debug) {
2193
+ console.log("[MCP-Proxy]", ...args);
2194
+ }
2195
+ }
2196
+ /**
2197
+ * Get statistics about the MCP proxy hub
2198
+ */
2199
+ getStats() {
2200
+ const clients = Array.from(this.clients.entries()).map(([name, info]) => ({
2201
+ name,
2202
+ toolCount: info.tools.length,
2203
+ status: info.status,
2204
+ transportType: info.transportType
2205
+ }));
2206
+ return {
2207
+ totalClients: this.clients.size,
2208
+ totalTools: clients.reduce((sum, c) => sum + c.toolCount, 0),
2209
+ clients
2210
+ };
2211
+ }
2212
+ };
2213
+
2214
+ // src/daemon/index.ts
2215
+ var RainfallDaemon = class {
2216
+ wss;
2217
+ openaiApp;
2218
+ rainfall;
2219
+ port;
2220
+ openaiPort;
2221
+ rainfallConfig;
2222
+ tools = [];
2223
+ toolSchemas = /* @__PURE__ */ new Map();
2224
+ clients = /* @__PURE__ */ new Set();
2225
+ debug;
2226
+ // New services
2227
+ networkedExecutor;
2228
+ context;
2229
+ listeners;
2230
+ mcpProxy;
2231
+ enableMcpProxy;
2232
+ mcpNamespacePrefix;
2233
+ constructor(config = {}) {
2234
+ this.port = config.port || 8765;
2235
+ this.openaiPort = config.openaiPort || 8787;
2236
+ this.rainfallConfig = config.rainfallConfig;
2237
+ this.debug = config.debug || false;
2238
+ this.enableMcpProxy = config.enableMcpProxy ?? true;
2239
+ this.mcpNamespacePrefix = config.mcpNamespacePrefix ?? true;
2240
+ this.openaiApp = (0, import_express.default)();
2241
+ this.openaiApp.use(import_express.default.json());
2242
+ }
2243
+ async start() {
2244
+ this.log("\u{1F327}\uFE0F Rainfall Daemon starting...");
2245
+ await this.initializeRainfall();
2246
+ if (!this.rainfall) {
2247
+ throw new Error("Failed to initialize Rainfall SDK");
2248
+ }
2249
+ this.context = new RainfallDaemonContext(this.rainfall, {
2250
+ maxLocalMemories: 1e3,
2251
+ maxMessageHistory: 100,
2252
+ ...this.rainfallConfig
2253
+ });
2254
+ await this.context.initialize();
2255
+ this.networkedExecutor = new RainfallNetworkedExecutor(this.rainfall, {
2256
+ wsPort: this.port,
2257
+ httpPort: this.openaiPort,
2258
+ hostname: process.env.HOSTNAME || "local-daemon",
2259
+ capabilities: {
2260
+ localExec: true,
2261
+ fileWatch: true,
2262
+ passiveListen: true
2263
+ }
2264
+ });
2265
+ await this.networkedExecutor.registerEdgeNode();
2266
+ await this.networkedExecutor.subscribeToResults((jobId, result, error) => {
2267
+ this.log(`\u{1F4EC} Job ${jobId} ${error ? "failed" : "completed"}`, error || result);
2268
+ });
2269
+ this.listeners = new RainfallListenerRegistry(
2270
+ this.rainfall,
2271
+ this.context,
2272
+ this.networkedExecutor
2273
+ );
2274
+ await this.loadTools();
2275
+ if (this.enableMcpProxy) {
2276
+ this.mcpProxy = new MCPProxyHub({ debug: this.debug });
2277
+ await this.mcpProxy.initialize();
2278
+ if (this.rainfallConfig?.mcpClients) {
2279
+ for (const clientConfig of this.rainfallConfig.mcpClients) {
2280
+ try {
2281
+ await this.mcpProxy.connectClient(clientConfig);
2282
+ } catch (error) {
2283
+ this.log(`Failed to connect MCP client ${clientConfig.name}:`, error);
2284
+ }
2285
+ }
2286
+ }
2287
+ }
2288
+ await this.startWebSocketServer();
2289
+ await this.startOpenAIProxy();
2290
+ console.log(`\u{1F680} Rainfall daemon running`);
2291
+ console.log(` WebSocket (MCP): ws://localhost:${this.port}`);
2292
+ console.log(` OpenAI API: http://localhost:${this.openaiPort}/v1/chat/completions`);
2293
+ console.log(` Health Check: http://localhost:${this.openaiPort}/health`);
2294
+ console.log(` Edge Node ID: ${this.networkedExecutor.getEdgeNodeId() || "local"}`);
2295
+ console.log(` Tools loaded: ${this.tools.length}`);
2296
+ console.log(` Press Ctrl+C to stop`);
2297
+ process.on("SIGINT", () => this.stop());
2298
+ process.on("SIGTERM", () => this.stop());
2299
+ }
2300
+ async stop() {
2301
+ this.log("\u{1F6D1} Shutting down Rainfall daemon...");
2302
+ if (this.listeners) {
2303
+ await this.listeners.stopAll();
2304
+ }
2305
+ if (this.networkedExecutor) {
2306
+ await this.networkedExecutor.unregisterEdgeNode();
2307
+ }
2308
+ if (this.mcpProxy) {
2309
+ await this.mcpProxy.shutdown();
2310
+ this.mcpProxy = void 0;
2311
+ }
2312
+ for (const client of this.clients) {
2313
+ client.close();
2314
+ }
2315
+ this.clients.clear();
2316
+ if (this.wss) {
2317
+ this.wss.close();
2318
+ this.wss = void 0;
2319
+ }
2320
+ console.log("\u{1F44B} Rainfall daemon stopped");
2321
+ }
2322
+ /**
2323
+ * Get the networked executor for distributed job management
2324
+ */
2325
+ getNetworkedExecutor() {
2326
+ return this.networkedExecutor;
2327
+ }
2328
+ /**
2329
+ * Get the context for memory/session management
2330
+ */
2331
+ getContext() {
2332
+ return this.context;
2333
+ }
2334
+ /**
2335
+ * Get the listener registry for passive triggers
2336
+ */
2337
+ getListenerRegistry() {
2338
+ return this.listeners;
2339
+ }
2340
+ /**
2341
+ * Get the MCP Proxy Hub for managing external MCP clients
2342
+ */
2343
+ getMCPProxy() {
2344
+ return this.mcpProxy;
2345
+ }
2346
+ /**
2347
+ * Connect an MCP client dynamically
2348
+ */
2349
+ async connectMCPClient(config) {
2350
+ if (!this.mcpProxy) {
2351
+ throw new Error("MCP Proxy Hub is not enabled");
2352
+ }
2353
+ return this.mcpProxy.connectClient(config);
2354
+ }
2355
+ /**
2356
+ * Disconnect an MCP client
2357
+ */
2358
+ async disconnectMCPClient(name) {
2359
+ if (!this.mcpProxy) {
2360
+ throw new Error("MCP Proxy Hub is not enabled");
2361
+ }
2362
+ return this.mcpProxy.disconnectClient(name);
2363
+ }
2364
+ async initializeRainfall() {
2365
+ if (this.rainfallConfig?.apiKey) {
2366
+ this.rainfall = new Rainfall(this.rainfallConfig);
2367
+ } else {
2368
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2369
+ const config = loadConfig2();
2370
+ if (config.apiKey) {
2371
+ this.rainfall = new Rainfall({
2372
+ apiKey: config.apiKey,
2373
+ baseUrl: config.baseUrl
2374
+ });
2375
+ } else {
2376
+ throw new Error("No API key configured. Run: rainfall auth login <api-key>");
2377
+ }
2378
+ }
2379
+ }
2380
+ async loadTools() {
2381
+ if (!this.rainfall) return;
2382
+ try {
2383
+ this.tools = await this.rainfall.listTools();
2384
+ this.log(`\u{1F4E6} Loaded ${this.tools.length} tools`);
2385
+ } catch (error) {
2386
+ console.warn("\u26A0\uFE0F Failed to load tools:", error instanceof Error ? error.message : error);
2387
+ this.tools = [];
2388
+ }
2389
+ }
2390
+ async getToolSchema(toolId) {
2391
+ if (this.toolSchemas.has(toolId)) {
2392
+ return this.toolSchemas.get(toolId);
2393
+ }
2394
+ if (!this.rainfall) return null;
2395
+ try {
2396
+ const schema = await this.rainfall.getToolSchema(toolId);
2397
+ this.toolSchemas.set(toolId, schema);
2398
+ return schema;
2399
+ } catch {
2400
+ return null;
2401
+ }
2402
+ }
2403
+ async startWebSocketServer() {
2404
+ this.wss = new import_ws2.WebSocketServer({ port: this.port });
2405
+ this.wss.on("connection", (ws) => {
2406
+ this.log("\u{1F7E2} MCP client connected");
2407
+ this.clients.add(ws);
2408
+ ws.on("message", async (data) => {
2409
+ try {
2410
+ const message = JSON.parse(data.toString());
2411
+ const response = await this.handleMCPMessage(message);
2412
+ ws.send(JSON.stringify(response));
2413
+ } catch (error) {
2414
+ const errorResponse = {
2415
+ jsonrpc: "2.0",
2416
+ id: void 0,
2417
+ error: {
2418
+ code: -32700,
2419
+ message: error instanceof Error ? error.message : "Parse error"
2420
+ }
2421
+ };
2422
+ ws.send(JSON.stringify(errorResponse));
2423
+ }
2424
+ });
2425
+ ws.on("close", () => {
2426
+ this.log("\u{1F534} MCP client disconnected");
2427
+ this.clients.delete(ws);
2428
+ });
2429
+ ws.on("error", (error) => {
2430
+ console.error("WebSocket error:", error);
2431
+ this.clients.delete(ws);
2432
+ });
2433
+ });
2434
+ }
2435
+ async handleMCPMessage(message) {
2436
+ const { id, method, params } = message;
2437
+ switch (method) {
2438
+ case "initialize":
2439
+ return {
2440
+ jsonrpc: "2.0",
2441
+ id,
2442
+ result: {
2443
+ protocolVersion: "2024-11-05",
2444
+ capabilities: {
2445
+ tools: { listChanged: true }
2446
+ },
2447
+ serverInfo: {
2448
+ name: "rainfall-daemon",
2449
+ version: "0.1.0"
2450
+ }
2451
+ }
2452
+ };
2453
+ case "tools/list":
2454
+ return {
2455
+ jsonrpc: "2.0",
2456
+ id,
2457
+ result: {
2458
+ tools: await this.getMCPTools()
2459
+ }
2460
+ };
2461
+ case "tools/call": {
2462
+ const toolName = params?.name;
2463
+ const toolParams = params?.arguments;
2464
+ try {
2465
+ const startTime = Date.now();
2466
+ const result = await this.executeToolWithMCP(toolName, toolParams);
2467
+ const duration = Date.now() - startTime;
2468
+ if (this.context) {
2469
+ this.context.recordExecution(toolName, toolParams || {}, result, { duration });
2470
+ }
2471
+ return {
2472
+ jsonrpc: "2.0",
2473
+ id,
2474
+ result: {
2475
+ content: [
2476
+ {
2477
+ type: "text",
2478
+ text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
2479
+ }
2480
+ ]
2481
+ }
2482
+ };
2483
+ } catch (error) {
2484
+ const errorMessage = error instanceof Error ? error.message : "Tool execution failed";
2485
+ if (this.context) {
2486
+ this.context.recordExecution(toolName, toolParams || {}, null, {
2487
+ error: errorMessage,
2488
+ duration: 0
2489
+ });
2490
+ }
2491
+ return {
2492
+ jsonrpc: "2.0",
2493
+ id,
2494
+ error: {
2495
+ code: -32603,
2496
+ message: errorMessage
2497
+ }
2498
+ };
2499
+ }
2500
+ }
2501
+ case "ping":
2502
+ return {
2503
+ jsonrpc: "2.0",
2504
+ id,
2505
+ result: {}
2506
+ };
2507
+ default:
2508
+ return {
2509
+ jsonrpc: "2.0",
2510
+ id,
2511
+ error: {
2512
+ code: -32601,
2513
+ message: `Method not found: ${method}`
2514
+ }
2515
+ };
2516
+ }
2517
+ }
2518
+ async getMCPTools() {
2519
+ const mcpTools = [];
2520
+ for (const tool of this.tools) {
2521
+ const schema = await this.getToolSchema(tool.id);
2522
+ if (schema) {
2523
+ const toolSchema = schema;
2524
+ mcpTools.push({
2525
+ name: tool.id,
2526
+ description: toolSchema.description || tool.description,
2527
+ inputSchema: toolSchema.parameters || { type: "object", properties: {} }
2528
+ });
2529
+ }
2530
+ }
2531
+ if (this.mcpProxy) {
2532
+ const proxyTools = this.mcpProxy.getAllTools({ namespacePrefix: this.mcpNamespacePrefix });
2533
+ for (const tool of proxyTools) {
2534
+ mcpTools.push({
2535
+ name: this.mcpNamespacePrefix ? `${tool.serverName}-${tool.name}` : tool.name,
2536
+ description: tool.description,
2537
+ inputSchema: tool.inputSchema || { type: "object", properties: {} }
2538
+ });
2539
+ }
2540
+ }
2541
+ return mcpTools;
2542
+ }
2543
+ async executeTool(toolId, params) {
2544
+ if (!this.rainfall) {
2545
+ throw new Error("Rainfall SDK not initialized");
2546
+ }
2547
+ return this.rainfall.executeTool(toolId, params);
2548
+ }
2549
+ /**
2550
+ * Execute a tool, trying MCP proxy first, then falling back to Rainfall tools
2551
+ */
2552
+ async executeToolWithMCP(toolName, params) {
2553
+ if (this.mcpProxy) {
2554
+ try {
2555
+ if (this.mcpNamespacePrefix && toolName.includes("-")) {
2556
+ const namespace = toolName.split("-")[0];
2557
+ const actualToolName = toolName.slice(namespace.length + 1);
2558
+ if (this.mcpProxy.getClient(namespace)) {
2559
+ return await this.mcpProxy.callTool(toolName, params || {}, {
2560
+ namespace
2561
+ });
2562
+ }
2563
+ }
2564
+ return await this.mcpProxy.callTool(toolName, params || {});
2565
+ } catch (error) {
2566
+ if (error instanceof Error && !error.message.includes("not found")) {
2567
+ throw error;
2568
+ }
2569
+ }
2570
+ }
2571
+ return this.executeTool(toolName, params);
2572
+ }
2573
+ async startOpenAIProxy() {
2574
+ this.openaiApp.get("/v1/models", async (_req, res) => {
2575
+ try {
2576
+ if (this.rainfall) {
2577
+ const models = await this.rainfall.listModels();
2578
+ res.json({
2579
+ object: "list",
2580
+ data: models.map((m) => ({
2581
+ id: m.id,
2582
+ object: "model",
2583
+ created: Math.floor(Date.now() / 1e3),
2584
+ owned_by: "rainfall"
2585
+ }))
2586
+ });
2587
+ } else {
2588
+ res.json({
2589
+ object: "list",
2590
+ data: [
2591
+ { id: "llama-3.3-70b-versatile", object: "model", created: Date.now(), owned_by: "groq" },
2592
+ { id: "gpt-4o", object: "model", created: Date.now(), owned_by: "openai" },
2593
+ { id: "claude-3-5-sonnet", object: "model", created: Date.now(), owned_by: "anthropic" },
2594
+ { id: "gemini-2.0-flash-exp", object: "model", created: Date.now(), owned_by: "gemini" }
2595
+ ]
2596
+ });
2597
+ }
2598
+ } catch (error) {
2599
+ res.status(500).json({ error: "Failed to fetch models" });
2600
+ }
2601
+ });
2602
+ this.openaiApp.post("/v1/chat/completions", async (req, res) => {
2603
+ const body = req.body;
2604
+ if (!body.messages || !Array.isArray(body.messages)) {
2605
+ res.status(400).json({
2606
+ error: {
2607
+ message: "Missing required field: messages",
2608
+ type: "invalid_request_error"
2609
+ }
2610
+ });
2611
+ return;
2612
+ }
2613
+ if (!this.rainfall) {
2614
+ res.status(503).json({
2615
+ error: {
2616
+ message: "Rainfall SDK not initialized",
2617
+ type: "service_unavailable"
2618
+ }
2619
+ });
2620
+ return;
2621
+ }
2622
+ try {
2623
+ const me = await this.rainfall.getMe();
2624
+ const subscriberId = me.id;
2625
+ const localToolMap = await this.buildLocalToolMap();
2626
+ let allTools = [];
2627
+ if (body.tools && body.tools.length > 0) {
2628
+ allTools = body.tools;
2629
+ } else if (body.tool_choice) {
2630
+ const openaiTools = await this.getOpenAITools();
2631
+ allTools = openaiTools;
2632
+ }
2633
+ let messages = [...body.messages];
2634
+ const maxToolIterations = 10;
2635
+ let toolIterations = 0;
2636
+ while (toolIterations < maxToolIterations) {
2637
+ toolIterations++;
2638
+ const llmResponse = await this.callLLM({
2639
+ subscriberId,
2640
+ model: body.model,
2641
+ messages,
2642
+ tools: allTools.length > 0 ? allTools : void 0,
2643
+ tool_choice: body.tool_choice,
2644
+ temperature: body.temperature,
2645
+ max_tokens: body.max_tokens,
2646
+ stream: false,
2647
+ // Always non-streaming for tool loop
2648
+ tool_priority: body.tool_priority,
2649
+ enable_stacked: body.enable_stacked
2650
+ });
2651
+ const choice = llmResponse.choices?.[0];
2652
+ let toolCalls = choice?.message?.tool_calls || [];
2653
+ const content = choice?.message?.content || "";
2654
+ const reasoningContent = choice?.message?.reasoning_content || "";
2655
+ const fullContent = content + " " + reasoningContent;
2656
+ const xmlToolCalls = this.parseXMLToolCalls(fullContent);
2657
+ if (xmlToolCalls.length > 0) {
2658
+ this.log(`\u{1F4CB} Parsed ${xmlToolCalls.length} XML tool calls from content`);
2659
+ toolCalls = xmlToolCalls;
2660
+ }
2661
+ if (!toolCalls || toolCalls.length === 0) {
2662
+ if (body.stream) {
2663
+ await this.streamResponse(res, llmResponse);
2664
+ } else {
2665
+ res.json(llmResponse);
2666
+ }
2667
+ this.updateContext(body.messages, llmResponse);
2668
+ return;
2669
+ }
2670
+ messages.push({
2671
+ role: "assistant",
2672
+ content: choice?.message?.content || "",
2673
+ tool_calls: toolCalls
2674
+ });
2675
+ for (const toolCall of toolCalls) {
2676
+ const toolName = toolCall.function?.name;
2677
+ const toolArgsStr = toolCall.function?.arguments || "{}";
2678
+ if (!toolName) continue;
2679
+ this.log(`\u{1F527} Tool call: ${toolName}`);
2680
+ let toolResult;
2681
+ let toolError;
2682
+ try {
2683
+ const localTool = this.findLocalTool(toolName, localToolMap);
2684
+ if (localTool) {
2685
+ this.log(` \u2192 Executing locally`);
2686
+ const args = JSON.parse(toolArgsStr);
2687
+ toolResult = await this.executeLocalTool(localTool.id, args);
2688
+ } else if (this.mcpProxy) {
2689
+ this.log(` \u2192 Trying MCP proxy`);
2690
+ const args = JSON.parse(toolArgsStr);
2691
+ toolResult = await this.executeToolWithMCP(toolName.replace(/_/g, "-"), args);
2692
+ } else {
2693
+ const shouldExecuteLocal = body.tool_priority === "local" || body.tool_priority === "stacked";
2694
+ if (shouldExecuteLocal) {
2695
+ try {
2696
+ const args = JSON.parse(toolArgsStr);
2697
+ toolResult = await this.rainfall.executeTool(toolName.replace(/_/g, "-"), args);
2698
+ } catch {
2699
+ toolResult = { _pending: true, tool: toolName, args: toolArgsStr };
2700
+ }
2701
+ } else {
2702
+ toolResult = { _pending: true, tool: toolName, args: toolArgsStr };
2703
+ }
2704
+ }
2705
+ } catch (error) {
2706
+ toolError = error instanceof Error ? error.message : String(error);
2707
+ this.log(` \u2192 Error: ${toolError}`);
2708
+ }
2709
+ messages.push({
2710
+ role: "tool",
2711
+ content: toolError ? JSON.stringify({ error: toolError }) : typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult),
2712
+ tool_call_id: toolCall.id
2713
+ });
2714
+ if (this.context) {
2715
+ this.context.recordExecution(
2716
+ toolName,
2717
+ JSON.parse(toolArgsStr || "{}"),
2718
+ toolResult,
2719
+ { error: toolError, duration: 0 }
2720
+ );
2721
+ }
2722
+ }
2723
+ }
2724
+ res.status(500).json({
2725
+ error: {
2726
+ message: "Maximum tool execution iterations reached",
2727
+ type: "tool_execution_error"
2728
+ }
2729
+ });
2730
+ } catch (error) {
2731
+ this.log("Chat completions error:", error);
2732
+ res.status(500).json({
2733
+ error: {
2734
+ message: error instanceof Error ? error.message : "Internal server error",
2735
+ type: "internal_error"
2736
+ }
2737
+ });
2738
+ }
2739
+ });
2740
+ this.openaiApp.get("/health", (_req, res) => {
2741
+ const mcpStats = this.mcpProxy?.getStats();
2742
+ res.json({
2743
+ status: "ok",
2744
+ daemon: "rainfall",
2745
+ version: "0.2.0",
2746
+ tools_loaded: this.tools.length,
2747
+ mcp_clients: mcpStats?.totalClients || 0,
2748
+ mcp_tools: mcpStats?.totalTools || 0,
2749
+ edge_node_id: this.networkedExecutor?.getEdgeNodeId(),
2750
+ clients_connected: this.clients.size
2751
+ });
2752
+ });
2753
+ this.openaiApp.get("/v1/mcp/clients", (_req, res) => {
2754
+ if (!this.mcpProxy) {
2755
+ res.status(503).json({ error: "MCP proxy not enabled" });
2756
+ return;
2757
+ }
2758
+ res.json(this.mcpProxy.listClients());
2759
+ });
2760
+ this.openaiApp.post("/v1/mcp/connect", async (req, res) => {
2761
+ if (!this.mcpProxy) {
2762
+ res.status(503).json({ error: "MCP proxy not enabled" });
2763
+ return;
2764
+ }
2765
+ try {
2766
+ const name = await this.mcpProxy.connectClient(req.body);
2767
+ res.json({ success: true, client: name });
2768
+ } catch (error) {
2769
+ res.status(500).json({
2770
+ error: error instanceof Error ? error.message : "Failed to connect MCP client"
2771
+ });
2772
+ }
2773
+ });
2774
+ this.openaiApp.post("/v1/mcp/disconnect", async (req, res) => {
2775
+ if (!this.mcpProxy) {
2776
+ res.status(503).json({ error: "MCP proxy not enabled" });
2777
+ return;
2778
+ }
2779
+ const { name } = req.body;
2780
+ if (!name) {
2781
+ res.status(400).json({ error: "Missing required field: name" });
2782
+ return;
2783
+ }
2784
+ await this.mcpProxy.disconnectClient(name);
2785
+ res.json({ success: true });
2786
+ });
2787
+ this.openaiApp.get("/status", (_req, res) => {
2788
+ res.json(this.getStatus());
2789
+ });
2790
+ this.openaiApp.post("/v1/queue", async (req, res) => {
2791
+ const { tool_id, params, execution_mode = "any" } = req.body;
2792
+ if (!tool_id) {
2793
+ res.status(400).json({ error: "Missing required field: tool_id" });
2794
+ return;
2795
+ }
2796
+ if (!this.networkedExecutor) {
2797
+ res.status(503).json({ error: "Networked executor not available" });
2798
+ return;
2799
+ }
2800
+ try {
2801
+ const jobId = await this.networkedExecutor.queueToolExecution(
2802
+ tool_id,
2803
+ params || {},
2804
+ { executionMode: execution_mode }
2805
+ );
2806
+ res.json({ job_id: jobId, status: "queued" });
2807
+ } catch (error) {
2808
+ res.status(500).json({
2809
+ error: error instanceof Error ? error.message : "Failed to queue job"
2810
+ });
2811
+ }
2812
+ });
2813
+ return new Promise((resolve) => {
2814
+ this.openaiApp.listen(this.openaiPort, () => {
2815
+ resolve();
2816
+ });
2817
+ });
2818
+ }
2819
+ /**
2820
+ * Build a map of local Rainfall tools for quick lookup
2821
+ * Maps OpenAI-style underscore names to Rainfall tool IDs
2822
+ */
2823
+ async buildLocalToolMap() {
2824
+ const map = /* @__PURE__ */ new Map();
2825
+ for (const tool of this.tools) {
2826
+ const openAiName = tool.id.replace(/-/g, "_");
2827
+ map.set(openAiName, {
2828
+ id: tool.id,
2829
+ name: openAiName,
2830
+ description: tool.description
2831
+ });
2832
+ map.set(tool.id, {
2833
+ id: tool.id,
2834
+ name: openAiName,
2835
+ description: tool.description
2836
+ });
2837
+ }
2838
+ return map;
2839
+ }
2840
+ /**
2841
+ * Find a local Rainfall tool by name (OpenAI underscore format or original)
2842
+ */
2843
+ findLocalTool(toolName, localToolMap) {
2844
+ if (localToolMap.has(toolName)) {
2845
+ return localToolMap.get(toolName);
2846
+ }
2847
+ const dashedName = toolName.replace(/_/g, "-");
2848
+ if (localToolMap.has(dashedName)) {
2849
+ return localToolMap.get(dashedName);
2850
+ }
2851
+ return void 0;
2852
+ }
2853
+ /**
2854
+ * Execute a local Rainfall tool
2855
+ */
2856
+ async executeLocalTool(toolId, args) {
2857
+ if (!this.rainfall) {
2858
+ throw new Error("Rainfall SDK not initialized");
2859
+ }
2860
+ const startTime = Date.now();
2861
+ try {
2862
+ const result = await this.rainfall.executeTool(toolId, args);
2863
+ const duration = Date.now() - startTime;
2864
+ this.log(` \u2713 Completed in ${duration}ms`);
2865
+ return result;
2866
+ } catch (error) {
2867
+ const duration = Date.now() - startTime;
2868
+ this.log(` \u2717 Failed after ${duration}ms`);
2869
+ throw error;
2870
+ }
2871
+ }
2872
+ /**
2873
+ * Parse XML-style tool calls from model output
2874
+ * Handles formats like: <function=name><parameter=key>value</parameter></function>
2875
+ */
2876
+ parseXMLToolCalls(content) {
2877
+ const toolCalls = [];
2878
+ const functionRegex = /<function=([^>]+)>([\s\S]*?)<\/function>/gi;
2879
+ let match;
2880
+ while ((match = functionRegex.exec(content)) !== null) {
2881
+ const functionName = match[1].trim();
2882
+ const paramsBlock = match[2];
2883
+ const params = {};
2884
+ const paramRegex = /<parameter=([^>]+)>([\s\S]*?)<\/parameter>/gi;
2885
+ let paramMatch;
2886
+ while ((paramMatch = paramRegex.exec(paramsBlock)) !== null) {
2887
+ const paramName = paramMatch[1].trim();
2888
+ const paramValue = paramMatch[2].trim();
2889
+ params[paramName] = paramValue;
2890
+ }
2891
+ toolCalls.push({
2892
+ id: `xml-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
2893
+ type: "function",
2894
+ function: {
2895
+ name: functionName,
2896
+ arguments: JSON.stringify(params)
2897
+ }
2898
+ });
2899
+ this.log(`\u{1F4CB} Parsed XML tool call: ${functionName}(${JSON.stringify(params)})`);
2900
+ }
2901
+ return toolCalls;
2902
+ }
2903
+ /**
2904
+ * Call the LLM via Rainfall backend, LM Studio, RunPod, or other providers
2905
+ *
2906
+ * Provider priority:
2907
+ * 1. Config file (llm.provider, llm.baseUrl)
2908
+ * 2. Environment variables (OPENAI_API_KEY, OLLAMA_HOST, etc.)
2909
+ * 3. Default to Rainfall (credits-based)
2910
+ */
2911
+ async callLLM(params) {
2912
+ if (!this.rainfall) {
2913
+ throw new Error("Rainfall SDK not initialized");
2914
+ }
2915
+ const { loadConfig: loadConfig2, getProviderBaseUrl: getProviderBaseUrl2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2916
+ const config = loadConfig2();
2917
+ const provider = config.llm?.provider || "rainfall";
2918
+ switch (provider) {
2919
+ case "local":
2920
+ case "ollama":
2921
+ case "custom":
2922
+ return this.callLocalLLM(params, config);
2923
+ case "openai":
2924
+ case "anthropic":
2925
+ return this.callExternalLLM(params, config, provider);
2926
+ case "rainfall":
2927
+ default:
2928
+ return this.rainfall.chatCompletions({
2929
+ subscriber_id: params.subscriberId,
2930
+ model: params.model,
2931
+ messages: params.messages,
2932
+ stream: params.stream || false,
2933
+ temperature: params.temperature,
2934
+ max_tokens: params.max_tokens,
2935
+ tools: params.tools,
2936
+ tool_choice: params.tool_choice,
2937
+ tool_priority: params.tool_priority,
2938
+ enable_stacked: params.enable_stacked
2939
+ });
2940
+ }
2941
+ }
2942
+ /**
2943
+ * Call external LLM provider (OpenAI, Anthropic) via their OpenAI-compatible APIs
2944
+ */
2945
+ async callExternalLLM(params, config, provider) {
2946
+ const { getProviderBaseUrl: getProviderBaseUrl2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2947
+ const baseUrl = config.llm?.baseUrl || getProviderBaseUrl2({ llm: { provider } });
2948
+ const apiKey = config.llm?.apiKey;
2949
+ if (!apiKey) {
2950
+ throw new Error(`${provider} API key not configured. Set via: rainfall config set llm.apiKey <key>`);
2951
+ }
2952
+ const model = params.model || config.llm?.model || (provider === "anthropic" ? "claude-3-5-sonnet-20241022" : "gpt-4o");
2953
+ const url = `${baseUrl}/chat/completions`;
2954
+ const response = await fetch(url, {
2955
+ method: "POST",
2956
+ headers: {
2957
+ "Content-Type": "application/json",
2958
+ "Authorization": `Bearer ${apiKey}`,
2959
+ "User-Agent": "Rainfall-DevKit/1.0"
2960
+ },
2961
+ body: JSON.stringify({
2962
+ model,
2963
+ messages: params.messages,
2964
+ tools: params.tools,
2965
+ tool_choice: params.tool_choice,
2966
+ temperature: params.temperature,
2967
+ max_tokens: params.max_tokens,
2968
+ stream: false
2969
+ // Tool loop requires non-streaming
2970
+ })
2971
+ });
2972
+ if (!response.ok) {
2973
+ const error = await response.text();
2974
+ throw new Error(`${provider} API error: ${error}`);
2975
+ }
2976
+ return response.json();
2977
+ }
2978
+ /**
2979
+ * Call a local LLM (LM Studio, Ollama, etc.)
2980
+ */
2981
+ async callLocalLLM(params, config) {
2982
+ const baseUrl = config.llm?.baseUrl || "http://localhost:1234/v1";
2983
+ const apiKey = config.llm?.apiKey || "not-needed";
2984
+ const model = params.model || config.llm?.model || "local-model";
2985
+ const url = `${baseUrl}/chat/completions`;
2986
+ const response = await fetch(url, {
2987
+ method: "POST",
2988
+ headers: {
2989
+ "Content-Type": "application/json",
2990
+ "Authorization": `Bearer ${apiKey}`,
2991
+ "User-Agent": "Rainfall-DevKit/1.0"
2992
+ },
2993
+ body: JSON.stringify({
2994
+ model,
2995
+ messages: params.messages,
2996
+ tools: params.tools,
2997
+ tool_choice: params.tool_choice,
2998
+ temperature: params.temperature,
2999
+ max_tokens: params.max_tokens,
3000
+ stream: false
3001
+ // Tool loop requires non-streaming
3002
+ })
3003
+ });
3004
+ if (!response.ok) {
3005
+ const error = await response.text();
3006
+ throw new Error(`Local LLM error: ${error}`);
3007
+ }
3008
+ return response.json();
3009
+ }
3010
+ /**
3011
+ * Stream a response to the client (converts non-streaming to SSE format)
3012
+ */
3013
+ async streamResponse(res, response) {
3014
+ res.setHeader("Content-Type", "text/event-stream");
3015
+ res.setHeader("Cache-Control", "no-cache");
3016
+ res.setHeader("Connection", "keep-alive");
3017
+ const message = response.choices?.[0]?.message;
3018
+ const id = response.id || `chatcmpl-${Date.now()}`;
3019
+ const model = response.model || "unknown";
3020
+ const created = Math.floor(Date.now() / 1e3);
3021
+ res.write(`data: ${JSON.stringify({
3022
+ id,
3023
+ object: "chat.completion.chunk",
3024
+ created,
3025
+ model,
3026
+ choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }]
3027
+ })}
3028
+
3029
+ `);
3030
+ const content = message?.content || "";
3031
+ const chunkSize = 10;
3032
+ for (let i = 0; i < content.length; i += chunkSize) {
3033
+ const chunk = content.slice(i, i + chunkSize);
3034
+ res.write(`data: ${JSON.stringify({
3035
+ id,
3036
+ object: "chat.completion.chunk",
3037
+ created,
3038
+ model,
3039
+ choices: [{ index: 0, delta: { content: chunk }, finish_reason: null }]
3040
+ })}
3041
+
3042
+ `);
3043
+ }
3044
+ res.write(`data: ${JSON.stringify({
3045
+ id,
3046
+ object: "chat.completion.chunk",
3047
+ created,
3048
+ model,
3049
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
3050
+ })}
3051
+
3052
+ `);
3053
+ res.write("data: [DONE]\n\n");
3054
+ res.end();
3055
+ }
3056
+ /**
3057
+ * Update context with conversation history
3058
+ */
3059
+ updateContext(originalMessages, response) {
3060
+ if (!this.context) return;
3061
+ const lastUserMessage = originalMessages.filter((m) => m.role === "user").pop();
3062
+ if (lastUserMessage) {
3063
+ this.context.addMessage("user", lastUserMessage.content);
3064
+ }
3065
+ const assistantContent = response.choices?.[0]?.message?.content;
3066
+ if (assistantContent) {
3067
+ this.context.addMessage("assistant", assistantContent);
3068
+ }
3069
+ }
3070
+ async getOpenAITools() {
3071
+ const tools = [];
3072
+ for (const tool of this.tools.slice(0, 100)) {
3073
+ const schema = await this.getToolSchema(tool.id);
3074
+ if (schema) {
3075
+ const toolSchema = schema;
3076
+ let parameters = { type: "object", properties: {}, required: [] };
3077
+ if (toolSchema.parameters && typeof toolSchema.parameters === "object") {
3078
+ const rawParams = toolSchema.parameters;
3079
+ parameters = {
3080
+ type: rawParams.type || "object",
3081
+ properties: rawParams.properties || {},
3082
+ required: rawParams.required || []
3083
+ };
3084
+ }
3085
+ tools.push({
3086
+ type: "function",
3087
+ function: {
3088
+ name: tool.id.replace(/-/g, "_"),
3089
+ // OpenAI requires underscore names
3090
+ description: toolSchema.description || tool.description,
3091
+ parameters
3092
+ }
3093
+ });
3094
+ }
3095
+ }
3096
+ if (this.mcpProxy) {
3097
+ const proxyTools = this.mcpProxy.getAllTools({ namespacePrefix: this.mcpNamespacePrefix });
3098
+ for (const tool of proxyTools.slice(0, 28)) {
3099
+ const inputSchema = tool.inputSchema || {};
3100
+ tools.push({
3101
+ type: "function",
3102
+ function: {
3103
+ name: this.mcpNamespacePrefix ? `${tool.serverName}_${tool.name}`.replace(/-/g, "_") : tool.name.replace(/-/g, "_"),
3104
+ description: `[${tool.serverName}] ${tool.description}`,
3105
+ parameters: {
3106
+ type: "object",
3107
+ properties: inputSchema.properties || {},
3108
+ required: inputSchema.required || []
3109
+ }
3110
+ }
3111
+ });
3112
+ }
3113
+ }
3114
+ return tools;
3115
+ }
3116
+ buildResponseContent() {
3117
+ const edgeNodeId = this.networkedExecutor?.getEdgeNodeId();
3118
+ const toolCount = this.tools.length;
3119
+ return `Rainfall daemon online. Edge node: ${edgeNodeId || "local"}. ${toolCount} tools available. What would you like to execute locally or in the cloud?`;
3120
+ }
3121
+ getStatus() {
3122
+ return {
3123
+ running: !!this.wss,
3124
+ port: this.port,
3125
+ openaiPort: this.openaiPort,
3126
+ toolsLoaded: this.tools.length,
3127
+ clientsConnected: this.clients.size,
3128
+ edgeNodeId: this.networkedExecutor?.getEdgeNodeId(),
3129
+ context: this.context?.getStatus() || {
3130
+ memoriesCached: 0,
3131
+ activeSessions: 0,
3132
+ executionHistorySize: 0
3133
+ },
3134
+ listeners: this.listeners?.getStatus() || {
3135
+ fileWatchers: 0,
3136
+ cronTriggers: 0,
3137
+ recentEvents: 0
3138
+ }
3139
+ };
3140
+ }
3141
+ log(...args) {
3142
+ if (this.debug) {
3143
+ console.log(...args);
3144
+ }
3145
+ }
3146
+ };
3147
+ var daemonInstance = null;
3148
+ async function startDaemon(config = {}) {
3149
+ if (daemonInstance) {
3150
+ console.log("Daemon already running");
3151
+ return daemonInstance;
3152
+ }
3153
+ daemonInstance = new RainfallDaemon(config);
3154
+ await daemonInstance.start();
3155
+ return daemonInstance;
3156
+ }
3157
+ async function stopDaemon() {
3158
+ if (!daemonInstance) {
3159
+ console.log("Daemon not running");
3160
+ return;
3161
+ }
3162
+ await daemonInstance.stop();
3163
+ daemonInstance = null;
3164
+ }
3165
+ function getDaemonStatus() {
3166
+ if (!daemonInstance) {
3167
+ return null;
3168
+ }
3169
+ return daemonInstance.getStatus();
3170
+ }
3171
+ function getDaemonInstance() {
3172
+ return daemonInstance;
3173
+ }
3174
+ // Annotate the CommonJS export names for ESM import in node:
3175
+ 0 && (module.exports = {
3176
+ MCPProxyHub,
3177
+ RainfallDaemon,
3178
+ getDaemonInstance,
3179
+ getDaemonStatus,
3180
+ startDaemon,
3181
+ stopDaemon
3182
+ });