@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 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
@@ -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
- const parsedInput = await safeParseJSON2({ text: rawToolCall.input });
1957
- const toolCall = {
1958
- type: "tool-call",
1959
- toolCallId: rawToolCall.toolCallId,
1960
- toolName: rawToolCall.toolName,
1961
- input: parsedInput.success ? parsedInput.value : rawToolCall.input,
1962
- ...rawToolCall.providerExecuted !== void 0 ? { providerExecuted: rawToolCall.providerExecuted } : {}
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, parsedToolCall);
2202
- result.enqueue(parsed);
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
- const parsed = await safeParseJSON2({ text: input.event.input });
2559
- const args = parsed.success ? parsed.value : input.event.input;
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: args,
2712
+ input: input.parsedToolCall.input,
2583
2713
  options: {
2584
2714
  toolCallId: input.event.toolCallId,
2585
2715
  messages: [],