opencode-copilot-account-switcher 0.12.0 → 0.12.2
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type OpenAIOAuthAuth } from "./codex-auth-source.js";
|
|
2
2
|
import { type CodexStatusFetcherResult } from "./codex-status-fetcher.js";
|
|
3
3
|
import { type CodexStoreFile } from "./codex-store.js";
|
|
4
|
+
import { type AccountEntry } from "./store.js";
|
|
4
5
|
type ToastVariant = "info" | "success" | "warning" | "error";
|
|
5
6
|
type ToastClient = {
|
|
6
7
|
tui?: {
|
|
@@ -36,12 +37,14 @@ type ToastClient = {
|
|
|
36
37
|
type AuthPayload = {
|
|
37
38
|
openai?: OpenAIOAuthAuth;
|
|
38
39
|
} & Record<string, unknown>;
|
|
40
|
+
type AuthEntries = Record<string, AccountEntry>;
|
|
39
41
|
export declare class CodexStatusCommandHandledError extends Error {
|
|
40
42
|
constructor();
|
|
41
43
|
}
|
|
42
44
|
export declare function handleCodexStatusCommand(input: {
|
|
43
45
|
client?: ToastClient;
|
|
44
46
|
loadAuth?: () => Promise<AuthPayload | undefined>;
|
|
47
|
+
readAuthEntries?: () => Promise<AuthEntries>;
|
|
45
48
|
persistAuth?: (auth: AuthPayload) => Promise<void>;
|
|
46
49
|
fetchStatus?: (input: {
|
|
47
50
|
oauth: OpenAIOAuthAuth;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveCodexAuthSource } from "./codex-auth-source.js";
|
|
2
2
|
import { fetchCodexStatus } from "./codex-status-fetcher.js";
|
|
3
3
|
import { readCodexStore, writeCodexStore } from "./codex-store.js";
|
|
4
|
+
import { readAuth } from "./store.js";
|
|
4
5
|
export class CodexStatusCommandHandledError extends Error {
|
|
5
6
|
constructor() {
|
|
6
7
|
super("codex-status-command-handled");
|
|
@@ -19,11 +20,12 @@ function pickNumber(value) {
|
|
|
19
20
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20
21
|
}
|
|
21
22
|
async function showToast(input) {
|
|
23
|
+
const tui = input.client?.tui;
|
|
22
24
|
const show = input.client?.tui?.showToast;
|
|
23
25
|
if (!show)
|
|
24
26
|
return;
|
|
25
27
|
try {
|
|
26
|
-
await show({
|
|
28
|
+
await show.call(tui, {
|
|
27
29
|
body: {
|
|
28
30
|
message: input.message,
|
|
29
31
|
variant: input.variant,
|
|
@@ -77,31 +79,57 @@ function hasCachedStore(store) {
|
|
|
77
79
|
|| store.status?.premium?.remaining !== undefined);
|
|
78
80
|
}
|
|
79
81
|
async function defaultLoadAuth(client) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
return defaultLoadAuthWithFallback({
|
|
83
|
+
client,
|
|
84
|
+
readAuthEntries: readAuth,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function mapAuthEntryToOpenAI(entry) {
|
|
88
|
+
if (!entry)
|
|
82
89
|
return undefined;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
return {
|
|
91
|
+
type: "oauth",
|
|
92
|
+
refresh: entry.refresh,
|
|
93
|
+
access: entry.access,
|
|
94
|
+
expires: entry.expires,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function defaultLoadAuthWithFallback(input) {
|
|
98
|
+
const client = input.client;
|
|
99
|
+
const authClient = client?.auth;
|
|
100
|
+
const getAuth = client?.auth?.get;
|
|
101
|
+
if (getAuth) {
|
|
102
|
+
try {
|
|
103
|
+
const result = await getAuth.call(authClient, { path: { id: "openai" }, throwOnError: true });
|
|
104
|
+
const withData = asRecord(result)?.data;
|
|
105
|
+
const payload = asRecord(withData) ?? asRecord(result);
|
|
106
|
+
if (payload) {
|
|
107
|
+
return {
|
|
108
|
+
openai: payload,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// fall through to auth.json fallback
|
|
114
|
+
}
|
|
92
115
|
}
|
|
93
|
-
catch {
|
|
116
|
+
const authEntries = await input.readAuthEntries().catch(() => ({}));
|
|
117
|
+
const openai = mapAuthEntryToOpenAI(authEntries.openai);
|
|
118
|
+
if (!openai)
|
|
94
119
|
return undefined;
|
|
95
|
-
|
|
120
|
+
return {
|
|
121
|
+
openai,
|
|
122
|
+
};
|
|
96
123
|
}
|
|
97
124
|
async function defaultPersistAuth(client, auth) {
|
|
125
|
+
const authClient = client?.auth;
|
|
98
126
|
const setAuth = client?.auth?.set;
|
|
99
127
|
if (!setAuth)
|
|
100
128
|
return;
|
|
101
129
|
const openai = asRecord(auth.openai);
|
|
102
130
|
if (!openai)
|
|
103
131
|
return;
|
|
104
|
-
await setAuth({
|
|
132
|
+
await setAuth.call(authClient, {
|
|
105
133
|
path: { id: "openai" },
|
|
106
134
|
body: {
|
|
107
135
|
type: "oauth",
|
|
@@ -128,7 +156,10 @@ function patchAuth(auth, patch) {
|
|
|
128
156
|
};
|
|
129
157
|
}
|
|
130
158
|
export async function handleCodexStatusCommand(input) {
|
|
131
|
-
const loadAuth = input.loadAuth ?? (() =>
|
|
159
|
+
const loadAuth = input.loadAuth ?? (() => defaultLoadAuthWithFallback({
|
|
160
|
+
client: input.client,
|
|
161
|
+
readAuthEntries: input.readAuthEntries ?? readAuth,
|
|
162
|
+
}));
|
|
132
163
|
const persistAuth = input.persistAuth ?? ((nextAuth) => defaultPersistAuth(input.client, nextAuth));
|
|
133
164
|
const fetchStatus = input.fetchStatus ?? ((next) => fetchCodexStatus(next));
|
|
134
165
|
const readStore = input.readStore ?? (() => readCodexStore());
|
package/dist/plugin-hooks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendFileSync } from "node:fs";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import { createCompactionLoopSafetyBypass, createLoopSafetySystemTransform, getLoopSafetyProviderScope, } from "./loop-safety-plugin.js";
|
|
4
5
|
import { COPILOT_PROVIDER_DESCRIPTOR } from "./providers/descriptor.js";
|
|
5
6
|
import { createCopilotRetryingFetch, cleanupLongIdsForAccountSwitch, detectRateLimitEvidence, INTERNAL_SESSION_CONTEXT_KEY, } from "./copilot-network-retry.js";
|
|
@@ -164,9 +165,11 @@ function mergeAndRewriteRequestHeaders(request, init, rewriteHeaders) {
|
|
|
164
165
|
headers: normalizedHeaders,
|
|
165
166
|
};
|
|
166
167
|
if (request instanceof Request) {
|
|
168
|
+
const requestInit = { ...(init ?? {}) };
|
|
169
|
+
delete requestInit.headers;
|
|
167
170
|
return {
|
|
168
171
|
request: rewriteRequestHeaders(request),
|
|
169
|
-
init:
|
|
172
|
+
init: Object.keys(requestInit).length > 0 ? requestInit : undefined,
|
|
170
173
|
};
|
|
171
174
|
}
|
|
172
175
|
return {
|
|
@@ -174,6 +177,30 @@ function mergeAndRewriteRequestHeaders(request, init, rewriteHeaders) {
|
|
|
174
177
|
init: normalizedInit,
|
|
175
178
|
};
|
|
176
179
|
}
|
|
180
|
+
function stripDuplicateHeadersFromRequestWhenInitOverrides(request, init) {
|
|
181
|
+
if (!(request instanceof Request) || init?.headers == null) {
|
|
182
|
+
return { request, init };
|
|
183
|
+
}
|
|
184
|
+
const initHeaders = new Headers(init.headers);
|
|
185
|
+
if ([...initHeaders.keys()].length === 0) {
|
|
186
|
+
return { request, init };
|
|
187
|
+
}
|
|
188
|
+
const requestHeaders = new Headers(request.headers);
|
|
189
|
+
let changed = false;
|
|
190
|
+
for (const name of initHeaders.keys()) {
|
|
191
|
+
if (requestHeaders.has(name)) {
|
|
192
|
+
requestHeaders.delete(name);
|
|
193
|
+
changed = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!changed) {
|
|
197
|
+
return { request, init };
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
request: new Request(request, { headers: requestHeaders }),
|
|
201
|
+
init,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
177
204
|
function getMergedRequestHeader(request, init, name) {
|
|
178
205
|
const headers = new Headers(request instanceof Request ? request.headers : undefined);
|
|
179
206
|
for (const [headerName, value] of new Headers(init?.headers).entries()) {
|
|
@@ -210,6 +237,11 @@ function sanitizeLoggedRequestHeadersRecord(headers) {
|
|
|
210
237
|
}
|
|
211
238
|
return sanitized;
|
|
212
239
|
}
|
|
240
|
+
function toAuthFingerprint(value) {
|
|
241
|
+
if (typeof value !== "string" || value.length === 0)
|
|
242
|
+
return undefined;
|
|
243
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
244
|
+
}
|
|
213
245
|
function getInternalSessionID(request, init) {
|
|
214
246
|
const headerValue = getMergedRequestHeader(request, init, "x-opencode-session-id");
|
|
215
247
|
if (typeof headerValue === "string" && headerValue.length > 0)
|
|
@@ -661,8 +693,12 @@ export function buildPluginHooks(input) {
|
|
|
661
693
|
let decisionTouchWriteOutcome = "skipped-missing-session";
|
|
662
694
|
let decisionTouchWriteError;
|
|
663
695
|
let finalChosenAccount = resolved.name;
|
|
696
|
+
let chosenAccountAuthFingerprint = toAuthFingerprint(resolved.entry.refresh);
|
|
664
697
|
let finalRequestHeaders = getFinalSentRequestHeadersRecord(selectionRequest, selectionInit);
|
|
698
|
+
let networkRequestHeaders;
|
|
699
|
+
let networkRequestUsedInitHeaders = selectionInit?.headers != null;
|
|
665
700
|
const previousBindingAccount = sessionID.length > 0 ? sessionAccountBindings.get(sessionID)?.accountName : undefined;
|
|
701
|
+
const debugLinkId = getMergedRequestHeader(selectionRequest, selectionInit, INTERNAL_DEBUG_LINK_HEADER) ?? undefined;
|
|
666
702
|
if (sessionID.length > 0) {
|
|
667
703
|
sessionAccountBindings.set(sessionID, {
|
|
668
704
|
accountName: resolved.name,
|
|
@@ -730,9 +766,12 @@ export function buildPluginHooks(input) {
|
|
|
730
766
|
expires: candidate.entry.expires,
|
|
731
767
|
enterpriseUrl: candidate.entry.enterpriseUrl,
|
|
732
768
|
};
|
|
733
|
-
const
|
|
769
|
+
const deduplicated = stripDuplicateHeadersFromRequestWhenInitOverrides(requestValue, initValue);
|
|
770
|
+
const outbound = stripInternalSessionHeader(deduplicated.request, deduplicated.init);
|
|
734
771
|
return finalHeaderCapture.run((headers) => {
|
|
735
772
|
finalRequestHeaders = headers;
|
|
773
|
+
networkRequestHeaders = headers;
|
|
774
|
+
networkRequestUsedInitHeaders = outbound.init?.headers != null;
|
|
736
775
|
}, () => authOverride.run(candidateAuth, () => config.fetch(rewriteRequestForAccount(outbound.request, candidate.entry.enterpriseUrl), outbound.init)));
|
|
737
776
|
};
|
|
738
777
|
const response = await sendWithAccount(resolved, nextRequest, nextInit);
|
|
@@ -856,6 +895,7 @@ export function buildPluginHooks(input) {
|
|
|
856
895
|
decisionSwitchFrom = resolved.name;
|
|
857
896
|
decisionSwitchBlockedBy = undefined;
|
|
858
897
|
finalChosenAccount = replacement.name;
|
|
898
|
+
chosenAccountAuthFingerprint = toAuthFingerprint(replacement.entry.refresh);
|
|
859
899
|
modelAccountFirstUse.add(replacement.name);
|
|
860
900
|
const switchToast = buildConsumptionToast({
|
|
861
901
|
accountName: replacement.name,
|
|
@@ -887,6 +927,9 @@ export function buildPluginHooks(input) {
|
|
|
887
927
|
candidateNames,
|
|
888
928
|
loads: decisionLoads,
|
|
889
929
|
chosenAccount: finalChosenAccount,
|
|
930
|
+
chosenAccountAuthFingerprint,
|
|
931
|
+
debugLinkId,
|
|
932
|
+
networkRequestUsedInitHeaders,
|
|
890
933
|
reason: decisionReason,
|
|
891
934
|
switched: decisionSwitched,
|
|
892
935
|
switchFrom: decisionSwitchFrom,
|
|
@@ -896,6 +939,7 @@ export function buildPluginHooks(input) {
|
|
|
896
939
|
rateLimitMatched: decisionRateLimitMatched,
|
|
897
940
|
retryAfterMs: decisionRetryAfterMs,
|
|
898
941
|
finalRequestHeaders,
|
|
942
|
+
networkRequestHeaders,
|
|
899
943
|
},
|
|
900
944
|
}).catch(() => undefined);
|
|
901
945
|
return sendWithAccount(replacement, retriedRequest, retriedInit);
|
|
@@ -919,6 +963,9 @@ export function buildPluginHooks(input) {
|
|
|
919
963
|
candidateNames,
|
|
920
964
|
loads: decisionLoads,
|
|
921
965
|
chosenAccount: finalChosenAccount,
|
|
966
|
+
chosenAccountAuthFingerprint,
|
|
967
|
+
debugLinkId,
|
|
968
|
+
networkRequestUsedInitHeaders,
|
|
922
969
|
reason: decisionReason,
|
|
923
970
|
switched: decisionSwitched,
|
|
924
971
|
switchFrom: decisionSwitchFrom,
|
|
@@ -928,6 +975,7 @@ export function buildPluginHooks(input) {
|
|
|
928
975
|
rateLimitMatched: decisionRateLimitMatched,
|
|
929
976
|
retryAfterMs: decisionRetryAfterMs,
|
|
930
977
|
finalRequestHeaders,
|
|
978
|
+
networkRequestHeaders,
|
|
931
979
|
},
|
|
932
980
|
}).catch(() => undefined);
|
|
933
981
|
if (shouldShowConsumptionToast({ reason: decisionReason, isFirstUse })) {
|
|
@@ -1177,6 +1225,8 @@ export function buildPluginHooks(input) {
|
|
|
1177
1225
|
injectArmed = false;
|
|
1178
1226
|
return;
|
|
1179
1227
|
}
|
|
1228
|
+
if (hookInput.tool === "task")
|
|
1229
|
+
return;
|
|
1180
1230
|
if (!injectArmed)
|
|
1181
1231
|
return;
|
|
1182
1232
|
const begin = "[COPILOT_INJECT_V1_BEGIN]";
|
package/dist/routing-state.d.ts
CHANGED
|
@@ -64,6 +64,9 @@ export type RouteDecisionEvent = {
|
|
|
64
64
|
at: number;
|
|
65
65
|
modelID?: string;
|
|
66
66
|
chosenAccount: string;
|
|
67
|
+
chosenAccountAuthFingerprint?: string;
|
|
68
|
+
debugLinkId?: string;
|
|
69
|
+
networkRequestUsedInitHeaders?: boolean;
|
|
67
70
|
sessionID?: string;
|
|
68
71
|
sessionIDPresent: boolean;
|
|
69
72
|
groupSource: "model" | "active";
|
|
@@ -78,6 +81,7 @@ export type RouteDecisionEvent = {
|
|
|
78
81
|
rateLimitMatched: boolean;
|
|
79
82
|
retryAfterMs?: number;
|
|
80
83
|
finalRequestHeaders?: Record<string, string>;
|
|
84
|
+
networkRequestHeaders?: Record<string, string>;
|
|
81
85
|
};
|
|
82
86
|
export type RoutingEvent = SessionTouchEvent | RateLimitFlaggedEvent;
|
|
83
87
|
export declare function appendRouteDecisionEvent(input: {
|
|
@@ -246,26 +246,20 @@ async function patchStoppedToolTranscript(input) {
|
|
|
246
246
|
export async function handleCompactCommand(input) {
|
|
247
247
|
const session = input.client?.session;
|
|
248
248
|
const summarize = session?.summarize;
|
|
249
|
-
|
|
250
|
-
if (!model) {
|
|
249
|
+
if (!summarize) {
|
|
251
250
|
await showToast({
|
|
252
251
|
client: input.client,
|
|
253
|
-
message: "
|
|
252
|
+
message: "Session summarize is unavailable for compact.",
|
|
254
253
|
variant: "warning",
|
|
255
254
|
});
|
|
256
255
|
throw new SessionControlCommandHandledError();
|
|
257
256
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
await showToast({
|
|
266
|
-
client: input.client,
|
|
267
|
-
message: "Session summarize is unavailable for compact.",
|
|
268
|
-
variant: "warning",
|
|
257
|
+
const model = input.model ?? getLatestAssistantModel(await getSessionMessages(session, input.sessionID));
|
|
258
|
+
await summarize(model ? {
|
|
259
|
+
auto: true,
|
|
260
|
+
model,
|
|
261
|
+
} : {
|
|
262
|
+
auto: true,
|
|
269
263
|
});
|
|
270
264
|
throw new SessionControlCommandHandledError();
|
|
271
265
|
}
|