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