@ljoukov/llm 0.1.3 → 2.1.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/README.md +175 -31
- package/dist/index.cjs +2167 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -6
- package/dist/index.d.ts +254 -6
- package/dist/index.js +2154 -41
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.cjs
CHANGED
|
@@ -30,10 +30,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
InMemoryAgentFilesystem: () => InMemoryAgentFilesystem,
|
|
33
34
|
LlmJsonCallError: () => LlmJsonCallError,
|
|
34
35
|
appendMarkdownSourcesSection: () => appendMarkdownSourcesSection,
|
|
36
|
+
applyPatch: () => applyPatch,
|
|
35
37
|
configureGemini: () => configureGemini,
|
|
36
38
|
convertGooglePartsToLlmParts: () => convertGooglePartsToLlmParts,
|
|
39
|
+
createApplyPatchTool: () => createApplyPatchTool,
|
|
40
|
+
createCodexApplyPatchTool: () => createCodexApplyPatchTool,
|
|
41
|
+
createCodexFilesystemToolSet: () => createCodexFilesystemToolSet,
|
|
42
|
+
createCodexReadFileTool: () => createCodexReadFileTool,
|
|
43
|
+
createFilesystemToolSetForModel: () => createFilesystemToolSetForModel,
|
|
44
|
+
createGeminiFilesystemToolSet: () => createGeminiFilesystemToolSet,
|
|
45
|
+
createGlobTool: () => createGlobTool,
|
|
46
|
+
createGrepFilesTool: () => createGrepFilesTool,
|
|
47
|
+
createGrepSearchTool: () => createGrepSearchTool,
|
|
48
|
+
createInMemoryAgentFilesystem: () => createInMemoryAgentFilesystem,
|
|
49
|
+
createListDirTool: () => createListDirTool,
|
|
50
|
+
createListDirectoryTool: () => createListDirectoryTool,
|
|
51
|
+
createModelAgnosticFilesystemToolSet: () => createModelAgnosticFilesystemToolSet,
|
|
52
|
+
createNodeAgentFilesystem: () => createNodeAgentFilesystem,
|
|
53
|
+
createReadFileTool: () => createReadFileTool,
|
|
54
|
+
createReplaceTool: () => createReplaceTool,
|
|
55
|
+
createWriteFileTool: () => createWriteFileTool,
|
|
37
56
|
encodeChatGptAuthJson: () => encodeChatGptAuthJson,
|
|
38
57
|
encodeChatGptAuthJsonB64: () => encodeChatGptAuthJsonB64,
|
|
39
58
|
estimateCallCostUsd: () => estimateCallCostUsd,
|
|
@@ -49,8 +68,11 @@ __export(index_exports, {
|
|
|
49
68
|
loadLocalEnv: () => loadLocalEnv,
|
|
50
69
|
parseJsonFromLlmText: () => parseJsonFromLlmText,
|
|
51
70
|
refreshChatGptOauthToken: () => refreshChatGptOauthToken,
|
|
71
|
+
resolveFilesystemToolProfile: () => resolveFilesystemToolProfile,
|
|
72
|
+
runAgentLoop: () => runAgentLoop,
|
|
52
73
|
runToolLoop: () => runToolLoop,
|
|
53
74
|
sanitisePartForLogging: () => sanitisePartForLogging,
|
|
75
|
+
streamJson: () => streamJson,
|
|
54
76
|
streamText: () => streamText,
|
|
55
77
|
stripCodexCitationMarkers: () => stripCodexCitationMarkers,
|
|
56
78
|
toGeminiJsonSchema: () => toGeminiJsonSchema,
|
|
@@ -182,12 +204,23 @@ var OPENAI_GPT_52_PRICING = {
|
|
|
182
204
|
cachedRate: 0.175 / 1e6,
|
|
183
205
|
outputRate: 14 / 1e6
|
|
184
206
|
};
|
|
207
|
+
var OPENAI_GPT_53_CODEX_PRICING = {
|
|
208
|
+
inputRate: 1.25 / 1e6,
|
|
209
|
+
cachedRate: 0.125 / 1e6,
|
|
210
|
+
outputRate: 10 / 1e6
|
|
211
|
+
};
|
|
185
212
|
var OPENAI_GPT_51_CODEX_MINI_PRICING = {
|
|
186
213
|
inputRate: 0.25 / 1e6,
|
|
187
214
|
cachedRate: 0.025 / 1e6,
|
|
188
215
|
outputRate: 2 / 1e6
|
|
189
216
|
};
|
|
190
217
|
function getOpenAiPricing(modelId) {
|
|
218
|
+
if (modelId.includes("gpt-5.3-codex")) {
|
|
219
|
+
return OPENAI_GPT_53_CODEX_PRICING;
|
|
220
|
+
}
|
|
221
|
+
if (modelId.includes("gpt-5-codex")) {
|
|
222
|
+
return OPENAI_GPT_53_CODEX_PRICING;
|
|
223
|
+
}
|
|
191
224
|
if (modelId.includes("gpt-5.2")) {
|
|
192
225
|
return OPENAI_GPT_52_PRICING;
|
|
193
226
|
}
|
|
@@ -1575,10 +1608,13 @@ function convertGooglePartsToLlmParts(parts) {
|
|
|
1575
1608
|
function assertLlmRole(value) {
|
|
1576
1609
|
switch (value) {
|
|
1577
1610
|
case "user":
|
|
1578
|
-
case "
|
|
1611
|
+
case "assistant":
|
|
1579
1612
|
case "system":
|
|
1613
|
+
case "developer":
|
|
1580
1614
|
case "tool":
|
|
1581
1615
|
return value;
|
|
1616
|
+
case "model":
|
|
1617
|
+
return "assistant";
|
|
1582
1618
|
default:
|
|
1583
1619
|
throw new Error(`Unsupported LLM role: ${String(value)}`);
|
|
1584
1620
|
}
|
|
@@ -1608,8 +1644,9 @@ function toGeminiPart(part) {
|
|
|
1608
1644
|
}
|
|
1609
1645
|
}
|
|
1610
1646
|
function convertLlmContentToGeminiContent(content) {
|
|
1647
|
+
const role = content.role === "assistant" ? "model" : "user";
|
|
1611
1648
|
return {
|
|
1612
|
-
role
|
|
1649
|
+
role,
|
|
1613
1650
|
parts: content.parts.map(toGeminiPart)
|
|
1614
1651
|
};
|
|
1615
1652
|
}
|
|
@@ -1780,28 +1817,350 @@ function parseJsonFromLlmText(rawText) {
|
|
|
1780
1817
|
const repairedText = escapeNewlinesInStrings(cleanedText);
|
|
1781
1818
|
return JSON.parse(repairedText);
|
|
1782
1819
|
}
|
|
1783
|
-
function
|
|
1784
|
-
|
|
1785
|
-
|
|
1820
|
+
function parsePartialJsonFromLlmText(rawText) {
|
|
1821
|
+
const jsonStart = extractJsonStartText(rawText);
|
|
1822
|
+
if (!jsonStart) {
|
|
1823
|
+
return null;
|
|
1824
|
+
}
|
|
1825
|
+
try {
|
|
1826
|
+
return parsePartialJson(jsonStart);
|
|
1827
|
+
} catch {
|
|
1828
|
+
return null;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function extractJsonStartText(rawText) {
|
|
1832
|
+
let text = rawText.trimStart();
|
|
1833
|
+
if (text.startsWith("```")) {
|
|
1834
|
+
text = text.replace(/^```[a-zA-Z0-9_-]*\s*\n?/, "");
|
|
1835
|
+
}
|
|
1836
|
+
const objIndex = text.indexOf("{");
|
|
1837
|
+
const arrIndex = text.indexOf("[");
|
|
1838
|
+
let start = -1;
|
|
1839
|
+
if (objIndex !== -1 && arrIndex !== -1) {
|
|
1840
|
+
start = Math.min(objIndex, arrIndex);
|
|
1841
|
+
} else {
|
|
1842
|
+
start = objIndex !== -1 ? objIndex : arrIndex;
|
|
1843
|
+
}
|
|
1844
|
+
if (start === -1) {
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
return text.slice(start);
|
|
1848
|
+
}
|
|
1849
|
+
function parsePartialJson(text) {
|
|
1850
|
+
let i = 0;
|
|
1851
|
+
const len = text.length;
|
|
1852
|
+
const isWhitespace = (char) => char === " " || char === "\n" || char === "\r" || char === " ";
|
|
1853
|
+
const skipWhitespace = () => {
|
|
1854
|
+
while (i < len && isWhitespace(text[i] ?? "")) {
|
|
1855
|
+
i += 1;
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
const parseString = () => {
|
|
1859
|
+
if (text[i] !== '"') {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
i += 1;
|
|
1863
|
+
let value = "";
|
|
1864
|
+
while (i < len) {
|
|
1865
|
+
const ch = text[i] ?? "";
|
|
1866
|
+
if (ch === '"') {
|
|
1867
|
+
i += 1;
|
|
1868
|
+
return { value, complete: true };
|
|
1869
|
+
}
|
|
1870
|
+
if (ch === "\\") {
|
|
1871
|
+
if (i + 1 >= len) {
|
|
1872
|
+
return { value, complete: false };
|
|
1873
|
+
}
|
|
1874
|
+
const esc = text[i + 1] ?? "";
|
|
1875
|
+
switch (esc) {
|
|
1876
|
+
case '"':
|
|
1877
|
+
case "\\":
|
|
1878
|
+
case "/":
|
|
1879
|
+
value += esc;
|
|
1880
|
+
i += 2;
|
|
1881
|
+
continue;
|
|
1882
|
+
case "b":
|
|
1883
|
+
value += "\b";
|
|
1884
|
+
i += 2;
|
|
1885
|
+
continue;
|
|
1886
|
+
case "f":
|
|
1887
|
+
value += "\f";
|
|
1888
|
+
i += 2;
|
|
1889
|
+
continue;
|
|
1890
|
+
case "n":
|
|
1891
|
+
value += "\n";
|
|
1892
|
+
i += 2;
|
|
1893
|
+
continue;
|
|
1894
|
+
case "r":
|
|
1895
|
+
value += "\r";
|
|
1896
|
+
i += 2;
|
|
1897
|
+
continue;
|
|
1898
|
+
case "t":
|
|
1899
|
+
value += " ";
|
|
1900
|
+
i += 2;
|
|
1901
|
+
continue;
|
|
1902
|
+
case "u": {
|
|
1903
|
+
if (i + 5 >= len) {
|
|
1904
|
+
return { value, complete: false };
|
|
1905
|
+
}
|
|
1906
|
+
const hex = text.slice(i + 2, i + 6);
|
|
1907
|
+
if (!/^[0-9a-fA-F]{4}$/u.test(hex)) {
|
|
1908
|
+
value += "u";
|
|
1909
|
+
i += 2;
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
1913
|
+
i += 6;
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
default:
|
|
1917
|
+
value += esc;
|
|
1918
|
+
i += 2;
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
value += ch;
|
|
1923
|
+
i += 1;
|
|
1924
|
+
}
|
|
1925
|
+
return { value, complete: false };
|
|
1926
|
+
};
|
|
1927
|
+
const parseNumber = () => {
|
|
1928
|
+
const start = i;
|
|
1929
|
+
while (i < len) {
|
|
1930
|
+
const ch = text[i] ?? "";
|
|
1931
|
+
if (isWhitespace(ch) || ch === "," || ch === "}" || ch === "]") {
|
|
1932
|
+
break;
|
|
1933
|
+
}
|
|
1934
|
+
i += 1;
|
|
1935
|
+
}
|
|
1936
|
+
const raw = text.slice(start, i);
|
|
1937
|
+
if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u.test(raw)) {
|
|
1938
|
+
i = start;
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
return { value: Number(raw), complete: true };
|
|
1942
|
+
};
|
|
1943
|
+
const parseLiteral = () => {
|
|
1944
|
+
if (text.startsWith("true", i)) {
|
|
1945
|
+
i += 4;
|
|
1946
|
+
return { value: true, complete: true };
|
|
1947
|
+
}
|
|
1948
|
+
if (text.startsWith("false", i)) {
|
|
1949
|
+
i += 5;
|
|
1950
|
+
return { value: false, complete: true };
|
|
1951
|
+
}
|
|
1952
|
+
if (text.startsWith("null", i)) {
|
|
1953
|
+
i += 4;
|
|
1954
|
+
return { value: null, complete: true };
|
|
1955
|
+
}
|
|
1956
|
+
return null;
|
|
1957
|
+
};
|
|
1958
|
+
skipWhitespace();
|
|
1959
|
+
const first = text[i];
|
|
1960
|
+
if (first !== "{" && first !== "[") {
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
const root = first === "{" ? {} : [];
|
|
1964
|
+
const stack = first === "{" ? [{ type: "object", value: root, state: "keyOrEnd" }] : [{ type: "array", value: root, state: "valueOrEnd" }];
|
|
1965
|
+
i += 1;
|
|
1966
|
+
while (stack.length > 0) {
|
|
1967
|
+
skipWhitespace();
|
|
1968
|
+
if (i >= len) {
|
|
1969
|
+
break;
|
|
1970
|
+
}
|
|
1971
|
+
const ctx = stack[stack.length - 1];
|
|
1972
|
+
if (!ctx) {
|
|
1973
|
+
break;
|
|
1974
|
+
}
|
|
1975
|
+
const ch = text[i] ?? "";
|
|
1976
|
+
if (ctx.type === "object") {
|
|
1977
|
+
if (ctx.state === "keyOrEnd") {
|
|
1978
|
+
if (ch === "}") {
|
|
1979
|
+
i += 1;
|
|
1980
|
+
stack.pop();
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
if (ch === ",") {
|
|
1984
|
+
i += 1;
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
if (ch !== '"') {
|
|
1988
|
+
break;
|
|
1989
|
+
}
|
|
1990
|
+
const key = parseString();
|
|
1991
|
+
if (!key) {
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
if (!key.complete) {
|
|
1995
|
+
break;
|
|
1996
|
+
}
|
|
1997
|
+
ctx.key = key.value;
|
|
1998
|
+
ctx.state = "colon";
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
if (ctx.state === "colon") {
|
|
2002
|
+
if (ch === ":") {
|
|
2003
|
+
i += 1;
|
|
2004
|
+
ctx.state = "value";
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
break;
|
|
2008
|
+
}
|
|
2009
|
+
if (ctx.state === "value") {
|
|
2010
|
+
if (ch === "}") {
|
|
2011
|
+
i += 1;
|
|
2012
|
+
ctx.key = void 0;
|
|
2013
|
+
stack.pop();
|
|
2014
|
+
continue;
|
|
2015
|
+
}
|
|
2016
|
+
if (ch === ",") {
|
|
2017
|
+
i += 1;
|
|
2018
|
+
ctx.key = void 0;
|
|
2019
|
+
ctx.state = "keyOrEnd";
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
const key = ctx.key;
|
|
2023
|
+
if (!key) {
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
if (ch === "{" || ch === "[") {
|
|
2027
|
+
const container = ch === "{" ? {} : [];
|
|
2028
|
+
ctx.value[key] = container;
|
|
2029
|
+
ctx.key = void 0;
|
|
2030
|
+
ctx.state = "commaOrEnd";
|
|
2031
|
+
stack.push(
|
|
2032
|
+
ch === "{" ? { type: "object", value: container, state: "keyOrEnd" } : { type: "array", value: container, state: "valueOrEnd" }
|
|
2033
|
+
);
|
|
2034
|
+
i += 1;
|
|
2035
|
+
continue;
|
|
2036
|
+
}
|
|
2037
|
+
let primitive = null;
|
|
2038
|
+
if (ch === '"') {
|
|
2039
|
+
primitive = parseString();
|
|
2040
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
2041
|
+
primitive = parseNumber();
|
|
2042
|
+
} else {
|
|
2043
|
+
primitive = parseLiteral();
|
|
2044
|
+
}
|
|
2045
|
+
if (!primitive) {
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
ctx.value[key] = primitive.value;
|
|
2049
|
+
ctx.key = void 0;
|
|
2050
|
+
ctx.state = "commaOrEnd";
|
|
2051
|
+
if (!primitive.complete) {
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
if (ctx.state === "commaOrEnd") {
|
|
2057
|
+
if (ch === ",") {
|
|
2058
|
+
i += 1;
|
|
2059
|
+
ctx.state = "keyOrEnd";
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
if (ch === "}") {
|
|
2063
|
+
i += 1;
|
|
2064
|
+
stack.pop();
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
break;
|
|
2068
|
+
}
|
|
2069
|
+
} else {
|
|
2070
|
+
if (ctx.state === "valueOrEnd") {
|
|
2071
|
+
if (ch === "]") {
|
|
2072
|
+
i += 1;
|
|
2073
|
+
stack.pop();
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
if (ch === ",") {
|
|
2077
|
+
i += 1;
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
if (ch === "{" || ch === "[") {
|
|
2081
|
+
const container = ch === "{" ? {} : [];
|
|
2082
|
+
ctx.value.push(container);
|
|
2083
|
+
ctx.state = "commaOrEnd";
|
|
2084
|
+
stack.push(
|
|
2085
|
+
ch === "{" ? { type: "object", value: container, state: "keyOrEnd" } : { type: "array", value: container, state: "valueOrEnd" }
|
|
2086
|
+
);
|
|
2087
|
+
i += 1;
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
let primitive = null;
|
|
2091
|
+
if (ch === '"') {
|
|
2092
|
+
primitive = parseString();
|
|
2093
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
2094
|
+
primitive = parseNumber();
|
|
2095
|
+
} else {
|
|
2096
|
+
primitive = parseLiteral();
|
|
2097
|
+
}
|
|
2098
|
+
if (!primitive) {
|
|
2099
|
+
break;
|
|
2100
|
+
}
|
|
2101
|
+
ctx.value.push(primitive.value);
|
|
2102
|
+
ctx.state = "commaOrEnd";
|
|
2103
|
+
if (!primitive.complete) {
|
|
2104
|
+
break;
|
|
2105
|
+
}
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
if (ctx.state === "commaOrEnd") {
|
|
2109
|
+
if (ch === ",") {
|
|
2110
|
+
i += 1;
|
|
2111
|
+
ctx.state = "valueOrEnd";
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
if (ch === "]") {
|
|
2115
|
+
i += 1;
|
|
2116
|
+
stack.pop();
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
break;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
1786
2122
|
}
|
|
2123
|
+
return root;
|
|
2124
|
+
}
|
|
2125
|
+
function resolveTextContents(input) {
|
|
1787
2126
|
const contents = [];
|
|
1788
|
-
if (input.
|
|
2127
|
+
if (input.instructions) {
|
|
2128
|
+
const instructions = input.instructions.trim();
|
|
2129
|
+
if (instructions.length > 0) {
|
|
2130
|
+
contents.push({
|
|
2131
|
+
role: "system",
|
|
2132
|
+
parts: [{ type: "text", text: instructions }]
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
if (typeof input.input === "string") {
|
|
1789
2137
|
contents.push({
|
|
1790
|
-
role: "
|
|
1791
|
-
parts: [{ type: "text", text: input.
|
|
2138
|
+
role: "user",
|
|
2139
|
+
parts: [{ type: "text", text: input.input }]
|
|
2140
|
+
});
|
|
2141
|
+
return contents;
|
|
2142
|
+
}
|
|
2143
|
+
for (const message of input.input) {
|
|
2144
|
+
const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
|
|
2145
|
+
contents.push({
|
|
2146
|
+
role: message.role,
|
|
2147
|
+
parts: parts.map(
|
|
2148
|
+
(part) => part.type === "text" ? {
|
|
2149
|
+
type: "text",
|
|
2150
|
+
text: part.text,
|
|
2151
|
+
thought: "thought" in part && part.thought === true ? true : void 0
|
|
2152
|
+
} : { type: "inlineData", data: part.data, mimeType: part.mimeType }
|
|
2153
|
+
)
|
|
1792
2154
|
});
|
|
1793
2155
|
}
|
|
1794
|
-
contents.push({
|
|
1795
|
-
role: "user",
|
|
1796
|
-
parts: [{ type: "text", text: input.prompt }]
|
|
1797
|
-
});
|
|
1798
2156
|
return contents;
|
|
1799
2157
|
}
|
|
1800
2158
|
function toOpenAiInput(contents) {
|
|
1801
2159
|
const OPENAI_ROLE_FROM_LLM = {
|
|
1802
2160
|
user: "user",
|
|
1803
|
-
|
|
2161
|
+
assistant: "assistant",
|
|
1804
2162
|
system: "system",
|
|
2163
|
+
developer: "developer",
|
|
1805
2164
|
tool: "assistant"
|
|
1806
2165
|
};
|
|
1807
2166
|
return contents.map((content) => {
|
|
@@ -1840,7 +2199,7 @@ function toChatGptInput(contents) {
|
|
|
1840
2199
|
const instructionsParts = [];
|
|
1841
2200
|
const input = [];
|
|
1842
2201
|
for (const content of contents) {
|
|
1843
|
-
if (content.role === "system") {
|
|
2202
|
+
if (content.role === "system" || content.role === "developer") {
|
|
1844
2203
|
for (const part of content.parts) {
|
|
1845
2204
|
if (part.type === "text") {
|
|
1846
2205
|
instructionsParts.push(part.text);
|
|
@@ -1848,7 +2207,7 @@ function toChatGptInput(contents) {
|
|
|
1848
2207
|
}
|
|
1849
2208
|
continue;
|
|
1850
2209
|
}
|
|
1851
|
-
const isAssistant = content.role === "
|
|
2210
|
+
const isAssistant = content.role === "assistant" || content.role === "tool";
|
|
1852
2211
|
const parts = [];
|
|
1853
2212
|
for (const part of content.parts) {
|
|
1854
2213
|
if (part.type === "text") {
|
|
@@ -2120,8 +2479,8 @@ function parseOpenAiToolArguments(raw) {
|
|
|
2120
2479
|
function formatZodIssues(issues) {
|
|
2121
2480
|
const messages = [];
|
|
2122
2481
|
for (const issue of issues) {
|
|
2123
|
-
const
|
|
2124
|
-
messages.push(`${
|
|
2482
|
+
const path5 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
|
|
2483
|
+
messages.push(`${path5}: ${issue.message}`);
|
|
2125
2484
|
}
|
|
2126
2485
|
return messages.join("; ");
|
|
2127
2486
|
}
|
|
@@ -2568,7 +2927,7 @@ async function runTextCall(params) {
|
|
|
2568
2927
|
});
|
|
2569
2928
|
}
|
|
2570
2929
|
const mergedParts = mergeConsecutiveTextParts(responseParts);
|
|
2571
|
-
const content = mergedParts.length > 0 ? { role: responseRole ?? "
|
|
2930
|
+
const content = mergedParts.length > 0 ? { role: responseRole ?? "assistant", parts: mergedParts } : void 0;
|
|
2572
2931
|
const { text, thoughts } = extractTextByChannel(content);
|
|
2573
2932
|
const costUsd = estimateCallCostUsd({
|
|
2574
2933
|
modelId: modelVersion,
|
|
@@ -2618,8 +2977,7 @@ async function generateText(request) {
|
|
|
2618
2977
|
}
|
|
2619
2978
|
return await call.result;
|
|
2620
2979
|
}
|
|
2621
|
-
|
|
2622
|
-
const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
|
|
2980
|
+
function buildJsonSchemaConfig(request) {
|
|
2623
2981
|
const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
|
|
2624
2982
|
const providerInfo = resolveProvider(request.model);
|
|
2625
2983
|
const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
|
|
@@ -2631,31 +2989,143 @@ async function generateJson(request) {
|
|
|
2631
2989
|
if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
|
|
2632
2990
|
throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
|
|
2633
2991
|
}
|
|
2634
|
-
const openAiTextFormat =
|
|
2992
|
+
const openAiTextFormat = isOpenAiVariant ? {
|
|
2635
2993
|
type: "json_schema",
|
|
2636
2994
|
name: schemaName,
|
|
2637
2995
|
strict: true,
|
|
2638
2996
|
schema: normalizeOpenAiSchema(responseJsonSchema)
|
|
2639
2997
|
} : void 0;
|
|
2998
|
+
return { providerInfo, responseJsonSchema, openAiTextFormat };
|
|
2999
|
+
}
|
|
3000
|
+
function streamJson(request) {
|
|
3001
|
+
const queue = createAsyncQueue();
|
|
3002
|
+
const abortController = new AbortController();
|
|
3003
|
+
const resolveAbortSignal = () => {
|
|
3004
|
+
if (!request.signal) {
|
|
3005
|
+
return abortController.signal;
|
|
3006
|
+
}
|
|
3007
|
+
if (request.signal.aborted) {
|
|
3008
|
+
abortController.abort(request.signal.reason);
|
|
3009
|
+
} else {
|
|
3010
|
+
request.signal.addEventListener(
|
|
3011
|
+
"abort",
|
|
3012
|
+
() => abortController.abort(request.signal?.reason),
|
|
3013
|
+
{
|
|
3014
|
+
once: true
|
|
3015
|
+
}
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
return abortController.signal;
|
|
3019
|
+
};
|
|
3020
|
+
const result = (async () => {
|
|
3021
|
+
const signal = resolveAbortSignal();
|
|
3022
|
+
const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
|
|
3023
|
+
const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
|
|
3024
|
+
const streamMode = request.streamMode ?? "partial";
|
|
3025
|
+
const failures = [];
|
|
3026
|
+
let openAiTextFormatForAttempt = openAiTextFormat;
|
|
3027
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3028
|
+
let rawText = "";
|
|
3029
|
+
let lastPartial = "";
|
|
3030
|
+
try {
|
|
3031
|
+
const call = streamText({
|
|
3032
|
+
model: request.model,
|
|
3033
|
+
input: request.input,
|
|
3034
|
+
instructions: request.instructions,
|
|
3035
|
+
tools: request.tools,
|
|
3036
|
+
responseMimeType: request.responseMimeType ?? "application/json",
|
|
3037
|
+
responseJsonSchema,
|
|
3038
|
+
openAiReasoningEffort: request.openAiReasoningEffort,
|
|
3039
|
+
...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
|
|
3040
|
+
signal
|
|
3041
|
+
});
|
|
3042
|
+
try {
|
|
3043
|
+
for await (const event of call.events) {
|
|
3044
|
+
queue.push(event);
|
|
3045
|
+
if (event.type === "delta" && event.channel === "response") {
|
|
3046
|
+
rawText += event.text;
|
|
3047
|
+
if (streamMode === "partial") {
|
|
3048
|
+
const partial = parsePartialJsonFromLlmText(rawText);
|
|
3049
|
+
if (partial !== null) {
|
|
3050
|
+
const serialized = JSON.stringify(partial);
|
|
3051
|
+
if (serialized !== lastPartial) {
|
|
3052
|
+
lastPartial = serialized;
|
|
3053
|
+
queue.push({
|
|
3054
|
+
type: "json",
|
|
3055
|
+
stage: "partial",
|
|
3056
|
+
value: partial
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
} catch (streamError) {
|
|
3064
|
+
await call.result.catch(() => void 0);
|
|
3065
|
+
throw streamError;
|
|
3066
|
+
}
|
|
3067
|
+
const result2 = await call.result;
|
|
3068
|
+
rawText = rawText || result2.text;
|
|
3069
|
+
const cleanedText = normalizeJsonText(rawText);
|
|
3070
|
+
const repairedText = escapeNewlinesInStrings(cleanedText);
|
|
3071
|
+
const payload = JSON.parse(repairedText);
|
|
3072
|
+
const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
|
|
3073
|
+
const parsed = request.schema.parse(normalized);
|
|
3074
|
+
queue.push({ type: "json", stage: "final", value: parsed });
|
|
3075
|
+
queue.close();
|
|
3076
|
+
return { value: parsed, rawText, result: result2 };
|
|
3077
|
+
} catch (error) {
|
|
3078
|
+
const handled = error instanceof Error ? error : new Error(String(error));
|
|
3079
|
+
failures.push({ attempt, rawText, error: handled });
|
|
3080
|
+
if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
|
|
3081
|
+
openAiTextFormatForAttempt = void 0;
|
|
3082
|
+
}
|
|
3083
|
+
if (attempt >= maxAttempts) {
|
|
3084
|
+
throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
throw new LlmJsonCallError("LLM JSON call failed", failures);
|
|
3089
|
+
})().catch((error) => {
|
|
3090
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3091
|
+
queue.fail(err);
|
|
3092
|
+
throw err;
|
|
3093
|
+
});
|
|
3094
|
+
return {
|
|
3095
|
+
events: queue.iterable,
|
|
3096
|
+
result,
|
|
3097
|
+
abort: () => abortController.abort()
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
async function generateJson(request) {
|
|
3101
|
+
const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
|
|
3102
|
+
const { providerInfo, responseJsonSchema, openAiTextFormat } = buildJsonSchemaConfig(request);
|
|
3103
|
+
let openAiTextFormatForAttempt = openAiTextFormat;
|
|
2640
3104
|
const failures = [];
|
|
2641
3105
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2642
3106
|
let rawText = "";
|
|
2643
3107
|
try {
|
|
2644
|
-
const contents = resolveTextContents(request);
|
|
2645
3108
|
const call = streamText({
|
|
2646
3109
|
model: request.model,
|
|
2647
|
-
|
|
3110
|
+
input: request.input,
|
|
3111
|
+
instructions: request.instructions,
|
|
2648
3112
|
tools: request.tools,
|
|
2649
3113
|
responseMimeType: request.responseMimeType ?? "application/json",
|
|
2650
3114
|
responseJsonSchema,
|
|
2651
3115
|
openAiReasoningEffort: request.openAiReasoningEffort,
|
|
2652
|
-
...
|
|
3116
|
+
...openAiTextFormatForAttempt ? { openAiTextFormat: openAiTextFormatForAttempt } : {},
|
|
2653
3117
|
signal: request.signal
|
|
2654
3118
|
});
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3119
|
+
try {
|
|
3120
|
+
for await (const event of call.events) {
|
|
3121
|
+
request.onEvent?.(event);
|
|
3122
|
+
if (event.type === "delta" && event.channel === "response") {
|
|
3123
|
+
rawText += event.text;
|
|
3124
|
+
}
|
|
2658
3125
|
}
|
|
3126
|
+
} catch (streamError) {
|
|
3127
|
+
await call.result.catch(() => void 0);
|
|
3128
|
+
throw streamError;
|
|
2659
3129
|
}
|
|
2660
3130
|
const result = await call.result;
|
|
2661
3131
|
rawText = rawText || result.text;
|
|
@@ -2668,6 +3138,9 @@ async function generateJson(request) {
|
|
|
2668
3138
|
} catch (error) {
|
|
2669
3139
|
const handled = error instanceof Error ? error : new Error(String(error));
|
|
2670
3140
|
failures.push({ attempt, rawText, error: handled });
|
|
3141
|
+
if (providerInfo.provider === "chatgpt" && openAiTextFormatForAttempt) {
|
|
3142
|
+
openAiTextFormatForAttempt = void 0;
|
|
3143
|
+
}
|
|
2671
3144
|
if (attempt >= maxAttempts) {
|
|
2672
3145
|
throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
|
|
2673
3146
|
}
|
|
@@ -3253,7 +3726,7 @@ async function gradeGeneratedImage(params) {
|
|
|
3253
3726
|
];
|
|
3254
3727
|
const { value } = await generateJson({
|
|
3255
3728
|
model: params.model,
|
|
3256
|
-
|
|
3729
|
+
input: [{ role: "user", content: parts }],
|
|
3257
3730
|
schema: IMAGE_GRADE_SCHEMA
|
|
3258
3731
|
});
|
|
3259
3732
|
return value;
|
|
@@ -3330,7 +3803,7 @@ async function generateImages(request) {
|
|
|
3330
3803
|
lines.push(`\\nPlease make all ${pending.length} remaining images.`);
|
|
3331
3804
|
return [{ type: "text", text: lines.join("\\n") }];
|
|
3332
3805
|
};
|
|
3333
|
-
const
|
|
3806
|
+
const inputMessages = [{ role: "user", content: buildInitialPromptParts() }];
|
|
3334
3807
|
const orderedEntries = [...promptEntries];
|
|
3335
3808
|
const resolvedImages = /* @__PURE__ */ new Map();
|
|
3336
3809
|
const removeResolvedEntries = (resolved) => {
|
|
@@ -3350,7 +3823,7 @@ async function generateImages(request) {
|
|
|
3350
3823
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3351
3824
|
const result = await generateText({
|
|
3352
3825
|
model: request.model,
|
|
3353
|
-
|
|
3826
|
+
input: inputMessages,
|
|
3354
3827
|
responseModalities: ["IMAGE", "TEXT"],
|
|
3355
3828
|
imageAspectRatio: request.imageAspectRatio,
|
|
3356
3829
|
imageSize: request.imageSize ?? "2K"
|
|
@@ -3397,8 +3870,11 @@ async function generateImages(request) {
|
|
|
3397
3870
|
if (promptEntries.length === 0) {
|
|
3398
3871
|
break;
|
|
3399
3872
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3873
|
+
inputMessages.push({
|
|
3874
|
+
role: "assistant",
|
|
3875
|
+
content: result.content.parts
|
|
3876
|
+
});
|
|
3877
|
+
inputMessages.push({ role: "user", content: buildContinuationPromptParts(promptEntries) });
|
|
3402
3878
|
}
|
|
3403
3879
|
const orderedImages = [];
|
|
3404
3880
|
for (const entry of orderedEntries) {
|
|
@@ -3472,12 +3948,1668 @@ function appendMarkdownSourcesSection(value, sources) {
|
|
|
3472
3948
|
## Sources
|
|
3473
3949
|
${lines}`;
|
|
3474
3950
|
}
|
|
3951
|
+
|
|
3952
|
+
// src/tools/filesystemTools.ts
|
|
3953
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
3954
|
+
var import_zod5 = require("zod");
|
|
3955
|
+
|
|
3956
|
+
// src/tools/applyPatch.ts
|
|
3957
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
3958
|
+
var import_zod4 = require("zod");
|
|
3959
|
+
|
|
3960
|
+
// src/tools/filesystem.ts
|
|
3961
|
+
var import_node_fs2 = require("fs");
|
|
3962
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
3963
|
+
var InMemoryAgentFilesystem = class {
|
|
3964
|
+
#files = /* @__PURE__ */ new Map();
|
|
3965
|
+
#dirs = /* @__PURE__ */ new Map();
|
|
3966
|
+
#clock = 0;
|
|
3967
|
+
constructor(initialFiles = {}) {
|
|
3968
|
+
const root = import_node_path2.default.resolve("/");
|
|
3969
|
+
this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
|
|
3970
|
+
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
3971
|
+
const absolutePath = import_node_path2.default.resolve(filePath);
|
|
3972
|
+
this.#ensureDirSync(import_node_path2.default.dirname(absolutePath));
|
|
3973
|
+
this.#files.set(absolutePath, {
|
|
3974
|
+
content,
|
|
3975
|
+
mtimeMs: this.#nextMtime()
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
async readTextFile(filePath) {
|
|
3980
|
+
const absolutePath = import_node_path2.default.resolve(filePath);
|
|
3981
|
+
const file = this.#files.get(absolutePath);
|
|
3982
|
+
if (!file) {
|
|
3983
|
+
throw createNoSuchFileError("open", absolutePath);
|
|
3984
|
+
}
|
|
3985
|
+
return file.content;
|
|
3986
|
+
}
|
|
3987
|
+
async writeTextFile(filePath, content) {
|
|
3988
|
+
const absolutePath = import_node_path2.default.resolve(filePath);
|
|
3989
|
+
const parentPath = import_node_path2.default.dirname(absolutePath);
|
|
3990
|
+
if (!this.#dirs.has(parentPath)) {
|
|
3991
|
+
throw createNoSuchFileError("open", parentPath);
|
|
3992
|
+
}
|
|
3993
|
+
this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
|
|
3994
|
+
}
|
|
3995
|
+
async deleteFile(filePath) {
|
|
3996
|
+
const absolutePath = import_node_path2.default.resolve(filePath);
|
|
3997
|
+
if (!this.#files.delete(absolutePath)) {
|
|
3998
|
+
throw createNoSuchFileError("unlink", absolutePath);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
async ensureDir(directoryPath) {
|
|
4002
|
+
this.#ensureDirSync(import_node_path2.default.resolve(directoryPath));
|
|
4003
|
+
}
|
|
4004
|
+
async readDir(directoryPath) {
|
|
4005
|
+
const absolutePath = import_node_path2.default.resolve(directoryPath);
|
|
4006
|
+
const directory = this.#dirs.get(absolutePath);
|
|
4007
|
+
if (!directory) {
|
|
4008
|
+
throw createNoSuchFileError("scandir", absolutePath);
|
|
4009
|
+
}
|
|
4010
|
+
const entries = [];
|
|
4011
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
4012
|
+
for (const [dirPath, dirRecord] of this.#dirs.entries()) {
|
|
4013
|
+
if (dirPath === absolutePath) {
|
|
4014
|
+
continue;
|
|
4015
|
+
}
|
|
4016
|
+
if (import_node_path2.default.dirname(dirPath) !== absolutePath) {
|
|
4017
|
+
continue;
|
|
4018
|
+
}
|
|
4019
|
+
const name = import_node_path2.default.basename(dirPath);
|
|
4020
|
+
if (seenNames.has(name)) {
|
|
4021
|
+
continue;
|
|
4022
|
+
}
|
|
4023
|
+
seenNames.add(name);
|
|
4024
|
+
entries.push({
|
|
4025
|
+
name,
|
|
4026
|
+
path: dirPath,
|
|
4027
|
+
kind: "directory",
|
|
4028
|
+
mtimeMs: dirRecord.mtimeMs
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
for (const [filePath, fileRecord] of this.#files.entries()) {
|
|
4032
|
+
if (import_node_path2.default.dirname(filePath) !== absolutePath) {
|
|
4033
|
+
continue;
|
|
4034
|
+
}
|
|
4035
|
+
const name = import_node_path2.default.basename(filePath);
|
|
4036
|
+
if (seenNames.has(name)) {
|
|
4037
|
+
continue;
|
|
4038
|
+
}
|
|
4039
|
+
seenNames.add(name);
|
|
4040
|
+
entries.push({
|
|
4041
|
+
name,
|
|
4042
|
+
path: filePath,
|
|
4043
|
+
kind: "file",
|
|
4044
|
+
mtimeMs: fileRecord.mtimeMs
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
4047
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
4048
|
+
return entries;
|
|
4049
|
+
}
|
|
4050
|
+
async stat(entryPath) {
|
|
4051
|
+
const absolutePath = import_node_path2.default.resolve(entryPath);
|
|
4052
|
+
const file = this.#files.get(absolutePath);
|
|
4053
|
+
if (file) {
|
|
4054
|
+
return { kind: "file", mtimeMs: file.mtimeMs };
|
|
4055
|
+
}
|
|
4056
|
+
const directory = this.#dirs.get(absolutePath);
|
|
4057
|
+
if (directory) {
|
|
4058
|
+
return { kind: "directory", mtimeMs: directory.mtimeMs };
|
|
4059
|
+
}
|
|
4060
|
+
throw createNoSuchFileError("stat", absolutePath);
|
|
4061
|
+
}
|
|
4062
|
+
snapshot() {
|
|
4063
|
+
const entries = [...this.#files.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
4064
|
+
return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
|
|
4065
|
+
}
|
|
4066
|
+
#ensureDirSync(directoryPath) {
|
|
4067
|
+
const absolutePath = import_node_path2.default.resolve(directoryPath);
|
|
4068
|
+
const parts = [];
|
|
4069
|
+
let cursor = absolutePath;
|
|
4070
|
+
for (; ; ) {
|
|
4071
|
+
if (this.#dirs.has(cursor)) {
|
|
4072
|
+
break;
|
|
4073
|
+
}
|
|
4074
|
+
parts.push(cursor);
|
|
4075
|
+
const parent = import_node_path2.default.dirname(cursor);
|
|
4076
|
+
if (parent === cursor) {
|
|
4077
|
+
break;
|
|
4078
|
+
}
|
|
4079
|
+
cursor = parent;
|
|
4080
|
+
}
|
|
4081
|
+
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
4082
|
+
const nextDir = parts[index];
|
|
4083
|
+
if (nextDir === void 0) {
|
|
4084
|
+
continue;
|
|
4085
|
+
}
|
|
4086
|
+
if (!this.#dirs.has(nextDir)) {
|
|
4087
|
+
this.#dirs.set(nextDir, { mtimeMs: this.#nextMtime() });
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
#nextMtime() {
|
|
4092
|
+
this.#clock += 1;
|
|
4093
|
+
return this.#clock;
|
|
4094
|
+
}
|
|
4095
|
+
};
|
|
4096
|
+
function createNodeAgentFilesystem() {
|
|
4097
|
+
return {
|
|
4098
|
+
readTextFile: async (filePath) => import_node_fs2.promises.readFile(filePath, "utf8"),
|
|
4099
|
+
writeTextFile: async (filePath, content) => import_node_fs2.promises.writeFile(filePath, content, "utf8"),
|
|
4100
|
+
deleteFile: async (filePath) => import_node_fs2.promises.unlink(filePath),
|
|
4101
|
+
ensureDir: async (directoryPath) => {
|
|
4102
|
+
await import_node_fs2.promises.mkdir(directoryPath, { recursive: true });
|
|
4103
|
+
},
|
|
4104
|
+
readDir: async (directoryPath) => {
|
|
4105
|
+
const entries = await import_node_fs2.promises.readdir(directoryPath, { withFileTypes: true });
|
|
4106
|
+
const result = [];
|
|
4107
|
+
for (const entry of entries) {
|
|
4108
|
+
const entryPath = import_node_path2.default.resolve(directoryPath, entry.name);
|
|
4109
|
+
const stats = await import_node_fs2.promises.lstat(entryPath);
|
|
4110
|
+
result.push({
|
|
4111
|
+
name: entry.name,
|
|
4112
|
+
path: entryPath,
|
|
4113
|
+
kind: statsToKind(stats),
|
|
4114
|
+
mtimeMs: stats.mtimeMs
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
return result;
|
|
4118
|
+
},
|
|
4119
|
+
stat: async (entryPath) => {
|
|
4120
|
+
const stats = await import_node_fs2.promises.lstat(entryPath);
|
|
4121
|
+
return {
|
|
4122
|
+
kind: statsToKind(stats),
|
|
4123
|
+
mtimeMs: stats.mtimeMs
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
};
|
|
4127
|
+
}
|
|
4128
|
+
function createInMemoryAgentFilesystem(initialFiles = {}) {
|
|
4129
|
+
return new InMemoryAgentFilesystem(initialFiles);
|
|
4130
|
+
}
|
|
4131
|
+
function statsToKind(stats) {
|
|
4132
|
+
if (stats.isSymbolicLink()) {
|
|
4133
|
+
return "symlink";
|
|
4134
|
+
}
|
|
4135
|
+
if (stats.isDirectory()) {
|
|
4136
|
+
return "directory";
|
|
4137
|
+
}
|
|
4138
|
+
if (stats.isFile()) {
|
|
4139
|
+
return "file";
|
|
4140
|
+
}
|
|
4141
|
+
return "other";
|
|
4142
|
+
}
|
|
4143
|
+
function createNoSuchFileError(syscall, filePath) {
|
|
4144
|
+
const error = new Error(
|
|
4145
|
+
`ENOENT: no such file or directory, ${syscall} '${filePath}'`
|
|
4146
|
+
);
|
|
4147
|
+
error.code = "ENOENT";
|
|
4148
|
+
error.syscall = syscall;
|
|
4149
|
+
error.path = filePath;
|
|
4150
|
+
return error;
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
// src/tools/applyPatch.ts
|
|
4154
|
+
var BEGIN_PATCH_LINE = "*** Begin Patch";
|
|
4155
|
+
var END_PATCH_LINE = "*** End Patch";
|
|
4156
|
+
var ADD_FILE_PREFIX = "*** Add File: ";
|
|
4157
|
+
var DELETE_FILE_PREFIX = "*** Delete File: ";
|
|
4158
|
+
var UPDATE_FILE_PREFIX = "*** Update File: ";
|
|
4159
|
+
var MOVE_TO_PREFIX = "*** Move to: ";
|
|
4160
|
+
var END_OF_FILE_LINE = "*** End of File";
|
|
4161
|
+
var DEFAULT_MAX_PATCH_BYTES = 1024 * 1024;
|
|
4162
|
+
var applyPatchToolInputSchema = import_zod4.z.object({
|
|
4163
|
+
input: import_zod4.z.string().min(1).describe("The entire apply_patch payload, including Begin/End markers.")
|
|
4164
|
+
});
|
|
4165
|
+
function createApplyPatchTool(options = {}) {
|
|
4166
|
+
return tool({
|
|
4167
|
+
description: options.description ?? "Apply edits using a Codex-style apply_patch payload with Begin/End markers.",
|
|
4168
|
+
inputSchema: applyPatchToolInputSchema,
|
|
4169
|
+
execute: async ({ input }) => applyPatch({
|
|
4170
|
+
patch: input,
|
|
4171
|
+
cwd: options.cwd,
|
|
4172
|
+
fs: options.fs,
|
|
4173
|
+
allowOutsideCwd: options.allowOutsideCwd,
|
|
4174
|
+
checkAccess: options.checkAccess,
|
|
4175
|
+
maxPatchBytes: options.maxPatchBytes
|
|
4176
|
+
})
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
async function applyPatch(request) {
|
|
4180
|
+
const cwd = import_node_path3.default.resolve(request.cwd ?? process.cwd());
|
|
4181
|
+
const adapter = request.fs ?? createNodeAgentFilesystem();
|
|
4182
|
+
const allowOutsideCwd = request.allowOutsideCwd === true;
|
|
4183
|
+
const patchBytes = Buffer.byteLength(request.patch, "utf8");
|
|
4184
|
+
const maxPatchBytes = request.maxPatchBytes ?? DEFAULT_MAX_PATCH_BYTES;
|
|
4185
|
+
if (patchBytes > maxPatchBytes) {
|
|
4186
|
+
throw new Error(
|
|
4187
|
+
`apply_patch failed: patch too large (${patchBytes} bytes > ${maxPatchBytes} bytes)`
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
const parsed = parsePatchDocument(normalizePatchText(request.patch));
|
|
4191
|
+
const added = [];
|
|
4192
|
+
const modified = [];
|
|
4193
|
+
const deleted = [];
|
|
4194
|
+
for (const operation of parsed.operations) {
|
|
4195
|
+
if (operation.type === "add") {
|
|
4196
|
+
const absolutePath2 = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
|
|
4197
|
+
await runAccessHook(request.checkAccess, {
|
|
4198
|
+
cwd,
|
|
4199
|
+
kind: "add",
|
|
4200
|
+
path: absolutePath2
|
|
4201
|
+
});
|
|
4202
|
+
await adapter.ensureDir(import_node_path3.default.dirname(absolutePath2));
|
|
4203
|
+
await adapter.writeTextFile(absolutePath2, operation.content);
|
|
4204
|
+
added.push(toDisplayPath(absolutePath2, cwd));
|
|
4205
|
+
continue;
|
|
4206
|
+
}
|
|
4207
|
+
if (operation.type === "delete") {
|
|
4208
|
+
const absolutePath2 = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
|
|
4209
|
+
await runAccessHook(request.checkAccess, {
|
|
4210
|
+
cwd,
|
|
4211
|
+
kind: "delete",
|
|
4212
|
+
path: absolutePath2
|
|
4213
|
+
});
|
|
4214
|
+
await adapter.readTextFile(absolutePath2);
|
|
4215
|
+
await adapter.deleteFile(absolutePath2);
|
|
4216
|
+
deleted.push(toDisplayPath(absolutePath2, cwd));
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
const absolutePath = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
|
|
4220
|
+
await runAccessHook(request.checkAccess, {
|
|
4221
|
+
cwd,
|
|
4222
|
+
kind: "update",
|
|
4223
|
+
path: absolutePath
|
|
4224
|
+
});
|
|
4225
|
+
const current = await adapter.readTextFile(absolutePath);
|
|
4226
|
+
const next = deriveUpdatedContent(current, operation.chunks, toDisplayPath(absolutePath, cwd));
|
|
4227
|
+
if (operation.movePath) {
|
|
4228
|
+
const destinationPath = resolvePatchPath(operation.movePath, cwd, allowOutsideCwd);
|
|
4229
|
+
await runAccessHook(request.checkAccess, {
|
|
4230
|
+
cwd,
|
|
4231
|
+
kind: "move",
|
|
4232
|
+
path: destinationPath,
|
|
4233
|
+
fromPath: absolutePath,
|
|
4234
|
+
toPath: destinationPath
|
|
4235
|
+
});
|
|
4236
|
+
await adapter.ensureDir(import_node_path3.default.dirname(destinationPath));
|
|
4237
|
+
await adapter.writeTextFile(destinationPath, next);
|
|
4238
|
+
await adapter.deleteFile(absolutePath);
|
|
4239
|
+
modified.push(toDisplayPath(destinationPath, cwd));
|
|
4240
|
+
continue;
|
|
4241
|
+
}
|
|
4242
|
+
await adapter.writeTextFile(absolutePath, next);
|
|
4243
|
+
modified.push(toDisplayPath(absolutePath, cwd));
|
|
4244
|
+
}
|
|
4245
|
+
return {
|
|
4246
|
+
success: true,
|
|
4247
|
+
summary: formatSummary(added, modified, deleted),
|
|
4248
|
+
added,
|
|
4249
|
+
modified,
|
|
4250
|
+
deleted
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
async function runAccessHook(hook, context) {
|
|
4254
|
+
if (!hook) {
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
await hook(context);
|
|
4258
|
+
}
|
|
4259
|
+
function normalizePatchText(raw) {
|
|
4260
|
+
return raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4261
|
+
}
|
|
4262
|
+
function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
|
|
4263
|
+
const trimmed = rawPath.trim();
|
|
4264
|
+
if (trimmed.length === 0) {
|
|
4265
|
+
throw new Error("apply_patch failed: empty file path");
|
|
4266
|
+
}
|
|
4267
|
+
const absolutePath = import_node_path3.default.isAbsolute(trimmed) ? import_node_path3.default.resolve(trimmed) : import_node_path3.default.resolve(cwd, trimmed);
|
|
4268
|
+
if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
|
|
4269
|
+
throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
|
|
4270
|
+
}
|
|
4271
|
+
return absolutePath;
|
|
4272
|
+
}
|
|
4273
|
+
function isPathInsideCwd(candidatePath, cwd) {
|
|
4274
|
+
const relative = import_node_path3.default.relative(cwd, candidatePath);
|
|
4275
|
+
return relative === "" || !relative.startsWith("..") && !import_node_path3.default.isAbsolute(relative);
|
|
4276
|
+
}
|
|
4277
|
+
function toDisplayPath(absolutePath, cwd) {
|
|
4278
|
+
const relative = import_node_path3.default.relative(cwd, absolutePath);
|
|
4279
|
+
if (relative === "") {
|
|
4280
|
+
return ".";
|
|
4281
|
+
}
|
|
4282
|
+
if (!relative.startsWith("..") && !import_node_path3.default.isAbsolute(relative)) {
|
|
4283
|
+
return relative;
|
|
4284
|
+
}
|
|
4285
|
+
return absolutePath;
|
|
4286
|
+
}
|
|
4287
|
+
function parsePatchDocument(patch) {
|
|
4288
|
+
const lines = patch.split("\n");
|
|
4289
|
+
if (lines.at(-1) === "") {
|
|
4290
|
+
lines.pop();
|
|
4291
|
+
}
|
|
4292
|
+
if (lines.length < 2) {
|
|
4293
|
+
throw new Error("apply_patch failed: patch must contain Begin/End markers");
|
|
4294
|
+
}
|
|
4295
|
+
if (lines[0] !== BEGIN_PATCH_LINE) {
|
|
4296
|
+
throw new Error(`apply_patch failed: missing "${BEGIN_PATCH_LINE}" header`);
|
|
4297
|
+
}
|
|
4298
|
+
if (lines[lines.length - 1] !== END_PATCH_LINE) {
|
|
4299
|
+
throw new Error(`apply_patch failed: missing "${END_PATCH_LINE}" footer`);
|
|
4300
|
+
}
|
|
4301
|
+
const body = lines.slice(1, -1);
|
|
4302
|
+
if (body.length === 0) {
|
|
4303
|
+
throw new Error("apply_patch failed: patch body is empty");
|
|
4304
|
+
}
|
|
4305
|
+
const operations = [];
|
|
4306
|
+
let index = 0;
|
|
4307
|
+
while (index < body.length) {
|
|
4308
|
+
const line = body[index];
|
|
4309
|
+
if (!line) {
|
|
4310
|
+
throw new Error("apply_patch failed: unexpected empty line between file sections");
|
|
4311
|
+
}
|
|
4312
|
+
if (line.startsWith(ADD_FILE_PREFIX)) {
|
|
4313
|
+
const filePath = extractPatchPath(line, ADD_FILE_PREFIX);
|
|
4314
|
+
index += 1;
|
|
4315
|
+
const contentLines = [];
|
|
4316
|
+
while (index < body.length) {
|
|
4317
|
+
const contentLine = body[index];
|
|
4318
|
+
if (contentLine === void 0 || isPatchSectionHeader(contentLine)) {
|
|
4319
|
+
break;
|
|
4320
|
+
}
|
|
4321
|
+
if (!contentLine.startsWith("+")) {
|
|
4322
|
+
throw new Error(`apply_patch failed: invalid add-file line "${contentLine}"`);
|
|
4323
|
+
}
|
|
4324
|
+
contentLines.push(contentLine.slice(1));
|
|
4325
|
+
index += 1;
|
|
4326
|
+
}
|
|
4327
|
+
if (contentLines.length === 0) {
|
|
4328
|
+
throw new Error(`apply_patch failed: add-file section for "${filePath}" is empty`);
|
|
4329
|
+
}
|
|
4330
|
+
operations.push({
|
|
4331
|
+
type: "add",
|
|
4332
|
+
path: filePath,
|
|
4333
|
+
content: `${contentLines.join("\n")}
|
|
4334
|
+
`
|
|
4335
|
+
});
|
|
4336
|
+
continue;
|
|
4337
|
+
}
|
|
4338
|
+
if (line.startsWith(DELETE_FILE_PREFIX)) {
|
|
4339
|
+
operations.push({
|
|
4340
|
+
type: "delete",
|
|
4341
|
+
path: extractPatchPath(line, DELETE_FILE_PREFIX)
|
|
4342
|
+
});
|
|
4343
|
+
index += 1;
|
|
4344
|
+
continue;
|
|
4345
|
+
}
|
|
4346
|
+
if (line.startsWith(UPDATE_FILE_PREFIX)) {
|
|
4347
|
+
const filePath = extractPatchPath(line, UPDATE_FILE_PREFIX);
|
|
4348
|
+
index += 1;
|
|
4349
|
+
let movePath;
|
|
4350
|
+
const moveHeader = body[index];
|
|
4351
|
+
if (moveHeader?.startsWith(MOVE_TO_PREFIX)) {
|
|
4352
|
+
movePath = extractPatchPath(moveHeader, MOVE_TO_PREFIX);
|
|
4353
|
+
index += 1;
|
|
4354
|
+
}
|
|
4355
|
+
const chunks = [];
|
|
4356
|
+
while (index < body.length) {
|
|
4357
|
+
const hunkHeader = body[index];
|
|
4358
|
+
if (hunkHeader === void 0 || isPatchSectionHeader(hunkHeader)) {
|
|
4359
|
+
break;
|
|
4360
|
+
}
|
|
4361
|
+
if (!(hunkHeader === "@@" || hunkHeader.startsWith("@@ "))) {
|
|
4362
|
+
throw new Error(
|
|
4363
|
+
`apply_patch failed: expected hunk marker in "${filePath}", got "${hunkHeader}"`
|
|
4364
|
+
);
|
|
4365
|
+
}
|
|
4366
|
+
const contextSelector = hunkHeader.length > 2 ? hunkHeader.slice(3) : void 0;
|
|
4367
|
+
index += 1;
|
|
4368
|
+
const oldLines = [];
|
|
4369
|
+
const newLines = [];
|
|
4370
|
+
let sawBodyLine = false;
|
|
4371
|
+
let sawChangeLine = false;
|
|
4372
|
+
let isEndOfFile = false;
|
|
4373
|
+
while (index < body.length) {
|
|
4374
|
+
const chunkLine = body[index];
|
|
4375
|
+
if (chunkLine === void 0) {
|
|
4376
|
+
break;
|
|
4377
|
+
}
|
|
4378
|
+
if (chunkLine === "@@" || chunkLine.startsWith("@@ ") || isPatchSectionHeader(chunkLine)) {
|
|
4379
|
+
break;
|
|
4380
|
+
}
|
|
4381
|
+
if (chunkLine === END_OF_FILE_LINE) {
|
|
4382
|
+
isEndOfFile = true;
|
|
4383
|
+
index += 1;
|
|
4384
|
+
break;
|
|
4385
|
+
}
|
|
4386
|
+
if (chunkLine.length === 0) {
|
|
4387
|
+
throw new Error(`apply_patch failed: invalid empty hunk line in "${filePath}"`);
|
|
4388
|
+
}
|
|
4389
|
+
const prefix = chunkLine[0];
|
|
4390
|
+
const content = chunkLine.slice(1);
|
|
4391
|
+
if (prefix === " ") {
|
|
4392
|
+
oldLines.push(content);
|
|
4393
|
+
newLines.push(content);
|
|
4394
|
+
} else if (prefix === "-") {
|
|
4395
|
+
oldLines.push(content);
|
|
4396
|
+
sawChangeLine = true;
|
|
4397
|
+
} else if (prefix === "+") {
|
|
4398
|
+
newLines.push(content);
|
|
4399
|
+
sawChangeLine = true;
|
|
4400
|
+
} else {
|
|
4401
|
+
throw new Error(
|
|
4402
|
+
`apply_patch failed: unsupported hunk prefix "${prefix}" in "${chunkLine}"`
|
|
4403
|
+
);
|
|
4404
|
+
}
|
|
4405
|
+
sawBodyLine = true;
|
|
4406
|
+
index += 1;
|
|
4407
|
+
}
|
|
4408
|
+
if (!sawBodyLine) {
|
|
4409
|
+
throw new Error(`apply_patch failed: empty hunk body in "${filePath}"`);
|
|
4410
|
+
}
|
|
4411
|
+
if (!sawChangeLine) {
|
|
4412
|
+
throw new Error(
|
|
4413
|
+
`apply_patch failed: hunk in "${filePath}" must include '+' or '-' lines`
|
|
4414
|
+
);
|
|
4415
|
+
}
|
|
4416
|
+
chunks.push({
|
|
4417
|
+
contextSelector,
|
|
4418
|
+
oldLines,
|
|
4419
|
+
newLines,
|
|
4420
|
+
isEndOfFile
|
|
4421
|
+
});
|
|
4422
|
+
}
|
|
4423
|
+
if (chunks.length === 0) {
|
|
4424
|
+
throw new Error(`apply_patch failed: update section for "${filePath}" has no hunks`);
|
|
4425
|
+
}
|
|
4426
|
+
operations.push({
|
|
4427
|
+
type: "update",
|
|
4428
|
+
path: filePath,
|
|
4429
|
+
movePath,
|
|
4430
|
+
chunks
|
|
4431
|
+
});
|
|
4432
|
+
continue;
|
|
4433
|
+
}
|
|
4434
|
+
throw new Error(`apply_patch failed: unrecognized section header "${line}"`);
|
|
4435
|
+
}
|
|
4436
|
+
return { operations };
|
|
4437
|
+
}
|
|
4438
|
+
function extractPatchPath(line, prefix) {
|
|
4439
|
+
const value = line.slice(prefix.length).trim();
|
|
4440
|
+
if (value.length === 0) {
|
|
4441
|
+
throw new Error(`apply_patch failed: missing file path in "${line}"`);
|
|
4442
|
+
}
|
|
4443
|
+
return value;
|
|
4444
|
+
}
|
|
4445
|
+
function isPatchSectionHeader(line) {
|
|
4446
|
+
return line.startsWith(ADD_FILE_PREFIX) || line.startsWith(DELETE_FILE_PREFIX) || line.startsWith(UPDATE_FILE_PREFIX);
|
|
4447
|
+
}
|
|
4448
|
+
function deriveUpdatedContent(originalContent, chunks, displayPath) {
|
|
4449
|
+
const originalLines = splitFileContentIntoLines(originalContent);
|
|
4450
|
+
const replacements = [];
|
|
4451
|
+
let lineIndex = 0;
|
|
4452
|
+
for (const chunk of chunks) {
|
|
4453
|
+
if (chunk.contextSelector !== void 0) {
|
|
4454
|
+
const contextIndex = seekSequence(originalLines, [chunk.contextSelector], lineIndex, false);
|
|
4455
|
+
if (contextIndex === null) {
|
|
4456
|
+
throw new Error(
|
|
4457
|
+
`apply_patch failed: unable to locate context "${chunk.contextSelector}" in ${displayPath}`
|
|
4458
|
+
);
|
|
4459
|
+
}
|
|
4460
|
+
lineIndex = contextIndex + 1;
|
|
4461
|
+
}
|
|
4462
|
+
if (chunk.oldLines.length === 0) {
|
|
4463
|
+
replacements.push({
|
|
4464
|
+
startIndex: originalLines.length,
|
|
4465
|
+
oldLength: 0,
|
|
4466
|
+
newLines: [...chunk.newLines]
|
|
4467
|
+
});
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
let oldLines = [...chunk.oldLines];
|
|
4471
|
+
let newLines = [...chunk.newLines];
|
|
4472
|
+
let startIndex = seekSequence(originalLines, oldLines, lineIndex, chunk.isEndOfFile);
|
|
4473
|
+
if (startIndex === null && oldLines.at(-1) === "") {
|
|
4474
|
+
oldLines = oldLines.slice(0, -1);
|
|
4475
|
+
if (newLines.at(-1) === "") {
|
|
4476
|
+
newLines = newLines.slice(0, -1);
|
|
4477
|
+
}
|
|
4478
|
+
startIndex = seekSequence(originalLines, oldLines, lineIndex, chunk.isEndOfFile);
|
|
4479
|
+
}
|
|
4480
|
+
if (startIndex === null) {
|
|
4481
|
+
throw new Error(
|
|
4482
|
+
`apply_patch failed: failed to match hunk in ${displayPath}:
|
|
4483
|
+
${chunk.oldLines.join("\n")}`
|
|
4484
|
+
);
|
|
4485
|
+
}
|
|
4486
|
+
replacements.push({
|
|
4487
|
+
startIndex,
|
|
4488
|
+
oldLength: oldLines.length,
|
|
4489
|
+
newLines
|
|
4490
|
+
});
|
|
4491
|
+
lineIndex = startIndex + oldLines.length;
|
|
4492
|
+
}
|
|
4493
|
+
replacements.sort((left, right) => left.startIndex - right.startIndex);
|
|
4494
|
+
const nextLines = applyReplacements(originalLines, replacements);
|
|
4495
|
+
if (nextLines.length > 0 && nextLines[nextLines.length - 1] !== "") {
|
|
4496
|
+
nextLines.push("");
|
|
4497
|
+
}
|
|
4498
|
+
return nextLines.join("\n");
|
|
4499
|
+
}
|
|
4500
|
+
function splitFileContentIntoLines(content) {
|
|
4501
|
+
const lines = content.split("\n");
|
|
4502
|
+
if (lines.at(-1) === "") {
|
|
4503
|
+
lines.pop();
|
|
4504
|
+
}
|
|
4505
|
+
return lines;
|
|
4506
|
+
}
|
|
4507
|
+
function seekSequence(sourceLines, targetLines, startIndex, isEndOfFile) {
|
|
4508
|
+
if (targetLines.length === 0) {
|
|
4509
|
+
return Math.min(Math.max(startIndex, 0), sourceLines.length);
|
|
4510
|
+
}
|
|
4511
|
+
const from = Math.max(startIndex, 0);
|
|
4512
|
+
const maxStart = sourceLines.length - targetLines.length;
|
|
4513
|
+
if (maxStart < from) {
|
|
4514
|
+
return null;
|
|
4515
|
+
}
|
|
4516
|
+
const matchesAt = (candidateIndex) => {
|
|
4517
|
+
for (let offset = 0; offset < targetLines.length; offset += 1) {
|
|
4518
|
+
if (sourceLines[candidateIndex + offset] !== targetLines[offset]) {
|
|
4519
|
+
return false;
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
return true;
|
|
4523
|
+
};
|
|
4524
|
+
if (isEndOfFile) {
|
|
4525
|
+
return matchesAt(maxStart) ? maxStart : null;
|
|
4526
|
+
}
|
|
4527
|
+
for (let candidate = from; candidate <= maxStart; candidate += 1) {
|
|
4528
|
+
if (matchesAt(candidate)) {
|
|
4529
|
+
return candidate;
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
return null;
|
|
4533
|
+
}
|
|
4534
|
+
function applyReplacements(lines, replacements) {
|
|
4535
|
+
const result = [...lines];
|
|
4536
|
+
for (let index = replacements.length - 1; index >= 0; index -= 1) {
|
|
4537
|
+
const replacement = replacements[index];
|
|
4538
|
+
if (replacement === void 0) {
|
|
4539
|
+
continue;
|
|
4540
|
+
}
|
|
4541
|
+
result.splice(replacement.startIndex, replacement.oldLength, ...replacement.newLines);
|
|
4542
|
+
}
|
|
4543
|
+
return result;
|
|
4544
|
+
}
|
|
4545
|
+
function formatSummary(added, modified, deleted) {
|
|
4546
|
+
const lines = ["Success. Updated the following files:"];
|
|
4547
|
+
for (const filePath of added) {
|
|
4548
|
+
lines.push(`A ${filePath}`);
|
|
4549
|
+
}
|
|
4550
|
+
for (const filePath of modified) {
|
|
4551
|
+
lines.push(`M ${filePath}`);
|
|
4552
|
+
}
|
|
4553
|
+
for (const filePath of deleted) {
|
|
4554
|
+
lines.push(`D ${filePath}`);
|
|
4555
|
+
}
|
|
4556
|
+
return `${lines.join("\n")}
|
|
4557
|
+
`;
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4560
|
+
// src/tools/filesystemTools.ts
|
|
4561
|
+
var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
|
|
4562
|
+
var DEFAULT_LIST_DIR_LIMIT = 25;
|
|
4563
|
+
var DEFAULT_LIST_DIR_DEPTH = 2;
|
|
4564
|
+
var DEFAULT_GREP_LIMIT = 100;
|
|
4565
|
+
var MAX_GREP_LIMIT = 2e3;
|
|
4566
|
+
var DEFAULT_MAX_LINE_LENGTH = 500;
|
|
4567
|
+
var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
|
|
4568
|
+
var DEFAULT_TAB_WIDTH = 4;
|
|
4569
|
+
var codexReadFileInputSchema = import_zod5.z.object({
|
|
4570
|
+
file_path: import_zod5.z.string().min(1).describe("Absolute path to the file"),
|
|
4571
|
+
offset: import_zod5.z.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
|
|
4572
|
+
limit: import_zod5.z.number().int().min(1).optional().describe("The maximum number of lines to return."),
|
|
4573
|
+
mode: import_zod5.z.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
|
|
4574
|
+
indentation: import_zod5.z.object({
|
|
4575
|
+
anchor_line: import_zod5.z.number().int().min(1).optional(),
|
|
4576
|
+
max_levels: import_zod5.z.number().int().min(0).optional(),
|
|
4577
|
+
include_siblings: import_zod5.z.boolean().optional(),
|
|
4578
|
+
include_header: import_zod5.z.boolean().optional(),
|
|
4579
|
+
max_lines: import_zod5.z.number().int().min(1).optional()
|
|
4580
|
+
}).optional()
|
|
4581
|
+
});
|
|
4582
|
+
var codexListDirInputSchema = import_zod5.z.object({
|
|
4583
|
+
dir_path: import_zod5.z.string().min(1).describe("Absolute path to the directory to list."),
|
|
4584
|
+
offset: import_zod5.z.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
|
|
4585
|
+
limit: import_zod5.z.number().int().min(1).optional().describe("The maximum number of entries to return."),
|
|
4586
|
+
depth: import_zod5.z.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
|
|
4587
|
+
});
|
|
4588
|
+
var codexGrepFilesInputSchema = import_zod5.z.object({
|
|
4589
|
+
pattern: import_zod5.z.string().min(1).describe("Regular expression pattern to search for."),
|
|
4590
|
+
include: import_zod5.z.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
|
|
4591
|
+
path: import_zod5.z.string().optional().describe("Directory or file path to search. Defaults to cwd."),
|
|
4592
|
+
limit: import_zod5.z.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
|
|
4593
|
+
});
|
|
4594
|
+
var applyPatchInputSchema = import_zod5.z.object({
|
|
4595
|
+
input: import_zod5.z.string().min(1)
|
|
4596
|
+
});
|
|
4597
|
+
var geminiReadFileInputSchema = import_zod5.z.object({
|
|
4598
|
+
file_path: import_zod5.z.string().min(1),
|
|
4599
|
+
offset: import_zod5.z.number().int().min(0).optional(),
|
|
4600
|
+
limit: import_zod5.z.number().int().min(1).optional()
|
|
4601
|
+
});
|
|
4602
|
+
var geminiWriteFileInputSchema = import_zod5.z.object({
|
|
4603
|
+
file_path: import_zod5.z.string().min(1),
|
|
4604
|
+
content: import_zod5.z.string()
|
|
4605
|
+
});
|
|
4606
|
+
var geminiReplaceInputSchema = import_zod5.z.object({
|
|
4607
|
+
file_path: import_zod5.z.string().min(1),
|
|
4608
|
+
instruction: import_zod5.z.string().min(1),
|
|
4609
|
+
old_string: import_zod5.z.string(),
|
|
4610
|
+
new_string: import_zod5.z.string(),
|
|
4611
|
+
expected_replacements: import_zod5.z.number().int().min(1).optional()
|
|
4612
|
+
});
|
|
4613
|
+
var geminiListDirectoryInputSchema = import_zod5.z.object({
|
|
4614
|
+
dir_path: import_zod5.z.string().min(1),
|
|
4615
|
+
ignore: import_zod5.z.array(import_zod5.z.string()).optional(),
|
|
4616
|
+
file_filtering_options: import_zod5.z.object({
|
|
4617
|
+
respect_git_ignore: import_zod5.z.boolean().optional(),
|
|
4618
|
+
respect_gemini_ignore: import_zod5.z.boolean().optional()
|
|
4619
|
+
}).optional()
|
|
4620
|
+
});
|
|
4621
|
+
var geminiGrepSearchInputSchema = import_zod5.z.object({
|
|
4622
|
+
pattern: import_zod5.z.string().min(1),
|
|
4623
|
+
dir_path: import_zod5.z.string().optional(),
|
|
4624
|
+
include: import_zod5.z.string().optional(),
|
|
4625
|
+
exclude_pattern: import_zod5.z.string().optional(),
|
|
4626
|
+
names_only: import_zod5.z.boolean().optional(),
|
|
4627
|
+
max_matches_per_file: import_zod5.z.number().int().min(1).optional(),
|
|
4628
|
+
total_max_matches: import_zod5.z.number().int().min(1).optional()
|
|
4629
|
+
});
|
|
4630
|
+
var geminiGlobInputSchema = import_zod5.z.object({
|
|
4631
|
+
pattern: import_zod5.z.string().min(1),
|
|
4632
|
+
dir_path: import_zod5.z.string().optional(),
|
|
4633
|
+
case_sensitive: import_zod5.z.boolean().optional(),
|
|
4634
|
+
respect_git_ignore: import_zod5.z.boolean().optional(),
|
|
4635
|
+
respect_gemini_ignore: import_zod5.z.boolean().optional()
|
|
4636
|
+
});
|
|
4637
|
+
function resolveFilesystemToolProfile(model, profile = "auto") {
|
|
4638
|
+
if (profile !== "auto") {
|
|
4639
|
+
return profile;
|
|
4640
|
+
}
|
|
4641
|
+
if (isCodexModel(model)) {
|
|
4642
|
+
return "codex";
|
|
4643
|
+
}
|
|
4644
|
+
if (isGeminiModel(model)) {
|
|
4645
|
+
return "gemini";
|
|
4646
|
+
}
|
|
4647
|
+
return "model-agnostic";
|
|
4648
|
+
}
|
|
4649
|
+
function createFilesystemToolSetForModel(model, profileOrOptions = "auto", maybeOptions) {
|
|
4650
|
+
if (typeof profileOrOptions === "string") {
|
|
4651
|
+
const resolvedProfile2 = resolveFilesystemToolProfile(model, profileOrOptions);
|
|
4652
|
+
if (resolvedProfile2 === "codex") {
|
|
4653
|
+
return createCodexFilesystemToolSet(maybeOptions);
|
|
4654
|
+
}
|
|
4655
|
+
if (resolvedProfile2 === "gemini") {
|
|
4656
|
+
return createGeminiFilesystemToolSet(maybeOptions);
|
|
4657
|
+
}
|
|
4658
|
+
return createModelAgnosticFilesystemToolSet(maybeOptions);
|
|
4659
|
+
}
|
|
4660
|
+
const resolvedProfile = resolveFilesystemToolProfile(model, "auto");
|
|
4661
|
+
if (resolvedProfile === "codex") {
|
|
4662
|
+
return createCodexFilesystemToolSet(profileOrOptions);
|
|
4663
|
+
}
|
|
4664
|
+
if (resolvedProfile === "gemini") {
|
|
4665
|
+
return createGeminiFilesystemToolSet(profileOrOptions);
|
|
4666
|
+
}
|
|
4667
|
+
return createModelAgnosticFilesystemToolSet(profileOrOptions);
|
|
4668
|
+
}
|
|
4669
|
+
function createCodexFilesystemToolSet(options = {}) {
|
|
4670
|
+
return {
|
|
4671
|
+
apply_patch: createCodexApplyPatchTool(options),
|
|
4672
|
+
read_file: createCodexReadFileTool(options),
|
|
4673
|
+
list_dir: createListDirTool(options),
|
|
4674
|
+
grep_files: createGrepFilesTool(options)
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
function createGeminiFilesystemToolSet(options = {}) {
|
|
4678
|
+
return {
|
|
4679
|
+
read_file: createReadFileTool(options),
|
|
4680
|
+
write_file: createWriteFileTool(options),
|
|
4681
|
+
replace: createReplaceTool(options),
|
|
4682
|
+
list_directory: createListDirectoryTool(options),
|
|
4683
|
+
grep_search: createGrepSearchTool(options),
|
|
4684
|
+
glob: createGlobTool(options)
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
function createModelAgnosticFilesystemToolSet(options = {}) {
|
|
4688
|
+
return createGeminiFilesystemToolSet(options);
|
|
4689
|
+
}
|
|
4690
|
+
function createCodexApplyPatchTool(options = {}) {
|
|
4691
|
+
return tool({
|
|
4692
|
+
description: "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.",
|
|
4693
|
+
inputSchema: applyPatchInputSchema,
|
|
4694
|
+
execute: async ({ input }) => {
|
|
4695
|
+
const runtime = resolveRuntime(options);
|
|
4696
|
+
const result = await applyPatch({
|
|
4697
|
+
patch: input,
|
|
4698
|
+
cwd: runtime.cwd,
|
|
4699
|
+
fs: runtime.filesystem,
|
|
4700
|
+
allowOutsideCwd: runtime.allowOutsideCwd,
|
|
4701
|
+
checkAccess: runtime.checkAccess ? async (context) => {
|
|
4702
|
+
await runtime.checkAccess?.({
|
|
4703
|
+
cwd: runtime.cwd,
|
|
4704
|
+
tool: "apply_patch",
|
|
4705
|
+
action: mapApplyPatchAction(context.kind),
|
|
4706
|
+
path: context.path,
|
|
4707
|
+
fromPath: context.fromPath,
|
|
4708
|
+
toPath: context.toPath
|
|
4709
|
+
});
|
|
4710
|
+
} : void 0,
|
|
4711
|
+
maxPatchBytes: options.applyPatch?.maxPatchBytes
|
|
4712
|
+
});
|
|
4713
|
+
return result.summary;
|
|
4714
|
+
}
|
|
4715
|
+
});
|
|
4716
|
+
}
|
|
4717
|
+
function createCodexReadFileTool(options = {}) {
|
|
4718
|
+
return tool({
|
|
4719
|
+
description: "Reads a local file with 1-indexed line numbers, supporting slice and indentation-aware block modes.",
|
|
4720
|
+
inputSchema: codexReadFileInputSchema,
|
|
4721
|
+
execute: async (input) => readFileCodex(input, options)
|
|
4722
|
+
});
|
|
4723
|
+
}
|
|
4724
|
+
function createListDirTool(options = {}) {
|
|
4725
|
+
return tool({
|
|
4726
|
+
description: "Lists entries in a local directory with 1-indexed entry numbers and simple type labels.",
|
|
4727
|
+
inputSchema: codexListDirInputSchema,
|
|
4728
|
+
execute: async (input) => listDirectoryCodex(input, options)
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4731
|
+
function createGrepFilesTool(options = {}) {
|
|
4732
|
+
return tool({
|
|
4733
|
+
description: "Finds files whose contents match the pattern and lists them by modification time.",
|
|
4734
|
+
inputSchema: codexGrepFilesInputSchema,
|
|
4735
|
+
execute: async (input) => grepFilesCodex(input, options)
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
function createReadFileTool(options = {}) {
|
|
4739
|
+
return tool({
|
|
4740
|
+
description: "Reads and returns content of a specified file.",
|
|
4741
|
+
inputSchema: geminiReadFileInputSchema,
|
|
4742
|
+
execute: async (input) => readFileGemini(input, options)
|
|
4743
|
+
});
|
|
4744
|
+
}
|
|
4745
|
+
function createWriteFileTool(options = {}) {
|
|
4746
|
+
return tool({
|
|
4747
|
+
description: "Writes content to a specified file in the local filesystem.",
|
|
4748
|
+
inputSchema: geminiWriteFileInputSchema,
|
|
4749
|
+
execute: async (input) => writeFileGemini(input, options)
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
function createReplaceTool(options = {}) {
|
|
4753
|
+
return tool({
|
|
4754
|
+
description: "Replaces exact literal text within a file.",
|
|
4755
|
+
inputSchema: geminiReplaceInputSchema,
|
|
4756
|
+
execute: async (input) => replaceFileContentGemini(input, options)
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4759
|
+
function createListDirectoryTool(options = {}) {
|
|
4760
|
+
return tool({
|
|
4761
|
+
description: "Lists files and subdirectories directly within a specified directory path.",
|
|
4762
|
+
inputSchema: geminiListDirectoryInputSchema,
|
|
4763
|
+
execute: async (input) => listDirectoryGemini(input, options)
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4766
|
+
function createGrepSearchTool(options = {}) {
|
|
4767
|
+
return tool({
|
|
4768
|
+
description: "Searches for a regex pattern within file contents.",
|
|
4769
|
+
inputSchema: geminiGrepSearchInputSchema,
|
|
4770
|
+
execute: async (input) => grepSearchGemini(input, options)
|
|
4771
|
+
});
|
|
4772
|
+
}
|
|
4773
|
+
function createGlobTool(options = {}) {
|
|
4774
|
+
return tool({
|
|
4775
|
+
description: "Finds files matching glob patterns, sorted by modification time (newest first).",
|
|
4776
|
+
inputSchema: geminiGlobInputSchema,
|
|
4777
|
+
execute: async (input) => globFilesGemini(input, options)
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4780
|
+
async function readFileCodex(input, options) {
|
|
4781
|
+
const runtime = resolveRuntime(options);
|
|
4782
|
+
if (!import_node_path4.default.isAbsolute(input.file_path)) {
|
|
4783
|
+
throw new Error("file_path must be an absolute path");
|
|
4784
|
+
}
|
|
4785
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
4786
|
+
await runAccessHook2(runtime, {
|
|
4787
|
+
cwd: runtime.cwd,
|
|
4788
|
+
tool: "read_file",
|
|
4789
|
+
action: "read",
|
|
4790
|
+
path: filePath
|
|
4791
|
+
});
|
|
4792
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
4793
|
+
const lines = splitLines(content);
|
|
4794
|
+
const offset = input.offset ?? 1;
|
|
4795
|
+
const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
|
|
4796
|
+
const mode = input.mode ?? "slice";
|
|
4797
|
+
if (offset > lines.length) {
|
|
4798
|
+
throw new Error("offset exceeds file length");
|
|
4799
|
+
}
|
|
4800
|
+
if (mode === "slice") {
|
|
4801
|
+
const output = [];
|
|
4802
|
+
const lastLine = Math.min(lines.length, offset + limit - 1);
|
|
4803
|
+
for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
|
|
4804
|
+
const line = lines[lineNumber - 1] ?? "";
|
|
4805
|
+
output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
|
|
4806
|
+
}
|
|
4807
|
+
return output.join("\n");
|
|
4808
|
+
}
|
|
4809
|
+
const indentation = input.indentation ?? {};
|
|
4810
|
+
const anchorLine = indentation.anchor_line ?? offset;
|
|
4811
|
+
if (anchorLine < 1 || anchorLine > lines.length) {
|
|
4812
|
+
throw new Error("anchor_line exceeds file length");
|
|
4813
|
+
}
|
|
4814
|
+
const records = lines.map((line, index) => ({
|
|
4815
|
+
number: index + 1,
|
|
4816
|
+
raw: line,
|
|
4817
|
+
display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
|
|
4818
|
+
indent: measureIndent(line, DEFAULT_TAB_WIDTH)
|
|
4819
|
+
}));
|
|
4820
|
+
const selected = readWithIndentationMode({
|
|
4821
|
+
records,
|
|
4822
|
+
anchorLine,
|
|
4823
|
+
limit,
|
|
4824
|
+
maxLevels: indentation.max_levels ?? 0,
|
|
4825
|
+
includeSiblings: indentation.include_siblings ?? false,
|
|
4826
|
+
includeHeader: indentation.include_header ?? true,
|
|
4827
|
+
maxLines: indentation.max_lines
|
|
4828
|
+
});
|
|
4829
|
+
return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
|
|
4830
|
+
}
|
|
4831
|
+
async function listDirectoryCodex(input, options) {
|
|
4832
|
+
const runtime = resolveRuntime(options);
|
|
4833
|
+
if (!import_node_path4.default.isAbsolute(input.dir_path)) {
|
|
4834
|
+
throw new Error("dir_path must be an absolute path");
|
|
4835
|
+
}
|
|
4836
|
+
const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
4837
|
+
await runAccessHook2(runtime, {
|
|
4838
|
+
cwd: runtime.cwd,
|
|
4839
|
+
tool: "list_dir",
|
|
4840
|
+
action: "list",
|
|
4841
|
+
path: dirPath
|
|
4842
|
+
});
|
|
4843
|
+
const stats = await runtime.filesystem.stat(dirPath);
|
|
4844
|
+
if (stats.kind !== "directory") {
|
|
4845
|
+
throw new Error(`failed to read directory: "${dirPath}" is not a directory`);
|
|
4846
|
+
}
|
|
4847
|
+
const offset = input.offset ?? 1;
|
|
4848
|
+
const limit = input.limit ?? DEFAULT_LIST_DIR_LIMIT;
|
|
4849
|
+
const depth = input.depth ?? DEFAULT_LIST_DIR_DEPTH;
|
|
4850
|
+
const entries = await collectDirectoryEntries(
|
|
4851
|
+
runtime.filesystem,
|
|
4852
|
+
dirPath,
|
|
4853
|
+
depth,
|
|
4854
|
+
runtime.maxLineLength
|
|
4855
|
+
);
|
|
4856
|
+
if (offset > entries.length) {
|
|
4857
|
+
throw new Error("offset exceeds directory entry count");
|
|
4858
|
+
}
|
|
4859
|
+
const startIndex = offset - 1;
|
|
4860
|
+
const remaining = entries.length - startIndex;
|
|
4861
|
+
const cappedLimit = Math.min(limit, remaining);
|
|
4862
|
+
const selected = entries.slice(startIndex, startIndex + cappedLimit);
|
|
4863
|
+
const output = [`Absolute path: ${dirPath}`];
|
|
4864
|
+
for (const entry of selected) {
|
|
4865
|
+
output.push(formatListEntry(entry));
|
|
4866
|
+
}
|
|
4867
|
+
if (startIndex + cappedLimit < entries.length) {
|
|
4868
|
+
output.push(`More than ${cappedLimit} entries found`);
|
|
4869
|
+
}
|
|
4870
|
+
return output.join("\n");
|
|
4871
|
+
}
|
|
4872
|
+
async function grepFilesCodex(input, options) {
|
|
4873
|
+
const runtime = resolveRuntime(options);
|
|
4874
|
+
const pattern = input.pattern.trim();
|
|
4875
|
+
if (pattern.length === 0) {
|
|
4876
|
+
throw new Error("pattern must not be empty");
|
|
4877
|
+
}
|
|
4878
|
+
const regex = compileRegex(pattern);
|
|
4879
|
+
const searchPath = resolvePathWithPolicy(
|
|
4880
|
+
input.path ?? runtime.cwd,
|
|
4881
|
+
runtime.cwd,
|
|
4882
|
+
runtime.allowOutsideCwd
|
|
4883
|
+
);
|
|
4884
|
+
await runAccessHook2(runtime, {
|
|
4885
|
+
cwd: runtime.cwd,
|
|
4886
|
+
tool: "grep_files",
|
|
4887
|
+
action: "search",
|
|
4888
|
+
path: searchPath,
|
|
4889
|
+
pattern,
|
|
4890
|
+
include: input.include?.trim()
|
|
4891
|
+
});
|
|
4892
|
+
const searchPathInfo = await runtime.filesystem.stat(searchPath);
|
|
4893
|
+
const filesToScan = await collectSearchFiles({
|
|
4894
|
+
filesystem: runtime.filesystem,
|
|
4895
|
+
searchPath,
|
|
4896
|
+
rootKind: searchPathInfo.kind,
|
|
4897
|
+
maxScannedFiles: runtime.grepMaxScannedFiles
|
|
4898
|
+
});
|
|
4899
|
+
const includeMatcher = input.include ? createGlobMatcher(input.include) : null;
|
|
4900
|
+
const matches = [];
|
|
4901
|
+
for (const filePath of filesToScan) {
|
|
4902
|
+
const relativePath = toDisplayPath2(filePath, runtime.cwd);
|
|
4903
|
+
if (includeMatcher && !includeMatcher(relativePath)) {
|
|
4904
|
+
continue;
|
|
4905
|
+
}
|
|
4906
|
+
const fileContent = await runtime.filesystem.readTextFile(filePath);
|
|
4907
|
+
if (!regex.test(fileContent)) {
|
|
4908
|
+
continue;
|
|
4909
|
+
}
|
|
4910
|
+
const stats = await runtime.filesystem.stat(filePath);
|
|
4911
|
+
matches.push({ filePath: normalizeSlashes(relativePath), mtimeMs: stats.mtimeMs });
|
|
4912
|
+
}
|
|
4913
|
+
if (matches.length === 0) {
|
|
4914
|
+
return "No matches found.";
|
|
4915
|
+
}
|
|
4916
|
+
matches.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
4917
|
+
const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
|
|
4918
|
+
return matches.slice(0, limit).map((match) => match.filePath).join("\n");
|
|
4919
|
+
}
|
|
4920
|
+
async function readFileGemini(input, options) {
|
|
4921
|
+
const runtime = resolveRuntime(options);
|
|
4922
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
4923
|
+
await runAccessHook2(runtime, {
|
|
4924
|
+
cwd: runtime.cwd,
|
|
4925
|
+
tool: "read_file",
|
|
4926
|
+
action: "read",
|
|
4927
|
+
path: filePath
|
|
4928
|
+
});
|
|
4929
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
4930
|
+
if (input.offset === void 0 && input.limit === void 0) {
|
|
4931
|
+
return content;
|
|
4932
|
+
}
|
|
4933
|
+
const lines = splitLines(content);
|
|
4934
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
4935
|
+
const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
|
|
4936
|
+
if (offset >= lines.length) {
|
|
4937
|
+
return "";
|
|
4938
|
+
}
|
|
4939
|
+
const end = Math.min(lines.length, offset + limit);
|
|
4940
|
+
return lines.slice(offset, end).join("\n");
|
|
4941
|
+
}
|
|
4942
|
+
async function writeFileGemini(input, options) {
|
|
4943
|
+
const runtime = resolveRuntime(options);
|
|
4944
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
4945
|
+
await runAccessHook2(runtime, {
|
|
4946
|
+
cwd: runtime.cwd,
|
|
4947
|
+
tool: "write_file",
|
|
4948
|
+
action: "write",
|
|
4949
|
+
path: filePath
|
|
4950
|
+
});
|
|
4951
|
+
await runtime.filesystem.ensureDir(import_node_path4.default.dirname(filePath));
|
|
4952
|
+
await runtime.filesystem.writeTextFile(filePath, input.content);
|
|
4953
|
+
return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
|
|
4954
|
+
}
|
|
4955
|
+
async function replaceFileContentGemini(input, options) {
|
|
4956
|
+
const runtime = resolveRuntime(options);
|
|
4957
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
4958
|
+
await runAccessHook2(runtime, {
|
|
4959
|
+
cwd: runtime.cwd,
|
|
4960
|
+
tool: "replace",
|
|
4961
|
+
action: "write",
|
|
4962
|
+
path: filePath
|
|
4963
|
+
});
|
|
4964
|
+
const expectedReplacements = input.expected_replacements ?? 1;
|
|
4965
|
+
const oldValue = input.old_string;
|
|
4966
|
+
const newValue = input.new_string;
|
|
4967
|
+
let originalContent = "";
|
|
4968
|
+
try {
|
|
4969
|
+
originalContent = await runtime.filesystem.readTextFile(filePath);
|
|
4970
|
+
} catch (error) {
|
|
4971
|
+
if (isNoEntError(error) && oldValue.length === 0) {
|
|
4972
|
+
await runtime.filesystem.ensureDir(import_node_path4.default.dirname(filePath));
|
|
4973
|
+
await runtime.filesystem.writeTextFile(filePath, newValue);
|
|
4974
|
+
return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
|
|
4975
|
+
}
|
|
4976
|
+
throw error;
|
|
4977
|
+
}
|
|
4978
|
+
if (oldValue === newValue) {
|
|
4979
|
+
throw new Error("No changes to apply. old_string and new_string are identical.");
|
|
4980
|
+
}
|
|
4981
|
+
const occurrences = countOccurrences(originalContent, oldValue);
|
|
4982
|
+
if (occurrences === 0) {
|
|
4983
|
+
throw new Error("Failed to edit, could not find old_string in file.");
|
|
4984
|
+
}
|
|
4985
|
+
if (occurrences !== expectedReplacements) {
|
|
4986
|
+
throw new Error(
|
|
4987
|
+
`Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`
|
|
4988
|
+
);
|
|
4989
|
+
}
|
|
4990
|
+
const updatedContent = safeReplaceAll(originalContent, oldValue, newValue);
|
|
4991
|
+
await runtime.filesystem.writeTextFile(filePath, updatedContent);
|
|
4992
|
+
return `Successfully replaced ${occurrences} occurrence(s) in ${toDisplayPath2(filePath, runtime.cwd)}.`;
|
|
4993
|
+
}
|
|
4994
|
+
async function listDirectoryGemini(input, options) {
|
|
4995
|
+
const runtime = resolveRuntime(options);
|
|
4996
|
+
const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
4997
|
+
await runAccessHook2(runtime, {
|
|
4998
|
+
cwd: runtime.cwd,
|
|
4999
|
+
tool: "list_directory",
|
|
5000
|
+
action: "list",
|
|
5001
|
+
path: dirPath
|
|
5002
|
+
});
|
|
5003
|
+
const stats = await runtime.filesystem.stat(dirPath);
|
|
5004
|
+
if (stats.kind !== "directory") {
|
|
5005
|
+
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
5006
|
+
}
|
|
5007
|
+
const entries = await runtime.filesystem.readDir(dirPath);
|
|
5008
|
+
const ignoreMatchers = (input.ignore ?? []).map((pattern) => createGlobMatcher(pattern));
|
|
5009
|
+
const filtered = entries.filter((entry) => {
|
|
5010
|
+
if (ignoreMatchers.length === 0) {
|
|
5011
|
+
return true;
|
|
5012
|
+
}
|
|
5013
|
+
return !ignoreMatchers.some((matches) => matches(entry.name));
|
|
5014
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
5015
|
+
if (filtered.length === 0) {
|
|
5016
|
+
return `Directory ${toDisplayPath2(dirPath, runtime.cwd)} is empty.`;
|
|
5017
|
+
}
|
|
5018
|
+
return filtered.map((entry) => {
|
|
5019
|
+
const label = entry.kind === "directory" ? `${entry.name}/` : entry.name;
|
|
5020
|
+
return label;
|
|
5021
|
+
}).join("\n");
|
|
5022
|
+
}
|
|
5023
|
+
async function grepSearchGemini(input, options) {
|
|
5024
|
+
const runtime = resolveRuntime(options);
|
|
5025
|
+
const pattern = input.pattern.trim();
|
|
5026
|
+
if (pattern.length === 0) {
|
|
5027
|
+
throw new Error("pattern must not be empty");
|
|
5028
|
+
}
|
|
5029
|
+
const include = input.include?.trim();
|
|
5030
|
+
const searchPath = resolvePathWithPolicy(
|
|
5031
|
+
input.dir_path ?? runtime.cwd,
|
|
5032
|
+
runtime.cwd,
|
|
5033
|
+
runtime.allowOutsideCwd
|
|
5034
|
+
);
|
|
5035
|
+
await runAccessHook2(runtime, {
|
|
5036
|
+
cwd: runtime.cwd,
|
|
5037
|
+
tool: "grep_search",
|
|
5038
|
+
action: "search",
|
|
5039
|
+
path: searchPath,
|
|
5040
|
+
pattern,
|
|
5041
|
+
include
|
|
5042
|
+
});
|
|
5043
|
+
const searchPathInfo = await runtime.filesystem.stat(searchPath);
|
|
5044
|
+
const filesToScan = await collectSearchFiles({
|
|
5045
|
+
filesystem: runtime.filesystem,
|
|
5046
|
+
searchPath,
|
|
5047
|
+
rootKind: searchPathInfo.kind,
|
|
5048
|
+
maxScannedFiles: runtime.grepMaxScannedFiles
|
|
5049
|
+
});
|
|
5050
|
+
const matcher = include ? createGlobMatcher(include) : null;
|
|
5051
|
+
const patternRegex = compileRegex(pattern);
|
|
5052
|
+
const excludeRegex = input.exclude_pattern ? compileRegex(input.exclude_pattern) : null;
|
|
5053
|
+
const totalMaxMatches = input.total_max_matches ?? DEFAULT_GREP_LIMIT;
|
|
5054
|
+
const perFileMaxMatches = input.max_matches_per_file ?? Number.POSITIVE_INFINITY;
|
|
5055
|
+
const matches = [];
|
|
5056
|
+
const fileMatches = /* @__PURE__ */ new Set();
|
|
5057
|
+
for (const filePath of filesToScan) {
|
|
5058
|
+
const relativePath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
|
|
5059
|
+
if (matcher && !matcher(relativePath)) {
|
|
5060
|
+
continue;
|
|
5061
|
+
}
|
|
5062
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
5063
|
+
const lines = splitLines(content);
|
|
5064
|
+
let fileMatchCount = 0;
|
|
5065
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
5066
|
+
const line = lines[index] ?? "";
|
|
5067
|
+
if (!patternRegex.test(line)) {
|
|
5068
|
+
continue;
|
|
5069
|
+
}
|
|
5070
|
+
if (excludeRegex?.test(line)) {
|
|
5071
|
+
continue;
|
|
5072
|
+
}
|
|
5073
|
+
if (fileMatches.has(relativePath) === false) {
|
|
5074
|
+
fileMatches.add(relativePath);
|
|
5075
|
+
}
|
|
5076
|
+
if (input.names_only) {
|
|
5077
|
+
continue;
|
|
5078
|
+
}
|
|
5079
|
+
matches.push({
|
|
5080
|
+
filePath: relativePath,
|
|
5081
|
+
mtimeMs: 0,
|
|
5082
|
+
lineNumber: index + 1,
|
|
5083
|
+
line
|
|
5084
|
+
});
|
|
5085
|
+
fileMatchCount += 1;
|
|
5086
|
+
if (fileMatchCount >= perFileMaxMatches || matches.length >= totalMaxMatches) {
|
|
5087
|
+
break;
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
if (input.names_only && fileMatches.size >= totalMaxMatches) {
|
|
5091
|
+
break;
|
|
5092
|
+
}
|
|
5093
|
+
if (!input.names_only && matches.length >= totalMaxMatches) {
|
|
5094
|
+
break;
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
if (input.names_only) {
|
|
5098
|
+
if (fileMatches.size === 0) {
|
|
5099
|
+
return "No matches found.";
|
|
5100
|
+
}
|
|
5101
|
+
return [...fileMatches].slice(0, totalMaxMatches).join("\n");
|
|
5102
|
+
}
|
|
5103
|
+
if (matches.length === 0) {
|
|
5104
|
+
return "No matches found.";
|
|
5105
|
+
}
|
|
5106
|
+
return matches.slice(0, totalMaxMatches).map((match) => `${match.filePath}:${match.lineNumber}:${match.line ?? ""}`).join("\n");
|
|
5107
|
+
}
|
|
5108
|
+
async function globFilesGemini(input, options) {
|
|
5109
|
+
const runtime = resolveRuntime(options);
|
|
5110
|
+
const dirPath = resolvePathWithPolicy(
|
|
5111
|
+
input.dir_path ?? runtime.cwd,
|
|
5112
|
+
runtime.cwd,
|
|
5113
|
+
runtime.allowOutsideCwd
|
|
5114
|
+
);
|
|
5115
|
+
await runAccessHook2(runtime, {
|
|
5116
|
+
cwd: runtime.cwd,
|
|
5117
|
+
tool: "glob",
|
|
5118
|
+
action: "search",
|
|
5119
|
+
path: dirPath,
|
|
5120
|
+
pattern: input.pattern
|
|
5121
|
+
});
|
|
5122
|
+
const dirStats = await runtime.filesystem.stat(dirPath);
|
|
5123
|
+
if (dirStats.kind !== "directory") {
|
|
5124
|
+
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
5125
|
+
}
|
|
5126
|
+
const matcher = createGlobMatcher(input.pattern, input.case_sensitive === true);
|
|
5127
|
+
const files = await collectSearchFiles({
|
|
5128
|
+
filesystem: runtime.filesystem,
|
|
5129
|
+
searchPath: dirPath,
|
|
5130
|
+
rootKind: "directory",
|
|
5131
|
+
maxScannedFiles: runtime.grepMaxScannedFiles
|
|
5132
|
+
});
|
|
5133
|
+
const matched = [];
|
|
5134
|
+
for (const filePath of files) {
|
|
5135
|
+
const relativePath = normalizeSlashes(import_node_path4.default.relative(dirPath, filePath));
|
|
5136
|
+
if (!matcher(relativePath)) {
|
|
5137
|
+
continue;
|
|
5138
|
+
}
|
|
5139
|
+
const fileStats = await runtime.filesystem.stat(filePath);
|
|
5140
|
+
matched.push({
|
|
5141
|
+
filePath,
|
|
5142
|
+
mtimeMs: fileStats.mtimeMs
|
|
5143
|
+
});
|
|
5144
|
+
}
|
|
5145
|
+
if (matched.length === 0) {
|
|
5146
|
+
return "No files found.";
|
|
5147
|
+
}
|
|
5148
|
+
matched.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
5149
|
+
return matched.map((entry) => normalizeSlashes(import_node_path4.default.resolve(entry.filePath))).join("\n");
|
|
5150
|
+
}
|
|
5151
|
+
function resolveRuntime(options) {
|
|
5152
|
+
return {
|
|
5153
|
+
cwd: import_node_path4.default.resolve(options.cwd ?? process.cwd()),
|
|
5154
|
+
filesystem: options.fs ?? createNodeAgentFilesystem(),
|
|
5155
|
+
allowOutsideCwd: options.allowOutsideCwd === true,
|
|
5156
|
+
checkAccess: options.checkAccess,
|
|
5157
|
+
maxLineLength: options.maxLineLength ?? DEFAULT_MAX_LINE_LENGTH,
|
|
5158
|
+
grepMaxScannedFiles: options.grepMaxScannedFiles ?? DEFAULT_GREP_MAX_SCANNED_FILES
|
|
5159
|
+
};
|
|
5160
|
+
}
|
|
5161
|
+
async function runAccessHook2(runtime, context) {
|
|
5162
|
+
if (!runtime.checkAccess) {
|
|
5163
|
+
return;
|
|
5164
|
+
}
|
|
5165
|
+
await runtime.checkAccess(context);
|
|
5166
|
+
}
|
|
5167
|
+
function isCodexModel(model) {
|
|
5168
|
+
const normalized = model.startsWith("chatgpt-") ? model.slice("chatgpt-".length) : model;
|
|
5169
|
+
return normalized.includes("codex");
|
|
5170
|
+
}
|
|
5171
|
+
function isGeminiModel(model) {
|
|
5172
|
+
return model.startsWith("gemini-");
|
|
5173
|
+
}
|
|
5174
|
+
function mapApplyPatchAction(action) {
|
|
5175
|
+
if (action === "add" || action === "update") {
|
|
5176
|
+
return "write";
|
|
5177
|
+
}
|
|
5178
|
+
if (action === "delete") {
|
|
5179
|
+
return "delete";
|
|
5180
|
+
}
|
|
5181
|
+
return "move";
|
|
5182
|
+
}
|
|
5183
|
+
function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
|
|
5184
|
+
const absolutePath = import_node_path4.default.isAbsolute(inputPath) ? import_node_path4.default.resolve(inputPath) : import_node_path4.default.resolve(cwd, inputPath);
|
|
5185
|
+
if (!allowOutsideCwd && !isPathInsideCwd2(absolutePath, cwd)) {
|
|
5186
|
+
throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
|
|
5187
|
+
}
|
|
5188
|
+
return absolutePath;
|
|
5189
|
+
}
|
|
5190
|
+
function isPathInsideCwd2(candidatePath, cwd) {
|
|
5191
|
+
const relative = import_node_path4.default.relative(cwd, candidatePath);
|
|
5192
|
+
return relative === "" || !relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative);
|
|
5193
|
+
}
|
|
5194
|
+
function toDisplayPath2(absolutePath, cwd) {
|
|
5195
|
+
const relative = import_node_path4.default.relative(cwd, absolutePath);
|
|
5196
|
+
if (relative === "") {
|
|
5197
|
+
return ".";
|
|
5198
|
+
}
|
|
5199
|
+
if (!relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative)) {
|
|
5200
|
+
return relative;
|
|
5201
|
+
}
|
|
5202
|
+
return absolutePath;
|
|
5203
|
+
}
|
|
5204
|
+
function splitLines(content) {
|
|
5205
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
5206
|
+
const lines = normalized.split("\n");
|
|
5207
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
5208
|
+
lines.pop();
|
|
5209
|
+
}
|
|
5210
|
+
return lines;
|
|
5211
|
+
}
|
|
5212
|
+
function truncateAtCodePointBoundary(value, maxLength) {
|
|
5213
|
+
if (value.length <= maxLength) {
|
|
5214
|
+
return value;
|
|
5215
|
+
}
|
|
5216
|
+
return Array.from(value).slice(0, maxLength).join("");
|
|
5217
|
+
}
|
|
5218
|
+
function measureIndent(line, tabWidth) {
|
|
5219
|
+
let count = 0;
|
|
5220
|
+
for (const char of line) {
|
|
5221
|
+
if (char === " ") {
|
|
5222
|
+
count += 1;
|
|
5223
|
+
continue;
|
|
5224
|
+
}
|
|
5225
|
+
if (char === " ") {
|
|
5226
|
+
count += tabWidth;
|
|
5227
|
+
continue;
|
|
5228
|
+
}
|
|
5229
|
+
break;
|
|
5230
|
+
}
|
|
5231
|
+
return count;
|
|
5232
|
+
}
|
|
5233
|
+
function computeEffectiveIndents(records) {
|
|
5234
|
+
const effective = [];
|
|
5235
|
+
let previous = 0;
|
|
5236
|
+
for (const record of records) {
|
|
5237
|
+
if (record.raw.trim().length === 0) {
|
|
5238
|
+
effective.push(previous);
|
|
5239
|
+
} else {
|
|
5240
|
+
previous = record.indent;
|
|
5241
|
+
effective.push(previous);
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
return effective;
|
|
5245
|
+
}
|
|
5246
|
+
function trimBoundaryBlankLines(records) {
|
|
5247
|
+
while (records.length > 0 && records[0]?.raw.trim().length === 0) {
|
|
5248
|
+
records.shift();
|
|
5249
|
+
}
|
|
5250
|
+
while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
|
|
5251
|
+
records.pop();
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
function isCommentLine(line) {
|
|
5255
|
+
const trimmed = line.trim();
|
|
5256
|
+
return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
|
|
5257
|
+
}
|
|
5258
|
+
function readWithIndentationMode(params) {
|
|
5259
|
+
const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
|
|
5260
|
+
const anchorIndex = anchorLine - 1;
|
|
5261
|
+
const effectiveIndents = computeEffectiveIndents(records);
|
|
5262
|
+
const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
|
|
5263
|
+
const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
|
|
5264
|
+
const guardLimit = maxLines ?? limit;
|
|
5265
|
+
const finalLimit = Math.min(limit, guardLimit, records.length);
|
|
5266
|
+
if (finalLimit <= 1) {
|
|
5267
|
+
return [records[anchorIndex]].filter((entry) => Boolean(entry));
|
|
5268
|
+
}
|
|
5269
|
+
let upper = anchorIndex - 1;
|
|
5270
|
+
let lower = anchorIndex + 1;
|
|
5271
|
+
let upperMinIndentHits = 0;
|
|
5272
|
+
let lowerMinIndentHits = 0;
|
|
5273
|
+
const output = [records[anchorIndex]].filter(
|
|
5274
|
+
(entry) => Boolean(entry)
|
|
5275
|
+
);
|
|
5276
|
+
while (output.length < finalLimit) {
|
|
5277
|
+
let progressed = 0;
|
|
5278
|
+
if (upper >= 0) {
|
|
5279
|
+
const candidate = records[upper];
|
|
5280
|
+
const candidateIndent = effectiveIndents[upper] ?? 0;
|
|
5281
|
+
if (candidate && candidateIndent >= minIndent) {
|
|
5282
|
+
output.unshift(candidate);
|
|
5283
|
+
progressed += 1;
|
|
5284
|
+
upper -= 1;
|
|
5285
|
+
if (candidateIndent === minIndent && !includeSiblings) {
|
|
5286
|
+
const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
|
|
5287
|
+
const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
|
|
5288
|
+
if (canTakeLine) {
|
|
5289
|
+
upperMinIndentHits += 1;
|
|
5290
|
+
} else {
|
|
5291
|
+
output.shift();
|
|
5292
|
+
progressed -= 1;
|
|
5293
|
+
upper = -1;
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
if (output.length >= finalLimit) {
|
|
5297
|
+
break;
|
|
5298
|
+
}
|
|
5299
|
+
} else {
|
|
5300
|
+
upper = -1;
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
if (lower < records.length) {
|
|
5304
|
+
const candidate = records[lower];
|
|
5305
|
+
const candidateIndent = effectiveIndents[lower] ?? 0;
|
|
5306
|
+
if (candidate && candidateIndent >= minIndent) {
|
|
5307
|
+
output.push(candidate);
|
|
5308
|
+
progressed += 1;
|
|
5309
|
+
lower += 1;
|
|
5310
|
+
if (candidateIndent === minIndent && !includeSiblings) {
|
|
5311
|
+
if (lowerMinIndentHits > 0) {
|
|
5312
|
+
output.pop();
|
|
5313
|
+
progressed -= 1;
|
|
5314
|
+
lower = records.length;
|
|
5315
|
+
}
|
|
5316
|
+
lowerMinIndentHits += 1;
|
|
5317
|
+
}
|
|
5318
|
+
} else {
|
|
5319
|
+
lower = records.length;
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
if (progressed === 0) {
|
|
5323
|
+
break;
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
trimBoundaryBlankLines(output);
|
|
5327
|
+
return output;
|
|
5328
|
+
}
|
|
5329
|
+
async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
|
|
5330
|
+
const queue = [
|
|
5331
|
+
{ path: rootPath, relativePrefix: "", remainingDepth: depth }
|
|
5332
|
+
];
|
|
5333
|
+
const records = [];
|
|
5334
|
+
while (queue.length > 0) {
|
|
5335
|
+
const next = queue.shift();
|
|
5336
|
+
if (!next) {
|
|
5337
|
+
break;
|
|
5338
|
+
}
|
|
5339
|
+
const entries = await filesystem.readDir(next.path);
|
|
5340
|
+
const nextEntries = [...entries].map((entry) => {
|
|
5341
|
+
const relativePath = next.relativePrefix ? `${next.relativePrefix}/${entry.name}` : entry.name;
|
|
5342
|
+
return {
|
|
5343
|
+
entry,
|
|
5344
|
+
relativePath,
|
|
5345
|
+
depth: next.relativePrefix.length === 0 ? 0 : next.relativePrefix.split("/").length,
|
|
5346
|
+
sortName: normalizeSlashes(relativePath)
|
|
5347
|
+
};
|
|
5348
|
+
}).sort((left, right) => left.sortName.localeCompare(right.sortName));
|
|
5349
|
+
for (const item of nextEntries) {
|
|
5350
|
+
if (item.entry.kind === "directory" && next.remainingDepth > 1) {
|
|
5351
|
+
queue.push({
|
|
5352
|
+
path: item.entry.path,
|
|
5353
|
+
relativePrefix: item.relativePath,
|
|
5354
|
+
remainingDepth: next.remainingDepth - 1
|
|
5355
|
+
});
|
|
5356
|
+
}
|
|
5357
|
+
records.push({
|
|
5358
|
+
name: item.sortName,
|
|
5359
|
+
displayName: truncateAtCodePointBoundary(item.entry.name, maxLineLength),
|
|
5360
|
+
depth: item.depth,
|
|
5361
|
+
kind: item.entry.kind
|
|
5362
|
+
});
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
records.sort((left, right) => left.name.localeCompare(right.name));
|
|
5366
|
+
return records;
|
|
5367
|
+
}
|
|
5368
|
+
function formatListEntry(entry) {
|
|
5369
|
+
const indent = " ".repeat(entry.depth * 2);
|
|
5370
|
+
let name = entry.displayName;
|
|
5371
|
+
if (entry.kind === "directory") {
|
|
5372
|
+
name += "/";
|
|
5373
|
+
} else if (entry.kind === "symlink") {
|
|
5374
|
+
name += "@";
|
|
5375
|
+
} else if (entry.kind === "other") {
|
|
5376
|
+
name += "?";
|
|
5377
|
+
}
|
|
5378
|
+
return `${indent}${name}`;
|
|
5379
|
+
}
|
|
5380
|
+
async function collectSearchFiles(params) {
|
|
5381
|
+
const { filesystem, searchPath, rootKind, maxScannedFiles } = params;
|
|
5382
|
+
if (rootKind === "file") {
|
|
5383
|
+
return [searchPath];
|
|
5384
|
+
}
|
|
5385
|
+
const queue = [searchPath];
|
|
5386
|
+
const files = [];
|
|
5387
|
+
while (queue.length > 0) {
|
|
5388
|
+
const current = queue.shift();
|
|
5389
|
+
if (!current) {
|
|
5390
|
+
break;
|
|
5391
|
+
}
|
|
5392
|
+
const entries = await filesystem.readDir(current);
|
|
5393
|
+
for (const entry of entries) {
|
|
5394
|
+
if (entry.kind === "directory") {
|
|
5395
|
+
queue.push(entry.path);
|
|
5396
|
+
continue;
|
|
5397
|
+
}
|
|
5398
|
+
if (entry.kind !== "file") {
|
|
5399
|
+
continue;
|
|
5400
|
+
}
|
|
5401
|
+
files.push(entry.path);
|
|
5402
|
+
if (files.length >= maxScannedFiles) {
|
|
5403
|
+
return files;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
return files;
|
|
5408
|
+
}
|
|
5409
|
+
function compileRegex(pattern) {
|
|
5410
|
+
try {
|
|
5411
|
+
return new RegExp(pattern, "m");
|
|
5412
|
+
} catch (error) {
|
|
5413
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5414
|
+
throw new Error(`invalid regex pattern: ${message}`);
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
function createGlobMatcher(pattern, caseSensitive = false) {
|
|
5418
|
+
const expanded = expandBracePatterns(normalizeSlashes(pattern.trim()));
|
|
5419
|
+
const flags = caseSensitive ? "" : "i";
|
|
5420
|
+
const compiled = expanded.map((entry) => ({
|
|
5421
|
+
regex: globToRegex(entry, flags),
|
|
5422
|
+
applyToBasename: !entry.includes("/")
|
|
5423
|
+
}));
|
|
5424
|
+
return (candidatePath) => {
|
|
5425
|
+
const normalizedPath = normalizeSlashes(candidatePath);
|
|
5426
|
+
const basename = import_node_path4.default.posix.basename(normalizedPath);
|
|
5427
|
+
return compiled.some(
|
|
5428
|
+
(entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
|
|
5429
|
+
);
|
|
5430
|
+
};
|
|
5431
|
+
}
|
|
5432
|
+
function globToRegex(globPattern, flags) {
|
|
5433
|
+
let source = "^";
|
|
5434
|
+
for (let index = 0; index < globPattern.length; index += 1) {
|
|
5435
|
+
const char = globPattern[index];
|
|
5436
|
+
const nextChar = globPattern[index + 1];
|
|
5437
|
+
if (char === void 0) {
|
|
5438
|
+
continue;
|
|
5439
|
+
}
|
|
5440
|
+
if (char === "*" && nextChar === "*") {
|
|
5441
|
+
source += ".*";
|
|
5442
|
+
index += 1;
|
|
5443
|
+
continue;
|
|
5444
|
+
}
|
|
5445
|
+
if (char === "*") {
|
|
5446
|
+
source += "[^/]*";
|
|
5447
|
+
continue;
|
|
5448
|
+
}
|
|
5449
|
+
if (char === "?") {
|
|
5450
|
+
source += "[^/]";
|
|
5451
|
+
continue;
|
|
5452
|
+
}
|
|
5453
|
+
source += escapeRegexCharacter(char);
|
|
5454
|
+
}
|
|
5455
|
+
source += "$";
|
|
5456
|
+
return new RegExp(source, flags);
|
|
5457
|
+
}
|
|
5458
|
+
function expandBracePatterns(pattern) {
|
|
5459
|
+
const start = pattern.indexOf("{");
|
|
5460
|
+
if (start === -1) {
|
|
5461
|
+
return [pattern];
|
|
5462
|
+
}
|
|
5463
|
+
let depth = 0;
|
|
5464
|
+
let end = -1;
|
|
5465
|
+
for (let index = start; index < pattern.length; index += 1) {
|
|
5466
|
+
const char = pattern[index];
|
|
5467
|
+
if (char === "{") {
|
|
5468
|
+
depth += 1;
|
|
5469
|
+
continue;
|
|
5470
|
+
}
|
|
5471
|
+
if (char === "}") {
|
|
5472
|
+
depth -= 1;
|
|
5473
|
+
if (depth === 0) {
|
|
5474
|
+
end = index;
|
|
5475
|
+
break;
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
if (end === -1) {
|
|
5480
|
+
return [pattern];
|
|
5481
|
+
}
|
|
5482
|
+
const prefix = pattern.slice(0, start);
|
|
5483
|
+
const suffix = pattern.slice(end + 1);
|
|
5484
|
+
const body = pattern.slice(start + 1, end);
|
|
5485
|
+
const variants = splitTopLevel(body, ",");
|
|
5486
|
+
const expanded = [];
|
|
5487
|
+
for (const variant of variants) {
|
|
5488
|
+
expanded.push(...expandBracePatterns(`${prefix}${variant}${suffix}`));
|
|
5489
|
+
}
|
|
5490
|
+
return expanded;
|
|
5491
|
+
}
|
|
5492
|
+
function splitTopLevel(value, separator) {
|
|
5493
|
+
const parts = [];
|
|
5494
|
+
let depth = 0;
|
|
5495
|
+
let current = "";
|
|
5496
|
+
for (const char of value) {
|
|
5497
|
+
if (char === "{") {
|
|
5498
|
+
depth += 1;
|
|
5499
|
+
current += char;
|
|
5500
|
+
continue;
|
|
5501
|
+
}
|
|
5502
|
+
if (char === "}") {
|
|
5503
|
+
depth = Math.max(0, depth - 1);
|
|
5504
|
+
current += char;
|
|
5505
|
+
continue;
|
|
5506
|
+
}
|
|
5507
|
+
if (char === separator && depth === 0) {
|
|
5508
|
+
parts.push(current);
|
|
5509
|
+
current = "";
|
|
5510
|
+
continue;
|
|
5511
|
+
}
|
|
5512
|
+
current += char;
|
|
5513
|
+
}
|
|
5514
|
+
parts.push(current);
|
|
5515
|
+
return parts;
|
|
5516
|
+
}
|
|
5517
|
+
function escapeRegexCharacter(char) {
|
|
5518
|
+
return /[.*+?^${}()|[\]\\]/u.test(char) ? `\\${char}` : char;
|
|
5519
|
+
}
|
|
5520
|
+
function normalizeSlashes(value) {
|
|
5521
|
+
return value.replaceAll("\\", "/");
|
|
5522
|
+
}
|
|
5523
|
+
function countOccurrences(text, search) {
|
|
5524
|
+
if (search.length === 0) {
|
|
5525
|
+
return 0;
|
|
5526
|
+
}
|
|
5527
|
+
return text.split(search).length - 1;
|
|
5528
|
+
}
|
|
5529
|
+
function safeReplaceAll(text, search, replacement) {
|
|
5530
|
+
if (search.length === 0) {
|
|
5531
|
+
return text;
|
|
5532
|
+
}
|
|
5533
|
+
return text.split(search).join(replacement);
|
|
5534
|
+
}
|
|
5535
|
+
function isNoEntError(error) {
|
|
5536
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5539
|
+
// src/agent.ts
|
|
5540
|
+
async function runAgentLoop(request) {
|
|
5541
|
+
const { tools: customTools, filesystemTool, filesystem_tool, ...toolLoopRequest } = request;
|
|
5542
|
+
const filesystemSelection = filesystemTool ?? filesystem_tool;
|
|
5543
|
+
const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
|
|
5544
|
+
const mergedTools = mergeToolSets(filesystemTools, customTools ?? {});
|
|
5545
|
+
if (Object.keys(mergedTools).length === 0) {
|
|
5546
|
+
throw new Error(
|
|
5547
|
+
"runAgentLoop requires at least one tool. Provide `tools` or enable `filesystemTool`."
|
|
5548
|
+
);
|
|
5549
|
+
}
|
|
5550
|
+
return runToolLoop({
|
|
5551
|
+
...toolLoopRequest,
|
|
5552
|
+
tools: mergedTools
|
|
5553
|
+
});
|
|
5554
|
+
}
|
|
5555
|
+
function resolveFilesystemTools(model, selection) {
|
|
5556
|
+
if (selection === void 0 || selection === false) {
|
|
5557
|
+
return {};
|
|
5558
|
+
}
|
|
5559
|
+
if (selection === true) {
|
|
5560
|
+
return createFilesystemToolSetForModel(model, "auto");
|
|
5561
|
+
}
|
|
5562
|
+
if (typeof selection === "string") {
|
|
5563
|
+
return createFilesystemToolSetForModel(model, selection);
|
|
5564
|
+
}
|
|
5565
|
+
if (selection.enabled === false) {
|
|
5566
|
+
return {};
|
|
5567
|
+
}
|
|
5568
|
+
if (selection.options && selection.profile !== void 0) {
|
|
5569
|
+
return createFilesystemToolSetForModel(model, selection.profile, selection.options);
|
|
5570
|
+
}
|
|
5571
|
+
if (selection.options) {
|
|
5572
|
+
return createFilesystemToolSetForModel(model, selection.options);
|
|
5573
|
+
}
|
|
5574
|
+
return createFilesystemToolSetForModel(model, selection.profile ?? "auto");
|
|
5575
|
+
}
|
|
5576
|
+
function mergeToolSets(base, extra) {
|
|
5577
|
+
const merged = { ...base };
|
|
5578
|
+
for (const [toolName, toolSpec] of Object.entries(extra)) {
|
|
5579
|
+
if (Object.hasOwn(merged, toolName)) {
|
|
5580
|
+
throw new Error(
|
|
5581
|
+
`Duplicate tool name "${toolName}" in runAgentLoop. Rename the custom tool or disable that filesystem tool.`
|
|
5582
|
+
);
|
|
5583
|
+
}
|
|
5584
|
+
merged[toolName] = toolSpec;
|
|
5585
|
+
}
|
|
5586
|
+
return merged;
|
|
5587
|
+
}
|
|
3475
5588
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3476
5589
|
0 && (module.exports = {
|
|
5590
|
+
InMemoryAgentFilesystem,
|
|
3477
5591
|
LlmJsonCallError,
|
|
3478
5592
|
appendMarkdownSourcesSection,
|
|
5593
|
+
applyPatch,
|
|
3479
5594
|
configureGemini,
|
|
3480
5595
|
convertGooglePartsToLlmParts,
|
|
5596
|
+
createApplyPatchTool,
|
|
5597
|
+
createCodexApplyPatchTool,
|
|
5598
|
+
createCodexFilesystemToolSet,
|
|
5599
|
+
createCodexReadFileTool,
|
|
5600
|
+
createFilesystemToolSetForModel,
|
|
5601
|
+
createGeminiFilesystemToolSet,
|
|
5602
|
+
createGlobTool,
|
|
5603
|
+
createGrepFilesTool,
|
|
5604
|
+
createGrepSearchTool,
|
|
5605
|
+
createInMemoryAgentFilesystem,
|
|
5606
|
+
createListDirTool,
|
|
5607
|
+
createListDirectoryTool,
|
|
5608
|
+
createModelAgnosticFilesystemToolSet,
|
|
5609
|
+
createNodeAgentFilesystem,
|
|
5610
|
+
createReadFileTool,
|
|
5611
|
+
createReplaceTool,
|
|
5612
|
+
createWriteFileTool,
|
|
3481
5613
|
encodeChatGptAuthJson,
|
|
3482
5614
|
encodeChatGptAuthJsonB64,
|
|
3483
5615
|
estimateCallCostUsd,
|
|
@@ -3493,8 +5625,11 @@ ${lines}`;
|
|
|
3493
5625
|
loadLocalEnv,
|
|
3494
5626
|
parseJsonFromLlmText,
|
|
3495
5627
|
refreshChatGptOauthToken,
|
|
5628
|
+
resolveFilesystemToolProfile,
|
|
5629
|
+
runAgentLoop,
|
|
3496
5630
|
runToolLoop,
|
|
3497
5631
|
sanitisePartForLogging,
|
|
5632
|
+
streamJson,
|
|
3498
5633
|
streamText,
|
|
3499
5634
|
stripCodexCitationMarkers,
|
|
3500
5635
|
toGeminiJsonSchema,
|