feat: account management overhaul + 7 issue resolutions (#147)
- Add smart JSON export/import with schema validation and deduplication - Add per-account proxy configurations (Issue #79) - Add advanced account sorting engine (Issues #126 & #99) - Add compact horizontal layout, AI credits, inline model visibility (Issues #145 & #134) - Add desktop push notifications for low quota (Issue #53) - Fix Arch Linux AUR process detection bug (Issue #117) - Default theme set to Dark Mode (it's better) - UI Polishing: Improved sort dropdown and symmetrical dialog layouts - Dev Mode: Resolved port conflicts and single-instance locks - Updated README and README.zh-CN with new features
This commit is contained in:
37 files changed
+1989
-455
No files matched your search
@@ -15,9 +15,23 @@ export class UpstreamRequestError extends Error {
|
||||
}) {
|
||||
super(params.message);
|
||||
this.name = 'UpstreamRequestError';
|
||||
|
||||
if (params.status && typeof params.status !== 'number') {
|
||||
throw new TypeError('status must be a number');
|
||||
}
|
||||
if (params.headers && typeof params.headers !== 'object') {
|
||||
throw new TypeError('headers must be an object');
|
||||
}
|
||||
|
||||
this.status = params.status;
|
||||
this.headers = params.headers;
|
||||
this.body = params.body;
|
||||
|
||||
// Sanitize and limit body size
|
||||
if (typeof params.body === 'string') {
|
||||
const sanitized = params.body.replace(/<[^>]*>?/gm, '');
|
||||
this.body = sanitized.substring(0, 1000);
|
||||
} else {
|
||||
this.body = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ import {
|
||||
AnthropicContent,
|
||||
} from './interfaces/request-interfaces';
|
||||
import { getServerConfig } from '../../server-config';
|
||||
import { normalizeGeminiModelAlias, resolveModelRoute } from '../../../lib/antigravity/ModelMapping';
|
||||
import {
|
||||
normalizeGeminiModelAlias,
|
||||
resolveModelRoute,
|
||||
} from '../../../lib/antigravity/ModelMapping';
|
||||
import { getMaxOutputTokens, getThinkingBudget } from '../../../lib/antigravity/ModelSpecs';
|
||||
import { resolveRequestUserAgent } from './request-user-agent';
|
||||
import { UpstreamRequestError } from './clients/upstream-error';
|
||||
@@ -679,7 +682,9 @@ export class ProxyService {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
if (i > 0) {
|
||||
const delay = calculateRetryDelay(i - 1);
|
||||
this.logger.log(`OpenAI-compatible retry ${i + 1}/${maxRetries}, backoff=${delay}ms (jittered)`);
|
||||
this.logger.log(
|
||||
`OpenAI-compatible retry ${i + 1}/${maxRetries}, backoff=${delay}ms (jittered)`,
|
||||
);
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
|
||||
@@ -435,6 +435,10 @@ export class TokenManagerService implements OnModuleInit {
|
||||
return candidates.find(([accountId]) => accountId === stickyBinding.accountId) ?? null;
|
||||
}
|
||||
|
||||
public resetSelectionState(): void {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
|
||||
private pickRoundRobinEntry(candidates: TokenEntry[]): TokenEntry | null {
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
@@ -507,10 +511,7 @@ export class TokenManagerService implements OnModuleInit {
|
||||
|
||||
const stickyBinding = this.getValidSessionBinding(sessionKey, now);
|
||||
if (stickyBinding && mode === 'cache-first') {
|
||||
const waitSec = this.rateLimitTracker.getRemainingWaitSeconds(
|
||||
stickyBinding.accountId,
|
||||
model,
|
||||
);
|
||||
const waitSec = this.rateLimitTracker.getRemainingWaitSeconds(stickyBinding.accountId, model);
|
||||
const waitMs = waitSec * 1000;
|
||||
const maxWaitMs = this.getMaxWaitDurationMs();
|
||||
if (waitMs > 0 && waitMs <= maxWaitMs) {
|
||||
@@ -586,18 +587,18 @@ export class TokenManagerService implements OnModuleInit {
|
||||
): void {
|
||||
const now = Date.now();
|
||||
const legacyAccountId = this.predictLegacyAccountCandidateId(allTokens, sessionKey, now);
|
||||
const parityAccountId = this.predictParityAccountCandidateId(
|
||||
allTokens,
|
||||
sessionKey,
|
||||
model,
|
||||
now,
|
||||
);
|
||||
const parityAccountId = this.predictParityAccountCandidateId(allTokens, sessionKey, model, now);
|
||||
|
||||
this.updateShadowStats(legacyAccountId, parityAccountId);
|
||||
}
|
||||
|
||||
private updateShadowStats(legacyId: string | null, parityId: string | null): void {
|
||||
this.shadowComparisonCount++;
|
||||
|
||||
if (legacyAccountId !== parityAccountId) {
|
||||
if (legacyId !== parityId) {
|
||||
this.shadowMismatchCount++;
|
||||
this.logger.warn(
|
||||
`Parity shadow mismatch detected: legacy=${legacyAccountId ?? 'n/a'}, parity=${parityAccountId ?? 'n/a'}`,
|
||||
`Parity shadow mismatch detected: legacy=${legacyId ?? 'n/a'}, parity=${parityId ?? 'n/a'}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user