@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.0
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 +46 -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 +34 -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 +62 -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 +49 -0
- package/src/core/tools/patch/parser.ts +528 -0
- package/src/core/tools/patch/shared.ts +228 -0
- package/src/core/tools/patch/types.ts +244 -0
- package/src/core/tools/python.ts +139 -136
- package/src/core/tools/read.ts +237 -216
- package/src/core/tools/render-utils.ts +196 -77
- package/src/core/tools/renderers.ts +1 -1
- 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 +73 -44
- 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 +24 -0
- package/src/modes/interactive/components/tool-execution.ts +34 -6
- 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,109 @@ 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 lsTool: LsTool;
|
|
445
|
+
|
|
446
|
+
constructor(session: ToolSession) {
|
|
447
|
+
this.session = session;
|
|
448
|
+
this.autoResizeImages = session.settings?.getImageAutoResize() ?? true;
|
|
449
|
+
this.lsTool = new LsTool(session);
|
|
450
|
+
this.description = renderPromptTemplate(readDescription, {
|
|
435
451
|
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}.`;
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
public async execute(
|
|
456
|
+
toolCallId: string,
|
|
457
|
+
params: ReadParams,
|
|
458
|
+
signal?: AbortSignal,
|
|
459
|
+
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
460
|
+
_context?: AgentToolContext,
|
|
461
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
462
|
+
const { path: readPath, offset, limit, lines } = params;
|
|
463
|
+
const absolutePath = resolveReadPath(readPath, this.session.cwd);
|
|
464
|
+
|
|
465
|
+
return untilAborted(signal, async () => {
|
|
466
|
+
let isDirectory = false;
|
|
467
|
+
let fileSize = 0;
|
|
468
|
+
try {
|
|
469
|
+
const stat = await Bun.file(absolutePath).stat();
|
|
470
|
+
fileSize = stat.size;
|
|
471
|
+
isDirectory = stat.isDirectory();
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (isNotFoundError(error)) {
|
|
474
|
+
let message = `File not found: ${readPath}`;
|
|
475
|
+
|
|
476
|
+
// Skip fuzzy matching for remote mounts (sshfs) to avoid hangs
|
|
477
|
+
if (!isRemoteMountPath(absolutePath)) {
|
|
478
|
+
const suggestions = await findReadPathSuggestions(readPath, this.session.cwd, signal);
|
|
479
|
+
|
|
480
|
+
if (suggestions?.suggestions.length) {
|
|
481
|
+
const scopeLabel = suggestions.scopeLabel ? ` in ${suggestions.scopeLabel}` : "";
|
|
482
|
+
message += `\n\nClosest matches${scopeLabel}:\n${suggestions.suggestions.map((match) => `- ${match}`).join("\n")}`;
|
|
483
|
+
if (suggestions.truncated) {
|
|
484
|
+
message += `\n[Search truncated to first ${MAX_FUZZY_CANDIDATES} paths. Refine the path if the match isn't listed.]`;
|
|
470
485
|
}
|
|
486
|
+
} else if (suggestions?.error) {
|
|
487
|
+
message += `\n\nFuzzy match failed: ${suggestions.error}`;
|
|
488
|
+
} else if (suggestions?.scopeLabel) {
|
|
489
|
+
message += `\n\nNo similar paths found in ${suggestions.scopeLabel}.`;
|
|
471
490
|
}
|
|
472
|
-
|
|
473
|
-
throw new Error(message);
|
|
474
491
|
}
|
|
475
|
-
throw error;
|
|
476
|
-
}
|
|
477
492
|
|
|
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
|
-
};
|
|
493
|
+
throw new Error(message);
|
|
484
494
|
}
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
485
497
|
|
|
486
|
-
|
|
487
|
-
const
|
|
498
|
+
if (isDirectory) {
|
|
499
|
+
const lsResult = await this.lsTool.execute(toolCallId, { path: readPath, limit }, signal);
|
|
500
|
+
return {
|
|
501
|
+
content: lsResult.content,
|
|
502
|
+
details: { redirectedTo: "ls", truncation: lsResult.details?.truncation },
|
|
503
|
+
};
|
|
504
|
+
}
|
|
488
505
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
506
|
+
const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
|
|
507
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
508
|
+
|
|
509
|
+
// Read the file based on type
|
|
510
|
+
let content: (TextContent | ImageContent)[];
|
|
511
|
+
let details: ReadToolDetails | undefined;
|
|
512
|
+
|
|
513
|
+
if (mimeType) {
|
|
514
|
+
if (fileSize > MAX_IMAGE_SIZE) {
|
|
515
|
+
const sizeStr = formatSize(fileSize);
|
|
516
|
+
const maxStr = formatSize(MAX_IMAGE_SIZE);
|
|
517
|
+
content = [
|
|
518
|
+
{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: `[Image file too large: ${sizeStr} exceeds ${maxStr} limit. Use an image viewer or resize the image.]`,
|
|
521
|
+
},
|
|
522
|
+
];
|
|
523
|
+
} else {
|
|
524
|
+
// Read as image (binary)
|
|
525
|
+
const file = Bun.file(absolutePath);
|
|
526
|
+
const buffer = await file.arrayBuffer();
|
|
492
527
|
|
|
493
|
-
|
|
494
|
-
if (
|
|
495
|
-
const sizeStr = formatSize(
|
|
528
|
+
// Check actual buffer size after reading to prevent OOM during serialization
|
|
529
|
+
if (buffer.byteLength > MAX_IMAGE_SIZE) {
|
|
530
|
+
const sizeStr = formatSize(buffer.byteLength);
|
|
496
531
|
const maxStr = formatSize(MAX_IMAGE_SIZE);
|
|
497
532
|
content = [
|
|
498
533
|
{
|
|
@@ -501,178 +536,164 @@ export function createReadTool(session: ToolSession): AgentTool<typeof readSchem
|
|
|
501
536
|
},
|
|
502
537
|
];
|
|
503
538
|
} 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
|
-
];
|
|
539
|
+
const base64 = Buffer.from(buffer).toString("base64");
|
|
540
|
+
|
|
541
|
+
if (this.autoResizeImages) {
|
|
542
|
+
// Resize image if needed - catch errors from WASM
|
|
543
|
+
try {
|
|
544
|
+
const resized = await resizeImage({ type: "image", data: base64, mimeType });
|
|
545
|
+
const dimensionNote = formatDimensionNote(resized);
|
|
546
|
+
|
|
547
|
+
let textNote = `Read image file [${resized.mimeType}]`;
|
|
548
|
+
if (dimensionNote) {
|
|
549
|
+
textNote += `\n${dimensionNote}`;
|
|
542
550
|
}
|
|
543
|
-
|
|
551
|
+
|
|
552
|
+
content = [
|
|
553
|
+
{ type: "text", text: textNote },
|
|
554
|
+
{ type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
555
|
+
];
|
|
556
|
+
} catch {
|
|
557
|
+
// Fall back to original image on resize failure
|
|
544
558
|
content = [
|
|
545
559
|
{ type: "text", text: `Read image file [${mimeType}]` },
|
|
546
560
|
{ type: "image", data: base64, mimeType },
|
|
547
561
|
];
|
|
548
562
|
}
|
|
563
|
+
} else {
|
|
564
|
+
content = [
|
|
565
|
+
{ type: "text", text: `Read image file [${mimeType}]` },
|
|
566
|
+
{ type: "image", data: base64, mimeType },
|
|
567
|
+
];
|
|
549
568
|
}
|
|
550
569
|
}
|
|
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}]` }];
|
|
570
|
+
}
|
|
571
|
+
} else if (CONVERTIBLE_EXTENSIONS.has(ext)) {
|
|
572
|
+
// Convert document via markitdown
|
|
573
|
+
const result = await convertWithMarkitdown(absolutePath, signal);
|
|
574
|
+
if (result.ok) {
|
|
575
|
+
// Apply truncation to converted content
|
|
576
|
+
const truncation = truncateHead(result.content);
|
|
577
|
+
let outputText = truncation.content;
|
|
578
|
+
|
|
579
|
+
if (truncation.truncated) {
|
|
580
|
+
outputText += `\n\n[Document converted via markitdown. Output truncated to ${formatSize(DEFAULT_MAX_BYTES)}]`;
|
|
581
|
+
details = { truncation };
|
|
572
582
|
}
|
|
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
583
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
584
|
+
content = [{ type: "text", text: outputText }];
|
|
585
|
+
} else if (result.error) {
|
|
586
|
+
// markitdown not available or failed
|
|
587
|
+
const errorMsg =
|
|
588
|
+
result.error === "markitdown not found"
|
|
589
|
+
? `markitdown not installed. Install with: pip install markitdown`
|
|
590
|
+
: result.error || "conversion failed";
|
|
591
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: ${errorMsg}]` }];
|
|
592
|
+
} else {
|
|
593
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: conversion failed]` }];
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
// Read as text
|
|
597
|
+
const file = Bun.file(absolutePath);
|
|
598
|
+
const textContent = await file.text();
|
|
599
|
+
const allLines = textContent.split("\n");
|
|
600
|
+
const totalFileLines = allLines.length;
|
|
601
|
+
|
|
602
|
+
// Apply offset if specified (1-indexed to 0-indexed)
|
|
603
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
604
|
+
const startLineDisplay = startLine + 1; // For display (1-indexed)
|
|
605
|
+
|
|
606
|
+
// Check if offset is out of bounds
|
|
607
|
+
if (startLine >= allLines.length) {
|
|
608
|
+
throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
|
|
609
|
+
}
|
|
583
610
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
611
|
+
// If limit is specified by user, use it; otherwise we'll let truncateHead decide
|
|
612
|
+
let selectedContent: string;
|
|
613
|
+
let userLimitedLines: number | undefined;
|
|
614
|
+
if (limit !== undefined) {
|
|
615
|
+
const endLine = Math.min(startLine + limit, allLines.length);
|
|
616
|
+
selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
617
|
+
userLimitedLines = endLine - startLine;
|
|
618
|
+
} else {
|
|
619
|
+
selectedContent = allLines.slice(startLine).join("\n");
|
|
620
|
+
}
|
|
588
621
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
622
|
+
// Apply truncation (respects both line and byte limits)
|
|
623
|
+
const truncation = truncateHead(selectedContent);
|
|
624
|
+
|
|
625
|
+
// Add line numbers if requested (default: false)
|
|
626
|
+
const shouldAddLineNumbers = lines === true;
|
|
627
|
+
const prependLineNumbers = (text: string, startNum: number): string => {
|
|
628
|
+
const textLines = text.split("\n");
|
|
629
|
+
const lastLineNum = startNum + textLines.length - 1;
|
|
630
|
+
const padWidth = String(lastLineNum).length;
|
|
631
|
+
return textLines
|
|
632
|
+
.map((line, i) => {
|
|
633
|
+
const lineNum = String(startNum + i).padStart(padWidth, " ");
|
|
634
|
+
return `${lineNum}\t${line}`;
|
|
635
|
+
})
|
|
636
|
+
.join("\n");
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
let outputText: string;
|
|
640
|
+
|
|
641
|
+
if (truncation.firstLineExceedsLimit) {
|
|
642
|
+
const firstLine = allLines[startLine] ?? "";
|
|
643
|
+
const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
|
|
644
|
+
const snippet = truncateStringToBytesFromStart(firstLine, DEFAULT_MAX_BYTES);
|
|
645
|
+
const shownSize = formatSize(snippet.bytes);
|
|
646
|
+
|
|
647
|
+
outputText = shouldAddLineNumbers ? prependLineNumbers(snippet.text, startLineDisplay) : snippet.text;
|
|
648
|
+
if (snippet.text.length > 0) {
|
|
649
|
+
outputText += `\n\n[Line ${startLineDisplay} is ${formatSize(
|
|
650
|
+
firstLineBytes,
|
|
651
|
+
)}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Showing first ${shownSize} of the line.]`;
|
|
596
652
|
} else {
|
|
597
|
-
|
|
653
|
+
outputText = `[Line ${startLineDisplay} is ${formatSize(
|
|
654
|
+
firstLineBytes,
|
|
655
|
+
)}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
598
656
|
}
|
|
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]`;
|
|
657
|
+
details = { truncation };
|
|
658
|
+
} else if (truncation.truncated) {
|
|
659
|
+
// Truncation occurred - build actionable notice
|
|
660
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
661
|
+
const nextOffset = endLineDisplay + 1;
|
|
662
|
+
|
|
663
|
+
outputText = shouldAddLineNumbers
|
|
664
|
+
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
665
|
+
: truncation.content;
|
|
666
|
+
|
|
667
|
+
if (truncation.truncatedBy === "lines") {
|
|
668
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;
|
|
662
669
|
} else {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
: truncation.content;
|
|
670
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(
|
|
671
|
+
DEFAULT_MAX_BYTES,
|
|
672
|
+
)} limit). Use offset=${nextOffset} to continue]`;
|
|
667
673
|
}
|
|
668
|
-
|
|
669
|
-
|
|
674
|
+
details = { truncation };
|
|
675
|
+
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
676
|
+
// User specified limit, there's more content, but no truncation
|
|
677
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
678
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
679
|
+
|
|
680
|
+
outputText = shouldAddLineNumbers
|
|
681
|
+
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
682
|
+
: truncation.content;
|
|
683
|
+
outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;
|
|
684
|
+
} else {
|
|
685
|
+
// No truncation, no user limit exceeded
|
|
686
|
+
outputText = shouldAddLineNumbers
|
|
687
|
+
? prependLineNumbers(truncation.content, startLineDisplay)
|
|
688
|
+
: truncation.content;
|
|
670
689
|
}
|
|
671
690
|
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
691
|
+
content = [{ type: "text", text: outputText }];
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return { content, details };
|
|
695
|
+
});
|
|
696
|
+
}
|
|
676
697
|
}
|
|
677
698
|
|
|
678
699
|
// =============================================================================
|