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