@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.cjs
CHANGED
|
@@ -69,9 +69,27 @@ function parseJsonRecord(value) {
|
|
|
69
69
|
}
|
|
70
70
|
function parseToolArguments(value) {
|
|
71
71
|
if (typeof value !== "string") {
|
|
72
|
-
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.
|
|
72
|
+
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
73
|
+
}
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(value);
|
|
77
|
+
} catch {
|
|
78
|
+
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(parsed)) {
|
|
81
|
+
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
82
|
+
}
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
function assertUniqueToolCallIds(calls) {
|
|
86
|
+
const ids = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const call of calls) {
|
|
88
|
+
if (ids.has(call.id)) {
|
|
89
|
+
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.TOOL_CALL_ID_CONFLICT);
|
|
90
|
+
}
|
|
91
|
+
ids.add(call.id);
|
|
73
92
|
}
|
|
74
|
-
return parseJsonRecord(value);
|
|
75
93
|
}
|
|
76
94
|
function requireString(record, field) {
|
|
77
95
|
const value = record[field];
|
|
@@ -81,6 +99,7 @@ function requireString(record, field) {
|
|
|
81
99
|
return value;
|
|
82
100
|
}
|
|
83
101
|
function validateToolResults(pendingCalls, results) {
|
|
102
|
+
assertUniqueToolCallIds(pendingCalls);
|
|
84
103
|
if (pendingCalls.length === 0) {
|
|
85
104
|
if (results !== void 0 && results.length > 0) {
|
|
86
105
|
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.INTERNAL_ERROR);
|
|
@@ -478,6 +497,7 @@ function parseChatEvents(events) {
|
|
|
478
497
|
}
|
|
479
498
|
const finalText = content.join("");
|
|
480
499
|
const toolCalls = finalizeToolCalls(calls);
|
|
500
|
+
assertUniqueToolCallIds(toolCalls);
|
|
481
501
|
if (toolCalls.length === 0 && finalText.trim().length === 0) {
|
|
482
502
|
throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED);
|
|
483
503
|
}
|
|
@@ -574,7 +594,7 @@ function collectFunctionCall(item, calls) {
|
|
|
574
594
|
});
|
|
575
595
|
const existing = calls.get(id);
|
|
576
596
|
if (existing !== void 0 && (existing.name !== call.name || JSON.stringify(existing.arguments) !== JSON.stringify(call.arguments))) {
|
|
577
|
-
throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.
|
|
597
|
+
throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.TOOL_CALL_ID_CONFLICT);
|
|
578
598
|
}
|
|
579
599
|
calls.set(id, call);
|
|
580
600
|
}
|
|
@@ -874,7 +894,7 @@ function encodeUtf8Text(content, includeByteOrderMark) {
|
|
|
874
894
|
}
|
|
875
895
|
async function writeAgentTextFileIfContentMatches(root, relativePath, expectedContent, nextContent, maximumBytes) {
|
|
876
896
|
if (nextContent.includes("\0")) {
|
|
877
|
-
throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.
|
|
897
|
+
throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
878
898
|
}
|
|
879
899
|
const absolutePath = await resolveExistingAgentPath(root, relativePath);
|
|
880
900
|
const [metadata, currentBytes] = await Promise.all([
|
|
@@ -1143,7 +1163,7 @@ async function runConfiguredCheck(options) {
|
|
|
1143
1163
|
function requireConfiguredCheck(checkId, checks) {
|
|
1144
1164
|
const check = checks[checkId];
|
|
1145
1165
|
if (check === void 0) {
|
|
1146
|
-
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.
|
|
1166
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
1147
1167
|
}
|
|
1148
1168
|
return check;
|
|
1149
1169
|
}
|
|
@@ -1455,7 +1475,7 @@ var MAX_DISCOVERED_FILES = 2e4;
|
|
|
1455
1475
|
var TEXT_SAMPLE_BYTES = 8192;
|
|
1456
1476
|
function compileGlob(glob) {
|
|
1457
1477
|
if (glob.length === 0 || glob.length > 256 || glob.includes("\0") || glob.includes("\\") || glob.startsWith("/") || ["[", "]", "{", "}", "(", ")", "!"].some((character) => glob.includes(character)) || glob.split("/").some((segment) => segment === "..")) {
|
|
1458
|
-
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.
|
|
1478
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
1459
1479
|
}
|
|
1460
1480
|
let expression = "^";
|
|
1461
1481
|
for (let index = 0; index < glob.length; index += 1) {
|
|
@@ -1694,7 +1714,7 @@ var runCheckSchema = import_zod.z.strictObject({
|
|
|
1694
1714
|
checkId: import_zod.z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u)
|
|
1695
1715
|
});
|
|
1696
1716
|
function invalidTool() {
|
|
1697
|
-
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.
|
|
1717
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
1698
1718
|
}
|
|
1699
1719
|
function parseArguments(schema, value) {
|
|
1700
1720
|
const parsed = schema.safeParse(value);
|
|
@@ -1751,8 +1771,16 @@ function retryableWriteRejection(reason, guidance) {
|
|
|
1751
1771
|
guidance
|
|
1752
1772
|
});
|
|
1753
1773
|
}
|
|
1774
|
+
function retryableArgumentsRejection() {
|
|
1775
|
+
return Object.freeze({
|
|
1776
|
+
errorCode: import_shared15.ERROR_CODES.TOOL_ARGUMENTS_INVALID,
|
|
1777
|
+
retryable: true,
|
|
1778
|
+
reason: "ARGUMENTS_DO_NOT_MATCH_CONTRACT",
|
|
1779
|
+
guidance: "No files changed. Retry once with a new tool call ID and only the declared fields and value types."
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1754
1782
|
function createAgentToolExecutor(options) {
|
|
1755
|
-
const
|
|
1783
|
+
const cacheByTurn = /* @__PURE__ */ new Map();
|
|
1756
1784
|
const touchedPaths = /* @__PURE__ */ new Set();
|
|
1757
1785
|
const executeUncached = async (call, signal) => {
|
|
1758
1786
|
switch (call.name) {
|
|
@@ -1968,20 +1996,32 @@ function createAgentToolExecutor(options) {
|
|
|
1968
1996
|
}
|
|
1969
1997
|
};
|
|
1970
1998
|
return Object.freeze({
|
|
1971
|
-
async execute(call, signal) {
|
|
1999
|
+
async execute(call, scope, signal) {
|
|
2000
|
+
if (!Number.isSafeInteger(scope.turn) || scope.turn < 1) {
|
|
2001
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INTERNAL_ERROR);
|
|
2002
|
+
}
|
|
2003
|
+
const turnCache = cacheByTurn.get(scope.turn) ?? /* @__PURE__ */ new Map();
|
|
2004
|
+
cacheByTurn.set(scope.turn, turnCache);
|
|
1972
2005
|
const signature = `${call.name}\0${JSON.stringify(call.arguments)}`;
|
|
1973
|
-
const cached =
|
|
2006
|
+
const cached = turnCache.get(call.id);
|
|
1974
2007
|
if (cached !== void 0) {
|
|
1975
2008
|
if (cached.signature !== signature) {
|
|
1976
|
-
|
|
2009
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.TOOL_CALL_ID_CONFLICT);
|
|
1977
2010
|
}
|
|
1978
2011
|
return cached.result;
|
|
1979
2012
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
output
|
|
1983
|
-
})
|
|
1984
|
-
|
|
2013
|
+
let output;
|
|
2014
|
+
try {
|
|
2015
|
+
output = await executeUncached(call, signal);
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
if (error instanceof import_shared15.SpotPatchError && error.code === import_shared15.ERROR_CODES.TOOL_ARGUMENTS_INVALID) {
|
|
2018
|
+
output = retryableArgumentsRejection();
|
|
2019
|
+
} else {
|
|
2020
|
+
throw error;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
const result = Object.freeze({ toolCallId: call.id, output });
|
|
2024
|
+
turnCache.set(call.id, Object.freeze({ signature, result }));
|
|
1985
2025
|
return result;
|
|
1986
2026
|
},
|
|
1987
2027
|
touchedPaths() {
|
|
@@ -2552,6 +2592,7 @@ Follow these rules exactly:
|
|
|
2552
2592
|
- 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.
|
|
2553
2593
|
- 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.
|
|
2554
2594
|
- 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.
|
|
2595
|
+
- 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.
|
|
2555
2596
|
- Never modify credentials, environment files, lockfiles, generated output, Git metadata, or dependencies.
|
|
2556
2597
|
- Do not claim a check passed unless run_check returned a passed status.
|
|
2557
2598
|
- Finish with a concise factual summary after all needed tool calls. Do not include secrets or absolute paths.`;
|
|
@@ -2711,7 +2752,7 @@ function isRetryableToolFailure(result) {
|
|
|
2711
2752
|
return false;
|
|
2712
2753
|
}
|
|
2713
2754
|
const candidate = output;
|
|
2714
|
-
return candidate.errorCode === import_shared20.ERROR_CODES.PATCH_REJECTED
|
|
2755
|
+
return candidate.retryable === true && (candidate.errorCode === import_shared20.ERROR_CODES.PATCH_REJECTED || candidate.errorCode === import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
2715
2756
|
}
|
|
2716
2757
|
function throwIfCancelled(signal) {
|
|
2717
2758
|
if (signal.aborted) {
|
|
@@ -2787,8 +2828,10 @@ async function executeAgentChange(options) {
|
|
|
2787
2828
|
let summary;
|
|
2788
2829
|
let toolCallCount = 0;
|
|
2789
2830
|
for (let turn = 0; turn < options.execution.limits.maxTurns; turn += 1) {
|
|
2831
|
+
const turnNumber = turn + 1;
|
|
2790
2832
|
throwIfCancelled(controller.signal);
|
|
2791
2833
|
const response = await session.next(pendingResults, controller.signal);
|
|
2834
|
+
assertUniqueToolCallIds(response.toolCalls);
|
|
2792
2835
|
if (response.toolCalls.length === 0) {
|
|
2793
2836
|
summary = response.finalText.trim().slice(0, options.execution.limits.maxToolOutputCharacters);
|
|
2794
2837
|
break;
|
|
@@ -2801,16 +2844,22 @@ async function executeAgentChange(options) {
|
|
|
2801
2844
|
for (const call of response.toolCalls) {
|
|
2802
2845
|
options.callbacks?.onTool?.(
|
|
2803
2846
|
Object.freeze({
|
|
2847
|
+
turn: turnNumber,
|
|
2804
2848
|
toolCallId: call.id,
|
|
2805
2849
|
toolName: call.name,
|
|
2806
2850
|
state: "started"
|
|
2807
2851
|
})
|
|
2808
2852
|
);
|
|
2809
2853
|
try {
|
|
2810
|
-
const result2 = await executor.execute(
|
|
2854
|
+
const result2 = await executor.execute(
|
|
2855
|
+
call,
|
|
2856
|
+
Object.freeze({ turn: turnNumber }),
|
|
2857
|
+
controller.signal
|
|
2858
|
+
);
|
|
2811
2859
|
results.push(result2);
|
|
2812
2860
|
options.callbacks?.onTool?.(
|
|
2813
2861
|
Object.freeze({
|
|
2862
|
+
turn: turnNumber,
|
|
2814
2863
|
toolCallId: call.id,
|
|
2815
2864
|
toolName: call.name,
|
|
2816
2865
|
state: isRetryableToolFailure(result2) ? "failed" : "succeeded"
|
|
@@ -2819,6 +2868,7 @@ async function executeAgentChange(options) {
|
|
|
2819
2868
|
} catch (error) {
|
|
2820
2869
|
options.callbacks?.onTool?.(
|
|
2821
2870
|
Object.freeze({
|
|
2871
|
+
turn: turnNumber,
|
|
2822
2872
|
toolCallId: call.id,
|
|
2823
2873
|
toolName: call.name,
|
|
2824
2874
|
state: "failed"
|