@ai-sdk/harness-codex 1.0.152 → 1.0.153

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,15 @@
1
1
  # @ai-sdk/harness-codex
2
2
 
3
+ ## 1.0.153
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c65131: feat(harness): consistently support parallel and sequential tool approvals
8
+ - 1bebfcf: feat(harness): add support for tools with `toModelOutput()` transformation
9
+ - Updated dependencies [6c65131]
10
+ - Updated dependencies [1bebfcf]
11
+ - @ai-sdk/harness@1.0.151
12
+
3
13
  ## 1.0.152
4
14
 
5
15
  ### Patch Changes
@@ -8,6 +8,71 @@ import { spawn } from "node:child_process";
8
8
  import { isDeepStrictEqual } from "node:util";
9
9
  import path from "node:path";
10
10
  //#region ../harness/dist/bridge/index.js
11
+ const name = "AI_HarnessBridgeCapabilityUnsupportedError";
12
+ /**
13
+ * Signals an unsupported capability discovered inside a sandbox bridge.
14
+ * `isInstance` also recognizes the serialized error shape received by the
15
+ * host, where it can be translated to `HarnessCapabilityUnsupportedError`.
16
+ */
17
+ var HarnessBridgeCapabilityUnsupportedError = class extends Error {
18
+ constructor({ message, harnessId, cause }) {
19
+ super(message);
20
+ Object.defineProperty(this, "name", { value: name });
21
+ this.harnessId = harnessId;
22
+ this.cause = cause;
23
+ }
24
+ static isInstance(error) {
25
+ return error != null && typeof error === "object" && "name" in error && error.name === name && "message" in error && typeof error.message === "string";
26
+ }
27
+ };
28
+ function getNormalizedInlineImage(part) {
29
+ if (part.type === "file" && part.mediaType.startsWith("image/") && part.mediaType !== "image/*" && part.data.type === "data" && typeof part.data.data === "string") return {
30
+ data: part.data.data,
31
+ mediaType: part.mediaType
32
+ };
33
+ }
34
+ function convertHarnessToolModelOutput({ output }) {
35
+ switch (output.type) {
36
+ case "text":
37
+ case "error-text": return {
38
+ content: [{
39
+ type: "text",
40
+ text: output.value
41
+ }],
42
+ isError: output.type === "error-text"
43
+ };
44
+ case "json":
45
+ case "error-json": return {
46
+ content: [{
47
+ type: "text",
48
+ text: JSON.stringify(output.value)
49
+ }],
50
+ isError: output.type === "error-json"
51
+ };
52
+ case "execution-denied": return {
53
+ content: [{
54
+ type: "text",
55
+ text: output.reason ?? "Tool execution denied."
56
+ }],
57
+ isError: true
58
+ };
59
+ case "content": return {
60
+ content: output.value.map((part) => {
61
+ if (part.type === "text") return {
62
+ type: "text",
63
+ text: part.text
64
+ };
65
+ const image = getNormalizedInlineImage(part);
66
+ if (image != null) return {
67
+ type: "image",
68
+ ...image
69
+ };
70
+ throw new HarnessBridgeCapabilityUnsupportedError({ message: `Harnesses support only text and inline images in tool model output; unsupported content '${part.type}'.` });
71
+ }),
72
+ isError: false
73
+ };
74
+ }
75
+ }
11
76
  const DEBUG_LEVEL_WEIGHT = {
12
77
  error: 0,
13
78
  warn: 1,
@@ -1352,7 +1417,8 @@ function createCodexAppServerRuntime() {
1352
1417
  request,
1353
1418
  dynamicTools: active.dynamicTools,
1354
1419
  emit: active.options.emit,
1355
- requestToolResult: active.options.turn.requestToolResult
1420
+ requestToolResult: active.options.turn.requestToolResult,
1421
+ stepTracker: active.options.stepTracker
1356
1422
  }).catch((error) => {
1357
1423
  active.fail(error);
1358
1424
  throw error;
@@ -1607,7 +1673,7 @@ function createDynamicTools(tools) {
1607
1673
  function isValidDynamicToolName(name) {
1608
1674
  return name.length > 0 && name.length <= 128 && /^[a-zA-Z0-9_-]+$/.test(name) && name !== "mcp" && !name.startsWith("mcp__");
1609
1675
  }
1610
- async function handleAppServerRequest({ request, dynamicTools, emit, requestToolResult }) {
1676
+ async function handleAppServerRequest({ request, dynamicTools, emit, requestToolResult, stepTracker }) {
1611
1677
  if (request.method !== "item/tool/call") throw new Error(`Codex app-server requested unsupported method '${request.method}'.`);
1612
1678
  const params = asRecord(request.params);
1613
1679
  if (params == null || typeof params.callId !== "string" || typeof params.tool !== "string") throw new Error("Codex app-server sent an invalid dynamic tool request.");
@@ -1620,7 +1686,9 @@ async function handleAppServerRequest({ request, dynamicTools, emit, requestTool
1620
1686
  input: JSON.stringify(params.arguments ?? {}),
1621
1687
  providerExecuted: false
1622
1688
  });
1623
- const result = await requestToolResult(params.callId);
1689
+ const callId = params.callId;
1690
+ stepTracker.awaitHostInput({ itemId: callId });
1691
+ const result = await requestToolResult(callId).finally(() => stepTracker.resolveHostInput({ itemId: callId }));
1624
1692
  emit({
1625
1693
  type: "tool-result",
1626
1694
  toolCallId: params.callId,
@@ -1628,6 +1696,19 @@ async function handleAppServerRequest({ request, dynamicTools, emit, requestTool
1628
1696
  result: result.output ?? null,
1629
1697
  isError: result.isError === true
1630
1698
  });
1699
+ if (result.toolResult != null) {
1700
+ const converted = convertHarnessToolModelOutput({ output: result.toolResult.output });
1701
+ return {
1702
+ contentItems: converted.content.map((part) => part.type === "text" ? {
1703
+ type: "inputText",
1704
+ text: part.text
1705
+ } : {
1706
+ type: "inputImage",
1707
+ imageUrl: `data:${part.mediaType};base64,${part.data}`
1708
+ }),
1709
+ success: result.isError !== true && !converted.isError
1710
+ };
1711
+ }
1631
1712
  return {
1632
1713
  contentItems: [{
1633
1714
  type: "inputText",
@@ -1663,6 +1744,8 @@ function asRecord(value) {
1663
1744
  function createCodexStepTracker(input) {
1664
1745
  let stepOpen = false;
1665
1746
  const pendingToolItemIds = /* @__PURE__ */ new Set();
1747
+ const hostInputWaitingItemIds = /* @__PURE__ */ new Set();
1748
+ const earlyFinishStepItemIds = /* @__PURE__ */ new Set();
1666
1749
  const finishStep = () => {
1667
1750
  if (!stepOpen || pendingToolItemIds.size > 0) return;
1668
1751
  input.send({
@@ -1675,6 +1758,23 @@ function createCodexStepTracker(input) {
1675
1758
  harnessMetadata: { codex: { inferredStep: true } }
1676
1759
  });
1677
1760
  stepOpen = false;
1761
+ earlyFinishStepItemIds.clear();
1762
+ };
1763
+ const emitEarlyFinishStepIfReady = () => {
1764
+ if (!stepOpen || pendingToolItemIds.size === 0) return;
1765
+ for (const itemId of pendingToolItemIds) if (!hostInputWaitingItemIds.has(itemId)) return;
1766
+ const uncovered = [...pendingToolItemIds].filter((itemId) => !earlyFinishStepItemIds.has(itemId));
1767
+ if (uncovered.length === 0) return;
1768
+ for (const itemId of uncovered) earlyFinishStepItemIds.add(itemId);
1769
+ input.send({
1770
+ type: "finish-step",
1771
+ finishReason: {
1772
+ unified: "tool-calls",
1773
+ raw: "host-input"
1774
+ },
1775
+ usage: defaultUsage(),
1776
+ harnessMetadata: { codex: { inferredStep: true } }
1777
+ });
1678
1778
  };
1679
1779
  return {
1680
1780
  observeEvent({ event, itemId }) {
@@ -1682,8 +1782,10 @@ function createCodexStepTracker(input) {
1682
1782
  if (!item || !isStepItem(item)) return;
1683
1783
  stepOpen = true;
1684
1784
  if (isToolStepItem(item)) {
1685
- if (event.type === "item.started" && itemId) pendingToolItemIds.add(itemId);
1686
- else if (event.type === "item.completed") {
1785
+ if (event.type === "item.started" && itemId) {
1786
+ pendingToolItemIds.add(itemId);
1787
+ emitEarlyFinishStepIfReady();
1788
+ } else if (event.type === "item.completed") {
1687
1789
  if (itemId) pendingToolItemIds.delete(itemId);
1688
1790
  finishStep();
1689
1791
  }
@@ -1691,7 +1793,17 @@ function createCodexStepTracker(input) {
1691
1793
  },
1692
1794
  finishTurn() {
1693
1795
  pendingToolItemIds.clear();
1796
+ hostInputWaitingItemIds.clear();
1694
1797
  finishStep();
1798
+ },
1799
+ awaitHostInput({ itemId }) {
1800
+ stepOpen = true;
1801
+ pendingToolItemIds.add(itemId);
1802
+ hostInputWaitingItemIds.add(itemId);
1803
+ emitEarlyFinishStepIfReady();
1804
+ },
1805
+ resolveHostInput({ itemId }) {
1806
+ hostInputWaitingItemIds.delete(itemId);
1695
1807
  }
1696
1808
  };
1697
1809
  }