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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { COPILOT_RETRYABLE_MESSAGES, createCopilotRetryPolicy, isCopilotUrl, isRetryableApiCallError, isRetryableCopilotTransportError, toRetryableApiCallError, type CopilotRetryPolicy, type RetryableErrorGroup, } from "./retry/copilot-policy.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { COPILOT_RETRYABLE_MESSAGES, createCopilotRetryPolicy, isCopilotUrl, isRetryableApiCallError, isRetryableCopilotTransportError, toRetryableApiCallError, } from "./retry/copilot-policy.js";
|
|
@@ -75,7 +75,9 @@ export function rewriteModelAccountAssignments(store, rename) {
|
|
|
75
75
|
const seen = new Set();
|
|
76
76
|
const resolvedNames = [];
|
|
77
77
|
for (const originalName of accountNames) {
|
|
78
|
-
const mappedName = rename
|
|
78
|
+
const mappedName = Object.prototype.hasOwnProperty.call(rename, originalName)
|
|
79
|
+
? rename[originalName]
|
|
80
|
+
: originalName;
|
|
79
81
|
if (typeof mappedName !== "string" || !store.accounts[mappedName] || seen.has(mappedName))
|
|
80
82
|
continue;
|
|
81
83
|
seen.add(mappedName);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type NetworkRetryRequest = {
|
|
2
|
+
url: string;
|
|
3
|
+
method?: string;
|
|
4
|
+
body?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
export type NetworkRetryClassification = {
|
|
8
|
+
retryable: boolean;
|
|
9
|
+
category: string;
|
|
10
|
+
};
|
|
11
|
+
export type NetworkRetryPolicy = {
|
|
12
|
+
matchesRequest: (request: Request | URL | string) => boolean;
|
|
13
|
+
classifyFailure: (input: {
|
|
14
|
+
error: unknown;
|
|
15
|
+
request: NetworkRetryRequest;
|
|
16
|
+
}) => Promise<NetworkRetryClassification>;
|
|
17
|
+
handleResponse?: (input: {
|
|
18
|
+
response: Response;
|
|
19
|
+
request: NetworkRetryRequest;
|
|
20
|
+
}) => Promise<Response>;
|
|
21
|
+
normalizeFailure?: (input: {
|
|
22
|
+
error: unknown;
|
|
23
|
+
classification: NetworkRetryClassification;
|
|
24
|
+
request: NetworkRetryRequest;
|
|
25
|
+
}) => unknown;
|
|
26
|
+
buildRepairPlan: (input: {
|
|
27
|
+
request: NetworkRetryRequest;
|
|
28
|
+
classification: NetworkRetryClassification;
|
|
29
|
+
}) => Promise<unknown>;
|
|
30
|
+
};
|
|
31
|
+
export declare function createNetworkRetryEngine(input: {
|
|
32
|
+
policy: NetworkRetryPolicy;
|
|
33
|
+
}): (baseFetch: (request: Request | URL | string, init?: RequestInit) => Promise<Response>) => (request: Request | URL | string, init?: RequestInit) => Promise<Response>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function toHeaderRecord(headers) {
|
|
2
|
+
if (!headers)
|
|
3
|
+
return undefined;
|
|
4
|
+
return Object.fromEntries(new Headers(headers).entries());
|
|
5
|
+
}
|
|
6
|
+
async function tryGetRequestBodyString(request, init) {
|
|
7
|
+
if (typeof init?.body === "string")
|
|
8
|
+
return init.body;
|
|
9
|
+
if (!(request instanceof Request))
|
|
10
|
+
return undefined;
|
|
11
|
+
try {
|
|
12
|
+
return await request.clone().text();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function toNetworkRetryRequest(request, init) {
|
|
19
|
+
const url = request instanceof Request ? request.url : request instanceof URL ? request.href : String(request);
|
|
20
|
+
const method = init?.method ?? (request instanceof Request ? request.method : undefined);
|
|
21
|
+
const headers = toHeaderRecord(init?.headers) ?? (request instanceof Request ? toHeaderRecord(request.headers) : undefined);
|
|
22
|
+
const body = await tryGetRequestBodyString(request, init);
|
|
23
|
+
return { url, method, headers, body };
|
|
24
|
+
}
|
|
25
|
+
export function createNetworkRetryEngine(input) {
|
|
26
|
+
return function wrapNetworkFetch(baseFetch) {
|
|
27
|
+
return async function retryingNetworkFetch(request, init) {
|
|
28
|
+
if (!input.policy.matchesRequest(request)) {
|
|
29
|
+
return baseFetch(request, init);
|
|
30
|
+
}
|
|
31
|
+
const normalizedRequest = await toNetworkRetryRequest(request, init);
|
|
32
|
+
try {
|
|
33
|
+
const response = await baseFetch(request, init);
|
|
34
|
+
if (!input.policy.handleResponse)
|
|
35
|
+
return response;
|
|
36
|
+
return input.policy.handleResponse({
|
|
37
|
+
response,
|
|
38
|
+
request: normalizedRequest,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const classification = await input.policy.classifyFailure({
|
|
43
|
+
error,
|
|
44
|
+
request: normalizedRequest,
|
|
45
|
+
});
|
|
46
|
+
if (!classification.retryable)
|
|
47
|
+
throw error;
|
|
48
|
+
await input.policy.buildRepairPlan({
|
|
49
|
+
request: normalizedRequest,
|
|
50
|
+
classification,
|
|
51
|
+
});
|
|
52
|
+
if (!input.policy.normalizeFailure)
|
|
53
|
+
throw error;
|
|
54
|
+
throw input.policy.normalizeFailure({
|
|
55
|
+
error,
|
|
56
|
+
classification,
|
|
57
|
+
request: normalizedRequest,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/plugin-hooks.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type StoreFile, type StoreWriteDebugMeta } from "./store.js";
|
|
|
5
5
|
import { type CopilotAuthState, type CopilotProviderConfig, type OfficialCopilotConfig, type OfficialChatHeadersHook } from "./upstream/copilot-loader-adapter.js";
|
|
6
6
|
import { type RefreshActiveAccountQuotaResult } from "./active-account-quota.js";
|
|
7
7
|
import { handleStatusCommand } from "./status-command.js";
|
|
8
|
+
import { handleCodexStatusCommand } from "./codex-status-command.js";
|
|
8
9
|
import { handleCompactCommand, handleStopToolCommand } from "./session-control-command.js";
|
|
9
10
|
import { type AppendSessionTouchEventInput, type RouteDecisionEvent, type RoutingSnapshot, type RoutingEvent } from "./routing-state.js";
|
|
10
11
|
type ChatHeadersHook = (input: {
|
|
@@ -32,6 +33,7 @@ type CopilotPluginHooksWithChatHeaders = CopilotPluginHooks & {
|
|
|
32
33
|
"chat.headers"?: ChatHeadersHook;
|
|
33
34
|
};
|
|
34
35
|
type StatusCommandHandler = typeof handleStatusCommand;
|
|
36
|
+
type CodexStatusCommandHandler = typeof handleCodexStatusCommand;
|
|
35
37
|
type CompactCommandHandler = typeof handleCompactCommand;
|
|
36
38
|
type StopToolCommandHandler = typeof handleStopToolCommand;
|
|
37
39
|
type RefreshQuota = (store: StoreFile) => Promise<RefreshActiveAccountQuotaResult>;
|
|
@@ -82,6 +84,7 @@ export declare function buildPluginHooks(input: {
|
|
|
82
84
|
now?: () => number;
|
|
83
85
|
refreshQuota?: RefreshQuota;
|
|
84
86
|
handleStatusCommandImpl?: StatusCommandHandler;
|
|
87
|
+
handleCodexStatusCommandImpl?: CodexStatusCommandHandler;
|
|
85
88
|
handleCompactCommandImpl?: CompactCommandHandler;
|
|
86
89
|
handleStopToolCommandImpl?: StopToolCommandHandler;
|
|
87
90
|
loadCandidateAccountLoads?: (input: {
|
package/dist/plugin-hooks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendFileSync } from "node:fs";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { createCompactionLoopSafetyBypass, createLoopSafetySystemTransform, getLoopSafetyProviderScope,
|
|
3
|
+
import { createCompactionLoopSafetyBypass, createLoopSafetySystemTransform, getLoopSafetyProviderScope, } from "./loop-safety-plugin.js";
|
|
4
|
+
import { COPILOT_PROVIDER_DESCRIPTOR } from "./providers/descriptor.js";
|
|
4
5
|
import { createCopilotRetryingFetch, cleanupLongIdsForAccountSwitch, detectRateLimitEvidence, INTERNAL_SESSION_CONTEXT_KEY, } from "./copilot-network-retry.js";
|
|
5
6
|
import { createCopilotRetryNotifier } from "./copilot-retry-notifier.js";
|
|
6
7
|
import { resolveCopilotModelAccounts } from "./model-account-map.js";
|
|
@@ -11,6 +12,7 @@ import { createNotifyTool } from "./notify-tool.js";
|
|
|
11
12
|
import { createWaitTool } from "./wait-tool.js";
|
|
12
13
|
import { refreshActiveAccountQuota } from "./active-account-quota.js";
|
|
13
14
|
import { handleStatusCommand, showStatusToast } from "./status-command.js";
|
|
15
|
+
import { handleCodexStatusCommand } from "./codex-status-command.js";
|
|
14
16
|
import { handleCompactCommand, handleStopToolCommand, } from "./session-control-command.js";
|
|
15
17
|
import { appendRoutingEvent, appendRouteDecisionEvent, appendSessionTouchEvent, buildCandidateAccountLoads, isAccountRateLimitCooledDown, readRoutingState, routingStatePath, } from "./routing-state.js";
|
|
16
18
|
const SESSION_BINDING_IDLE_TTL_MS = 30 * 60 * 1000;
|
|
@@ -33,6 +35,9 @@ export class PolicyScopeCommandHandledError extends Error {
|
|
|
33
35
|
this.name = "PolicyScopeCommandHandledError";
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
function isCopilotProviderID(providerID) {
|
|
39
|
+
return COPILOT_PROVIDER_DESCRIPTOR.providerIDs.includes(providerID);
|
|
40
|
+
}
|
|
36
41
|
function isDebugEnabled() {
|
|
37
42
|
return process.env.OPENCODE_COPILOT_RETRY_DEBUG === "1";
|
|
38
43
|
}
|
|
@@ -176,6 +181,35 @@ function getMergedRequestHeader(request, init, name) {
|
|
|
176
181
|
}
|
|
177
182
|
return headers.get(name);
|
|
178
183
|
}
|
|
184
|
+
function getMergedRequestHeadersRecord(request, init) {
|
|
185
|
+
const headers = new Headers(request instanceof Request ? request.headers : undefined);
|
|
186
|
+
for (const [headerName, value] of new Headers(init?.headers).entries()) {
|
|
187
|
+
headers.set(headerName, value);
|
|
188
|
+
}
|
|
189
|
+
return Object.fromEntries(headers.entries());
|
|
190
|
+
}
|
|
191
|
+
function getFinalSentRequestHeadersRecord(request, init) {
|
|
192
|
+
const headers = new Headers(request instanceof Request ? request.headers : undefined);
|
|
193
|
+
for (const [headerName, value] of new Headers(init?.headers).entries()) {
|
|
194
|
+
headers.set(headerName, value);
|
|
195
|
+
}
|
|
196
|
+
headers.delete("x-opencode-session-id");
|
|
197
|
+
headers.delete(INTERNAL_DEBUG_LINK_HEADER);
|
|
198
|
+
return sanitizeLoggedRequestHeadersRecord(Object.fromEntries(headers.entries()));
|
|
199
|
+
}
|
|
200
|
+
function sanitizeLoggedRequestHeadersRecord(headers) {
|
|
201
|
+
const sanitized = { ...headers };
|
|
202
|
+
if (typeof sanitized.authorization === "string" && sanitized.authorization.length > 0) {
|
|
203
|
+
sanitized.authorization = "Bearer [redacted]";
|
|
204
|
+
}
|
|
205
|
+
if (typeof sanitized.Authorization === "string" && sanitized.Authorization.length > 0) {
|
|
206
|
+
sanitized.Authorization = "Bearer [redacted]";
|
|
207
|
+
}
|
|
208
|
+
if (typeof sanitized["x-api-key"] === "string" && sanitized["x-api-key"].length > 0) {
|
|
209
|
+
sanitized["x-api-key"] = "[redacted]";
|
|
210
|
+
}
|
|
211
|
+
return sanitized;
|
|
212
|
+
}
|
|
179
213
|
function getInternalSessionID(request, init) {
|
|
180
214
|
const headerValue = getMergedRequestHeader(request, init, "x-opencode-session-id");
|
|
181
215
|
if (typeof headerValue === "string" && headerValue.length > 0)
|
|
@@ -365,6 +399,7 @@ export function buildPluginHooks(input) {
|
|
|
365
399
|
};
|
|
366
400
|
const refreshQuota = input.refreshQuota ?? ((store) => refreshActiveAccountQuota({ store }));
|
|
367
401
|
const handleStatusCommandImpl = input.handleStatusCommandImpl ?? handleStatusCommand;
|
|
402
|
+
const handleCodexStatusCommandImpl = input.handleCodexStatusCommandImpl ?? handleCodexStatusCommand;
|
|
368
403
|
const handleCompactCommandImpl = input.handleCompactCommandImpl ?? handleCompactCommand;
|
|
369
404
|
const handleStopToolCommandImpl = input.handleStopToolCommandImpl ?? handleStopToolCommand;
|
|
370
405
|
const loadOfficialConfig = input.loadOfficialConfig ?? loadOfficialCopilotConfig;
|
|
@@ -496,11 +531,20 @@ export function buildPluginHooks(input) {
|
|
|
496
531
|
});
|
|
497
532
|
const loader = async (getAuth, provider) => {
|
|
498
533
|
const authOverride = new AsyncLocalStorage();
|
|
534
|
+
const finalHeaderCapture = new AsyncLocalStorage();
|
|
499
535
|
const getScopedAuth = async () => authOverride.getStore() ?? getAuth();
|
|
500
536
|
const providerConfig = provider;
|
|
501
537
|
const config = await loadOfficialConfig({
|
|
502
538
|
getAuth: getScopedAuth,
|
|
503
539
|
provider: providerConfig,
|
|
540
|
+
...(input.loadOfficialConfig == null
|
|
541
|
+
? {
|
|
542
|
+
baseFetch: async (nextRequest, nextInit) => {
|
|
543
|
+
finalHeaderCapture.getStore()?.(getFinalSentRequestHeadersRecord(nextRequest, nextInit));
|
|
544
|
+
return fetch(nextRequest, nextInit);
|
|
545
|
+
},
|
|
546
|
+
}
|
|
547
|
+
: {}),
|
|
504
548
|
});
|
|
505
549
|
if (!config)
|
|
506
550
|
return {};
|
|
@@ -559,7 +603,7 @@ export function buildPluginHooks(input) {
|
|
|
559
603
|
const initiator = getMergedRequestHeader(selectionRequest, selectionInit, "x-initiator");
|
|
560
604
|
const candidates = latestStore ? resolveCopilotModelAccounts(latestStore, modelID) : [];
|
|
561
605
|
if (candidates.length === 0) {
|
|
562
|
-
const outbound = stripInternalSessionHeader(
|
|
606
|
+
const outbound = stripInternalSessionHeader(selectionRequest, selectionInit);
|
|
563
607
|
return config.fetch(outbound.request, outbound.init);
|
|
564
608
|
}
|
|
565
609
|
const hasExistingBinding = sessionID.length > 0 && sessionAccountBindings.has(sessionID);
|
|
@@ -603,7 +647,7 @@ export function buildPluginHooks(input) {
|
|
|
603
647
|
})
|
|
604
648
|
: undefined;
|
|
605
649
|
if (!resolved) {
|
|
606
|
-
const outbound = stripInternalSessionHeader(
|
|
650
|
+
const outbound = stripInternalSessionHeader(selectionRequest, selectionInit);
|
|
607
651
|
return config.fetch(outbound.request, outbound.init);
|
|
608
652
|
}
|
|
609
653
|
const candidateNames = candidates.map((item) => item.name);
|
|
@@ -617,6 +661,7 @@ export function buildPluginHooks(input) {
|
|
|
617
661
|
let decisionTouchWriteOutcome = "skipped-missing-session";
|
|
618
662
|
let decisionTouchWriteError;
|
|
619
663
|
let finalChosenAccount = resolved.name;
|
|
664
|
+
let finalRequestHeaders = getFinalSentRequestHeadersRecord(selectionRequest, selectionInit);
|
|
620
665
|
const previousBindingAccount = sessionID.length > 0 ? sessionAccountBindings.get(sessionID)?.accountName : undefined;
|
|
621
666
|
if (sessionID.length > 0) {
|
|
622
667
|
sessionAccountBindings.set(sessionID, {
|
|
@@ -642,13 +687,13 @@ export function buildPluginHooks(input) {
|
|
|
642
687
|
if (isFirstUse) {
|
|
643
688
|
modelAccountFirstUse.add(resolved.name);
|
|
644
689
|
}
|
|
645
|
-
let nextRequest =
|
|
646
|
-
let nextInit =
|
|
647
|
-
const currentInitiator = getMergedRequestHeader(
|
|
690
|
+
let nextRequest = selectionRequest;
|
|
691
|
+
let nextInit = selectionInit;
|
|
692
|
+
const currentInitiator = getMergedRequestHeader(selectionRequest, selectionInit, "x-initiator");
|
|
648
693
|
const shouldStripAgentInitiator = classification.reason === "unbound-fallback"
|
|
649
694
|
|| (isFirstUse && currentInitiator === "agent");
|
|
650
695
|
if (shouldStripAgentInitiator && currentInitiator === "agent") {
|
|
651
|
-
const rewritten = mergeAndRewriteRequestHeaders(
|
|
696
|
+
const rewritten = mergeAndRewriteRequestHeaders(selectionRequest, selectionInit, (headers) => {
|
|
652
697
|
headers.delete("x-initiator");
|
|
653
698
|
});
|
|
654
699
|
nextRequest = rewritten.request;
|
|
@@ -686,7 +731,9 @@ export function buildPluginHooks(input) {
|
|
|
686
731
|
enterpriseUrl: candidate.entry.enterpriseUrl,
|
|
687
732
|
};
|
|
688
733
|
const outbound = stripInternalSessionHeader(requestValue, initValue);
|
|
689
|
-
return
|
|
734
|
+
return finalHeaderCapture.run((headers) => {
|
|
735
|
+
finalRequestHeaders = headers;
|
|
736
|
+
}, () => authOverride.run(candidateAuth, () => config.fetch(rewriteRequestForAccount(outbound.request, candidate.entry.enterpriseUrl), outbound.init)));
|
|
690
737
|
};
|
|
691
738
|
const response = await sendWithAccount(resolved, nextRequest, nextInit);
|
|
692
739
|
const observedAt = now();
|
|
@@ -782,6 +829,7 @@ export function buildPluginHooks(input) {
|
|
|
782
829
|
// keep fail-open on payload parse failures
|
|
783
830
|
}
|
|
784
831
|
}
|
|
832
|
+
finalRequestHeaders = getFinalSentRequestHeadersRecord(retriedRequest, retriedInit);
|
|
785
833
|
if (sessionID.length > 0) {
|
|
786
834
|
sessionAccountBindings.set(sessionID, {
|
|
787
835
|
accountName: replacement.name,
|
|
@@ -847,6 +895,7 @@ export function buildPluginHooks(input) {
|
|
|
847
895
|
touchWriteError: decisionTouchWriteError,
|
|
848
896
|
rateLimitMatched: decisionRateLimitMatched,
|
|
849
897
|
retryAfterMs: decisionRetryAfterMs,
|
|
898
|
+
finalRequestHeaders,
|
|
850
899
|
},
|
|
851
900
|
}).catch(() => undefined);
|
|
852
901
|
return sendWithAccount(replacement, retriedRequest, retriedInit);
|
|
@@ -878,6 +927,7 @@ export function buildPluginHooks(input) {
|
|
|
878
927
|
touchWriteError: decisionTouchWriteError,
|
|
879
928
|
rateLimitMatched: decisionRateLimitMatched,
|
|
880
929
|
retryAfterMs: decisionRetryAfterMs,
|
|
930
|
+
finalRequestHeaders,
|
|
881
931
|
},
|
|
882
932
|
}).catch(() => undefined);
|
|
883
933
|
if (shouldShowConsumptionToast({ reason: decisionReason, isFirstUse })) {
|
|
@@ -925,7 +975,7 @@ export function buildPluginHooks(input) {
|
|
|
925
975
|
directory: input.directory,
|
|
926
976
|
});
|
|
927
977
|
const chatHeaders = async (hookInput, output) => {
|
|
928
|
-
if (!
|
|
978
|
+
if (!isCopilotProviderID(hookInput.model.providerID))
|
|
929
979
|
return;
|
|
930
980
|
const headersBeforeOfficial = { ...output.headers };
|
|
931
981
|
await (await officialChatHeaders)(hookInput, output);
|
|
@@ -1023,7 +1073,7 @@ export function buildPluginHooks(input) {
|
|
|
1023
1073
|
return {
|
|
1024
1074
|
auth: {
|
|
1025
1075
|
...input.auth,
|
|
1026
|
-
provider: input.auth.provider ?? "github-copilot",
|
|
1076
|
+
provider: input.auth.provider ?? COPILOT_PROVIDER_DESCRIPTOR.providerIDs[0] ?? "github-copilot",
|
|
1027
1077
|
methods: input.auth.methods,
|
|
1028
1078
|
loader,
|
|
1029
1079
|
},
|
|
@@ -1031,8 +1081,13 @@ export function buildPluginHooks(input) {
|
|
|
1031
1081
|
if (!config.command)
|
|
1032
1082
|
config.command = {};
|
|
1033
1083
|
const store = loadStoreSync();
|
|
1034
|
-
if (!areExperimentalSlashCommandsEnabled(store))
|
|
1084
|
+
if (!areExperimentalSlashCommandsEnabled(store)) {
|
|
1035
1085
|
return;
|
|
1086
|
+
}
|
|
1087
|
+
config.command["codex-status"] = {
|
|
1088
|
+
template: "Show the current Codex status and usage snapshot via the experimental status path.",
|
|
1089
|
+
description: "Experimental Codex status command",
|
|
1090
|
+
};
|
|
1036
1091
|
config.command["copilot-status"] = {
|
|
1037
1092
|
template: "Show the current GitHub Copilot quota status via the experimental workaround path.",
|
|
1038
1093
|
description: "Experimental Copilot quota status workaround",
|
|
@@ -1083,6 +1138,13 @@ export function buildPluginHooks(input) {
|
|
|
1083
1138
|
refreshQuota,
|
|
1084
1139
|
});
|
|
1085
1140
|
}
|
|
1141
|
+
if (hookInput.command === "codex-status") {
|
|
1142
|
+
if (!areExperimentalSlashCommandsEnabled(store))
|
|
1143
|
+
return;
|
|
1144
|
+
await handleCodexStatusCommandImpl({
|
|
1145
|
+
client: input.client,
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1086
1148
|
if (hookInput.command === "copilot-compact") {
|
|
1087
1149
|
if (!areExperimentalSlashCommandsEnabled(store))
|
|
1088
1150
|
return;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { CODEX_PROVIDER_DESCRIPTOR, COPILOT_PROVIDER_DESCRIPTOR, createCodexProviderDescriptor, createCopilotProviderDescriptor, } from "./providers/descriptor.js";
|
|
2
|
+
export type { AssembledProviderDescriptor, ProviderCapability, ProviderDescriptor, } from "./providers/descriptor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CODEX_PROVIDER_DESCRIPTOR, COPILOT_PROVIDER_DESCRIPTOR, createCodexProviderDescriptor, createCopilotProviderDescriptor, } from "./providers/descriptor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createProviderRegistry, getProviderDescriptorByKey, getProviderDescriptorByProviderID, isProviderIDSupportedByAnyDescriptor, listProviderDescriptors, } from "./providers/registry.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createProviderRegistry, getProviderDescriptorByKey, getProviderDescriptorByProviderID, isProviderIDSupportedByAnyDescriptor, listProviderDescriptors, } from "./providers/registry.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { buildPluginHooks as buildPluginHooksFn } from "../plugin-hooks.js";
|
|
2
|
+
export type ProviderCapability = "auth" | "chat-headers" | "model-routing" | "network-retry" | "slash-commands" | "loop-safety";
|
|
3
|
+
export type ProviderDescriptor = {
|
|
4
|
+
key: string;
|
|
5
|
+
providerIDs: string[];
|
|
6
|
+
storeNamespace: string;
|
|
7
|
+
commands: string[];
|
|
8
|
+
menuEntries: string[];
|
|
9
|
+
capabilities: ProviderCapability[];
|
|
10
|
+
};
|
|
11
|
+
type BuildPluginHooks = typeof buildPluginHooksFn;
|
|
12
|
+
export type AssembledProviderDescriptor = {
|
|
13
|
+
key: string;
|
|
14
|
+
auth: {
|
|
15
|
+
provider: string;
|
|
16
|
+
};
|
|
17
|
+
buildPluginHooks: BuildPluginHooks;
|
|
18
|
+
enabledByDefault: boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare const COPILOT_PROVIDER_DESCRIPTOR: ProviderDescriptor;
|
|
21
|
+
export declare const CODEX_PROVIDER_DESCRIPTOR: ProviderDescriptor;
|
|
22
|
+
export declare function createCopilotProviderDescriptor(input: {
|
|
23
|
+
buildPluginHooks: BuildPluginHooks;
|
|
24
|
+
}): AssembledProviderDescriptor;
|
|
25
|
+
export declare function createCodexProviderDescriptor(input?: {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
}): Omit<AssembledProviderDescriptor, "buildPluginHooks">;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const COPILOT_PROVIDER_DESCRIPTOR = {
|
|
2
|
+
key: "copilot",
|
|
3
|
+
providerIDs: [
|
|
4
|
+
"github-copilot",
|
|
5
|
+
"github-copilot-enterprise",
|
|
6
|
+
],
|
|
7
|
+
storeNamespace: "copilot",
|
|
8
|
+
commands: [
|
|
9
|
+
"copilot-status",
|
|
10
|
+
"copilot-compact",
|
|
11
|
+
"copilot-stop-tool",
|
|
12
|
+
"copilot-inject",
|
|
13
|
+
"copilot-policy-all-models",
|
|
14
|
+
],
|
|
15
|
+
menuEntries: [
|
|
16
|
+
"switch-account",
|
|
17
|
+
"add-account",
|
|
18
|
+
"import-auth",
|
|
19
|
+
"quota-refresh",
|
|
20
|
+
"configure-default-account-group",
|
|
21
|
+
"assign-model-account",
|
|
22
|
+
"toggle-loop-safety",
|
|
23
|
+
"toggle-network-retry",
|
|
24
|
+
],
|
|
25
|
+
capabilities: [
|
|
26
|
+
"auth",
|
|
27
|
+
"chat-headers",
|
|
28
|
+
"model-routing",
|
|
29
|
+
"network-retry",
|
|
30
|
+
"slash-commands",
|
|
31
|
+
"loop-safety",
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
export const CODEX_PROVIDER_DESCRIPTOR = {
|
|
35
|
+
key: "codex",
|
|
36
|
+
providerIDs: [
|
|
37
|
+
"openai",
|
|
38
|
+
],
|
|
39
|
+
storeNamespace: "codex",
|
|
40
|
+
commands: [
|
|
41
|
+
"codex-status",
|
|
42
|
+
],
|
|
43
|
+
menuEntries: [],
|
|
44
|
+
capabilities: [
|
|
45
|
+
"slash-commands",
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
export function createCopilotProviderDescriptor(input) {
|
|
49
|
+
return {
|
|
50
|
+
key: "copilot",
|
|
51
|
+
auth: {
|
|
52
|
+
provider: "github-copilot",
|
|
53
|
+
},
|
|
54
|
+
buildPluginHooks: input.buildPluginHooks,
|
|
55
|
+
enabledByDefault: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function createCodexProviderDescriptor(input = {}) {
|
|
59
|
+
return {
|
|
60
|
+
key: "codex",
|
|
61
|
+
auth: {
|
|
62
|
+
provider: "openai",
|
|
63
|
+
},
|
|
64
|
+
enabledByDefault: input.enabled === true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ProviderDescriptor } from "./descriptor.js";
|
|
2
|
+
import type { buildPluginHooks as buildPluginHooksFn } from "../plugin-hooks.js";
|
|
3
|
+
export declare function listProviderDescriptors(): ProviderDescriptor[];
|
|
4
|
+
export declare function getProviderDescriptorByKey(key: string): ProviderDescriptor | undefined;
|
|
5
|
+
export declare function getProviderDescriptorByProviderID(providerID: string): ProviderDescriptor | undefined;
|
|
6
|
+
export declare function isProviderIDSupportedByAnyDescriptor(providerID: string): boolean;
|
|
7
|
+
type BuildPluginHooks = typeof buildPluginHooksFn;
|
|
8
|
+
export declare function createProviderRegistry(input: {
|
|
9
|
+
buildPluginHooks: BuildPluginHooks;
|
|
10
|
+
}): {
|
|
11
|
+
copilot: {
|
|
12
|
+
descriptor: import("./descriptor.js").AssembledProviderDescriptor;
|
|
13
|
+
};
|
|
14
|
+
codex: {
|
|
15
|
+
descriptor: Omit<import("./descriptor.js").AssembledProviderDescriptor, "buildPluginHooks">;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { COPILOT_PROVIDER_DESCRIPTOR } from "./descriptor.js";
|
|
2
|
+
import { createCodexProviderDescriptor, createCopilotProviderDescriptor } from "./descriptor.js";
|
|
3
|
+
const PROVIDER_DESCRIPTORS = [
|
|
4
|
+
COPILOT_PROVIDER_DESCRIPTOR,
|
|
5
|
+
];
|
|
6
|
+
export function listProviderDescriptors() {
|
|
7
|
+
return [...PROVIDER_DESCRIPTORS];
|
|
8
|
+
}
|
|
9
|
+
export function getProviderDescriptorByKey(key) {
|
|
10
|
+
return PROVIDER_DESCRIPTORS.find((descriptor) => descriptor.key === key);
|
|
11
|
+
}
|
|
12
|
+
export function getProviderDescriptorByProviderID(providerID) {
|
|
13
|
+
return PROVIDER_DESCRIPTORS.find((descriptor) => descriptor.providerIDs.includes(providerID));
|
|
14
|
+
}
|
|
15
|
+
export function isProviderIDSupportedByAnyDescriptor(providerID) {
|
|
16
|
+
return getProviderDescriptorByProviderID(providerID) !== undefined;
|
|
17
|
+
}
|
|
18
|
+
export function createProviderRegistry(input) {
|
|
19
|
+
return {
|
|
20
|
+
copilot: {
|
|
21
|
+
descriptor: createCopilotProviderDescriptor({ buildPluginHooks: input.buildPluginHooks }),
|
|
22
|
+
},
|
|
23
|
+
codex: {
|
|
24
|
+
descriptor: createCodexProviderDescriptor({ enabled: false }),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { NetworkRetryPolicy } from "../network-retry-engine.js";
|
|
2
|
+
export declare const COPILOT_RETRYABLE_MESSAGES: string[];
|
|
3
|
+
export type RetryableErrorGroup = "transport" | "status" | "stream";
|
|
4
|
+
type RetryableApiCallError = Error & {
|
|
5
|
+
url: string;
|
|
6
|
+
requestBodyValues: unknown;
|
|
7
|
+
statusCode?: number;
|
|
8
|
+
responseHeaders?: Record<string, string>;
|
|
9
|
+
responseBody?: string;
|
|
10
|
+
isRetryable: boolean;
|
|
11
|
+
cause: unknown;
|
|
12
|
+
[key: symbol]: unknown;
|
|
13
|
+
};
|
|
14
|
+
type CopilotStreamErrorInput = {
|
|
15
|
+
error: unknown;
|
|
16
|
+
request: Request | URL | string;
|
|
17
|
+
statusCode?: number;
|
|
18
|
+
responseHeaders?: Headers;
|
|
19
|
+
};
|
|
20
|
+
type JsonRecord = Record<string, unknown>;
|
|
21
|
+
export type CopilotRepairDecision = {
|
|
22
|
+
kind: "skip";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "connection-mismatch";
|
|
25
|
+
responseText: string;
|
|
26
|
+
shouldAttemptSessionRepair: boolean;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "input-id-too-long";
|
|
29
|
+
responseText: string;
|
|
30
|
+
serverReportedIndex?: number;
|
|
31
|
+
reportedLength?: number;
|
|
32
|
+
shouldAttemptSessionRepair: boolean;
|
|
33
|
+
};
|
|
34
|
+
export type CopilotRetryPolicy = NetworkRetryPolicy & {
|
|
35
|
+
shouldRunResponseRepair: (request: Request | URL | string) => boolean;
|
|
36
|
+
decideResponseRepair: (input: {
|
|
37
|
+
request: Request | URL | string;
|
|
38
|
+
response: Response;
|
|
39
|
+
requestPayload: JsonRecord | undefined;
|
|
40
|
+
sessionID?: string;
|
|
41
|
+
}) => Promise<CopilotRepairDecision>;
|
|
42
|
+
normalizeStreamError: (input: CopilotStreamErrorInput) => unknown;
|
|
43
|
+
};
|
|
44
|
+
type CreateCopilotRetryPolicyOptions = {
|
|
45
|
+
extraRetryableClassifier?: (error: unknown) => boolean;
|
|
46
|
+
};
|
|
47
|
+
export declare function isCopilotUrl(request: Request | URL | string): boolean;
|
|
48
|
+
export declare function isRetryableCopilotTransportError(error: unknown): boolean;
|
|
49
|
+
export declare function toRetryableApiCallError(error: unknown, request: {
|
|
50
|
+
url: string;
|
|
51
|
+
body?: string;
|
|
52
|
+
}, options?: {
|
|
53
|
+
group?: RetryableErrorGroup;
|
|
54
|
+
requestBodyValues?: unknown;
|
|
55
|
+
statusCode?: number;
|
|
56
|
+
responseHeaders?: Headers | Record<string, string>;
|
|
57
|
+
responseBody?: string;
|
|
58
|
+
}): RetryableApiCallError;
|
|
59
|
+
export declare function isRetryableApiCallError(error: unknown): error is RetryableApiCallError;
|
|
60
|
+
export declare function createCopilotRetryPolicy(options?: CreateCopilotRetryPolicyOptions): CopilotRetryPolicy;
|
|
61
|
+
export {};
|