@ai-sdk/harness-pi 1.0.73 → 1.0.75
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 +16 -0
- package/dist/index.js +435 -110
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/pi-session.ts +514 -100
- package/src/pi-workspace-mirror.ts +132 -41
package/dist/index.js
CHANGED
|
@@ -116,13 +116,16 @@ import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
|
116
116
|
import { tmpdir } from "os";
|
|
117
117
|
import path7 from "path";
|
|
118
118
|
import { Type as Type2 } from "typebox";
|
|
119
|
+
import {
|
|
120
|
+
HarnessCapabilityUnsupportedError as HarnessCapabilityUnsupportedError2
|
|
121
|
+
} from "@ai-sdk/harness";
|
|
119
122
|
import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
|
|
120
123
|
|
|
121
124
|
// src/pi-auth.ts
|
|
122
125
|
import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
|
|
123
126
|
|
|
124
127
|
// src/version.ts
|
|
125
|
-
var VERSION = true ? "1.0.
|
|
128
|
+
var VERSION = true ? "1.0.75" : "0.0.0-test";
|
|
126
129
|
|
|
127
130
|
// src/pi-auth.ts
|
|
128
131
|
var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
@@ -1557,34 +1560,113 @@ function normalizeRelativePath(inputPath) {
|
|
|
1557
1560
|
}
|
|
1558
1561
|
async function readCommandOutput(sandbox, command) {
|
|
1559
1562
|
const result = await sandbox.run({ command });
|
|
1560
|
-
const output = result.stdout || result.stderr;
|
|
1561
1563
|
if (result.exitCode != null && result.exitCode !== 0) {
|
|
1562
1564
|
throw new Error(
|
|
1563
|
-
|
|
1565
|
+
result.stderr || result.stdout || `Sandbox command failed with exit code ${result.exitCode}`
|
|
1564
1566
|
);
|
|
1565
1567
|
}
|
|
1566
|
-
return
|
|
1568
|
+
return result.stdout || result.stderr;
|
|
1567
1569
|
}
|
|
1568
1570
|
async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
|
|
1569
|
-
const
|
|
1570
|
-
(
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
(dir) => ` if [ -d ./${dir} ]; then find -L ./${dir} \\( -type d -o -type f \\) -print0; fi;`
|
|
1574
|
-
);
|
|
1571
|
+
const scopedPaths = [
|
|
1572
|
+
...PI_CONFIG_DIRS.map((dir) => `./${dir}`),
|
|
1573
|
+
...PI_CONTEXT_FILENAMES.map((name) => `./${name}`)
|
|
1574
|
+
];
|
|
1575
1575
|
const listCommand = [
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
"
|
|
1580
|
-
|
|
1581
|
-
"
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1576
|
+
`pi_config_sources=(${scopedPaths.map(
|
|
1577
|
+
(scopedPath) => shellQuote2(path6.posix.join(sandboxWorkDir, scopedPath.slice(2)))
|
|
1578
|
+
).join(" ")})`,
|
|
1579
|
+
`pi_config_relatives=(${scopedPaths.map((scopedPath) => shellQuote2(scopedPath.slice(2))).join(" ")})`,
|
|
1580
|
+
`pi_config_ancestors=(${scopedPaths.map(() => "''").join(" ")})`,
|
|
1581
|
+
`pi_config_resolve_ancestors=(${scopedPaths.map(() => "1").join(" ")})`,
|
|
1582
|
+
"pi_config_index=0",
|
|
1583
|
+
'while [ "$pi_config_index" -lt "${#pi_config_sources[@]}" ]; do',
|
|
1584
|
+
" source=${pi_config_sources[$pi_config_index]}",
|
|
1585
|
+
" relative=${pi_config_relatives[$pi_config_index]}",
|
|
1586
|
+
" ancestors=${pi_config_ancestors[$pi_config_index]}",
|
|
1587
|
+
" resolve_ancestors=${pi_config_resolve_ancestors[$pi_config_index]}",
|
|
1588
|
+
" pi_config_index=$((pi_config_index + 1))",
|
|
1589
|
+
' if [ "$resolve_ancestors" = 1 ] || [ -L "$source" ]; then',
|
|
1590
|
+
" pending=$source",
|
|
1591
|
+
" resolved=",
|
|
1592
|
+
" seen_links=",
|
|
1593
|
+
' case "$pending" in',
|
|
1594
|
+
" /*) ;;",
|
|
1595
|
+
" *) pending=$PWD/$pending ;;",
|
|
1596
|
+
" esac",
|
|
1597
|
+
' while [ -n "$pending" ]; do',
|
|
1598
|
+
" pending=${pending#/}",
|
|
1599
|
+
' [ -n "$pending" ] || break',
|
|
1600
|
+
" component=${pending%%/*}",
|
|
1601
|
+
' if [ "$pending" = "$component" ]; then',
|
|
1602
|
+
" pending=",
|
|
1603
|
+
" else",
|
|
1604
|
+
" pending=${pending#*/}",
|
|
1605
|
+
" fi",
|
|
1606
|
+
' case "$component" in',
|
|
1607
|
+
' ""|.) continue ;;',
|
|
1608
|
+
" ..)",
|
|
1609
|
+
" resolved=${resolved%/*}",
|
|
1610
|
+
" continue",
|
|
1611
|
+
" ;;",
|
|
1612
|
+
" esac",
|
|
1613
|
+
" candidate=$resolved/$component",
|
|
1614
|
+
' if [ -L "$candidate" ]; then',
|
|
1615
|
+
' case "$seen_links" in',
|
|
1616
|
+
' *"',
|
|
1617
|
+
'"$candidate"',
|
|
1618
|
+
'"*)',
|
|
1619
|
+
` printf 'Pi config traversal encountered a symlink cycle at %s\\n' "$candidate" >&2`,
|
|
1620
|
+
" exit 1",
|
|
1621
|
+
" ;;",
|
|
1622
|
+
" esac",
|
|
1623
|
+
' seen_links=$seen_links"',
|
|
1624
|
+
'"$candidate"',
|
|
1625
|
+
'"',
|
|
1626
|
+
' target=$(readlink "$candidate") || exit $?',
|
|
1627
|
+
' case "$target" in',
|
|
1628
|
+
" /*) pending=$target${pending:+/$pending} ;;",
|
|
1629
|
+
" *) pending=${candidate%/*}/$target${pending:+/$pending} ;;",
|
|
1630
|
+
" esac",
|
|
1631
|
+
" resolved=",
|
|
1632
|
+
" else",
|
|
1633
|
+
" resolved=$candidate",
|
|
1634
|
+
" fi",
|
|
1635
|
+
" done",
|
|
1636
|
+
' if [ -z "$resolved" ]; then',
|
|
1637
|
+
` printf 'Pi config traversal resolved %s to the filesystem root; skipping\\n' "$relative" >&2`,
|
|
1638
|
+
" continue",
|
|
1639
|
+
" fi",
|
|
1640
|
+
" source=$resolved",
|
|
1586
1641
|
" fi",
|
|
1587
|
-
|
|
1642
|
+
' if [ -d "$source" ]; then',
|
|
1643
|
+
' case "$ancestors" in',
|
|
1644
|
+
' *"',
|
|
1645
|
+
'"$source"',
|
|
1646
|
+
'"*)',
|
|
1647
|
+
` printf 'Pi config traversal encountered a symlink cycle at %s\\n' "$relative" >&2`,
|
|
1648
|
+
" exit 1",
|
|
1649
|
+
" ;;",
|
|
1650
|
+
" esac",
|
|
1651
|
+
' ancestors=$ancestors"',
|
|
1652
|
+
'"$source"',
|
|
1653
|
+
'"',
|
|
1654
|
+
` printf 'd\\t%s\\n' "$relative"`,
|
|
1655
|
+
' for child in "$source"/* "$source"/.[!.]* "$source"/..?*; do',
|
|
1656
|
+
' if [ -L "$child" ] || [ -d "$child" ] || [ -f "$child" ]; then',
|
|
1657
|
+
" child_name=${child##*/}",
|
|
1658
|
+
' pi_config_sources+=("$child")',
|
|
1659
|
+
' pi_config_relatives+=("$relative/$child_name")',
|
|
1660
|
+
' pi_config_ancestors+=("$ancestors")',
|
|
1661
|
+
" pi_config_resolve_ancestors+=(0)",
|
|
1662
|
+
" fi",
|
|
1663
|
+
" done",
|
|
1664
|
+
' elif [ -f "$source" ]; then',
|
|
1665
|
+
` printf 'f\\t%s\\t%s\\n' "$relative" "$source"`,
|
|
1666
|
+
" fi",
|
|
1667
|
+
" true",
|
|
1668
|
+
"done",
|
|
1669
|
+
"true"
|
|
1588
1670
|
].join("\n");
|
|
1589
1671
|
const output = await readCommandOutput(
|
|
1590
1672
|
sandbox,
|
|
@@ -1593,11 +1675,13 @@ async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
|
|
|
1593
1675
|
const directories = [];
|
|
1594
1676
|
const files = [];
|
|
1595
1677
|
for (const line of output.split("\n").filter(Boolean)) {
|
|
1596
|
-
const [kind, rawPath] = line.split(" ",
|
|
1678
|
+
const [kind, rawPath, sandboxPath] = line.split(" ", 3);
|
|
1597
1679
|
if (!rawPath) continue;
|
|
1598
1680
|
const relativePath = normalizeRelativePath(rawPath);
|
|
1599
1681
|
if (kind === "d") directories.push(relativePath);
|
|
1600
|
-
else if (kind === "f")
|
|
1682
|
+
else if (kind === "f" && sandboxPath) {
|
|
1683
|
+
files.push({ relativePath, sandboxPath });
|
|
1684
|
+
}
|
|
1601
1685
|
}
|
|
1602
1686
|
return { directories, files };
|
|
1603
1687
|
}
|
|
@@ -1648,7 +1732,7 @@ function buildRequiredDirectories(remoteDirectories, remoteFiles) {
|
|
|
1648
1732
|
directories.add(normalizeRelativePath(directory));
|
|
1649
1733
|
}
|
|
1650
1734
|
for (const file of remoteFiles) {
|
|
1651
|
-
let current = path6.dirname(normalizeRelativePath(file));
|
|
1735
|
+
let current = path6.dirname(normalizeRelativePath(file.relativePath));
|
|
1652
1736
|
while (current !== "." && current !== path6.sep && current.length > 0) {
|
|
1653
1737
|
directories.add(current);
|
|
1654
1738
|
current = path6.dirname(current);
|
|
@@ -1663,7 +1747,9 @@ async function syncHostWorkspaceFromSandbox(args) {
|
|
|
1663
1747
|
sandboxWorkDir
|
|
1664
1748
|
);
|
|
1665
1749
|
const hostEntries = await collectHostScopedEntries(hostWorkDir);
|
|
1666
|
-
const remoteFiles = new Set(
|
|
1750
|
+
const remoteFiles = new Set(
|
|
1751
|
+
remoteEntries.files.map((file) => file.relativePath)
|
|
1752
|
+
);
|
|
1667
1753
|
const requiredDirectories = buildRequiredDirectories(
|
|
1668
1754
|
remoteEntries.directories,
|
|
1669
1755
|
remoteEntries.files
|
|
@@ -1685,15 +1771,11 @@ async function syncHostWorkspaceFromSandbox(args) {
|
|
|
1685
1771
|
)) {
|
|
1686
1772
|
await mkdir2(path6.join(hostWorkDir, relativePath), { recursive: true });
|
|
1687
1773
|
}
|
|
1688
|
-
for (const relativePath of remoteEntries.files) {
|
|
1689
|
-
const
|
|
1690
|
-
sandboxWorkDir,
|
|
1691
|
-
relativePath.split(path6.sep).join("/")
|
|
1692
|
-
);
|
|
1693
|
-
const bytes = await sandbox.readBinaryFile({ path: remotePath });
|
|
1774
|
+
for (const { relativePath, sandboxPath } of remoteEntries.files) {
|
|
1775
|
+
const bytes = await sandbox.readBinaryFile({ path: sandboxPath });
|
|
1694
1776
|
if (!bytes) {
|
|
1695
1777
|
throw new Error(
|
|
1696
|
-
`Sandbox workspace file disappeared during mirror sync: ${
|
|
1778
|
+
`Sandbox workspace file disappeared during mirror sync: ${sandboxPath}`
|
|
1697
1779
|
);
|
|
1698
1780
|
}
|
|
1699
1781
|
const content = Buffer.from(bytes);
|
|
@@ -1973,6 +2055,9 @@ async function createPiSession(input) {
|
|
|
1973
2055
|
let suspending = false;
|
|
1974
2056
|
const pendingToolResults = /* @__PURE__ */ new Map();
|
|
1975
2057
|
const pendingToolApprovals = /* @__PURE__ */ new Map();
|
|
2058
|
+
const deliveredDanglingResults = /* @__PURE__ */ new Map();
|
|
2059
|
+
let restoredSessionManager;
|
|
2060
|
+
let deferredRerun;
|
|
1976
2061
|
let currentEmit;
|
|
1977
2062
|
let translatorState;
|
|
1978
2063
|
let activeTurn;
|
|
@@ -2011,15 +2096,161 @@ async function createPiSession(input) {
|
|
|
2011
2096
|
sessionFileName
|
|
2012
2097
|
});
|
|
2013
2098
|
}
|
|
2014
|
-
function
|
|
2015
|
-
|
|
2016
|
-
|
|
2099
|
+
function getRestoredSessionManager() {
|
|
2100
|
+
if (resumeSessionFilePath == null) return void 0;
|
|
2101
|
+
restoredSessionManager ??= SessionManager.open(
|
|
2102
|
+
resumeSessionFilePath,
|
|
2103
|
+
hostSessionDir,
|
|
2104
|
+
sessionWorkDir
|
|
2105
|
+
);
|
|
2106
|
+
return restoredSessionManager;
|
|
2107
|
+
}
|
|
2108
|
+
function findDanglingHostToolCalls(userTools) {
|
|
2109
|
+
if (piSession != null || resumeSessionFilePath == null) return [];
|
|
2110
|
+
const hostToolNames = new Set(userTools.map((tool2) => tool2.name));
|
|
2111
|
+
if (hostToolNames.size === 0) return [];
|
|
2112
|
+
const journal = getRestoredSessionManager();
|
|
2113
|
+
if (journal == null) return [];
|
|
2114
|
+
const messages = journal.buildSessionContext().messages;
|
|
2115
|
+
const resolvedToolCallIds = new Set(
|
|
2116
|
+
deliveredDanglingResults.keys()
|
|
2117
|
+
);
|
|
2118
|
+
for (const message of messages) {
|
|
2119
|
+
if (message.role === "toolResult") {
|
|
2120
|
+
resolvedToolCallIds.add(message.toolCallId);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
const dangling = [];
|
|
2124
|
+
for (const message of messages) {
|
|
2125
|
+
if (message.role !== "assistant") continue;
|
|
2126
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
for (const block of message.content) {
|
|
2130
|
+
if (block.type === "toolCall" && hostToolNames.has(block.name) && !resolvedToolCallIds.has(block.id)) {
|
|
2131
|
+
dangling.push({ toolCallId: block.id, toolName: block.name });
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
return dangling;
|
|
2136
|
+
}
|
|
2137
|
+
function acceptDanglingHostToolResult(args) {
|
|
2138
|
+
const barrier = deferredRerun;
|
|
2139
|
+
const toolName = barrier?.awaiting.get(args.toolCallId);
|
|
2140
|
+
if (barrier == null || toolName == null) return;
|
|
2141
|
+
barrier.awaiting.delete(args.toolCallId);
|
|
2142
|
+
deliveredDanglingResults.set(args.toolCallId, {
|
|
2143
|
+
toolName,
|
|
2144
|
+
output: args.output,
|
|
2145
|
+
isError: args.isError ?? false
|
|
2146
|
+
});
|
|
2147
|
+
if (barrier.awaiting.size === 0) {
|
|
2148
|
+
barrier.startRerun();
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
function appendDeliveredHostToolResults() {
|
|
2152
|
+
if (deliveredDanglingResults.size === 0 || resumeSessionFilePath == null) {
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2155
|
+
const journal = getRestoredSessionManager();
|
|
2156
|
+
if (journal == null) return false;
|
|
2157
|
+
for (const [toolCallId, delivered] of deliveredDanglingResults) {
|
|
2158
|
+
journal.appendMessage({
|
|
2159
|
+
role: "toolResult",
|
|
2160
|
+
toolCallId,
|
|
2161
|
+
toolName: delivered.toolName,
|
|
2162
|
+
content: [
|
|
2163
|
+
{ type: "text", text: serializeToolOutput(delivered.output) }
|
|
2164
|
+
],
|
|
2165
|
+
isError: delivered.isError,
|
|
2166
|
+
timestamp: Date.now()
|
|
2017
2167
|
});
|
|
2168
|
+
}
|
|
2169
|
+
deliveredDanglingResults.clear();
|
|
2170
|
+
if (!sessionFileName) {
|
|
2171
|
+
sessionFileName = safePiSessionFileName(
|
|
2172
|
+
path7.basename(resumeSessionFilePath)
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
return true;
|
|
2176
|
+
}
|
|
2177
|
+
function deferRerunUntilHostToolResults(danglingCalls, continueOpts) {
|
|
2178
|
+
deferredRerun?.cancel();
|
|
2179
|
+
let resolveDone;
|
|
2180
|
+
let rejectDone;
|
|
2181
|
+
const done = new Promise((resolve, reject) => {
|
|
2182
|
+
resolveDone = resolve;
|
|
2183
|
+
rejectDone = reject;
|
|
2184
|
+
});
|
|
2185
|
+
let settled = false;
|
|
2186
|
+
const startRerun = () => {
|
|
2187
|
+
if (settled) return;
|
|
2188
|
+
settled = true;
|
|
2189
|
+
deferredRerun = void 0;
|
|
2190
|
+
void (async () => {
|
|
2191
|
+
try {
|
|
2192
|
+
const control = await runTurn({
|
|
2193
|
+
text: "",
|
|
2194
|
+
tools: continueOpts.tools ?? [],
|
|
2195
|
+
instructions: continueOpts.instructions,
|
|
2196
|
+
emit: continueOpts.emit,
|
|
2197
|
+
abortSignal: continueOpts.abortSignal
|
|
2198
|
+
});
|
|
2199
|
+
await control.done;
|
|
2200
|
+
resolveDone();
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
rejectDone(error);
|
|
2203
|
+
}
|
|
2204
|
+
})();
|
|
2018
2205
|
};
|
|
2019
|
-
|
|
2020
|
-
|
|
2206
|
+
const cancel = (reason) => {
|
|
2207
|
+
if (settled) return;
|
|
2208
|
+
settled = true;
|
|
2209
|
+
deferredRerun = void 0;
|
|
2210
|
+
if (reason == null) {
|
|
2211
|
+
resolveDone();
|
|
2212
|
+
} else {
|
|
2213
|
+
rejectDone(reason);
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
deferredRerun = {
|
|
2217
|
+
awaiting: new Map(
|
|
2218
|
+
danglingCalls.map((call) => [call.toolCallId, call.toolName])
|
|
2219
|
+
),
|
|
2220
|
+
startRerun,
|
|
2221
|
+
cancel
|
|
2222
|
+
};
|
|
2223
|
+
const abortBarrier = () => {
|
|
2224
|
+
cancel(
|
|
2225
|
+
continueOpts.abortSignal?.reason ?? new Error(
|
|
2226
|
+
"Pi turn was aborted before its host tool results were delivered."
|
|
2227
|
+
)
|
|
2228
|
+
);
|
|
2229
|
+
};
|
|
2230
|
+
if (continueOpts.abortSignal?.aborted) {
|
|
2231
|
+
abortBarrier();
|
|
2232
|
+
} else {
|
|
2233
|
+
continueOpts.abortSignal?.addEventListener("abort", abortBarrier, {
|
|
2021
2234
|
once: true
|
|
2022
2235
|
});
|
|
2236
|
+
}
|
|
2237
|
+
return createPromptControl({
|
|
2238
|
+
done,
|
|
2239
|
+
abortSignal: continueOpts.abortSignal
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
function createPromptControl(input2) {
|
|
2243
|
+
const abortHandler = () => {
|
|
2244
|
+
void input2.abort?.(input2.abortSignal?.reason);
|
|
2245
|
+
};
|
|
2246
|
+
if (input2.abortSignal) {
|
|
2247
|
+
if (input2.abortSignal.aborted) {
|
|
2248
|
+
abortHandler();
|
|
2249
|
+
} else {
|
|
2250
|
+
input2.abortSignal.addEventListener("abort", abortHandler, {
|
|
2251
|
+
once: true
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2023
2254
|
void input2.done.then(
|
|
2024
2255
|
() => {
|
|
2025
2256
|
input2.abortSignal?.removeEventListener("abort", abortHandler);
|
|
@@ -2032,7 +2263,10 @@ async function createPiSession(input) {
|
|
|
2032
2263
|
return {
|
|
2033
2264
|
async submitToolResult(args) {
|
|
2034
2265
|
const pending = pendingToolResults.get(args.toolCallId);
|
|
2035
|
-
if (!pending)
|
|
2266
|
+
if (!pending) {
|
|
2267
|
+
acceptDanglingHostToolResult(args);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2036
2270
|
pendingToolResults.delete(args.toolCallId);
|
|
2037
2271
|
translatorState?.hostToolResults.set(args.toolCallId, args.output);
|
|
2038
2272
|
pending.resolve(args.output);
|
|
@@ -2121,11 +2355,7 @@ async function createPiSession(input) {
|
|
|
2121
2355
|
}
|
|
2122
2356
|
const { customTools, builtinNames } = buildToolDefinitions(userTools);
|
|
2123
2357
|
const toolNames = customTools.map((t) => t.name);
|
|
2124
|
-
const sessionManager = isFirstBuild && resumeSessionFilePath ? SessionManager.
|
|
2125
|
-
resumeSessionFilePath,
|
|
2126
|
-
hostSessionDir,
|
|
2127
|
-
sessionWorkDir
|
|
2128
|
-
) : SessionManager.create(sessionWorkDir, hostSessionDir);
|
|
2358
|
+
const sessionManager = isFirstBuild && resumeSessionFilePath ? getRestoredSessionManager() : SessionManager.create(sessionWorkDir, hostSessionDir);
|
|
2129
2359
|
const { session } = await createAgentSession({
|
|
2130
2360
|
cwd: sessionWorkDir,
|
|
2131
2361
|
agentDir: hostAgentDir,
|
|
@@ -2170,90 +2400,137 @@ async function createPiSession(input) {
|
|
|
2170
2400
|
throw new Error("Pi session has been stopped.");
|
|
2171
2401
|
}
|
|
2172
2402
|
const userTools = turnOpts.tools;
|
|
2173
|
-
const signature = JSON.stringify(userTools.map((t) => t.name).sort());
|
|
2174
|
-
const needsRebuild = piSession == null || signature !== lastToolsSignature;
|
|
2175
|
-
let resourcesReloaded = false;
|
|
2176
|
-
if (needsRebuild) {
|
|
2177
|
-
resourcesReloaded = await rebuildPiSession(userTools, piSession == null);
|
|
2178
|
-
lastToolsSignature = signature;
|
|
2179
|
-
}
|
|
2180
|
-
if (!resourcesReloaded) {
|
|
2181
|
-
await reloadResourcesOnly();
|
|
2182
|
-
}
|
|
2183
|
-
await syncHostWorkspaceFromSandbox({
|
|
2184
|
-
sandbox,
|
|
2185
|
-
sandboxWorkDir: input.sessionWorkDir,
|
|
2186
|
-
hostWorkDir
|
|
2187
|
-
});
|
|
2188
2403
|
currentEmit = turnOpts.emit;
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
const unsubErr = session.subscribe((raw) => {
|
|
2199
|
-
const ev = parseNativeEvent(raw);
|
|
2200
|
-
if (!ev) return;
|
|
2201
|
-
const err = getPiTerminalError(ev);
|
|
2202
|
-
if (err && !terminalError) {
|
|
2203
|
-
terminalError = err;
|
|
2204
|
-
}
|
|
2404
|
+
const turnAbortController = new AbortController();
|
|
2405
|
+
const abort = async (reason) => {
|
|
2406
|
+
if (turnAbortController.signal.aborted) return;
|
|
2407
|
+
if (reason === void 0) {
|
|
2408
|
+
turnAbortController.abort();
|
|
2409
|
+
} else {
|
|
2410
|
+
turnAbortController.abort(reason);
|
|
2411
|
+
}
|
|
2412
|
+
await Promise.resolve(piSession?.abort()).catch(() => {
|
|
2205
2413
|
});
|
|
2414
|
+
};
|
|
2415
|
+
const turnPromise = (async () => {
|
|
2206
2416
|
try {
|
|
2207
|
-
await
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2417
|
+
await applySessionInstructions(turnOpts.instructions);
|
|
2418
|
+
turnAbortController.signal.throwIfAborted();
|
|
2419
|
+
const didAppendDeliveredHostToolResults = appendDeliveredHostToolResults();
|
|
2420
|
+
const signature = JSON.stringify(userTools.map((t) => t.name).sort());
|
|
2421
|
+
const needsRebuild = piSession == null || signature !== lastToolsSignature;
|
|
2422
|
+
let resourcesReloaded = false;
|
|
2423
|
+
if (needsRebuild) {
|
|
2424
|
+
resourcesReloaded = await rebuildPiSession(
|
|
2425
|
+
userTools,
|
|
2426
|
+
piSession == null
|
|
2427
|
+
);
|
|
2428
|
+
turnAbortController.signal.throwIfAborted();
|
|
2429
|
+
lastToolsSignature = signature;
|
|
2212
2430
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2431
|
+
if (!resourcesReloaded) {
|
|
2432
|
+
await reloadResourcesOnly();
|
|
2433
|
+
turnAbortController.signal.throwIfAborted();
|
|
2434
|
+
}
|
|
2435
|
+
await syncHostWorkspaceFromSandbox({
|
|
2436
|
+
sandbox,
|
|
2437
|
+
sandboxWorkDir: input.sessionWorkDir,
|
|
2438
|
+
hostWorkDir
|
|
2439
|
+
});
|
|
2440
|
+
turnAbortController.signal.throwIfAborted();
|
|
2441
|
+
translatorState = createPiTranslatorState({
|
|
2442
|
+
builtinToolNames: [...PI_NATIVE_BUILTIN_NAMES],
|
|
2443
|
+
hostToolNames: userTools.map((tool2) => tool2.name),
|
|
2444
|
+
nativeToCommon: NATIVE_TO_COMMON
|
|
2445
|
+
});
|
|
2446
|
+
currentEmit?.({ type: "stream-start" });
|
|
2447
|
+
if (didAppendDeliveredHostToolResults) {
|
|
2448
|
+
currentEmit?.({
|
|
2449
|
+
type: "finish-step",
|
|
2450
|
+
finishReason: { unified: "tool-calls", raw: void 0 },
|
|
2451
|
+
usage: {
|
|
2452
|
+
inputTokens: {
|
|
2453
|
+
total: 0,
|
|
2454
|
+
noCache: 0,
|
|
2455
|
+
cacheRead: 0,
|
|
2456
|
+
cacheWrite: 0
|
|
2457
|
+
},
|
|
2458
|
+
outputTokens: {
|
|
2459
|
+
total: 0,
|
|
2460
|
+
text: 0,
|
|
2461
|
+
reasoning: 0
|
|
2462
|
+
}
|
|
2463
|
+
},
|
|
2464
|
+
harnessMetadata: { pi: { inferredStep: true } }
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
let terminalError;
|
|
2468
|
+
const session = piSession;
|
|
2469
|
+
const unsubErr = session.subscribe((raw) => {
|
|
2470
|
+
const ev = parseNativeEvent(raw);
|
|
2471
|
+
if (!ev) return;
|
|
2472
|
+
const err = getPiTerminalError(ev);
|
|
2473
|
+
if (err && !terminalError) {
|
|
2474
|
+
terminalError = err;
|
|
2229
2475
|
}
|
|
2230
|
-
};
|
|
2231
|
-
currentEmit?.({
|
|
2232
|
-
type: "finish",
|
|
2233
|
-
finishReason,
|
|
2234
|
-
totalUsage: usage
|
|
2235
2476
|
});
|
|
2477
|
+
try {
|
|
2478
|
+
await session.prompt(turnOpts.text);
|
|
2479
|
+
if (terminalError) {
|
|
2480
|
+
if (suspending && isAbortError(terminalError)) return;
|
|
2481
|
+
currentEmit?.({ type: "error", error: new Error(terminalError) });
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
const stats = session.getSessionStats();
|
|
2485
|
+
const finishReason = {
|
|
2486
|
+
unified: "stop",
|
|
2487
|
+
raw: void 0
|
|
2488
|
+
};
|
|
2489
|
+
const usage = {
|
|
2490
|
+
inputTokens: {
|
|
2491
|
+
total: stats.tokens.input,
|
|
2492
|
+
noCache: void 0,
|
|
2493
|
+
cacheRead: stats.tokens.cacheRead,
|
|
2494
|
+
cacheWrite: stats.tokens.cacheWrite
|
|
2495
|
+
},
|
|
2496
|
+
outputTokens: {
|
|
2497
|
+
total: stats.tokens.output,
|
|
2498
|
+
text: void 0,
|
|
2499
|
+
reasoning: void 0
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
currentEmit?.({
|
|
2503
|
+
type: "finish",
|
|
2504
|
+
finishReason,
|
|
2505
|
+
totalUsage: usage
|
|
2506
|
+
});
|
|
2507
|
+
} catch (err) {
|
|
2508
|
+
if (suspending && isAbortError(err)) return;
|
|
2509
|
+
currentEmit?.({ type: "error", error: err });
|
|
2510
|
+
} finally {
|
|
2511
|
+
unsubErr();
|
|
2512
|
+
}
|
|
2236
2513
|
} catch (err) {
|
|
2237
2514
|
if (suspending && isAbortError(err)) return;
|
|
2238
|
-
|
|
2239
|
-
} finally {
|
|
2240
|
-
unsubErr();
|
|
2515
|
+
throw err;
|
|
2241
2516
|
}
|
|
2242
2517
|
})();
|
|
2243
2518
|
const activeTurnToken = {};
|
|
2244
2519
|
const done = turnPromise.finally(() => {
|
|
2245
2520
|
if (activeTurn?.token === activeTurnToken) {
|
|
2246
2521
|
activeTurn = void 0;
|
|
2522
|
+
currentEmit = void 0;
|
|
2247
2523
|
}
|
|
2248
|
-
currentEmit = void 0;
|
|
2249
2524
|
});
|
|
2250
2525
|
activeTurn = {
|
|
2251
2526
|
token: activeTurnToken,
|
|
2252
|
-
done
|
|
2527
|
+
done,
|
|
2528
|
+
abort
|
|
2253
2529
|
};
|
|
2254
2530
|
return createPromptControl({
|
|
2255
2531
|
done,
|
|
2256
|
-
abortSignal: turnOpts.abortSignal
|
|
2532
|
+
abortSignal: turnOpts.abortSignal,
|
|
2533
|
+
abort
|
|
2257
2534
|
});
|
|
2258
2535
|
}
|
|
2259
2536
|
const doStop = async () => {
|
|
@@ -2262,8 +2539,18 @@ async function createPiSession(input) {
|
|
|
2262
2539
|
}
|
|
2263
2540
|
stopped = true;
|
|
2264
2541
|
parkedPiSessions.delete(input.sessionId);
|
|
2542
|
+
deferredRerun?.cancel();
|
|
2543
|
+
const turnToStop = activeTurn;
|
|
2544
|
+
const abortingTurn = turnToStop?.abort();
|
|
2265
2545
|
settlePendingToolResults("Pi session stopped");
|
|
2266
2546
|
settlePendingToolApprovals("Pi session stopped");
|
|
2547
|
+
await abortingTurn;
|
|
2548
|
+
await turnToStop?.done.catch(() => {
|
|
2549
|
+
});
|
|
2550
|
+
try {
|
|
2551
|
+
appendDeliveredHostToolResults();
|
|
2552
|
+
} catch {
|
|
2553
|
+
}
|
|
2267
2554
|
if (sessionFileName) {
|
|
2268
2555
|
try {
|
|
2269
2556
|
await persistSessionFile();
|
|
@@ -2290,26 +2577,51 @@ async function createPiSession(input) {
|
|
|
2290
2577
|
// only resume path is restoring the session file on a fresh/snapshotted
|
|
2291
2578
|
// sandbox, i.e. `rerun`.
|
|
2292
2579
|
doPromptTurn: async (promptOpts) => {
|
|
2293
|
-
|
|
2580
|
+
if (promptOpts.responseFormat?.type === "json") {
|
|
2581
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
2582
|
+
message: "Harness 'pi' does not support structured output.",
|
|
2583
|
+
harnessId: HARNESS_ID2
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2294
2586
|
return runTurn({
|
|
2295
2587
|
text: extractUserText(promptOpts.prompt),
|
|
2296
2588
|
tools: promptOpts.tools ?? [],
|
|
2589
|
+
instructions: promptOpts.instructions,
|
|
2297
2590
|
emit: promptOpts.emit,
|
|
2298
2591
|
abortSignal: promptOpts.abortSignal
|
|
2299
2592
|
});
|
|
2300
2593
|
},
|
|
2301
2594
|
doContinueTurn: async (continueOpts) => {
|
|
2595
|
+
if (continueOpts.responseFormat?.type === "json") {
|
|
2596
|
+
throw new HarnessCapabilityUnsupportedError2({
|
|
2597
|
+
message: "Harness 'pi' does not support structured output.",
|
|
2598
|
+
harnessId: HARNESS_ID2
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2302
2601
|
if (activeTurn != null) {
|
|
2303
2602
|
currentEmit = continueOpts.emit;
|
|
2304
2603
|
return createPromptControl({
|
|
2305
2604
|
done: activeTurn.done,
|
|
2306
|
-
abortSignal: continueOpts.abortSignal
|
|
2605
|
+
abortSignal: continueOpts.abortSignal,
|
|
2606
|
+
abort: activeTurn.abort
|
|
2307
2607
|
});
|
|
2308
2608
|
}
|
|
2309
|
-
|
|
2609
|
+
if (stopped) {
|
|
2610
|
+
throw new Error("Pi session has been stopped.");
|
|
2611
|
+
}
|
|
2612
|
+
const danglingHostToolCalls = findDanglingHostToolCalls(
|
|
2613
|
+
continueOpts.tools ?? []
|
|
2614
|
+
);
|
|
2615
|
+
if (danglingHostToolCalls.length > 0) {
|
|
2616
|
+
return deferRerunUntilHostToolResults(
|
|
2617
|
+
danglingHostToolCalls,
|
|
2618
|
+
continueOpts
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2310
2621
|
return runTurn({
|
|
2311
2622
|
text: "",
|
|
2312
2623
|
tools: continueOpts.tools ?? [],
|
|
2624
|
+
instructions: continueOpts.instructions,
|
|
2313
2625
|
emit: continueOpts.emit,
|
|
2314
2626
|
abortSignal: continueOpts.abortSignal
|
|
2315
2627
|
});
|
|
@@ -2324,8 +2636,14 @@ async function createPiSession(input) {
|
|
|
2324
2636
|
if (stopped) return;
|
|
2325
2637
|
stopped = true;
|
|
2326
2638
|
parkedPiSessions.delete(input.sessionId);
|
|
2639
|
+
deferredRerun?.cancel();
|
|
2640
|
+
const turnToDestroy = activeTurn;
|
|
2641
|
+
const abortingTurn = turnToDestroy?.abort();
|
|
2327
2642
|
settlePendingToolResults("Pi session stopped");
|
|
2328
2643
|
settlePendingToolApprovals("Pi session stopped");
|
|
2644
|
+
await abortingTurn;
|
|
2645
|
+
await turnToDestroy?.done.catch(() => {
|
|
2646
|
+
});
|
|
2329
2647
|
await disposePiSession();
|
|
2330
2648
|
workspaceVfs.unmount();
|
|
2331
2649
|
await rm2(hostRoot, { recursive: true, force: true });
|
|
@@ -2369,8 +2687,15 @@ async function createPiSession(input) {
|
|
|
2369
2687
|
};
|
|
2370
2688
|
}
|
|
2371
2689
|
suspending = true;
|
|
2372
|
-
|
|
2690
|
+
const turnToSuspend = activeTurn;
|
|
2691
|
+
await turnToSuspend?.abort();
|
|
2692
|
+
deferredRerun?.cancel();
|
|
2693
|
+
await turnToSuspend?.done.catch(() => {
|
|
2373
2694
|
});
|
|
2695
|
+
try {
|
|
2696
|
+
appendDeliveredHostToolResults();
|
|
2697
|
+
} catch {
|
|
2698
|
+
}
|
|
2374
2699
|
if (sessionFileName) {
|
|
2375
2700
|
try {
|
|
2376
2701
|
await persistSessionFile();
|