mslxdff 0.1.1 → 0.1.3
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 +157 -83
- package/bin/mslxdff.js +576 -0
- package/package.json +26 -26
- package/src/auto.js +99 -0
- package/src/daemon.js +63 -63
- package/src/groups.js +189 -0
- package/src/logs.js +80 -0
- package/src/models.js +127 -91
- package/src/peers.js +83 -0
- package/src/reasoning.js +32 -32
- package/src/routes.js +309 -125
- package/src/server.js +37 -27
- package/src/state.js +119 -39
- package/src/upstream.js +67 -67
- package/bin/mslxdfree.js +0 -78
package/src/upstream.js
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
export function createUpstreamClient({
|
|
2
|
-
baseUrl = process.env.UPSTREAM_BASE_URL || "https://opencode.ai",
|
|
3
|
-
authToken = process.env.UPSTREAM_AUTH_TOKEN || "public",
|
|
4
|
-
connectTimeoutMs = Number(process.env.UPSTREAM_CONNECT_TIMEOUT_MS) || 30_000,
|
|
5
|
-
retry = {
|
|
6
|
-
network: { attempts: 2, delayMs: 1000 },
|
|
7
|
-
429: { attempts: 2, delayMs: 2000 },
|
|
8
|
-
502: { attempts: 2, delayMs: 2000 },
|
|
9
|
-
503: { attempts: 2, delayMs: 2000 },
|
|
10
|
-
504: { attempts: 2, delayMs: 3000 },
|
|
11
|
-
},
|
|
12
|
-
fetchImpl = fetch,
|
|
13
|
-
} = {}) {
|
|
14
|
-
const headers = {
|
|
15
|
-
"Content-Type": "application/json",
|
|
16
|
-
"Authorization": `Bearer ${authToken}`,
|
|
17
|
-
"x-opencode-client": "desktop",
|
|
18
|
-
"Accept": "text/event-stream",
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
async function chat(body) {
|
|
22
|
-
const url = `${baseUrl}/zen/v1/chat/completions`;
|
|
23
|
-
for (let attempt = 0; ; attempt++) {
|
|
24
|
-
const result = await attemptOnce(url, body);
|
|
25
|
-
if (result instanceof Error) {
|
|
26
|
-
const entry = retry?.network;
|
|
27
|
-
if (entry && attempt < entry.attempts) {
|
|
28
|
-
await sleep(entry.delayMs);
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
throw result;
|
|
32
|
-
}
|
|
33
|
-
const entry = retry?.[result.status];
|
|
34
|
-
if (entry && attempt < entry.attempts) {
|
|
35
|
-
await sleep(entry.delayMs);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function attemptOnce(url, body) {
|
|
43
|
-
const controller = new AbortController();
|
|
44
|
-
const timer = setTimeout(() =>
|
|
45
|
-
controller.abort(new Error(`upstream timed out after ${connectTimeoutMs}ms`)),
|
|
46
|
-
connectTimeoutMs
|
|
47
|
-
);
|
|
48
|
-
try {
|
|
49
|
-
const res = await fetchImpl(url, {
|
|
50
|
-
method: "POST",
|
|
51
|
-
headers,
|
|
52
|
-
body: JSON.stringify(body),
|
|
53
|
-
signal: controller.signal,
|
|
54
|
-
});
|
|
55
|
-
return res;
|
|
56
|
-
} catch (err) {
|
|
57
|
-
return err;
|
|
58
|
-
} finally {
|
|
59
|
-
clearTimeout(timer);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { chat, headers };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function sleep(ms) {
|
|
67
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
1
|
+
export function createUpstreamClient({
|
|
2
|
+
baseUrl = process.env.UPSTREAM_BASE_URL || "https://opencode.ai",
|
|
3
|
+
authToken = process.env.UPSTREAM_AUTH_TOKEN || "public",
|
|
4
|
+
connectTimeoutMs = Number(process.env.UPSTREAM_CONNECT_TIMEOUT_MS) || 30_000,
|
|
5
|
+
retry = {
|
|
6
|
+
network: { attempts: 2, delayMs: 1000 },
|
|
7
|
+
429: { attempts: 2, delayMs: 2000 },
|
|
8
|
+
502: { attempts: 2, delayMs: 2000 },
|
|
9
|
+
503: { attempts: 2, delayMs: 2000 },
|
|
10
|
+
504: { attempts: 2, delayMs: 3000 },
|
|
11
|
+
},
|
|
12
|
+
fetchImpl = fetch,
|
|
13
|
+
} = {}) {
|
|
14
|
+
const headers = {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
"Authorization": `Bearer ${authToken}`,
|
|
17
|
+
"x-opencode-client": "desktop",
|
|
18
|
+
"Accept": "text/event-stream",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
async function chat(body) {
|
|
22
|
+
const url = `${baseUrl}/zen/v1/chat/completions`;
|
|
23
|
+
for (let attempt = 0; ; attempt++) {
|
|
24
|
+
const result = await attemptOnce(url, body);
|
|
25
|
+
if (result instanceof Error) {
|
|
26
|
+
const entry = retry?.network;
|
|
27
|
+
if (entry && attempt < entry.attempts) {
|
|
28
|
+
await sleep(entry.delayMs);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
throw result;
|
|
32
|
+
}
|
|
33
|
+
const entry = retry?.[result.status];
|
|
34
|
+
if (entry && attempt < entry.attempts) {
|
|
35
|
+
await sleep(entry.delayMs);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function attemptOnce(url, body) {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timer = setTimeout(() =>
|
|
45
|
+
controller.abort(new Error(`upstream timed out after ${connectTimeoutMs}ms`)),
|
|
46
|
+
connectTimeoutMs
|
|
47
|
+
);
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetchImpl(url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify(body),
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
});
|
|
55
|
+
return res;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return err;
|
|
58
|
+
} finally {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { chat, headers };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sleep(ms) {
|
|
67
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
68
68
|
}
|
package/bin/mslxdfree.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { startServer } from "../src/server.js";
|
|
3
|
-
import { createRouter } from "../src/routes.js";
|
|
4
|
-
import { createUpstreamClient } from "../src/upstream.js";
|
|
5
|
-
import { createModelsService } from "../src/models.js";
|
|
6
|
-
import { loadToken, refreshToken } from "../src/state.js";
|
|
7
|
-
import { startDaemon, stopDaemon, writePid, pidFile, logFile } from "../src/daemon.js";
|
|
8
|
-
|
|
9
|
-
const args = process.argv.slice(2);
|
|
10
|
-
|
|
11
|
-
if (args.includes("-refresh-token") || args.includes("--refresh-token")) {
|
|
12
|
-
const token = await refreshToken();
|
|
13
|
-
console.log(token);
|
|
14
|
-
process.exit(0);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (args.includes("-showtoken") || args.includes("--showtoken")) {
|
|
18
|
-
const { token } = await loadToken();
|
|
19
|
-
console.log(token);
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (args.includes("-stop") || args.includes("--stop")) {
|
|
24
|
-
const { stopped, pid, reason } = stopDaemon();
|
|
25
|
-
if (stopped) {
|
|
26
|
-
console.log(`mslxdfree daemon stopped (pid ${pid})`);
|
|
27
|
-
} else {
|
|
28
|
-
console.log(`mslxdfree daemon not running${reason ? ` (${reason})` : ""}`);
|
|
29
|
-
}
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (args.includes("-d") || args.includes("--daemon")) {
|
|
34
|
-
if (!process.env.MSLXDFREE_DAEMON) {
|
|
35
|
-
// foreground: spawn the detached background instance, then wait for health
|
|
36
|
-
const spawnedPid = startDaemon(args.filter((a) => a !== "-d" && a !== "--daemon"));
|
|
37
|
-
await waitForHealth(4000);
|
|
38
|
-
console.log(`mslxdfree daemon started (pid ${spawnedPid})`);
|
|
39
|
-
console.log(`log: ${logFile()}`);
|
|
40
|
-
console.log(`pid: ${pidFile()}`);
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
// we ARE the daemon; stdout/stderr already point at the log file via startDaemon stdio
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const { token, created } = await loadToken();
|
|
47
|
-
const upstream = createUpstreamClient({});
|
|
48
|
-
const baseUrl = process.env.UPSTREAM_BASE_URL || "https://opencode.ai";
|
|
49
|
-
const models = createModelsService({ baseUrl, headers: upstream.headers });
|
|
50
|
-
|
|
51
|
-
const router = createRouter({ token, upstream, models });
|
|
52
|
-
const srv = startServer({ router });
|
|
53
|
-
|
|
54
|
-
await srv.ready();
|
|
55
|
-
if (process.env.MSLXDFREE_DAEMON) {
|
|
56
|
-
writePid(process.pid);
|
|
57
|
-
}
|
|
58
|
-
const addr = srv.server.address();
|
|
59
|
-
const host = addr.address === "0.0.0.0" || addr.address === "::" ? "localhost" : addr.address;
|
|
60
|
-
console.log(`mslxdfree listening on http://${host}:${addr.port}`);
|
|
61
|
-
if (created) {
|
|
62
|
-
console.log(`auth token: ${token}`);
|
|
63
|
-
}
|
|
64
|
-
console.log(`endpoint: http://${host}:${addr.port}/v1`);
|
|
65
|
-
|
|
66
|
-
async function waitForHealth(timeoutMs) {
|
|
67
|
-
const start = Date.now();
|
|
68
|
-
const port = Number(process.env.PORT) || 8080;
|
|
69
|
-
while (Date.now() - start < timeoutMs) {
|
|
70
|
-
try {
|
|
71
|
-
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
72
|
-
if (res.ok) return;
|
|
73
|
-
} catch {
|
|
74
|
-
// not up yet
|
|
75
|
-
}
|
|
76
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
77
|
-
}
|
|
78
|
-
}
|