opencode-copilot-account-switcher 0.12.1 → 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");
|
|
@@ -78,23 +79,47 @@ function hasCachedStore(store) {
|
|
|
78
79
|
|| store.status?.premium?.remaining !== undefined);
|
|
79
80
|
}
|
|
80
81
|
async function defaultLoadAuth(client) {
|
|
82
|
+
return defaultLoadAuthWithFallback({
|
|
83
|
+
client,
|
|
84
|
+
readAuthEntries: readAuth,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function mapAuthEntryToOpenAI(entry) {
|
|
88
|
+
if (!entry)
|
|
89
|
+
return undefined;
|
|
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;
|
|
81
99
|
const authClient = client?.auth;
|
|
82
100
|
const getAuth = client?.auth?.get;
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
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
|
+
}
|
|
94
115
|
}
|
|
95
|
-
catch {
|
|
116
|
+
const authEntries = await input.readAuthEntries().catch(() => ({}));
|
|
117
|
+
const openai = mapAuthEntryToOpenAI(authEntries.openai);
|
|
118
|
+
if (!openai)
|
|
96
119
|
return undefined;
|
|
97
|
-
|
|
120
|
+
return {
|
|
121
|
+
openai,
|
|
122
|
+
};
|
|
98
123
|
}
|
|
99
124
|
async function defaultPersistAuth(client, auth) {
|
|
100
125
|
const authClient = client?.auth;
|
|
@@ -131,7 +156,10 @@ function patchAuth(auth, patch) {
|
|
|
131
156
|
};
|
|
132
157
|
}
|
|
133
158
|
export async function handleCodexStatusCommand(input) {
|
|
134
|
-
const loadAuth = input.loadAuth ?? (() =>
|
|
159
|
+
const loadAuth = input.loadAuth ?? (() => defaultLoadAuthWithFallback({
|
|
160
|
+
client: input.client,
|
|
161
|
+
readAuthEntries: input.readAuthEntries ?? readAuth,
|
|
162
|
+
}));
|
|
135
163
|
const persistAuth = input.persistAuth ?? ((nextAuth) => defaultPersistAuth(input.client, nextAuth));
|
|
136
164
|
const fetchStatus = input.fetchStatus ?? ((next) => fetchCodexStatus(next));
|
|
137
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,9 +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);
|
|
665
698
|
let networkRequestHeaders;
|
|
699
|
+
let networkRequestUsedInitHeaders = selectionInit?.headers != null;
|
|
666
700
|
const previousBindingAccount = sessionID.length > 0 ? sessionAccountBindings.get(sessionID)?.accountName : undefined;
|
|
701
|
+
const debugLinkId = getMergedRequestHeader(selectionRequest, selectionInit, INTERNAL_DEBUG_LINK_HEADER) ?? undefined;
|
|
667
702
|
if (sessionID.length > 0) {
|
|
668
703
|
sessionAccountBindings.set(sessionID, {
|
|
669
704
|
accountName: resolved.name,
|
|
@@ -731,10 +766,12 @@ export function buildPluginHooks(input) {
|
|
|
731
766
|
expires: candidate.entry.expires,
|
|
732
767
|
enterpriseUrl: candidate.entry.enterpriseUrl,
|
|
733
768
|
};
|
|
734
|
-
const
|
|
769
|
+
const deduplicated = stripDuplicateHeadersFromRequestWhenInitOverrides(requestValue, initValue);
|
|
770
|
+
const outbound = stripInternalSessionHeader(deduplicated.request, deduplicated.init);
|
|
735
771
|
return finalHeaderCapture.run((headers) => {
|
|
736
772
|
finalRequestHeaders = headers;
|
|
737
773
|
networkRequestHeaders = headers;
|
|
774
|
+
networkRequestUsedInitHeaders = outbound.init?.headers != null;
|
|
738
775
|
}, () => authOverride.run(candidateAuth, () => config.fetch(rewriteRequestForAccount(outbound.request, candidate.entry.enterpriseUrl), outbound.init)));
|
|
739
776
|
};
|
|
740
777
|
const response = await sendWithAccount(resolved, nextRequest, nextInit);
|
|
@@ -858,6 +895,7 @@ export function buildPluginHooks(input) {
|
|
|
858
895
|
decisionSwitchFrom = resolved.name;
|
|
859
896
|
decisionSwitchBlockedBy = undefined;
|
|
860
897
|
finalChosenAccount = replacement.name;
|
|
898
|
+
chosenAccountAuthFingerprint = toAuthFingerprint(replacement.entry.refresh);
|
|
861
899
|
modelAccountFirstUse.add(replacement.name);
|
|
862
900
|
const switchToast = buildConsumptionToast({
|
|
863
901
|
accountName: replacement.name,
|
|
@@ -889,6 +927,9 @@ export function buildPluginHooks(input) {
|
|
|
889
927
|
candidateNames,
|
|
890
928
|
loads: decisionLoads,
|
|
891
929
|
chosenAccount: finalChosenAccount,
|
|
930
|
+
chosenAccountAuthFingerprint,
|
|
931
|
+
debugLinkId,
|
|
932
|
+
networkRequestUsedInitHeaders,
|
|
892
933
|
reason: decisionReason,
|
|
893
934
|
switched: decisionSwitched,
|
|
894
935
|
switchFrom: decisionSwitchFrom,
|
|
@@ -922,6 +963,9 @@ export function buildPluginHooks(input) {
|
|
|
922
963
|
candidateNames,
|
|
923
964
|
loads: decisionLoads,
|
|
924
965
|
chosenAccount: finalChosenAccount,
|
|
966
|
+
chosenAccountAuthFingerprint,
|
|
967
|
+
debugLinkId,
|
|
968
|
+
networkRequestUsedInitHeaders,
|
|
925
969
|
reason: decisionReason,
|
|
926
970
|
switched: decisionSwitched,
|
|
927
971
|
switchFrom: decisionSwitchFrom,
|
|
@@ -1181,6 +1225,8 @@ export function buildPluginHooks(input) {
|
|
|
1181
1225
|
injectArmed = false;
|
|
1182
1226
|
return;
|
|
1183
1227
|
}
|
|
1228
|
+
if (hookInput.tool === "task")
|
|
1229
|
+
return;
|
|
1184
1230
|
if (!injectArmed)
|
|
1185
1231
|
return;
|
|
1186
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";
|
|
@@ -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
|
}
|