opencode-copilot-account-switcher 0.11.0 → 0.12.0
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/dist/codex-auth-source.d.ts +16 -0
- package/dist/codex-auth-source.js +63 -0
- package/dist/codex-status-command.d.ts +53 -0
- package/dist/codex-status-command.js +215 -0
- package/dist/codex-status-fetcher.d.ts +66 -0
- package/dist/codex-status-fetcher.js +232 -0
- package/dist/codex-store.d.ts +25 -0
- package/dist/codex-store.js +70 -0
- package/dist/copilot-network-retry.d.ts +2 -17
- package/dist/copilot-network-retry.js +236 -322
- package/dist/copilot-retry-policy.d.ts +1 -0
- package/dist/copilot-retry-policy.js +1 -0
- package/dist/model-account-map.js +3 -1
- package/dist/network-retry-engine.d.ts +33 -0
- package/dist/network-retry-engine.js +62 -0
- package/dist/plugin-hooks.d.ts +3 -0
- package/dist/plugin-hooks.js +73 -11
- package/dist/provider-descriptor.d.ts +2 -0
- package/dist/provider-descriptor.js +1 -0
- package/dist/provider-registry.d.ts +1 -0
- package/dist/provider-registry.js +1 -0
- package/dist/providers/descriptor.d.ts +28 -0
- package/dist/providers/descriptor.js +66 -0
- package/dist/providers/registry.d.ts +18 -0
- package/dist/providers/registry.js +27 -0
- package/dist/retry/copilot-policy.d.ts +61 -0
- package/dist/retry/copilot-policy.js +240 -0
- package/dist/retry/shared-engine.d.ts +50 -0
- package/dist/retry/shared-engine.js +56 -0
- package/dist/routing-state.d.ts +1 -0
- package/dist/status-command.js +0 -21
- package/package.json +1 -1
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import { appendFileSync } from "node:fs";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"network request failed",
|
|
6
|
-
"unable to connect",
|
|
7
|
-
"econnreset",
|
|
8
|
-
"etimedout",
|
|
9
|
-
"socket hang up",
|
|
10
|
-
"unknown certificate",
|
|
11
|
-
"self signed certificate",
|
|
12
|
-
"unable to verify the first certificate",
|
|
13
|
-
"self-signed certificate in certificate chain",
|
|
14
|
-
];
|
|
15
|
-
const AI_ERROR_MARKER = Symbol.for("vercel.ai.error");
|
|
16
|
-
const API_CALL_ERROR_MARKER = Symbol.for("vercel.ai.error.AI_APICallError");
|
|
2
|
+
import { getSharedErrorMessage, noopSharedRetryNotifier, notifySharedRetryEvent, runSharedFailOpenBoundary, runSharedRetryScheduler, } from "./retry/shared-engine.js";
|
|
3
|
+
import { createNetworkRetryEngine } from "./network-retry-engine.js";
|
|
4
|
+
import { createCopilotRetryPolicy, isRetryableCopilotTransportError, } from "./retry/copilot-policy.js";
|
|
17
5
|
const INTERNAL_SESSION_HEADER = "x-opencode-session-id";
|
|
18
6
|
const INTERNAL_DEBUG_LINK_HEADER = "x-opencode-debug-link-id";
|
|
19
7
|
export const INTERNAL_SESSION_CONTEXT_KEY = "__opencodeInternalSessionID";
|
|
@@ -44,7 +32,7 @@ function isAbortError(error) {
|
|
|
44
32
|
return error instanceof Error && error.name === "AbortError";
|
|
45
33
|
}
|
|
46
34
|
function getErrorMessage(error) {
|
|
47
|
-
return
|
|
35
|
+
return getSharedErrorMessage(error);
|
|
48
36
|
}
|
|
49
37
|
function isInputIdTooLongErrorBody(payload) {
|
|
50
38
|
if (!payload || typeof payload !== "object")
|
|
@@ -422,21 +410,6 @@ function buildRetryInit(init, payload) {
|
|
|
422
410
|
body: JSON.stringify(payload),
|
|
423
411
|
};
|
|
424
412
|
}
|
|
425
|
-
const noopNotifier = {
|
|
426
|
-
started: async () => { },
|
|
427
|
-
progress: async () => { },
|
|
428
|
-
repairWarning: async () => { },
|
|
429
|
-
completed: async () => { },
|
|
430
|
-
stopped: async () => { },
|
|
431
|
-
};
|
|
432
|
-
async function notify(notifier, event, remaining) {
|
|
433
|
-
try {
|
|
434
|
-
await notifier[event]({ remaining });
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
console.warn(`[copilot-network-retry] notifier ${event} failed`, error);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
413
|
function stripInternalSessionHeaderFromRequest(request) {
|
|
441
414
|
if (!(request instanceof Request))
|
|
442
415
|
return request;
|
|
@@ -705,52 +678,21 @@ async function repairSessionParts(sessionID, itemIds, ctx) {
|
|
|
705
678
|
}
|
|
706
679
|
return patchedAll;
|
|
707
680
|
}
|
|
708
|
-
async function maybeRetryConnectionMismatchItemIds(request, init,
|
|
709
|
-
if (response.ok) {
|
|
710
|
-
return {
|
|
711
|
-
response,
|
|
712
|
-
retried: false,
|
|
713
|
-
nextInit: init,
|
|
714
|
-
nextPayload: requestPayload,
|
|
715
|
-
retryState: undefined,
|
|
716
|
-
};
|
|
717
|
-
}
|
|
681
|
+
async function maybeRetryConnectionMismatchItemIds(request, init, currentResponse, decision, baseFetch, requestPayload, ctx, sessionID, startedNotified = false) {
|
|
718
682
|
const removableIds = collectInputItemIds(requestPayload);
|
|
719
683
|
if (!requestPayload || removableIds.length === 0) {
|
|
720
684
|
return {
|
|
721
|
-
response,
|
|
685
|
+
response: currentResponse,
|
|
722
686
|
retried: false,
|
|
723
687
|
nextInit: init,
|
|
724
688
|
nextPayload: requestPayload,
|
|
725
689
|
retryState: undefined,
|
|
726
690
|
};
|
|
727
691
|
}
|
|
728
|
-
const responseText =
|
|
729
|
-
.clone()
|
|
730
|
-
.text()
|
|
731
|
-
.catch(() => "");
|
|
692
|
+
const responseText = decision.responseText;
|
|
732
693
|
if (!responseText) {
|
|
733
694
|
return {
|
|
734
|
-
response,
|
|
735
|
-
retried: false,
|
|
736
|
-
nextInit: init,
|
|
737
|
-
nextPayload: requestPayload,
|
|
738
|
-
retryState: undefined,
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
let matched = isConnectionMismatchInputIdMessage(responseText);
|
|
742
|
-
if (!matched) {
|
|
743
|
-
try {
|
|
744
|
-
const bodyPayload = JSON.parse(responseText);
|
|
745
|
-
matched = isConnectionMismatchInputIdErrorBody(bodyPayload);
|
|
746
|
-
}
|
|
747
|
-
catch {
|
|
748
|
-
matched = false;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
if (!matched) {
|
|
752
|
-
return {
|
|
753
|
-
response,
|
|
695
|
+
response: currentResponse,
|
|
754
696
|
retried: false,
|
|
755
697
|
nextInit: init,
|
|
756
698
|
nextPayload: requestPayload,
|
|
@@ -760,13 +702,23 @@ async function maybeRetryConnectionMismatchItemIds(request, init, response, base
|
|
|
760
702
|
const remainingBefore = countInputIdCandidates(requestPayload);
|
|
761
703
|
const notifiedStarted = startedNotified || remainingBefore > 0;
|
|
762
704
|
let repairFailed = false;
|
|
763
|
-
if (sessionID) {
|
|
764
|
-
|
|
705
|
+
if (decision.shouldAttemptSessionRepair && sessionID) {
|
|
706
|
+
const repairResult = await runSharedFailOpenBoundary({
|
|
707
|
+
action: () => repairSessionParts(sessionID, removableIds, ctx),
|
|
708
|
+
isFailOpenError: () => true,
|
|
709
|
+
onFailOpen: (error) => {
|
|
710
|
+
debugLog("input-id retry session bulk repair failed-open", {
|
|
711
|
+
sessionID,
|
|
712
|
+
error: String(error instanceof Error ? error.message : error),
|
|
713
|
+
});
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
repairFailed = !(repairResult.ok ? repairResult.value : false);
|
|
765
717
|
}
|
|
766
718
|
const sanitized = stripAllInputIds(requestPayload);
|
|
767
719
|
if (sanitized === requestPayload) {
|
|
768
720
|
return {
|
|
769
|
-
response,
|
|
721
|
+
response: currentResponse,
|
|
770
722
|
retried: false,
|
|
771
723
|
nextInit: init,
|
|
772
724
|
nextPayload: requestPayload,
|
|
@@ -803,20 +755,11 @@ async function maybeRetryConnectionMismatchItemIds(request, init, response, base
|
|
|
803
755
|
});
|
|
804
756
|
return { response: retried, retried: true, nextInit, nextPayload: sanitized, retryState };
|
|
805
757
|
}
|
|
806
|
-
async function maybeRetryInputIdTooLong(request, init,
|
|
807
|
-
if (response.ok) {
|
|
808
|
-
return {
|
|
809
|
-
response,
|
|
810
|
-
retried: false,
|
|
811
|
-
nextInit: init,
|
|
812
|
-
nextPayload: requestPayload,
|
|
813
|
-
retryState: undefined,
|
|
814
|
-
};
|
|
815
|
-
}
|
|
758
|
+
async function maybeRetryInputIdTooLong(request, init, currentResponse, decision, baseFetch, requestPayload, ctx, sessionID, startedNotified = false) {
|
|
816
759
|
if (!requestPayload || !hasLongInputIds(requestPayload)) {
|
|
817
760
|
debugLog("skip input-id retry: request has no long ids");
|
|
818
761
|
return {
|
|
819
|
-
response,
|
|
762
|
+
response: currentResponse,
|
|
820
763
|
retried: false,
|
|
821
764
|
nextInit: init,
|
|
822
765
|
nextPayload: requestPayload,
|
|
@@ -824,38 +767,27 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, requ
|
|
|
824
767
|
};
|
|
825
768
|
}
|
|
826
769
|
debugLog("input-id retry candidate", {
|
|
827
|
-
|
|
828
|
-
|
|
770
|
+
serverReportedIndex: decision.serverReportedIndex,
|
|
771
|
+
reportedLength: decision.reportedLength,
|
|
829
772
|
});
|
|
830
|
-
const responseText =
|
|
831
|
-
.clone()
|
|
832
|
-
.text()
|
|
833
|
-
.catch(() => "");
|
|
773
|
+
const responseText = decision.responseText;
|
|
834
774
|
if (!responseText) {
|
|
835
775
|
debugLog("skip input-id retry: empty response body");
|
|
836
776
|
return {
|
|
837
|
-
response,
|
|
777
|
+
response: currentResponse,
|
|
838
778
|
retried: false,
|
|
839
779
|
nextInit: init,
|
|
840
780
|
nextPayload: requestPayload,
|
|
841
781
|
retryState: undefined,
|
|
842
782
|
};
|
|
843
783
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const error = bodyPayload.error;
|
|
850
|
-
parsed = parseInputIdTooLongDetails(String(error?.message ?? ""));
|
|
851
|
-
matched = parsed.matched || isInputIdTooLongErrorBody(bodyPayload);
|
|
852
|
-
}
|
|
853
|
-
catch {
|
|
854
|
-
matched = false;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
784
|
+
const parsed = {
|
|
785
|
+
matched: true,
|
|
786
|
+
serverReportedIndex: decision.serverReportedIndex,
|
|
787
|
+
reportedLength: decision.reportedLength,
|
|
788
|
+
};
|
|
857
789
|
debugLog("input-id retry detection", {
|
|
858
|
-
matched,
|
|
790
|
+
matched: true,
|
|
859
791
|
serverReportedIndex: parsed.serverReportedIndex,
|
|
860
792
|
reportedLength: parsed.reportedLength,
|
|
861
793
|
bodyPreview: responseText.slice(0, 200),
|
|
@@ -864,15 +796,6 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, requ
|
|
|
864
796
|
serverReportedIndex: parsed.serverReportedIndex,
|
|
865
797
|
reportedLength: parsed.reportedLength,
|
|
866
798
|
});
|
|
867
|
-
if (!matched) {
|
|
868
|
-
return {
|
|
869
|
-
response,
|
|
870
|
-
retried: false,
|
|
871
|
-
nextInit: init,
|
|
872
|
-
nextPayload: requestPayload,
|
|
873
|
-
retryState: undefined,
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
799
|
const payloadCandidates = getPayloadCandidates(requestPayload);
|
|
877
800
|
const targetSelection = getTargetedLongInputIdSelection(requestPayload, parsed.serverReportedIndex, parsed.reportedLength);
|
|
878
801
|
debugLog("input-id retry payload candidates", {
|
|
@@ -900,7 +823,7 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, requ
|
|
|
900
823
|
reportedLengthMatched: targetSelection.reportedLengthMatched,
|
|
901
824
|
});
|
|
902
825
|
return {
|
|
903
|
-
response,
|
|
826
|
+
response: currentResponse,
|
|
904
827
|
retried: false,
|
|
905
828
|
nextInit: init,
|
|
906
829
|
nextPayload: requestPayload,
|
|
@@ -918,14 +841,24 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, requ
|
|
|
918
841
|
}
|
|
919
842
|
const notifiedStarted = startedNotified || remainingBefore > 0;
|
|
920
843
|
let repairFailed = false;
|
|
921
|
-
if (sessionID && failingId) {
|
|
922
|
-
|
|
844
|
+
if (decision.shouldAttemptSessionRepair && sessionID && failingId) {
|
|
845
|
+
const repairResult = await runSharedFailOpenBoundary({
|
|
846
|
+
action: () => repairSessionPart(sessionID, failingId, ctx),
|
|
847
|
+
isFailOpenError: () => true,
|
|
848
|
+
onFailOpen: (error) => {
|
|
849
|
+
debugLog("input-id retry session repair failed-open", {
|
|
850
|
+
sessionID,
|
|
851
|
+
error: String(error instanceof Error ? error.message : error),
|
|
852
|
+
});
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
repairFailed = !(repairResult.ok ? repairResult.value : false);
|
|
923
856
|
}
|
|
924
857
|
const sanitized = stripTargetedLongInputId(requestPayload, parsed.serverReportedIndex, parsed.reportedLength);
|
|
925
858
|
if (sanitized === requestPayload) {
|
|
926
859
|
debugLog("skip input-id retry: sanitize made no changes");
|
|
927
860
|
return {
|
|
928
|
-
response,
|
|
861
|
+
response: currentResponse,
|
|
929
862
|
retried: false,
|
|
930
863
|
nextInit: init,
|
|
931
864
|
nextPayload: requestPayload,
|
|
@@ -961,49 +894,6 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, requ
|
|
|
961
894
|
});
|
|
962
895
|
return { response: retried, retried: true, nextInit, nextPayload: sanitized, retryState };
|
|
963
896
|
}
|
|
964
|
-
function getRequestUrl(request) {
|
|
965
|
-
return request instanceof Request ? request.url : request instanceof URL ? request.href : String(request);
|
|
966
|
-
}
|
|
967
|
-
function buildRetryableApiCallMessage(group, detail) {
|
|
968
|
-
return `Copilot retryable error [${group}]: ${detail}`;
|
|
969
|
-
}
|
|
970
|
-
function toRetryableApiCallError(error, request, options) {
|
|
971
|
-
const base = error instanceof Error ? error : new Error(String(error));
|
|
972
|
-
const wrapped = new Error(buildRetryableApiCallMessage(options?.group ?? "transport", base.message));
|
|
973
|
-
wrapped.name = "AI_APICallError";
|
|
974
|
-
wrapped.url = getRequestUrl(request);
|
|
975
|
-
wrapped.requestBodyValues = options?.requestBodyValues ?? {};
|
|
976
|
-
wrapped.statusCode = options?.statusCode;
|
|
977
|
-
wrapped.responseHeaders = options?.responseHeaders instanceof Headers
|
|
978
|
-
? Object.fromEntries(options.responseHeaders.entries())
|
|
979
|
-
: options?.responseHeaders;
|
|
980
|
-
wrapped.responseBody = options?.responseBody;
|
|
981
|
-
wrapped.isRetryable = true;
|
|
982
|
-
wrapped.cause = error;
|
|
983
|
-
wrapped[AI_ERROR_MARKER] = true;
|
|
984
|
-
wrapped[API_CALL_ERROR_MARKER] = true;
|
|
985
|
-
return wrapped;
|
|
986
|
-
}
|
|
987
|
-
function isRetryableApiCallError(error) {
|
|
988
|
-
return Boolean(error
|
|
989
|
-
&& typeof error === "object"
|
|
990
|
-
&& error[AI_ERROR_MARKER] === true
|
|
991
|
-
&& error[API_CALL_ERROR_MARKER] === true);
|
|
992
|
-
}
|
|
993
|
-
function isRetryableCopilotStatus(status) {
|
|
994
|
-
return status === 499;
|
|
995
|
-
}
|
|
996
|
-
function isCopilotUrl(request) {
|
|
997
|
-
const raw = request instanceof Request ? request.url : request instanceof URL ? request.href : String(request);
|
|
998
|
-
try {
|
|
999
|
-
const url = new URL(raw);
|
|
1000
|
-
const isCopilotHost = url.hostname === "api.githubcopilot.com" || url.hostname.startsWith("copilot-api.");
|
|
1001
|
-
return isCopilotHost;
|
|
1002
|
-
}
|
|
1003
|
-
catch {
|
|
1004
|
-
return false;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
897
|
async function getInputIdRetryErrorDetails(response) {
|
|
1008
898
|
if (response.ok)
|
|
1009
899
|
return undefined;
|
|
@@ -1055,7 +945,7 @@ async function parseJsonRequestPayload(request, init) {
|
|
|
1055
945
|
return undefined;
|
|
1056
946
|
}
|
|
1057
947
|
}
|
|
1058
|
-
function withStreamDebugLogs(response, request) {
|
|
948
|
+
function withStreamDebugLogs(response, request, policy) {
|
|
1059
949
|
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
1060
950
|
if (!contentType.includes("text/event-stream") || !response.body)
|
|
1061
951
|
return response;
|
|
@@ -1077,8 +967,14 @@ function withStreamDebugLogs(response, request) {
|
|
|
1077
967
|
}
|
|
1078
968
|
catch (error) {
|
|
1079
969
|
const message = getErrorMessage(error);
|
|
1080
|
-
const
|
|
1081
|
-
|
|
970
|
+
const normalized = policy.normalizeStreamError({
|
|
971
|
+
error,
|
|
972
|
+
request,
|
|
973
|
+
statusCode: response.status,
|
|
974
|
+
responseHeaders: response.headers,
|
|
975
|
+
});
|
|
976
|
+
const isSseReadTimeout = normalized === error && message.includes("sse read timed out");
|
|
977
|
+
const retryable = normalized !== error;
|
|
1082
978
|
if (isDebugEnabled()) {
|
|
1083
979
|
debugLog("sse stream read error", {
|
|
1084
980
|
url: rawUrl,
|
|
@@ -1087,15 +983,7 @@ function withStreamDebugLogs(response, request) {
|
|
|
1087
983
|
bypassedTimeoutWrap: isSseReadTimeout,
|
|
1088
984
|
});
|
|
1089
985
|
}
|
|
1090
|
-
controller.error(isSseReadTimeout
|
|
1091
|
-
? error
|
|
1092
|
-
: retryable
|
|
1093
|
-
? toRetryableApiCallError(error, request, {
|
|
1094
|
-
group: "stream",
|
|
1095
|
-
statusCode: response.status,
|
|
1096
|
-
responseHeaders: response.headers,
|
|
1097
|
-
})
|
|
1098
|
-
: error);
|
|
986
|
+
controller.error(isSseReadTimeout ? error : normalized);
|
|
1099
987
|
}
|
|
1100
988
|
};
|
|
1101
989
|
void pump();
|
|
@@ -1108,10 +996,7 @@ function withStreamDebugLogs(response, request) {
|
|
|
1108
996
|
});
|
|
1109
997
|
}
|
|
1110
998
|
export function isRetryableCopilotFetchError(error) {
|
|
1111
|
-
|
|
1112
|
-
return false;
|
|
1113
|
-
const message = getErrorMessage(error);
|
|
1114
|
-
return RETRYABLE_MESSAGES.some((part) => message.includes(part));
|
|
999
|
+
return isRetryableCopilotTransportError(error);
|
|
1115
1000
|
}
|
|
1116
1001
|
function isRetryableCopilotJsonParseError(error) {
|
|
1117
1002
|
if (!error || isAbortError(error))
|
|
@@ -1121,9 +1006,166 @@ function isRetryableCopilotJsonParseError(error) {
|
|
|
1121
1006
|
const hasAiJsonParseSignature = name === "AI_JSONParseError" || message.includes("ai_jsonparseerror");
|
|
1122
1007
|
return hasAiJsonParseSignature && message.includes("json parsing failed") && message.includes("text:");
|
|
1123
1008
|
}
|
|
1009
|
+
function toRequestUrl(request) {
|
|
1010
|
+
return request instanceof Request ? request.url : request instanceof URL ? request.href : String(request);
|
|
1011
|
+
}
|
|
1012
|
+
async function runCopilotRepairLoop(input) {
|
|
1013
|
+
const state = {
|
|
1014
|
+
currentResponse: input.response,
|
|
1015
|
+
currentInit: input.effectiveInit,
|
|
1016
|
+
currentPayload: input.currentPayload,
|
|
1017
|
+
attempts: 0,
|
|
1018
|
+
startedNotified: false,
|
|
1019
|
+
finishedNotified: false,
|
|
1020
|
+
repairWarningNotified: false,
|
|
1021
|
+
};
|
|
1022
|
+
const handleRetryResult = async (result) => {
|
|
1023
|
+
state.currentResponse = result.response;
|
|
1024
|
+
state.currentInit = result.nextInit;
|
|
1025
|
+
state.currentPayload = result.nextPayload;
|
|
1026
|
+
if (!result.retryState) {
|
|
1027
|
+
return {
|
|
1028
|
+
handled: false,
|
|
1029
|
+
stop: false,
|
|
1030
|
+
shouldContinue: false,
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
if (!state.startedNotified && result.retryState.notifiedStarted) {
|
|
1034
|
+
state.startedNotified = true;
|
|
1035
|
+
await notifySharedRetryEvent(input.notifier, "started", result.retryState.remainingLongIdCandidatesBefore);
|
|
1036
|
+
}
|
|
1037
|
+
if (result.retryState.repairFailed && !state.repairWarningNotified) {
|
|
1038
|
+
await notifySharedRetryEvent(input.notifier, "repairWarning", result.retryState.remainingLongIdCandidatesBefore);
|
|
1039
|
+
state.repairWarningNotified = true;
|
|
1040
|
+
}
|
|
1041
|
+
const currentError = await getInputIdRetryErrorDetails(state.currentResponse);
|
|
1042
|
+
let stopReason = result.retryState.stopReason;
|
|
1043
|
+
const madeProgress = result.retryState.remainingLongIdCandidatesAfter < result.retryState.remainingLongIdCandidatesBefore;
|
|
1044
|
+
if (!stopReason && result.retryState.remainingLongIdCandidatesAfter >= result.retryState.remainingLongIdCandidatesBefore) {
|
|
1045
|
+
stopReason = "remaining-candidates-not-reduced";
|
|
1046
|
+
}
|
|
1047
|
+
if (!stopReason
|
|
1048
|
+
&& currentError
|
|
1049
|
+
&& result.retryState.remainingLongIdCandidatesAfter > 0
|
|
1050
|
+
&& result.retryState.previousServerReportedIndex === currentError.serverReportedIndex
|
|
1051
|
+
&& result.retryState.previousReportedLength === currentError.reportedLength) {
|
|
1052
|
+
stopReason = "same-server-item-persists";
|
|
1053
|
+
}
|
|
1054
|
+
if (!stopReason && currentError && result.retryState.remainingLongIdCandidatesAfter === 0) {
|
|
1055
|
+
stopReason = "local-candidates-exhausted";
|
|
1056
|
+
}
|
|
1057
|
+
if ((currentError || stopReason) && stopReason !== "evidence-insufficient") {
|
|
1058
|
+
debugLog("input-id retry progress", {
|
|
1059
|
+
attempt: state.attempts + 1,
|
|
1060
|
+
previousServerReportedIndex: result.retryState.previousServerReportedIndex,
|
|
1061
|
+
currentServerReportedIndex: currentError?.serverReportedIndex,
|
|
1062
|
+
serverIndexChanged: result.retryState.previousServerReportedIndex !== currentError?.serverReportedIndex,
|
|
1063
|
+
previousErrorMessagePreview: result.retryState.previousErrorMessagePreview,
|
|
1064
|
+
currentErrorMessagePreview: currentError?.errorMessagePreview,
|
|
1065
|
+
remainingLongIdCandidatesBefore: result.retryState.remainingLongIdCandidatesBefore,
|
|
1066
|
+
remainingLongIdCandidatesAfter: result.retryState.remainingLongIdCandidatesAfter,
|
|
1067
|
+
stopReason,
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
if (stopReason === "local-candidates-exhausted") {
|
|
1071
|
+
logCleanupStopped("local-candidates-exhausted", {
|
|
1072
|
+
attempt: state.attempts + 1,
|
|
1073
|
+
previousServerReportedIndex: result.retryState.previousServerReportedIndex,
|
|
1074
|
+
currentServerReportedIndex: currentError?.serverReportedIndex,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
if (stopReason) {
|
|
1078
|
+
await notifySharedRetryEvent(input.notifier, "stopped", result.retryState.remainingLongIdCandidatesAfter);
|
|
1079
|
+
state.finishedNotified = true;
|
|
1080
|
+
return {
|
|
1081
|
+
handled: true,
|
|
1082
|
+
stop: true,
|
|
1083
|
+
shouldContinue: false,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (result.retried && madeProgress && result.retryState.remainingLongIdCandidatesAfter > 0) {
|
|
1087
|
+
await notifySharedRetryEvent(input.notifier, "progress", result.retryState.remainingLongIdCandidatesAfter);
|
|
1088
|
+
}
|
|
1089
|
+
if (result.retried && result.retryState.remainingLongIdCandidatesAfter === 0 && state.currentResponse.ok) {
|
|
1090
|
+
await notifySharedRetryEvent(input.notifier, "completed", 0);
|
|
1091
|
+
state.finishedNotified = true;
|
|
1092
|
+
}
|
|
1093
|
+
if (result.retried && result.retryState.remainingLongIdCandidatesAfter === 0 && !state.currentResponse.ok) {
|
|
1094
|
+
await notifySharedRetryEvent(input.notifier, "stopped", 0);
|
|
1095
|
+
state.finishedNotified = true;
|
|
1096
|
+
}
|
|
1097
|
+
if (!result.retried) {
|
|
1098
|
+
if (state.startedNotified && !state.finishedNotified) {
|
|
1099
|
+
await notifySharedRetryEvent(input.notifier, "stopped", result.retryState.remainingLongIdCandidatesAfter);
|
|
1100
|
+
state.finishedNotified = true;
|
|
1101
|
+
}
|
|
1102
|
+
return {
|
|
1103
|
+
handled: true,
|
|
1104
|
+
stop: true,
|
|
1105
|
+
shouldContinue: false,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
handled: true,
|
|
1110
|
+
stop: false,
|
|
1111
|
+
shouldContinue: madeProgress && result.retryState.remainingLongIdCandidatesAfter > 0,
|
|
1112
|
+
};
|
|
1113
|
+
};
|
|
1114
|
+
await runSharedRetryScheduler({
|
|
1115
|
+
initialShouldContinue: countInputIdCandidates(state.currentPayload) > 0,
|
|
1116
|
+
runIteration: async ({ attempts: scheduledAttempts }) => {
|
|
1117
|
+
state.attempts = scheduledAttempts;
|
|
1118
|
+
const decision = await input.policy.decideResponseRepair({
|
|
1119
|
+
request: input.safeRequest,
|
|
1120
|
+
response: state.currentResponse,
|
|
1121
|
+
requestPayload: state.currentPayload,
|
|
1122
|
+
sessionID: input.sessionID,
|
|
1123
|
+
});
|
|
1124
|
+
if (decision.kind === "skip") {
|
|
1125
|
+
if (state.startedNotified && !state.finishedNotified) {
|
|
1126
|
+
await notifySharedRetryEvent(input.notifier, "stopped", countInputIdCandidates(state.currentPayload));
|
|
1127
|
+
state.finishedNotified = true;
|
|
1128
|
+
}
|
|
1129
|
+
return {
|
|
1130
|
+
handled: false,
|
|
1131
|
+
stop: true,
|
|
1132
|
+
shouldContinue: false,
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
const result = decision.kind === "connection-mismatch"
|
|
1136
|
+
? await maybeRetryConnectionMismatchItemIds(input.safeRequest, state.currentInit, state.currentResponse, decision, input.baseFetch, state.currentPayload, input.options, input.sessionID, state.startedNotified)
|
|
1137
|
+
: await maybeRetryInputIdTooLong(input.safeRequest, state.currentInit, state.currentResponse, decision, input.baseFetch, state.currentPayload, input.options, input.sessionID, state.startedNotified);
|
|
1138
|
+
const handled = await handleRetryResult(result);
|
|
1139
|
+
if (handled.stop)
|
|
1140
|
+
return handled;
|
|
1141
|
+
if (!handled.handled) {
|
|
1142
|
+
if (state.startedNotified && !state.finishedNotified) {
|
|
1143
|
+
await notifySharedRetryEvent(input.notifier, "stopped", countInputIdCandidates(state.currentPayload));
|
|
1144
|
+
state.finishedNotified = true;
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
handled: false,
|
|
1148
|
+
stop: true,
|
|
1149
|
+
shouldContinue: false,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
return handled;
|
|
1153
|
+
},
|
|
1154
|
+
});
|
|
1155
|
+
return {
|
|
1156
|
+
response: state.currentResponse,
|
|
1157
|
+
payload: state.currentPayload,
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1124
1160
|
export function createCopilotRetryingFetch(baseFetch, options) {
|
|
1125
|
-
const notifier = options?.notifier ??
|
|
1126
|
-
|
|
1161
|
+
const notifier = options?.notifier ?? noopSharedRetryNotifier;
|
|
1162
|
+
const policy = createCopilotRetryPolicy({
|
|
1163
|
+
extraRetryableClassifier: isRetryableCopilotJsonParseError,
|
|
1164
|
+
});
|
|
1165
|
+
const retryEngine = createNetworkRetryEngine({
|
|
1166
|
+
policy,
|
|
1167
|
+
});
|
|
1168
|
+
return retryEngine(async (request, init) => {
|
|
1127
1169
|
const sessionID = getHeader(request, init, INTERNAL_SESSION_HEADER);
|
|
1128
1170
|
const headersBeforeWrapper = toHeaderRecord(init?.headers) ?? (request instanceof Request ? toHeaderRecord(request.headers) : undefined);
|
|
1129
1171
|
debugLog("fetch headers before wrapper", {
|
|
@@ -1145,157 +1187,38 @@ export function createCopilotRetryingFetch(baseFetch, options) {
|
|
|
1145
1187
|
removedInternalHeaders: strippedHeaders.removed,
|
|
1146
1188
|
isRetry: false,
|
|
1147
1189
|
});
|
|
1190
|
+
const isCopilotRequest = policy.matchesRequest(safeRequest);
|
|
1148
1191
|
debugLog("fetch start", {
|
|
1149
1192
|
url: safeRequest instanceof Request ? safeRequest.url : safeRequest instanceof URL ? safeRequest.href : String(safeRequest),
|
|
1150
|
-
isCopilot:
|
|
1193
|
+
isCopilot: isCopilotRequest,
|
|
1151
1194
|
});
|
|
1152
1195
|
debugLog("fetch headers before network", {
|
|
1153
1196
|
headers: toHeaderRecord(effectiveInit?.headers) ?? (safeRequest instanceof Request ? toHeaderRecord(safeRequest.headers) : undefined),
|
|
1154
1197
|
isRetry: false,
|
|
1155
1198
|
});
|
|
1156
|
-
|
|
1199
|
+
const currentPayload = await parseJsonRequestPayload(safeRequest, effectiveInit);
|
|
1157
1200
|
try {
|
|
1158
1201
|
const response = await baseFetch(safeRequest, effectiveInit);
|
|
1159
1202
|
debugLog("fetch resolved", {
|
|
1160
1203
|
status: response.status,
|
|
1161
1204
|
contentType: response.headers.get("content-type") ?? undefined,
|
|
1162
1205
|
});
|
|
1163
|
-
if (
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1206
|
+
if (isCopilotRequest && policy.shouldRunResponseRepair(safeRequest)) {
|
|
1207
|
+
const repaired = await runCopilotRepairLoop({
|
|
1208
|
+
safeRequest,
|
|
1209
|
+
effectiveInit,
|
|
1210
|
+
response,
|
|
1211
|
+
baseFetch,
|
|
1212
|
+
policy,
|
|
1213
|
+
options,
|
|
1214
|
+
notifier,
|
|
1215
|
+
sessionID,
|
|
1216
|
+
currentPayload,
|
|
1171
1217
|
});
|
|
1218
|
+
return withStreamDebugLogs(repaired.response, safeRequest, policy);
|
|
1172
1219
|
}
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1175
|
-
let currentInit = effectiveInit;
|
|
1176
|
-
let attempts = 0;
|
|
1177
|
-
let shouldContinueInputIdRepair = countInputIdCandidates(currentPayload) > 0;
|
|
1178
|
-
let startedNotified = false;
|
|
1179
|
-
let finishedNotified = false;
|
|
1180
|
-
let repairWarningNotified = false;
|
|
1181
|
-
const handleRetryResult = async (result) => {
|
|
1182
|
-
currentResponse = result.response;
|
|
1183
|
-
currentInit = result.nextInit;
|
|
1184
|
-
currentPayload = result.nextPayload;
|
|
1185
|
-
if (!result.retryState) {
|
|
1186
|
-
return {
|
|
1187
|
-
handled: false,
|
|
1188
|
-
stop: false,
|
|
1189
|
-
shouldContinue: false,
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
if (!startedNotified && result.retryState.notifiedStarted) {
|
|
1193
|
-
startedNotified = true;
|
|
1194
|
-
await notify(notifier, "started", result.retryState.remainingLongIdCandidatesBefore);
|
|
1195
|
-
}
|
|
1196
|
-
if (result.retryState.repairFailed && !repairWarningNotified) {
|
|
1197
|
-
await notify(notifier, "repairWarning", result.retryState.remainingLongIdCandidatesBefore);
|
|
1198
|
-
repairWarningNotified = true;
|
|
1199
|
-
}
|
|
1200
|
-
const currentError = await getInputIdRetryErrorDetails(currentResponse);
|
|
1201
|
-
let stopReason = result.retryState.stopReason;
|
|
1202
|
-
const madeProgress = result.retryState.remainingLongIdCandidatesAfter < result.retryState.remainingLongIdCandidatesBefore;
|
|
1203
|
-
if (!stopReason && result.retryState.remainingLongIdCandidatesAfter >= result.retryState.remainingLongIdCandidatesBefore) {
|
|
1204
|
-
stopReason = "remaining-candidates-not-reduced";
|
|
1205
|
-
}
|
|
1206
|
-
if (!stopReason &&
|
|
1207
|
-
currentError &&
|
|
1208
|
-
result.retryState.remainingLongIdCandidatesAfter > 0 &&
|
|
1209
|
-
result.retryState.previousServerReportedIndex === currentError.serverReportedIndex &&
|
|
1210
|
-
result.retryState.previousReportedLength === currentError.reportedLength) {
|
|
1211
|
-
stopReason = "same-server-item-persists";
|
|
1212
|
-
}
|
|
1213
|
-
if (!stopReason && currentError && result.retryState.remainingLongIdCandidatesAfter === 0) {
|
|
1214
|
-
stopReason = "local-candidates-exhausted";
|
|
1215
|
-
}
|
|
1216
|
-
if ((currentError || stopReason) && stopReason !== "evidence-insufficient") {
|
|
1217
|
-
debugLog("input-id retry progress", {
|
|
1218
|
-
attempt: attempts + 1,
|
|
1219
|
-
previousServerReportedIndex: result.retryState.previousServerReportedIndex,
|
|
1220
|
-
currentServerReportedIndex: currentError?.serverReportedIndex,
|
|
1221
|
-
serverIndexChanged: result.retryState.previousServerReportedIndex !== currentError?.serverReportedIndex,
|
|
1222
|
-
previousErrorMessagePreview: result.retryState.previousErrorMessagePreview,
|
|
1223
|
-
currentErrorMessagePreview: currentError?.errorMessagePreview,
|
|
1224
|
-
remainingLongIdCandidatesBefore: result.retryState.remainingLongIdCandidatesBefore,
|
|
1225
|
-
remainingLongIdCandidatesAfter: result.retryState.remainingLongIdCandidatesAfter,
|
|
1226
|
-
stopReason,
|
|
1227
|
-
});
|
|
1228
|
-
}
|
|
1229
|
-
if (stopReason === "local-candidates-exhausted") {
|
|
1230
|
-
logCleanupStopped("local-candidates-exhausted", {
|
|
1231
|
-
attempt: attempts + 1,
|
|
1232
|
-
previousServerReportedIndex: result.retryState.previousServerReportedIndex,
|
|
1233
|
-
currentServerReportedIndex: currentError?.serverReportedIndex,
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
if (stopReason) {
|
|
1237
|
-
await notify(notifier, "stopped", result.retryState.remainingLongIdCandidatesAfter);
|
|
1238
|
-
finishedNotified = true;
|
|
1239
|
-
return {
|
|
1240
|
-
handled: true,
|
|
1241
|
-
stop: true,
|
|
1242
|
-
shouldContinue: false,
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
if (result.retried && madeProgress && result.retryState.remainingLongIdCandidatesAfter > 0) {
|
|
1246
|
-
await notify(notifier, "progress", result.retryState.remainingLongIdCandidatesAfter);
|
|
1247
|
-
}
|
|
1248
|
-
if (result.retried && result.retryState.remainingLongIdCandidatesAfter === 0 && currentResponse.ok) {
|
|
1249
|
-
await notify(notifier, "completed", 0);
|
|
1250
|
-
finishedNotified = true;
|
|
1251
|
-
}
|
|
1252
|
-
if (result.retried && result.retryState.remainingLongIdCandidatesAfter === 0 && !currentResponse.ok) {
|
|
1253
|
-
await notify(notifier, "stopped", 0);
|
|
1254
|
-
finishedNotified = true;
|
|
1255
|
-
}
|
|
1256
|
-
if (!result.retried) {
|
|
1257
|
-
if (startedNotified && !finishedNotified) {
|
|
1258
|
-
await notify(notifier, "stopped", result.retryState.remainingLongIdCandidatesAfter);
|
|
1259
|
-
finishedNotified = true;
|
|
1260
|
-
}
|
|
1261
|
-
return {
|
|
1262
|
-
handled: true,
|
|
1263
|
-
stop: true,
|
|
1264
|
-
shouldContinue: false,
|
|
1265
|
-
};
|
|
1266
|
-
}
|
|
1267
|
-
return {
|
|
1268
|
-
handled: true,
|
|
1269
|
-
stop: false,
|
|
1270
|
-
shouldContinue: madeProgress && result.retryState.remainingLongIdCandidatesAfter > 0,
|
|
1271
|
-
};
|
|
1272
|
-
};
|
|
1273
|
-
while (shouldContinueInputIdRepair) {
|
|
1274
|
-
shouldContinueInputIdRepair = false;
|
|
1275
|
-
const connectionMismatchResult = await maybeRetryConnectionMismatchItemIds(safeRequest, currentInit, currentResponse, baseFetch, currentPayload, options, sessionID, startedNotified);
|
|
1276
|
-
const handledConnectionMismatch = await handleRetryResult(connectionMismatchResult);
|
|
1277
|
-
if (handledConnectionMismatch.stop)
|
|
1278
|
-
break;
|
|
1279
|
-
if (handledConnectionMismatch.handled) {
|
|
1280
|
-
attempts += 1;
|
|
1281
|
-
shouldContinueInputIdRepair = handledConnectionMismatch.shouldContinue;
|
|
1282
|
-
continue;
|
|
1283
|
-
}
|
|
1284
|
-
const result = await maybeRetryInputIdTooLong(safeRequest, currentInit, currentResponse, baseFetch, currentPayload, options, sessionID, startedNotified);
|
|
1285
|
-
const handled = await handleRetryResult(result);
|
|
1286
|
-
if (handled.stop)
|
|
1287
|
-
break;
|
|
1288
|
-
if (!handled.handled) {
|
|
1289
|
-
if (startedNotified && !finishedNotified) {
|
|
1290
|
-
await notify(notifier, "stopped", countInputIdCandidates(currentPayload));
|
|
1291
|
-
finishedNotified = true;
|
|
1292
|
-
}
|
|
1293
|
-
break;
|
|
1294
|
-
}
|
|
1295
|
-
attempts += 1;
|
|
1296
|
-
shouldContinueInputIdRepair = handled.shouldContinue;
|
|
1297
|
-
}
|
|
1298
|
-
return withStreamDebugLogs(currentResponse, safeRequest);
|
|
1220
|
+
if (isCopilotRequest) {
|
|
1221
|
+
return withStreamDebugLogs(response, safeRequest, policy);
|
|
1299
1222
|
}
|
|
1300
1223
|
return response;
|
|
1301
1224
|
}
|
|
@@ -1307,16 +1230,7 @@ export function createCopilotRetryingFetch(baseFetch, options) {
|
|
|
1307
1230
|
retryableByMessage,
|
|
1308
1231
|
retryableCopilotJsonParse,
|
|
1309
1232
|
});
|
|
1310
|
-
|
|
1311
|
-
throw error;
|
|
1312
|
-
}
|
|
1313
|
-
if (isRetryableApiCallError(error)) {
|
|
1314
|
-
throw error;
|
|
1315
|
-
}
|
|
1316
|
-
throw toRetryableApiCallError(error, safeRequest, {
|
|
1317
|
-
group: "transport",
|
|
1318
|
-
requestBodyValues: currentPayload,
|
|
1319
|
-
});
|
|
1233
|
+
throw error;
|
|
1320
1234
|
}
|
|
1321
|
-
};
|
|
1235
|
+
});
|
|
1322
1236
|
}
|