@spotpatch/agent 1.2.0 → 1.2.1
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/dist/index.cjs +68 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +68 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,9 +29,27 @@ function parseJsonRecord(value) {
|
|
|
29
29
|
}
|
|
30
30
|
function parseToolArguments(value) {
|
|
31
31
|
if (typeof value !== "string") {
|
|
32
|
-
throw new SpotPatchError(ERROR_CODES.
|
|
32
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
33
|
+
}
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(value);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(parsed)) {
|
|
41
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
function assertUniqueToolCallIds(calls) {
|
|
46
|
+
const ids = /* @__PURE__ */ new Set();
|
|
47
|
+
for (const call of calls) {
|
|
48
|
+
if (ids.has(call.id)) {
|
|
49
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_CALL_ID_CONFLICT);
|
|
50
|
+
}
|
|
51
|
+
ids.add(call.id);
|
|
33
52
|
}
|
|
34
|
-
return parseJsonRecord(value);
|
|
35
53
|
}
|
|
36
54
|
function requireString(record, field) {
|
|
37
55
|
const value = record[field];
|
|
@@ -41,6 +59,7 @@ function requireString(record, field) {
|
|
|
41
59
|
return value;
|
|
42
60
|
}
|
|
43
61
|
function validateToolResults(pendingCalls, results) {
|
|
62
|
+
assertUniqueToolCallIds(pendingCalls);
|
|
44
63
|
if (pendingCalls.length === 0) {
|
|
45
64
|
if (results !== void 0 && results.length > 0) {
|
|
46
65
|
throw new SpotPatchError(ERROR_CODES.INTERNAL_ERROR);
|
|
@@ -441,6 +460,7 @@ function parseChatEvents(events) {
|
|
|
441
460
|
}
|
|
442
461
|
const finalText = content.join("");
|
|
443
462
|
const toolCalls = finalizeToolCalls(calls);
|
|
463
|
+
assertUniqueToolCallIds(toolCalls);
|
|
444
464
|
if (toolCalls.length === 0 && finalText.trim().length === 0) {
|
|
445
465
|
throw new SpotPatchError5(ERROR_CODES5.PROVIDER_PROTOCOL_UNSUPPORTED);
|
|
446
466
|
}
|
|
@@ -537,7 +557,7 @@ function collectFunctionCall(item, calls) {
|
|
|
537
557
|
});
|
|
538
558
|
const existing = calls.get(id);
|
|
539
559
|
if (existing !== void 0 && (existing.name !== call.name || JSON.stringify(existing.arguments) !== JSON.stringify(call.arguments))) {
|
|
540
|
-
throw new SpotPatchError6(ERROR_CODES6.
|
|
560
|
+
throw new SpotPatchError6(ERROR_CODES6.TOOL_CALL_ID_CONFLICT);
|
|
541
561
|
}
|
|
542
562
|
calls.set(id, call);
|
|
543
563
|
}
|
|
@@ -840,7 +860,7 @@ function encodeUtf8Text(content, includeByteOrderMark) {
|
|
|
840
860
|
}
|
|
841
861
|
async function writeAgentTextFileIfContentMatches(root, relativePath, expectedContent, nextContent, maximumBytes) {
|
|
842
862
|
if (nextContent.includes("\0")) {
|
|
843
|
-
throw new SpotPatchError9(ERROR_CODES9.
|
|
863
|
+
throw new SpotPatchError9(ERROR_CODES9.TOOL_ARGUMENTS_INVALID);
|
|
844
864
|
}
|
|
845
865
|
const absolutePath = await resolveExistingAgentPath(root, relativePath);
|
|
846
866
|
const [metadata, currentBytes] = await Promise.all([
|
|
@@ -1113,7 +1133,7 @@ async function runConfiguredCheck(options) {
|
|
|
1113
1133
|
function requireConfiguredCheck(checkId, checks) {
|
|
1114
1134
|
const check = checks[checkId];
|
|
1115
1135
|
if (check === void 0) {
|
|
1116
|
-
throw new SpotPatchError10(ERROR_CODES10.
|
|
1136
|
+
throw new SpotPatchError10(ERROR_CODES10.TOOL_ARGUMENTS_INVALID);
|
|
1117
1137
|
}
|
|
1118
1138
|
return check;
|
|
1119
1139
|
}
|
|
@@ -1431,7 +1451,7 @@ var MAX_DISCOVERED_FILES = 2e4;
|
|
|
1431
1451
|
var TEXT_SAMPLE_BYTES = 8192;
|
|
1432
1452
|
function compileGlob(glob) {
|
|
1433
1453
|
if (glob.length === 0 || glob.length > 256 || glob.includes("\0") || glob.includes("\\") || glob.startsWith("/") || ["[", "]", "{", "}", "(", ")", "!"].some((character) => glob.includes(character)) || glob.split("/").some((segment) => segment === "..")) {
|
|
1434
|
-
throw new SpotPatchError14(ERROR_CODES14.
|
|
1454
|
+
throw new SpotPatchError14(ERROR_CODES14.TOOL_ARGUMENTS_INVALID);
|
|
1435
1455
|
}
|
|
1436
1456
|
let expression = "^";
|
|
1437
1457
|
for (let index = 0; index < glob.length; index += 1) {
|
|
@@ -1670,7 +1690,7 @@ var runCheckSchema = z.strictObject({
|
|
|
1670
1690
|
checkId: z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u)
|
|
1671
1691
|
});
|
|
1672
1692
|
function invalidTool() {
|
|
1673
|
-
throw new SpotPatchError15(ERROR_CODES15.
|
|
1693
|
+
throw new SpotPatchError15(ERROR_CODES15.TOOL_ARGUMENTS_INVALID);
|
|
1674
1694
|
}
|
|
1675
1695
|
function parseArguments(schema, value) {
|
|
1676
1696
|
const parsed = schema.safeParse(value);
|
|
@@ -1727,8 +1747,16 @@ function retryableWriteRejection(reason, guidance) {
|
|
|
1727
1747
|
guidance
|
|
1728
1748
|
});
|
|
1729
1749
|
}
|
|
1750
|
+
function retryableArgumentsRejection() {
|
|
1751
|
+
return Object.freeze({
|
|
1752
|
+
errorCode: ERROR_CODES15.TOOL_ARGUMENTS_INVALID,
|
|
1753
|
+
retryable: true,
|
|
1754
|
+
reason: "ARGUMENTS_DO_NOT_MATCH_CONTRACT",
|
|
1755
|
+
guidance: "No files changed. Retry once with a new tool call ID and only the declared fields and value types."
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1730
1758
|
function createAgentToolExecutor(options) {
|
|
1731
|
-
const
|
|
1759
|
+
const cacheByTurn = /* @__PURE__ */ new Map();
|
|
1732
1760
|
const touchedPaths = /* @__PURE__ */ new Set();
|
|
1733
1761
|
const executeUncached = async (call, signal) => {
|
|
1734
1762
|
switch (call.name) {
|
|
@@ -1944,20 +1972,32 @@ function createAgentToolExecutor(options) {
|
|
|
1944
1972
|
}
|
|
1945
1973
|
};
|
|
1946
1974
|
return Object.freeze({
|
|
1947
|
-
async execute(call, signal) {
|
|
1975
|
+
async execute(call, scope, signal) {
|
|
1976
|
+
if (!Number.isSafeInteger(scope.turn) || scope.turn < 1) {
|
|
1977
|
+
throw new SpotPatchError15(ERROR_CODES15.INTERNAL_ERROR);
|
|
1978
|
+
}
|
|
1979
|
+
const turnCache = cacheByTurn.get(scope.turn) ?? /* @__PURE__ */ new Map();
|
|
1980
|
+
cacheByTurn.set(scope.turn, turnCache);
|
|
1948
1981
|
const signature = `${call.name}\0${JSON.stringify(call.arguments)}`;
|
|
1949
|
-
const cached =
|
|
1982
|
+
const cached = turnCache.get(call.id);
|
|
1950
1983
|
if (cached !== void 0) {
|
|
1951
1984
|
if (cached.signature !== signature) {
|
|
1952
|
-
|
|
1985
|
+
throw new SpotPatchError15(ERROR_CODES15.TOOL_CALL_ID_CONFLICT);
|
|
1953
1986
|
}
|
|
1954
1987
|
return cached.result;
|
|
1955
1988
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
output
|
|
1959
|
-
})
|
|
1960
|
-
|
|
1989
|
+
let output;
|
|
1990
|
+
try {
|
|
1991
|
+
output = await executeUncached(call, signal);
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
if (error instanceof SpotPatchError15 && error.code === ERROR_CODES15.TOOL_ARGUMENTS_INVALID) {
|
|
1994
|
+
output = retryableArgumentsRejection();
|
|
1995
|
+
} else {
|
|
1996
|
+
throw error;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
const result = Object.freeze({ toolCallId: call.id, output });
|
|
2000
|
+
turnCache.set(call.id, Object.freeze({ signature, result }));
|
|
1961
2001
|
return result;
|
|
1962
2002
|
},
|
|
1963
2003
|
touchedPaths() {
|
|
@@ -2546,6 +2586,7 @@ Follow these rules exactly:
|
|
|
2546
2586
|
- Use apply_patch only when creating or deleting a file, or when the change cannot be expressed as one exact replacement. apply_patch accepts only a raw canonical unified Git diff.
|
|
2547
2587
|
- Every patch must begin with 'diff --git a/<path> b/<path>', include matching '--- a/<path>' and '+++ b/<path>' headers and valid '@@' hunks. Send only the raw diff: no Markdown fences, prose, shell commands, or '*** Begin Patch' markers.
|
|
2548
2588
|
- If a write tool returns a retryable PATCH_REJECTED result, no file changed. Follow its guidance, re-read the current file, and retry once with a new tool call ID.
|
|
2589
|
+
- If any tool returns a retryable TOOL_ARGUMENTS_INVALID result, no file changed. Retry once with a new tool call ID using only the declared fields and value types.
|
|
2549
2590
|
- Never modify credentials, environment files, lockfiles, generated output, Git metadata, or dependencies.
|
|
2550
2591
|
- Do not claim a check passed unless run_check returned a passed status.
|
|
2551
2592
|
- Finish with a concise factual summary after all needed tool calls. Do not include secrets or absolute paths.`;
|
|
@@ -2705,7 +2746,7 @@ function isRetryableToolFailure(result) {
|
|
|
2705
2746
|
return false;
|
|
2706
2747
|
}
|
|
2707
2748
|
const candidate = output;
|
|
2708
|
-
return candidate.errorCode === ERROR_CODES19.PATCH_REJECTED
|
|
2749
|
+
return candidate.retryable === true && (candidate.errorCode === ERROR_CODES19.PATCH_REJECTED || candidate.errorCode === ERROR_CODES19.TOOL_ARGUMENTS_INVALID);
|
|
2709
2750
|
}
|
|
2710
2751
|
function throwIfCancelled(signal) {
|
|
2711
2752
|
if (signal.aborted) {
|
|
@@ -2781,8 +2822,10 @@ async function executeAgentChange(options) {
|
|
|
2781
2822
|
let summary;
|
|
2782
2823
|
let toolCallCount = 0;
|
|
2783
2824
|
for (let turn = 0; turn < options.execution.limits.maxTurns; turn += 1) {
|
|
2825
|
+
const turnNumber = turn + 1;
|
|
2784
2826
|
throwIfCancelled(controller.signal);
|
|
2785
2827
|
const response = await session.next(pendingResults, controller.signal);
|
|
2828
|
+
assertUniqueToolCallIds(response.toolCalls);
|
|
2786
2829
|
if (response.toolCalls.length === 0) {
|
|
2787
2830
|
summary = response.finalText.trim().slice(0, options.execution.limits.maxToolOutputCharacters);
|
|
2788
2831
|
break;
|
|
@@ -2795,16 +2838,22 @@ async function executeAgentChange(options) {
|
|
|
2795
2838
|
for (const call of response.toolCalls) {
|
|
2796
2839
|
options.callbacks?.onTool?.(
|
|
2797
2840
|
Object.freeze({
|
|
2841
|
+
turn: turnNumber,
|
|
2798
2842
|
toolCallId: call.id,
|
|
2799
2843
|
toolName: call.name,
|
|
2800
2844
|
state: "started"
|
|
2801
2845
|
})
|
|
2802
2846
|
);
|
|
2803
2847
|
try {
|
|
2804
|
-
const result2 = await executor.execute(
|
|
2848
|
+
const result2 = await executor.execute(
|
|
2849
|
+
call,
|
|
2850
|
+
Object.freeze({ turn: turnNumber }),
|
|
2851
|
+
controller.signal
|
|
2852
|
+
);
|
|
2805
2853
|
results.push(result2);
|
|
2806
2854
|
options.callbacks?.onTool?.(
|
|
2807
2855
|
Object.freeze({
|
|
2856
|
+
turn: turnNumber,
|
|
2808
2857
|
toolCallId: call.id,
|
|
2809
2858
|
toolName: call.name,
|
|
2810
2859
|
state: isRetryableToolFailure(result2) ? "failed" : "succeeded"
|
|
@@ -2813,6 +2862,7 @@ async function executeAgentChange(options) {
|
|
|
2813
2862
|
} catch (error) {
|
|
2814
2863
|
options.callbacks?.onTool?.(
|
|
2815
2864
|
Object.freeze({
|
|
2865
|
+
turn: turnNumber,
|
|
2816
2866
|
toolCallId: call.id,
|
|
2817
2867
|
toolName: call.name,
|
|
2818
2868
|
state: "failed"
|