openaxies 0.5.1 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -10,32 +10,43 @@ const h = React.createElement;
10
10
 
11
11
  export default AppRoot;
12
12
 
13
+ const LOGO = [
14
+ ' \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
15
+ '\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 ',
16
+ '\u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
17
+ '\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588',
18
+ ' \u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 ',
19
+ ];
20
+
21
+ const LOGO_COLORS = ['#00F0FF', '#00DDFF', '#00CCFF', '#00BBFF', '#00AAFF'];
22
+
13
23
  function buildHistory(messages, newText) {
14
- const history = [];
24
+ const h = [];
15
25
  for (let i = 0; i < messages.length; i++) {
16
26
  const m = messages[i];
17
27
  if (m === null || m === undefined || typeof m !== 'object') continue;
18
28
  if (m.role === 'user' || m.role === 'assistant') {
19
- const content = typeof m.content === 'string' ? m.content : '';
20
- history.push({ role: m.role, content: content });
29
+ const c = typeof m.content === 'string' ? m.content : '';
30
+ h.push({ role: m.role, content: c });
21
31
  }
22
32
  }
23
33
  if (typeof newText === 'string' && newText.length > 0) {
24
- history.push({ role: 'user', content: newText });
34
+ h.push({ role: 'user', content: newText });
25
35
  }
26
- return history;
36
+ return h;
27
37
  }
28
38
 
29
- function formatTimer(seconds) {
30
- if (typeof seconds !== 'number' || seconds < 0) return '0.0s';
31
- return seconds.toFixed(1) + 's';
39
+ function formatTimer(s) {
40
+ if (typeof s !== 'number' || s < 0) return '0.0s';
41
+ return s.toFixed(1) + 's';
32
42
  }
33
43
 
34
- function hr(cols) {
44
+ function hr(cols, color) {
35
45
  const n = typeof cols === 'number' && cols > 0 ? cols : 80;
46
+ const c = typeof color === 'string' ? color : '#1A1A28';
36
47
  let s = '';
37
48
  for (let i = 0; i < n; i++) s = s + '\u2500';
38
- return h(Text, { color: '#1A1A28' }, s);
49
+ return h(Text, { color: c }, s);
39
50
  }
40
51
 
41
52
  const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4'];
@@ -58,8 +69,9 @@ function AppRoot() {
58
69
  const [toolInfo, setToolInfo] = React.useState(null);
59
70
 
60
71
  const commands = getCommands();
72
+ const LOGO_H = 5;
61
73
  const overlayH = showOverlay ? 6 : 0;
62
- const fixedH = 1 + overlayH + DOCK_HEIGHT;
74
+ const fixedH = LOGO_H + overlayH + DOCK_HEIGHT;
63
75
  const availLines = Math.max(1, rows - fixedH);
64
76
 
65
77
  const abortRef = React.useRef(null);
@@ -124,12 +136,10 @@ function AppRoot() {
124
136
  async function handleSubmit(text) {
125
137
  const safe = typeof text === 'string' ? text : '';
126
138
  if (safe.length === 0 || streamingActive === true) return;
127
-
128
139
  setStreamingActive(true);
129
140
  setToolInfo(null);
130
141
  setStreamText(' connecting');
131
142
  setThinkingElapsed(0);
132
-
133
143
  connRef.current = setInterval(function () {
134
144
  setStreamText(function (p) {
135
145
  if (p === ' connecting') return ' connecting.';
@@ -138,52 +148,36 @@ function AppRoot() {
138
148
  return ' connecting';
139
149
  });
140
150
  }, 400);
141
-
142
151
  setMessages(function (p) {
143
152
  return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]);
144
153
  });
145
154
  setInputBuffer('');
146
-
147
155
  const modelInfo = getModelById(activeModel);
148
156
  if (modelInfo === null) {
149
- if (connRef.current !== null) {
150
- clearInterval(connRef.current);
151
- connRef.current = null;
152
- }
157
+ if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
153
158
  setStreamingActive(false);
154
159
  setStreamText('');
155
- setMessages(function (p) {
156
- return p.concat([{ role: 'assistant', content: 'No model selected. Use /model.', id: 'e-' + Date.now() }]);
157
- });
160
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected. Use /model.', id: 'e-' + Date.now() }]); });
158
161
  return;
159
162
  }
160
-
161
163
  const history = buildHistory(messages, safe);
162
164
  const abortController = new AbortController();
163
165
  abortRef.current = abortController;
164
-
165
166
  try {
166
167
  const gen = callModel(modelInfo, history, abortController.signal);
167
168
  let accumulated = '';
168
169
  let firstEvent = true;
169
170
  let thinkStarted = false;
170
-
171
171
  for await (const event of gen) {
172
172
  if (abortController.signal.aborted === true) break;
173
-
174
173
  if (firstEvent === true) {
175
174
  firstEvent = false;
176
- if (connRef.current !== null) {
177
- clearInterval(connRef.current);
178
- connRef.current = null;
179
- }
175
+ if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
180
176
  }
181
-
182
177
  if (event.type === 'tool') {
183
178
  setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query });
184
179
  continue;
185
180
  }
186
-
187
181
  if (event.type === 'thinking' || event.type === 'token') {
188
182
  const content = typeof event.content === 'string' ? event.content : '';
189
183
  for (let i = 0; i < content.length; i++) {
@@ -204,53 +198,33 @@ function AppRoot() {
204
198
  await new Promise(function (r) { setTimeout(r, 0); });
205
199
  }
206
200
  }
207
-
208
201
  if (event.type === 'done') break;
209
202
  if (event.type === 'timeout') {
210
- if (accumulated.length > 0) {
211
- accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
212
- }
203
+ if (accumulated.length > 0) accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
213
204
  setStreamText(accumulated);
214
205
  break;
215
206
  }
216
207
  }
217
-
218
- if (connRef.current !== null) {
219
- clearInterval(connRef.current);
220
- connRef.current = null;
221
- }
208
+ if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
222
209
  setIsThinking(false);
223
210
  thinkStartRef.current = null;
224
211
  setThinkingElapsed(0);
225
212
  setStreamingActive(false);
226
-
227
213
  const finalText = streamText;
228
214
  setStreamText('');
229
-
230
215
  if (finalText.length > 0) {
231
- setMessages(function (p) {
232
- return p.concat([{ role: 'assistant', content: finalText, id: 'a-' + Date.now() }]);
233
- });
216
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: finalText, id: 'a-' + Date.now() }]); });
234
217
  }
235
218
  } catch (err) {
236
- if (connRef.current !== null) {
237
- clearInterval(connRef.current);
238
- connRef.current = null;
239
- }
219
+ if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
240
220
  setIsThinking(false);
241
221
  thinkStartRef.current = null;
242
222
  setThinkingElapsed(0);
243
223
  setStreamingActive(false);
244
224
  setStreamText('');
245
- const errMsg = err !== null && err !== undefined
246
- ? (err.message || String(err))
247
- : 'Unknown error';
225
+ const errMsg = err !== null && err !== undefined ? (err.message || String(err)) : 'Unknown error';
248
226
  setMessages(function (p) {
249
- return p.concat([{
250
- role: 'assistant',
251
- content: 'Error: ' + errMsg + '\n\nSpace cold-starting? Try again.',
252
- id: 'er-' + Date.now(),
253
- }]);
227
+ return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg + '\n\nSpace cold-starting? Try again.', id: 'er-' + Date.now() }]);
254
228
  });
255
229
  }
256
230
  }
@@ -258,13 +232,8 @@ function AppRoot() {
258
232
  function handleChange(v) {
259
233
  const s = typeof v === 'string' ? v : '';
260
234
  setInputBuffer(s);
261
- if (s.length === 1 && s[0] === '/' && showOverlay === false) {
262
- setShowOverlay(true);
263
- setOverlayIndex(0);
264
- }
265
- if (showOverlay === true && (s.length === 0 || s[0] !== '/')) {
266
- setShowOverlay(false);
267
- }
235
+ if (s.length === 1 && s[0] === '/' && showOverlay === false) { setShowOverlay(true); setOverlayIndex(0); }
236
+ if (showOverlay === true && (s.length === 0 || s[0] !== '/')) { setShowOverlay(false); }
268
237
  }
269
238
 
270
239
  function handleCmd(item) {
@@ -275,9 +244,7 @@ function AppRoot() {
275
244
  if (t === '/clear') setMessages([]);
276
245
  if (t === '/model') cycleModel();
277
246
  if (t === '/help') {
278
- setMessages(function (p) {
279
- return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /exit', id: 'h-' + Date.now() }]);
280
- });
247
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /exit', id: 'h-' + Date.now() }]); });
281
248
  }
282
249
  setInputBuffer('');
283
250
  setShowOverlay(false);
@@ -286,48 +253,33 @@ function AppRoot() {
286
253
 
287
254
  useInput(function handleInput(input, key) {
288
255
  if (showOverlay === true) {
289
- if (key.escape === true) {
290
- setShowOverlay(false);
291
- setInputBuffer('');
292
- setOverlayIndex(0);
293
- return;
294
- }
295
- if (key.upArrow === true) {
296
- setOverlayIndex(function (p) { return p > 0 ? p - 1 : commands.length - 1; });
297
- return;
298
- }
299
- if (key.downArrow === true) {
300
- setOverlayIndex(function (p) { return p < commands.length - 1 ? p + 1 : 0; });
301
- return;
302
- }
303
- if (key.return === true) {
304
- const cmd = commands[overlayIndex];
305
- if (cmd !== null && cmd !== undefined) handleCmd({ value: cmd.trigger });
306
- return;
307
- }
256
+ if (key.escape === true) { setShowOverlay(false); setInputBuffer(''); setOverlayIndex(0); return; }
257
+ if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : commands.length - 1; }); return; }
258
+ if (key.downArrow === true) { setOverlayIndex(function (p) { return p < commands.length - 1 ? p + 1 : 0; }); return; }
259
+ if (key.return === true) { const cmd = commands[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd({ value: cmd.trigger }); return; }
308
260
  return;
309
261
  }
310
-
311
262
  if (key.return === true) {
312
263
  const s = typeof inputBuffer === 'string' ? inputBuffer : '';
313
264
  if (s.length > 0 && streamingActive === false) handleSubmit(s);
314
265
  return;
315
266
  }
316
-
317
267
  if (key.ctrl === true && (input === 'c' || input === 'C')) {
318
- if (abortRef.current !== null) {
319
- try { abortRef.current.abort(); } catch (_) {}
320
- abortRef.current = null;
321
- setStreamingActive(false);
322
- setStreamText('');
323
- }
268
+ if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; setStreamingActive(false); setStreamText(''); }
324
269
  process.exit(0);
325
270
  }
326
271
  });
327
272
 
328
273
  const activeInfo = getModelById(activeModel);
329
274
  const activeLabel = activeInfo !== null ? activeInfo.label : 'OpenAxies';
330
- const activeBadge = activeInfo !== null ? activeInfo.badge : '';
275
+
276
+ const logoEls = [];
277
+ for (let r = 0; r < LOGO.length; r++) {
278
+ logoEls.push(
279
+ h(Text, { key: 'l' + r, color: LOGO_COLORS[r % LOGO_COLORS.length], bold: true }, LOGO[r])
280
+ );
281
+ }
282
+ const logo = h(Box, { flexDirection: 'column', width: '100%', flexShrink: 0, paddingLeft: 0 }, ...logoEls);
331
283
 
332
284
  const viewLines = [];
333
285
  for (let i = 0; i < messages.length; i++) {
@@ -335,11 +287,8 @@ function AppRoot() {
335
287
  if (msg === null || typeof msg !== 'object') continue;
336
288
  const content = typeof msg.content === 'string' ? msg.content : '';
337
289
  if (content.length === 0) continue;
338
-
339
290
  if (msg.role === 'user') {
340
- if (i > 0) viewLines.push({ t: 'sep' });
341
- viewLines.push({ t: 'user', text: content });
342
- viewLines.push({ t: 'sep' });
291
+ viewLines.push({ t: 'user', text: '\u25B7 ' + content });
343
292
  } else {
344
293
  viewLines.push({ t: 'text', text: content });
345
294
  }
@@ -347,7 +296,6 @@ function AppRoot() {
347
296
 
348
297
  const hasStream = typeof streamText === 'string' && streamText.length > 0;
349
298
  if (hasStream === true) {
350
- viewLines.push({ t: 'sep' });
351
299
  if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
352
300
  viewLines.push({ t: 'tool', text: '\u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)' });
353
301
  }
@@ -362,7 +310,10 @@ function AppRoot() {
362
310
  }
363
311
 
364
312
  if (viewLines.length === 0) {
365
- viewLines.push({ t: 'idle', text: ' start typing to chat...' });
313
+ viewLines.push({ t: 'idle', text: ' \u25B8 type a message to begin | /model to switch models | /help for commands' });
314
+ viewLines.push({ t: 'idle', text: '' });
315
+ viewLines.push({ t: 'idle-sm', text: ' Models: OpenAxies Llama | OpenAxies GPT | OpenAxies DeepSeek' });
316
+ viewLines.push({ t: 'idle-sm', text: ' Current: ' + activeLabel });
366
317
  }
367
318
 
368
319
  const visible = Math.max(1, availLines - 1);
@@ -371,23 +322,21 @@ function AppRoot() {
371
322
 
372
323
  for (let i = 0; i < display.length; i++) {
373
324
  const l = display[i];
374
- if (l.t === 'sep') {
375
- viewEls.push(h(Box, { key: 's-' + i, height: 1 }, hr(cols)));
376
- } else if (l.t === 'think') {
325
+ if (l.t === 'think') {
377
326
  viewEls.push(
378
- h(Box, { key: 'k-' + i, height: 1, paddingLeft: 1 },
379
- h(Text, { color: '#FF9F43', bold: true }, ' ' + l.spin + ' Thinking \u2022 ' + l.elapsed)
327
+ h(Box, { key: 'k-' + i, height: 1, paddingLeft: 2 },
328
+ h(Text, { color: '#FF9F43', bold: true }, l.spin + ' Thinking \u2022 ' + l.elapsed)
380
329
  )
381
330
  );
382
331
  } else if (l.t === 'think-text') {
383
332
  viewEls.push(
384
- h(Box, { key: 'kt-' + i, height: 1, paddingLeft: 2 },
333
+ h(Box, { key: 'kt-' + i, height: 1, paddingLeft: 3 },
385
334
  h(Text, { color: '#FF9F43' }, l.text)
386
335
  )
387
336
  );
388
337
  } else if (l.t === 'tool') {
389
338
  viewEls.push(
390
- h(Box, { key: 'tl-' + i, height: 1, paddingLeft: 1 },
339
+ h(Box, { key: 'tl-' + i, height: 1, paddingLeft: 2 },
391
340
  h(Text, { color: '#5B5B8A' }, l.text)
392
341
  )
393
342
  );
@@ -399,13 +348,19 @@ function AppRoot() {
399
348
  );
400
349
  } else if (l.t === 'text') {
401
350
  viewEls.push(
402
- h(Box, { key: 'a-' + i, height: 1, paddingLeft: 1 },
351
+ h(Box, { key: 'a-' + i, height: 1, paddingLeft: 2 },
403
352
  h(Text, { color: hex.primary }, l.text)
404
353
  )
405
354
  );
406
355
  } else if (l.t === 'idle') {
407
356
  viewEls.push(
408
357
  h(Box, { key: 'i-' + i, height: 1, paddingLeft: 1 },
358
+ h(Text, { color: '#555577' }, l.text)
359
+ )
360
+ );
361
+ } else if (l.t === 'idle-sm') {
362
+ viewEls.push(
363
+ h(Box, { key: 'is-' + i, height: 1, paddingLeft: 1 },
409
364
  h(Text, { color: '#333355' }, l.text)
410
365
  )
411
366
  );
@@ -417,12 +372,11 @@ function AppRoot() {
417
372
  height: availLines,
418
373
  flexDirection: 'column',
419
374
  overflow: 'hidden',
420
- paddingBottom: 1,
375
+ paddingTop: 0,
376
+ paddingBottom: 0,
421
377
  }, ...viewEls);
422
378
 
423
- const slashOverlay = showOverlay === true
424
- ? createSlashOverlay(commands, overlayIndex)
425
- : null;
379
+ const slashOverlay = showOverlay === true ? createSlashOverlay(commands, overlayIndex) : null;
426
380
 
427
381
  const dock = createComposerDock({
428
382
  value: inputBuffer,
@@ -442,7 +396,8 @@ function AppRoot() {
442
396
  backgroundColor: hex.black,
443
397
  overflow: 'hidden',
444
398
  },
445
- h(Box, { height: 1 }, hr(cols)),
399
+ logo,
400
+ h(Box, { height: 1 }, hr(cols, '#1A1A2E')),
446
401
  viewport,
447
402
  slashOverlay,
448
403
  dock
@@ -5,7 +5,7 @@ import { hex } from '../config/theme.js';
5
5
 
6
6
  const h = React.createElement;
7
7
 
8
- export const DOCK_HEIGHT = 3;
8
+ export const DOCK_HEIGHT = 4;
9
9
 
10
10
  const PROMPT = '> ';
11
11
 
@@ -21,8 +21,10 @@ export function createComposerDock(config) {
21
21
  const isStreaming = config.isStreaming === true;
22
22
  const modelLabel = typeof config.modelLabel === 'string' ? config.modelLabel : '';
23
23
  const modelStatus = config.modelStatus === true;
24
+ const safeCols = typeof config.terminalWidth === 'number' && config.terminalWidth > 0 ? config.terminalWidth : 80;
24
25
 
25
26
  const placeholder = isStreaming ? 'Ctrl+C to interrupt...' : 'type a message...';
27
+ const innerW = safeCols - 2;
26
28
 
27
29
  return h(Box, {
28
30
  width: '100%',
@@ -30,8 +32,12 @@ export function createComposerDock(config) {
30
32
  flexDirection: 'column',
31
33
  flexShrink: 0,
32
34
  },
35
+ h(Box, { height: 1, paddingLeft: 1 },
36
+ h(Text, { color: '#1A1A28' }, '\u2500'.repeat(innerW))
37
+ ),
33
38
  h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
34
- h(Text, { color: inputActive ? hex.primary : '#444466' }, PROMPT),
39
+ h(Text, { color: hex.border }, '\u2502'),
40
+ h(Text, { color: inputActive ? hex.neonBlue : '#444466', bold: true }, ' ' + PROMPT),
35
41
  h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
36
42
  h(TextInput, {
37
43
  value: value,
@@ -42,13 +48,19 @@ export function createComposerDock(config) {
42
48
  showCursor: true,
43
49
  })
44
50
  ),
45
- h(Text, { color: '#444466' }, ' '),
46
- h(Text, { color: modelStatus ? hex.greenOnline : '#444466', bold: modelStatus }, modelLabel || '')
51
+ h(Text, { color: '#444466' }, ' '),
52
+ h(Text, { color: hex.border }, '\u2502')
47
53
  ),
48
54
  h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
49
- h(Text, { color: '#333344' }, '/help /clear /model /exit'),
55
+ h(Text, { color: hex.border }, '\u2514'),
56
+ h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
57
+ h(Text, { color: '#333344' }, '\u2500'.repeat(innerW - 2))
58
+ ),
59
+ h(Text, { color: hex.border }, '\u2518')
60
+ ),
61
+ h(Box, { height: 1, paddingRight: 1 },
50
62
  h(Box, { flexGrow: 1 }),
51
- h(Text, { color: '#333344' }, isStreaming ? 'Ctrl+C to stop' : '')
63
+ h(Text, { color: '#444466', bold: modelStatus }, modelLabel || '')
52
64
  )
53
65
  );
54
66
  }
@@ -82,6 +82,7 @@ export async function* callModel(modelConfig, messages, signal) {
82
82
  messages: modelMessages,
83
83
  max_tokens: 4096,
84
84
  temperature: 0.7,
85
+ stream: true,
85
86
  };
86
87
 
87
88
  let lastError = null;
@@ -78,7 +78,7 @@ export async function* streamResponse(endpoint, body, signal) {
78
78
  }, READ_TIMEOUT);
79
79
  }
80
80
 
81
- resetReadTimeout();
81
+ let firstChunk = true;
82
82
 
83
83
  try {
84
84
  while (true) {
@@ -86,12 +86,48 @@ export async function* streamResponse(endpoint, body, signal) {
86
86
  resetReadTimeout();
87
87
 
88
88
  if (result.done === true) {
89
+ if (buffer.trim().length > 0 && firstChunk === false) {
90
+ try {
91
+ const parsed = JSON.parse(buffer);
92
+ const choice = parsed.choices && parsed.choices[0];
93
+ if (choice !== null && choice !== undefined) {
94
+ const msg = choice.message || choice.delta;
95
+ if (msg !== null && msg !== undefined) {
96
+ if (typeof msg.content === 'string' && msg.content.length > 0) {
97
+ yield { type: 'token', content: msg.content };
98
+ }
99
+ }
100
+ }
101
+ } catch (_) {
102
+ }
103
+ }
89
104
  yield { type: 'done' };
90
105
  return;
91
106
  }
92
107
 
93
108
  const chunk = decoder.decode(result.value, { stream: true });
94
109
  buffer = buffer + chunk;
110
+
111
+ if (firstChunk === true && buffer.includes('data: ') === false && buffer.trim().startsWith('{') === true) {
112
+ try {
113
+ const parsed = JSON.parse(buffer);
114
+ const choice = parsed.choices && parsed.choices[0];
115
+ if (choice !== null && choice !== undefined) {
116
+ const msg = choice.message || choice.delta;
117
+ if (msg !== null && msg !== undefined) {
118
+ if (typeof msg.content === 'string' && msg.content.length > 0) {
119
+ yield { type: 'token', content: msg.content };
120
+ }
121
+ }
122
+ }
123
+ yield { type: 'done' };
124
+ return;
125
+ } catch (_) {
126
+ }
127
+ }
128
+
129
+ firstChunk = false;
130
+
95
131
  const lines = buffer.split('\n');
96
132
  buffer = lines.pop() || '';
97
133