opencode-copilot-account-switcher 0.10.10 → 0.10.12

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.
@@ -1113,6 +1113,14 @@ export function isRetryableCopilotFetchError(error) {
1113
1113
  const message = getErrorMessage(error);
1114
1114
  return RETRYABLE_MESSAGES.some((part) => message.includes(part));
1115
1115
  }
1116
+ function isRetryableCopilotJsonParseError(error) {
1117
+ if (!error || isAbortError(error))
1118
+ return false;
1119
+ const message = getErrorMessage(error);
1120
+ const name = error instanceof Error ? error.name : "";
1121
+ const hasAiJsonParseSignature = name === "AI_JSONParseError" || message.includes("ai_jsonparseerror");
1122
+ return hasAiJsonParseSignature && message.includes("json parsing failed") && message.includes("text:");
1123
+ }
1116
1124
  export function createCopilotRetryingFetch(baseFetch, options) {
1117
1125
  const notifier = options?.notifier ?? noopNotifier;
1118
1126
  return async function retryingFetch(request, init) {
@@ -1292,11 +1300,14 @@ export function createCopilotRetryingFetch(baseFetch, options) {
1292
1300
  return response;
1293
1301
  }
1294
1302
  catch (error) {
1303
+ const retryableByMessage = isRetryableCopilotFetchError(error);
1304
+ const retryableCopilotJsonParse = isRetryableCopilotJsonParseError(error);
1295
1305
  debugLog("fetch threw", {
1296
1306
  message: getErrorMessage(error),
1297
- retryableByMessage: isRetryableCopilotFetchError(error),
1307
+ retryableByMessage,
1308
+ retryableCopilotJsonParse,
1298
1309
  });
1299
- if (!isCopilotUrl(safeRequest) || !isRetryableCopilotFetchError(error)) {
1310
+ if (!isCopilotUrl(safeRequest) || (!retryableByMessage && !retryableCopilotJsonParse)) {
1300
1311
  throw error;
1301
1312
  }
1302
1313
  if (isRetryableApiCallError(error)) {
@@ -14,10 +14,50 @@ function formatQuotaValue(snapshot) {
14
14
  return "n/a";
15
15
  return `${remaining ?? "?"}/${entitlement ?? "?"}`;
16
16
  }
17
- function formatUpdatedAt(updatedAt) {
18
- if (typeof updatedAt !== "number")
19
- return "updated at unknown";
20
- return `updated at ${new Date(updatedAt).toISOString()}`;
17
+ const ACCOUNT_CELL_WIDTH = 16;
18
+ const ACCOUNT_COLUMNS_PER_ROW = 3;
19
+ function formatPremiumQuota(quota) {
20
+ return formatQuotaValue(quota?.snapshots?.premium);
21
+ }
22
+ function truncateMiddle(value, maxWidth) {
23
+ if (maxWidth <= 0)
24
+ return "";
25
+ if (value.length <= maxWidth)
26
+ return value;
27
+ if (maxWidth <= 3)
28
+ return ".".repeat(maxWidth);
29
+ const visibleWidth = maxWidth - 3;
30
+ const leftWidth = Math.ceil(visibleWidth / 2);
31
+ const rightWidth = Math.floor(visibleWidth / 2);
32
+ return `${value.slice(0, leftWidth)}...${value.slice(value.length - rightWidth)}`;
33
+ }
34
+ function renderAccountCell(name, quotaText) {
35
+ if (quotaText.length >= ACCOUNT_CELL_WIDTH) {
36
+ return quotaText.slice(-ACCOUNT_CELL_WIDTH);
37
+ }
38
+ const usernameWidth = ACCOUNT_CELL_WIDTH - quotaText.length - 1;
39
+ if (usernameWidth <= 0) {
40
+ return quotaText.padStart(ACCOUNT_CELL_WIDTH);
41
+ }
42
+ const username = truncateMiddle(name, usernameWidth);
43
+ return `${username} ${quotaText}`.padEnd(ACCOUNT_CELL_WIDTH);
44
+ }
45
+ function renderAccountRow(cells) {
46
+ const rendered = [];
47
+ for (let i = 0; i < ACCOUNT_COLUMNS_PER_ROW; i += 1) {
48
+ const cell = cells[i];
49
+ rendered.push(cell ? renderAccountCell(cell.name, cell.quota) : "".padEnd(ACCOUNT_CELL_WIDTH));
50
+ }
51
+ return rendered.join(" ");
52
+ }
53
+ function renderAccountGrid(cells) {
54
+ if (cells.length === 0)
55
+ return [];
56
+ const rows = [];
57
+ for (let index = 0; index < cells.length; index += ACCOUNT_COLUMNS_PER_ROW) {
58
+ rows.push(renderAccountRow(cells.slice(index, index + ACCOUNT_COLUMNS_PER_ROW)));
59
+ }
60
+ return rows;
21
61
  }
22
62
  function formatActiveGroup(store) {
23
63
  const names = Array.isArray(store.activeAccountNames) ? store.activeAccountNames : [];
@@ -38,18 +78,44 @@ function formatRoutingGroup(store) {
38
78
  .filter((line) => Boolean(line));
39
79
  return mapped.length > 0 ? mapped.join("; ") : "none";
40
80
  }
41
- function buildSuccessMessage(store, name, quota) {
42
- const activeSummary = [
43
- `current active: ${name}`,
44
- `premium ${formatQuotaValue(quota?.snapshots?.premium)}`,
45
- `chat ${formatQuotaValue(quota?.snapshots?.chat)}`,
46
- `completions ${formatQuotaValue(quota?.snapshots?.completions)}`,
47
- formatUpdatedAt(quota?.updatedAt),
48
- ].join(" | ");
49
- return [activeSummary, `活跃组: ${formatActiveGroup(store)}`, `路由组: ${formatRoutingGroup(store)}`].join("\n");
81
+ function buildSuccessMessage(store, _name) {
82
+ const defaultNames = Array.isArray(store.activeAccountNames) && store.activeAccountNames.length > 0
83
+ ? store.activeAccountNames
84
+ : [];
85
+ const modelIDs = Object.keys(store.modelAccountAssignments ?? {}).sort((a, b) => a.localeCompare(b));
86
+ const lines = [];
87
+ lines.push("[default]");
88
+ if (defaultNames.length === 0) {
89
+ lines.push("(none)");
90
+ }
91
+ else {
92
+ lines.push(...renderAccountGrid(defaultNames.map((name) => ({
93
+ name,
94
+ quota: formatPremiumQuota(store.accounts[name]?.quota),
95
+ }))));
96
+ }
97
+ let hasModelRouteGroup = false;
98
+ for (const modelID of modelIDs) {
99
+ const names = store.modelAccountAssignments?.[modelID] ?? [];
100
+ if (names.length === 0)
101
+ continue;
102
+ hasModelRouteGroup = true;
103
+ lines.push(`[${modelID}]`);
104
+ lines.push(...renderAccountGrid(names.map((name) => ({
105
+ name,
106
+ quota: formatPremiumQuota(store.accounts[name]?.quota),
107
+ }))));
108
+ }
109
+ if (!hasModelRouteGroup) {
110
+ lines.push("[routes]");
111
+ lines.push("(none)");
112
+ }
113
+ lines.push(`活跃组: ${formatActiveGroup(store)}`);
114
+ lines.push(`路由组: ${formatRoutingGroup(store)}`);
115
+ return lines.join("\n");
50
116
  }
51
117
  function buildMissingActiveMessage() {
52
- return "No active account available for Copilot status.";
118
+ return "No default-group refreshable account available for Copilot status.";
53
119
  }
54
120
  function buildRefreshFailedMessage(result) {
55
121
  const previous = result.previousQuota?.snapshots?.premium;
@@ -57,7 +123,7 @@ function buildRefreshFailedMessage(result) {
57
123
  return `Copilot quota refresh failed: ${result.error}.${previousText}`;
58
124
  }
59
125
  function buildPersistFailedMessage(store, entry, error) {
60
- return `Latest quota refreshed but store persistence failed: ${summarizeError(error)}. ${buildSuccessMessage(store, entry.name, entry.quota)}`;
126
+ return `Latest quota refreshed but store persistence failed: ${summarizeError(error)}. ${buildSuccessMessage(store, entry.name)}`;
61
127
  }
62
128
  export async function showStatusToast(input) {
63
129
  const tui = input.client?.tui;
@@ -165,7 +231,7 @@ export async function handleStatusCommand(input) {
165
231
  }
166
232
  await showStatusToast({
167
233
  client: input.client,
168
- message: buildSuccessMessage(store, result.name, result.entry.quota),
234
+ message: buildSuccessMessage(store, result.name),
169
235
  variant: "success",
170
236
  warn,
171
237
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.10.10",
3
+ "version": "0.10.12",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",