@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.67
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/CHANGELOG.md +60 -0
- package/docs/sdk.md +1 -1
- package/package.json +5 -5
- package/scripts/generate-template.ts +6 -6
- package/src/cli/args.ts +3 -0
- package/src/core/agent-session.ts +39 -0
- package/src/core/bash-executor.ts +3 -3
- package/src/core/cursor/exec-bridge.ts +95 -88
- package/src/core/custom-commands/bundled/review/index.ts +142 -145
- package/src/core/custom-commands/bundled/wt/index.ts +68 -66
- package/src/core/custom-commands/loader.ts +4 -6
- package/src/core/custom-tools/index.ts +2 -2
- package/src/core/custom-tools/loader.ts +66 -61
- package/src/core/custom-tools/types.ts +4 -4
- package/src/core/custom-tools/wrapper.ts +61 -25
- package/src/core/event-bus.ts +19 -47
- package/src/core/extensions/index.ts +8 -4
- package/src/core/extensions/loader.ts +160 -120
- package/src/core/extensions/types.ts +4 -4
- package/src/core/extensions/wrapper.ts +149 -100
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +96 -70
- package/src/core/hooks/types.ts +1 -2
- package/src/core/index.ts +1 -0
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/json-rpc.ts +88 -0
- package/src/core/mcp/loader.ts +22 -4
- package/src/core/mcp/manager.ts +202 -48
- package/src/core/mcp/tool-bridge.ts +143 -55
- package/src/core/mcp/tool-cache.ts +122 -0
- package/src/core/python-executor.ts +3 -9
- package/src/core/sdk.ts +33 -32
- package/src/core/session-manager.ts +30 -0
- package/src/core/settings-manager.ts +54 -1
- package/src/core/ssh/ssh-executor.ts +6 -84
- package/src/core/streaming-output.ts +107 -53
- package/src/core/tools/ask.ts +92 -93
- package/src/core/tools/bash.ts +103 -94
- package/src/core/tools/calculator.ts +41 -26
- package/src/core/tools/complete.ts +76 -66
- package/src/core/tools/context.ts +22 -24
- package/src/core/tools/exa/index.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +56 -101
- package/src/core/tools/find.ts +250 -253
- package/src/core/tools/git.ts +39 -33
- package/src/core/tools/grep.ts +440 -427
- package/src/core/tools/index.ts +63 -61
- package/src/core/tools/ls.ts +119 -114
- package/src/core/tools/lsp/clients/biome-client.ts +5 -7
- package/src/core/tools/lsp/clients/index.ts +4 -4
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
- package/src/core/tools/lsp/config.ts +2 -2
- package/src/core/tools/lsp/index.ts +604 -578
- package/src/core/tools/notebook.ts +121 -119
- package/src/core/tools/output.ts +163 -147
- package/src/core/tools/patch/applicator.ts +1100 -0
- package/src/core/tools/patch/diff.ts +362 -0
- package/src/core/tools/patch/fuzzy.ts +647 -0
- package/src/core/tools/patch/index.ts +430 -0
- package/src/core/tools/patch/normalize.ts +220 -0
- package/src/core/tools/patch/normative.ts +73 -0
- package/src/core/tools/patch/parser.ts +528 -0
- package/src/core/tools/patch/shared.ts +257 -0
- package/src/core/tools/patch/types.ts +244 -0
- package/src/core/tools/python.ts +139 -136
- package/src/core/tools/read.ts +239 -216
- package/src/core/tools/render-utils.ts +196 -77
- package/src/core/tools/renderers.ts +6 -2
- package/src/core/tools/ssh.ts +99 -80
- package/src/core/tools/task/executor.ts +11 -7
- package/src/core/tools/task/index.ts +352 -343
- package/src/core/tools/task/worker.ts +13 -23
- package/src/core/tools/todo-write.ts +74 -59
- package/src/core/tools/web-fetch.ts +54 -47
- package/src/core/tools/web-search/index.ts +27 -16
- package/src/core/tools/write.ts +108 -47
- package/src/core/ttsr.ts +106 -152
- package/src/core/voice.ts +49 -39
- package/src/index.ts +16 -12
- package/src/lib/worktree/index.ts +1 -9
- package/src/modes/interactive/components/diff.ts +15 -8
- package/src/modes/interactive/components/settings-defs.ts +42 -0
- package/src/modes/interactive/components/tool-execution.ts +46 -8
- package/src/modes/interactive/controllers/event-controller.ts +6 -19
- package/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/src/modes/interactive/utils/ui-helpers.ts +5 -1
- package/src/modes/rpc/rpc-mode.ts +99 -81
- package/src/prompts/tools/patch.md +76 -0
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/{edit.md → replace.md} +1 -0
- package/src/utils/shell.ts +0 -40
- package/src/core/tools/edit-diff.ts +0 -574
- package/src/core/tools/edit.ts +0 -345
package/src/core/tools/read.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
@@ -15,7 +15,7 @@ import type { RenderResultOptions } from "../custom-tools/types";
|
|
|
15
15
|
import { renderPromptTemplate } from "../prompt-templates";
|
|
16
16
|
import type { ToolSession } from "../sdk";
|
|
17
17
|
import { ScopeSignal, untilAborted } from "../utils";
|
|
18
|
-
import {
|
|
18
|
+
import { LsTool } from "./ls";
|
|
19
19
|
import { resolveReadPath, resolveToCwd } from "./path-utils";
|
|
20
20
|
import { shortenPath, wrapBrackets } from "./render-utils";
|
|
21
21
|
import {
|
|
@@ -417,7 +417,7 @@ const readSchema = Type.Object({
|
|
|
417
417
|
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
418
418
|
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
419
419
|
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
420
|
-
lines: Type.Optional(Type.Boolean({ description: "Prepend line numbers to output (default:
|
|
420
|
+
lines: Type.Optional(Type.Boolean({ description: "Prepend line numbers to output (default: false)" })),
|
|
421
421
|
});
|
|
422
422
|
|
|
423
423
|
export interface ReadToolDetails {
|
|
@@ -425,74 +425,111 @@ export interface ReadToolDetails {
|
|
|
425
425
|
redirectedTo?: "ls";
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
428
|
+
type ReadParams = { path: string; offset?: number; limit?: number; lines?: boolean };
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Read tool implementation.
|
|
432
|
+
*
|
|
433
|
+
* Reads files with support for images, documents (via markitdown), and text.
|
|
434
|
+
* Directories redirect to the ls tool.
|
|
435
|
+
*/
|
|
436
|
+
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
437
|
+
public readonly name = "read";
|
|
438
|
+
public readonly label = "Read";
|
|
439
|
+
public readonly description: string;
|
|
440
|
+
public readonly parameters = readSchema;
|
|
441
|
+
|
|
442
|
+
private readonly session: ToolSession;
|
|
443
|
+
private readonly autoResizeImages: boolean;
|
|
444
|
+
private readonly defaultLineNumbers: boolean;
|
|
445
|
+
private readonly lsTool: LsTool;
|
|
446
|
+
|
|
447
|
+
constructor(session: ToolSession) {
|
|
448
|
+
this.session = session;
|
|
449
|
+
this.autoResizeImages = session.settings?.getImageAutoResize() ?? true;
|
|
450
|
+
this.defaultLineNumbers = session.settings?.getReadLineNumbers?.() ?? false;
|
|
451
|
+
this.lsTool = new LsTool(session);
|
|
452
|
+
this.description = renderPromptTemplate(readDescription, {
|
|
435
453
|
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
message += `\n\nNo similar paths found in ${suggestions.scopeLabel}.`;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
public async execute(
|
|
458
|
+
toolCallId: string,
|
|
459
|
+
params: ReadParams,
|
|
460
|
+
signal?: AbortSignal,
|
|
461
|
+
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
462
|
+
_context?: AgentToolContext,
|
|
463
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
464
|
+
const { path: readPath, offset, limit, lines } = params;
|
|
465
|
+
const absolutePath = resolveReadPath(readPath, this.session.cwd);
|
|
466
|
+
|
|
467
|
+
return untilAborted(signal, async () => {
|
|
468
|
+
let isDirectory = false;
|
|
469
|
+
let fileSize = 0;
|
|
470
|
+
try {
|
|
471
|
+
const stat = await Bun.file(absolutePath).stat();
|
|
472
|
+
fileSize = stat.size;
|
|
473
|
+
isDirectory = stat.isDirectory();
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (isNotFoundError(error)) {
|
|
476
|
+
let message = `File not found: ${readPath}`;
|
|
477
|
+
|
|
478
|
+
// Skip fuzzy matching for remote mounts (sshfs) to avoid hangs
|
|
479
|
+
if (!isRemoteMountPath(absolutePath)) {
|
|
480
|
+
const suggestions = await findReadPathSuggestions(readPath, this.session.cwd, signal);
|
|
481
|
+
|
|
482
|
+
if (suggestions?.suggestions.length) {
|
|
483
|
+
const scopeLabel = suggestions.scopeLabel ? ` in ${suggestions.scopeLabel}` : "";
|
|
484
|
+
message += `\n\nClosest matches${scopeLabel}:\n${suggestions.suggestions.map((match) => `- ${match}`).join("\n")}`;
|
|
485
|
+
if (suggestions.truncated) {
|
|
486
|
+
message += `\n[Search truncated to first ${MAX_FUZZY_CANDIDATES} paths. Refine the path if the match isn't listed.]`;
|
|
470
487
|
}
|
|
488
|
+
} else if (suggestions?.error) {
|
|
489
|
+
message += `\n\nFuzzy match failed: ${suggestions.error}`;
|
|
490
|
+
} else if (suggestions?.scopeLabel) {
|
|
491
|
+
message += `\n\nNo similar paths found in ${suggestions.scopeLabel}.`;
|
|
471
492
|
}
|
|
472
|
-
|
|
473
|
-
throw new Error(message);
|
|
474
493
|
}
|
|
475
|
-
throw error;
|
|
476
|
-
}
|
|
477
494
|
|
|
478
|
-
|
|
479
|
-
const lsResult = await lsTool.execute(toolCallId, { path: readPath, limit }, signal);
|
|
480
|
-
return {
|
|
481
|
-
content: lsResult.content,
|
|
482
|
-
details: { redirectedTo: "ls", truncation: lsResult.details?.truncation },
|
|
483
|
-
};
|
|
495
|
+
throw new Error(message);
|
|
484
496
|
}
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
485
499
|
|
|
486
|
-
|
|
487
|
-
const
|
|
500
|
+
if (isDirectory) {
|
|
501
|
+
const lsResult = await this.lsTool.execute(toolCallId, { path: readPath, limit }, signal);
|
|
502
|
+
return {
|
|
503
|
+
content: lsResult.content,
|
|
504
|
+
details: { redirectedTo: "ls", truncation: lsResult.details?.truncation },
|
|
505
|
+
};
|
|
506
|
+
}
|
|
488
507
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
508
|
+
const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
|
|
509
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
510
|
+
|
|
511
|
+
// Read the file based on type
|
|
512
|
+
let content: (TextContent | ImageContent)[];
|
|
513
|
+
let details: ReadToolDetails | undefined;
|
|
514
|
+
|
|
515
|
+
if (mimeType) {
|
|
516
|
+
if (fileSize > MAX_IMAGE_SIZE) {
|
|
517
|
+
const sizeStr = formatSize(fileSize);
|
|
518
|
+
const maxStr = formatSize(MAX_IMAGE_SIZE);
|
|
519
|
+
content = [
|
|
520
|
+
{
|
|
521
|
+
type: "text",
|
|
522
|
+
text: `[Image file too large: ${sizeStr} exceeds ${maxStr} limit. Use an image viewer or resize the image.]`,
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
} else {
|
|
526
|
+
// Read as image (binary)
|
|
527
|
+
const file = Bun.file(absolutePath);
|
|
528
|
+
const buffer = await file.arrayBuffer();
|
|
492
529
|
|
|
493
|
-
|
|
494
|
-
if (
|
|
495
|
-
const sizeStr = formatSize(
|
|
530
|
+
// Check actual buffer size after reading to prevent OOM during serialization
|
|
531
|
+
if (buffer.byteLength > MAX_IMAGE_SIZE) {
|
|
532
|
+
const sizeStr = formatSize(buffer.byteLength);
|
|
496
533
|
const maxStr = formatSize(MAX_IMAGE_SIZE);
|
|
497
534
|
content = [
|
|
498
535
|
{
|
|
@@ -501,178 +538,164 @@ export function createReadTool(session: ToolSession): AgentTool<typeof readSchem
|
|
|
501
538
|
},
|
|
502
539
|
];
|
|
503
540
|
} else {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
{
|
|
514
|
-
|
|
515
|
-
text: `[Image file too large: ${sizeStr} exceeds ${maxStr} limit. Use an image viewer or resize the image.]`,
|
|
516
|
-
},
|
|
517
|
-
];
|
|
518
|
-
} else {
|
|
519
|
-
const base64 = Buffer.from(buffer).toString("base64");
|
|
520
|
-
|
|
521
|
-
if (autoResizeImages) {
|
|
522
|
-
// Resize image if needed - catch errors from WASM
|
|
523
|
-
try {
|
|
524
|
-
const resized = await resizeImage({ type: "image", data: base64, mimeType });
|
|
525
|
-
const dimensionNote = formatDimensionNote(resized);
|
|
526
|
-
|
|
527
|
-
let textNote = `Read image file [${resized.mimeType}]`;
|
|
528
|
-
if (dimensionNote) {
|
|
529
|
-
textNote += `\n${dimensionNote}`;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
content = [
|
|
533
|
-
{ type: "text", text: textNote },
|
|
534
|
-
{ type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
535
|
-
];
|
|
536
|
-
} catch {
|
|
537
|
-
// Fall back to original image on resize failure
|
|
538
|
-
content = [
|
|
539
|
-
{ type: "text", text: `Read image file [${mimeType}]` },
|
|
540
|
-
{ type: "image", data: base64, mimeType },
|
|
541
|
-
];
|
|
541
|
+
const base64 = Buffer.from(buffer).toString("base64");
|
|
542
|
+
|
|
543
|
+
if (this.autoResizeImages) {
|
|
544
|
+
// Resize image if needed - catch errors from WASM
|
|
545
|
+
try {
|
|
546
|
+
const resized = await resizeImage({ type: "image", data: base64, mimeType });
|
|
547
|
+
const dimensionNote = formatDimensionNote(resized);
|
|
548
|
+
|
|
549
|
+
let textNote = `Read image file [${resized.mimeType}]`;
|
|
550
|
+
if (dimensionNote) {
|
|
551
|
+
textNote += `\n${dimensionNote}`;
|
|
542
552
|
}
|
|
543
|
-
|
|
553
|
+
|
|
554
|
+
content = [
|
|
555
|
+
{ type: "text", text: textNote },
|
|
556
|
+
{ type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
557
|
+
];
|
|
558
|
+
} catch {
|
|
559
|
+
// Fall back to original image on resize failure
|
|
544
560
|
content = [
|
|
545
561
|
{ type: "text", text: `Read image file [${mimeType}]` },
|
|
546
562
|
{ type: "image", data: base64, mimeType },
|
|
547
563
|
];
|
|
548
564
|
}
|
|
565
|
+
} else {
|
|
566
|
+
content = [
|
|
567
|
+
{ type: "text", text: `Read image file [${mimeType}]` },
|
|
568
|
+
{ type: "image", data: base64, mimeType },
|
|
569
|
+
];
|
|
549
570
|
}
|
|
550
571
|
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
content = [{ type: "text", text: outputText }];
|
|
565
|
-
} else {
|
|
566
|
-
// markitdown not available or failed
|
|
567
|
-
const errorMsg =
|
|
568
|
-
result.error === "markitdown not found"
|
|
569
|
-
? `markitdown not installed. Install with: pip install markitdown`
|
|
570
|
-
: result.error || "conversion failed";
|
|
571
|
-
content = [{ type: "text", text: `[Cannot read ${ext} file: ${errorMsg}]` }];
|
|
572
|
+
}
|
|
573
|
+
} else if (CONVERTIBLE_EXTENSIONS.has(ext)) {
|
|
574
|
+
// Convert document via markitdown
|
|
575
|
+
const result = await convertWithMarkitdown(absolutePath, signal);
|
|
576
|
+
if (result.ok) {
|
|
577
|
+
// Apply truncation to converted content
|
|
578
|
+
const truncation = truncateHead(result.content);
|
|
579
|
+
let outputText = truncation.content;
|
|
580
|
+
|
|
581
|
+
if (truncation.truncated) {
|
|
582
|
+
outputText += `\n\n[Document converted via markitdown. Output truncated to ${formatSize(DEFAULT_MAX_BYTES)}]`;
|
|
583
|
+
details = { truncation };
|
|
572
584
|
}
|
|
573
|
-
} else {
|
|
574
|
-
// Read as text
|
|
575
|
-
const file = Bun.file(absolutePath);
|
|
576
|
-
const textContent = await file.text();
|
|
577
|
-
const allLines = textContent.split("\n");
|
|
578
|
-
const totalFileLines = allLines.length;
|
|
579
585
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
586
|
+
content = [{ type: "text", text: outputText }];
|
|
587
|
+
} else if (result.error) {
|
|
588
|
+
// markitdown not available or failed
|
|
589
|
+
const errorMsg =
|
|
590
|
+
result.error === "markitdown not found"
|
|
591
|
+
? `markitdown not installed. Install with: pip install markitdown`
|
|
592
|
+
: result.error || "conversion failed";
|
|
593
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: ${errorMsg}]` }];
|
|
594
|
+
} else {
|
|
595
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: conversion failed]` }];
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
// Read as text
|
|
599
|
+
const file = Bun.file(absolutePath);
|
|
600
|
+
const textContent = await file.text();
|
|
601
|
+
const allLines = textContent.split("\n");
|
|
602
|
+
const totalFileLines = allLines.length;
|
|
603
|
+
|
|
604
|
+
// Apply offset if specified (1-indexed to 0-indexed)
|
|
605
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
606
|
+
const startLineDisplay = startLine + 1; // For display (1-indexed)
|
|
607
|
+
|
|
608
|
+
// Check if offset is out of bounds
|
|
609
|
+
if (startLine >= allLines.length) {
|
|
610
|
+
throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
|
|
611
|
+
}
|
|
583
612
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
613
|
+
// If limit is specified by user, use it; otherwise we'll let truncateHead decide
|
|
614
|
+
let selectedContent: string;
|
|
615
|
+
let userLimitedLines: number | undefined;
|
|
616
|
+
if (limit !== undefined) {
|
|
617
|
+
const endLine = Math.min(startLine + limit, allLines.length);
|
|
618
|
+
selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
619
|
+
userLimitedLines = endLine - startLine;
|
|
620
|
+
} else {
|
|
621
|
+
selectedContent = allLines.slice(startLine).join("\n");
|
|
622
|
+
}
|
|
588
623
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
624
|
+
// Apply truncation (respects both line and byte limits)
|
|
625
|
+
const truncation = truncateHead(selectedContent);
|
|
626
|
+
|
|
627
|
+
// Add line numbers if requested (uses setting default if not specified)
|
|
628
|
+
const shouldAddLineNumbers = lines ?? this.defaultLineNumbers;
|
|
629
|
+
const prependLineNumbers = (text: string, startNum: number): string => {
|
|
630
|
+
const textLines = text.split("\n");
|
|
631
|
+
const lastLineNum = startNum + textLines.length - 1;
|
|
632
|
+
const padWidth = String(lastLineNum).length;
|
|
633
|
+
return textLines
|
|
634
|
+
.map((line, i) => {
|
|
635
|
+
const lineNum = String(startNum + i).padStart(padWidth, " ");
|
|
636
|
+
return `${lineNum}\t${line}`;
|
|
637
|
+
})
|
|
638
|
+
.join("\n");
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
let outputText: string;
|
|
642
|
+
|
|
643
|
+
if (truncation.firstLineExceedsLimit) {
|
|
644
|
+
const firstLine = allLines[startLine] ?? "";
|
|
645
|
+
const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
|
|
646
|
+
const snippet = truncateStringToBytesFromStart(firstLine, DEFAULT_MAX_BYTES);
|
|
647
|
+
const shownSize = formatSize(snippet.bytes);
|
|
648
|
+
|
|
649
|
+
outputText = shouldAddLineNumbers ? prependLineNumbers(snippet.text, startLineDisplay) : snippet.text;
|
|
650
|
+
if (snippet.text.length > 0) {
|
|
651
|
+
outputText += `\n\n[Line ${startLineDisplay} is ${formatSize(
|
|
652
|
+
firstLineBytes,
|
|
653
|
+
)}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Showing first ${shownSize} of the line.]`;
|
|
596
654
|
} else {
|
|
597
|
-
|
|
655
|
+
outputText = `[Line ${startLineDisplay} is ${formatSize(
|
|
656
|
+
firstLineBytes,
|
|
657
|
+
)}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
598
658
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const lineNum = String(startNum + i).padStart(padWidth, " ");
|
|
612
|
-
return `${lineNum}\t${line}`;
|
|
613
|
-
})
|
|
614
|
-
.join("\n");
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
let outputText: string;
|
|
618
|
-
|
|
619
|
-
if (truncation.firstLineExceedsLimit) {
|
|
620
|
-
const firstLine = allLines[startLine] ?? "";
|
|
621
|
-
const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
|
|
622
|
-
const snippet = truncateStringToBytesFromStart(firstLine, DEFAULT_MAX_BYTES);
|
|
623
|
-
const shownSize = formatSize(snippet.bytes);
|
|
624
|
-
|
|
625
|
-
outputText = shouldAddLineNumbers ? prependLineNumbers(snippet.text, startLineDisplay) : snippet.text;
|
|
626
|
-
if (snippet.text.length > 0) {
|
|
627
|
-
outputText += `\n\n[Line ${startLineDisplay} is ${formatSize(
|
|
628
|
-
firstLineBytes,
|
|
629
|
-
)}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Showing first ${shownSize} of the line.]`;
|
|
630
|
-
} else {
|
|
631
|
-
outputText = `[Line ${startLineDisplay} is ${formatSize(
|
|
632
|
-
firstLineBytes,
|
|
633
|
-
)}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
634
|
-
}
|
|
635
|
-
details = { truncation };
|
|
636
|
-
} else if (truncation.truncated) {
|
|
637
|
-
// Truncation occurred - build actionable notice
|
|
638
|
-
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
639
|
-
const nextOffset = endLineDisplay + 1;
|
|
640
|
-
|
|
641
|
-
outputText = shouldAddLineNumbers
|
|
642
|
-
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
643
|
-
: truncation.content;
|
|
644
|
-
|
|
645
|
-
if (truncation.truncatedBy === "lines") {
|
|
646
|
-
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;
|
|
647
|
-
} else {
|
|
648
|
-
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(
|
|
649
|
-
DEFAULT_MAX_BYTES,
|
|
650
|
-
)} limit). Use offset=${nextOffset} to continue]`;
|
|
651
|
-
}
|
|
652
|
-
details = { truncation };
|
|
653
|
-
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
654
|
-
// User specified limit, there's more content, but no truncation
|
|
655
|
-
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
656
|
-
const nextOffset = startLine + userLimitedLines + 1;
|
|
657
|
-
|
|
658
|
-
outputText = shouldAddLineNumbers
|
|
659
|
-
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
660
|
-
: truncation.content;
|
|
661
|
-
outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;
|
|
659
|
+
details = { truncation };
|
|
660
|
+
} else if (truncation.truncated) {
|
|
661
|
+
// Truncation occurred - build actionable notice
|
|
662
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
663
|
+
const nextOffset = endLineDisplay + 1;
|
|
664
|
+
|
|
665
|
+
outputText = shouldAddLineNumbers
|
|
666
|
+
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
667
|
+
: truncation.content;
|
|
668
|
+
|
|
669
|
+
if (truncation.truncatedBy === "lines") {
|
|
670
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;
|
|
662
671
|
} else {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
: truncation.content;
|
|
672
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(
|
|
673
|
+
DEFAULT_MAX_BYTES,
|
|
674
|
+
)} limit). Use offset=${nextOffset} to continue]`;
|
|
667
675
|
}
|
|
668
|
-
|
|
669
|
-
|
|
676
|
+
details = { truncation };
|
|
677
|
+
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
678
|
+
// User specified limit, there's more content, but no truncation
|
|
679
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
680
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
681
|
+
|
|
682
|
+
outputText = shouldAddLineNumbers
|
|
683
|
+
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
684
|
+
: truncation.content;
|
|
685
|
+
outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;
|
|
686
|
+
} else {
|
|
687
|
+
// No truncation, no user limit exceeded
|
|
688
|
+
outputText = shouldAddLineNumbers
|
|
689
|
+
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
690
|
+
: truncation.content;
|
|
670
691
|
}
|
|
671
692
|
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
693
|
+
content = [{ type: "text", text: outputText }];
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return { content, details };
|
|
697
|
+
});
|
|
698
|
+
}
|
|
676
699
|
}
|
|
677
700
|
|
|
678
701
|
// =============================================================================
|