deepseek-local-api 0.8.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/.env.example +8 -0
- package/README.md +138 -0
- package/bin/cli.js +66 -0
- package/package.json +49 -0
- package/src/client/ChatSession.js +187 -0
- package/src/client/DeepseekClient.js +134 -0
- package/src/config/constants.js +29 -0
- package/src/config/headers.js +45 -0
- package/src/index.js +307 -0
- package/src/services/PowService.js +66 -0
- package/src/services/WasmService.js +117 -0
- package/src/services/autocomplete.js +48 -0
- package/src/services/server.js +289 -0
- package/src/utils/encoding.js +94 -0
- package/src/utils/memory.js +65 -0
- package/wasm/sha3_wasm_bg.7b9ca65ddd.wasm +0 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
|
3
|
+
require('dotenv').config();
|
|
4
|
+
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const DeepseekClient = require('./client/DeepseekClient');
|
|
7
|
+
const ChatSession = require('./client/ChatSession');
|
|
8
|
+
const { startServer } = require('./services/server');
|
|
9
|
+
const { createCompleter } = require('./services/autocomplete');
|
|
10
|
+
|
|
11
|
+
function printSessionBanner(session) {
|
|
12
|
+
const id = session.getId();
|
|
13
|
+
const url = session.getWebUrl();
|
|
14
|
+
console.log('\n┌──────────────────────────────────────────────────────────────────────────');
|
|
15
|
+
console.log(`│ 💬 DeepSeek Session ID : ${id}`);
|
|
16
|
+
console.log(`│ 🌐 Direct Web URL : ${url}`);
|
|
17
|
+
console.log('└──────────────────────────────────────────────────────────────────────────\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Send a message to DeepSeek and stream the response to console
|
|
22
|
+
* @param {string} token - DeepSeek auth token
|
|
23
|
+
* @param {string} message - Message prompt
|
|
24
|
+
* @param {string|null} sessionIdOrUrl - Optional existing session ID or web URL
|
|
25
|
+
* @returns {Promise<ChatSession>}
|
|
26
|
+
*/
|
|
27
|
+
async function chat(token, message, sessionIdOrUrl = null) {
|
|
28
|
+
const client = new DeepseekClient(token);
|
|
29
|
+
await client.initialize();
|
|
30
|
+
|
|
31
|
+
let session;
|
|
32
|
+
if (sessionIdOrUrl) {
|
|
33
|
+
console.log(`Connecting to existing session: ${sessionIdOrUrl}...`);
|
|
34
|
+
session = await client.resumeSession(sessionIdOrUrl);
|
|
35
|
+
} else {
|
|
36
|
+
session = await client.createSession();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
printSessionBanner(session);
|
|
40
|
+
|
|
41
|
+
console.log(`User: ${message}\n`);
|
|
42
|
+
const response = await client.sendMessage(message, session, {
|
|
43
|
+
thinking_enabled: true,
|
|
44
|
+
search_enabled: false
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
let currentMode = null;
|
|
48
|
+
for await (const chunk of client.streamResponse(response, session)) {
|
|
49
|
+
if (typeof chunk === 'string') {
|
|
50
|
+
process.stdout.write(chunk);
|
|
51
|
+
} else if (chunk.type === 'thinking') {
|
|
52
|
+
if (currentMode !== 'thinking') {
|
|
53
|
+
process.stdout.write('\n\x1b[2m[Thinking Process]:\n');
|
|
54
|
+
currentMode = 'thinking';
|
|
55
|
+
}
|
|
56
|
+
process.stdout.write(chunk.text);
|
|
57
|
+
} else if (chunk.type === 'content') {
|
|
58
|
+
if (currentMode === 'thinking') {
|
|
59
|
+
process.stdout.write('\x1b[0m\n\nAssistant:\n');
|
|
60
|
+
currentMode = 'content';
|
|
61
|
+
} else if (currentMode === null) {
|
|
62
|
+
process.stdout.write('Assistant:\n');
|
|
63
|
+
currentMode = 'content';
|
|
64
|
+
}
|
|
65
|
+
process.stdout.write(chunk.text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write('\x1b[0m\n\n');
|
|
69
|
+
return session;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Start an interactive multi-turn chat session in terminal
|
|
74
|
+
*/
|
|
75
|
+
async function startInteractive(token, initialSessionIdOrUrl = null) {
|
|
76
|
+
const client = new DeepseekClient(token);
|
|
77
|
+
process.stdout.write('Initializing DeepSeek client & WASM solver... ');
|
|
78
|
+
await client.initialize();
|
|
79
|
+
console.log('Done.\n');
|
|
80
|
+
|
|
81
|
+
let currentSession = null;
|
|
82
|
+
if (initialSessionIdOrUrl) {
|
|
83
|
+
currentSession = await client.resumeSession(initialSessionIdOrUrl);
|
|
84
|
+
} else {
|
|
85
|
+
const saved = ChatSession.loadAllSavedSessions();
|
|
86
|
+
if (saved.length > 0) {
|
|
87
|
+
console.log('Recent Saved Sessions:');
|
|
88
|
+
saved.slice(0, 5).forEach((s, idx) => {
|
|
89
|
+
console.log(` [${idx + 1}] ${s.title || 'Untitled'} (${s.id})`);
|
|
90
|
+
});
|
|
91
|
+
console.log(' [0] Start a brand new session\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const rlInit = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
95
|
+
const choice = await new Promise(resolve => {
|
|
96
|
+
rlInit.question('Enter number, paste a Session ID / Web URL, or press Enter for new session: ', answer => {
|
|
97
|
+
rlInit.close();
|
|
98
|
+
resolve(answer.trim());
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const num = parseInt(choice, 10);
|
|
103
|
+
if (!isNaN(num) && num > 0 && num <= saved.length) {
|
|
104
|
+
currentSession = await client.resumeSession(saved[num - 1].id);
|
|
105
|
+
} else if (choice.length > 10) {
|
|
106
|
+
currentSession = await client.resumeSession(choice);
|
|
107
|
+
} else {
|
|
108
|
+
currentSession = await client.createSession();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
printSessionBanner(currentSession);
|
|
113
|
+
console.log('Commands: /help (list all), /thinking (toggle think), /search (toggle web), /server (start API), /exit\n');
|
|
114
|
+
console.log('\x1b[2m(Tip: Press Tab to autocomplete commands)\x1b[0m\n');
|
|
115
|
+
|
|
116
|
+
let thinkingEnabled = true;
|
|
117
|
+
let searchEnabled = false;
|
|
118
|
+
|
|
119
|
+
const rl = readline.createInterface({
|
|
120
|
+
input: process.stdin,
|
|
121
|
+
output: process.stdout,
|
|
122
|
+
prompt: 'You > ',
|
|
123
|
+
completer: createCompleter()
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
rl.prompt();
|
|
127
|
+
|
|
128
|
+
rl.on('line', async (line) => {
|
|
129
|
+
const input = line.trim();
|
|
130
|
+
if (!input) {
|
|
131
|
+
rl.prompt();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (input === '/exit' || input === '/quit') {
|
|
136
|
+
rl.close();
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (input === '/help') {
|
|
141
|
+
console.log('\nAvailable In-Chat Commands (Press Tab to autocomplete):');
|
|
142
|
+
console.log(' /id - Show current session ID and web URL');
|
|
143
|
+
console.log(' /new - Start a brand new session');
|
|
144
|
+
console.log(' /resume [id] - Resume an existing session by ID or URL');
|
|
145
|
+
console.log(` /thinking - Toggle thinking mode (current: ${thinkingEnabled ? 'ON' : 'OFF'})`);
|
|
146
|
+
console.log(` /search - Toggle web search (current: ${searchEnabled ? 'ON' : 'OFF'})`);
|
|
147
|
+
console.log(' /server [p] - Start local OpenAI-compatible server on port [p]');
|
|
148
|
+
console.log(' /exit - Exit the chat\n');
|
|
149
|
+
rl.prompt();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (input === '/id' || input === '/url') {
|
|
154
|
+
printSessionBanner(currentSession);
|
|
155
|
+
rl.prompt();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (input === '/thinking' || input === '/think') {
|
|
160
|
+
thinkingEnabled = !thinkingEnabled;
|
|
161
|
+
console.log(`💡 Thinking Mode: ${thinkingEnabled ? '\x1b[32mON\x1b[0m (DeepSeek Reasoner enabled)' : '\x1b[31mOFF\x1b[0m'}\n`);
|
|
162
|
+
rl.prompt();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (input === '/search' || input === '/web') {
|
|
167
|
+
searchEnabled = !searchEnabled;
|
|
168
|
+
console.log(`🌐 Web Search: ${searchEnabled ? '\x1b[32mON\x1b[0m' : '\x1b[31mOFF\x1b[0m'}\n`);
|
|
169
|
+
rl.prompt();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (input.startsWith('/server')) {
|
|
174
|
+
const parts = input.split(/\s+/);
|
|
175
|
+
const targetPort = parts[1] ? parseInt(parts[1], 10) : 3000;
|
|
176
|
+
const isNet = parts.includes('--network') || parts.includes('-n');
|
|
177
|
+
console.log(`\n🚀 Launching local OpenAI-compatible API server on port ${targetPort}...`);
|
|
178
|
+
startServer({ token, port: targetPort, isNetworkAvailable: isNet }).catch(err => {
|
|
179
|
+
console.error('Failed to start server:', err.message);
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (input === '/new') {
|
|
185
|
+
currentSession = await client.createSession();
|
|
186
|
+
console.log('✓ Started new session:');
|
|
187
|
+
printSessionBanner(currentSession);
|
|
188
|
+
rl.prompt();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (input.startsWith('/resume ')) {
|
|
193
|
+
const targetId = input.slice(8).trim();
|
|
194
|
+
currentSession = await client.resumeSession(targetId);
|
|
195
|
+
console.log('✓ Resumed session:');
|
|
196
|
+
printSessionBanner(currentSession);
|
|
197
|
+
rl.prompt();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const response = await client.sendMessage(input, currentSession, {
|
|
203
|
+
thinking_enabled: thinkingEnabled,
|
|
204
|
+
search_enabled: searchEnabled
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
let currentMode = null;
|
|
208
|
+
for await (const chunk of client.streamResponse(response, currentSession)) {
|
|
209
|
+
if (typeof chunk === 'string') {
|
|
210
|
+
process.stdout.write(chunk);
|
|
211
|
+
} else if (chunk.type === 'thinking') {
|
|
212
|
+
if (currentMode !== 'thinking') {
|
|
213
|
+
process.stdout.write('\n\x1b[2m[Thinking Process]:\n');
|
|
214
|
+
currentMode = 'thinking';
|
|
215
|
+
}
|
|
216
|
+
process.stdout.write(chunk.text);
|
|
217
|
+
} else if (chunk.type === 'content') {
|
|
218
|
+
if (currentMode === 'thinking') {
|
|
219
|
+
process.stdout.write('\x1b[0m\n\nDeepSeek:\n');
|
|
220
|
+
currentMode = 'content';
|
|
221
|
+
} else if (currentMode === null) {
|
|
222
|
+
process.stdout.write('DeepSeek:\n');
|
|
223
|
+
currentMode = 'content';
|
|
224
|
+
}
|
|
225
|
+
process.stdout.write(chunk.text);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
process.stdout.write('\x1b[0m\n\n');
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.error('\nError:', err.message, '\n');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
rl.prompt();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function parseCliArgs(argv) {
|
|
238
|
+
const args = argv.slice(2);
|
|
239
|
+
let isServer = false;
|
|
240
|
+
let port = 3000;
|
|
241
|
+
let isNetworkAvailable = false;
|
|
242
|
+
const otherArgs = [];
|
|
243
|
+
|
|
244
|
+
for (let i = 0; i < args.length; i++) {
|
|
245
|
+
const arg = args[i];
|
|
246
|
+
|
|
247
|
+
if (arg === '--network' || arg === '--public' || arg === '-n') {
|
|
248
|
+
isNetworkAvailable = true;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (arg === '--server' || arg === '-s' || arg === '--port' || arg === '-p') {
|
|
253
|
+
isServer = true;
|
|
254
|
+
const next = args[i + 1];
|
|
255
|
+
if (next && !next.startsWith('-')) {
|
|
256
|
+
const parsedPort = parseInt(next, 10);
|
|
257
|
+
if (!isNaN(parsedPort) && parsedPort > 0) {
|
|
258
|
+
port = parsedPort;
|
|
259
|
+
i++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (arg.startsWith('--server=') || arg.startsWith('--port=')) {
|
|
266
|
+
isServer = true;
|
|
267
|
+
const val = arg.split('=')[1];
|
|
268
|
+
const parsedPort = parseInt(val, 10);
|
|
269
|
+
if (!isNaN(parsedPort) && parsedPort > 0) {
|
|
270
|
+
port = parsedPort;
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
otherArgs.push(arg);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return { isServer, port, isNetworkAvailable, otherArgs };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = { chat, startInteractive, startServer, parseCliArgs };
|
|
282
|
+
|
|
283
|
+
if (require.main === module) {
|
|
284
|
+
const token = process.env.DEEPSEEK_TOKEN;
|
|
285
|
+
if (!token) {
|
|
286
|
+
console.error("Please set DEEPSEEK_TOKEN in .env");
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const { isServer, port, isNetworkAvailable, otherArgs } = parseCliArgs(process.argv);
|
|
291
|
+
|
|
292
|
+
if (isServer) {
|
|
293
|
+
startServer({ token, port, isNetworkAvailable }).catch(err => {
|
|
294
|
+
console.error('Failed to start server:', err.message);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
const messageArg = otherArgs[0];
|
|
299
|
+
const sessionArg = otherArgs[1];
|
|
300
|
+
|
|
301
|
+
if (messageArg && messageArg !== '--interactive' && messageArg !== '-i') {
|
|
302
|
+
chat(token, messageArg, sessionArg).catch(console.error);
|
|
303
|
+
} else {
|
|
304
|
+
startInteractive(token, sessionArg).catch(console.error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const WasmService = require('./WasmService');
|
|
2
|
+
const { API_ENDPOINTS, WASM_CONFIG } = require('../config/constants');
|
|
3
|
+
const { HeadersBuilder } = require('../config/headers');
|
|
4
|
+
const { EncodingUtils } = require('../utils/encoding');
|
|
5
|
+
|
|
6
|
+
class PowService {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.wasmService = new WasmService();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async initialize() {
|
|
12
|
+
await this.wasmService.initialize(WASM_CONFIG.DEFAULT_PATH);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getPowResponse(token, targetPath) {
|
|
16
|
+
const headers = HeadersBuilder.getAuthHeaders(token);
|
|
17
|
+
const payload = { target_path: targetPath };
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(API_ENDPOINTS.CREATE_POW, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: headers,
|
|
23
|
+
body: JSON.stringify(payload)
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const errorData = await response.text();
|
|
28
|
+
console.error("PoW challenge error:", errorData);
|
|
29
|
+
throw new Error(`Failed to get PoW challenge: ${response.status}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
console.log("PoW challenge data:", data);
|
|
34
|
+
const challenge = data.data.biz_data.challenge;
|
|
35
|
+
|
|
36
|
+
if (!WASM_CONFIG.SUPPORTED_ALGORITHMS.includes(challenge.algorithm)) {
|
|
37
|
+
throw new Error(`Unsupported algorithm: ${challenge.algorithm}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const prefix = `${challenge.salt}_${challenge.expire_at}_`;
|
|
41
|
+
const answer = await this.wasmService.solve(
|
|
42
|
+
challenge.challenge,
|
|
43
|
+
prefix,
|
|
44
|
+
challenge.difficulty
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const powData = {
|
|
48
|
+
algorithm: challenge.algorithm,
|
|
49
|
+
answer: answer,
|
|
50
|
+
challenge: challenge.challenge,
|
|
51
|
+
difficulty: challenge.difficulty,
|
|
52
|
+
expire_at: challenge.expire_at,
|
|
53
|
+
salt: challenge.salt,
|
|
54
|
+
signature: challenge.signature,
|
|
55
|
+
target_path: challenge.target_path
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return EncodingUtils.encodeJSONToBase64(powData);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("Error in getPowResponse:", error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = PowService;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const {MemoryUtils} = require("../utils/memory");
|
|
4
|
+
const {EncodingUtils} = require("../utils/encoding");
|
|
5
|
+
|
|
6
|
+
class WasmService {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.instance = null;
|
|
9
|
+
this.memory = null;
|
|
10
|
+
this.addToStack = null;
|
|
11
|
+
this.alloc = null;
|
|
12
|
+
this.wasmSolve = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async initialize(wasmPath) {
|
|
16
|
+
const finalPath = path.resolve(process.cwd(), wasmPath);
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(finalPath)) {
|
|
19
|
+
throw new Error(`WASM file not found: ${finalPath}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const wasmBytes = await fs.promises.readFile(finalPath);
|
|
24
|
+
const wasmModule = await WebAssembly.compile(wasmBytes);
|
|
25
|
+
|
|
26
|
+
const importObject = {
|
|
27
|
+
env: {
|
|
28
|
+
abort: () => {
|
|
29
|
+
console.error("WASM aborted!");
|
|
30
|
+
throw new Error("WASM aborted");
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.instance = await WebAssembly.instantiate(wasmModule, importObject);
|
|
36
|
+
this.memory = new Uint8Array(this.instance.exports.memory.buffer);
|
|
37
|
+
this.addToStack = this.instance.exports.__wbindgen_add_to_stack_pointer;
|
|
38
|
+
this.alloc = this.instance.exports.__wbindgen_export_0;
|
|
39
|
+
this.wasmSolve = this.instance.exports.wasm_solve;
|
|
40
|
+
|
|
41
|
+
if (!this.memory || !this.addToStack || !this.alloc || !this.wasmSolve) {
|
|
42
|
+
throw new Error("Missing WASM export functions.");
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
throw new Error(`Failed to initialize WASM: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
writeMemory(offset, data) {
|
|
50
|
+
const view = MemoryUtils.createMemoryView(this.instance.exports.memory);
|
|
51
|
+
MemoryUtils.writeToMemory(view, offset, data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
readMemory(offset, size) {
|
|
55
|
+
const view = MemoryUtils.createMemoryView(this.instance.exports.memory);
|
|
56
|
+
return MemoryUtils.readFromMemory(view, offset, size);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
encodeString(text) {
|
|
60
|
+
const data = EncodingUtils.encodeUTF8(text);
|
|
61
|
+
const length = data.length;
|
|
62
|
+
const ptr = this.alloc(length, 1);
|
|
63
|
+
this.writeMemory(ptr, data);
|
|
64
|
+
return { ptr, length };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async solve(challengeStr, prefix, difficulty) {
|
|
68
|
+
if (!this.instance) {
|
|
69
|
+
throw new Error("WASM not initialized. Call initialize() first.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const retptr = this.addToStack(-16);
|
|
74
|
+
|
|
75
|
+
const { ptr: ptrChallenge, length: lenChallenge } = this.encodeString(challengeStr);
|
|
76
|
+
const { ptr: ptrPrefix, length: lenPrefix } = this.encodeString(prefix);
|
|
77
|
+
|
|
78
|
+
this.wasmSolve(
|
|
79
|
+
retptr,
|
|
80
|
+
ptrChallenge,
|
|
81
|
+
lenChallenge,
|
|
82
|
+
ptrPrefix,
|
|
83
|
+
lenPrefix,
|
|
84
|
+
parseFloat(difficulty)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const statusBytes = this.readMemory(retptr, 4);
|
|
88
|
+
if (statusBytes.length !== 4) {
|
|
89
|
+
this.addToStack(16);
|
|
90
|
+
throw new Error("Failed to read status bytes");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const view = MemoryUtils.createMemoryView(this.instance.exports.memory);
|
|
94
|
+
const status = MemoryUtils.readInt32(view, retptr);
|
|
95
|
+
|
|
96
|
+
const valueBytes = this.readMemory(retptr + 8, 8);
|
|
97
|
+
if (valueBytes.length !== 8) {
|
|
98
|
+
this.addToStack(16);
|
|
99
|
+
throw new Error("Failed to read result bytes");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const value = MemoryUtils.readFloat64(view, retptr + 8);
|
|
103
|
+
this.addToStack(16);
|
|
104
|
+
|
|
105
|
+
if (status === 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Math.floor(value);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.addToStack(16);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = WasmService;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete service for Readline terminal interface
|
|
3
|
+
* Enables Tab completion for in-chat CLI commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COMMANDS = [
|
|
7
|
+
'/help',
|
|
8
|
+
'/id',
|
|
9
|
+
'/url',
|
|
10
|
+
'/new',
|
|
11
|
+
'/sessions',
|
|
12
|
+
'/switch',
|
|
13
|
+
'/resume',
|
|
14
|
+
'/thinking',
|
|
15
|
+
'/think',
|
|
16
|
+
'/search',
|
|
17
|
+
'/web',
|
|
18
|
+
'/server',
|
|
19
|
+
'/exit',
|
|
20
|
+
'/quit'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a Readline completer function
|
|
25
|
+
* @param {string[]} [customCommands=[]] - Optional additional commands to autocomplete
|
|
26
|
+
* @returns {(line: string) => [string[], string]}
|
|
27
|
+
*/
|
|
28
|
+
function createCompleter(customCommands = []) {
|
|
29
|
+
const commandList = Array.from(new Set([...DEFAULT_COMMANDS, ...customCommands]));
|
|
30
|
+
|
|
31
|
+
return function completer(line) {
|
|
32
|
+
const trimmed = line.trimStart();
|
|
33
|
+
|
|
34
|
+
// Autocomplete only when user starts typing a command with '/'
|
|
35
|
+
if (trimmed.startsWith('/')) {
|
|
36
|
+
const hits = commandList.filter((cmd) => cmd.startsWith(trimmed));
|
|
37
|
+
return [hits.length ? hits : commandList, line];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Return empty if not a slash command
|
|
41
|
+
return [[], line];
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
DEFAULT_COMMANDS,
|
|
47
|
+
createCompleter
|
|
48
|
+
};
|