@ai-sdk/harness-codex 1.0.151 → 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 +21 -0
- package/dist/bridge/index.mjs +117 -5
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/codex-app-server-driver.ts +28 -2
- package/src/bridge/codex-step-tracker.ts +40 -0
- package/src/codex-harness.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
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
|
+
|
|
13
|
+
## 1.0.152
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [6617f11]
|
|
18
|
+
- Updated dependencies [aae69cb]
|
|
19
|
+
- Updated dependencies [a7a66f5]
|
|
20
|
+
- Updated dependencies [52e3f53]
|
|
21
|
+
- Updated dependencies [6c0295c]
|
|
22
|
+
- @ai-sdk/harness@1.0.150
|
|
23
|
+
|
|
3
24
|
## 1.0.151
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -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
|
|
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)
|
|
1686
|
-
|
|
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
|
}
|