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.
@@ -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[originalName] ?? originalName;
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
+ }
@@ -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: {
@@ -1,6 +1,7 @@
1
1
  import { appendFileSync } from "node:fs";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
- import { createCompactionLoopSafetyBypass, createLoopSafetySystemTransform, getLoopSafetyProviderScope, isCopilotProvider, } from "./loop-safety-plugin.js";
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(request, init);
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(request, init);
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 = request;
646
- let nextInit = init;
647
- const currentInitiator = getMergedRequestHeader(request, init, "x-initiator");
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(request, init, (headers) => {
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 authOverride.run(candidateAuth, () => config.fetch(rewriteRequestForAccount(outbound.request, candidate.entry.enterpriseUrl), outbound.init));
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 (!isCopilotProvider(hookInput.model.providerID))
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 {};