lonny-agent 0.2.3 → 0.2.6
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/README.md +326 -326
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/llm.d.ts +4 -0
- package/dist/agent/llm.d.ts.map +1 -1
- package/dist/agent/project.d.ts +26 -0
- package/dist/agent/project.d.ts.map +1 -0
- package/dist/agent/project.js +303 -0
- package/dist/agent/project.js.map +1 -0
- package/dist/agent/prompt-builder.d.ts +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +22 -13
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/anthropic.d.ts.map +1 -1
- package/dist/agent/providers/anthropic.js +20 -2
- package/dist/agent/providers/anthropic.js.map +1 -1
- package/dist/agent/providers/google.d.ts.map +1 -1
- package/dist/agent/providers/google.js +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/ollama.d.ts.map +1 -1
- package/dist/agent/providers/ollama.js +2 -1
- package/dist/agent/providers/ollama.js.map +1 -1
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +63 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts +8 -2
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +250 -153
- package/dist/agent/session.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +7 -1
- package/dist/config/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +1 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +875 -371
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +32 -0
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/sed.test.d.ts +2 -0
- package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sed.test.js +228 -0
- package/dist/tools/__tests__/sed.test.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +83 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +58 -0
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +366 -149
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +54 -30
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/read.d.ts +6 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +42 -15
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/registry.d.ts +0 -6
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -33
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sed.d.ts +4 -0
- package/dist/tools/sed.d.ts.map +1 -0
- package/dist/tools/sed.js +121 -0
- package/dist/tools/sed.js.map +1 -0
- package/dist/tools/write_plan.d.ts +1 -1
- package/dist/tools/write_plan.d.ts.map +1 -1
- package/dist/tools/write_plan.js +14 -4
- package/dist/tools/write_plan.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +5 -4
- package/dist/tui/index.js.map +1 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +30 -7
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +5 -855
- package/dist/web/public/confirm.js +70 -0
- package/dist/web/public/index.html +121 -120
- package/dist/web/public/input.js +107 -0
- package/dist/web/public/messages.js +395 -0
- package/dist/web/public/sidebar.js +82 -0
- package/dist/web/public/state.js +72 -0
- package/dist/web/public/style.css +996 -949
- package/dist/web/public/utils.js +37 -0
- package/dist/web/public/websocket.js +267 -0
- package/dist/web/public/ws.js +19 -0
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +3 -7
- package/dist/web/session-bridge.js.map +1 -1
- package/package.json +55 -54
- package/scripts/copy-native.mjs +24 -0
- package/scripts/copy-web.mjs +15 -0
- package/src/agent/index.ts +25 -25
- package/src/agent/llm.ts +4 -0
- package/src/agent/project.ts +366 -0
- package/src/agent/prompt-builder.ts +23 -13
- package/src/agent/providers/anthropic.ts +179 -172
- package/src/agent/providers/google.ts +210 -211
- package/src/agent/providers/ollama.ts +186 -182
- package/src/agent/providers/openai.ts +73 -5
- package/src/agent/session.ts +275 -153
- package/src/config/index.ts +342 -335
- package/src/tools/__tests__/bash.test.ts +1 -1
- package/src/tools/__tests__/edit.test.ts +981 -385
- package/src/tools/__tests__/grep.test.ts +35 -0
- package/src/tools/bash.ts +97 -11
- package/src/tools/edit.ts +414 -154
- package/src/tools/git.ts +76 -76
- package/src/tools/grep.ts +57 -30
- package/src/tools/read.ts +61 -25
- package/src/tools/registry.ts +231 -265
- package/src/tools/write_plan.ts +21 -5
- package/src/tui/index.ts +786 -785
- package/src/web/index.ts +29 -7
- package/src/web/public/app.js +5 -855
- package/src/web/public/confirm.js +70 -0
- package/src/web/public/index.html +121 -120
- package/src/web/public/input.js +107 -0
- package/src/web/public/messages.js +395 -0
- package/src/web/public/sidebar.js +82 -0
- package/src/web/public/state.js +72 -0
- package/src/web/public/style.css +996 -949
- package/src/web/public/utils.js +37 -0
- package/src/web/public/websocket.js +267 -0
- package/src/web/public/ws.js +19 -0
- package/src/web/session-bridge.ts +193 -194
- package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
- package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
- package/.lonny/plan-web-cwd-status.md +0 -38
- package/src/tools/exec.ts +0 -348
package/dist/agent/session.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as os from 'node:os';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { saveTokenUsage } from '../config/tokens.js';
|
|
6
6
|
import { FileReadTracker } from '../diff/apply.js';
|
|
7
|
+
import { fmtErr } from '../tools/errors.js';
|
|
7
8
|
import { ToolRegistry } from '../tools/registry.js';
|
|
8
9
|
import { compact, shouldCompact } from './compaction.js';
|
|
9
10
|
import { EventChannels, getGlobalEventBus } from './event-bus.js';
|
|
@@ -179,14 +180,33 @@ export function formatToolInput(tc) {
|
|
|
179
180
|
}
|
|
180
181
|
return parts.join(' \u2502 ');
|
|
181
182
|
}
|
|
182
|
-
function printTokenStats(turnIn, turnOut, totalIn, totalOut, turnApi, totalApi, output) {
|
|
183
|
+
function printTokenStats(turnIn, turnOut, totalIn, totalOut, turnApi, totalApi, output, turnCacheHit, turnCacheMiss, totalCacheHit, totalCacheMiss) {
|
|
183
184
|
const bus = getGlobalEventBus();
|
|
184
|
-
bus.emit(EventChannels.TOKEN_STATS, {
|
|
185
|
+
bus.emit(EventChannels.TOKEN_STATS, {
|
|
186
|
+
turnIn,
|
|
187
|
+
turnOut,
|
|
188
|
+
totalIn,
|
|
189
|
+
totalOut,
|
|
190
|
+
turnApi,
|
|
191
|
+
totalApi,
|
|
192
|
+
turnCacheHit,
|
|
193
|
+
turnCacheMiss,
|
|
194
|
+
totalCacheHit,
|
|
195
|
+
totalCacheMiss,
|
|
196
|
+
});
|
|
185
197
|
// Skip terminal output in Web UI mode
|
|
186
198
|
if (output?.suppressToolOutput)
|
|
187
199
|
return;
|
|
188
200
|
const total = totalIn + totalOut;
|
|
189
|
-
|
|
201
|
+
let msg = ` ${GY}┃${RS} ${GY}${BLD}▴${RS}${GY}${turnIn}${RS} ${GY}${BLD}▾${RS}${GY}${turnOut}${RS} ${GY}total${RS} ${total} ${GY}calls${RS} ${turnApi}(${totalApi})`;
|
|
202
|
+
// Show cache hit rate if available
|
|
203
|
+
const cacheHit = totalCacheHit ?? 0;
|
|
204
|
+
const cacheMiss = totalCacheMiss ?? 0;
|
|
205
|
+
const cacheTotal = cacheHit + cacheMiss;
|
|
206
|
+
if (cacheTotal > 0) {
|
|
207
|
+
const pct = Math.round((cacheHit / cacheTotal) * 100);
|
|
208
|
+
msg += ` ${GY}cached${RS} ${pct}%`;
|
|
209
|
+
}
|
|
190
210
|
writeOut(`\n${msg}\n`, output);
|
|
191
211
|
}
|
|
192
212
|
export class Session {
|
|
@@ -216,6 +236,10 @@ export class Session {
|
|
|
216
236
|
turnOutputTokens = 0;
|
|
217
237
|
turnApiCalls = 0;
|
|
218
238
|
totalApiCalls = 0;
|
|
239
|
+
turnCacheHitTokens = 0;
|
|
240
|
+
turnCacheMissTokens = 0;
|
|
241
|
+
totalCacheHitTokens = 0;
|
|
242
|
+
totalCacheMissTokens = 0;
|
|
219
243
|
stopped = false;
|
|
220
244
|
abortController = null;
|
|
221
245
|
constructor(config, output) {
|
|
@@ -241,7 +265,16 @@ export class Session {
|
|
|
241
265
|
else {
|
|
242
266
|
this.provider = new AnthropicProvider(config.apiKey, config.baseUrl, config.model);
|
|
243
267
|
}
|
|
244
|
-
|
|
268
|
+
// Placeholder until initSystemPrompt() is called
|
|
269
|
+
this.messages = [{ role: 'system', content: '' }];
|
|
270
|
+
// Initialize system prompt asynchronously
|
|
271
|
+
this.initSystemPrompt(config);
|
|
272
|
+
}
|
|
273
|
+
/** Initialize the system prompt asynchronously */
|
|
274
|
+
initSystemPrompt(config) {
|
|
275
|
+
buildSystemPrompt(config).then(prompt => {
|
|
276
|
+
this.messages = [{ role: 'system', content: prompt }];
|
|
277
|
+
});
|
|
245
278
|
}
|
|
246
279
|
/** Persist the current session to ~/.lonny/sessions/ */
|
|
247
280
|
save() {
|
|
@@ -254,6 +287,8 @@ export class Session {
|
|
|
254
287
|
totalInputTokens: this.totalInputTokens,
|
|
255
288
|
totalOutputTokens: this.totalOutputTokens,
|
|
256
289
|
totalApiCalls: this.totalApiCalls,
|
|
290
|
+
totalCacheHitTokens: this.totalCacheHitTokens || undefined,
|
|
291
|
+
totalCacheMissTokens: this.totalCacheMissTokens || undefined,
|
|
257
292
|
mode: this.config.mode,
|
|
258
293
|
model: this.config.model,
|
|
259
294
|
provider: this.config.provider,
|
|
@@ -262,7 +297,7 @@ export class Session {
|
|
|
262
297
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
263
298
|
}
|
|
264
299
|
/** Try to load a saved session for the given cwd. Returns null if none exists. */
|
|
265
|
-
static load(config, output) {
|
|
300
|
+
static async load(config, output) {
|
|
266
301
|
const filePath = getSessionFilePath(config.cwd);
|
|
267
302
|
let data;
|
|
268
303
|
try {
|
|
@@ -285,12 +320,15 @@ export class Session {
|
|
|
285
320
|
if (data.model !== config.model ||
|
|
286
321
|
data.provider !== config.provider ||
|
|
287
322
|
data.mode !== config.mode) {
|
|
288
|
-
|
|
323
|
+
const prompt = await buildSystemPrompt(config);
|
|
324
|
+
session.messages[0] = { role: 'system', content: prompt };
|
|
289
325
|
}
|
|
290
326
|
// Restore token stats
|
|
291
327
|
session.totalInputTokens = data.totalInputTokens;
|
|
292
328
|
session.totalOutputTokens = data.totalOutputTokens;
|
|
293
329
|
session.totalApiCalls = data.totalApiCalls;
|
|
330
|
+
session.totalCacheHitTokens = data.totalCacheHitTokens ?? 0;
|
|
331
|
+
session.totalCacheMissTokens = data.totalCacheMissTokens ?? 0;
|
|
294
332
|
return session;
|
|
295
333
|
}
|
|
296
334
|
/** Remove the saved session file for the given cwd. */
|
|
@@ -303,9 +341,10 @@ export class Session {
|
|
|
303
341
|
// Ignore if file doesn't exist
|
|
304
342
|
}
|
|
305
343
|
}
|
|
306
|
-
setMode(mode) {
|
|
344
|
+
async setMode(mode) {
|
|
307
345
|
this.config.mode = mode;
|
|
308
|
-
|
|
346
|
+
const prompt = await buildSystemPrompt(this.config);
|
|
347
|
+
this.messages[0] = { role: 'system', content: prompt };
|
|
309
348
|
this.registry.setMode(mode);
|
|
310
349
|
this.save();
|
|
311
350
|
}
|
|
@@ -322,6 +361,8 @@ export class Session {
|
|
|
322
361
|
/** Reset the stopped flag for a new conversation */
|
|
323
362
|
resetStopped() {
|
|
324
363
|
this.stopped = false;
|
|
364
|
+
// Create new AbortController for next conversation
|
|
365
|
+
this.abortController = new AbortController();
|
|
325
366
|
}
|
|
326
367
|
async chat(userPrompt) {
|
|
327
368
|
const bus = getGlobalEventBus();
|
|
@@ -334,6 +375,8 @@ export class Session {
|
|
|
334
375
|
this.turnInputTokens = 0;
|
|
335
376
|
this.turnOutputTokens = 0;
|
|
336
377
|
this.turnApiCalls = 0;
|
|
378
|
+
this.turnCacheHitTokens = 0;
|
|
379
|
+
this.turnCacheMissTokens = 0;
|
|
337
380
|
let iterations = 0;
|
|
338
381
|
const maxIterations = 30;
|
|
339
382
|
// Reset stopped flag for new conversation
|
|
@@ -361,171 +404,214 @@ export class Session {
|
|
|
361
404
|
bus.emit(EventChannels.TURN_START, { prompt: userPrompt, iteration: iterations });
|
|
362
405
|
bus.emit(EventChannels.LLM_STREAM_START, { iteration: iterations });
|
|
363
406
|
const stream = this.provider.chat(this.messages, this.registry.getDefinitions(), this.abortController.signal);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
reasoningOutput
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
// Terminal display with box drawing (skip in Web UI mode, handled by EventBus)
|
|
379
|
-
if (!out?.suppressToolOutput) {
|
|
380
|
-
// Track column position on current line for wrapping
|
|
381
|
-
let thinkCol = 0;
|
|
382
|
-
// Handle newlines in streamed content - add left border on each new line
|
|
383
|
-
// Also manually wrap long lines so wrapped lines keep the │ prefix.
|
|
384
|
-
let remaining = chunk.reasoning_content;
|
|
385
|
-
const maxContentWidth = termWidth() - THINK_PREFIX_WIDTH;
|
|
386
|
-
while (remaining.length > 0) {
|
|
387
|
-
if (reasoningLineStart) {
|
|
388
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
389
|
-
reasoningLineStart = false;
|
|
390
|
-
thinkCol = 0;
|
|
407
|
+
try {
|
|
408
|
+
for await (const chunk of stream) {
|
|
409
|
+
if (chunk.reasoning_content) {
|
|
410
|
+
reasoningContent = chunk.reasoning_content;
|
|
411
|
+
// Stream reasoning content in real-time (only when no text in same chunk)
|
|
412
|
+
if (!chunk.text) {
|
|
413
|
+
// Emit thinking via EventBus for Web UI
|
|
414
|
+
bus.emit(EventChannels.THINKING, { text: chunk.reasoning_content });
|
|
415
|
+
if (!reasoningOutput) {
|
|
416
|
+
reasoningOutput = true;
|
|
417
|
+
reasoningLineStart = true;
|
|
418
|
+
if (!out?.suppressToolOutput) {
|
|
419
|
+
writeOut(thinkTopBorder(), out);
|
|
391
420
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
421
|
+
}
|
|
422
|
+
// Terminal display with box drawing (skip in Web UI mode, handled by EventBus)
|
|
423
|
+
if (!out?.suppressToolOutput) {
|
|
424
|
+
// Track column position on current line for wrapping
|
|
425
|
+
let thinkCol = 0;
|
|
426
|
+
// Handle newlines in streamed content - add left border on each new line
|
|
427
|
+
// Also manually wrap long lines so wrapped lines keep the │ prefix.
|
|
428
|
+
let remaining = chunk.reasoning_content;
|
|
429
|
+
const maxContentWidth = termWidth() - THINK_PREFIX_WIDTH;
|
|
430
|
+
while (remaining.length > 0) {
|
|
431
|
+
if (reasoningLineStart) {
|
|
432
|
+
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
433
|
+
reasoningLineStart = false;
|
|
434
|
+
thinkCol = 0;
|
|
435
|
+
}
|
|
436
|
+
const nlIdx = remaining.indexOf('\n');
|
|
437
|
+
if (nlIdx === -1) {
|
|
438
|
+
// No newline — write as much as fits on current line, wrap if needed
|
|
439
|
+
while (remaining.length > 0) {
|
|
440
|
+
const segWidth = visibleWidth(remaining);
|
|
441
|
+
const avail = maxContentWidth - thinkCol;
|
|
442
|
+
if (segWidth <= avail) {
|
|
443
|
+
// Fits entirely on current line
|
|
444
|
+
writeOut(remaining, out);
|
|
445
|
+
thinkCol += segWidth;
|
|
446
|
+
remaining = '';
|
|
447
|
+
}
|
|
448
|
+
else if (avail <= 0) {
|
|
449
|
+
// Current line is full, wrap to next
|
|
450
|
+
writeOut(`${RS}\n`, out);
|
|
451
|
+
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
452
|
+
thinkCol = 0;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
// Write first part that fits, then wrap
|
|
456
|
+
// Find character boundary that fits within avail
|
|
457
|
+
let cut = avail;
|
|
458
|
+
while (cut > 0 && visibleWidth(remaining.slice(0, cut)) > avail)
|
|
459
|
+
cut--;
|
|
460
|
+
if (cut <= 0)
|
|
461
|
+
cut = 1;
|
|
462
|
+
writeOut(remaining.slice(0, cut), out);
|
|
463
|
+
writeOut(`${RS}\n`, out);
|
|
464
|
+
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
465
|
+
thinkCol = 0;
|
|
466
|
+
remaining = remaining.slice(cut);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
// Has newline — process the segment up to newline
|
|
472
|
+
const segment = remaining.slice(0, nlIdx);
|
|
473
|
+
const segWidth = visibleWidth(segment);
|
|
397
474
|
const avail = maxContentWidth - thinkCol;
|
|
398
475
|
if (segWidth <= avail) {
|
|
399
|
-
//
|
|
400
|
-
writeOut(
|
|
401
|
-
thinkCol += segWidth;
|
|
402
|
-
remaining = '';
|
|
403
|
-
}
|
|
404
|
-
else if (avail <= 0) {
|
|
405
|
-
// Current line is full, wrap to next
|
|
476
|
+
// Segment fits on current line
|
|
477
|
+
writeOut(segment, out);
|
|
406
478
|
writeOut(`${RS}\n`, out);
|
|
407
|
-
|
|
479
|
+
reasoningLineStart = true;
|
|
408
480
|
thinkCol = 0;
|
|
409
481
|
}
|
|
410
482
|
else {
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
cut
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
483
|
+
// Segment too long — write what fits, wrap, then rest
|
|
484
|
+
let rest = segment;
|
|
485
|
+
// Write remainder of current line
|
|
486
|
+
if (avail > 0) {
|
|
487
|
+
let cut = avail;
|
|
488
|
+
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > avail)
|
|
489
|
+
cut--;
|
|
490
|
+
if (cut <= 0)
|
|
491
|
+
cut = 1;
|
|
492
|
+
writeOut(rest.slice(0, cut), out);
|
|
493
|
+
rest = rest.slice(cut);
|
|
494
|
+
}
|
|
419
495
|
writeOut(`${RS}\n`, out);
|
|
420
|
-
|
|
496
|
+
reasoningLineStart = true;
|
|
421
497
|
thinkCol = 0;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > avail)
|
|
445
|
-
cut--;
|
|
446
|
-
if (cut <= 0)
|
|
447
|
-
cut = 1;
|
|
448
|
-
writeOut(rest.slice(0, cut), out);
|
|
449
|
-
rest = rest.slice(cut);
|
|
450
|
-
}
|
|
451
|
-
writeOut(`${RS}\n`, out);
|
|
452
|
-
reasoningLineStart = true;
|
|
453
|
-
thinkCol = 0;
|
|
454
|
-
// Write rest of segment on continuation line(s)
|
|
455
|
-
if (rest.length > 0) {
|
|
456
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
457
|
-
reasoningLineStart = false;
|
|
458
|
-
while (rest.length > 0) {
|
|
459
|
-
const rw = visibleWidth(rest);
|
|
460
|
-
if (rw <= maxContentWidth) {
|
|
461
|
-
writeOut(rest, out);
|
|
462
|
-
thinkCol = rw;
|
|
463
|
-
rest = '';
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
let cut = maxContentWidth;
|
|
467
|
-
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > maxContentWidth)
|
|
468
|
-
cut--;
|
|
469
|
-
if (cut <= 0)
|
|
470
|
-
cut = 1;
|
|
471
|
-
writeOut(rest.slice(0, cut), out);
|
|
472
|
-
writeOut(`${RS}\n`, out);
|
|
473
|
-
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
474
|
-
rest = rest.slice(cut);
|
|
498
|
+
// Write rest of segment on continuation line(s)
|
|
499
|
+
if (rest.length > 0) {
|
|
500
|
+
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
501
|
+
reasoningLineStart = false;
|
|
502
|
+
while (rest.length > 0) {
|
|
503
|
+
const rw = visibleWidth(rest);
|
|
504
|
+
if (rw <= maxContentWidth) {
|
|
505
|
+
writeOut(rest, out);
|
|
506
|
+
thinkCol = rw;
|
|
507
|
+
rest = '';
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
let cut = maxContentWidth;
|
|
511
|
+
while (cut > 0 && visibleWidth(rest.slice(0, cut)) > maxContentWidth)
|
|
512
|
+
cut--;
|
|
513
|
+
if (cut <= 0)
|
|
514
|
+
cut = 1;
|
|
515
|
+
writeOut(rest.slice(0, cut), out);
|
|
516
|
+
writeOut(`${RS}\n`, out);
|
|
517
|
+
writeOut(` ${GY}│${RS}${TH}`, out);
|
|
518
|
+
rest = rest.slice(cut);
|
|
519
|
+
}
|
|
475
520
|
}
|
|
476
521
|
}
|
|
522
|
+
writeOut(`${RS}\n`, out);
|
|
523
|
+
reasoningLineStart = true;
|
|
524
|
+
thinkCol = 0;
|
|
477
525
|
}
|
|
478
|
-
|
|
479
|
-
reasoningLineStart = true;
|
|
480
|
-
thinkCol = 0;
|
|
526
|
+
remaining = remaining.slice(nlIdx + 1);
|
|
481
527
|
}
|
|
482
|
-
remaining = remaining.slice(nlIdx + 1);
|
|
483
528
|
}
|
|
484
529
|
}
|
|
485
530
|
}
|
|
486
531
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
532
|
+
if (chunk.type === 'text' && chunk.text) {
|
|
533
|
+
if (reasoningOutput) {
|
|
534
|
+
bus.emit(EventChannels.THINKING_END, {});
|
|
535
|
+
if (!out?.suppressToolOutput) {
|
|
536
|
+
writeOut(`${RS}\n`, out);
|
|
537
|
+
writeOut(thinkBottomBorder(), out);
|
|
538
|
+
}
|
|
539
|
+
reasoningOutput = false;
|
|
540
|
+
reasoningLineStart = false;
|
|
494
541
|
}
|
|
495
|
-
|
|
496
|
-
|
|
542
|
+
fullResponse += chunk.text;
|
|
543
|
+
writeOut(chunk.text, out);
|
|
497
544
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
else if (chunk.type === 'tool_use' && chunk.tool_call) {
|
|
502
|
-
toolCalls.push(chunk.tool_call);
|
|
503
|
-
}
|
|
504
|
-
else if (chunk.type === 'complete') {
|
|
505
|
-
if (chunk.usage) {
|
|
506
|
-
this.turnInputTokens += chunk.usage.input_tokens;
|
|
507
|
-
this.turnOutputTokens += chunk.usage.output_tokens;
|
|
508
|
-
this.totalInputTokens += chunk.usage.input_tokens;
|
|
509
|
-
this.totalOutputTokens += chunk.usage.output_tokens;
|
|
545
|
+
else if (chunk.type === 'tool_use' && chunk.tool_call) {
|
|
546
|
+
toolCalls.push(chunk.tool_call);
|
|
510
547
|
}
|
|
511
|
-
if (chunk.
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
548
|
+
else if (chunk.type === 'complete') {
|
|
549
|
+
if (chunk.usage) {
|
|
550
|
+
this.turnInputTokens += chunk.usage.input_tokens;
|
|
551
|
+
this.turnOutputTokens += chunk.usage.output_tokens;
|
|
552
|
+
this.totalInputTokens += chunk.usage.input_tokens;
|
|
553
|
+
this.totalOutputTokens += chunk.usage.output_tokens;
|
|
554
|
+
// Accumulate DeepSeek cache hit/miss tokens
|
|
555
|
+
if (chunk.usage.prompt_cache_hit_tokens != null) {
|
|
556
|
+
this.turnCacheHitTokens += chunk.usage.prompt_cache_hit_tokens;
|
|
557
|
+
this.totalCacheHitTokens += chunk.usage.prompt_cache_hit_tokens;
|
|
558
|
+
}
|
|
559
|
+
if (chunk.usage.prompt_cache_miss_tokens != null) {
|
|
560
|
+
this.turnCacheMissTokens += chunk.usage.prompt_cache_miss_tokens;
|
|
561
|
+
this.totalCacheMissTokens += chunk.usage.prompt_cache_miss_tokens;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (chunk.finish_reason === 'stop' || chunk.finish_reason === 'end_turn') {
|
|
565
|
+
if (toolCalls.length === 0) {
|
|
566
|
+
const finalAssistantMsg = {
|
|
567
|
+
role: 'assistant',
|
|
568
|
+
content: fullResponse || null,
|
|
569
|
+
reasoning_content: reasoningContent,
|
|
570
|
+
};
|
|
571
|
+
this.messages.push(finalAssistantMsg);
|
|
572
|
+
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
|
|
573
|
+
writeOut('\n\n', out);
|
|
574
|
+
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
575
|
+
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
576
|
+
this.save();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
525
579
|
}
|
|
526
580
|
}
|
|
527
581
|
}
|
|
528
582
|
}
|
|
583
|
+
catch (e) {
|
|
584
|
+
const errMsg = fmtErr(e);
|
|
585
|
+
const partialContent = fullResponse ? fullResponse.slice(0, 500) : '(empty)';
|
|
586
|
+
if (!out?.suppressToolOutput) {
|
|
587
|
+
writeOut(`\n${RE}Stream error:${RS} ${errMsg}`, out);
|
|
588
|
+
writeOut(`\n ${GY}┃${RS} Partial response: ${partialContent}\n`, out);
|
|
589
|
+
}
|
|
590
|
+
console.error('[session] Stream error:', errMsg, '| Partial response:', partialContent);
|
|
591
|
+
if (reasoningOutput) {
|
|
592
|
+
bus.emit(EventChannels.THINKING_END, {});
|
|
593
|
+
if (!out?.suppressToolOutput) {
|
|
594
|
+
writeOut(`${RS}\n`, out);
|
|
595
|
+
writeOut(thinkBottomBorder(), out);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
bus.emit(EventChannels.LLM_STREAM_END, { iteration: iterations, toolCallCount: 0 });
|
|
599
|
+
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
|
|
600
|
+
// If there are pending toolCalls that weren't executed due to abort,
|
|
601
|
+
// add an assistant message so the model knows they weren't executed
|
|
602
|
+
if (toolCalls.length > 0) {
|
|
603
|
+
const interruptedMsg = {
|
|
604
|
+
role: 'assistant',
|
|
605
|
+
content: null,
|
|
606
|
+
tool_calls: toolCalls,
|
|
607
|
+
reasoning_content: reasoningContent,
|
|
608
|
+
};
|
|
609
|
+
this.messages.push(interruptedMsg);
|
|
610
|
+
}
|
|
611
|
+
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
612
|
+
this.save();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
529
615
|
bus.emit(EventChannels.LLM_STREAM_END, {
|
|
530
616
|
iteration: iterations,
|
|
531
617
|
toolCallCount: toolCalls.length,
|
|
@@ -548,7 +634,7 @@ export class Session {
|
|
|
548
634
|
reasoning_content: reasoningContent,
|
|
549
635
|
};
|
|
550
636
|
this.messages.push(finalAssistantMsg);
|
|
551
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out);
|
|
637
|
+
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
|
|
552
638
|
writeOut('\n\n', out);
|
|
553
639
|
}
|
|
554
640
|
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|
|
@@ -566,7 +652,7 @@ export class Session {
|
|
|
566
652
|
this.save();
|
|
567
653
|
// ── User confirmation for write-type tool calls ──
|
|
568
654
|
if (!this.config.autoApprove && this.output?.confirmTool && toolCalls.length > 0) {
|
|
569
|
-
const writeTools = ['edit', 'bash', 'write_plan', '
|
|
655
|
+
const writeTools = ['edit', 'bash', 'write_plan', 'install_skill'];
|
|
570
656
|
const needsConfirm = toolCalls.filter(tc => writeTools.includes(tc.name));
|
|
571
657
|
if (needsConfirm.length > 0) {
|
|
572
658
|
const approved = await this.output.confirmTool(needsConfirm);
|
|
@@ -583,9 +669,20 @@ export class Session {
|
|
|
583
669
|
}
|
|
584
670
|
}
|
|
585
671
|
}
|
|
586
|
-
for (
|
|
672
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
673
|
+
const tc = toolCalls[i];
|
|
587
674
|
// Check if stop was requested after each tool call
|
|
588
675
|
if (this.isStopped()) {
|
|
676
|
+
// Add assistant message with remaining tool_calls so model knows they weren't executed
|
|
677
|
+
const remainingToolCalls = toolCalls.slice(i);
|
|
678
|
+
if (remainingToolCalls.length > 0) {
|
|
679
|
+
const interruptedMsg = {
|
|
680
|
+
role: 'assistant',
|
|
681
|
+
content: null,
|
|
682
|
+
tool_calls: remainingToolCalls,
|
|
683
|
+
};
|
|
684
|
+
this.messages.push(interruptedMsg);
|
|
685
|
+
}
|
|
589
686
|
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
|
|
590
687
|
this.save();
|
|
591
688
|
return;
|
|
@@ -629,7 +726,7 @@ export class Session {
|
|
|
629
726
|
}
|
|
630
727
|
}
|
|
631
728
|
if (iterations >= maxIterations) {
|
|
632
|
-
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out);
|
|
729
|
+
printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
|
|
633
730
|
writeOut('\nAgent reached maximum iterations. Stopping.\n', out);
|
|
634
731
|
}
|
|
635
732
|
saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
|