opencode-copilot-account-switcher 0.2.8 → 0.3.1
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/copilot-network-retry.d.ts +40 -0
- package/dist/copilot-network-retry.js +254 -59
- package/dist/copilot-retry-notifier.d.ts +33 -0
- package/dist/copilot-retry-notifier.js +69 -0
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/internal.d.ts +3 -0
- package/dist/internal.js +3 -0
- package/dist/plugin-actions.d.ts +6 -0
- package/dist/plugin-actions.js +6 -0
- package/dist/plugin-hooks.d.ts +3 -0
- package/dist/plugin-hooks.js +42 -2
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.js +45 -12
- package/dist/store.d.ts +1 -0
- package/dist/store.js +3 -0
- package/package.json +11 -1
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
export type FetchLike = (request: Request | URL | string, init?: RequestInit) => Promise<Response>;
|
|
2
|
+
export type CopilotRetryNotifier = {
|
|
3
|
+
started: (state: {
|
|
4
|
+
remaining: number;
|
|
5
|
+
}) => Promise<void>;
|
|
6
|
+
progress: (state: {
|
|
7
|
+
remaining: number;
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
repairWarning: (state: {
|
|
10
|
+
remaining: number;
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
completed: (state: {
|
|
13
|
+
remaining: number;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
stopped: (state: {
|
|
16
|
+
remaining: number;
|
|
17
|
+
}) => Promise<void>;
|
|
18
|
+
};
|
|
2
19
|
type JsonRecord = Record<string, unknown>;
|
|
3
20
|
export type CopilotRetryContext = {
|
|
4
21
|
client?: {
|
|
@@ -27,14 +44,37 @@ export type CopilotRetryContext = {
|
|
|
27
44
|
};
|
|
28
45
|
}>;
|
|
29
46
|
};
|
|
47
|
+
part?: {
|
|
48
|
+
update?: (input: {
|
|
49
|
+
sessionID: string;
|
|
50
|
+
messageID: string;
|
|
51
|
+
partID: string;
|
|
52
|
+
directory?: string;
|
|
53
|
+
part?: JsonRecord;
|
|
54
|
+
}) => Promise<unknown>;
|
|
55
|
+
};
|
|
56
|
+
tui?: {
|
|
57
|
+
showToast?: (options: {
|
|
58
|
+
body: {
|
|
59
|
+
title?: string;
|
|
60
|
+
message: string;
|
|
61
|
+
variant: "info" | "success" | "warning" | "error";
|
|
62
|
+
duration?: number;
|
|
63
|
+
};
|
|
64
|
+
query?: undefined;
|
|
65
|
+
}) => Promise<unknown>;
|
|
66
|
+
};
|
|
30
67
|
};
|
|
31
68
|
directory?: string;
|
|
32
69
|
serverUrl?: URL;
|
|
70
|
+
lastAccountSwitchAt?: number;
|
|
71
|
+
clearAccountSwitchContext?: () => Promise<void>;
|
|
33
72
|
wait?: (ms: number) => Promise<void>;
|
|
34
73
|
patchPart?: (request: {
|
|
35
74
|
url: string;
|
|
36
75
|
init: RequestInit;
|
|
37
76
|
}) => Promise<unknown>;
|
|
77
|
+
notifier?: CopilotRetryNotifier;
|
|
38
78
|
};
|
|
39
79
|
export declare function isRetryableCopilotFetchError(error: unknown): boolean;
|
|
40
80
|
export declare function createCopilotRetryingFetch(baseFetch: FetchLike, options?: CopilotRetryContext): (request: Request | URL | string, init?: RequestInit) => Promise<Response>;
|
|
@@ -13,7 +13,6 @@ const RETRYABLE_MESSAGES = [
|
|
|
13
13
|
"unable to verify the first certificate",
|
|
14
14
|
"self-signed certificate in certificate chain",
|
|
15
15
|
];
|
|
16
|
-
const INPUT_ID_REPAIR_HARD_LIMIT = 64;
|
|
17
16
|
const INTERNAL_SESSION_HEADER = "x-opencode-session-id";
|
|
18
17
|
const defaultDebugLogFile = (() => {
|
|
19
18
|
const tmp = process.env.TEMP || process.env.TMP || "/tmp";
|
|
@@ -110,34 +109,64 @@ function collectLongInputIdCandidates(payload) {
|
|
|
110
109
|
return [{ item: item, payloadIndex, idLength: id.length }];
|
|
111
110
|
});
|
|
112
111
|
}
|
|
113
|
-
function
|
|
114
|
-
const hintedPayloadIndex = serverReportedIndex - 1;
|
|
115
|
-
return candidates
|
|
116
|
-
.filter((candidate) => candidate.payloadIndex >= hintedPayloadIndex)
|
|
117
|
-
.sort((left, right) => left.payloadIndex - right.payloadIndex)[0];
|
|
118
|
-
}
|
|
119
|
-
function getTargetedLongInputId(payload, serverReportedIndex, reportedLength) {
|
|
112
|
+
function getTargetedLongInputIdSelection(payload, serverReportedIndex, reportedLength) {
|
|
120
113
|
const matches = collectLongInputIdCandidates(payload);
|
|
121
|
-
if (matches.length === 0)
|
|
122
|
-
return
|
|
114
|
+
if (matches.length === 0) {
|
|
115
|
+
return {
|
|
116
|
+
strategy: "ambiguous",
|
|
117
|
+
candidates: [],
|
|
118
|
+
reportedLengthMatched: reportedLength === undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
123
121
|
const lengthMatches = reportedLength
|
|
124
122
|
? matches.filter((item) => item.idLength === reportedLength)
|
|
125
123
|
: matches;
|
|
126
|
-
if (lengthMatches.length ===
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
if (reportedLength !== undefined && lengthMatches.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
strategy: "ambiguous",
|
|
127
|
+
candidates: [],
|
|
128
|
+
reportedLengthMatched: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (lengthMatches.length === 1) {
|
|
132
|
+
return {
|
|
133
|
+
candidate: lengthMatches[0],
|
|
134
|
+
strategy: reportedLength !== undefined && matches.length > 1 ? "reported-length" : "single-long-id",
|
|
135
|
+
candidates: lengthMatches,
|
|
136
|
+
reportedLengthMatched: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (matches.length === 1) {
|
|
140
|
+
return {
|
|
141
|
+
candidate: matches[0],
|
|
142
|
+
strategy: "single-long-id",
|
|
143
|
+
candidates: matches,
|
|
144
|
+
reportedLengthMatched: reportedLength === undefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
130
147
|
const narrowedCandidates = lengthMatches.length > 0 ? lengthMatches : matches;
|
|
131
148
|
if (typeof serverReportedIndex === "number") {
|
|
132
|
-
|
|
149
|
+
const hintedCandidates = narrowedCandidates.filter((candidate) => candidate.payloadIndex === serverReportedIndex || candidate.payloadIndex + 1 === serverReportedIndex);
|
|
150
|
+
if (hintedCandidates.length === 1) {
|
|
151
|
+
return {
|
|
152
|
+
candidate: hintedCandidates[0],
|
|
153
|
+
strategy: "index-hint",
|
|
154
|
+
candidates: narrowedCandidates,
|
|
155
|
+
reportedLengthMatched: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
133
158
|
}
|
|
134
|
-
return
|
|
159
|
+
return {
|
|
160
|
+
strategy: "ambiguous",
|
|
161
|
+
candidates: narrowedCandidates,
|
|
162
|
+
reportedLengthMatched: true,
|
|
163
|
+
};
|
|
135
164
|
}
|
|
136
165
|
function stripTargetedLongInputId(payload, serverReportedIndex, reportedLength) {
|
|
137
166
|
const input = payload.input;
|
|
138
167
|
if (!Array.isArray(input))
|
|
139
168
|
return payload;
|
|
140
|
-
const target =
|
|
169
|
+
const target = getTargetedLongInputIdSelection(payload, serverReportedIndex, reportedLength).candidate?.item;
|
|
141
170
|
if (!target)
|
|
142
171
|
return payload;
|
|
143
172
|
let changed = false;
|
|
@@ -181,6 +210,21 @@ function buildRetryInit(init, payload) {
|
|
|
181
210
|
body: JSON.stringify(payload),
|
|
182
211
|
};
|
|
183
212
|
}
|
|
213
|
+
const noopNotifier = {
|
|
214
|
+
started: async () => { },
|
|
215
|
+
progress: async () => { },
|
|
216
|
+
repairWarning: async () => { },
|
|
217
|
+
completed: async () => { },
|
|
218
|
+
stopped: async () => { },
|
|
219
|
+
};
|
|
220
|
+
async function notify(notifier, event, remaining) {
|
|
221
|
+
try {
|
|
222
|
+
await notifier[event]({ remaining });
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.warn(`[copilot-network-retry] notifier ${event} failed`, error);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
184
228
|
function stripInternalSessionHeaderFromRequest(request) {
|
|
185
229
|
if (!(request instanceof Request))
|
|
186
230
|
return request;
|
|
@@ -200,12 +244,18 @@ function getHeader(request, init, name) {
|
|
|
200
244
|
return undefined;
|
|
201
245
|
}
|
|
202
246
|
function getTargetedInputId(payload, serverReportedIndex, reportedLength) {
|
|
203
|
-
const target =
|
|
247
|
+
const target = getTargetedLongInputIdSelection(payload, serverReportedIndex, reportedLength).candidate?.item;
|
|
204
248
|
const id = target?.id;
|
|
205
249
|
if (typeof id !== "string")
|
|
206
250
|
return undefined;
|
|
207
251
|
return id;
|
|
208
252
|
}
|
|
253
|
+
function logCleanupStopped(reason, details) {
|
|
254
|
+
debugLog("input-id retry cleanup-stopped", {
|
|
255
|
+
reason,
|
|
256
|
+
...(details ?? {}),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
209
259
|
function stripOpenAIItemId(part) {
|
|
210
260
|
const metadata = part.metadata;
|
|
211
261
|
if (!metadata || typeof metadata !== "object")
|
|
@@ -268,6 +318,32 @@ async function repairSessionPart(sessionID, failingId, ctx) {
|
|
|
268
318
|
},
|
|
269
319
|
body: JSON.stringify(body),
|
|
270
320
|
};
|
|
321
|
+
if (ctx?.client?.part?.update) {
|
|
322
|
+
try {
|
|
323
|
+
await ctx.client.part.update({
|
|
324
|
+
sessionID,
|
|
325
|
+
messageID: match.messageID,
|
|
326
|
+
partID: match.partID,
|
|
327
|
+
directory: ctx.directory,
|
|
328
|
+
part: body,
|
|
329
|
+
});
|
|
330
|
+
debugLog("input-id retry session repair", {
|
|
331
|
+
partID: match.partID,
|
|
332
|
+
messageID: match.messageID,
|
|
333
|
+
sessionID,
|
|
334
|
+
});
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
debugLog("input-id retry session repair failed", {
|
|
339
|
+
partID: match.partID,
|
|
340
|
+
messageID: match.messageID,
|
|
341
|
+
sessionID,
|
|
342
|
+
error: String(error instanceof Error ? error.message : error),
|
|
343
|
+
});
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
271
347
|
if (ctx?.patchPart) {
|
|
272
348
|
try {
|
|
273
349
|
await ctx.patchPart({ url: url.href, init });
|
|
@@ -316,14 +392,25 @@ async function repairSessionPart(sessionID, failingId, ctx) {
|
|
|
316
392
|
return false;
|
|
317
393
|
}
|
|
318
394
|
}
|
|
319
|
-
async function maybeRetryInputIdTooLong(request, init, response, baseFetch, ctx, sessionID) {
|
|
395
|
+
async function maybeRetryInputIdTooLong(request, init, response, baseFetch, requestPayload, ctx, sessionID, startedNotified = false) {
|
|
320
396
|
if (response.status !== 400) {
|
|
321
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
response,
|
|
399
|
+
retried: false,
|
|
400
|
+
nextInit: init,
|
|
401
|
+
nextPayload: requestPayload,
|
|
402
|
+
retryState: undefined,
|
|
403
|
+
};
|
|
322
404
|
}
|
|
323
|
-
const requestPayload = parseJsonBody(init);
|
|
324
405
|
if (!requestPayload || !hasLongInputIds(requestPayload)) {
|
|
325
406
|
debugLog("skip input-id retry: request has no long ids");
|
|
326
|
-
return {
|
|
407
|
+
return {
|
|
408
|
+
response,
|
|
409
|
+
retried: false,
|
|
410
|
+
nextInit: init,
|
|
411
|
+
nextPayload: requestPayload,
|
|
412
|
+
retryState: undefined,
|
|
413
|
+
};
|
|
327
414
|
}
|
|
328
415
|
debugLog("input-id retry candidate", {
|
|
329
416
|
status: response.status,
|
|
@@ -335,7 +422,13 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, ctx,
|
|
|
335
422
|
.catch(() => "");
|
|
336
423
|
if (!responseText) {
|
|
337
424
|
debugLog("skip input-id retry: empty response body");
|
|
338
|
-
return {
|
|
425
|
+
return {
|
|
426
|
+
response,
|
|
427
|
+
retried: false,
|
|
428
|
+
nextInit: init,
|
|
429
|
+
nextPayload: requestPayload,
|
|
430
|
+
retryState: undefined,
|
|
431
|
+
};
|
|
339
432
|
}
|
|
340
433
|
let parsed = parseInputIdTooLongDetails(responseText);
|
|
341
434
|
let matched = parsed.matched;
|
|
@@ -361,31 +454,61 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, ctx,
|
|
|
361
454
|
reportedLength: parsed.reportedLength,
|
|
362
455
|
});
|
|
363
456
|
if (!matched) {
|
|
364
|
-
return {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
457
|
+
return {
|
|
458
|
+
response,
|
|
459
|
+
retried: false,
|
|
460
|
+
nextInit: init,
|
|
461
|
+
nextPayload: requestPayload,
|
|
462
|
+
retryState: undefined,
|
|
463
|
+
};
|
|
371
464
|
}
|
|
372
465
|
const payloadCandidates = getPayloadCandidates(requestPayload);
|
|
466
|
+
const targetSelection = getTargetedLongInputIdSelection(requestPayload, parsed.serverReportedIndex, parsed.reportedLength);
|
|
373
467
|
debugLog("input-id retry payload candidates", {
|
|
374
468
|
serverReportedIndex: parsed.serverReportedIndex,
|
|
375
469
|
candidates: payloadCandidates,
|
|
376
470
|
});
|
|
377
471
|
const failingId = getTargetedInputId(requestPayload, parsed.serverReportedIndex, parsed.reportedLength);
|
|
378
|
-
const targetedPayload =
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
472
|
+
const targetedPayload = targetSelection.candidate
|
|
473
|
+
? payloadCandidates.find((item) => item.payloadIndex === targetSelection.candidate?.payloadIndex)
|
|
474
|
+
: undefined;
|
|
475
|
+
debugLog("input-id retry payload target", {
|
|
476
|
+
serverReportedIndex: parsed.serverReportedIndex,
|
|
477
|
+
targetedPayloadIndex: targetedPayload?.payloadIndex,
|
|
478
|
+
itemKind: targetedPayload?.itemKind,
|
|
479
|
+
idLength: targetedPayload?.idLength,
|
|
480
|
+
idPreview: targetedPayload?.idPreview,
|
|
481
|
+
strategy: targetSelection.strategy,
|
|
482
|
+
});
|
|
483
|
+
const remainingBefore = countLongInputIdCandidates(requestPayload);
|
|
484
|
+
if (!targetSelection.candidate) {
|
|
485
|
+
logCleanupStopped("evidence-insufficient", {
|
|
486
|
+
serverReportedIndex: parsed.serverReportedIndex,
|
|
487
|
+
reportedLength: parsed.reportedLength,
|
|
488
|
+
candidateCount: targetSelection.candidates.length,
|
|
489
|
+
reportedLengthMatched: targetSelection.reportedLengthMatched,
|
|
385
490
|
});
|
|
491
|
+
return {
|
|
492
|
+
response,
|
|
493
|
+
retried: false,
|
|
494
|
+
nextInit: init,
|
|
495
|
+
nextPayload: requestPayload,
|
|
496
|
+
retryState: {
|
|
497
|
+
previousServerReportedIndex: parsed.serverReportedIndex,
|
|
498
|
+
previousErrorMessagePreview: buildMessagePreview(responseText),
|
|
499
|
+
remainingLongIdCandidatesBefore: remainingBefore,
|
|
500
|
+
remainingLongIdCandidatesAfter: remainingBefore,
|
|
501
|
+
previousReportedLength: parsed.reportedLength,
|
|
502
|
+
notifiedStarted: startedNotified || remainingBefore > 0,
|
|
503
|
+
repairFailed: false,
|
|
504
|
+
stopReason: "evidence-insufficient",
|
|
505
|
+
},
|
|
506
|
+
};
|
|
386
507
|
}
|
|
508
|
+
const notifiedStarted = startedNotified || remainingBefore > 0;
|
|
509
|
+
let repairFailed = false;
|
|
387
510
|
if (sessionID && failingId) {
|
|
388
|
-
await repairSessionPart(sessionID, failingId, ctx).catch(() => false);
|
|
511
|
+
repairFailed = !(await repairSessionPart(sessionID, failingId, ctx).catch(() => false));
|
|
389
512
|
}
|
|
390
513
|
const sanitized = stripTargetedLongInputId(requestPayload, parsed.serverReportedIndex, parsed.reportedLength);
|
|
391
514
|
if (sanitized === requestPayload) {
|
|
@@ -394,11 +517,15 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, ctx,
|
|
|
394
517
|
response,
|
|
395
518
|
retried: false,
|
|
396
519
|
nextInit: init,
|
|
520
|
+
nextPayload: requestPayload,
|
|
397
521
|
retryState: {
|
|
398
522
|
previousServerReportedIndex: parsed.serverReportedIndex,
|
|
399
523
|
previousErrorMessagePreview: buildMessagePreview(responseText),
|
|
400
|
-
remainingLongIdCandidatesBefore:
|
|
401
|
-
remainingLongIdCandidatesAfter:
|
|
524
|
+
remainingLongIdCandidatesBefore: remainingBefore,
|
|
525
|
+
remainingLongIdCandidatesAfter: remainingBefore,
|
|
526
|
+
previousReportedLength: parsed.reportedLength,
|
|
527
|
+
notifiedStarted,
|
|
528
|
+
repairFailed,
|
|
402
529
|
},
|
|
403
530
|
};
|
|
404
531
|
}
|
|
@@ -411,14 +538,17 @@ async function maybeRetryInputIdTooLong(request, init, response, baseFetch, ctx,
|
|
|
411
538
|
const retryState = {
|
|
412
539
|
previousServerReportedIndex: parsed.serverReportedIndex,
|
|
413
540
|
previousErrorMessagePreview: buildMessagePreview(responseText),
|
|
414
|
-
remainingLongIdCandidatesBefore:
|
|
541
|
+
remainingLongIdCandidatesBefore: remainingBefore,
|
|
415
542
|
remainingLongIdCandidatesAfter: countLongInputIdCandidates(parseJsonBody(nextInit)),
|
|
543
|
+
previousReportedLength: parsed.reportedLength,
|
|
544
|
+
notifiedStarted,
|
|
545
|
+
repairFailed,
|
|
416
546
|
};
|
|
417
547
|
debugLog("input-id retry response", {
|
|
418
548
|
status: retried.status,
|
|
419
549
|
contentType: retried.headers.get("content-type") ?? undefined,
|
|
420
550
|
});
|
|
421
|
-
return { response: retried, retried: true, nextInit, retryState };
|
|
551
|
+
return { response: retried, retried: true, nextInit, nextPayload: sanitized, retryState };
|
|
422
552
|
}
|
|
423
553
|
function toRetryableSystemError(error) {
|
|
424
554
|
const base = error instanceof Error ? error : new Error(String(error));
|
|
@@ -468,9 +598,29 @@ async function getInputIdRetryErrorDetails(response) {
|
|
|
468
598
|
return undefined;
|
|
469
599
|
return {
|
|
470
600
|
serverReportedIndex: parsed.serverReportedIndex,
|
|
601
|
+
reportedLength: parsed.reportedLength,
|
|
471
602
|
errorMessagePreview: buildMessagePreview(message),
|
|
472
603
|
};
|
|
473
604
|
}
|
|
605
|
+
async function parseJsonRequestPayload(request, init) {
|
|
606
|
+
const initPayload = parseJsonBody(init);
|
|
607
|
+
if (initPayload)
|
|
608
|
+
return initPayload;
|
|
609
|
+
if (!(request instanceof Request))
|
|
610
|
+
return undefined;
|
|
611
|
+
try {
|
|
612
|
+
const body = await request.clone().text();
|
|
613
|
+
if (!body)
|
|
614
|
+
return undefined;
|
|
615
|
+
const parsed = JSON.parse(body);
|
|
616
|
+
if (!parsed || typeof parsed !== "object")
|
|
617
|
+
return undefined;
|
|
618
|
+
return parsed;
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
474
624
|
function withStreamDebugLogs(response, request) {
|
|
475
625
|
if (!isDebugEnabled())
|
|
476
626
|
return response;
|
|
@@ -519,7 +669,7 @@ export function isRetryableCopilotFetchError(error) {
|
|
|
519
669
|
return RETRYABLE_MESSAGES.some((part) => message.includes(part));
|
|
520
670
|
}
|
|
521
671
|
export function createCopilotRetryingFetch(baseFetch, options) {
|
|
522
|
-
|
|
672
|
+
const notifier = options?.notifier ?? noopNotifier;
|
|
523
673
|
return async function retryingFetch(request, init) {
|
|
524
674
|
const sessionID = getHeader(request, init, INTERNAL_SESSION_HEADER);
|
|
525
675
|
const safeRequest = stripInternalSessionHeaderFromRequest(request);
|
|
@@ -535,6 +685,7 @@ export function createCopilotRetryingFetch(baseFetch, options) {
|
|
|
535
685
|
url: safeRequest instanceof Request ? safeRequest.url : safeRequest instanceof URL ? safeRequest.href : String(safeRequest),
|
|
536
686
|
isCopilot: isCopilotUrl(safeRequest),
|
|
537
687
|
});
|
|
688
|
+
let currentPayload = await parseJsonRequestPayload(safeRequest, effectiveInit);
|
|
538
689
|
try {
|
|
539
690
|
const response = await baseFetch(safeRequest, effectiveInit);
|
|
540
691
|
debugLog("fetch resolved", {
|
|
@@ -545,27 +696,42 @@ export function createCopilotRetryingFetch(baseFetch, options) {
|
|
|
545
696
|
let currentResponse = response;
|
|
546
697
|
let currentInit = effectiveInit;
|
|
547
698
|
let attempts = 0;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
699
|
+
let shouldContinueInputIdRepair = countLongInputIdCandidates(currentPayload) > 0;
|
|
700
|
+
let startedNotified = false;
|
|
701
|
+
let finishedNotified = false;
|
|
702
|
+
let repairWarningNotified = false;
|
|
703
|
+
while (shouldContinueInputIdRepair) {
|
|
704
|
+
shouldContinueInputIdRepair = false;
|
|
705
|
+
const result = await maybeRetryInputIdTooLong(safeRequest, currentInit, currentResponse, baseFetch, currentPayload, options, sessionID, startedNotified);
|
|
553
706
|
currentResponse = result.response;
|
|
554
707
|
currentInit = result.nextInit;
|
|
708
|
+
currentPayload = result.nextPayload;
|
|
555
709
|
if (result.retryState) {
|
|
710
|
+
if (!startedNotified && result.retryState.notifiedStarted) {
|
|
711
|
+
startedNotified = true;
|
|
712
|
+
await notify(notifier, "started", result.retryState.remainingLongIdCandidatesBefore);
|
|
713
|
+
}
|
|
714
|
+
if (result.retryState.repairFailed && !repairWarningNotified) {
|
|
715
|
+
await notify(notifier, "repairWarning", result.retryState.remainingLongIdCandidatesBefore);
|
|
716
|
+
repairWarningNotified = true;
|
|
717
|
+
}
|
|
556
718
|
const currentError = await getInputIdRetryErrorDetails(currentResponse);
|
|
557
|
-
let stopReason;
|
|
558
|
-
|
|
719
|
+
let stopReason = result.retryState.stopReason;
|
|
720
|
+
const madeProgress = result.retryState.remainingLongIdCandidatesAfter < result.retryState.remainingLongIdCandidatesBefore;
|
|
721
|
+
if (!stopReason && result.retryState.remainingLongIdCandidatesAfter >= result.retryState.remainingLongIdCandidatesBefore) {
|
|
559
722
|
stopReason = "remaining-candidates-not-reduced";
|
|
560
723
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
724
|
+
if (!stopReason &&
|
|
725
|
+
currentError &&
|
|
726
|
+
result.retryState.remainingLongIdCandidatesAfter > 0 &&
|
|
727
|
+
result.retryState.previousServerReportedIndex === currentError.serverReportedIndex &&
|
|
728
|
+
result.retryState.previousReportedLength === currentError.reportedLength) {
|
|
729
|
+
stopReason = "same-server-item-persists";
|
|
730
|
+
}
|
|
731
|
+
if (!stopReason && currentError && result.retryState.remainingLongIdCandidatesAfter === 0) {
|
|
732
|
+
stopReason = "local-candidates-exhausted";
|
|
567
733
|
}
|
|
568
|
-
if (currentError || stopReason) {
|
|
734
|
+
if ((currentError || stopReason) && stopReason !== "evidence-insufficient") {
|
|
569
735
|
debugLog("input-id retry progress", {
|
|
570
736
|
attempt: attempts + 1,
|
|
571
737
|
previousServerReportedIndex: result.retryState.previousServerReportedIndex,
|
|
@@ -578,11 +744,40 @@ export function createCopilotRetryingFetch(baseFetch, options) {
|
|
|
578
744
|
stopReason,
|
|
579
745
|
});
|
|
580
746
|
}
|
|
581
|
-
if (stopReason)
|
|
747
|
+
if (stopReason === "local-candidates-exhausted") {
|
|
748
|
+
logCleanupStopped("local-candidates-exhausted", {
|
|
749
|
+
attempt: attempts + 1,
|
|
750
|
+
previousServerReportedIndex: result.retryState.previousServerReportedIndex,
|
|
751
|
+
currentServerReportedIndex: currentError?.serverReportedIndex,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
if (stopReason) {
|
|
755
|
+
await notify(notifier, "stopped", result.retryState.remainingLongIdCandidatesAfter);
|
|
756
|
+
finishedNotified = true;
|
|
582
757
|
break;
|
|
758
|
+
}
|
|
759
|
+
if (result.retried && madeProgress && result.retryState.remainingLongIdCandidatesAfter > 0) {
|
|
760
|
+
await notify(notifier, "progress", result.retryState.remainingLongIdCandidatesAfter);
|
|
761
|
+
}
|
|
762
|
+
if (result.retried && result.retryState.remainingLongIdCandidatesAfter === 0 && currentResponse.ok) {
|
|
763
|
+
await notify(notifier, "completed", 0);
|
|
764
|
+
finishedNotified = true;
|
|
765
|
+
}
|
|
766
|
+
if (result.retried && result.retryState.remainingLongIdCandidatesAfter === 0 && !currentResponse.ok) {
|
|
767
|
+
await notify(notifier, "stopped", 0);
|
|
768
|
+
finishedNotified = true;
|
|
769
|
+
}
|
|
770
|
+
if (result.retried && madeProgress && result.retryState.remainingLongIdCandidatesAfter > 0) {
|
|
771
|
+
shouldContinueInputIdRepair = true;
|
|
772
|
+
}
|
|
583
773
|
}
|
|
584
|
-
if (!result.retried)
|
|
774
|
+
if (!result.retried) {
|
|
775
|
+
if (startedNotified && !finishedNotified) {
|
|
776
|
+
await notify(notifier, "stopped", countLongInputIdCandidates(currentPayload));
|
|
777
|
+
finishedNotified = true;
|
|
778
|
+
}
|
|
585
779
|
break;
|
|
780
|
+
}
|
|
586
781
|
attempts += 1;
|
|
587
782
|
}
|
|
588
783
|
return withStreamDebugLogs(currentResponse, safeRequest);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const ACCOUNT_SWITCH_TTL_MS: number;
|
|
2
|
+
type ToastVariant = "info" | "success" | "warning" | "error";
|
|
3
|
+
type RetryToastState = {
|
|
4
|
+
remaining: number;
|
|
5
|
+
};
|
|
6
|
+
type RetryToastClient = {
|
|
7
|
+
tui?: {
|
|
8
|
+
showToast?: (options: {
|
|
9
|
+
body: {
|
|
10
|
+
title?: string;
|
|
11
|
+
message: string;
|
|
12
|
+
variant: ToastVariant;
|
|
13
|
+
duration?: number;
|
|
14
|
+
};
|
|
15
|
+
query?: undefined;
|
|
16
|
+
}) => Promise<unknown>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type RetryNotifierContext = {
|
|
20
|
+
client?: RetryToastClient;
|
|
21
|
+
lastAccountSwitchAt?: number;
|
|
22
|
+
getLastAccountSwitchAt?: () => Promise<number | undefined> | number | undefined;
|
|
23
|
+
clearAccountSwitchContext?: (lastAccountSwitchAt?: number) => Promise<void>;
|
|
24
|
+
now?: () => number;
|
|
25
|
+
};
|
|
26
|
+
export declare function createCopilotRetryNotifier(ctx: RetryNotifierContext): {
|
|
27
|
+
started: (state: RetryToastState) => Promise<void>;
|
|
28
|
+
progress: (state: RetryToastState) => Promise<void>;
|
|
29
|
+
repairWarning: (state: RetryToastState) => Promise<void>;
|
|
30
|
+
completed: (state: RetryToastState) => Promise<void>;
|
|
31
|
+
stopped: (state: RetryToastState) => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const ACCOUNT_SWITCH_TTL_MS = 30 * 60 * 1000;
|
|
2
|
+
function buildPrefix(lastAccountSwitchAt) {
|
|
3
|
+
if (typeof lastAccountSwitchAt !== "number")
|
|
4
|
+
return "Copilot 输入 ID 自动清理中";
|
|
5
|
+
return "正在清理可能因账号切换遗留的非法输入 ID";
|
|
6
|
+
}
|
|
7
|
+
async function resolveToastAccountSwitchAt(ctx) {
|
|
8
|
+
if (ctx.lastAccountSwitchAt !== undefined && ctx.lastAccountSwitchAt !== null) {
|
|
9
|
+
return ctx.lastAccountSwitchAt;
|
|
10
|
+
}
|
|
11
|
+
return ctx.getLastAccountSwitchAt?.();
|
|
12
|
+
}
|
|
13
|
+
function isAccountSwitchContextExpired(lastAccountSwitchAt, now) {
|
|
14
|
+
if (typeof lastAccountSwitchAt !== "number")
|
|
15
|
+
return false;
|
|
16
|
+
return now() - lastAccountSwitchAt >= ACCOUNT_SWITCH_TTL_MS;
|
|
17
|
+
}
|
|
18
|
+
async function clearContext(ctx) {
|
|
19
|
+
try {
|
|
20
|
+
await ctx.clearAccountSwitchContext?.();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.warn("[copilot-retry-notifier] failed to clear account-switch context", error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function createCopilotRetryNotifier(ctx) {
|
|
27
|
+
let lastExpiredContextClearedAt;
|
|
28
|
+
async function send(variant, detail, state, clear = false) {
|
|
29
|
+
const now = ctx.now ?? Date.now;
|
|
30
|
+
const lastAccountSwitchAt = await resolveToastAccountSwitchAt(ctx);
|
|
31
|
+
try {
|
|
32
|
+
await ctx.client?.tui?.showToast?.({
|
|
33
|
+
body: {
|
|
34
|
+
variant,
|
|
35
|
+
message: `${buildPrefix(lastAccountSwitchAt)}:${detail},剩余 ${state.remaining} 项。`,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.warn("[copilot-retry-notifier] failed to show toast", error);
|
|
41
|
+
}
|
|
42
|
+
if (!clear
|
|
43
|
+
&& isAccountSwitchContextExpired(lastAccountSwitchAt, now)
|
|
44
|
+
&& lastAccountSwitchAt !== lastExpiredContextClearedAt) {
|
|
45
|
+
lastExpiredContextClearedAt = lastAccountSwitchAt;
|
|
46
|
+
await clearContext({
|
|
47
|
+
...ctx,
|
|
48
|
+
clearAccountSwitchContext: async () => {
|
|
49
|
+
await ctx.clearAccountSwitchContext?.(lastAccountSwitchAt);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (!clear)
|
|
54
|
+
return;
|
|
55
|
+
await clearContext({
|
|
56
|
+
...ctx,
|
|
57
|
+
clearAccountSwitchContext: async () => {
|
|
58
|
+
await ctx.clearAccountSwitchContext?.(lastAccountSwitchAt);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
started: async (state) => send("info", "已开始自动清理", state),
|
|
64
|
+
progress: async (state) => send("info", "自动清理仍在继续", state),
|
|
65
|
+
repairWarning: async (state) => send("warning", "会话回写失败,继续尝试仅清理请求体", state),
|
|
66
|
+
completed: async (state) => send("success", "自动清理已完成", state, true),
|
|
67
|
+
stopped: async (state) => send("warning", "自动清理已停止", state, true),
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
1
|
export { CopilotAccountSwitcher } from "./plugin.js";
|
|
2
|
-
export { applyMenuAction } from "./plugin-actions.js";
|
|
3
|
-
export { buildPluginHooks } from "./plugin-hooks.js";
|
|
4
|
-
export { createOfficialFetchAdapter, loadOfficialCopilotConfig } from "./upstream/copilot-loader-adapter.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
1
|
export { CopilotAccountSwitcher } from "./plugin.js";
|
|
2
|
-
export { applyMenuAction } from "./plugin-actions.js";
|
|
3
|
-
export { buildPluginHooks } from "./plugin-hooks.js";
|
|
4
|
-
export { createOfficialFetchAdapter, loadOfficialCopilotConfig } from "./upstream/copilot-loader-adapter.js";
|
package/dist/internal.js
ADDED
package/dist/plugin-actions.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { StoreFile } from "./store.js";
|
|
2
2
|
import type { MenuAction } from "./ui/menu.js";
|
|
3
|
+
export declare function persistAccountSwitch(input: {
|
|
4
|
+
store: StoreFile;
|
|
5
|
+
name: string;
|
|
6
|
+
at: number;
|
|
7
|
+
writeStore: (store: StoreFile) => Promise<void>;
|
|
8
|
+
}): Promise<void>;
|
|
3
9
|
export declare function applyMenuAction(input: {
|
|
4
10
|
action: MenuAction;
|
|
5
11
|
store: StoreFile;
|
package/dist/plugin-actions.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export async function persistAccountSwitch(input) {
|
|
2
|
+
input.store.active = input.name;
|
|
3
|
+
input.store.accounts[input.name].lastUsed = input.at;
|
|
4
|
+
input.store.lastAccountSwitchAt = input.at;
|
|
5
|
+
await input.writeStore(input.store);
|
|
6
|
+
}
|
|
1
7
|
export async function applyMenuAction(input) {
|
|
2
8
|
if (input.action.type === "toggle-loop-safety") {
|
|
3
9
|
input.store.loopSafetyEnabled = input.store.loopSafetyEnabled !== true;
|
package/dist/plugin-hooks.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ type CopilotPluginHooksWithChatHeaders = CopilotPluginHooks & {
|
|
|
25
25
|
export declare function buildPluginHooks(input: {
|
|
26
26
|
auth: NonNullable<CopilotPluginHooks["auth"]>;
|
|
27
27
|
loadStore?: () => Promise<StoreFile | undefined>;
|
|
28
|
+
writeStore?: (store: StoreFile) => Promise<void>;
|
|
28
29
|
loadOfficialConfig?: (input: {
|
|
29
30
|
getAuth: () => Promise<CopilotAuthState | undefined>;
|
|
30
31
|
provider?: CopilotProviderConfig;
|
|
@@ -33,5 +34,7 @@ export declare function buildPluginHooks(input: {
|
|
|
33
34
|
client?: CopilotRetryContext["client"];
|
|
34
35
|
directory?: CopilotRetryContext["directory"];
|
|
35
36
|
serverUrl?: CopilotRetryContext["serverUrl"];
|
|
37
|
+
clearAccountSwitchContext?: (lastAccountSwitchAt?: number) => Promise<void>;
|
|
38
|
+
now?: () => number;
|
|
36
39
|
}): CopilotPluginHooksWithChatHeaders;
|
|
37
40
|
export {};
|
package/dist/plugin-hooks.js
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import { createLoopSafetySystemTransform, isCopilotProvider, } from "./loop-safety-plugin.js";
|
|
2
2
|
import { createCopilotRetryingFetch, } from "./copilot-network-retry.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createCopilotRetryNotifier } from "./copilot-retry-notifier.js";
|
|
4
|
+
import { readStoreSafe, writeStore } from "./store.js";
|
|
4
5
|
import { loadOfficialCopilotConfig, } from "./upstream/copilot-loader-adapter.js";
|
|
6
|
+
function readRetryStoreContext(store) {
|
|
7
|
+
if (!store)
|
|
8
|
+
return undefined;
|
|
9
|
+
const maybeLastAccountSwitchAt = store.lastAccountSwitchAt;
|
|
10
|
+
return {
|
|
11
|
+
networkRetryEnabled: store.networkRetryEnabled,
|
|
12
|
+
lastAccountSwitchAt: typeof maybeLastAccountSwitchAt === "number" ? maybeLastAccountSwitchAt : undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
5
15
|
export function buildPluginHooks(input) {
|
|
6
16
|
const loadStore = input.loadStore ?? readStoreSafe;
|
|
17
|
+
const persistStore = input.writeStore ?? writeStore;
|
|
7
18
|
const loadOfficialConfig = input.loadOfficialConfig ?? loadOfficialCopilotConfig;
|
|
8
19
|
const createRetryFetch = input.createRetryFetch ?? createCopilotRetryingFetch;
|
|
20
|
+
const getLatestLastAccountSwitchAt = async () => {
|
|
21
|
+
const store = readRetryStoreContext(await loadStore().catch(() => undefined));
|
|
22
|
+
return store?.lastAccountSwitchAt;
|
|
23
|
+
};
|
|
24
|
+
const clearAccountSwitchContext = input.clearAccountSwitchContext ?? (async (capturedLastAccountSwitchAt) => {
|
|
25
|
+
if (capturedLastAccountSwitchAt === undefined)
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
const latestStore = await loadStore().catch(() => undefined);
|
|
29
|
+
if (!latestStore)
|
|
30
|
+
return;
|
|
31
|
+
if (latestStore.lastAccountSwitchAt !== capturedLastAccountSwitchAt)
|
|
32
|
+
return;
|
|
33
|
+
delete latestStore.lastAccountSwitchAt;
|
|
34
|
+
await persistStore(latestStore);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.warn("[plugin-hooks] failed to clear account-switch context", error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
9
40
|
const loader = async (getAuth, provider) => {
|
|
10
41
|
const config = await loadOfficialConfig({
|
|
11
42
|
getAuth: getAuth,
|
|
@@ -13,7 +44,7 @@ export function buildPluginHooks(input) {
|
|
|
13
44
|
});
|
|
14
45
|
if (!config)
|
|
15
46
|
return {};
|
|
16
|
-
const store = await loadStore().catch(() => undefined);
|
|
47
|
+
const store = readRetryStoreContext(await loadStore().catch(() => undefined));
|
|
17
48
|
if (store?.networkRetryEnabled !== true) {
|
|
18
49
|
return config;
|
|
19
50
|
}
|
|
@@ -23,6 +54,15 @@ export function buildPluginHooks(input) {
|
|
|
23
54
|
client: input.client,
|
|
24
55
|
directory: input.directory,
|
|
25
56
|
serverUrl: input.serverUrl,
|
|
57
|
+
lastAccountSwitchAt: store.lastAccountSwitchAt,
|
|
58
|
+
notifier: createCopilotRetryNotifier({
|
|
59
|
+
client: input.client,
|
|
60
|
+
lastAccountSwitchAt: store.lastAccountSwitchAt,
|
|
61
|
+
getLastAccountSwitchAt: getLatestLastAccountSwitchAt,
|
|
62
|
+
clearAccountSwitchContext,
|
|
63
|
+
now: input.now,
|
|
64
|
+
}),
|
|
65
|
+
clearAccountSwitchContext: async () => clearAccountSwitchContext(store.lastAccountSwitchAt),
|
|
26
66
|
}),
|
|
27
67
|
};
|
|
28
68
|
};
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { type StoreFile } from "./store.js";
|
|
3
|
+
export declare function activateAddedAccount(input: {
|
|
4
|
+
store: StoreFile;
|
|
5
|
+
name: string;
|
|
6
|
+
switchAccount: () => Promise<void>;
|
|
7
|
+
writeStore: (store: StoreFile) => Promise<void>;
|
|
8
|
+
now?: () => number;
|
|
9
|
+
}): Promise<void>;
|
|
2
10
|
export declare const CopilotAccountSwitcher: Plugin;
|
package/dist/plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
-
import { applyMenuAction } from "./plugin-actions.js";
|
|
3
|
+
import { applyMenuAction, persistAccountSwitch } from "./plugin-actions.js";
|
|
4
4
|
import { buildPluginHooks } from "./plugin-hooks.js";
|
|
5
5
|
import { isTTY } from "./ui/ansi.js";
|
|
6
6
|
import { showAccountActions, showMenu } from "./ui/menu.js";
|
|
@@ -443,6 +443,16 @@ async function switchAccount(client, entry) {
|
|
|
443
443
|
body: payload,
|
|
444
444
|
});
|
|
445
445
|
}
|
|
446
|
+
export async function activateAddedAccount(input) {
|
|
447
|
+
await input.writeStore(input.store);
|
|
448
|
+
await input.switchAccount();
|
|
449
|
+
await persistAccountSwitch({
|
|
450
|
+
store: input.store,
|
|
451
|
+
name: input.name,
|
|
452
|
+
at: (input.now ?? now)(),
|
|
453
|
+
writeStore: input.writeStore,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
446
456
|
export const CopilotAccountSwitcher = async (input) => {
|
|
447
457
|
const client = input.client;
|
|
448
458
|
const directory = input.directory;
|
|
@@ -487,8 +497,12 @@ export const CopilotAccountSwitcher = async (input) => {
|
|
|
487
497
|
const { name, entry } = await promptAccountEntry([]);
|
|
488
498
|
store.accounts[name] = entry;
|
|
489
499
|
store.active = name;
|
|
490
|
-
await
|
|
491
|
-
|
|
500
|
+
await activateAddedAccount({
|
|
501
|
+
store,
|
|
502
|
+
name,
|
|
503
|
+
switchAccount: () => switchAccount(client, entry),
|
|
504
|
+
writeStore,
|
|
505
|
+
});
|
|
492
506
|
// fallthrough to menu
|
|
493
507
|
}
|
|
494
508
|
if (!Object.values(store.accounts).some((entry) => entry.user || entry.email || (entry.orgs && entry.orgs.length > 0))) {
|
|
@@ -590,9 +604,17 @@ export const CopilotAccountSwitcher = async (input) => {
|
|
|
590
604
|
entry.name = buildName(entry, user?.login);
|
|
591
605
|
store.accounts[entry.name] = entry;
|
|
592
606
|
store.active = store.active ?? entry.name;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
607
|
+
if (store.active === entry.name) {
|
|
608
|
+
await activateAddedAccount({
|
|
609
|
+
store,
|
|
610
|
+
name: entry.name,
|
|
611
|
+
switchAccount: () => switchAccount(client, entry),
|
|
612
|
+
writeStore,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
await writeStore(store);
|
|
617
|
+
}
|
|
596
618
|
continue;
|
|
597
619
|
}
|
|
598
620
|
const manual = await promptAccountEntry(Object.keys(store.accounts));
|
|
@@ -606,9 +628,17 @@ export const CopilotAccountSwitcher = async (input) => {
|
|
|
606
628
|
manual.entry.name = buildName(manual.entry, user?.login);
|
|
607
629
|
store.accounts[manual.entry.name] = manual.entry;
|
|
608
630
|
store.active = store.active ?? manual.entry.name;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
631
|
+
if (store.active === manual.entry.name) {
|
|
632
|
+
await activateAddedAccount({
|
|
633
|
+
store,
|
|
634
|
+
name: manual.entry.name,
|
|
635
|
+
switchAccount: () => switchAccount(client, manual.entry),
|
|
636
|
+
writeStore,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
await writeStore(store);
|
|
641
|
+
}
|
|
612
642
|
continue;
|
|
613
643
|
}
|
|
614
644
|
if (action.type === "import") {
|
|
@@ -700,9 +730,12 @@ export const CopilotAccountSwitcher = async (input) => {
|
|
|
700
730
|
continue;
|
|
701
731
|
}
|
|
702
732
|
await switchAccount(client, entry);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
733
|
+
await persistAccountSwitch({
|
|
734
|
+
store,
|
|
735
|
+
name,
|
|
736
|
+
at: now(),
|
|
737
|
+
writeStore,
|
|
738
|
+
});
|
|
706
739
|
console.log("Switched account. If a later Copilot session hits input[*].id too long after switching, enable Copilot Network Retry from the menu.");
|
|
707
740
|
continue;
|
|
708
741
|
}
|
package/dist/store.d.ts
CHANGED
package/dist/store.js
CHANGED
|
@@ -16,6 +16,9 @@ export function parseStore(raw) {
|
|
|
16
16
|
const data = raw ? JSON.parse(raw) : { accounts: {} };
|
|
17
17
|
if (!data.accounts)
|
|
18
18
|
data.accounts = {};
|
|
19
|
+
if (typeof data.lastAccountSwitchAt !== "number" || Number.isNaN(data.lastAccountSwitchAt)) {
|
|
20
|
+
delete data.lastAccountSwitchAt;
|
|
21
|
+
}
|
|
19
22
|
if (data.loopSafetyEnabled !== false)
|
|
20
23
|
data.loopSafetyEnabled = true;
|
|
21
24
|
if (data.networkRetryEnabled !== true)
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-copilot-account-switcher",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "GitHub Copilot account switcher plugin for OpenCode",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./internal": {
|
|
13
|
+
"types": "./dist/internal.d.ts",
|
|
14
|
+
"default": "./dist/internal.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"type": "module",
|
|
8
18
|
"license": "MPL-2.0",
|
|
9
19
|
"author": "jiwangyihao",
|