@averyyy/pi-coding-agent 0.82.1-piclient.4 → 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 +2 -2
- package/dist/core/agent-session.d.ts +0 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -27
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/model-runtime.d.ts +0 -7
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +0 -66
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/pi-server-client.d.ts +1 -3
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +301 -55
- 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/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +0 -5
- package/dist/core/sdk.js.map +1 -1
- package/package.json +4 -4
- package/dist/core/pi-server-provider-execution.d.ts +0 -19
- package/dist/core/pi-server-provider-execution.d.ts.map +0 -1
- package/dist/core/pi-server-provider-execution.js +0 -43
- package/dist/core/pi-server-provider-execution.js.map +0 -1
- package/dist/core/provider-execution-contract.d.ts +0 -36
- package/dist/core/provider-execution-contract.d.ts.map +0 -1
- package/dist/core/provider-execution-contract.js +0 -30
- package/dist/core/provider-execution-contract.js.map +0 -1
|
@@ -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
|
}
|
|
@@ -186,11 +186,6 @@ function isObject(value) {
|
|
|
186
186
|
function isNonNegativeInteger(value) {
|
|
187
187
|
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
188
188
|
}
|
|
189
|
-
function validateProviderExecutionFingerprint(value) {
|
|
190
|
-
if (value !== undefined && !/^[a-f0-9]{64}$/u.test(value)) {
|
|
191
|
-
throw new Error("pi-server provider execution fingerprint must be a lowercase SHA-256 digest");
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
189
|
export function getPiServerRunDiagnostic(message) {
|
|
195
190
|
const diagnostics = message.diagnostics?.filter((diagnostic) => diagnostic.type === "pi_server_run") ?? [];
|
|
196
191
|
if (diagnostics.length === 0)
|
|
@@ -951,7 +946,7 @@ function isRecoverableTreeDivergenceError(error) {
|
|
|
951
946
|
function isRecoverableMissingServerState(response, errorBody) {
|
|
952
947
|
if (response.status !== 400 && response.status !== 404)
|
|
953
948
|
return false;
|
|
954
|
-
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);
|
|
955
950
|
}
|
|
956
951
|
async function syncFullPiServerTree(sessionId, tree, request) {
|
|
957
952
|
await postTreeJson(request, "/api/session/tree/sync", {
|
|
@@ -1050,7 +1045,6 @@ export function hashPiServerCompactRequest(body) {
|
|
|
1050
1045
|
const serialized = canonicalJsonStringify({
|
|
1051
1046
|
protocolVersion: body.protocolVersion,
|
|
1052
1047
|
sessionId: body.sessionId,
|
|
1053
|
-
providerExecutionFingerprint: body.providerExecutionFingerprint,
|
|
1054
1048
|
model: body.model,
|
|
1055
1049
|
options: body.options,
|
|
1056
1050
|
settings: body.settings,
|
|
@@ -1368,7 +1362,6 @@ async function persistAndAcknowledgePiServerCompactTerminal(payload, compactStat
|
|
|
1368
1362
|
await acknowledgeObservedPiServerCompactTerminal(pending, compactStatePath, signal);
|
|
1369
1363
|
}
|
|
1370
1364
|
export async function compactPiServer(model, context, options) {
|
|
1371
|
-
validateProviderExecutionFingerprint(options?.providerExecutionFingerprint);
|
|
1372
1365
|
const sessionId = options?.sessionId ?? "default";
|
|
1373
1366
|
const signal = options?.signal;
|
|
1374
1367
|
const recoveryWindowMs = getPiServerCompactRecoveryWindowMs(options?.piServerRecoveryWindowMs);
|
|
@@ -1381,6 +1374,7 @@ export async function compactPiServer(model, context, options) {
|
|
|
1381
1374
|
const recovered = await recoverPendingPiServerCompaction(sessionId, tree, compactStatePath, {
|
|
1382
1375
|
signal,
|
|
1383
1376
|
recoveryWindowMs,
|
|
1377
|
+
onHistoryReconciled: options?.onHistoryReconciled,
|
|
1384
1378
|
});
|
|
1385
1379
|
if (recovered)
|
|
1386
1380
|
return recovered;
|
|
@@ -1416,7 +1410,6 @@ export async function compactPiServer(model, context, options) {
|
|
|
1416
1410
|
protocolVersion: PI_SERVER_PROTOCOL_VERSION,
|
|
1417
1411
|
sessionId,
|
|
1418
1412
|
operationId,
|
|
1419
|
-
providerExecutionFingerprint: options?.providerExecutionFingerprint,
|
|
1420
1413
|
model,
|
|
1421
1414
|
options: serializeOptions(options),
|
|
1422
1415
|
settings: options?.settings,
|
|
@@ -1504,6 +1497,14 @@ export async function compactPiServer(model, context, options) {
|
|
|
1504
1497
|
}
|
|
1505
1498
|
return await persistAndCompletePiServerCompactTerminal(terminal, compactStatePath, signal);
|
|
1506
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
|
+
}
|
|
1507
1508
|
if (isRecoverablePiServerCompactResponse(response)) {
|
|
1508
1509
|
lastInterruption = new Error(`Server compaction failed (${failure.details})`);
|
|
1509
1510
|
recoveryDeadline ??= Date.now() + recoveryWindowMs;
|
|
@@ -1580,25 +1581,20 @@ export async function compactPiServer(model, context, options) {
|
|
|
1580
1581
|
signal?.removeEventListener("abort", onAbort);
|
|
1581
1582
|
}
|
|
1582
1583
|
}
|
|
1583
|
-
function
|
|
1584
|
-
if (pending.
|
|
1585
|
-
pending.
|
|
1586
|
-
pending.baseEntryCount > tree.entries.length) {
|
|
1587
|
-
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`);
|
|
1588
1587
|
}
|
|
1588
|
+
if (pending.baseEntryCount > tree.entries.length)
|
|
1589
|
+
return undefined;
|
|
1589
1590
|
const baseEntries = tree.entries.slice(0, pending.baseEntryCount);
|
|
1590
1591
|
if (hashEntries(baseEntries) !== pending.baseTreeHash ||
|
|
1591
1592
|
(pending.baseLeafId !== null && !baseEntries.some((entry) => entry.id === pending.baseLeafId))) {
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1594
|
-
if (!pending.observation || pending.observation.kind === "terminal") {
|
|
1595
|
-
if (tree.entries.length !== pending.baseEntryCount || tree.leafId !== pending.baseLeafId) {
|
|
1596
|
-
throw new Error(`Cannot safely resume pending pi-server compaction ${pending.operationId}: local tree changed before a committed compaction was observed`);
|
|
1597
|
-
}
|
|
1593
|
+
return undefined;
|
|
1598
1594
|
}
|
|
1599
1595
|
return { entries: baseEntries, leafId: pending.baseLeafId };
|
|
1600
1596
|
}
|
|
1601
|
-
function
|
|
1597
|
+
function tryRecoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compactStatePath) {
|
|
1602
1598
|
const applied = pending.observation;
|
|
1603
1599
|
if (!applied || applied.kind !== "applied") {
|
|
1604
1600
|
throw new Error("Pending pi-server compaction does not contain a durable local apply observation");
|
|
@@ -1612,7 +1608,7 @@ function recoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compac
|
|
|
1612
1608
|
applied.updatedLeafId !== applied.entryId ||
|
|
1613
1609
|
hashEntries(tree.entries) !== applied.updatedTreeHash ||
|
|
1614
1610
|
applied.updatedRevision !== pending.baseRevision + 1) {
|
|
1615
|
-
|
|
1611
|
+
return undefined;
|
|
1616
1612
|
}
|
|
1617
1613
|
return normalizePiServerCompactionResult({
|
|
1618
1614
|
protocolVersion: PI_SERVER_PROTOCOL_VERSION,
|
|
@@ -1643,10 +1639,22 @@ export async function recoverPendingPiServerCompaction(sessionId, tree, compactS
|
|
|
1643
1639
|
const pending = readPiServerPendingCompact(compactStatePath);
|
|
1644
1640
|
if (!pending)
|
|
1645
1641
|
return undefined;
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
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)
|
|
1649
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);
|
|
1650
1658
|
if (pending.observation?.kind === "terminal" && pending.observation.operationDisposition === "not_started") {
|
|
1651
1659
|
return await persistAndCompletePiServerCompactTerminal({
|
|
1652
1660
|
protocolVersion: PI_SERVER_PROTOCOL_VERSION,
|
|
@@ -1704,8 +1712,15 @@ export async function recoverPendingPiServerCompaction(sessionId, tree, compactS
|
|
|
1704
1712
|
await acknowledgeObservedPiServerCompactTerminal(pending, compactStatePath, options?.signal);
|
|
1705
1713
|
throw new PiServerCompactOperationError(`Pending server compaction failed (${pending.observation.error})`, pending.observation);
|
|
1706
1714
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
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;
|
|
1709
1724
|
}
|
|
1710
1725
|
if (!isRecoverablePiServerCompactResponse(response)) {
|
|
1711
1726
|
throw new Error(`Pending server compaction recovery failed (${failure.details})`);
|
|
@@ -1716,6 +1731,20 @@ export async function recoverPendingPiServerCompaction(sessionId, tree, compactS
|
|
|
1716
1731
|
}
|
|
1717
1732
|
try {
|
|
1718
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
|
+
}
|
|
1719
1748
|
const result = normalizePiServerCompactionResult(responseResult, sessionId, pending.operationId, pending.requestHash, baseTree, {
|
|
1720
1749
|
staticContextHash: pending.baseStaticContextHash,
|
|
1721
1750
|
treeHash: pending.baseTreeHash,
|
|
@@ -1839,34 +1868,30 @@ function findPendingPiServerRunTerminal(entries, pending) {
|
|
|
1839
1868
|
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
1840
1869
|
continue;
|
|
1841
1870
|
const message = entry.message;
|
|
1842
|
-
|
|
1871
|
+
let diagnostic;
|
|
1872
|
+
try {
|
|
1873
|
+
diagnostic = getPiServerRunDiagnostic(message);
|
|
1874
|
+
}
|
|
1875
|
+
catch {
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1843
1878
|
if (!diagnostic || diagnostic.runId !== pending.runId)
|
|
1844
1879
|
continue;
|
|
1845
1880
|
if (diagnostic.sessionId !== pending.sessionId || diagnostic.requestMac !== pending.requestHash) {
|
|
1846
|
-
|
|
1881
|
+
continue;
|
|
1847
1882
|
}
|
|
1848
1883
|
return message;
|
|
1849
1884
|
}
|
|
1850
1885
|
return undefined;
|
|
1851
1886
|
}
|
|
1852
|
-
function
|
|
1853
|
-
|
|
1887
|
+
function pendingPiServerRunBaseMatches(pending, serverHash, sessionId, tree) {
|
|
1888
|
+
return !(pending.serverHash !== serverHash ||
|
|
1854
1889
|
pending.sessionId !== sessionId ||
|
|
1855
|
-
pending.baseEntryCount
|
|
1890
|
+
pending.baseEntryCount !== tree.entries.length ||
|
|
1856
1891
|
hashLocalTreePrefix(sessionId, tree.entries, pending.baseEntryCount) !== pending.baseTreeHash ||
|
|
1857
|
-
pending.baseLeafId !== tree.leafId)
|
|
1858
|
-
throw new Error(`Cannot safely resume pending pi-server run ${pending.runId}: server, tree, or provider request identity changed`);
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
function missingDurablePiServerRunError(runId, cause) {
|
|
1862
|
-
const prefix = cause ? `${cause}; ` : "";
|
|
1863
|
-
return new Error(`${prefix}pi-server no longer has durable journal for pending run ${runId}. ` +
|
|
1864
|
-
"The provider outcome is unknown and the pending marker was retained. Restore the server journal, " +
|
|
1865
|
-
"or after independently verifying that the provider did not execute, start an explicit new session or fork; " +
|
|
1866
|
-
"automatic provider resubmission is disabled.");
|
|
1892
|
+
pending.baseLeafId !== tree.leafId);
|
|
1867
1893
|
}
|
|
1868
1894
|
export async function streamPiServer(model, context, options) {
|
|
1869
|
-
validateProviderExecutionFingerprint(options?.providerExecutionFingerprint);
|
|
1870
1895
|
const runMode = options?.runMode;
|
|
1871
1896
|
if (runMode === "auxiliary-transient" && options?.piServerRunStatePath) {
|
|
1872
1897
|
throw new Error("Auxiliary pi-server streams cannot use the main durable run-state path");
|
|
@@ -2143,12 +2168,11 @@ export async function streamPiServer(model, context, options) {
|
|
|
2143
2168
|
};
|
|
2144
2169
|
try {
|
|
2145
2170
|
const request = createPiServerRequest(options?.signal);
|
|
2146
|
-
await ensureSessionInit(sessionId, context, request);
|
|
2147
2171
|
const tree = options?.sessionTree ?? {
|
|
2148
2172
|
...getLinearTreeFromMessages(context.messages),
|
|
2149
2173
|
replace: true,
|
|
2150
2174
|
};
|
|
2151
|
-
|
|
2175
|
+
let syncTree = tree;
|
|
2152
2176
|
if (runStatePath) {
|
|
2153
2177
|
attachRunStateLease();
|
|
2154
2178
|
}
|
|
@@ -2157,6 +2181,9 @@ export async function streamPiServer(model, context, options) {
|
|
|
2157
2181
|
: sessionPendingRuns.get(sessionId);
|
|
2158
2182
|
unresolvedRunPending = pending !== undefined;
|
|
2159
2183
|
let treeSynced = false;
|
|
2184
|
+
if (pending && pending.serverHash !== hashPiServerIdentity(getServerUrl())) {
|
|
2185
|
+
resetSessionTracking(sessionId);
|
|
2186
|
+
}
|
|
2160
2187
|
if (pending) {
|
|
2161
2188
|
const persistedTerminal = findPendingPiServerRunTerminal(syncTree.entries, pending);
|
|
2162
2189
|
if (persistedTerminal) {
|
|
@@ -2171,12 +2198,14 @@ export async function streamPiServer(model, context, options) {
|
|
|
2171
2198
|
unresolvedRunPending = false;
|
|
2172
2199
|
}
|
|
2173
2200
|
}
|
|
2201
|
+
if (!pending) {
|
|
2202
|
+
await ensureSessionInit(sessionId, context, request);
|
|
2203
|
+
}
|
|
2174
2204
|
const buildRequestIdentity = (baseTreeHash, baseEntryCount, baseLeafId, baseStaticContextHash, baseRevision) => JSON.parse(JSON.stringify({
|
|
2175
2205
|
sessionId,
|
|
2176
2206
|
runMode,
|
|
2177
2207
|
model,
|
|
2178
2208
|
options: serializeOptions(options),
|
|
2179
|
-
providerExecutionFingerprint: options?.providerExecutionFingerprint,
|
|
2180
2209
|
ephemeralMessages: options?.ephemeralMessages,
|
|
2181
2210
|
contextOverlay: options?.contextOverlay,
|
|
2182
2211
|
baseTreeHash,
|
|
@@ -2195,14 +2224,95 @@ export async function streamPiServer(model, context, options) {
|
|
|
2195
2224
|
let resumedPendingRun = false;
|
|
2196
2225
|
let recoveredPendingRun;
|
|
2197
2226
|
if (pending) {
|
|
2198
|
-
|
|
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) {
|
|
2199
2240
|
runId = pending.runId;
|
|
2200
2241
|
phase = "provider_stream";
|
|
2201
2242
|
recoveredPendingRun = await recoverPiServerRun(sessionId, runId, request, options?.signal);
|
|
2202
2243
|
if (recoveredPendingRun.status === "not_found") {
|
|
2203
|
-
|
|
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";
|
|
2204
2265
|
}
|
|
2205
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
|
+
}
|
|
2206
2316
|
resumedPendingRun = true;
|
|
2207
2317
|
runSubmissionStarted = true;
|
|
2208
2318
|
requestHash = pending.requestHash;
|
|
@@ -2241,6 +2351,7 @@ export async function streamPiServer(model, context, options) {
|
|
|
2241
2351
|
};
|
|
2242
2352
|
if (!runStatePath)
|
|
2243
2353
|
sessionPendingRuns.set(sessionId, newPending);
|
|
2354
|
+
pending = newPending;
|
|
2244
2355
|
unresolvedRunPending = true;
|
|
2245
2356
|
}
|
|
2246
2357
|
const makeBody = (eventCursor) => ({
|
|
@@ -2372,13 +2483,147 @@ export async function streamPiServer(model, context, options) {
|
|
|
2372
2483
|
recoveredRun.status === "completed" ||
|
|
2373
2484
|
recoveredRun.status === "failed";
|
|
2374
2485
|
if (hasDurableRun && recoveredRun.requestMac !== requestHash) {
|
|
2375
|
-
|
|
2376
|
-
|
|
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";
|
|
2377
2523
|
}
|
|
2378
2524
|
if (recoveredRun.status === "not_found") {
|
|
2379
|
-
|
|
2380
|
-
await
|
|
2381
|
-
|
|
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;
|
|
2382
2627
|
}
|
|
2383
2628
|
if (recoveredRun.status === "unavailable") {
|
|
2384
2629
|
const failureMessage = interruption instanceof Error ? interruption.message : String(interruption);
|
|
@@ -2406,7 +2651,8 @@ export async function streamPiServer(model, context, options) {
|
|
|
2406
2651
|
const reconnectResponse = await getRunEvents(eventCursor);
|
|
2407
2652
|
if (!reconnectResponse.ok) {
|
|
2408
2653
|
const failure = await readPiServerFailure(reconnectResponse);
|
|
2409
|
-
if (!isTransientPiServerResponse(reconnectResponse, failure.bodyText)
|
|
2654
|
+
if (!isTransientPiServerResponse(reconnectResponse, failure.bodyText) &&
|
|
2655
|
+
!isRecoverableMissingServerState(reconnectResponse, failure.matchText)) {
|
|
2410
2656
|
await settleStreamFailure(new Error(`pi-server reconnect failed: ${failure.details}`), false);
|
|
2411
2657
|
return;
|
|
2412
2658
|
}
|