@averyyy/pi-coding-agent 0.82.1-piclient.5 → 0.82.1-piclient.6
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 +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/pi-server-client.d.ts +1 -0
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +301 -45
- package/dist/core/pi-server-client.js.map +1 -1
- package/dist/core/pi-server-compact-state.d.ts +1 -1
- package/dist/core/pi-server-compact-state.d.ts.map +1 -1
- package/dist/core/pi-server-compact-state.js +2 -1
- package/dist/core/pi-server-compact-state.js.map +1 -1
- package/dist/core/pi-server-run-state.d.ts +2 -1
- package/dist/core/pi-server-run-state.d.ts.map +1 -1
- package/dist/core/pi-server-run-state.js +5 -3
- package/dist/core/pi-server-run-state.js.map +1 -1
- package/package.json +4 -4
|
@@ -50,7 +50,7 @@ const PI_SERVER_RESPONSE_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
|
50
50
|
const PI_SERVER_ERROR_BODY_MAX_BYTES = 64 * 1024;
|
|
51
51
|
const PI_SERVER_EVENT_STREAM_MAX_LINE_BYTES = 256 * 1024 * 1024;
|
|
52
52
|
const TRANSIENT_PI_SERVER_RETRY_DELAYS_MS = [250, 750, 1500];
|
|
53
|
-
const TRANSIENT_PI_SERVER_STATUS_CODES = new Set([408, 425, 429, 502, 503, 504, 530]);
|
|
53
|
+
const TRANSIENT_PI_SERVER_STATUS_CODES = new Set([408, 425, 429, 502, 503, 504, 520, 521, 522, 523, 524, 525, 530]);
|
|
54
54
|
export function hashStaticContext(ctx) {
|
|
55
55
|
return hashPiServerStaticContext(ctx);
|
|
56
56
|
}
|
|
@@ -946,7 +946,7 @@ function isRecoverableTreeDivergenceError(error) {
|
|
|
946
946
|
function isRecoverableMissingServerState(response, errorBody) {
|
|
947
947
|
if (response.status !== 400 && response.status !== 404)
|
|
948
948
|
return false;
|
|
949
|
-
return /Session has no static context|session not found|parent entry .* does not exist|leafId .* does not exist|entry .* already exists/i.test(errorBody);
|
|
949
|
+
return /Session has no static context|session not found|(?:stream )?run not found|parent entry .* does not exist|leafId .* does not exist|entry .* already exists/i.test(errorBody);
|
|
950
950
|
}
|
|
951
951
|
async function syncFullPiServerTree(sessionId, tree, request) {
|
|
952
952
|
await postTreeJson(request, "/api/session/tree/sync", {
|
|
@@ -1374,6 +1374,7 @@ export async function compactPiServer(model, context, options) {
|
|
|
1374
1374
|
const recovered = await recoverPendingPiServerCompaction(sessionId, tree, compactStatePath, {
|
|
1375
1375
|
signal,
|
|
1376
1376
|
recoveryWindowMs,
|
|
1377
|
+
onHistoryReconciled: options?.onHistoryReconciled,
|
|
1377
1378
|
});
|
|
1378
1379
|
if (recovered)
|
|
1379
1380
|
return recovered;
|
|
@@ -1496,6 +1497,14 @@ export async function compactPiServer(model, context, options) {
|
|
|
1496
1497
|
}
|
|
1497
1498
|
return await persistAndCompletePiServerCompactTerminal(terminal, compactStatePath, signal);
|
|
1498
1499
|
}
|
|
1500
|
+
if (isRecoverableMissingServerState(response, failure.matchText)) {
|
|
1501
|
+
resetSessionTracking(sessionId);
|
|
1502
|
+
await ensureSessionInit(sessionId, context, request);
|
|
1503
|
+
await syncPiServerTreeWithRequest(sessionId, tree, request, options?.onHistoryReconciled);
|
|
1504
|
+
lastInterruption = new Error(`Server compaction state was rebuilt (${failure.details})`);
|
|
1505
|
+
recoveryDeadline ??= Date.now() + recoveryWindowMs;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1499
1508
|
if (isRecoverablePiServerCompactResponse(response)) {
|
|
1500
1509
|
lastInterruption = new Error(`Server compaction failed (${failure.details})`);
|
|
1501
1510
|
recoveryDeadline ??= Date.now() + recoveryWindowMs;
|
|
@@ -1572,25 +1581,20 @@ export async function compactPiServer(model, context, options) {
|
|
|
1572
1581
|
signal?.removeEventListener("abort", onAbort);
|
|
1573
1582
|
}
|
|
1574
1583
|
}
|
|
1575
|
-
function
|
|
1576
|
-
if (pending.
|
|
1577
|
-
pending.
|
|
1578
|
-
pending.baseEntryCount > tree.entries.length) {
|
|
1579
|
-
throw new Error(`Cannot safely resume pending pi-server compaction ${pending.operationId}: server, session, or tree identity changed`);
|
|
1584
|
+
function getPendingPiServerCompactBase(pending, sessionId, tree) {
|
|
1585
|
+
if (pending.sessionId !== sessionId) {
|
|
1586
|
+
throw new Error(`Cannot recover pending pi-server compaction ${pending.operationId}: durable marker belongs to another session`);
|
|
1580
1587
|
}
|
|
1588
|
+
if (pending.baseEntryCount > tree.entries.length)
|
|
1589
|
+
return undefined;
|
|
1581
1590
|
const baseEntries = tree.entries.slice(0, pending.baseEntryCount);
|
|
1582
1591
|
if (hashEntries(baseEntries) !== pending.baseTreeHash ||
|
|
1583
1592
|
(pending.baseLeafId !== null && !baseEntries.some((entry) => entry.id === pending.baseLeafId))) {
|
|
1584
|
-
|
|
1585
|
-
}
|
|
1586
|
-
if (!pending.observation || pending.observation.kind === "terminal") {
|
|
1587
|
-
if (tree.entries.length !== pending.baseEntryCount || tree.leafId !== pending.baseLeafId) {
|
|
1588
|
-
throw new Error(`Cannot safely resume pending pi-server compaction ${pending.operationId}: local tree changed before a committed compaction was observed`);
|
|
1589
|
-
}
|
|
1593
|
+
return undefined;
|
|
1590
1594
|
}
|
|
1591
1595
|
return { entries: baseEntries, leafId: pending.baseLeafId };
|
|
1592
1596
|
}
|
|
1593
|
-
function
|
|
1597
|
+
function tryRecoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compactStatePath) {
|
|
1594
1598
|
const applied = pending.observation;
|
|
1595
1599
|
if (!applied || applied.kind !== "applied") {
|
|
1596
1600
|
throw new Error("Pending pi-server compaction does not contain a durable local apply observation");
|
|
@@ -1604,7 +1608,7 @@ function recoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compac
|
|
|
1604
1608
|
applied.updatedLeafId !== applied.entryId ||
|
|
1605
1609
|
hashEntries(tree.entries) !== applied.updatedTreeHash ||
|
|
1606
1610
|
applied.updatedRevision !== pending.baseRevision + 1) {
|
|
1607
|
-
|
|
1611
|
+
return undefined;
|
|
1608
1612
|
}
|
|
1609
1613
|
return normalizePiServerCompactionResult({
|
|
1610
1614
|
protocolVersion: PI_SERVER_PROTOCOL_VERSION,
|
|
@@ -1635,10 +1639,22 @@ export async function recoverPendingPiServerCompaction(sessionId, tree, compactS
|
|
|
1635
1639
|
const pending = readPiServerPendingCompact(compactStatePath);
|
|
1636
1640
|
if (!pending)
|
|
1637
1641
|
return undefined;
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1642
|
+
if (pending.sessionId !== sessionId) {
|
|
1643
|
+
acknowledgePiServerPendingCompact(compactStatePath, pending.operationId, "server_authoritative_missing");
|
|
1644
|
+
return undefined;
|
|
1645
|
+
}
|
|
1646
|
+
if (pending.serverHash !== hashPiServerIdentity(getServerUrl())) {
|
|
1647
|
+
resetSessionTracking(sessionId);
|
|
1648
|
+
}
|
|
1649
|
+
let baseTree = getPendingPiServerCompactBase(pending, sessionId, tree);
|
|
1650
|
+
const locallyApplied = pending.observation?.kind === "applied" && baseTree
|
|
1651
|
+
? tryRecoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compactStatePath)
|
|
1641
1652
|
: undefined;
|
|
1653
|
+
const localIdentityMatches = pending.serverHash === hashPiServerIdentity(getServerUrl()) &&
|
|
1654
|
+
baseTree !== undefined &&
|
|
1655
|
+
(pending.observation?.kind === "applied"
|
|
1656
|
+
? locallyApplied !== undefined
|
|
1657
|
+
: tree.entries.length === pending.baseEntryCount && tree.leafId === pending.baseLeafId);
|
|
1642
1658
|
if (pending.observation?.kind === "terminal" && pending.observation.operationDisposition === "not_started") {
|
|
1643
1659
|
return await persistAndCompletePiServerCompactTerminal({
|
|
1644
1660
|
protocolVersion: PI_SERVER_PROTOCOL_VERSION,
|
|
@@ -1696,8 +1712,15 @@ export async function recoverPendingPiServerCompaction(sessionId, tree, compactS
|
|
|
1696
1712
|
await acknowledgeObservedPiServerCompactTerminal(pending, compactStatePath, options?.signal);
|
|
1697
1713
|
throw new PiServerCompactOperationError(`Pending server compaction failed (${pending.observation.error})`, pending.observation);
|
|
1698
1714
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1715
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
1716
|
+
if (snapshot) {
|
|
1717
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
resetSessionTracking(sessionId);
|
|
1721
|
+
}
|
|
1722
|
+
acknowledgePiServerPendingCompact(compactStatePath, pending.operationId, "server_authoritative_missing");
|
|
1723
|
+
return undefined;
|
|
1701
1724
|
}
|
|
1702
1725
|
if (!isRecoverablePiServerCompactResponse(response)) {
|
|
1703
1726
|
throw new Error(`Pending server compaction recovery failed (${failure.details})`);
|
|
@@ -1708,6 +1731,20 @@ export async function recoverPendingPiServerCompaction(sessionId, tree, compactS
|
|
|
1708
1731
|
}
|
|
1709
1732
|
try {
|
|
1710
1733
|
const responseResult = await readPiServerCompactResponse(response, "Pending server compaction recovery failed");
|
|
1734
|
+
if (!localIdentityMatches) {
|
|
1735
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
1736
|
+
if (!snapshot) {
|
|
1737
|
+
throw new PiServerCompactProtocolError(`Pending server compaction recovery failed (operation journal exists but authoritative session history is missing)`);
|
|
1738
|
+
}
|
|
1739
|
+
baseTree = getPendingPiServerCompactBase(pending, sessionId, snapshot);
|
|
1740
|
+
if (!baseTree) {
|
|
1741
|
+
throw new PiServerCompactProtocolError(`Pending server compaction recovery failed (authoritative history did not contain the operation base tree)`);
|
|
1742
|
+
}
|
|
1743
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
1744
|
+
}
|
|
1745
|
+
if (!baseTree) {
|
|
1746
|
+
throw new PiServerCompactProtocolError(`Pending server compaction recovery failed (local operation base tree was unavailable)`);
|
|
1747
|
+
}
|
|
1711
1748
|
const result = normalizePiServerCompactionResult(responseResult, sessionId, pending.operationId, pending.requestHash, baseTree, {
|
|
1712
1749
|
staticContextHash: pending.baseStaticContextHash,
|
|
1713
1750
|
treeHash: pending.baseTreeHash,
|
|
@@ -1831,31 +1868,28 @@ function findPendingPiServerRunTerminal(entries, pending) {
|
|
|
1831
1868
|
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
1832
1869
|
continue;
|
|
1833
1870
|
const message = entry.message;
|
|
1834
|
-
|
|
1871
|
+
let diagnostic;
|
|
1872
|
+
try {
|
|
1873
|
+
diagnostic = getPiServerRunDiagnostic(message);
|
|
1874
|
+
}
|
|
1875
|
+
catch {
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1835
1878
|
if (!diagnostic || diagnostic.runId !== pending.runId)
|
|
1836
1879
|
continue;
|
|
1837
1880
|
if (diagnostic.sessionId !== pending.sessionId || diagnostic.requestMac !== pending.requestHash) {
|
|
1838
|
-
|
|
1881
|
+
continue;
|
|
1839
1882
|
}
|
|
1840
1883
|
return message;
|
|
1841
1884
|
}
|
|
1842
1885
|
return undefined;
|
|
1843
1886
|
}
|
|
1844
|
-
function
|
|
1845
|
-
|
|
1887
|
+
function pendingPiServerRunBaseMatches(pending, serverHash, sessionId, tree) {
|
|
1888
|
+
return !(pending.serverHash !== serverHash ||
|
|
1846
1889
|
pending.sessionId !== sessionId ||
|
|
1847
|
-
pending.baseEntryCount
|
|
1890
|
+
pending.baseEntryCount !== tree.entries.length ||
|
|
1848
1891
|
hashLocalTreePrefix(sessionId, tree.entries, pending.baseEntryCount) !== pending.baseTreeHash ||
|
|
1849
|
-
pending.baseLeafId !== tree.leafId)
|
|
1850
|
-
throw new Error(`Cannot safely resume pending pi-server run ${pending.runId}: server, tree, or provider request identity changed`);
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
function missingDurablePiServerRunError(runId, cause) {
|
|
1854
|
-
const prefix = cause ? `${cause}; ` : "";
|
|
1855
|
-
return new Error(`${prefix}pi-server no longer has durable journal for pending run ${runId}. ` +
|
|
1856
|
-
"The provider outcome is unknown and the pending marker was retained. Restore the server journal, " +
|
|
1857
|
-
"or after independently verifying that the provider did not execute, start an explicit new session or fork; " +
|
|
1858
|
-
"automatic provider resubmission is disabled.");
|
|
1892
|
+
pending.baseLeafId !== tree.leafId);
|
|
1859
1893
|
}
|
|
1860
1894
|
export async function streamPiServer(model, context, options) {
|
|
1861
1895
|
const runMode = options?.runMode;
|
|
@@ -2134,12 +2168,11 @@ export async function streamPiServer(model, context, options) {
|
|
|
2134
2168
|
};
|
|
2135
2169
|
try {
|
|
2136
2170
|
const request = createPiServerRequest(options?.signal);
|
|
2137
|
-
await ensureSessionInit(sessionId, context, request);
|
|
2138
2171
|
const tree = options?.sessionTree ?? {
|
|
2139
2172
|
...getLinearTreeFromMessages(context.messages),
|
|
2140
2173
|
replace: true,
|
|
2141
2174
|
};
|
|
2142
|
-
|
|
2175
|
+
let syncTree = tree;
|
|
2143
2176
|
if (runStatePath) {
|
|
2144
2177
|
attachRunStateLease();
|
|
2145
2178
|
}
|
|
@@ -2148,6 +2181,9 @@ export async function streamPiServer(model, context, options) {
|
|
|
2148
2181
|
: sessionPendingRuns.get(sessionId);
|
|
2149
2182
|
unresolvedRunPending = pending !== undefined;
|
|
2150
2183
|
let treeSynced = false;
|
|
2184
|
+
if (pending && pending.serverHash !== hashPiServerIdentity(getServerUrl())) {
|
|
2185
|
+
resetSessionTracking(sessionId);
|
|
2186
|
+
}
|
|
2151
2187
|
if (pending) {
|
|
2152
2188
|
const persistedTerminal = findPendingPiServerRunTerminal(syncTree.entries, pending);
|
|
2153
2189
|
if (persistedTerminal) {
|
|
@@ -2162,6 +2198,9 @@ export async function streamPiServer(model, context, options) {
|
|
|
2162
2198
|
unresolvedRunPending = false;
|
|
2163
2199
|
}
|
|
2164
2200
|
}
|
|
2201
|
+
if (!pending) {
|
|
2202
|
+
await ensureSessionInit(sessionId, context, request);
|
|
2203
|
+
}
|
|
2165
2204
|
const buildRequestIdentity = (baseTreeHash, baseEntryCount, baseLeafId, baseStaticContextHash, baseRevision) => JSON.parse(JSON.stringify({
|
|
2166
2205
|
sessionId,
|
|
2167
2206
|
runMode,
|
|
@@ -2185,14 +2224,95 @@ export async function streamPiServer(model, context, options) {
|
|
|
2185
2224
|
let resumedPendingRun = false;
|
|
2186
2225
|
let recoveredPendingRun;
|
|
2187
2226
|
if (pending) {
|
|
2188
|
-
|
|
2227
|
+
if (pending.sessionId !== sessionId) {
|
|
2228
|
+
if (runStatePath) {
|
|
2229
|
+
acknowledgePiServerPendingRun(runStatePath, pending.runId, Date.now(), runStateLease, "server_authoritative_rebind");
|
|
2230
|
+
}
|
|
2231
|
+
else {
|
|
2232
|
+
sessionPendingRuns.delete(sessionId);
|
|
2233
|
+
}
|
|
2234
|
+
pending = undefined;
|
|
2235
|
+
unresolvedRunPending = false;
|
|
2236
|
+
await ensureSessionInit(sessionId, context, request);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
if (pending) {
|
|
2189
2240
|
runId = pending.runId;
|
|
2190
2241
|
phase = "provider_stream";
|
|
2191
2242
|
recoveredPendingRun = await recoverPiServerRun(sessionId, runId, request, options?.signal);
|
|
2192
2243
|
if (recoveredPendingRun.status === "not_found") {
|
|
2193
|
-
|
|
2244
|
+
phase = "history_reconcile";
|
|
2245
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
2246
|
+
if (snapshot) {
|
|
2247
|
+
syncTree = snapshot;
|
|
2248
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
2249
|
+
treeSynced = true;
|
|
2250
|
+
}
|
|
2251
|
+
else {
|
|
2252
|
+
resetSessionTracking(sessionId);
|
|
2253
|
+
}
|
|
2254
|
+
if (runStatePath) {
|
|
2255
|
+
acknowledgePiServerPendingRun(runStatePath, pending.runId, Date.now(), runStateLease, "server_authoritative_missing");
|
|
2256
|
+
}
|
|
2257
|
+
else {
|
|
2258
|
+
sessionPendingRuns.delete(sessionId);
|
|
2259
|
+
}
|
|
2260
|
+
pending = undefined;
|
|
2261
|
+
unresolvedRunPending = false;
|
|
2262
|
+
runId = randomUUID();
|
|
2263
|
+
await ensureSessionInit(sessionId, context, request);
|
|
2264
|
+
phase = "provider_stream";
|
|
2194
2265
|
}
|
|
2195
2266
|
else {
|
|
2267
|
+
if (recoveredPendingRun.status !== "unavailable" &&
|
|
2268
|
+
recoveredPendingRun.requestMac !== pending.requestHash) {
|
|
2269
|
+
phase = "history_reconcile";
|
|
2270
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
2271
|
+
if (snapshot) {
|
|
2272
|
+
syncTree = snapshot;
|
|
2273
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
2274
|
+
treeSynced = true;
|
|
2275
|
+
}
|
|
2276
|
+
const authoritativeTree = snapshot ?? syncTree;
|
|
2277
|
+
const pendingInput = {
|
|
2278
|
+
serverHash: hashPiServerIdentity(getServerUrl()),
|
|
2279
|
+
sessionId,
|
|
2280
|
+
runId: pending.runId,
|
|
2281
|
+
baseTreeHash: hashLocalTree(sessionId, authoritativeTree.entries),
|
|
2282
|
+
baseEntryCount: authoritativeTree.entries.length,
|
|
2283
|
+
baseLeafId: authoritativeTree.leafId,
|
|
2284
|
+
requestHash: recoveredPendingRun.requestMac,
|
|
2285
|
+
};
|
|
2286
|
+
if (runStatePath) {
|
|
2287
|
+
acknowledgePiServerPendingRun(runStatePath, pending.runId, Date.now(), runStateLease, "server_authoritative_rebind");
|
|
2288
|
+
pending = writePiServerPendingRun(runStatePath, pendingInput, runStateLease);
|
|
2289
|
+
}
|
|
2290
|
+
else {
|
|
2291
|
+
pending = {
|
|
2292
|
+
version: 1,
|
|
2293
|
+
kind: "run",
|
|
2294
|
+
sequence: 0,
|
|
2295
|
+
timestamp: Date.now(),
|
|
2296
|
+
...pendingInput,
|
|
2297
|
+
};
|
|
2298
|
+
sessionPendingRuns.set(sessionId, pending);
|
|
2299
|
+
}
|
|
2300
|
+
phase = "provider_stream";
|
|
2301
|
+
}
|
|
2302
|
+
if (recoveredPendingRun.status !== "unavailable" &&
|
|
2303
|
+
!pendingPiServerRunBaseMatches(pending, hashPiServerIdentity(getServerUrl()), sessionId, syncTree)) {
|
|
2304
|
+
phase = "history_reconcile";
|
|
2305
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
2306
|
+
if (!snapshot) {
|
|
2307
|
+
resetSessionTracking(sessionId);
|
|
2308
|
+
}
|
|
2309
|
+
else {
|
|
2310
|
+
syncTree = snapshot;
|
|
2311
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
2312
|
+
treeSynced = true;
|
|
2313
|
+
}
|
|
2314
|
+
phase = "provider_stream";
|
|
2315
|
+
}
|
|
2196
2316
|
resumedPendingRun = true;
|
|
2197
2317
|
runSubmissionStarted = true;
|
|
2198
2318
|
requestHash = pending.requestHash;
|
|
@@ -2231,6 +2351,7 @@ export async function streamPiServer(model, context, options) {
|
|
|
2231
2351
|
};
|
|
2232
2352
|
if (!runStatePath)
|
|
2233
2353
|
sessionPendingRuns.set(sessionId, newPending);
|
|
2354
|
+
pending = newPending;
|
|
2234
2355
|
unresolvedRunPending = true;
|
|
2235
2356
|
}
|
|
2236
2357
|
const makeBody = (eventCursor) => ({
|
|
@@ -2362,13 +2483,147 @@ export async function streamPiServer(model, context, options) {
|
|
|
2362
2483
|
recoveredRun.status === "completed" ||
|
|
2363
2484
|
recoveredRun.status === "failed";
|
|
2364
2485
|
if (hasDurableRun && recoveredRun.requestMac !== requestHash) {
|
|
2365
|
-
|
|
2366
|
-
|
|
2486
|
+
phase = "history_reconcile";
|
|
2487
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
2488
|
+
if (snapshot) {
|
|
2489
|
+
syncTree = snapshot;
|
|
2490
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
2491
|
+
treeSynced = true;
|
|
2492
|
+
}
|
|
2493
|
+
else {
|
|
2494
|
+
resetSessionTracking(sessionId);
|
|
2495
|
+
treeSynced = false;
|
|
2496
|
+
}
|
|
2497
|
+
const authoritativeTree = snapshot ?? syncTree;
|
|
2498
|
+
const reboundPendingInput = {
|
|
2499
|
+
serverHash: hashPiServerIdentity(getServerUrl()),
|
|
2500
|
+
sessionId,
|
|
2501
|
+
runId,
|
|
2502
|
+
baseTreeHash: hashLocalTree(sessionId, authoritativeTree.entries),
|
|
2503
|
+
baseEntryCount: authoritativeTree.entries.length,
|
|
2504
|
+
baseLeafId: authoritativeTree.leafId,
|
|
2505
|
+
requestHash: recoveredRun.requestMac,
|
|
2506
|
+
};
|
|
2507
|
+
if (runStatePath) {
|
|
2508
|
+
acknowledgePiServerPendingRun(runStatePath, runId, Date.now(), runStateLease, "server_authoritative_rebind");
|
|
2509
|
+
pending = writePiServerPendingRun(runStatePath, reboundPendingInput, runStateLease);
|
|
2510
|
+
}
|
|
2511
|
+
else {
|
|
2512
|
+
pending = {
|
|
2513
|
+
version: 1,
|
|
2514
|
+
kind: "run",
|
|
2515
|
+
sequence: 0,
|
|
2516
|
+
timestamp: Date.now(),
|
|
2517
|
+
...reboundPendingInput,
|
|
2518
|
+
};
|
|
2519
|
+
sessionPendingRuns.set(sessionId, pending);
|
|
2520
|
+
}
|
|
2521
|
+
requestHash = recoveredRun.requestMac;
|
|
2522
|
+
phase = "provider_stream";
|
|
2367
2523
|
}
|
|
2368
2524
|
if (recoveredRun.status === "not_found") {
|
|
2369
|
-
|
|
2370
|
-
await
|
|
2371
|
-
|
|
2525
|
+
phase = "history_reconcile";
|
|
2526
|
+
const snapshot = await fetchPiServerHistory(sessionId, request);
|
|
2527
|
+
if (snapshot) {
|
|
2528
|
+
syncTree = snapshot;
|
|
2529
|
+
await applyPiServerHistory(sessionId, snapshot, options?.onHistoryReconciled);
|
|
2530
|
+
treeSynced = true;
|
|
2531
|
+
}
|
|
2532
|
+
else {
|
|
2533
|
+
resetSessionTracking(sessionId);
|
|
2534
|
+
treeSynced = false;
|
|
2535
|
+
}
|
|
2536
|
+
if (runStatePath) {
|
|
2537
|
+
acknowledgePiServerPendingRun(runStatePath, runId, Date.now(), runStateLease, "server_authoritative_missing");
|
|
2538
|
+
}
|
|
2539
|
+
else {
|
|
2540
|
+
sessionPendingRuns.delete(sessionId);
|
|
2541
|
+
}
|
|
2542
|
+
pending = undefined;
|
|
2543
|
+
unresolvedRunPending = false;
|
|
2544
|
+
runId = randomUUID();
|
|
2545
|
+
await ensureSessionInit(sessionId, context, request);
|
|
2546
|
+
if (!treeSynced) {
|
|
2547
|
+
phase = "tree_sync";
|
|
2548
|
+
await syncPiServerTreeWithRequest(sessionId, syncTree, request, options?.onHistoryReconciled);
|
|
2549
|
+
treeSynced = true;
|
|
2550
|
+
}
|
|
2551
|
+
baseTreeHash = hashLocalTree(sessionId, syncTree.entries);
|
|
2552
|
+
baseEntryCount = syncTree.entries.length;
|
|
2553
|
+
baseLeafId = syncTree.leafId;
|
|
2554
|
+
baseStaticContextHash = sessionStaticContextHashes.get(sessionId) ?? hashStaticContext(context);
|
|
2555
|
+
baseRevision = sessionTreeRevisions.get(sessionId) ?? 0;
|
|
2556
|
+
requestIdentity = buildRequestIdentity(baseTreeHash, baseEntryCount, baseLeafId, baseStaticContextHash, baseRevision);
|
|
2557
|
+
requestHash = hashPiServerStreamRequest(requestIdentity);
|
|
2558
|
+
const replacementPendingInput = {
|
|
2559
|
+
serverHash: hashPiServerIdentity(getServerUrl()),
|
|
2560
|
+
sessionId,
|
|
2561
|
+
runId,
|
|
2562
|
+
baseTreeHash,
|
|
2563
|
+
baseEntryCount,
|
|
2564
|
+
baseLeafId,
|
|
2565
|
+
requestHash,
|
|
2566
|
+
};
|
|
2567
|
+
pending = runStatePath
|
|
2568
|
+
? writePiServerPendingRun(runStatePath, replacementPendingInput, runStateLease)
|
|
2569
|
+
: {
|
|
2570
|
+
version: 1,
|
|
2571
|
+
kind: "run",
|
|
2572
|
+
sequence: 0,
|
|
2573
|
+
timestamp: Date.now(),
|
|
2574
|
+
...replacementPendingInput,
|
|
2575
|
+
};
|
|
2576
|
+
if (!runStatePath)
|
|
2577
|
+
sessionPendingRuns.set(sessionId, pending);
|
|
2578
|
+
unresolvedRunPending = true;
|
|
2579
|
+
partial.content = [];
|
|
2580
|
+
partial.stopReason = "stop";
|
|
2581
|
+
partial.errorMessage = undefined;
|
|
2582
|
+
partial.diagnostics = undefined;
|
|
2583
|
+
partial.usage = {
|
|
2584
|
+
input: 0,
|
|
2585
|
+
output: 0,
|
|
2586
|
+
cacheRead: 0,
|
|
2587
|
+
cacheWrite: 0,
|
|
2588
|
+
totalTokens: 0,
|
|
2589
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
2590
|
+
};
|
|
2591
|
+
partial.timestamp = Date.now();
|
|
2592
|
+
receivedProxyEventCount = 0;
|
|
2593
|
+
reconnectAttempt = 0;
|
|
2594
|
+
recoveryDeadline = undefined;
|
|
2595
|
+
phase = "provider_stream";
|
|
2596
|
+
try {
|
|
2597
|
+
const replacementResponse = await postStream(0);
|
|
2598
|
+
if (replacementResponse.ok) {
|
|
2599
|
+
activeResponse = replacementResponse;
|
|
2600
|
+
activeEventCursor = 0;
|
|
2601
|
+
}
|
|
2602
|
+
else {
|
|
2603
|
+
const failure = await readPiServerFailure(replacementResponse);
|
|
2604
|
+
const retryable = isTransientPiServerResponse(replacementResponse, failure.bodyText) ||
|
|
2605
|
+
isRecoverableMissingServerState(replacementResponse, failure.matchText);
|
|
2606
|
+
if (!retryable) {
|
|
2607
|
+
await settleStreamFailure(new PiServerHttpResponseError(`pi-server replacement run failed: ${failure.details}`, replacementResponse.status, false), false);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
interruption = new PiServerHttpResponseError(`pi-server replacement run failed: ${failure.details}`, replacementResponse.status, true);
|
|
2611
|
+
activeResponse = undefined;
|
|
2612
|
+
recoveryDeadline = Date.now() + recoveryWindowMs;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
catch (error) {
|
|
2616
|
+
if (isAbortError(error, options?.signal))
|
|
2617
|
+
throw error;
|
|
2618
|
+
if (!isRetryablePiServerInterruption(error)) {
|
|
2619
|
+
await settleStreamFailure(error, false);
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
interruption = error;
|
|
2623
|
+
activeResponse = undefined;
|
|
2624
|
+
recoveryDeadline = Date.now() + recoveryWindowMs;
|
|
2625
|
+
}
|
|
2626
|
+
continue;
|
|
2372
2627
|
}
|
|
2373
2628
|
if (recoveredRun.status === "unavailable") {
|
|
2374
2629
|
const failureMessage = interruption instanceof Error ? interruption.message : String(interruption);
|
|
@@ -2396,7 +2651,8 @@ export async function streamPiServer(model, context, options) {
|
|
|
2396
2651
|
const reconnectResponse = await getRunEvents(eventCursor);
|
|
2397
2652
|
if (!reconnectResponse.ok) {
|
|
2398
2653
|
const failure = await readPiServerFailure(reconnectResponse);
|
|
2399
|
-
if (!isTransientPiServerResponse(reconnectResponse, failure.bodyText)
|
|
2654
|
+
if (!isTransientPiServerResponse(reconnectResponse, failure.bodyText) &&
|
|
2655
|
+
!isRecoverableMissingServerState(reconnectResponse, failure.matchText)) {
|
|
2400
2656
|
await settleStreamFailure(new Error(`pi-server reconnect failed: ${failure.details}`), false);
|
|
2401
2657
|
return;
|
|
2402
2658
|
}
|