@tiens.nguyen/gonext-local-worker 1.0.12 → 1.0.15
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/gonext-local-worker.mjs +75 -5
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
* - `gonext-local-worker` starts polling loop
|
|
7
7
|
*/
|
|
8
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
9
10
|
import { homedir, platform } from "node:os";
|
|
10
11
|
import { join } from "node:path";
|
|
11
|
-
import {
|
|
12
|
+
import { promisify } from "node:util";
|
|
12
13
|
import dotenv from "dotenv";
|
|
14
|
+
|
|
15
|
+
/** Avoid `node:child_process/promises` — not available on some Node builds / older runtimes. */
|
|
16
|
+
const execFile = promisify(execFileCallback);
|
|
13
17
|
import OpenAI from "openai";
|
|
14
18
|
|
|
15
19
|
const ENV_FILE = join(homedir(), ".gonext", "worker.env");
|
|
@@ -141,25 +145,91 @@ async function runChatJob(job) {
|
|
|
141
145
|
apiKey: payload.apiKey || "ollama",
|
|
142
146
|
});
|
|
143
147
|
|
|
148
|
+
let buf = "";
|
|
149
|
+
let flushTimer = null;
|
|
150
|
+
let fullText = "";
|
|
151
|
+
|
|
152
|
+
const flushChunks = async () => {
|
|
153
|
+
flushTimer = null;
|
|
154
|
+
const t = buf;
|
|
155
|
+
buf = "";
|
|
156
|
+
if (!t) return;
|
|
157
|
+
const res = await workerFetch(`/api/worker/jobs/${jobId}/chunk`, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
body: JSON.stringify({ text: t }),
|
|
160
|
+
});
|
|
161
|
+
if (!res.ok && res.status !== 204) {
|
|
162
|
+
console.error(`[gonext-worker] chunk POST failed ${res.status} for ${jobId}`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const enqueueText = (s) => {
|
|
167
|
+
if (!s) return;
|
|
168
|
+
fullText += s;
|
|
169
|
+
buf += s;
|
|
170
|
+
if (!flushTimer) {
|
|
171
|
+
flushTimer = setTimeout(() => void flushChunks(), 12);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
144
175
|
try {
|
|
145
|
-
const
|
|
176
|
+
const stream = await client.chat.completions.create({
|
|
146
177
|
model: payload.modelId,
|
|
147
178
|
messages: toOpenAIMessages(payload.messages),
|
|
179
|
+
stream: true,
|
|
148
180
|
temperature: 0,
|
|
149
181
|
});
|
|
150
|
-
|
|
182
|
+
|
|
183
|
+
let tokenCount = 0;
|
|
184
|
+
let isStartThinking = false;
|
|
185
|
+
let isEndThinking = false;
|
|
186
|
+
|
|
187
|
+
for await (const chunk of stream) {
|
|
188
|
+
const delta = chunk.choices[0]?.delta;
|
|
189
|
+
const content = delta?.content ?? "";
|
|
190
|
+
const reasoningContent = delta?.reasoning_content;
|
|
191
|
+
tokenCount += 1;
|
|
192
|
+
|
|
193
|
+
if (reasoningContent) {
|
|
194
|
+
if (!isStartThinking) {
|
|
195
|
+
isStartThinking = true;
|
|
196
|
+
enqueueText("<think>");
|
|
197
|
+
}
|
|
198
|
+
enqueueText(reasoningContent);
|
|
199
|
+
} else {
|
|
200
|
+
if (isStartThinking && !isEndThinking) {
|
|
201
|
+
isEndThinking = true;
|
|
202
|
+
enqueueText("</think>");
|
|
203
|
+
}
|
|
204
|
+
if (content) {
|
|
205
|
+
enqueueText(content);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (flushTimer) {
|
|
211
|
+
clearTimeout(flushTimer);
|
|
212
|
+
flushTimer = null;
|
|
213
|
+
}
|
|
214
|
+
await flushChunks();
|
|
215
|
+
|
|
151
216
|
const totalTimeSeconds = (Date.now() - start) / 1000;
|
|
152
217
|
await workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
153
218
|
method: "PATCH",
|
|
154
219
|
body: JSON.stringify({
|
|
155
220
|
jobStatus: "completed",
|
|
156
|
-
resultText:
|
|
157
|
-
tokenCount: 1,
|
|
221
|
+
resultText: fullText,
|
|
222
|
+
tokenCount: Math.max(1, tokenCount),
|
|
158
223
|
totalTimeSeconds,
|
|
159
224
|
}),
|
|
160
225
|
});
|
|
161
226
|
console.log(`[gonext-worker] completed ${jobId} (${totalTimeSeconds.toFixed(1)}s)`);
|
|
162
227
|
} catch (e) {
|
|
228
|
+
if (flushTimer) {
|
|
229
|
+
clearTimeout(flushTimer);
|
|
230
|
+
flushTimer = null;
|
|
231
|
+
}
|
|
232
|
+
await flushChunks().catch(() => {});
|
|
163
233
|
const message = e instanceof Error ? e.message : String(e);
|
|
164
234
|
await workerFetch(`/api/worker/jobs/${jobId}`, {
|
|
165
235
|
method: "PATCH",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|