@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.
@@ -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 { execFile } from "node:child_process/promises";
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 completion = await client.chat.completions.create({
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
- const text = completion.choices[0]?.message?.content ?? "";
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: text,
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.12",
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",