@aryacode/dsh-local-llm 0.0.0-stage → 0.8.2

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/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Local LLM",
4
+ "description": "AryaStrata engine controls with the local NInfer Qwen3.8 dense model for AryaAI."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "本地 LLM",
4
+ "description": "AryaStrata 引擎控制,以及本地 NInfer(Qwen3.8 稠密模型)界面。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
- {
2
- "name": "@aryacode/dsh-local-llm",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@aryacode/dsh-local-llm",
3
+ "version": "0.8.2",
4
+ "description": "AryaStrata engine controls with the local NInfer Qwen3.8 dense model for AryaAI",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": "./lib/client.js",
14
+ "./package.json": "./package.json",
15
+ "./locale/*.json": "./locale/*.json"
16
+ },
17
+ "dsh": {
18
+ "bundle": {
19
+ "patch": "./cordis.patch.yml"
20
+ },
21
+ "client": {
22
+ "platform": "web",
23
+ "inject": [
24
+ "@deepseek-ai/dsh-client-locale",
25
+ "@deepseek-ai/dsh-client-ui-layout",
26
+ "@deepseek-ai/dsh-client-ui-renderer",
27
+ "@deepseek-ai/dsh-client-ui-slots"
28
+ ]
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown",
33
+ "typecheck": "tsc --noEmit",
34
+ "prebuild": "node scripts/theme.mjs",
35
+ "test": "node --test tests/*.test.mjs",
36
+ "test:ui": "node tests/control-center-ui.mjs"
37
+ },
38
+ "devDependencies": {
39
+ "@deepseek-ai/cordis": "^4.0.4",
40
+ "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
41
+ "@deepseek-ai/dsh-llm-pi-ai": "^0.2.0-rc.2",
42
+ "@types/node": "^22.10.0",
43
+ "@types/react": "~18.3.1",
44
+ "react": "^18.2.0",
45
+ "tsdown": "^0.22.14",
46
+ "typescript": "^5.6.0"
47
+ },
48
+ "files": [
49
+ "icon.svg",
50
+ "locale/*.json",
51
+ "lib/index.js",
52
+ "lib/index.d.ts",
53
+ "lib/client.js",
54
+ "lib/client.js.map",
55
+ "lib/openai-completions-*.js",
56
+ "lib/event-stream-*.js",
57
+ "cordis.patch.yml",
58
+ "blue.png",
59
+ "README.md",
60
+ "LEGACY-CONTROL-CENTER.md",
61
+ "scripts/freetoken-control.py",
62
+ "scripts/ninfer-control.py"
63
+ ],
64
+ "dependencies": {
65
+ "@earendil-works/pi-ai": "^0.87.1",
66
+ "saxes": "^6.0.0",
67
+ "systeminformation": "5.33.15"
68
+ },
69
+ "peerDependencies": {
70
+ "@deepseek-ai/dsh-llm-pi-ai": "^0.2.0-rc.2"
71
+ },
72
+ "icon": "./icon.svg"
73
+ }
@@ -0,0 +1,82 @@
1
+ """Own one FreeToken process group inside WSL; refuse unrelated processes."""
2
+ import json
3
+ import os
4
+ import signal
5
+ import subprocess
6
+ import sys
7
+ import time
8
+ from pathlib import Path
9
+
10
+ directory = Path(sys.argv[1])
11
+ action = sys.argv[2]
12
+ request = json.load(sys.stdin)
13
+ directory.mkdir(parents=True, exist_ok=True)
14
+ owner_file = directory / 'owner.json'
15
+ log_file = directory / 'server.log'
16
+
17
+ def identity(pid):
18
+ try:
19
+ fields = Path('/proc/' + str(pid) + '/stat').read_text().rsplit(')', 1)[1].split()
20
+ return None if fields[0] == 'Z' else fields[19]
21
+ except (FileNotFoundError, ProcessLookupError):
22
+ return None
23
+
24
+ def owner():
25
+ if not owner_file.exists():
26
+ return None
27
+ record = json.loads(owner_file.read_text())
28
+ return record if identity(record['pid']) == record['started'] else None
29
+
30
+ def stop():
31
+ record = owner()
32
+ if record:
33
+ os.killpg(record['pid'], signal.SIGTERM)
34
+ deadline = time.monotonic() + 20
35
+ while identity(record['pid']) is not None and time.monotonic() < deadline:
36
+ time.sleep(.2)
37
+ if identity(record['pid']) == record['started']:
38
+ os.killpg(record['pid'], signal.SIGKILL)
39
+ deadline = time.monotonic() + 5
40
+ while identity(record['pid']) is not None and time.monotonic() < deadline:
41
+ time.sleep(.1)
42
+ if identity(record['pid']) is not None:
43
+ raise RuntimeError('FreeToken has not stopped yet.')
44
+ owner_file.unlink(missing_ok=True)
45
+
46
+ if action == 'status':
47
+ record = owner()
48
+ print(json.dumps({'owned': bool(record), 'pid': record['pid'] if record else None}))
49
+ elif action == 'logs':
50
+ if log_file.exists():
51
+ with log_file.open('rb') as stream:
52
+ stream.seek(max(0, log_file.stat().st_size - 16000))
53
+ text = stream.read().decode(errors='replace')
54
+ else:
55
+ text = ''
56
+ print(json.dumps({'text': text}))
57
+ elif action == 'stop':
58
+ stop()
59
+ print(json.dumps({'ok': True, 'detail': 'FreeToken stopped.'}))
60
+ elif action == 'start':
61
+ if owner():
62
+ raise RuntimeError('FreeToken is already running.')
63
+ args = [request['runtime'] + '/bin/ft', 'serve', '--model-path', request['modelPath'],
64
+ '--served-model-name', request['alias'], '--host', '127.0.0.1', '--port', str(request['port']),
65
+ '--max-seq-len-override', str(request['context']), '--max-running-requests', '1',
66
+ '--max-prefill-length', str(request['prefill']), '--memory-ratio', str(request['memoryRatio']),
67
+ '--moe-strategy', request['strategy'], '--moe-cpu-threads', str(request['threads']),
68
+ '--moe-cpu-layers', 'auto', '--moe-cache-auto', '--kv-reserve-tokens', str(request['context']),
69
+ '--nvfp4-backend', 'triton', '--expert-load', 'serial',
70
+ '--mm-encoder-weights', 'host', '--image-max-tokens', '1024']
71
+ if not request['vision']:
72
+ args += ['--mm-disable', 'vision']
73
+ with log_file.open('ab', buffering=0) as log:
74
+ process = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=log, stderr=log,
75
+ start_new_session=True, cwd=directory)
76
+ started = identity(process.pid)
77
+ if started is None:
78
+ raise RuntimeError('FreeToken exited during launch; inspect its logs.')
79
+ owner_file.write_text(json.dumps({'pid': process.pid, 'started': started}))
80
+ print(json.dumps({'ok': True, 'detail': 'FreeToken is loading. Check server status.', 'pid': process.pid}))
81
+ else:
82
+ raise ValueError('Unsupported FreeToken operation.')
@@ -0,0 +1,120 @@
1
+ """Manage one identified NInfer process group without Docker or shell commands."""
2
+ import json
3
+ import errno
4
+ import os
5
+ import signal
6
+ import socket
7
+ import subprocess
8
+ import sys
9
+ import time
10
+ from pathlib import Path
11
+
12
+
13
+ def identity(pid):
14
+ try:
15
+ fields = Path(f'/proc/{pid}/stat').read_text().rsplit(')', 1)[1].split()
16
+ return None if fields[0] == 'Z' else fields[19]
17
+ except (FileNotFoundError, ProcessLookupError):
18
+ return None
19
+
20
+
21
+ def owned_record(owner_file, binary):
22
+ if not owner_file.exists():
23
+ return None
24
+ record = json.loads(owner_file.read_text())
25
+ pid = record['pid']
26
+ if not isinstance(pid, int) or pid <= 1 or identity(pid) != record['started']:
27
+ return None
28
+ try:
29
+ executable = Path(f'/proc/{pid}/exe').resolve(strict=True)
30
+ args = Path(f'/proc/{pid}/cmdline').read_bytes().split(b'\0')
31
+ expected = [arg.encode() for arg in record['args']]
32
+ matches = executable == binary.resolve() and args[:-1] == expected and os.getpgid(pid) == pid
33
+ except (FileNotFoundError, ProcessLookupError):
34
+ return None
35
+ if not matches:
36
+ raise RuntimeError('NInfer process identity changed; no process was stopped.')
37
+ return record
38
+
39
+
40
+ def stop(owner_file, binary):
41
+ record = owned_record(owner_file, binary)
42
+ if not record:
43
+ return
44
+ os.killpg(record['pid'], signal.SIGTERM)
45
+ deadline = time.monotonic() + 20
46
+ while owned_record(owner_file, binary) and time.monotonic() < deadline:
47
+ time.sleep(.1)
48
+ if owned_record(owner_file, binary):
49
+ os.killpg(record['pid'], signal.SIGKILL)
50
+ deadline = time.monotonic() + 5
51
+ while owned_record(owner_file, binary) and time.monotonic() < deadline:
52
+ time.sleep(.1)
53
+ if owned_record(owner_file, binary):
54
+ raise RuntimeError('NInfer has not stopped yet.')
55
+ owner_file.unlink(missing_ok=True)
56
+
57
+
58
+ def ensure_port_available(port):
59
+ with socket.socket() as probe:
60
+ probe.settimeout(1)
61
+ result = probe.connect_ex(('127.0.0.1', port))
62
+ if result != errno.ECONNREFUSED:
63
+ raise RuntimeError('NInfer port has a listener or could not be checked; no server was stopped.')
64
+
65
+
66
+ def main():
67
+ directory, action = Path(sys.argv[1]), sys.argv[2]
68
+ request = json.load(sys.stdin)
69
+ directory.mkdir(parents=True, exist_ok=True)
70
+ binary = Path(request['binary'])
71
+ owner_file = directory / 'wsl-owner.json'
72
+ log_file = directory / 'wsl-server.log'
73
+ record = owned_record(owner_file, binary)
74
+ if action == 'status':
75
+ print(json.dumps({'owned': bool(record), 'pid': record['pid'] if record else None,
76
+ 'available': binary.is_file() and os.access(binary, os.X_OK)}))
77
+ elif action == 'logs':
78
+ text = ''
79
+ if log_file.exists():
80
+ with log_file.open('rb') as stream:
81
+ stream.seek(max(0, log_file.stat().st_size - 16000))
82
+ text = stream.read().decode(errors='replace')
83
+ print(json.dumps({'text': text}))
84
+ elif action == 'stop':
85
+ stop(owner_file, binary)
86
+ print(json.dumps({'ok': True, 'detail': 'NInfer stopped.'}))
87
+ elif action == 'start':
88
+ if record:
89
+ print(json.dumps({'ok': True, 'detail': 'NInfer is already started.'}))
90
+ return
91
+ if not binary.is_file() or not os.access(binary, os.X_OK):
92
+ raise RuntimeError('NInfer binary is unavailable. Finish the WSL build first.')
93
+ ensure_port_available(request['port'])
94
+ args = [str(binary), request['artifact'], '--host', '127.0.0.1', '--port', str(request['port']),
95
+ '--max-context', str(request['context']), '--kv-capacity', str(request['context']),
96
+ '--kv-dtype', 'rk4v4-e8', '--max-concurrency', '1', '--max-pending-requests', '16',
97
+ '--prefill-chunk', str(request['prefill']), '--host-kv-mib', str(request['hostKvMiB']),
98
+ '--default-max-tokens', str(request.get('outputTokens', 8192)),
99
+ '--spec', 'mtp', '--draft-tokens', str(request['draftTokens']), '--lm-head-draft',
100
+ '--ngram', request['ngram'], '--preserve-thinking']
101
+ if request['vision']:
102
+ args.append('--vision')
103
+ environment = dict(os.environ)
104
+ environment['LD_LIBRARY_PATH'] = request['libraryPath']
105
+ with log_file.open('ab', buffering=0) as log:
106
+ process = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=log, stderr=log,
107
+ start_new_session=True, cwd=directory, env=environment)
108
+ started = identity(process.pid)
109
+ if started is None:
110
+ raise RuntimeError('NInfer exited during launch; inspect its logs.')
111
+ marker = owner_file.with_suffix('.tmp')
112
+ marker.write_text(json.dumps({'pid': process.pid, 'started': started, 'args': args}))
113
+ marker.replace(owner_file)
114
+ print(json.dumps({'ok': True, 'detail': 'NInfer is loading in WSL. Check readiness and logs.', 'pid': process.pid}))
115
+ else:
116
+ raise ValueError('Unsupported NInfer operation.')
117
+
118
+
119
+ if __name__ == '__main__':
120
+ main()