@ai-sdk/harness 1.0.123 → 1.0.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/agent/index.js +145 -15
- package/dist/agent/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/internal/run-prompt.ts +134 -19
- package/src/agent/internal/strip-work-dir.ts +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @ai-sdk/harness
|
|
2
2
|
|
|
3
|
+
## 1.0.124
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 40231b6: fix(harness): enforce tool input schemas at execution and consistently reject invalid inputs
|
|
8
|
+
- Updated dependencies [b5679a7]
|
|
9
|
+
- Updated dependencies [ca31b89]
|
|
10
|
+
- ai@7.0.114
|
|
11
|
+
|
|
3
12
|
## 1.0.123
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/dist/agent/index.js
CHANGED
|
@@ -1103,6 +1103,45 @@ function stripWorkDir(part, sessionWorkDir) {
|
|
|
1103
1103
|
return part;
|
|
1104
1104
|
}
|
|
1105
1105
|
}
|
|
1106
|
+
function stripParsedToolInputWorkDir({
|
|
1107
|
+
value,
|
|
1108
|
+
sessionWorkDir
|
|
1109
|
+
}) {
|
|
1110
|
+
if (sessionWorkDir.length === 0) return value;
|
|
1111
|
+
const projected = /* @__PURE__ */ new WeakMap();
|
|
1112
|
+
const pending = [];
|
|
1113
|
+
const project = (item) => {
|
|
1114
|
+
if (typeof item === "string") return stripString(item, sessionWorkDir);
|
|
1115
|
+
if (item === null || typeof item !== "object") return item;
|
|
1116
|
+
if (!Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) {
|
|
1117
|
+
return item;
|
|
1118
|
+
}
|
|
1119
|
+
if (projected.has(item)) return projected.get(item);
|
|
1120
|
+
const target = Array.isArray(item) ? new Array(item.length) : {};
|
|
1121
|
+
projected.set(item, target);
|
|
1122
|
+
pending.push({ source: item, target });
|
|
1123
|
+
return target;
|
|
1124
|
+
};
|
|
1125
|
+
const result = project(value);
|
|
1126
|
+
while (pending.length > 0) {
|
|
1127
|
+
const { source, target } = pending.pop();
|
|
1128
|
+
if (Array.isArray(source) && Array.isArray(target)) {
|
|
1129
|
+
for (let index = 0; index < source.length; index++) {
|
|
1130
|
+
if (index in source) target[index] = project(source[index]);
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
for (const [key, item] of Object.entries(source)) {
|
|
1134
|
+
Object.defineProperty(target, key, {
|
|
1135
|
+
value: project(item),
|
|
1136
|
+
enumerable: true,
|
|
1137
|
+
writable: true,
|
|
1138
|
+
configurable: true
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return result;
|
|
1144
|
+
}
|
|
1106
1145
|
function stripString(value, workDir) {
|
|
1107
1146
|
return stripStreamingString({
|
|
1108
1147
|
value,
|
|
@@ -1531,6 +1570,7 @@ function pinSandboxChannelEventCheckpoint(event) {
|
|
|
1531
1570
|
}
|
|
1532
1571
|
|
|
1533
1572
|
// src/agent/internal/run-prompt.ts
|
|
1573
|
+
var invalidToolInputMessage = "Tool input validation failed.";
|
|
1534
1574
|
function unwrapToolResultOutput(toolResult) {
|
|
1535
1575
|
switch (toolResult.output.type) {
|
|
1536
1576
|
case "text":
|
|
@@ -1945,7 +1985,7 @@ function runPrompt(input) {
|
|
|
1945
1985
|
};
|
|
1946
1986
|
const processPendingApprovalContinuation = async (approval, continuation) => {
|
|
1947
1987
|
var _a6;
|
|
1948
|
-
const rawToolCall = (_a6 = rawToolCallsByToolCallId.get(approval.toolCallId)) != null ? _a6 : {
|
|
1988
|
+
const rawToolCall = (_a6 = approval.kind === "builtin" ? rawToolCallsByToolCallId.get(approval.toolCallId) : void 0) != null ? _a6 : {
|
|
1949
1989
|
type: "tool-call",
|
|
1950
1990
|
toolCallId: approval.toolCallId,
|
|
1951
1991
|
toolName: approval.toolName,
|
|
@@ -1953,14 +1993,29 @@ function runPrompt(input) {
|
|
|
1953
1993
|
providerExecuted: approval.providerExecuted,
|
|
1954
1994
|
nativeName: approval.nativeName
|
|
1955
1995
|
};
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1996
|
+
let validatedToolCall;
|
|
1997
|
+
let toolCall;
|
|
1998
|
+
if (approval.kind === "custom" && continuation.approved) {
|
|
1999
|
+
validatedToolCall = asToolCallTextStreamPart({
|
|
2000
|
+
part: await validateToolCall({
|
|
2001
|
+
event: rawToolCall,
|
|
2002
|
+
tools: input.tools
|
|
2003
|
+
})
|
|
2004
|
+
});
|
|
2005
|
+
toolCall = displayHostToolCall({
|
|
2006
|
+
toolCall: validatedToolCall,
|
|
2007
|
+
sessionWorkDir: input.sessionWorkDir
|
|
2008
|
+
});
|
|
2009
|
+
} else {
|
|
2010
|
+
const parsedInput = await safeParseJSON2({ text: rawToolCall.input });
|
|
2011
|
+
toolCall = {
|
|
2012
|
+
type: "tool-call",
|
|
2013
|
+
toolCallId: rawToolCall.toolCallId,
|
|
2014
|
+
toolName: rawToolCall.toolName,
|
|
2015
|
+
input: parsedInput.success ? parsedInput.value : rawToolCall.input,
|
|
2016
|
+
...rawToolCall.providerExecuted !== void 0 ? { providerExecuted: rawToolCall.providerExecuted } : {}
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
1964
2019
|
enqueueApprovalResponse(approval, continuation, toolCall);
|
|
1965
2020
|
onToolApprovalSettled(approval.approvalId);
|
|
1966
2021
|
pendingApprovalsByApprovalId.delete(approval.approvalId);
|
|
@@ -1990,6 +2045,27 @@ function runPrompt(input) {
|
|
|
1990
2045
|
});
|
|
1991
2046
|
return "continued";
|
|
1992
2047
|
}
|
|
2048
|
+
if (validatedToolCall == null) {
|
|
2049
|
+
throw new Error(
|
|
2050
|
+
`Harness '${input.harness.harnessId}' could not validate approved host tool '${approval.toolName}'.`
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
if (validatedToolCall.invalid) {
|
|
2054
|
+
const error = new Error(invalidToolInputMessage);
|
|
2055
|
+
await submitToolResult({
|
|
2056
|
+
toolCallId: approval.toolCallId,
|
|
2057
|
+
output: { error: invalidToolInputMessage },
|
|
2058
|
+
isError: true
|
|
2059
|
+
});
|
|
2060
|
+
bufferedToolOutcomes.push(() => {
|
|
2061
|
+
enqueueHostToolOutcome({
|
|
2062
|
+
toolCall,
|
|
2063
|
+
outcome: { ok: false, error }
|
|
2064
|
+
});
|
|
2065
|
+
});
|
|
2066
|
+
await publishToolExecutions();
|
|
2067
|
+
return "continued";
|
|
2068
|
+
}
|
|
1993
2069
|
await lifecycle.start(input.model);
|
|
1994
2070
|
toolExecutions.set(rawToolCall.toolCallId, {
|
|
1995
2071
|
toolCall
|
|
@@ -1997,6 +2073,7 @@ function runPrompt(input) {
|
|
|
1997
2073
|
const executionStartedAt = Date.now();
|
|
1998
2074
|
const execution = await maybeExecuteHostTool({
|
|
1999
2075
|
event: rawToolCall,
|
|
2076
|
+
parsedToolCall: validatedToolCall,
|
|
2000
2077
|
tools: activeTools,
|
|
2001
2078
|
toolsContext,
|
|
2002
2079
|
wrappedExecuteTool: lifecycle.executeTool,
|
|
@@ -2191,15 +2268,25 @@ function runPrompt(input) {
|
|
|
2191
2268
|
} else {
|
|
2192
2269
|
for (const part of translatedParts) result.enqueue(part);
|
|
2193
2270
|
}
|
|
2271
|
+
let validatedHostToolCall;
|
|
2194
2272
|
if (displayValue.type === "tool-call") {
|
|
2273
|
+
const isHostTool = value.type === "tool-call" && !value.providerExecuted && hasTool({ tools: activeTools, toolName: value.toolName }) && !(value.toolName === "askUserQuestions" && hasTool({
|
|
2274
|
+
tools: input.harness.builtinTools,
|
|
2275
|
+
toolName: value.toolName
|
|
2276
|
+
}));
|
|
2195
2277
|
const parsed = await validateToolCall({
|
|
2196
|
-
event: displayValue,
|
|
2278
|
+
event: isHostTool ? value : displayValue,
|
|
2197
2279
|
tools: input.tools
|
|
2198
2280
|
});
|
|
2199
2281
|
const parsedToolCall = asToolCallTextStreamPart({ part: parsed });
|
|
2282
|
+
validatedHostToolCall = isHostTool ? parsedToolCall : void 0;
|
|
2283
|
+
const displayToolCall = isHostTool ? displayHostToolCall({
|
|
2284
|
+
toolCall: parsedToolCall,
|
|
2285
|
+
sessionWorkDir: input.sessionWorkDir
|
|
2286
|
+
}) : parsedToolCall;
|
|
2200
2287
|
rawToolCallsByToolCallId.set(displayValue.toolCallId, displayValue);
|
|
2201
|
-
toolCallsByToolCallId.set(displayValue.toolCallId,
|
|
2202
|
-
result.enqueue(
|
|
2288
|
+
toolCallsByToolCallId.set(displayValue.toolCallId, displayToolCall);
|
|
2289
|
+
result.enqueue(displayToolCall);
|
|
2203
2290
|
}
|
|
2204
2291
|
if (value.type === "text-delta") {
|
|
2205
2292
|
stepText += value.delta;
|
|
@@ -2345,6 +2432,31 @@ function runPrompt(input) {
|
|
|
2345
2432
|
await finishForHostInputPause({ completeCurrentStep: true });
|
|
2346
2433
|
return;
|
|
2347
2434
|
}
|
|
2435
|
+
if (validatedHostToolCall == null) {
|
|
2436
|
+
throw new Error(
|
|
2437
|
+
`Harness '${input.harness.harnessId}' could not validate host tool '${toolCall.toolName}'.`
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
if (validatedHostToolCall.invalid) {
|
|
2441
|
+
settledHostToolCallIds.add(toolCall.toolCallId);
|
|
2442
|
+
toolExecutions.delete(toolCall.toolCallId);
|
|
2443
|
+
bufferedToolOutcomes.push(() => {
|
|
2444
|
+
result.enqueue({
|
|
2445
|
+
type: "tool-error",
|
|
2446
|
+
toolCallId: toolCall.toolCallId,
|
|
2447
|
+
toolName: toolCall.toolName,
|
|
2448
|
+
input: parsedToolCall.input,
|
|
2449
|
+
error: new Error(invalidToolInputMessage),
|
|
2450
|
+
dynamic: true
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
await submitToolResult({
|
|
2454
|
+
toolCallId: toolCall.toolCallId,
|
|
2455
|
+
output: { error: invalidToolInputMessage },
|
|
2456
|
+
isError: true
|
|
2457
|
+
});
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2348
2460
|
const customToolApprovalDecision = resolveCustomToolApproval({
|
|
2349
2461
|
toolName: toolCall.toolName,
|
|
2350
2462
|
toolApproval: input.toolApproval
|
|
@@ -2444,6 +2556,7 @@ function runPrompt(input) {
|
|
|
2444
2556
|
const executionStartedAt = Date.now();
|
|
2445
2557
|
const execution = await maybeExecuteHostTool({
|
|
2446
2558
|
event: toolCall,
|
|
2559
|
+
parsedToolCall: validatedHostToolCall,
|
|
2447
2560
|
tools: activeTools,
|
|
2448
2561
|
toolsContext,
|
|
2449
2562
|
wrappedExecuteTool: lifecycle.executeTool,
|
|
@@ -2549,14 +2662,31 @@ function asToolCallTextStreamPart(input) {
|
|
|
2549
2662
|
}
|
|
2550
2663
|
return input.part;
|
|
2551
2664
|
}
|
|
2665
|
+
function displayHostToolCall(input) {
|
|
2666
|
+
return {
|
|
2667
|
+
...input.toolCall,
|
|
2668
|
+
input: stripParsedToolInputWorkDir({
|
|
2669
|
+
value: input.toolCall.input,
|
|
2670
|
+
sessionWorkDir: input.sessionWorkDir
|
|
2671
|
+
}),
|
|
2672
|
+
...input.toolCall.invalid ? { error: new Error(invalidToolInputMessage) } : {}
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2552
2675
|
function hasTool(input) {
|
|
2553
2676
|
return Object.prototype.hasOwnProperty.call(input.tools, input.toolName);
|
|
2554
2677
|
}
|
|
2555
2678
|
async function maybeExecuteHostTool(input) {
|
|
2556
2679
|
const tool = input.tools[input.event.toolName];
|
|
2557
2680
|
if (!isExecutableTool(tool)) return { executed: false };
|
|
2558
|
-
|
|
2559
|
-
|
|
2681
|
+
if (input.parsedToolCall.invalid) {
|
|
2682
|
+
const error = new Error(invalidToolInputMessage);
|
|
2683
|
+
await input.submitToolResult({
|
|
2684
|
+
toolCallId: input.event.toolCallId,
|
|
2685
|
+
output: { error: invalidToolInputMessage },
|
|
2686
|
+
isError: true
|
|
2687
|
+
});
|
|
2688
|
+
return { executed: true, outcome: { ok: false, error } };
|
|
2689
|
+
}
|
|
2560
2690
|
let context;
|
|
2561
2691
|
try {
|
|
2562
2692
|
context = await validateToolContext({
|
|
@@ -2579,7 +2709,7 @@ async function maybeExecuteHostTool(input) {
|
|
|
2579
2709
|
let output2;
|
|
2580
2710
|
const stream = executeTool({
|
|
2581
2711
|
tool,
|
|
2582
|
-
input:
|
|
2712
|
+
input: input.parsedToolCall.input,
|
|
2583
2713
|
options: {
|
|
2584
2714
|
toolCallId: input.event.toolCallId,
|
|
2585
2715
|
messages: [],
|