@foxden-app/foxclaw 0.12.0 → 0.13.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.
- package/CHANGELOG.md +20 -0
- package/dist/antigravity/account_ui.d.ts +34 -0
- package/dist/antigravity/account_ui.js +354 -0
- package/dist/antigravity/adapter.js +1 -1
- package/dist/antigravity/auth.d.ts +4 -1
- package/dist/antigravity/auth.js +17 -5
- package/dist/antigravity/controller.d.ts +3 -108
- package/dist/antigravity/controller.js +2 -2129
- package/dist/antigravity/conversation_ui.d.ts +56 -0
- package/dist/antigravity/conversation_ui.js +509 -0
- package/dist/antigravity/ui_callbacks.d.ts +6 -0
- package/dist/antigravity/ui_callbacks.js +6 -0
- package/dist/bridge/unified_bridge.d.ts +114 -0
- package/dist/bridge/unified_bridge.js +1343 -0
- package/dist/channels/bridge_messaging_router.d.ts +8 -4
- package/dist/channels/bridge_messaging_router.js +15 -4
- package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
- package/dist/channels/telegram/telegram_messaging_port.js +22 -2
- package/dist/channels/text_actions.d.ts +14 -0
- package/dist/channels/text_actions.js +48 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
- package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
- package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
- package/dist/channels/weixin/weixin_messaging_port.js +34 -7
- package/dist/codex_app/activity.d.ts +62 -0
- package/dist/codex_app/activity.js +418 -0
- package/dist/codex_app/adapter.d.ts +2 -2
- package/dist/codex_app/adapter.js +55 -25
- package/dist/codex_app/backend_ui.d.ts +4 -0
- package/dist/codex_app/backend_ui.js +48 -0
- package/dist/codex_app/client.js +0 -12
- package/dist/controller/access.d.ts +1 -11
- package/dist/controller/access.js +1 -33
- package/dist/controller/activity.d.ts +1 -62
- package/dist/controller/activity.js +2 -418
- package/dist/controller/auth_errors.d.ts +11 -0
- package/dist/controller/auth_errors.js +124 -0
- package/dist/controller/auth_files.d.ts +13 -0
- package/dist/controller/auth_files.js +156 -0
- package/dist/controller/auth_presentation.d.ts +77 -0
- package/dist/controller/auth_presentation.js +697 -0
- package/dist/controller/auth_quota_service.d.ts +27 -0
- package/dist/controller/auth_quota_service.js +151 -0
- package/dist/controller/controller.d.ts +30 -56
- package/dist/controller/controller.js +181 -3497
- package/dist/controller/interactions.d.ts +74 -0
- package/dist/controller/interactions.js +560 -0
- package/dist/controller/local_usage_service.d.ts +19 -0
- package/dist/controller/local_usage_service.js +62 -0
- package/dist/controller/native_panel_service.d.ts +55 -0
- package/dist/controller/native_panel_service.js +214 -0
- package/dist/controller/native_panels.d.ts +61 -0
- package/dist/controller/native_panels.js +784 -0
- package/dist/controller/shared_helpers.d.ts +53 -0
- package/dist/controller/shared_helpers.js +399 -0
- package/dist/controller/state_types.d.ts +390 -0
- package/dist/controller/state_types.js +82 -0
- package/dist/controller/turn_rendering.d.ts +22 -0
- package/dist/controller/turn_rendering.js +288 -0
- package/dist/core/access.d.ts +11 -0
- package/dist/core/access.js +33 -0
- package/dist/core/attachment_files.d.ts +13 -0
- package/dist/core/attachment_files.js +180 -0
- package/dist/core/attachment_types.d.ts +22 -0
- package/dist/core/attachment_types.js +1 -0
- package/dist/core/attachments.d.ts +5 -3
- package/dist/core/attachments.js +2 -2
- package/dist/core/backend_registry.d.ts +22 -0
- package/dist/core/backend_registry.js +50 -0
- package/dist/core/backend_ui.d.ts +28 -0
- package/dist/core/backend_ui.js +1 -0
- package/dist/core/channel_events.d.ts +48 -0
- package/dist/core/channel_events.js +1 -0
- package/dist/core/channel_port.d.ts +52 -8
- package/dist/core/engine_spi.d.ts +19 -4
- package/dist/core/listener_scope.d.ts +11 -0
- package/dist/core/listener_scope.js +10 -0
- package/dist/core/message_text.d.ts +2 -0
- package/dist/core/message_text.js +28 -0
- package/dist/core/network_errors.d.ts +1 -0
- package/dist/core/network_errors.js +23 -0
- package/dist/core/orchestrator.d.ts +53 -24
- package/dist/core/orchestrator.js +855 -507
- package/dist/core/queued_engine_input.d.ts +12 -0
- package/dist/core/queued_engine_input.js +16 -0
- package/dist/core/runtime_supervisor.d.ts +19 -0
- package/dist/core/runtime_supervisor.js +45 -0
- package/dist/core/scope_operations.d.ts +7 -0
- package/dist/core/scope_operations.js +19 -0
- package/dist/core/stream_preview.js +21 -9
- package/dist/dsh/adapter.js +18 -5
- package/dist/dsh/backend.d.ts +7 -0
- package/dist/dsh/backend.js +21 -0
- package/dist/dsh/runtime.d.ts +0 -1
- package/dist/dsh/runtime.js +4 -13
- package/dist/dsh/ui.d.ts +8 -8
- package/dist/dsh/ui.js +1 -1
- package/dist/main.js +55 -43
- package/dist/opencode/adapter.js +28 -9
- package/dist/opencode/controller.js +1 -1
- package/dist/store/channel_inbox.d.ts +14 -0
- package/dist/store/channel_inbox.js +21 -0
- package/dist/store/database.d.ts +7 -3
- package/dist/store/database.js +7 -2
- package/dist/store/task_journal.d.ts +32 -0
- package/dist/store/task_journal.js +70 -0
- package/dist/telegram/api.d.ts +2 -2
- package/dist/telegram/api.js +10 -28
- package/dist/telegram/gateway.d.ts +13 -27
- package/dist/telegram/gateway.js +86 -43
- package/dist/telegram/media.d.ts +2 -34
- package/dist/telegram/media.js +1 -180
- package/dist/telegram/task_preview.d.ts +13 -0
- package/dist/telegram/task_preview.js +92 -0
- package/dist/telegram/task_result.d.ts +2 -0
- package/dist/telegram/task_result.js +12 -0
- package/dist/telegram/text.d.ts +1 -1
- package/dist/telegram/text.js +2 -27
- package/dist/types.d.ts +1 -1
- package/docs/release.md +13 -11
- package/docs/zh/architecture-review.md +106 -0
- package/docs/zh/release.md +13 -11
- package/docs/zh/telegram-experience.md +40 -0
- package/package.json +5 -4
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
import { CODEX_AUTH_LIST_PAGE_SIZE, CODEX_AUTH_LOW_QUOTA_PERCENT, CODEX_AUTH_STALE_CREDENTIAL_DAYS, } from './state_types.js';
|
|
4
|
+
import { clipButtonText, formatShortStatusError, formatCompactNumber, formatLocalTimestamp } from './shared_helpers.js';
|
|
5
|
+
import { truncateInline } from './turn_rendering.js';
|
|
6
|
+
export function parseCodexAuthListRequest(action, args) {
|
|
7
|
+
if (action === 'list') {
|
|
8
|
+
return { searchTerm: args.slice(1).join(' ').trim() || null };
|
|
9
|
+
}
|
|
10
|
+
if (action === 'filter') {
|
|
11
|
+
const filter = args[1]?.toLowerCase();
|
|
12
|
+
return filter === 'all' || filter === 'enabled' || filter === 'attention'
|
|
13
|
+
? { filter }
|
|
14
|
+
: null;
|
|
15
|
+
}
|
|
16
|
+
if (action === 'page') {
|
|
17
|
+
const page = Number.parseInt(args[1] ?? '', 10);
|
|
18
|
+
return Number.isFinite(page) && page > 0
|
|
19
|
+
? { offset: (page - 1) * CODEX_AUTH_LIST_PAGE_SIZE }
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export function createPendingAuthChoiceList(chatId, candidates, view = {}) {
|
|
25
|
+
const record = {
|
|
26
|
+
localId: crypto.randomBytes(8).toString('hex'),
|
|
27
|
+
chatId,
|
|
28
|
+
messageId: null,
|
|
29
|
+
candidates,
|
|
30
|
+
createdAt: Date.now(),
|
|
31
|
+
offset: Math.max(0, view.offset ?? 0),
|
|
32
|
+
pageSize: CODEX_AUTH_LIST_PAGE_SIZE,
|
|
33
|
+
filter: view.filter ?? 'all',
|
|
34
|
+
searchTerm: view.searchTerm?.trim() || null,
|
|
35
|
+
};
|
|
36
|
+
clampCodexAuthListOffset(record);
|
|
37
|
+
return record;
|
|
38
|
+
}
|
|
39
|
+
export function clampCodexAuthListOffset(view) {
|
|
40
|
+
const filteredCount = filterCodexAuthCandidates(view.candidates, view).length;
|
|
41
|
+
const finalOffset = filteredCount > 0
|
|
42
|
+
? Math.floor((filteredCount - 1) / view.pageSize) * view.pageSize
|
|
43
|
+
: 0;
|
|
44
|
+
view.offset = Math.min(Math.max(0, view.offset), finalOffset);
|
|
45
|
+
}
|
|
46
|
+
export function codexAuthListPage(candidates, view) {
|
|
47
|
+
const pageSize = Math.max(1, view?.pageSize ?? CODEX_AUTH_LIST_PAGE_SIZE);
|
|
48
|
+
const filter = view?.filter ?? 'all';
|
|
49
|
+
const searchTerm = view?.searchTerm?.trim() || null;
|
|
50
|
+
const indexed = filterCodexAuthCandidates(candidates, { filter, searchTerm });
|
|
51
|
+
const finalOffset = indexed.length > 0
|
|
52
|
+
? Math.floor((indexed.length - 1) / pageSize) * pageSize
|
|
53
|
+
: 0;
|
|
54
|
+
const offset = Math.min(Math.max(0, view?.offset ?? 0), finalOffset);
|
|
55
|
+
return {
|
|
56
|
+
visible: indexed.slice(offset, offset + pageSize),
|
|
57
|
+
filteredCount: indexed.length,
|
|
58
|
+
offset,
|
|
59
|
+
pageIndex: Math.floor(offset / pageSize),
|
|
60
|
+
pageCount: Math.max(1, Math.ceil(indexed.length / pageSize)),
|
|
61
|
+
filter,
|
|
62
|
+
searchTerm,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function filterCodexAuthCandidates(candidates, view) {
|
|
66
|
+
const searchTerm = view.searchTerm?.trim().toLowerCase() || null;
|
|
67
|
+
return candidates
|
|
68
|
+
.map((candidate, index) => ({ candidate, index }))
|
|
69
|
+
.filter(({ candidate }) => !searchTerm || candidate.name.toLowerCase().includes(searchTerm))
|
|
70
|
+
.filter(({ candidate }) => view.filter === 'all'
|
|
71
|
+
|| (view.filter === 'enabled' && !candidate.disabled && candidate.state !== 'needs_repair')
|
|
72
|
+
|| (view.filter === 'attention' && codexAuthCandidateNeedsAttention(candidate)));
|
|
73
|
+
}
|
|
74
|
+
export function renderAuthListMessage(locale, state, botLabel = null, includeWeixinCopyPaste = false, view = null) {
|
|
75
|
+
const lines = [
|
|
76
|
+
t(locale, 'auth_list_title'),
|
|
77
|
+
];
|
|
78
|
+
if (botLabel) {
|
|
79
|
+
lines.push(t(locale, 'auth_bot', { value: botLabel }));
|
|
80
|
+
}
|
|
81
|
+
lines.push(t(locale, 'auth_current', {
|
|
82
|
+
value: state.currentLabel
|
|
83
|
+
? formatCodexAuthCandidateDisplayName(state.currentLabel)
|
|
84
|
+
: t(locale, 'none'),
|
|
85
|
+
}), t(locale, 'auth_dir', { value: state.authDir }));
|
|
86
|
+
if (state.candidates.length === 0) {
|
|
87
|
+
lines.push(t(locale, 'auth_no_candidates'));
|
|
88
|
+
if (includeWeixinCopyPaste) {
|
|
89
|
+
lines.push('', t(locale, 'weixin_copy_paste_divider'), t(locale, 'weixin_copy_auth_title'), '/login_device', '/auth sync safe', '/auth reload', '/permissions');
|
|
90
|
+
}
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
lines.push(t(locale, 'auth_candidate_count', { value: state.candidates.length }));
|
|
94
|
+
lines.push(t(locale, 'auth_quota_legend'));
|
|
95
|
+
const page = codexAuthListPage(state.candidates, view);
|
|
96
|
+
if (page.searchTerm) {
|
|
97
|
+
lines.push(t(locale, 'auth_search', { value: page.searchTerm }));
|
|
98
|
+
}
|
|
99
|
+
if (page.filter !== 'all') {
|
|
100
|
+
lines.push(t(locale, 'auth_filter', { value: formatCodexAuthFilter(locale, page.filter) }));
|
|
101
|
+
}
|
|
102
|
+
if (page.filteredCount !== state.candidates.length || page.pageCount > 1) {
|
|
103
|
+
lines.push(t(locale, 'auth_page', {
|
|
104
|
+
from: page.filteredCount === 0 ? 0 : page.offset + 1,
|
|
105
|
+
to: page.offset + page.visible.length,
|
|
106
|
+
filtered: page.filteredCount,
|
|
107
|
+
total: state.candidates.length,
|
|
108
|
+
page: page.pageIndex + 1,
|
|
109
|
+
pages: page.pageCount,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
if (page.visible.length === 0) {
|
|
113
|
+
lines.push(t(locale, 'auth_no_matches'));
|
|
114
|
+
}
|
|
115
|
+
page.visible.forEach(({ candidate, index }) => {
|
|
116
|
+
const marker = candidate.isCurrent ? ' *' : '';
|
|
117
|
+
lines.push(`${index + 1}. ${formatAuthQuotaPrefix(locale, candidate.quota)}|${formatCodexAuthCandidateDisplayName(candidate.name)}${marker} ${formatCodexAuthCandidateStatus(locale, candidate)}`);
|
|
118
|
+
});
|
|
119
|
+
if (includeWeixinCopyPaste) {
|
|
120
|
+
lines.push('', t(locale, 'weixin_copy_paste_divider'), t(locale, 'weixin_copy_auth_title'), ...page.visible.map(({ index }) => `/auth use ${index + 1}`), ...page.visible.map(({ candidate, index }) => candidate.disabled
|
|
121
|
+
? `/auth enable ${index + 1}`
|
|
122
|
+
: `/auth disable ${index + 1}`), '/auth filter all', '/auth filter enabled', '/auth filter attention', '/auth list <keyword>', '/login_device', '/auth sync safe', '/auth reload', '/permissions');
|
|
123
|
+
}
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
|
126
|
+
export function authChoiceKeyboard(locale, record) {
|
|
127
|
+
const page = codexAuthListPage(record.candidates, record);
|
|
128
|
+
const rows = page.visible.map(({ candidate, index }) => [
|
|
129
|
+
{
|
|
130
|
+
text: clipButtonText(`${candidate.state === 'needs_repair' ? '? ' : candidate.isCurrent ? '✅ ' : '🔐 '}${formatAuthQuotaButtonPrefix(candidate.quota)}|${formatCodexAuthCandidateDisplayName(candidate.name)}${candidate.disabled ? ' · off' : ''}`),
|
|
131
|
+
callback_data: candidate.state === 'needs_repair' ? `auth:${record.localId}:repair:${index}` : `auth:${record.localId}:${index}`,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
text: candidate.state === 'needs_repair' ? '?' : t(locale, candidate.disabled ? 'button_auth_disable' : 'button_auth_enable'),
|
|
135
|
+
callback_data: candidate.state === 'needs_repair' ? `auth:${record.localId}:repair:${index}` : `auth:${record.localId}:toggle:${index}`,
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
const navigationRow = [];
|
|
139
|
+
if (page.offset > 0) {
|
|
140
|
+
navigationRow.push({ text: t(locale, 'button_prev_page'), callback_data: `auth:${record.localId}:page:prev` });
|
|
141
|
+
}
|
|
142
|
+
if (page.offset + page.visible.length < page.filteredCount) {
|
|
143
|
+
navigationRow.push({ text: t(locale, 'button_next_page'), callback_data: `auth:${record.localId}:page:next` });
|
|
144
|
+
}
|
|
145
|
+
if (navigationRow.length > 0) {
|
|
146
|
+
rows.push(navigationRow);
|
|
147
|
+
}
|
|
148
|
+
rows.push([
|
|
149
|
+
authFilterButton(locale, record, 'all'),
|
|
150
|
+
authFilterButton(locale, record, 'enabled'),
|
|
151
|
+
authFilterButton(locale, record, 'attention'),
|
|
152
|
+
]);
|
|
153
|
+
if (record.searchTerm) {
|
|
154
|
+
rows.push([{ text: t(locale, 'button_clear_filter'), callback_data: `auth:${record.localId}:clear_search` }]);
|
|
155
|
+
}
|
|
156
|
+
rows.push([{ text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` }]);
|
|
157
|
+
rows.push([{ text: t(locale, 'button_auth_safe_sync'), callback_data: `auth:${record.localId}:safe_sync` }]);
|
|
158
|
+
return rows;
|
|
159
|
+
}
|
|
160
|
+
export function formatCodexAuthCandidateDisplayName(name) {
|
|
161
|
+
const prefix = 'auth.json_';
|
|
162
|
+
return name.length > prefix.length && name.startsWith(prefix)
|
|
163
|
+
? name.slice(prefix.length)
|
|
164
|
+
: name;
|
|
165
|
+
}
|
|
166
|
+
export function authFilterButton(locale, record, filter) {
|
|
167
|
+
const label = t(locale, `button_auth_filter_${filter}`);
|
|
168
|
+
return {
|
|
169
|
+
text: record.filter === filter ? `☑️ ${label}` : label,
|
|
170
|
+
callback_data: `auth:${record.localId}:filter:${filter}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export function codexAuthCandidateHealth(candidate) {
|
|
174
|
+
if (candidate.state === 'needs_repair') {
|
|
175
|
+
return 'needs_repair';
|
|
176
|
+
}
|
|
177
|
+
if (candidate.disabled) {
|
|
178
|
+
return 'disabled';
|
|
179
|
+
}
|
|
180
|
+
if (candidate.credentialKind === 'api-key') {
|
|
181
|
+
return 'api-key';
|
|
182
|
+
}
|
|
183
|
+
if (candidate.credentialKind === 'invalid') {
|
|
184
|
+
return 'invalid';
|
|
185
|
+
}
|
|
186
|
+
const remaining = [
|
|
187
|
+
candidate.quota?.primaryRemainingPercent,
|
|
188
|
+
candidate.quota?.secondaryRemainingPercent,
|
|
189
|
+
].filter((value) => typeof value === 'number' && Number.isFinite(value));
|
|
190
|
+
if (remaining.some(value => value <= 0)) {
|
|
191
|
+
return 'exhausted';
|
|
192
|
+
}
|
|
193
|
+
if (remaining.some(value => value <= CODEX_AUTH_LOW_QUOTA_PERCENT)) {
|
|
194
|
+
return 'low';
|
|
195
|
+
}
|
|
196
|
+
if (candidate.credentialLastRefreshMs !== null
|
|
197
|
+
&& candidate.credentialLastRefreshMs < Date.now() - CODEX_AUTH_STALE_CREDENTIAL_DAYS * 24 * 60 * 60_000) {
|
|
198
|
+
return 'stale';
|
|
199
|
+
}
|
|
200
|
+
if (remaining.length === 0) {
|
|
201
|
+
return 'unknown';
|
|
202
|
+
}
|
|
203
|
+
return 'ready';
|
|
204
|
+
}
|
|
205
|
+
export function codexAuthCandidateNeedsAttention(candidate) {
|
|
206
|
+
const health = codexAuthCandidateHealth(candidate);
|
|
207
|
+
return health === 'stale'
|
|
208
|
+
|| health === 'unknown'
|
|
209
|
+
|| health === 'low'
|
|
210
|
+
|| health === 'exhausted'
|
|
211
|
+
|| health === 'needs_repair'
|
|
212
|
+
|| health === 'invalid';
|
|
213
|
+
}
|
|
214
|
+
export function formatCodexAuthCandidateStatus(locale, candidate) {
|
|
215
|
+
const details = [];
|
|
216
|
+
if (candidate.quota?.planType) {
|
|
217
|
+
details.push(formatPlanTypeLabel(candidate.quota.planType));
|
|
218
|
+
}
|
|
219
|
+
details.push(t(locale, `auth_candidate_health_${codexAuthCandidateHealth(candidate)}`));
|
|
220
|
+
if (candidate.credentialLastRefreshMs !== null) {
|
|
221
|
+
details.push(t(locale, 'auth_candidate_last_refresh', {
|
|
222
|
+
value: formatCompactAge(locale, Date.now() - candidate.credentialLastRefreshMs),
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
if (candidate.credentialExpiresAtMs !== null) {
|
|
226
|
+
details.push(t(locale, 'auth_candidate_expires_at', {
|
|
227
|
+
value: formatUtcDateTime(candidate.credentialExpiresAtMs),
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
return `[${details.join(' · ')}]`;
|
|
231
|
+
}
|
|
232
|
+
export function formatCodexAuthFilter(locale, filter) {
|
|
233
|
+
return t(locale, `auth_filter_${filter}`);
|
|
234
|
+
}
|
|
235
|
+
export function authRefreshAllConfirmKeyboard(locale, record) {
|
|
236
|
+
return [
|
|
237
|
+
[{ text: t(locale, 'button_auth_refresh_all_confirm'), callback_data: `auth:${record.localId}:refresh_all_confirm` }],
|
|
238
|
+
[{ text: t(locale, 'button_cancel'), callback_data: `auth:${record.localId}:refresh_all_cancel` }],
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
export function authRepairKeyboard(locale, record, index) {
|
|
242
|
+
return [
|
|
243
|
+
[{ text: t(locale, 'button_auth_repair_login'), callback_data: `auth:${record.localId}:repair_login:${index}` }],
|
|
244
|
+
[{ text: t(locale, 'button_auth_delete'), callback_data: `auth:${record.localId}:repair_delete:${index}` }],
|
|
245
|
+
[{ text: t(locale, 'button_cancel'), callback_data: `auth:${record.localId}:repair_cancel:${index}` }],
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
export function formatAuthRefreshAllResult(locale, result, mode = 'manual') {
|
|
249
|
+
const lines = [t(locale, mode === 'proactive' ? 'auth_proactive_refresh_done' : 'auth_refresh_all_done', {
|
|
250
|
+
refreshed: String(result.refreshed.length),
|
|
251
|
+
skipped: String(result.skipped.length),
|
|
252
|
+
failed: String(result.failed.length),
|
|
253
|
+
})];
|
|
254
|
+
if (result.refreshed.length > 0) {
|
|
255
|
+
lines.push(t(locale, 'auth_refresh_all_refreshed', { value: result.refreshed.join(', ') }));
|
|
256
|
+
}
|
|
257
|
+
if (result.skipped.length > 0) {
|
|
258
|
+
lines.push(t(locale, 'auth_refresh_all_skipped', { value: result.skipped.join(', ') }));
|
|
259
|
+
}
|
|
260
|
+
if (result.failed.length > 0) {
|
|
261
|
+
const details = result.failed
|
|
262
|
+
.slice(0, 5)
|
|
263
|
+
.map((failure) => `${failure.name}: ${failure.error}`)
|
|
264
|
+
.join('; ');
|
|
265
|
+
lines.push(t(locale, 'auth_refresh_all_failed', { value: details }));
|
|
266
|
+
}
|
|
267
|
+
return lines.join('\n');
|
|
268
|
+
}
|
|
269
|
+
export function formatAuthClusterAuditResult(locale, outcome) {
|
|
270
|
+
const { audit, refresh, push } = outcome;
|
|
271
|
+
const lines = [t(locale, 'auth_cluster_audit_summary', {
|
|
272
|
+
responded: audit.nodesResponded,
|
|
273
|
+
expected: audit.nodesExpected,
|
|
274
|
+
checked: audit.checkedCandidates,
|
|
275
|
+
valid: audit.validCandidates,
|
|
276
|
+
invalid: audit.invalidCandidates,
|
|
277
|
+
})];
|
|
278
|
+
if (audit.synchronizedCandidates.length > 0) {
|
|
279
|
+
lines.push(t(locale, 'auth_cluster_audit_synced', { value: audit.synchronizedCandidates.join(', ') }));
|
|
280
|
+
}
|
|
281
|
+
if (audit.consensusInvalidCandidates.length > 0) {
|
|
282
|
+
lines.push(t(locale, 'auth_cluster_audit_repair', { value: audit.consensusInvalidCandidates.join(', ') }));
|
|
283
|
+
}
|
|
284
|
+
if (audit.missingPeers.length > 0) {
|
|
285
|
+
lines.push(t(locale, 'auth_cluster_audit_missing', { value: audit.missingPeers.join(', ') }));
|
|
286
|
+
}
|
|
287
|
+
if (audit.busyNodes.length > 0) {
|
|
288
|
+
lines.push(t(locale, 'auth_cluster_audit_busy', { value: audit.busyNodes.join(', ') }));
|
|
289
|
+
}
|
|
290
|
+
if (audit.identityConflicts.length > 0) {
|
|
291
|
+
lines.push(t(locale, 'auth_cluster_audit_conflicts', { value: audit.identityConflicts.join(', ') }));
|
|
292
|
+
}
|
|
293
|
+
if (outcome.refreshSkippedReason) {
|
|
294
|
+
lines.push(t(locale, 'auth_cluster_audit_refresh_skipped'));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
lines.push(formatAuthRefreshAllResult(locale, refresh, 'proactive'));
|
|
298
|
+
}
|
|
299
|
+
lines.push(t(locale, 'auth_cluster_audit_push', { sent: push.sent, skipped: push.skipped }));
|
|
300
|
+
return lines.join('\n');
|
|
301
|
+
}
|
|
302
|
+
export function formatAuthSyncStatus(locale, status, proactiveRefresh = null) {
|
|
303
|
+
if (!status?.enabled) {
|
|
304
|
+
return t(locale, 'auth_sync_disabled');
|
|
305
|
+
}
|
|
306
|
+
const lines = [
|
|
307
|
+
t(locale, 'auth_sync_status_title'),
|
|
308
|
+
t(locale, 'auth_sync_status_node', { value: status.nodeId ?? t(locale, 'unknown') }),
|
|
309
|
+
t(locale, 'auth_sync_status_transport', { value: status.transportLabel ?? t(locale, 'unknown') }),
|
|
310
|
+
t(locale, 'auth_sync_status_peers', { value: status.peers.length === 0 ? t(locale, 'none') : status.peers.join(', ') }),
|
|
311
|
+
t(locale, 'auth_sync_status_pending', { value: status.pendingImports }),
|
|
312
|
+
t(locale, 'auth_sync_status_sent', { value: status.lastSentAt ?? t(locale, 'none') }),
|
|
313
|
+
t(locale, 'auth_sync_status_received', { value: status.lastReceivedAt ?? t(locale, 'none') }),
|
|
314
|
+
t(locale, 'auth_sync_status_imported', {
|
|
315
|
+
value: status.lastImportedAt
|
|
316
|
+
? `${status.lastImportCandidate ?? t(locale, 'unknown')} @ ${status.lastImportedAt}`
|
|
317
|
+
: t(locale, 'none'),
|
|
318
|
+
}),
|
|
319
|
+
t(locale, 'auth_sync_status_pull', {
|
|
320
|
+
value: status.lastPullAt
|
|
321
|
+
? `${status.lastPullCandidate ?? t(locale, 'unknown')} @ ${status.lastPullAt}`
|
|
322
|
+
: t(locale, 'none'),
|
|
323
|
+
}),
|
|
324
|
+
];
|
|
325
|
+
if (status.activeLeaseId) {
|
|
326
|
+
lines.push(t(locale, 'auth_sync_status_lease', { value: status.activeLeaseId }));
|
|
327
|
+
}
|
|
328
|
+
if (status.lastError) {
|
|
329
|
+
lines.push(t(locale, 'auth_sync_status_error', { value: status.lastError }));
|
|
330
|
+
}
|
|
331
|
+
if (proactiveRefresh) {
|
|
332
|
+
lines.push(t(locale, 'auth_sync_status_proactive_refresh', {
|
|
333
|
+
value: formatAuthProactiveRefreshStatus(locale, proactiveRefresh),
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
if (status.peerStatuses?.length) {
|
|
337
|
+
lines.push(t(locale, 'auth_sync_status_peer_activity'));
|
|
338
|
+
for (const peer of status.peerStatuses.slice(0, 5)) {
|
|
339
|
+
lines.push(t(locale, 'auth_sync_status_peer_activity_item', {
|
|
340
|
+
peer: peer.peer,
|
|
341
|
+
time: peer.lastReceivedAt ?? t(locale, 'none'),
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (status.candidateFailures?.length) {
|
|
346
|
+
lines.push(t(locale, 'auth_sync_status_candidate_failures'));
|
|
347
|
+
for (const failure of status.candidateFailures.slice(0, 5)) {
|
|
348
|
+
lines.push(t(locale, 'auth_sync_status_candidate_failure', {
|
|
349
|
+
candidate: failure.candidateName,
|
|
350
|
+
reason: failure.reason,
|
|
351
|
+
source: failure.sourceLabel ?? failure.sourceNodeId ?? t(locale, 'unknown'),
|
|
352
|
+
peer: failure.peer ?? t(locale, 'unknown'),
|
|
353
|
+
time: failure.updatedAt,
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const recentEvents = [...(status.recentEvents ?? [])].slice(-5);
|
|
358
|
+
if (recentEvents.length > 0) {
|
|
359
|
+
lines.push(t(locale, 'auth_sync_status_recent_events'));
|
|
360
|
+
for (const event of recentEvents) {
|
|
361
|
+
lines.push(formatAuthSyncEventLine(event));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return lines.join('\n');
|
|
365
|
+
}
|
|
366
|
+
export function formatAuthProactiveRefreshStatus(locale, status) {
|
|
367
|
+
const time = status.finishedAt ?? status.startedAt;
|
|
368
|
+
const candidates = formatLimitedList(status.candidates, 3);
|
|
369
|
+
const state = formatAuthProactiveRefreshState(locale, status.state);
|
|
370
|
+
const result = locale === 'zh'
|
|
371
|
+
? `已刷新 ${status.refreshed},已跳过 ${status.skipped},失败 ${status.failed}`
|
|
372
|
+
: `refreshed ${status.refreshed}, skipped ${status.skipped}, failed ${status.failed}`;
|
|
373
|
+
const parts = [
|
|
374
|
+
`${state} @ ${time}`,
|
|
375
|
+
locale === 'zh' ? `候选 ${candidates}` : `candidates ${candidates}`,
|
|
376
|
+
result,
|
|
377
|
+
];
|
|
378
|
+
if (status.error) {
|
|
379
|
+
parts.push(locale === 'zh'
|
|
380
|
+
? `原因 ${formatShortStatusError(status.error)}`
|
|
381
|
+
: `reason ${formatShortStatusError(status.error)}`);
|
|
382
|
+
}
|
|
383
|
+
if (status.details.length > 0) {
|
|
384
|
+
parts.push(locale === 'zh'
|
|
385
|
+
? `明细 ${formatLimitedList(status.details.map(formatShortStatusError), 2)}`
|
|
386
|
+
: `details ${formatLimitedList(status.details.map(formatShortStatusError), 2)}`);
|
|
387
|
+
}
|
|
388
|
+
return parts.join('; ');
|
|
389
|
+
}
|
|
390
|
+
export function formatAuthProactiveRefreshState(locale, state) {
|
|
391
|
+
if (locale === 'zh') {
|
|
392
|
+
switch (state) {
|
|
393
|
+
case 'running': return '进行中';
|
|
394
|
+
case 'completed': return '已完成';
|
|
395
|
+
case 'lease_failed': return '锁未授予';
|
|
396
|
+
case 'failed': return '失败';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
switch (state) {
|
|
400
|
+
case 'running': return 'running';
|
|
401
|
+
case 'completed': return 'completed';
|
|
402
|
+
case 'lease_failed': return 'lease not granted';
|
|
403
|
+
case 'failed': return 'failed';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export function formatLimitedList(values, limit) {
|
|
407
|
+
if (values.length === 0)
|
|
408
|
+
return '-';
|
|
409
|
+
const shown = values.slice(0, limit).join(', ');
|
|
410
|
+
return values.length > limit ? `${shown}, +${values.length - limit}` : shown;
|
|
411
|
+
}
|
|
412
|
+
export function formatAuthSyncEvents(locale, status, filter) {
|
|
413
|
+
if (!status?.enabled) {
|
|
414
|
+
return t(locale, 'auth_sync_disabled');
|
|
415
|
+
}
|
|
416
|
+
const events = filterAuthSyncEvents(status.recentEvents ?? [], filter).slice(-15);
|
|
417
|
+
if (events.length === 0) {
|
|
418
|
+
return [
|
|
419
|
+
t(locale, 'auth_sync_events_title'),
|
|
420
|
+
t(locale, 'auth_sync_events_empty'),
|
|
421
|
+
].join('\n');
|
|
422
|
+
}
|
|
423
|
+
return [
|
|
424
|
+
t(locale, 'auth_sync_events_title'),
|
|
425
|
+
...events.map(formatAuthSyncEventLine),
|
|
426
|
+
].join('\n');
|
|
427
|
+
}
|
|
428
|
+
export function formatAuthSyncTrace(locale, status, requestId) {
|
|
429
|
+
if (!status?.enabled) {
|
|
430
|
+
return t(locale, 'auth_sync_disabled');
|
|
431
|
+
}
|
|
432
|
+
const events = (status.recentEvents ?? [])
|
|
433
|
+
.filter(event => event.requestId === requestId || event.id === requestId)
|
|
434
|
+
.slice(-25);
|
|
435
|
+
if (events.length === 0) {
|
|
436
|
+
return [
|
|
437
|
+
t(locale, 'auth_sync_trace_title', { value: requestId }),
|
|
438
|
+
t(locale, 'auth_sync_events_empty'),
|
|
439
|
+
].join('\n');
|
|
440
|
+
}
|
|
441
|
+
return [
|
|
442
|
+
t(locale, 'auth_sync_trace_title', { value: requestId }),
|
|
443
|
+
...events.map(formatAuthSyncEventLine),
|
|
444
|
+
].join('\n');
|
|
445
|
+
}
|
|
446
|
+
export function filterAuthSyncEvents(events, filter) {
|
|
447
|
+
const normalized = filter?.trim().toLowerCase() ?? '';
|
|
448
|
+
if (!normalized) {
|
|
449
|
+
return events;
|
|
450
|
+
}
|
|
451
|
+
return events.filter((event) => [
|
|
452
|
+
event.id,
|
|
453
|
+
event.requestId,
|
|
454
|
+
event.candidateName,
|
|
455
|
+
event.peer,
|
|
456
|
+
event.kind,
|
|
457
|
+
event.stage,
|
|
458
|
+
event.detail,
|
|
459
|
+
].some(value => value?.toLowerCase().includes(normalized)));
|
|
460
|
+
}
|
|
461
|
+
export function formatAuthSyncEventLine(event) {
|
|
462
|
+
const fields = [
|
|
463
|
+
event.createdAt,
|
|
464
|
+
event.direction,
|
|
465
|
+
event.kind,
|
|
466
|
+
event.stage,
|
|
467
|
+
event.peer ? `peer=${event.peer}` : null,
|
|
468
|
+
event.requestId ? `requestId=${event.requestId}` : null,
|
|
469
|
+
event.candidateName ? `candidate=${event.candidateName}` : null,
|
|
470
|
+
event.detail ? truncateInline(event.detail, 160) : null,
|
|
471
|
+
].filter(Boolean);
|
|
472
|
+
return `- ${fields.join(' | ')}`;
|
|
473
|
+
}
|
|
474
|
+
export function selectCodexRateLimitSnapshot(limits) {
|
|
475
|
+
return limits?.rateLimitsByLimitId?.codex
|
|
476
|
+
?? Object.values(limits?.rateLimitsByLimitId ?? {})[0]
|
|
477
|
+
?? limits?.rateLimits
|
|
478
|
+
?? null;
|
|
479
|
+
}
|
|
480
|
+
export function formatCodexAccountLabel(account) {
|
|
481
|
+
if (account.type === 'chatgpt') {
|
|
482
|
+
return 'ChatGPT';
|
|
483
|
+
}
|
|
484
|
+
if (account.type === 'apiKey') {
|
|
485
|
+
return 'API key';
|
|
486
|
+
}
|
|
487
|
+
if (account.type === 'amazonBedrock') {
|
|
488
|
+
return 'Amazon Bedrock';
|
|
489
|
+
}
|
|
490
|
+
return formatPlanTypeLabel(account.type || 'unknown');
|
|
491
|
+
}
|
|
492
|
+
export function formatPlanTypeLabel(value) {
|
|
493
|
+
const words = value
|
|
494
|
+
.replace(/[_-]+/g, ' ')
|
|
495
|
+
.trim()
|
|
496
|
+
.split(/\s+/)
|
|
497
|
+
.filter(Boolean);
|
|
498
|
+
if (words.length === 0) {
|
|
499
|
+
return 'Unknown';
|
|
500
|
+
}
|
|
501
|
+
return words
|
|
502
|
+
.map((word) => word.toLowerCase() === 'api' ? 'API' : `${word[0].toUpperCase()}${word.slice(1)}`)
|
|
503
|
+
.join(' ');
|
|
504
|
+
}
|
|
505
|
+
export function formatRateLimitWindowLabel(locale, window, fallback) {
|
|
506
|
+
const minutes = window.windowDurationMins;
|
|
507
|
+
if (!minutes || minutes <= 0) {
|
|
508
|
+
return fallback === 'primary'
|
|
509
|
+
? (locale === 'zh' ? '短周期' : 'Primary window')
|
|
510
|
+
: (locale === 'zh' ? '长周期' : 'Secondary window');
|
|
511
|
+
}
|
|
512
|
+
if (minutes % 10080 === 0) {
|
|
513
|
+
const days = minutes / 1440;
|
|
514
|
+
return locale === 'zh' ? `${formatCompactNumber(days)}天` : `${formatCompactNumber(days)}d window`;
|
|
515
|
+
}
|
|
516
|
+
if (minutes % 1440 === 0) {
|
|
517
|
+
const days = minutes / 1440;
|
|
518
|
+
return locale === 'zh' ? `${formatCompactNumber(days)}天` : `${formatCompactNumber(days)}d window`;
|
|
519
|
+
}
|
|
520
|
+
if (minutes % 60 === 0) {
|
|
521
|
+
const hours = minutes / 60;
|
|
522
|
+
return locale === 'zh' ? `${formatCompactNumber(hours)}小时` : `${formatCompactNumber(hours)}h window`;
|
|
523
|
+
}
|
|
524
|
+
return locale === 'zh' ? `${formatCompactNumber(minutes)}分钟` : `${formatCompactNumber(minutes)}m window`;
|
|
525
|
+
}
|
|
526
|
+
export function formatCompactRateLimitWindowLabel(locale, minutes, fallback) {
|
|
527
|
+
if (!minutes || minutes <= 0) {
|
|
528
|
+
return fallback === 'primary'
|
|
529
|
+
? (locale === 'zh' ? '短' : 'P')
|
|
530
|
+
: (locale === 'zh' ? '长' : 'S');
|
|
531
|
+
}
|
|
532
|
+
if (minutes % 1440 === 0) {
|
|
533
|
+
return `${formatCompactNumber(minutes / 1440)}d`;
|
|
534
|
+
}
|
|
535
|
+
if (minutes % 60 === 0) {
|
|
536
|
+
return `${formatCompactNumber(minutes / 60)}h`;
|
|
537
|
+
}
|
|
538
|
+
return `${formatCompactNumber(minutes)}m`;
|
|
539
|
+
}
|
|
540
|
+
export function formatCompactAge(locale, ageMs) {
|
|
541
|
+
const minutes = Math.max(0, ageMs) / 60_000;
|
|
542
|
+
if (minutes >= 1440) {
|
|
543
|
+
const days = Math.floor(minutes / 1440);
|
|
544
|
+
return locale === 'zh' ? `${days}天前` : `${days}d ago`;
|
|
545
|
+
}
|
|
546
|
+
if (minutes >= 60) {
|
|
547
|
+
const hours = Math.floor(minutes / 60);
|
|
548
|
+
return locale === 'zh' ? `${hours}小时前` : `${hours}h ago`;
|
|
549
|
+
}
|
|
550
|
+
const roundedMinutes = Math.floor(minutes);
|
|
551
|
+
return locale === 'zh' ? `${roundedMinutes}分钟前` : `${roundedMinutes}m ago`;
|
|
552
|
+
}
|
|
553
|
+
export function formatUtcDateTime(timestampMs) {
|
|
554
|
+
if (!Number.isFinite(timestampMs)) {
|
|
555
|
+
return '-';
|
|
556
|
+
}
|
|
557
|
+
return `${new Date(timestampMs).toISOString().slice(0, 16).replace('T', ' ')}Z`;
|
|
558
|
+
}
|
|
559
|
+
export function formatUsagePercent(value) {
|
|
560
|
+
if (!Number.isFinite(value)) {
|
|
561
|
+
return '?';
|
|
562
|
+
}
|
|
563
|
+
return formatCompactNumber(value);
|
|
564
|
+
}
|
|
565
|
+
export function formatRemainingUsagePercent(usedPercent) {
|
|
566
|
+
const remainingPercent = remainingUsagePercent(usedPercent);
|
|
567
|
+
return remainingPercent === null ? '?' : formatUsagePercent(remainingPercent);
|
|
568
|
+
}
|
|
569
|
+
export function authQuotaSnapshotFromRateLimit(snapshot, accountId = null, quotaIdentityId = null) {
|
|
570
|
+
return {
|
|
571
|
+
capturedAtMs: Date.now(),
|
|
572
|
+
accountId,
|
|
573
|
+
quotaIdentityId,
|
|
574
|
+
planType: snapshot.planType,
|
|
575
|
+
primaryWindowDurationMins: snapshot.primary?.windowDurationMins ?? null,
|
|
576
|
+
primaryRemainingPercent: snapshot.primary ? remainingUsagePercent(snapshot.primary.usedPercent) : null,
|
|
577
|
+
primaryResetsAt: snapshot.primary?.resetsAt ?? null,
|
|
578
|
+
secondaryWindowDurationMins: snapshot.secondary?.windowDurationMins ?? null,
|
|
579
|
+
secondaryRemainingPercent: snapshot.secondary ? remainingUsagePercent(snapshot.secondary.usedPercent) : null,
|
|
580
|
+
secondaryResetsAt: snapshot.secondary?.resetsAt ?? null,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
export function codexAuthQuotaSnapshotFromRecord(record) {
|
|
584
|
+
return {
|
|
585
|
+
capturedAtMs: record.capturedAtMs,
|
|
586
|
+
accountId: record.accountId,
|
|
587
|
+
quotaIdentityId: record.quotaIdentityId,
|
|
588
|
+
planType: record.planType,
|
|
589
|
+
primaryWindowDurationMins: record.primaryWindowDurationMins,
|
|
590
|
+
primaryRemainingPercent: record.primaryRemainingPercent,
|
|
591
|
+
primaryResetsAt: record.primaryResetsAt,
|
|
592
|
+
secondaryWindowDurationMins: record.secondaryWindowDurationMins,
|
|
593
|
+
secondaryRemainingPercent: record.secondaryRemainingPercent,
|
|
594
|
+
secondaryResetsAt: record.secondaryResetsAt,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
export function mergeCodexAuthQuotaSnapshots(current, incoming) {
|
|
598
|
+
if (!current) {
|
|
599
|
+
return incoming;
|
|
600
|
+
}
|
|
601
|
+
if (!incoming) {
|
|
602
|
+
return current;
|
|
603
|
+
}
|
|
604
|
+
const freshest = incoming.capturedAtMs >= current.capturedAtMs ? incoming : current;
|
|
605
|
+
const older = freshest === incoming ? current : incoming;
|
|
606
|
+
return {
|
|
607
|
+
...freshest,
|
|
608
|
+
accountId: freshest.accountId ?? older.accountId ?? null,
|
|
609
|
+
quotaIdentityId: freshest.quotaIdentityId ?? older.quotaIdentityId ?? null,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
export function isFiniteCodexAuthQuotaSnapshotRecord(record) {
|
|
613
|
+
return Number.isFinite(record.capturedAtMs)
|
|
614
|
+
&& isNullableString(record.planType)
|
|
615
|
+
&& isNullableFiniteNumber(record.primaryWindowDurationMins)
|
|
616
|
+
&& isNullableFiniteNumber(record.primaryRemainingPercent)
|
|
617
|
+
&& isNullableFiniteNumber(record.primaryResetsAt)
|
|
618
|
+
&& isNullableFiniteNumber(record.secondaryWindowDurationMins)
|
|
619
|
+
&& isNullableFiniteNumber(record.secondaryRemainingPercent)
|
|
620
|
+
&& isNullableFiniteNumber(record.secondaryResetsAt);
|
|
621
|
+
}
|
|
622
|
+
export function remainingUsagePercent(usedPercent) {
|
|
623
|
+
if (!Number.isFinite(usedPercent)) {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
return Math.max(0, Math.min(100, 100 - usedPercent));
|
|
627
|
+
}
|
|
628
|
+
export function formatAuthQuotaPrefix(locale, snapshot) {
|
|
629
|
+
if (!snapshot) {
|
|
630
|
+
return '--';
|
|
631
|
+
}
|
|
632
|
+
const windows = [
|
|
633
|
+
[snapshot.primaryWindowDurationMins, snapshot.primaryRemainingPercent, snapshot.primaryResetsAt, 'primary'],
|
|
634
|
+
[snapshot.secondaryWindowDurationMins, snapshot.secondaryRemainingPercent, snapshot.secondaryResetsAt, 'secondary'],
|
|
635
|
+
];
|
|
636
|
+
const values = windows
|
|
637
|
+
.filter(([duration, remaining, resetsAt]) => duration !== null || remaining !== null || resetsAt !== null)
|
|
638
|
+
.map(([duration, remaining, resetsAt, fallback]) => (`${formatCompactRateLimitWindowLabel(locale, duration, fallback)}:${remaining === null ? '--' : formatUsagePercent(remaining)}${resetsAt === null ? '' : `@${formatLocalTimestamp(resetsAt)}`}`));
|
|
639
|
+
return values.length > 0 ? values.join('|') : '--';
|
|
640
|
+
}
|
|
641
|
+
export function formatAuthQuotaButtonPrefix(snapshot) {
|
|
642
|
+
return [
|
|
643
|
+
snapshot?.primaryRemainingPercent ?? null,
|
|
644
|
+
snapshot?.secondaryRemainingPercent ?? null,
|
|
645
|
+
].map(formatAuthQuotaButtonValue).join('|');
|
|
646
|
+
}
|
|
647
|
+
export function formatAuthQuotaButtonValue(value) {
|
|
648
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
649
|
+
? formatUsagePercent(value)
|
|
650
|
+
: '—';
|
|
651
|
+
}
|
|
652
|
+
export function isCodexAuthQuotaSnapshot(value) {
|
|
653
|
+
if (!value || typeof value !== 'object') {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
const snapshot = value;
|
|
657
|
+
return typeof snapshot.capturedAtMs === 'number'
|
|
658
|
+
&& Number.isFinite(snapshot.capturedAtMs)
|
|
659
|
+
&& (snapshot.accountId === undefined || snapshot.accountId === null || typeof snapshot.accountId === 'string')
|
|
660
|
+
&& (snapshot.quotaIdentityId === undefined || snapshot.quotaIdentityId === null || typeof snapshot.quotaIdentityId === 'string')
|
|
661
|
+
&& (snapshot.planType === undefined || isNullableString(snapshot.planType))
|
|
662
|
+
&& (snapshot.primaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.primaryWindowDurationMins))
|
|
663
|
+
&& isNullableFiniteNumber(snapshot.primaryRemainingPercent)
|
|
664
|
+
&& (snapshot.primaryResetsAt === undefined || isNullableFiniteNumber(snapshot.primaryResetsAt))
|
|
665
|
+
&& (snapshot.secondaryWindowDurationMins === undefined || isNullableFiniteNumber(snapshot.secondaryWindowDurationMins))
|
|
666
|
+
&& isNullableFiniteNumber(snapshot.secondaryRemainingPercent)
|
|
667
|
+
&& (snapshot.secondaryResetsAt === undefined || isNullableFiniteNumber(snapshot.secondaryResetsAt));
|
|
668
|
+
}
|
|
669
|
+
export function normalizeCodexAuthQuotaSnapshot(snapshot) {
|
|
670
|
+
return {
|
|
671
|
+
capturedAtMs: snapshot.capturedAtMs,
|
|
672
|
+
accountId: snapshot.accountId ?? null,
|
|
673
|
+
quotaIdentityId: snapshot.quotaIdentityId ?? snapshot.accountId ?? null,
|
|
674
|
+
planType: snapshot.planType ?? null,
|
|
675
|
+
primaryWindowDurationMins: snapshot.primaryWindowDurationMins ?? null,
|
|
676
|
+
primaryRemainingPercent: snapshot.primaryRemainingPercent,
|
|
677
|
+
primaryResetsAt: snapshot.primaryResetsAt ?? null,
|
|
678
|
+
secondaryWindowDurationMins: snapshot.secondaryWindowDurationMins ?? null,
|
|
679
|
+
secondaryRemainingPercent: snapshot.secondaryRemainingPercent,
|
|
680
|
+
secondaryResetsAt: snapshot.secondaryResetsAt ?? null,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
export function chatGptAuthMetadataCompatible(left, right) {
|
|
684
|
+
if (left.accountId !== right.accountId) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
if (left.quotaIdentityId === left.accountId || right.quotaIdentityId === right.accountId) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
return left.quotaIdentityId === right.quotaIdentityId;
|
|
691
|
+
}
|
|
692
|
+
export function isNullableFiniteNumber(value) {
|
|
693
|
+
return value === null || (typeof value === 'number' && Number.isFinite(value));
|
|
694
|
+
}
|
|
695
|
+
export function isNullableString(value) {
|
|
696
|
+
return value === null || typeof value === 'string';
|
|
697
|
+
}
|