@oh-my-pi/pi-coding-agent 6.1.0 → 6.2.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 +10 -0
- package/package.json +5 -5
- package/src/core/tools/context.ts +5 -3
- package/src/core/tools/edit.ts +21 -2
- package/src/core/tools/lsp/index.ts +221 -62
- package/src/core/tools/write.ts +21 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [6.2.0] - 2026-01-19
|
|
6
|
+
### Changed
|
|
7
|
+
|
|
8
|
+
- Improved LSP batching to coalesce formatting and diagnostics for parallel edits
|
|
9
|
+
- Updated edit and write tools to support batched LSP operations
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Coalesced LSP formatting/diagnostics for parallel edits so only the final write triggers LSP across touched files
|
|
14
|
+
|
|
5
15
|
## [6.1.0] - 2026-01-19
|
|
6
16
|
|
|
7
17
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@oh-my-pi/pi-agent-core": "6.
|
|
44
|
-
"@oh-my-pi/pi-ai": "6.
|
|
45
|
-
"@oh-my-pi/pi-git-tool": "6.
|
|
46
|
-
"@oh-my-pi/pi-tui": "6.
|
|
43
|
+
"@oh-my-pi/pi-agent-core": "6.2.0",
|
|
44
|
+
"@oh-my-pi/pi-ai": "6.2.0",
|
|
45
|
+
"@oh-my-pi/pi-git-tool": "6.2.0",
|
|
46
|
+
"@oh-my-pi/pi-tui": "6.2.0",
|
|
47
47
|
"@openai/agents": "^0.3.7",
|
|
48
48
|
"@sinclair/typebox": "^0.34.46",
|
|
49
49
|
"ajv": "^8.17.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentToolContext } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import type { AgentToolContext, ToolCallContext } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { CustomToolContext } from "../custom-tools/types";
|
|
3
3
|
import type { ExtensionUIContext } from "../extensions/types";
|
|
4
4
|
|
|
@@ -7,11 +7,12 @@ declare module "@oh-my-pi/pi-agent-core" {
|
|
|
7
7
|
ui?: ExtensionUIContext;
|
|
8
8
|
hasUI?: boolean;
|
|
9
9
|
toolNames?: string[];
|
|
10
|
+
toolCall?: ToolCallContext;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface ToolContextStore {
|
|
14
|
-
getContext(): AgentToolContext;
|
|
15
|
+
getContext(toolCall?: ToolCallContext): AgentToolContext;
|
|
15
16
|
setUIContext(uiContext: ExtensionUIContext, hasUI: boolean): void;
|
|
16
17
|
setToolNames(names: string[]): void;
|
|
17
18
|
}
|
|
@@ -22,11 +23,12 @@ export function createToolContextStore(getBaseContext: () => CustomToolContext):
|
|
|
22
23
|
let toolNames: string[] = [];
|
|
23
24
|
|
|
24
25
|
return {
|
|
25
|
-
getContext: () => ({
|
|
26
|
+
getContext: (toolCall) => ({
|
|
26
27
|
...getBaseContext(),
|
|
27
28
|
ui: uiContext,
|
|
28
29
|
hasUI,
|
|
29
30
|
toolNames,
|
|
31
|
+
toolCall,
|
|
30
32
|
}),
|
|
31
33
|
setUIContext: (context, uiAvailable) => {
|
|
32
34
|
uiContext = context;
|
package/src/core/tools/edit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import type { AgentTool, AgentToolContext, ToolCallContext } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -41,6 +41,22 @@ export interface EditToolDetails {
|
|
|
41
41
|
diagnostics?: FileDiagnosticsResult;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
const LSP_BATCH_TOOLS = new Set(["edit", "write"]);
|
|
45
|
+
|
|
46
|
+
function getLspBatchRequest(toolCall: ToolCallContext | undefined): { id: string; flush: boolean } | undefined {
|
|
47
|
+
if (!toolCall) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const hasOtherWrites = toolCall.toolCalls.some(
|
|
51
|
+
(call, index) => index !== toolCall.index && LSP_BATCH_TOOLS.has(call.name),
|
|
52
|
+
);
|
|
53
|
+
if (!hasOtherWrites) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const hasLaterWrites = toolCall.toolCalls.slice(toolCall.index + 1).some((call) => LSP_BATCH_TOOLS.has(call.name));
|
|
57
|
+
return { id: toolCall.batchId, flush: !hasLaterWrites };
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
export function createEditTool(session: ToolSession): AgentTool<typeof editSchema> {
|
|
45
61
|
const allowFuzzy = session.settings?.getEditFuzzyMatch() ?? true;
|
|
46
62
|
const enableLsp = session.enableLsp ?? true;
|
|
@@ -58,6 +74,8 @@ export function createEditTool(session: ToolSession): AgentTool<typeof editSchem
|
|
|
58
74
|
_toolCallId: string,
|
|
59
75
|
{ path, oldText, newText, all }: { path: string; oldText: string; newText: string; all?: boolean },
|
|
60
76
|
signal?: AbortSignal,
|
|
77
|
+
_onUpdate?: unknown,
|
|
78
|
+
context?: AgentToolContext,
|
|
61
79
|
) => {
|
|
62
80
|
// Reject .ipynb files - use NotebookEdit tool instead
|
|
63
81
|
if (path.endsWith(".ipynb")) {
|
|
@@ -163,7 +181,8 @@ export function createEditTool(session: ToolSession): AgentTool<typeof editSchem
|
|
|
163
181
|
}
|
|
164
182
|
|
|
165
183
|
const finalContent = bom + restoreLineEndings(normalizedNewContent, originalEnding);
|
|
166
|
-
const
|
|
184
|
+
const batchRequest = getLspBatchRequest(context?.toolCall);
|
|
185
|
+
const diagnostics = await writethrough(absolutePath, finalContent, signal, file, batchRequest);
|
|
167
186
|
|
|
168
187
|
const diffResult = generateDiffString(normalizedContent, normalizedNewContent);
|
|
169
188
|
|
|
@@ -667,6 +667,7 @@ export type WritethroughCallback = (
|
|
|
667
667
|
content: string,
|
|
668
668
|
signal?: AbortSignal,
|
|
669
669
|
file?: BunFile,
|
|
670
|
+
batch?: LspWritethroughBatchRequest,
|
|
670
671
|
) => Promise<FileDiagnosticsResult | undefined>;
|
|
671
672
|
|
|
672
673
|
/** No-op writethrough callback */
|
|
@@ -684,83 +685,241 @@ export async function writethroughNoop(
|
|
|
684
685
|
return undefined;
|
|
685
686
|
}
|
|
686
687
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
688
|
+
interface PendingWritethrough {
|
|
689
|
+
dst: string;
|
|
690
|
+
content: string;
|
|
691
|
+
file?: BunFile;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
interface LspWritethroughBatchRequest {
|
|
695
|
+
id: string;
|
|
696
|
+
flush: boolean;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
interface LspWritethroughBatchState {
|
|
700
|
+
entries: Map<string, PendingWritethrough>;
|
|
701
|
+
options: Required<WritethroughOptions>;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const writethroughBatches = new Map<string, LspWritethroughBatchState>();
|
|
705
|
+
|
|
706
|
+
function getOrCreateWritethroughBatch(id: string, options: Required<WritethroughOptions>): LspWritethroughBatchState {
|
|
707
|
+
const existing = writethroughBatches.get(id);
|
|
708
|
+
if (existing) {
|
|
709
|
+
existing.options.enableFormat ||= options.enableFormat;
|
|
710
|
+
existing.options.enableDiagnostics ||= options.enableDiagnostics;
|
|
711
|
+
return existing;
|
|
692
712
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
713
|
+
const batch: LspWritethroughBatchState = {
|
|
714
|
+
entries: new Map<string, PendingWritethrough>(),
|
|
715
|
+
options: { ...options },
|
|
716
|
+
};
|
|
717
|
+
writethroughBatches.set(id, batch);
|
|
718
|
+
return batch;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function summarizeDiagnosticMessages(messages: string[]): { summary: string; errored: boolean } {
|
|
722
|
+
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
|
|
723
|
+
for (const message of messages) {
|
|
724
|
+
const match = message.match(/\[(error|warning|info|hint)\]/i);
|
|
725
|
+
if (!match) continue;
|
|
726
|
+
const key = match[1].toLowerCase() as keyof typeof counts;
|
|
727
|
+
counts[key] += 1;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const parts: string[] = [];
|
|
731
|
+
if (counts.error > 0) parts.push(`${counts.error} error(s)`);
|
|
732
|
+
if (counts.warning > 0) parts.push(`${counts.warning} warning(s)`);
|
|
733
|
+
if (counts.info > 0) parts.push(`${counts.info} info(s)`);
|
|
734
|
+
if (counts.hint > 0) parts.push(`${counts.hint} hint(s)`);
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
summary: parts.length > 0 ? parts.join(", ") : "no issues",
|
|
738
|
+
errored: counts.error > 0,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function mergeDiagnostics(
|
|
743
|
+
results: Array<FileDiagnosticsResult | undefined>,
|
|
744
|
+
options: Required<WritethroughOptions>,
|
|
745
|
+
): FileDiagnosticsResult | undefined {
|
|
746
|
+
const messages: string[] = [];
|
|
747
|
+
const servers = new Set<string>();
|
|
748
|
+
let hasResults = false;
|
|
749
|
+
let hasFormatter = false;
|
|
750
|
+
let formatted = false;
|
|
751
|
+
|
|
752
|
+
for (const result of results) {
|
|
753
|
+
if (!result) continue;
|
|
754
|
+
hasResults = true;
|
|
755
|
+
if (result.server) {
|
|
756
|
+
for (const server of result.server.split(",")) {
|
|
757
|
+
const trimmed = server.trim();
|
|
758
|
+
if (trimmed) {
|
|
759
|
+
servers.add(trimmed);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (result.messages.length > 0) {
|
|
764
|
+
messages.push(...result.messages);
|
|
765
|
+
}
|
|
766
|
+
if (result.formatter !== undefined) {
|
|
767
|
+
hasFormatter = true;
|
|
768
|
+
if (result.formatter === FileFormatResult.FORMATTED) {
|
|
769
|
+
formatted = true;
|
|
770
|
+
}
|
|
698
771
|
}
|
|
699
|
-
|
|
772
|
+
}
|
|
700
773
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const useCustomFormatter = enableFormat && customLinterServers.length > 0;
|
|
774
|
+
if (!hasResults && !hasFormatter) {
|
|
775
|
+
return undefined;
|
|
776
|
+
}
|
|
705
777
|
|
|
706
|
-
|
|
707
|
-
|
|
778
|
+
let summary = options.enableDiagnostics ? "no issues" : "OK";
|
|
779
|
+
let errored = false;
|
|
780
|
+
if (messages.length > 0) {
|
|
781
|
+
const summaryInfo = summarizeDiagnosticMessages(messages);
|
|
782
|
+
summary = summaryInfo.summary;
|
|
783
|
+
errored = summaryInfo.errored;
|
|
784
|
+
}
|
|
785
|
+
const formatter = hasFormatter ? (formatted ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED) : undefined;
|
|
708
786
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
787
|
+
return {
|
|
788
|
+
server: servers.size > 0 ? Array.from(servers).join(", ") : undefined,
|
|
789
|
+
messages,
|
|
790
|
+
summary,
|
|
791
|
+
errored,
|
|
792
|
+
formatter,
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async function runLspWritethrough(
|
|
797
|
+
dst: string,
|
|
798
|
+
content: string,
|
|
799
|
+
cwd: string,
|
|
800
|
+
options: Required<WritethroughOptions>,
|
|
801
|
+
signal?: AbortSignal,
|
|
802
|
+
file?: BunFile,
|
|
803
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
804
|
+
const { enableFormat, enableDiagnostics } = options;
|
|
805
|
+
const config = await getConfig(cwd);
|
|
806
|
+
const servers = getServersForFile(config, dst);
|
|
807
|
+
if (servers.length === 0) {
|
|
808
|
+
return writethroughNoop(dst, content, signal, file);
|
|
809
|
+
}
|
|
810
|
+
const { lspServers, customLinterServers } = splitServers(servers);
|
|
811
|
+
|
|
812
|
+
let finalContent = content;
|
|
813
|
+
const writeContent = async (value: string) => (file ? file.write(value) : Bun.write(dst, value));
|
|
814
|
+
const getWritePromise = once(() => writeContent(finalContent));
|
|
815
|
+
const useCustomFormatter = enableFormat && customLinterServers.length > 0;
|
|
816
|
+
|
|
817
|
+
// Capture diagnostic versions BEFORE syncing to detect stale diagnostics
|
|
818
|
+
const minVersions = enableDiagnostics ? await captureDiagnosticVersions(cwd, servers) : undefined;
|
|
819
|
+
|
|
820
|
+
let formatter: FileFormatResult | undefined;
|
|
821
|
+
let diagnostics: FileDiagnosticsResult | undefined;
|
|
822
|
+
try {
|
|
823
|
+
const timeoutSignal = AbortSignal.timeout(10_000);
|
|
824
|
+
const operationSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
825
|
+
await untilAborted(operationSignal, async () => {
|
|
826
|
+
if (useCustomFormatter) {
|
|
827
|
+
// Custom linters (e.g. Biome CLI) require on-disk input.
|
|
828
|
+
await writeContent(content);
|
|
829
|
+
finalContent = await formatContent(dst, content, cwd, customLinterServers, operationSignal);
|
|
830
|
+
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
831
|
+
await writeContent(finalContent);
|
|
832
|
+
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
|
|
833
|
+
} else {
|
|
834
|
+
// 1. Sync original content to LSP servers
|
|
835
|
+
await syncFileContent(dst, content, cwd, lspServers, operationSignal);
|
|
836
|
+
|
|
837
|
+
// 2. Format in-memory via LSP
|
|
838
|
+
if (enableFormat) {
|
|
839
|
+
finalContent = await formatContent(dst, content, cwd, lspServers, operationSignal);
|
|
719
840
|
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
720
|
-
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// 3. If formatted, sync formatted content to LSP servers
|
|
844
|
+
if (finalContent !== content) {
|
|
721
845
|
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
|
|
722
|
-
}
|
|
723
|
-
// 1. Sync original content to LSP servers
|
|
724
|
-
await syncFileContent(dst, content, cwd, lspServers, operationSignal);
|
|
725
|
-
|
|
726
|
-
// 2. Format in-memory via LSP
|
|
727
|
-
if (enableFormat) {
|
|
728
|
-
finalContent = await formatContent(dst, content, cwd, lspServers, operationSignal);
|
|
729
|
-
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
730
|
-
}
|
|
846
|
+
}
|
|
731
847
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
848
|
+
// 4. Write to disk
|
|
849
|
+
await getWritePromise();
|
|
850
|
+
}
|
|
736
851
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
}
|
|
852
|
+
// 5. Notify saved to LSP servers
|
|
853
|
+
await notifyFileSaved(dst, cwd, lspServers, operationSignal);
|
|
740
854
|
|
|
741
|
-
|
|
742
|
-
|
|
855
|
+
// 6. Get diagnostics from all servers (wait for fresh results)
|
|
856
|
+
if (enableDiagnostics) {
|
|
857
|
+
diagnostics = await getDiagnosticsForFile(dst, cwd, servers, operationSignal, minVersions);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
} catch {
|
|
861
|
+
await getWritePromise();
|
|
862
|
+
}
|
|
743
863
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
864
|
+
if (formatter !== undefined) {
|
|
865
|
+
diagnostics ??= {
|
|
866
|
+
server: servers.map(([name]) => name).join(", "),
|
|
867
|
+
messages: [],
|
|
868
|
+
summary: "OK",
|
|
869
|
+
errored: false,
|
|
870
|
+
};
|
|
871
|
+
diagnostics.formatter = formatter;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return diagnostics;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function flushWritethroughBatch(
|
|
878
|
+
batch: PendingWritethrough[],
|
|
879
|
+
cwd: string,
|
|
880
|
+
options: Required<WritethroughOptions>,
|
|
881
|
+
signal?: AbortSignal,
|
|
882
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
883
|
+
if (batch.length === 0) {
|
|
884
|
+
return undefined;
|
|
885
|
+
}
|
|
886
|
+
const results: Array<FileDiagnosticsResult | undefined> = [];
|
|
887
|
+
for (const entry of batch) {
|
|
888
|
+
results.push(await runLspWritethrough(entry.dst, entry.content, cwd, options, signal, entry.file));
|
|
889
|
+
}
|
|
890
|
+
return mergeDiagnostics(results, options);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** Create a writethrough callback for LSP aware write operations */
|
|
894
|
+
export function createLspWritethrough(cwd: string, options?: WritethroughOptions): WritethroughCallback {
|
|
895
|
+
const resolvedOptions: Required<WritethroughOptions> = {
|
|
896
|
+
enableFormat: options?.enableFormat ?? false,
|
|
897
|
+
enableDiagnostics: options?.enableDiagnostics ?? false,
|
|
898
|
+
};
|
|
899
|
+
if (!resolvedOptions.enableFormat && !resolvedOptions.enableDiagnostics) {
|
|
900
|
+
return writethroughNoop;
|
|
901
|
+
}
|
|
902
|
+
return async (
|
|
903
|
+
dst: string,
|
|
904
|
+
content: string,
|
|
905
|
+
signal?: AbortSignal,
|
|
906
|
+
file?: BunFile,
|
|
907
|
+
batch?: LspWritethroughBatchRequest,
|
|
908
|
+
) => {
|
|
909
|
+
if (!batch) {
|
|
910
|
+
return runLspWritethrough(dst, content, cwd, resolvedOptions, signal, file);
|
|
751
911
|
}
|
|
752
912
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
};
|
|
760
|
-
diagnostics.formatter = formatter;
|
|
913
|
+
const state = getOrCreateWritethroughBatch(batch.id, resolvedOptions);
|
|
914
|
+
state.entries.set(dst, { dst, content, file });
|
|
915
|
+
|
|
916
|
+
if (!batch.flush) {
|
|
917
|
+
await writethroughNoop(dst, content, signal, file);
|
|
918
|
+
return undefined;
|
|
761
919
|
}
|
|
762
920
|
|
|
763
|
-
|
|
921
|
+
writethroughBatches.delete(batch.id);
|
|
922
|
+
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal);
|
|
764
923
|
};
|
|
765
924
|
}
|
|
766
925
|
|
package/src/core/tools/write.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import type { AgentTool, AgentToolContext, ToolCallContext } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -22,6 +22,22 @@ export interface WriteToolDetails {
|
|
|
22
22
|
diagnostics?: FileDiagnosticsResult;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const LSP_BATCH_TOOLS = new Set(["edit", "write"]);
|
|
26
|
+
|
|
27
|
+
function getLspBatchRequest(toolCall: ToolCallContext | undefined): { id: string; flush: boolean } | undefined {
|
|
28
|
+
if (!toolCall) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const hasOtherWrites = toolCall.toolCalls.some(
|
|
32
|
+
(call, index) => index !== toolCall.index && LSP_BATCH_TOOLS.has(call.name),
|
|
33
|
+
);
|
|
34
|
+
if (!hasOtherWrites) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const hasLaterWrites = toolCall.toolCalls.slice(toolCall.index + 1).some((call) => LSP_BATCH_TOOLS.has(call.name));
|
|
38
|
+
return { id: toolCall.batchId, flush: !hasLaterWrites };
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
export function createWriteTool(session: ToolSession): AgentTool<typeof writeSchema, WriteToolDetails> {
|
|
26
42
|
const enableLsp = session.enableLsp ?? true;
|
|
27
43
|
const enableFormat = enableLsp ? (session.settings?.getLspFormatOnWrite() ?? true) : false;
|
|
@@ -38,11 +54,14 @@ export function createWriteTool(session: ToolSession): AgentTool<typeof writeSch
|
|
|
38
54
|
_toolCallId: string,
|
|
39
55
|
{ path, content }: { path: string; content: string },
|
|
40
56
|
signal?: AbortSignal,
|
|
57
|
+
_onUpdate?: unknown,
|
|
58
|
+
context?: AgentToolContext,
|
|
41
59
|
) => {
|
|
42
60
|
return untilAborted(signal, async () => {
|
|
43
61
|
const absolutePath = resolveToCwd(path, session.cwd);
|
|
62
|
+
const batchRequest = getLspBatchRequest(context?.toolCall);
|
|
44
63
|
|
|
45
|
-
const diagnostics = await writethrough(absolutePath, content, signal);
|
|
64
|
+
const diagnostics = await writethrough(absolutePath, content, signal, undefined, batchRequest);
|
|
46
65
|
|
|
47
66
|
let resultText = `Successfully wrote ${content.length} bytes to ${path}`;
|
|
48
67
|
if (!diagnostics) {
|