@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.
@@ -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 assertPendingPiServerCompactMatches(pending, sessionId, tree) {
1576
- if (pending.serverHash !== hashPiServerIdentity(getServerUrl()) ||
1577
- pending.sessionId !== sessionId ||
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
- throw new Error(`Cannot safely resume pending pi-server compaction ${pending.operationId}: local base tree no longer matches its durable marker`);
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 recoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compactStatePath) {
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
- throw new Error(`Cannot safely resume pending pi-server compaction ${pending.operationId}: local applied tree does not match its durable observation`);
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
- const baseTree = assertPendingPiServerCompactMatches(pending, sessionId, tree);
1639
- const locallyApplied = pending.observation?.kind === "applied"
1640
- ? recoverLocallyAppliedPiServerCompaction(pending, tree, baseTree, compactStatePath)
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
- throw new Error(`Pending pi-server compaction ${pending.operationId} is missing from the durable server journal; ` +
1700
- "the local marker was retained and automatic provider resubmission is disabled");
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
- const diagnostic = getPiServerRunDiagnostic(message);
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
- throw new Error("Cannot recover pi-server run: persisted terminal diagnostic did not match pending state");
1881
+ continue;
1839
1882
  }
1840
1883
  return message;
1841
1884
  }
1842
1885
  return undefined;
1843
1886
  }
1844
- function assertPendingPiServerRunMatches(pending, serverHash, sessionId, tree) {
1845
- if (pending.serverHash !== serverHash ||
1887
+ function pendingPiServerRunBaseMatches(pending, serverHash, sessionId, tree) {
1888
+ return !(pending.serverHash !== serverHash ||
1846
1889
  pending.sessionId !== sessionId ||
1847
- pending.baseEntryCount > tree.entries.length ||
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
- const syncTree = tree;
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
- assertPendingPiServerRunMatches(pending, hashPiServerIdentity(getServerUrl()), sessionId, syncTree);
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
- throw missingDurablePiServerRunError(pending.runId);
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
- await settleStreamFailure(new Error("pi-server recovered run request identity did not match the pending provider request"), false);
2366
- return;
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
- const failureMessage = interruption instanceof Error ? interruption.message : String(interruption);
2370
- await settleStreamFailure(missingDurablePiServerRunError(runId, failureMessage), false);
2371
- return;
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
  }