tune-sdk 0.2.23 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/tune.js +13 -12
- package/package.json +11 -3
- package/src/cli.js +126 -258
- package/src/contextws.js +201 -0
- package/src/ws.js +123 -0
package/README.md
CHANGED
|
@@ -6,8 +6,7 @@ Tune is a handy [extension for Visual Studio Code and](https://marketplace.visua
|
|
|
6
6
|
With tune [javascript sdk](https://www.npmjs.com/package/tune-sdk) you can make apps and agents.
|
|
7
7
|
|
|
8
8
|
## Demo
|
|
9
|
-
|
|
10
|
-
</video>
|
|
9
|
+
[](https://asciinema.org/a/757894)
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
## Setup
|
|
@@ -137,6 +136,8 @@ image generated
|
|
|
137
136
|
# install tune globally
|
|
138
137
|
npm install -g tune-sdk
|
|
139
138
|
|
|
139
|
+
tune "hi how are you?"
|
|
140
|
+
|
|
140
141
|
# append user message to newchat.chat run and save
|
|
141
142
|
tune --user "hi how are you?" --filename newchat.chat --save
|
|
142
143
|
|
|
@@ -144,8 +145,10 @@ tune --user "hi how are you?" --filename newchat.chat --save
|
|
|
144
145
|
# print result to console
|
|
145
146
|
tune --system "You are Groot" --user "Hi how are you?"
|
|
146
147
|
|
|
147
|
-
#set context variable
|
|
148
|
-
tune --set
|
|
148
|
+
# set context variable
|
|
149
|
+
tune --set test="hello" --user "@test" --system "You are echo you print everythting back"
|
|
150
|
+
# prints hello
|
|
151
|
+
|
|
149
152
|
```
|
|
150
153
|
|
|
151
154
|
|
package/dist/tune.js
CHANGED
|
@@ -2282,7 +2282,7 @@ function text2run(text, ctx, opts) {
|
|
|
2282
2282
|
}
|
|
2283
2283
|
text2run;
|
|
2284
2284
|
async function file2run(args, params, ctx) {
|
|
2285
|
-
var lctx, text, stop, errors, turnsSaved, longFormatRegex, isLong, initialText,
|
|
2285
|
+
var lctx, text, stop, errors, turnsSaved, node, longFormatRegex, isLong, initialText, response, res, r, chunk, itergMKZ8fG, _ref;
|
|
2286
2286
|
var lctx;
|
|
2287
2287
|
lctx = ctx.clone();
|
|
2288
2288
|
if (params) lctx.ms.unshift(envmd(params));
|
|
@@ -2290,10 +2290,12 @@ async function file2run(args, params, ctx) {
|
|
|
2290
2290
|
var stop;
|
|
2291
2291
|
var errors;
|
|
2292
2292
|
var turnsSaved;
|
|
2293
|
+
var node;
|
|
2293
2294
|
text = args.text;
|
|
2294
2295
|
stop = (((typeof args !== "undefined") && (args !== null) && !Number.isNaN(args) && (typeof args.stop !== "undefined") && (args.stop !== null) && !Number.isNaN(args.stop)) ? args.stop : (((typeof "assistant" !== "undefined") && ("assistant" !== null) && !Number.isNaN("assistant")) ? "assistant" : undefined));
|
|
2295
2296
|
errors = (((typeof args !== "undefined") && (args !== null) && !Number.isNaN(args) && (typeof args.errors !== "undefined") && (args.errors !== null) && !Number.isNaN(args.errors)) ? args.errors : (((typeof "throw" !== "undefined") && ("throw" !== null) && !Number.isNaN("throw")) ? "throw" : undefined));
|
|
2296
2297
|
turnsSaved = 0;
|
|
2298
|
+
node = null;
|
|
2297
2299
|
var longFormatRegex;
|
|
2298
2300
|
var isLong;
|
|
2299
2301
|
longFormatRegex = /^(system|user|tool_call|tool_result|assistant|error):/;
|
|
@@ -2314,22 +2316,21 @@ async function file2run(args, params, ctx) {
|
|
|
2314
2316
|
initialText = (args.system ? tpl("system:\n{system}", args) : "");
|
|
2315
2317
|
if (args.filename) {
|
|
2316
2318
|
node = await ctx.resolve(args.filename);
|
|
2317
|
-
if (node) lctx.stack.push(node);
|
|
2318
2319
|
if ((node && !text)) text = await node.read();
|
|
2319
2320
|
}
|
|
2320
2321
|
if ((!text && args.system)) text = initialText;
|
|
2321
2322
|
text = text || "";
|
|
2323
|
+
if (args.user) text += ((text ? "\n" : "") + tpl("user:\n{user}", args));
|
|
2322
2324
|
node = node || {
|
|
2323
|
-
type: "text"
|
|
2324
|
-
read: (async function() {
|
|
2325
|
-
return text;
|
|
2326
|
-
})
|
|
2325
|
+
type: "text"
|
|
2327
2326
|
}
|
|
2328
2327
|
node.name = args.filename;
|
|
2329
2328
|
node.fullname = args.filename;
|
|
2330
2329
|
node.mimetype = "text/chat";
|
|
2330
|
+
node.read = (async function() {
|
|
2331
|
+
return text;
|
|
2332
|
+
});
|
|
2331
2333
|
lctx.stack.push(node);
|
|
2332
|
-
if (args.user) text += ((text ? "\n" : "") + tpl("user:\n{user}", args));
|
|
2333
2334
|
if (!text) throw new TuneError("ether 'text' or 'system' or 'user' should be specified or 'filename' should exist ");
|
|
2334
2335
|
isLong = longFormatRegex.test(text);
|
|
2335
2336
|
var response;
|
|
@@ -2373,7 +2374,7 @@ async function file2run(args, params, ctx) {
|
|
|
2373
2374
|
hookTurnEnd: save
|
|
2374
2375
|
});
|
|
2375
2376
|
chunk = {};
|
|
2376
|
-
|
|
2377
|
+
itergMKZ8fG = new AsyncIter();
|
|
2377
2378
|
(async function($lastRes) {
|
|
2378
2379
|
var _ref;
|
|
2379
2380
|
try {
|
|
@@ -2381,20 +2382,20 @@ async function file2run(args, params, ctx) {
|
|
|
2381
2382
|
chunk = await r.next();
|
|
2382
2383
|
res = (chunk.value || "");
|
|
2383
2384
|
$lastRes = transformOutput(res) || $lastRes;
|
|
2384
|
-
|
|
2385
|
+
itergMKZ8fG.result = {
|
|
2385
2386
|
value: $lastRes
|
|
2386
2387
|
}
|
|
2387
2388
|
}
|
|
2388
|
-
_ref =
|
|
2389
|
+
_ref = itergMKZ8fG.result = {
|
|
2389
2390
|
value: $lastRes,
|
|
2390
2391
|
done: true
|
|
2391
2392
|
}
|
|
2392
2393
|
} catch (e) {
|
|
2393
|
-
_ref = (
|
|
2394
|
+
_ref = (itergMKZ8fG.err = e);
|
|
2394
2395
|
}
|
|
2395
2396
|
return _ref;
|
|
2396
2397
|
})();
|
|
2397
|
-
_ref =
|
|
2398
|
+
_ref = itergMKZ8fG;
|
|
2398
2399
|
}
|
|
2399
2400
|
return _ref;
|
|
2400
2401
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tune-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "tune - LLM chat in text file",
|
|
5
5
|
"main": "dist/tune.js",
|
|
6
6
|
"module": "dist/tune.mjs",
|
|
7
|
-
"bin": {
|
|
7
|
+
"bin": {
|
|
8
8
|
"tune": "bin/cli.js",
|
|
9
|
-
"tune-sdk": "bin/cli.js"
|
|
9
|
+
"tune-sdk": "bin/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
},
|
|
19
19
|
"./rpc": {
|
|
20
20
|
"require": "./src/rpc.js"
|
|
21
|
+
},
|
|
22
|
+
"./contextws": {
|
|
23
|
+
"require": "./src/contextws.js"
|
|
21
24
|
}
|
|
22
25
|
},
|
|
23
26
|
"keywords": [
|
|
@@ -34,5 +37,10 @@
|
|
|
34
37
|
"scripts": {
|
|
35
38
|
"test": "node test/index.js",
|
|
36
39
|
"test:watch": "node --watch test/index.js"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^14.0.3",
|
|
43
|
+
"mime-types": "^3.0.2",
|
|
44
|
+
"ws": "^8.20.0"
|
|
37
45
|
}
|
|
38
46
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const { Command, Option } = require("commander");
|
|
2
|
+
const tune = require("../dist/tune.js");
|
|
3
|
+
const ws = require("./ws.js");
|
|
4
|
+
const rpc = require("./rpc.js");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const cp = require("child_process");
|
|
9
|
+
const stream = require("stream");
|
|
4
10
|
|
|
5
11
|
// tune app - run web server from current directory serving index.html and making it availble to call ctx via websocket
|
|
6
12
|
// tune ps - list of executing agents or the ones finished
|
|
@@ -10,195 +16,15 @@ assert = require("assert");
|
|
|
10
16
|
// tune - execute call/file and quit
|
|
11
17
|
// tune rpc - run rpc server
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var _i;
|
|
16
|
-
var params = 2 <= arguments.length ? [].slice.call(arguments, 1, _i = arguments.length - 0) : (_i = 1, []);
|
|
17
|
-
return (function(paramIndex, params) {
|
|
18
|
-
var _ref;
|
|
19
|
-
try {
|
|
20
|
-
_ref = str.replace(/{(\W*)(\w*)(\W*)}/gm, (function(_, pre, name, post) {
|
|
21
|
-
return (function(res) {
|
|
22
|
-
paramIndex += 1;
|
|
23
|
-
return ((typeof res !== 'undefined') ? ((pre || "") + res + (post || "")) : "");
|
|
24
|
-
})(params[name || paramIndex]);
|
|
25
|
-
}));
|
|
26
|
-
} catch (e) {
|
|
27
|
-
_ref = console.log.apply(console, [].concat([e, str]).concat(params));
|
|
28
|
-
}
|
|
29
|
-
return _ref;
|
|
30
|
-
})(0, (((typeof params[0] === "object") && (params.length === 1)) ? params[0] : params));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function showHelp() {
|
|
34
|
-
console.log("TUNE-CLI - Command Line Interface for Tune");
|
|
35
|
-
console.log("");
|
|
36
|
-
console.log("USAGE:");
|
|
37
|
-
console.log(" tune [cmd] [OPTIONS]");
|
|
38
|
-
console.log("");
|
|
39
|
-
console.log("COMMANDS:");
|
|
40
|
-
console.log(" rpc Start RPC server mode");
|
|
41
|
-
console.log(" init Initialize Tune config directory");
|
|
42
|
-
console.log("");
|
|
43
|
-
console.log("EXAMPLES:");
|
|
44
|
-
console.log(" # Quick chat with system prompt");
|
|
45
|
-
console.log(" tune --system \"You are Groot\" --user \"Hi how are you?\"");
|
|
46
|
-
console.log("");
|
|
47
|
-
console.log(" # Continue existing chat");
|
|
48
|
-
console.log(" tune --user \"continue the conversation\" --filename chat.chat --save");
|
|
49
|
-
console.log("");
|
|
50
|
-
console.log(" # Set context variables");
|
|
51
|
-
console.log(" tune --set-test=hello --user \"@test\" --system \"Echo assistant\"");
|
|
52
|
-
console.log("");
|
|
53
|
-
console.log(" # RPC mode for editor integration");
|
|
54
|
-
console.log(" tune rpc");
|
|
55
|
-
console.log("");
|
|
56
|
-
console.log(" # Initialize or reinitialize config directory");
|
|
57
|
-
console.log(" tune init --force");
|
|
58
|
-
console.log("");
|
|
59
|
-
console.log("OPTIONS:");
|
|
60
|
-
console.log(" --user <text> User message to send");
|
|
61
|
-
console.log(" --system <text> System prompt to use");
|
|
62
|
-
console.log(" --filename <file> Chat file to load/save");
|
|
63
|
-
console.log(" --save Save conversation to file");
|
|
64
|
-
console.log(" --stop <mode> Stop condition: assistant|step|<custom>");
|
|
65
|
-
console.log(" --text <content> chat content");
|
|
66
|
-
console.log(" --response <type> Response format: content|json|messages|chat (default: content)");
|
|
67
|
-
console.log(" --set-<name>=<value> Set context parameter");
|
|
68
|
-
console.log(" --path <paths> Additional search paths (colon-separated)");
|
|
69
|
-
console.log(" --home <dir> Tune config directory (default: ~/.tune)");
|
|
70
|
-
console.log(" --debug Enable debug output");
|
|
71
|
-
console.log(" --silent Suppress output");
|
|
72
|
-
console.log(" --force Force config initialization (with 'init')");
|
|
73
|
-
console.log(" --help Show this help");
|
|
74
|
-
console.log(" --version Show CLI version");
|
|
75
|
-
return console.log("");
|
|
76
|
-
}
|
|
77
|
-
showHelp;
|
|
78
|
-
|
|
79
|
-
function validateArgs(args) {
|
|
80
|
-
assert(!!args && (typeof args === "object"), "Arguments must be an object");
|
|
81
|
-
if (args.user) assert(typeof args.user === "string", "--user must be a string");
|
|
82
|
-
if (args.system) assert(typeof args.system === "string", "--system must be a string");
|
|
83
|
-
if (args.filename) assert(typeof args.filename === "string", "--filename must be a string");
|
|
84
|
-
if (args.text) assert(typeof args.text === "string", "--text must be a string");
|
|
85
|
-
if (args.response) assert(typeof args.response === "string", "--response must be a string");
|
|
86
|
-
if (args.stop) assert(typeof args.stop === "string", "--stop must be a string");
|
|
87
|
-
if (args.path) assert(typeof args.path === "string", "--path must be a string");
|
|
88
|
-
if (args.home) assert(typeof args.home === "string", "--home must be a string");
|
|
89
|
-
if (!!args.save) assert(typeof args.save === "boolean", "--save must be a boolean");
|
|
90
|
-
if (!!args.debug) assert(typeof args.debug === "boolean" || typeof args.debug === "string", "--debug must be a boolean");
|
|
91
|
-
if (!!args.silent) assert(typeof args.silent === "boolean", "--silent must be a boolean");
|
|
92
|
-
if (!!args.force) assert(typeof args.force === "boolean", "--force must be a boolean");
|
|
93
|
-
if (typeof args.rpc !== "undefined") assert(false, "Use 'tune rpc' instead of --rpc");
|
|
94
|
-
if (typeof args.forceInit !== "undefined") assert(false, "Use 'tune init --force' instead of --force-init");
|
|
95
|
-
if (args.params) assert(!!args.params && (typeof args.params === "object"), "--set-* parameters must form a valid object");
|
|
96
|
-
if ((args.stop && (typeof args.stop === "string"))) assert((args.stop === "assistant") || (args.stop === "step") || (args.stop.length > 0), "--stop must be 'assistant', 'step', or a non-empty custom string");
|
|
97
|
-
if (args.cmd) {
|
|
98
|
-
assert(typeof args.cmd === "string", "Command must be a string");
|
|
99
|
-
assert((args.cmd === "rpc") || (args.cmd === "init"), "Unknown command: " + args.cmd);
|
|
100
|
-
}
|
|
101
|
-
if ((!args.help && !args.version && !args.cmd && !args.user && !args.filename && !args.text)) assert(false, "Must specify --user, --filename, a command (rpc|init), --version, or --help");
|
|
102
|
-
return args;
|
|
103
|
-
}
|
|
104
|
-
validateArgs;
|
|
105
|
-
|
|
106
|
-
function parseArgs(args) {
|
|
107
|
-
var curKey, res, res1, key, value, stop, _ref, _len;
|
|
108
|
-
assert(Array.isArray(args), "parseArgs expects an array of arguments");
|
|
109
|
-
var curKey;
|
|
110
|
-
curKey = null;
|
|
111
|
-
var res;
|
|
112
|
-
res = args.reduce((function(memo, arg) {
|
|
113
|
-
var key, value, _ref, _i;
|
|
114
|
-
assert(typeof arg === "string", "Each argument must be a string");
|
|
115
|
-
if (arg.startsWith("--")) {
|
|
116
|
-
_ref = arg.substring(2)
|
|
117
|
-
.split("=");
|
|
118
|
-
key = _ref[0];
|
|
119
|
-
value = _ref[1];
|
|
120
|
-
assert((typeof key === "string") && (key.length > 0), "Argument key must be a non-empty string");
|
|
121
|
-
if (!!value) {
|
|
122
|
-
memo[key] = value;
|
|
123
|
-
curKey = null;
|
|
124
|
-
} else {
|
|
125
|
-
curKey = key;
|
|
126
|
-
memo[key] = true;
|
|
127
|
-
}
|
|
128
|
-
} else if (curKey) {
|
|
129
|
-
memo[curKey] = arg;
|
|
130
|
-
curKey = null;
|
|
131
|
-
} else {
|
|
132
|
-
if (!memo.__cmd) {
|
|
133
|
-
memo.__cmd = arg;
|
|
134
|
-
} else {
|
|
135
|
-
assert(false, "Only a single positional command is allowed");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return memo;
|
|
139
|
-
}), {});
|
|
140
|
-
assert(!!res && (typeof res === "object"), "Parsed arguments must form an object");
|
|
141
|
-
var res1;
|
|
142
|
-
res1 = {};
|
|
143
|
-
_ref = res;
|
|
144
|
-
for (key in _ref) {
|
|
145
|
-
value = _ref[key];
|
|
146
|
-
assert(typeof key === "string", "Argument keys must be strings");
|
|
147
|
-
if (key === "__cmd") {
|
|
148
|
-
res1.cmd = value;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (key.startsWith("set-")) {
|
|
152
|
-
res1.params = res1.params || {}
|
|
153
|
-
assert(key.substr(4).length > 0, "Set parameter name cannot be empty");
|
|
154
|
-
res1.params[key.substr(4)] = value;
|
|
155
|
-
} else {
|
|
156
|
-
res1[key] = value;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if ((res1.h || res1.help)) res1.help = true;
|
|
160
|
-
if ((res1.v || res1.version)) res1.version = true;
|
|
161
|
-
stop = res1.stop;
|
|
162
|
-
if ((!!stop && (stop !== "step" && stop !== "assistant"))) {
|
|
163
|
-
assert(typeof stop === "string", "Custom stop condition must be a string");
|
|
164
|
-
assert(stop.length > 0, "Custom stop condition cannot be empty");
|
|
165
|
-
res1.stop = (function(msgs) {
|
|
166
|
-
var lastMsg;
|
|
167
|
-
assert(Array.isArray(msgs), "Messages must be an array");
|
|
168
|
-
if (!msgs.length) return false;
|
|
169
|
-
var lastMsg;
|
|
170
|
-
lastMsg = msgs["slice"](-1)[0];
|
|
171
|
-
assert(!!lastMsg && (typeof lastMsg === "object"), "Last message must be an object");
|
|
172
|
-
if (!lastMsg.content) return false;
|
|
173
|
-
assert(typeof lastMsg.content === "string", "Message content must be a string");
|
|
174
|
-
return (-1 !== lastMsg.content.indexOf(stop));
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
return res1;
|
|
178
|
-
}
|
|
179
|
-
parseArgs;
|
|
180
|
-
tune = require("../dist/tune.js");
|
|
181
|
-
rpc = require("../src/rpc.js");
|
|
182
|
-
path = require("path");
|
|
183
|
-
fs = require("fs");
|
|
184
|
-
os = require("os");
|
|
185
|
-
cp = require("child_process");
|
|
186
|
-
stream = require("stream");
|
|
187
|
-
|
|
188
|
-
function getHomedir(args) {
|
|
189
|
-
assert(!!args && (typeof args === "object"), "getHomedir expects args to be an object");
|
|
190
|
-
if (args.home) assert(typeof args.home === "string", "args.home must be a string");
|
|
191
|
-
return path.resolve(path.normalize((args.home || process.env.TUNE_HOME || "~/.tune")
|
|
19
|
+
function getHomedir(home) {
|
|
20
|
+
return path.resolve(path.normalize((home || process.env.TUNE_HOME || "~/.tune")
|
|
192
21
|
.replace("~", os.homedir())));
|
|
193
22
|
}
|
|
194
|
-
|
|
195
|
-
async function initConfig(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
homedir = getHomedir(args);
|
|
200
|
-
assert(typeof homedir === "string", "Home directory must be a string");
|
|
201
|
-
if ((!args.force && fs.existsSync(homedir))) return;
|
|
23
|
+
|
|
24
|
+
async function initConfig({ home, force }) {
|
|
25
|
+
let stdout, stderr, _i;
|
|
26
|
+
const homedir = getHomedir(home);
|
|
27
|
+
if (!force && fs.existsSync(homedir)) return;
|
|
202
28
|
console.error("[tune] initialize " + homedir);
|
|
203
29
|
fs.mkdirSync(homedir, {
|
|
204
30
|
recursive: true
|
|
@@ -207,11 +33,10 @@ async function initConfig(args) {
|
|
|
207
33
|
fs.cpSync(path.resolve(__dirname, "../config"), path.resolve(homedir), { recursive: true });
|
|
208
34
|
console.error("[tune] installing npm");
|
|
209
35
|
try {
|
|
210
|
-
|
|
36
|
+
stdout = cp.execSync("npm i", {
|
|
211
37
|
cwd: homedir,
|
|
212
38
|
encoding: "utf8"
|
|
213
39
|
});
|
|
214
|
-
stdout = _ref;
|
|
215
40
|
if (stdout.trim()) console.error("[tune]", stdout.trim());
|
|
216
41
|
//stderr.trim() ? console.error("[tune]", stderr.trim()) : undefined;
|
|
217
42
|
} catch (err) {
|
|
@@ -220,7 +45,7 @@ async function initConfig(args) {
|
|
|
220
45
|
console.error("[tune] done");
|
|
221
46
|
console.error(`[tune] edit ${homedir}/.env and add OPENAI_KEY and other keys, change ${homedir}/default.ctx.js to customize tune`);
|
|
222
47
|
}
|
|
223
|
-
|
|
48
|
+
|
|
224
49
|
async function suggest(params, ctx) {
|
|
225
50
|
var node, _ref;
|
|
226
51
|
var node;
|
|
@@ -247,7 +72,7 @@ async function suggest(params, ctx) {
|
|
|
247
72
|
}
|
|
248
73
|
}));
|
|
249
74
|
}
|
|
250
|
-
|
|
75
|
+
|
|
251
76
|
async function remoteContext(name, params) {
|
|
252
77
|
var server, node;
|
|
253
78
|
var server;
|
|
@@ -275,20 +100,20 @@ async function remoteContext(name, params) {
|
|
|
275
100
|
});
|
|
276
101
|
return node;
|
|
277
102
|
}
|
|
278
|
-
|
|
279
|
-
async function runRpc(
|
|
103
|
+
|
|
104
|
+
async function runRpc({ debug, home, path }) {
|
|
280
105
|
var inStream, outStream, debugStream, ctx, server;
|
|
281
106
|
inStream = stream.Readable.toWeb(process.stdin);
|
|
282
107
|
outStream = stream.Writable.toWeb(process.stdout);
|
|
283
|
-
debugStream = ((typeof
|
|
108
|
+
debugStream = ((typeof debug === "string") ? fs.createWriteStream(debug, {
|
|
284
109
|
flags: "a"
|
|
285
110
|
}) : undefined);
|
|
286
|
-
ctx = await initContext(
|
|
111
|
+
ctx = await initContext({ home, path });
|
|
287
112
|
let cleanCtx = ctx.clone()
|
|
288
113
|
server = rpc.jsonrpc({
|
|
289
114
|
inStream: inStream,
|
|
290
115
|
outStream: outStream,
|
|
291
|
-
debug: ((typeof
|
|
116
|
+
debug: ((typeof debug === "string") ? (function() {
|
|
292
117
|
var _i;
|
|
293
118
|
var args = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
|
|
294
119
|
return debugStream.write(args.join(" ") + "\n", "utf8");
|
|
@@ -346,20 +171,13 @@ async function runRpc(args) {
|
|
|
346
171
|
// console.log("node", node)
|
|
347
172
|
return null;
|
|
348
173
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
var params;
|
|
356
|
-
params = args.params || {}
|
|
357
|
-
delete args.params;
|
|
358
|
-
var res;
|
|
359
|
-
res = await ctx.file2run({ ...args, errors: "message", stop }, params);
|
|
360
|
-
return (!args.silent ? console.log(res) : undefined);
|
|
174
|
+
async function run({ home, stop, params, silent, user, system, save, text , response, path, filename }) {
|
|
175
|
+
const ctx = await initContext({ home, path });
|
|
176
|
+
const res = await ctx.file2run({ user, system, save, text, response, errors: "message", stop, filename }, params);
|
|
177
|
+
if (!silent) {
|
|
178
|
+
console.log(res)
|
|
179
|
+
}
|
|
361
180
|
}
|
|
362
|
-
run;
|
|
363
181
|
|
|
364
182
|
function flatten(array) {
|
|
365
183
|
return array.reduce((memo, item) => {
|
|
@@ -371,17 +189,17 @@ function flatten(array) {
|
|
|
371
189
|
}, [])
|
|
372
190
|
}
|
|
373
191
|
|
|
374
|
-
async function initContext(
|
|
192
|
+
async function initContext({ home, path: addPaths }) {
|
|
375
193
|
var dirs, pwd, ctx, dir, ctxName, ext, module, m, _i, _ref, _len, _i0, _ref0, _len0;
|
|
376
194
|
var dirs;
|
|
377
195
|
var pwd;
|
|
378
196
|
dirs = [];
|
|
379
197
|
pwd = process.cwd();
|
|
380
|
-
if (
|
|
198
|
+
if (addPaths) dirs = addPaths.split(path.delimiter)
|
|
381
199
|
.map((function(dir) {
|
|
382
200
|
return path.resolve(pwd, dir);
|
|
383
201
|
}));
|
|
384
|
-
dirs.push(getHomedir(
|
|
202
|
+
dirs.push(getHomedir(home));
|
|
385
203
|
dirs.unshift(pwd);
|
|
386
204
|
if (process.env.TUNE_PATH) {
|
|
387
205
|
dirs = dirs.concat(process.env.TUNE_PATH.split(path.delimiter))
|
|
@@ -389,7 +207,7 @@ async function initContext(args) {
|
|
|
389
207
|
process.env.TUNE_PATH = dirs.join(path.delimiter);
|
|
390
208
|
ctx = tune.makeContext({
|
|
391
209
|
TUNE_PATH: process.env.TUNE_PATH,
|
|
392
|
-
TUNE_HOME: getHomedir(
|
|
210
|
+
TUNE_HOME: getHomedir(home)
|
|
393
211
|
});
|
|
394
212
|
_ref = dirs;
|
|
395
213
|
for (_i = 0, _len = _ref.length; _i < _len; ++_i) {
|
|
@@ -422,61 +240,111 @@ async function initContext(args) {
|
|
|
422
240
|
if ((typeof m === "function")) {
|
|
423
241
|
ctx.use(m);
|
|
424
242
|
} else {
|
|
425
|
-
throw Error(
|
|
426
|
-
name: ctxName,
|
|
427
|
-
module: m
|
|
428
|
-
}));
|
|
243
|
+
throw Error(`err: Context file export is not an array of functions or function ${ctxName}: ${m}`);
|
|
429
244
|
}
|
|
430
245
|
}
|
|
431
246
|
} else {
|
|
432
|
-
throw Error(
|
|
433
|
-
name: ctxName,
|
|
434
|
-
module: module
|
|
435
|
-
}));
|
|
247
|
+
throw Error(`err: Context file export is not an array of functions or function ${ctxName}: ${m}`);
|
|
436
248
|
}
|
|
437
249
|
}
|
|
438
250
|
return ctx;
|
|
439
251
|
}
|
|
440
|
-
initContext;
|
|
441
252
|
async function main() {
|
|
442
|
-
|
|
253
|
+
|
|
254
|
+
let version = "0.0.0";
|
|
443
255
|
try {
|
|
444
|
-
var
|
|
445
|
-
|
|
446
|
-
if (args.help) {
|
|
447
|
-
showHelp();
|
|
448
|
-
process.exit(0);
|
|
449
|
-
}
|
|
450
|
-
if (args.version) {
|
|
451
|
-
try {
|
|
452
|
-
var pkg = require(path.resolve(__dirname, "../package.json"));
|
|
453
|
-
console.log(pkg.version || "0.0.0");
|
|
454
|
-
} catch (e) {
|
|
455
|
-
console.log("0.0.0");
|
|
456
|
-
}
|
|
457
|
-
process.exit(0);
|
|
458
|
-
}
|
|
459
|
-
validateArgs(args);
|
|
460
|
-
if (args.cmd === "rpc") {
|
|
461
|
-
await initConfig(args); // ensure config exists if needed
|
|
462
|
-
_ref = await runRpc(args);
|
|
463
|
-
} else if (args.cmd === "init") {
|
|
464
|
-
await initConfig(args);
|
|
465
|
-
_ref = null;
|
|
466
|
-
} else {
|
|
467
|
-
await initConfig(args); // auto-init if missing
|
|
468
|
-
_ref = await run(args);
|
|
469
|
-
}
|
|
256
|
+
var pkg = require(path.resolve(__dirname, "../package.json"));
|
|
257
|
+
version = pkg.version || "0.0.0"
|
|
470
258
|
} catch (e) {
|
|
471
|
-
console.error(e.stack);
|
|
472
|
-
_ref = process.exit(1);
|
|
473
259
|
}
|
|
474
|
-
|
|
260
|
+
|
|
261
|
+
const program = new Command();
|
|
262
|
+
|
|
263
|
+
program
|
|
264
|
+
.name("tune")
|
|
265
|
+
.description("Command Line Interface for Tune")
|
|
266
|
+
.version(version)
|
|
267
|
+
.helpOption(true)
|
|
268
|
+
.option("--home <dir>", "Tune config directory (default: ~/.tune)")
|
|
269
|
+
.option("--path <paths>", "Additional search paths (colon-separated)")
|
|
270
|
+
.addHelpText("after", "\nEXAMPLES:\n tune --system \"You are Groot\" --user \"Hi how are you?\"\n tune --user \"continue the conversation\" --filename chat.chat --save\n tune --set test=hello --user \"@test\" --system \"Echo assistant\"\n tune rpc\n tune init --force\n");
|
|
271
|
+
|
|
272
|
+
program
|
|
273
|
+
.command("gen", { isDefault: true })
|
|
274
|
+
.description("start or continue ai conversation,\ndefault command")
|
|
275
|
+
.argument("[user]", "user message, the same as --user", (value) => value.replace(/\\n/g, "\n"))
|
|
276
|
+
.option("-u, --user <text>", "User message ", (value) => value.replace(/\\n/g, "\n"))
|
|
277
|
+
.option("-s, --system <text>", "System prompt to use", (value) => value.replace(/\\n/g, "\n"))
|
|
278
|
+
.option("-f, --filename <file>", "Chat file to load/save")
|
|
279
|
+
.option("--save", "Save conversation to file")
|
|
280
|
+
.addOption(new Option("--stop <mode>", "Stop condition", "assistant").choices(["assistant", "step"]).default("assistant"))
|
|
281
|
+
.option("--text <content>", "chat file content, overwrites system message", (value) => value.replace(/\\n/g, "\n"))
|
|
282
|
+
.addOption(new Option("-r, --response <type>", "response format").choices(["content", "json", "messages", "chat"]).default("content"))
|
|
283
|
+
.option("--silent", "generate response but do not print it")
|
|
284
|
+
.option("--set [params...]", "set a template variables, --set a=b --set c=d")
|
|
285
|
+
.action(async (user, opts, cmd) => {
|
|
286
|
+
opts = { ...opts, ...cmd.parent.opts() }
|
|
287
|
+
opts.user ||= user
|
|
288
|
+
opts.params = (opts.set || []).reduce((memo, item) => {
|
|
289
|
+
const [key, value] = item.split("=");
|
|
290
|
+
memo[key] = value
|
|
291
|
+
return memo
|
|
292
|
+
}, {})
|
|
293
|
+
// console.log("gen ", opts)
|
|
294
|
+
if (!opts.user && !opts.text && !opts.filename && !opts.system) {
|
|
295
|
+
return cmd.help();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
await initConfig({ home: opts.home });
|
|
299
|
+
await run(opts);
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
program
|
|
303
|
+
.command("rpc")
|
|
304
|
+
.description("Start rpc server mode over stdio")
|
|
305
|
+
.option("-d, --debug [file]", "Enable debug output or write debug output to file")
|
|
306
|
+
.action(async (opts, cmd) => {
|
|
307
|
+
opts = { ...opts, ...cmd.parent.opts() }
|
|
308
|
+
// console.log(opts)
|
|
309
|
+
await initConfig(opts); // ensure config exists if needed
|
|
310
|
+
await runRpc(opts);
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
program
|
|
314
|
+
.command("ws")
|
|
315
|
+
.description("Start static webserver with tune context available browser side")
|
|
316
|
+
.option("-p, --port <port>", "port or socket to listen to", 8080)
|
|
317
|
+
.option("-s, --static", "should it serve static files like index.html etc", true)
|
|
318
|
+
.option("-r, --root <path>", "root directory for server", process.cwd())
|
|
319
|
+
.action( async (opts, cmd) => {
|
|
320
|
+
opts = { ...opts, ...cmd.parent.opts() }
|
|
321
|
+
// console.log(opts)
|
|
322
|
+
const { home, path, static, port, root } = opts
|
|
323
|
+
|
|
324
|
+
await initConfig({ home });
|
|
325
|
+
|
|
326
|
+
const ctx = await initContext({ home, path });
|
|
327
|
+
ws({ port, static, root, ctx})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
program
|
|
331
|
+
.command("init")
|
|
332
|
+
.description("Initialize Tune config directory")
|
|
333
|
+
.option("--force", "Force config initialization (with 'init')")
|
|
334
|
+
.action(async (opts, cmd) => {
|
|
335
|
+
opts = { ...opts, ...cmd.parent.opts() }
|
|
336
|
+
await initConfig(opts);
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
await program.parseAsync(process.argv);
|
|
341
|
+
}catch (e) {
|
|
342
|
+
console.error(e.stack)
|
|
343
|
+
process.exit(1)
|
|
344
|
+
}
|
|
475
345
|
}
|
|
476
|
-
main;
|
|
477
346
|
|
|
478
|
-
tpl;
|
|
479
|
-
exports.parseArgs = parseArgs;
|
|
480
347
|
exports.rpc = rpc;
|
|
481
348
|
exports.main = main;
|
|
482
349
|
exports.run = run;
|
|
350
|
+
exports.ws = ws;
|
package/src/contextws.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// ContextWebsocket: explicit request/response wrapper over WebSocket.
|
|
2
|
+
|
|
3
|
+
function assert(cond, msg) {
|
|
4
|
+
if (!cond) throw new Error(msg);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class StreamController {
|
|
8
|
+
constructor(onClose) {
|
|
9
|
+
this.queue = [];
|
|
10
|
+
this.waiter = null;
|
|
11
|
+
this.closed = false;
|
|
12
|
+
this.err = null;
|
|
13
|
+
this.onClose = onClose;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
push(value, done = false) {
|
|
17
|
+
if (this.closed) return;
|
|
18
|
+
const items = [];
|
|
19
|
+
if (typeof value !== 'undefined') items.push({ value });
|
|
20
|
+
if (done) items.push({ done });
|
|
21
|
+
|
|
22
|
+
if (this.waiter && items.length) {
|
|
23
|
+
const { resolve } = this.waiter;
|
|
24
|
+
this.waiter = null;
|
|
25
|
+
resolve(items.shift());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.queue.push(...items);
|
|
29
|
+
if (done) this.close();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
close() {
|
|
33
|
+
if (this.closed) return;
|
|
34
|
+
this.closed = true;
|
|
35
|
+
if (this.waiter) {
|
|
36
|
+
const { resolve } = this.waiter;
|
|
37
|
+
this.waiter = null;
|
|
38
|
+
resolve({ value: undefined, done: true });
|
|
39
|
+
}
|
|
40
|
+
if (this.onClose) this.onClose();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fail(err) {
|
|
44
|
+
if (this.closed) return;
|
|
45
|
+
this.err = err;
|
|
46
|
+
this.closed = true;
|
|
47
|
+
if (this.waiter) {
|
|
48
|
+
const { reject } = this.waiter;
|
|
49
|
+
this.waiter = null;
|
|
50
|
+
reject(err);
|
|
51
|
+
}
|
|
52
|
+
if (this.onClose) this.onClose();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async next() {
|
|
56
|
+
if (this.err) throw this.err;
|
|
57
|
+
if (this.queue.length) return this.queue.shift();
|
|
58
|
+
if (this.closed) return { value: undefined, done: true };
|
|
59
|
+
|
|
60
|
+
return await new Promise((resolve, reject) => {
|
|
61
|
+
this.waiter = { resolve, reject };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[Symbol.asyncIterator]() {
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function ContextWebsocket(url, { debug } = {}) {
|
|
71
|
+
assert(typeof WebSocket !== 'undefined', 'WebSocket is not available');
|
|
72
|
+
assert(typeof url !== 'undefined', 'url is not set');
|
|
73
|
+
|
|
74
|
+
socket = new WebSocket(url);
|
|
75
|
+
console.log(socket)
|
|
76
|
+
|
|
77
|
+
this.socket = socket;
|
|
78
|
+
this.callbacks = Object.create(null);
|
|
79
|
+
this.iterators = Object.create(null);
|
|
80
|
+
this.pending = [];
|
|
81
|
+
this.debug = typeof debug === 'function' ? debug : () => {};
|
|
82
|
+
this.ready = new Promise((resolve, reject) => {
|
|
83
|
+
if (socket.readyState === WebSocket.OPEN) return resolve();
|
|
84
|
+
socket.addEventListener('open', resolve, { once: true });
|
|
85
|
+
socket.addEventListener('error', reject, { once: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
socket.addEventListener('message', (event) => this._onMessage(event));
|
|
90
|
+
socket.addEventListener('close', () => this._onClose());
|
|
91
|
+
socket.addEventListener('error', () => this._onClose());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
ContextWebsocket.prototype._onClose = function () {
|
|
95
|
+
const err = new Error('websocket closed');
|
|
96
|
+
for (const id of Object.keys(this.callbacks)) {
|
|
97
|
+
this.callbacks[id].reject(err);
|
|
98
|
+
delete this.callbacks[id];
|
|
99
|
+
}
|
|
100
|
+
for (const id of Object.keys(this.iterators)) {
|
|
101
|
+
this.iterators[id].fail(err);
|
|
102
|
+
delete this.iterators[id];
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
ContextWebsocket.prototype._send = async function (payload) {
|
|
107
|
+
const data = JSON.stringify(payload);
|
|
108
|
+
this.debug('==>', data);
|
|
109
|
+
await this.ready;
|
|
110
|
+
this.socket.send(data);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
ContextWebsocket.prototype._call = async function (method, params, stream) {
|
|
114
|
+
const id = Math.random().toString(36).slice(2);
|
|
115
|
+
await this._send({ id, method, args: params, stream: !!stream });
|
|
116
|
+
|
|
117
|
+
if (!stream) {
|
|
118
|
+
return await new Promise((resolve, reject) => {
|
|
119
|
+
this.callbacks[id] = { resolve, reject };
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ctrl = new StreamController(() => {
|
|
124
|
+
delete this.iterators[id];
|
|
125
|
+
});
|
|
126
|
+
this.iterators[id] = ctrl;
|
|
127
|
+
return ctrl;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
ContextWebsocket.prototype._onMessage = function (event) {
|
|
131
|
+
let msg;
|
|
132
|
+
try {
|
|
133
|
+
msg = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
|
134
|
+
} catch {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.debug('<==', msg);
|
|
139
|
+
if (!msg || !msg.id) return;
|
|
140
|
+
|
|
141
|
+
const cb = this.callbacks[msg.id];
|
|
142
|
+
const iter = this.iterators[msg.id];
|
|
143
|
+
|
|
144
|
+
if (msg.error) {
|
|
145
|
+
const err = new Error(msg.error.message || 'rpc error');
|
|
146
|
+
err.stack = msg.error.stack;
|
|
147
|
+
|
|
148
|
+
if (cb) {
|
|
149
|
+
delete this.callbacks[msg.id];
|
|
150
|
+
cb.reject(err);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (iter) {
|
|
154
|
+
iter.fail(err);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (cb && (Object.prototype.hasOwnProperty.call(msg, 'result') || msg.done)) {
|
|
161
|
+
delete this.callbacks[msg.id];
|
|
162
|
+
cb.resolve(msg.result);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (iter) {
|
|
167
|
+
if (msg.done && !Object.prototype.hasOwnProperty.call(msg, 'result')) {
|
|
168
|
+
iter.close();
|
|
169
|
+
} else {
|
|
170
|
+
iter.push(msg.result, !!msg.done);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
ContextWebsocket.prototype.read = function (name, binary) {
|
|
176
|
+
return this._call('read', [name, binary], false);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
ContextWebsocket.prototype.write = function (name, content) {
|
|
180
|
+
return this._call('write', [name, content], false);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
ContextWebsocket.prototype.resolve = function (name, params) {
|
|
184
|
+
return this._call('resolve', [name, params], false);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
ContextWebsocket.prototype.exec = function (name, params) {
|
|
188
|
+
return this._call('exec', [name, params], false);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
ContextWebsocket.prototype.file2run = function (payload, params) {
|
|
192
|
+
return this._call('file2run', [payload, params], payload?.stream);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
197
|
+
module.exports = ContextWebsocket;
|
|
198
|
+
} else {
|
|
199
|
+
window.ContextWebsocket = ContextWebsocket;
|
|
200
|
+
window.ctx = new ContextWebsocket(window.location.origin.replace(/^http/, 'ws'));
|
|
201
|
+
}
|
package/src/ws.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const WebSocket = require('ws');
|
|
5
|
+
const mime = require('mime-types');
|
|
6
|
+
|
|
7
|
+
function makeServer({ port, static, root, ctx }) {
|
|
8
|
+
root = path.join(root || process.cwd());
|
|
9
|
+
|
|
10
|
+
function sendFile(filePath, res){
|
|
11
|
+
fs.readFile(filePath, (err, data) => {
|
|
12
|
+
if (err) {
|
|
13
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
14
|
+
return res.end('404 Not Found');
|
|
15
|
+
}
|
|
16
|
+
res.writeHead(200, {
|
|
17
|
+
'Content-Type': mime.lookup(filePath) || 'application/octet-stream',
|
|
18
|
+
});
|
|
19
|
+
res.end(data);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const server = http.createServer((req, res) => {
|
|
23
|
+
console.log(`[${req.method.toUpperCase()}] ${req.url}`)
|
|
24
|
+
if (req.method.toUpperCase() !== "GET") {
|
|
25
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
26
|
+
return res.end("ok");
|
|
27
|
+
}
|
|
28
|
+
if (req.url === "/contextws.js") {
|
|
29
|
+
return sendFile(path.join(__dirname, "contextws.js"), res)
|
|
30
|
+
}
|
|
31
|
+
if (!static) {
|
|
32
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
33
|
+
return res.end("ok");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let filePath = req.url === '/' ? '/index.html' : req.url;
|
|
37
|
+
// TODO: check if resolved path is not out of root
|
|
38
|
+
filePath = path.join(root, filePath);
|
|
39
|
+
sendFile(filePath, res)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const wss = new WebSocket.Server({ server });
|
|
43
|
+
|
|
44
|
+
wss.on('connection', (ws) => {
|
|
45
|
+
ws.on('message', async (msg) => {
|
|
46
|
+
let lctx = ctx.clone()
|
|
47
|
+
let { id, method, args, ...rest } = JSON.parse(msg.toString());
|
|
48
|
+
let result = { id }
|
|
49
|
+
if (method === "read") {
|
|
50
|
+
const [ name, binary ] = args
|
|
51
|
+
let content = await lctx.read(name, binary)
|
|
52
|
+
if (binary) {
|
|
53
|
+
content = content.toString("base64");
|
|
54
|
+
}
|
|
55
|
+
result = { id, result: content, binary, done: true }
|
|
56
|
+
} else if (method === "write") {
|
|
57
|
+
let [ name, content ] = args;
|
|
58
|
+
const { binary } = rest
|
|
59
|
+
if (binary) {
|
|
60
|
+
content = Buffer.from(content, "base64")
|
|
61
|
+
}
|
|
62
|
+
await lctx.write(name, content)
|
|
63
|
+
result.done = true
|
|
64
|
+
} else if (method === "exec") {
|
|
65
|
+
const [name, params] = args;
|
|
66
|
+
const node = await lctx.resolve(name)
|
|
67
|
+
if (!node) {
|
|
68
|
+
result.error = { message: `${name} not found` }
|
|
69
|
+
} else if (!node.exec) {
|
|
70
|
+
result.error = { message: `${name} not executable` }
|
|
71
|
+
} else {
|
|
72
|
+
const content = await ctx.exec(name, params)
|
|
73
|
+
result.result = content
|
|
74
|
+
result.done = true
|
|
75
|
+
}
|
|
76
|
+
} else if (method === "resolve") {
|
|
77
|
+
const [name, params] = args;
|
|
78
|
+
const content = await lctx.resolve(name, params)
|
|
79
|
+
result.result = content
|
|
80
|
+
result.done = true
|
|
81
|
+
} else if (method === "file2run") {
|
|
82
|
+
const [ payload, params ] = args
|
|
83
|
+
|
|
84
|
+
const r = await lctx.file2run({ ...payload, errors: "message" }, params);
|
|
85
|
+
if (!payload?.stream) {
|
|
86
|
+
result.result = r
|
|
87
|
+
result.done = true
|
|
88
|
+
} else {
|
|
89
|
+
(async () => {
|
|
90
|
+
try {
|
|
91
|
+
let chunk = {};
|
|
92
|
+
let lastRes
|
|
93
|
+
while(!chunk.done) {
|
|
94
|
+
chunk = await r.next();
|
|
95
|
+
result.result = (chunk.value || "");
|
|
96
|
+
ws.send(JSON.stringify(result));
|
|
97
|
+
}
|
|
98
|
+
result.done = true
|
|
99
|
+
ws.send(JSON.stringify(result));
|
|
100
|
+
} catch (e) {
|
|
101
|
+
delete result.result
|
|
102
|
+
result.error = {message: e.message, stack: e.stack}
|
|
103
|
+
ws.send(JSON.stringify(result));
|
|
104
|
+
}
|
|
105
|
+
})()
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
result.error = { message: `method not found ${method}`}
|
|
109
|
+
}
|
|
110
|
+
ws.send(JSON.stringify(result));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
server.listen(port, () => {
|
|
115
|
+
if (Number.isNaN(parseInt(port))) {
|
|
116
|
+
console.log(`listening ${port}`);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`listening http://localhost:${port}`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = makeServer
|