Merge upstream/main into feat/one-click-update

This commit is contained in:
zhiqicloud
2026-05-22 15:28:27 +08:00
269 changed files with 31791 additions and 6988 deletions
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
canRefreshOAuthCredential,
getProviderMaskedSecretLabel,
shouldShowOAuthRefreshControl,
} from '@/utils/providerKeyAuth'
@@ -17,14 +18,25 @@ describe('providerKeyAuth', () => {
expect(shouldShowOAuthRefreshControl(key, 'grok')).toBe(false)
})
it('keeps standard OAuth providers on OAuth token semantics', () => {
const key = {
it('hides oauth refresh control when backend marks a provider as non-refreshable', () => {
const input = {
auth_type: 'oauth',
oauth_managed: true,
can_refresh_oauth: false,
}
expect(getProviderMaskedSecretLabel(key, 'codex')).toBe('[OAuth Token]')
expect(shouldShowOAuthRefreshControl(key, 'codex')).toBe(true)
expect(canRefreshOAuthCredential(input)).toBe(false)
expect(shouldShowOAuthRefreshControl(input)).toBe(false)
})
it('keeps legacy oauth refresh control visible when backend capability is absent', () => {
const input = {
auth_type: 'oauth',
oauth_managed: true,
}
expect(canRefreshOAuthCredential(input)).toBe(true)
expect(shouldShowOAuthRefreshControl(input)).toBe(true)
expect(getProviderMaskedSecretLabel(input, 'codex')).toBe('[OAuth Token]')
})
})
@@ -104,4 +104,122 @@ describe('providerKeyQuota', () => {
},
}, 'grok')).toBe('Auto剩余 40.0% (60/150) | Heavy剩余 0.0% (0/20)')
})
it('surfaces Windsurf hard account states', () => {
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'windsurf',
code: 'quarantined',
label: '账号隔离中',
exhausted: false,
},
},
}, 'windsurf')).toBe('账号隔离中')
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'windsurf',
code: 'cooldown',
label: '冷却中',
exhausted: false,
},
},
}, 'windsurf')).toBe('冷却中')
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'windsurf',
code: 'cooldown',
exhausted: false,
},
},
}, 'windsurf')).toBe('冷却中')
})
it('includes Windsurf quota windows and model availability in display text', () => {
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'windsurf',
code: 'ok',
exhausted: false,
allowed_models_count: 7,
windows: [
{
code: 'daily',
remaining_ratio: 0.75,
},
{
code: 'weekly',
remaining_ratio: 0.5,
},
{
code: 'prompt',
remaining_value: 12,
limit_value: 20,
},
{
code: 'flex',
used_value: 2,
limit_value: 5,
},
],
},
},
}, 'windsurf')).toBe('日剩余 75.0% | 周剩余 50.0% | Prompt 剩余 12/20 | Flex 剩余 3/5 | 可用模型 7 个')
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'windsurf',
code: 'cooldown',
label: '冷却中',
exhausted: false,
rate_limit: {
limited: true,
has_capacity: true,
messages_remaining: -1,
max_messages: -1,
},
allowed_models_count: 118,
windows: [
{
code: 'daily',
remaining_ratio: 0.99,
},
{
code: 'weekly',
remaining_ratio: 1,
},
{
code: 'prompt',
remaining_value: 100,
limit_value: 100,
},
{
code: 'rate_limit',
reset_seconds: null,
is_exhausted: false,
},
],
},
},
}, 'windsurf')).toBe('日剩余 99.0% | 周剩余 100.0% | Prompt 剩余 100/100 | 可用模型 118 个')
})
it('uses Windsurf model availability when no quota window is present', () => {
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'windsurf',
code: 'ok',
exhausted: false,
allowed_models_count: 3,
},
},
}, 'windsurf')).toBe('可用模型 3 个')
})
})
@@ -251,4 +251,20 @@ describe('providerKeyStatus', () => {
})
expect(getOAuthStatusTitle(input, 0)).toBe('Refresh Token 未添加,无法自动刷新')
})
it('does not treat non-refreshable provider sessions as missing refresh token', () => {
const input = {
auth_type: 'oauth',
oauth_managed: true,
can_refresh_oauth: false,
}
expect(getOAuthStatusDisplayWithFallback(input, 0)).toEqual({
text: '有效期未知',
isExpired: false,
isExpiringSoon: false,
isInvalid: false,
})
expect(getOAuthStatusTitle(input, 0)).toBe('Token 有效期未知')
})
})
+35
View File
@@ -3,6 +3,10 @@ export interface ChatPiiRedactionFeatureSettings {
inject_model_instruction: boolean
}
export interface NotificationPushServiceFeatureSettings {
enabled: boolean
}
export type FeatureSettingsMap = Record<string, unknown>
const DEFAULT_CHAT_PII_REDACTION_FEATURE_SETTINGS: ChatPiiRedactionFeatureSettings = {
@@ -10,6 +14,10 @@ const DEFAULT_CHAT_PII_REDACTION_FEATURE_SETTINGS: ChatPiiRedactionFeatureSettin
inject_model_instruction: true,
}
const DEFAULT_NOTIFICATION_PUSH_SERVICE_FEATURE_SETTINGS: NotificationPushServiceFeatureSettings = {
enabled: false,
}
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value)
}
@@ -49,3 +57,30 @@ export function mergeChatPiiRedactionFeatureSettings(
}
return Object.keys(settings).length > 0 ? settings : null
}
export function readNotificationPushServiceFeatureSettings(
featureSettings: unknown,
): NotificationPushServiceFeatureSettings {
const feature = isRecord(featureSettings)
? featureSettings.notification_push_service
: null
if (!isRecord(feature)) {
return { ...DEFAULT_NOTIFICATION_PUSH_SERVICE_FEATURE_SETTINGS }
}
return {
enabled: feature.enabled === true,
}
}
export function mergeNotificationPushServiceFeatureSettings(
featureSettings: unknown,
notificationPushService: NotificationPushServiceFeatureSettings,
): FeatureSettingsMap | null {
const settings: FeatureSettingsMap = isRecord(featureSettings)
? { ...featureSettings }
: {}
settings.notification_push_service = {
enabled: notificationPushService.enabled,
}
return Object.keys(settings).length > 0 ? settings : null
}
+1 -1
View File
@@ -92,7 +92,7 @@ export function shouldShowOAuthRefreshControl(
providerType?: string | null,
): boolean {
if (isGrokSessionCredential(input, providerType)) return false
return isOAuthManagedCredential(input)
return canRefreshOAuthCredential(input)
}
export function canExportOAuthCredential(input: ProviderKeyAuthCarrier): boolean {
+74
View File
@@ -76,6 +76,24 @@ function getQuotaWindow(
return getQuotaWindows(quota).find(window => normalizeText(window.code)?.toLowerCase() === normalizedCode) ?? null
}
function positiveNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null
}
function windsurfCooldownHasPositiveReset(quota: QuotaStatusSnapshot): boolean {
const rateLimit = quota.rate_limit
if (rateLimit && typeof rateLimit === 'object') {
const retryAfterMs = positiveNumber(rateLimit.retry_after_ms) ?? positiveNumber(rateLimit.retryAfterMs)
if (retryAfterMs != null) return true
}
const rateLimitWindow = getQuotaWindow(quota, 'rate_limit')
return (
positiveNumber(rateLimitWindow?.reset_seconds) != null
|| positiveNumber(rateLimitWindow?.reset_at) != null
)
}
function getQuotaWindowsByScope(
quota: QuotaStatusSnapshot | null | undefined,
scope: string,
@@ -214,6 +232,60 @@ function getGrokQuotaText(quota: QuotaStatusSnapshot): string | null {
return normalizeText(quota.label)
}
function getWindsurfQuotaText(quota: QuotaStatusSnapshot): string | null {
const code = normalizeText(quota.code)?.toLowerCase()
if (code === 'banned' || code === 'forbidden' || code === 'quarantined') {
return normalizeText(quota.label) || '账号不可用'
}
if (code === 'cooldown' && windsurfCooldownHasPositiveReset(quota)) {
return normalizeText(quota.label) || '冷却中'
}
if (code === 'rate_limited' || code === 'rate_limit') {
return normalizeText(quota.label) || '速率受限'
}
if (code === 'exhausted') {
return normalizeText(quota.label) || '额度已耗尽'
}
const parts: string[] = []
const dailyRemaining = getQuotaWindowRemainingPercent(getQuotaWindow(quota, 'daily'))
const weeklyRemaining = getQuotaWindowRemainingPercent(getQuotaWindow(quota, 'weekly'))
if (dailyRemaining != null) parts.push(`日剩余 ${formatPercent(dailyRemaining)}`)
if (weeklyRemaining != null) parts.push(`周剩余 ${formatPercent(weeklyRemaining)}`)
for (const [label, code] of [
['Prompt', 'prompt'],
['Flex', 'flex'],
] as const) {
const window = getQuotaWindow(quota, code)
if (!window) continue
if (typeof window.remaining_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0) {
parts.push(`${label} 剩余 ${formatQuotaValue(window.remaining_value)}/${formatQuotaValue(window.limit_value)}`)
continue
}
if (typeof window.used_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0) {
parts.push(`${label} 剩余 ${formatQuotaValue(Math.max(window.limit_value - window.used_value, 0))}/${formatQuotaValue(window.limit_value)}`)
continue
}
const remainingPercent = getQuotaWindowRemainingPercent(window)
if (remainingPercent != null) {
parts.push(`${label} 剩余 ${formatPercent(remainingPercent)}`)
}
}
if (typeof quota.allowed_models_count === 'number') {
parts.push(`可用模型 ${quota.allowed_models_count} 个`)
}
if (parts.length > 0) return parts.join(' | ')
if (code === 'cooldown') {
return normalizeText(quota.label) || '冷却中'
}
return normalizeText(quota.label)
}
function getAntigravityQuotaText(quota: QuotaStatusSnapshot): string | null {
const code = normalizeText(quota.code)?.toLowerCase()
if (code === 'forbidden') {
@@ -312,6 +384,8 @@ export function getQuotaSnapshotFallbackText(
return getKiroQuotaText(quota)
case 'grok':
return getGrokQuotaText(quota)
case 'windsurf':
return getWindsurfQuotaText(quota)
case 'antigravity':
return getAntigravityQuotaText(quota)
case 'gemini_cli':
+1 -1
View File
@@ -202,7 +202,7 @@ function mergeOAuthStatusDisplay(
}
function isOAuthCredentialWithoutRefreshToken(input: ProviderKeyStatusCarrier): boolean {
return isOAuthManagedCredential(input) && !canRefreshOAuthCredential(input)
return isOAuthManagedCredential(input) && input.oauth_temporary === true
}
function getMissingRefreshTokenStatus(): OAuthStatusInfo {