Skip to content

chore: sync upstream release v3.1.3 - #55

Merged
JayHome137 merged 5 commits into
codex2api-customfrom
sync/upstream-v3.1.3
Nov 4, 2026
Merged

JayHome137 merged 5 commits into
codex2api-customfrom
sync/upstream-v3.1.3

Conversation

@github-actions

@github-actions github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Upstream release sync

This PR merges the validated main baseline for v3.1.3 into codex2api-custom.

  • Custom multiplier, billing, channel-monitor, mobile-display, and deployment-source behavior is protected on conflicts.
  • All other files follow the already validated main baseline.
  • The custom contract, CI, and security checks must pass before merge.
  • The custom Release is published only after this PR is merged.

Preserve-list files conflicted. Custom content was kept whole, and this PR will not auto-merge or publish a release:

  • database/api_key_usage.go
  • frontend/src/pages/APIKeyUsagePortal.tsx
  • frontend/src/pages/Usage.tsx
  • frontend/src/types.ts

@github-actions github-actions Bot added the preserve-conflict Preserve-list merge kept custom and dropped upstream hunks label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Author

Preserve-list files conflicted. Each file below was kept from codex2api-custom so custom content is unchanged.

This PR will not auto-merge, and no release will be published until it is merged by a reviewer.

database/api_key_usage.go

Kept the custom file. Upstream changes since the merge base that were not applied:

diff --git a/database/api_key_usage.go b/database/api_key_usage.go
index 9a33ed3f..7c9f8b44 100644
--- a/database/api_key_usage.go
+++ b/database/api_key_usage.go
@@ -652,7 +652,8 @@ func (db *DB) getAllAPIKeysCostSince(ctx context.Context, since time.Time, honor
 }
 
 // APIKeySelfUsageReport 是 API Key 自助统计页使用的只读聚合结果。
-// 只包含当前 key 自己的 usage_logs 数据,不返回账号池、客户端 IP、raw key 等后台字段。
+// 只包含当前 key 自己的 usage_logs 数据(含经该 key 发起请求的客户端 IP 与 User-Agent,
+// 共享同一 key 的持有者可互相看到),不返回其他 key 的日志、账号池、raw key 等后台字段。
 type APIKeySelfUsageReport struct {
 	Summary            APIKeySelfUsageSummary     `json:"summary"`
 	Windows            APIKeySelfUsageWindows     `json:"windows"`
@@ -733,6 +734,8 @@ type APIKeySelfUsageLog struct {
 	UserBilling
 	ID                     int64     `json:"id"`
 	Channel                string    `json:"channel"`
+	ClientIP               string    `json:"client_ip"`
+	ClientUserAgent        string    `json:"client_user_agent"`
 	Endpoint               string    `json:"endpoint"`
 	Model                  string    `json:"model"`
 	EffectiveModel         string    `json:"effective_model"`
@@ -742,7 +745,11 @@ type APIKeySelfUsageLog struct {
 	FirstTokenMS           int       `json:"first_token_ms"`
 	InputTokens            int       `json:"input_tokens"`
 	OutputTokens           int       `json:"output_tokens"`
+	ReasoningTokens        int       `json:"reasoning_tokens"`
+	ReasoningEffort        string    `json:"reasoning_effort"`
 	CachedTokens           int       `json:"cached_tokens"`
+	CacheWrite5mTokens     int       `json:"cache_write_5m_tokens"`
+	CacheWrite1hTokens     int       `json:"cache_write_1h_tokens"`
 	ImageInputTokens       int       `json:"image_input_tokens"`
 	ImageOutputTokens      int       `json:"image_output_tokens"`
 	CachedImageInputTokens int       `json:"cached_image_input_tokens"`
@@ -1266,6 +1273,7 @@ func (db *DB) listAPIKeySelfRecentLogs(ctx context.Context, apiKeyID int64, rang
 		SELECT
 			id,
 			COALESCE(channel, ''),
+			COALESCE(client_ip, ''), COALESCE(client_user_agent, ''),
 			` + apiKeySelfEndpointExpr + ` AS endpoint_name,
 			COALESCE(model, ''),
 			COALESCE(effective_model, ''),
@@ -1275,7 +1283,10 @@ func (db *DB) listAPIKeySelfRecentLogs(ctx context.Context, apiKeyID int64, rang
 			COALESCE(first_token_ms, 0),
 			COALESCE(input_tokens, 0),
 			COALESCE(output_tokens, 0),
+			COALESCE(reasoning_tokens, 0),
+			COALESCE(reasoning_effort, ''),
 			COALESCE(cached_tokens, 0),
+			COALESCE(cache_write_5m_tokens, 0), COALESCE(cache_write_1h_tokens, 0),
 			COALESCE(image_input_tokens, 0), COALESCE(image_output_tokens, 0), COALESCE(cached_image_input_tokens, 0),
 			COALESCE(total_tokens, 0),
 			COALESCE(user_billed, 0),
@@ -1304,6 +1315,7 @@ func (db *DB) listAPIKeySelfRecentLogs(ctx context.Context, apiKeyID int64, rang
 		if err := rows.Scan(
 			&item.ID,
 			&item.Channel,
+			&item.ClientIP, &item.ClientUserAgent,
 			&item.Endpoint,
 			&item.Model,
 			&item.EffectiveModel,
@@ -1313,7 +1325,10 @@ func (db *DB) listAPIKeySelfRecentLogs(ctx context.Context, apiKeyID int64, rang
 			&item.FirstTokenMS,
 			&item.InputTokens,
 			&item.OutputTokens,
-			&item.CachedTokens, &item.ImageInputTokens, &item.ImageOutputTokens, &item.CachedImageInputTokens,
+			&item.ReasoningTokens,
+			&item.ReasoningEffort,
+			&item.CachedTokens, &item.CacheWrite5mTokens, &item.CacheWrite1hTokens,
+			&item.ImageInputTokens, &item.ImageOutputTokens, &item.CachedImageInputTokens,
 			&item.TotalTokens,
 			&item.UserBilled, &item.UserBillingMode, &item.ImageUnitPrice, &item.BilledImageCount,
 			&item.ServiceTier,

frontend/src/pages/APIKeyUsagePortal.tsx

Kept the custom file. Upstream changes since the merge base that were not applied:

diff --git a/frontend/src/pages/APIKeyUsagePortal.tsx b/frontend/src/pages/APIKeyUsagePortal.tsx
index a6a3342c..bed55512 100644
--- a/frontend/src/pages/APIKeyUsagePortal.tsx
+++ b/frontend/src/pages/APIKeyUsagePortal.tsx
@@ -1,6 +1,6 @@
 import { ImageBillingCost } from '../components/image-studio/ImageBillingCost'
 import { mediaBillingUnit } from '../lib/imageBilling'
-import { type FormEvent, type ReactNode, useEffect, useMemo, useState } from 'react'
+import { type FormEvent, type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { NavLink, useNavigate, useParams } from 'react-router-dom'
 import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip as RechartsTooltip } from 'recharts'
@@ -12,7 +12,6 @@ import {
   CalendarClock,
   Clock3,
   CircleDollarSign,
-  DatabaseZap,
   Eye,
   EyeOff,
   Gauge,
@@ -37,11 +36,15 @@ import {
 import { api } from '../api'
 import { DEFAULT_SITE_LOGO, useBranding } from '../branding'
 import Pagination from '../components/Pagination'
-import CompactionBadges from '../components/CompactionBadges'
+import { PublicUsageRequestType, PublicUsageTokens, PublicUsageUserAgent } from '../components/PublicAPIKeyUsageLogDetails'
+import { TimingCell, TokensPerSecCell, UsageCacheBadges } from '../components/UsageLogMetrics'
+import ReasoningEffortBadge from '../components/ReasoningEffortBadge'
 import ChannelLogo from '../components/ChannelLogo'
 import ModelLogo from '../components/ModelLogo'
 import DisplayTimezoneSelect from '../components/DisplayTimezoneSelect'
 import APIKeyModelRequestUsageCard from '../components/APIKeyModelRequestUsage'
+import PublicAPIKeyConcurrencyBadge from '../components/PublicAPIKeyConcurrencyBadge'
+import APIKeyChannelBadge from '../components/APIKeyChannelBadge'
 import { usePersistedPageSize } from '../hooks/usePersistedPageSize'
 import type {
   APIKeyLimits,
@@ -95,6 +98,8 @@ export default function APIKeyUsagePortal() {
     return stored.key ? stored.remember : true
   })
   const [activeAPIKey, setActiveAPIKey] = useState(() => readStoredAPIKey().key)
+  // 记录最新的有效 key,用于丢弃切换/退出前发出的过期响应
+  const activeAPIKeyRef = useRef(activeAPIKey)
   const [showKey, setShowKey] = useState(false)
   const [range, setRange] = useState<UsageRange>('30d')
   const [logPage, setLogPage] = useState(1)
@@ -108,22 +113,25 @@ export default function APIKeyUsagePortal() {
 
   // 已登录后的刷新/切换时间范围(沿用当前会话的 key)
   const reload = async (nextRange = range, nextLogPage = logPage, nextLogPageSize = logPageSize, nextFilter = logFilter) => {
-    if (!activeAPIKey) return
+    const requestAPIKey = activeAPIKey
+    if (!requestAPIKey) return
     setLoading(true)
     setError('')
     try {
-      const result = await api.getPublicAPIKeyUsage(activeAPIKey, nextRange, {
+      const result = await api.getPublicAPIKeyUsage(requestAPIKey, nextRange, {
         page: nextLogPage,
         pageSize: nextLogPageSize,
         ...nextFilter,
       })
+      if (activeAPIKeyRef.current !== requestAPIKey) return
       setData(result)
       const responsePage = result.usage.recent_logs_page || nextLogPage
       if (responsePage !== nextLogPage) setLogPage(responsePage)
     } catch (err) {
+      if (activeAPIKeyRef.current !== requestAPIKey) return
       setError(getErrorMessage(err))
     } finally {
-      setLoading(false)
+      if (activeAPIKeyRef.current === requestAPIKey) setLoading(false)
     }
   }
 
@@ -146,6 +154,7 @@ export default function APIKeyUsagePortal() {
       } catch (err) {
         if (!cancelled) {
           setError(getErrorMessage(err))
+          activeAPIKeyRef.current = ''
           setActiveAPIKey('')
           clearStoredAPIKey()
           setData(null)
@@ -180,6 +189,7 @@ export default function APIKeyUsagePortal() {
       })
       setData(result)
       setLogPage(1)
+      activeAPIKeyRef.current = trimmed
       setActiveAPIKey(trimmed)
       writeStoredAPIKey(trimmed, remember)
     } catch (err) {
@@ -229,9 +239,11 @@ export default function APIKeyUsagePortal() {
 
   const handleLogout = () => {
     clearStoredAPIKey()
+    activeAPIKeyRef.current = ''
     setActiveAPIKey('')
     setAPIKeyInput('')
     setData(null)
+    setLoading(false)
     setError('')
     setLogPage(1)
     setLogFilter({})
@@ -391,7 +403,7 @@ export default function APIKeyUsagePortal() {
 
       <main className="relative mx-auto max-w-[1600px] space-y-5 px-4 py-5 sm:px-6">
         {data ? (
-          <QuotaHero data={data} quotaPercent={quotaPercent} controls={controls} />
+          <QuotaHero data={data} apiKey={activeAPIKey} quotaPercent={quotaPercent} controls={controls} />
         ) : (
           <Card className="py-0">
             <CardContent className="flex flex-wrap items-center justify-between gap-3 p-3.5">
@@ -718,7 +730,7 @@ function quotaTone(percent: number): QuotaTone {
   return 'emerald'
 }
 
-function QuotaHero({ data, quotaPercent, controls }: { data: PublicAPIKeyUsageResponse; quotaPercent: number; controls: ReactNode }) {
+function QuotaHero({ data, apiKey, quotaPercent, controls }: { data: PublicAPIKeyUsageResponse; apiKey: string; quotaPercent: number; controls: ReactNode }) {
   const { t } = useTranslation()
   const { key } = data
   const hasQuota = key.quota_limit > 0
@@ -760,9 +772,11 @@ function QuotaHero({ data, quotaPercent, controls }: { data: PublicAPIKeyUsageRe
               <KeyRound className="size-4" />
             </div>
             <div className="min-w-0">
-              <div className="flex min-w-0 items-center gap-2">
+              <div className="flex min-w-0 flex-wrap items-center gap-2">
                 <span className="truncate text-[15px] font-semibold text-foreground">{key.name || t('keyUsage.keyCard')}</span>
                 <StatusPill status={key.status} label={t(`apiKeys.status.${key.status}`)} />
+                <PublicAPIKeyConcurrencyBadge key={apiKey} apiKey={apiKey} limit={key.limits.max_concurrency} />
+                <APIKeyChannelBadge channel={key.limits.upstream_channel} />
               </div>
               <div className="truncate font-mono text-xs text-muted-foreground">{key.key}</div>
             </div>
@@ -1460,6 +1474,7 @@ function RecentLogsTable({
                 <div className="flex flex-wrap items-center gap-1.5 min-w-0">
                   <LogStatusBadge code={log.status_code} onFilter={filterStatus} compact />
                   <LogModelBadge log={log} active={filter.model === log.model} onToggle={toggleModel} compact />
+                  <ReasoningEffortBadge effort={log.reasoning_effort} />
                   {log.effective_model && log.effective_model !== log.model ? (
                     <Badge variant="outline" className="border-transparent bg-blue-500/10 text-[11px] font-medium text-blue-600 dark:bg-blue-500/20 dark:text-blue-400">→ {log.effective_model}</Badge>
                   ) : null}
@@ -1468,25 +1483,31 @@ function RecentLogsTable({
               </div>
               <div className="flex flex-wrap items-center justify-between gap-2 text-xs">
                 <span className="font-mono text-muted-foreground">{log.endpoint || '-'}</span>
-                <Badge
-                  variant="outline"
-                  className="text-[11px]"
-                  style={{ background: log.stream ? 'rgba(99, 102, 241, 0.12)' : 'rgba(107, 114, 128, 0.12)', color: log.stream ? '#6366f1' : '#6b7280', borderColor: 'transparent' }}
-                >
-                  {log.stream ? 'stream' : 'sync'}
-                </Badge>
+                <PublicUsageRequestType log={log} />
               </div>
-              <div className="flex flex-wrap items-center justify-between gap-2 pt-1 border-t border-border/50 text-xs">
-                {log.status_code < 400 && (log.input_tokens > 0 || log.output_tokens > 0) ? (
-                  <div className="font-mono text-[12px] tabular-nums">
-                    <span className="

... diff truncated ...

frontend/src/pages/Usage.tsx

Kept the custom file. Upstream changes since the merge base that were not applied:

diff --git a/frontend/src/pages/Usage.tsx b/frontend/src/pages/Usage.tsx
index 1bae138f..825b38d8 100644
--- a/frontend/src/pages/Usage.tsx
+++ b/frontend/src/pages/Usage.tsx
@@ -13,6 +13,8 @@ import ChannelLogo from '../components/ChannelLogo'
 import CompactionBadges from '../components/CompactionBadges'
 import UsageDaybreakBadge from '../components/UsageDaybreakBadge'
 import ModelLogo from '../components/ModelLogo'
+import ReasoningEffortBadge from '../components/ReasoningEffortBadge'
+import { TimingCell, TokensPerSecCell, UsageCacheBadges, UsageInputTokenCount } from '../components/UsageLogMetrics'
 import Modal from '../components/Modal'
 import ColumnSettingsMenu from '../components/ColumnSettingsMenu'
 import StateShell from '../components/StateShell'
@@ -25,7 +27,6 @@ import { cn, formatCompactEmail } from '../lib/utils'
 import { formatUsageNumber as formatTokens } from '../lib/usageFormat'
 import { buildModelShareData, formatSharePercent, type ModelShareMetric } from '../lib/usageInsights'
 import './usage-insights.css'
-import { getUsageTokenBreakdown } from '../lib/usageTokenDisplay'
 import { formatBeijingTime } from '../utils/time'
 import { Card, CardContent } from '@/components/ui/card'
 import { Badge } from '@/components/ui/badge'
@@ -39,47 +40,11 @@ import {
   TableHeader,
   TableRow,
 } from '@/components/ui/table'
-import { createLucideIcon, Activity, Box, Clock, Zap, Sparkles, AlertTriangle, Search, Brain, DatabaseZap, X, Image as ImageIcon, Info, CircleDollarSign, BarChart3, KeyRound, Route, SlidersHorizontal, ShieldAlert, RefreshCw, ChevronDown, RotateCcw, PlugZap, FlaskConical } from 'lucide-react'
+import { Activity, Box, Clock, Zap, Sparkles, AlertTriangle, Search, Brain, DatabaseZap, X, Image as ImageIcon, Info, CircleDollarSign, BarChart3, KeyRound, Route, SlidersHorizontal, ShieldAlert, RefreshCw, ChevronDown, RotateCcw, PlugZap, FlaskConical } from 'lucide-react'
 
 import { Input } from '@/components/ui/input'
 import { Select } from '@/components/ui/select'
 
-// 缓存写入图标:取 Lucide (ISC) 的 database-plus,与读取用的 DatabaseZap 成对。
-// 锁定的 lucide-react 1.0.1 还没收录它,升级前在这里按同一路径注册。
-const DatabasePlus = createLucideIcon('database-plus', [
-  ['path', { d: 'M19 16v6', key: 'tddt3s' }],
-  ['path', { d: 'M21 12.536V5', key: 'zeza6i' }],
-  ['path', { d: 'M22 19h-6', key: 'vcuq98' }],
-  ['path', { d: 'M3 12A9 3 0 0 0 15.1824 14.8061', key: 'ukc3b1' }],
-  ['path', { d: 'M3 5V19A9 3 0 0 0 13.318 21.968', key: '1lyu4j' }],
-  ['ellipse', { cx: '12', cy: '5', rx: '9', ry: '3', key: 'msslwz' }],
-])
-
-/** Color ramp for reasoning effort: cool/muted → hot/intense. */
-function getReasoningEffortBadgeClassName(effort: string): string {
-  switch (effort.trim().toLowerCase()) {
-    case 'none':
-    case 'off':
-      return 'border-transparent bg-slate-500/12 text-slate-500 dark:bg-slate-500/20 dark:text-slate-400'
-    case 'minimal':
-    case 'min':
-      return 'border-transparent bg-sky-500/12 text-sky-600 dark:bg-sky-500/20 dark:text-sky-400'
-    case 'low':
-      return 'border-transparent bg-emerald-500/12 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400'
-    case 'medium':
-    case 'med':
-      return 'border-transparent bg-amber-500/12 text-amber-600 dark:bg-amber-500/20 dark:text-amber-400'
-    case 'high':
-      return 'border-transparent bg-orange-500/14 text-orange-600 dark:bg-orange-500/22 dark:text-orange-400'
-    case 'xhigh':
-    case 'max':
-      return 'border-transparent bg-rose-500/14 text-rose-600 dark:bg-rose-500/22 dark:text-rose-400'
-    case 'ultra':
-      return 'border-transparent bg-violet-500/14 text-violet-600 dark:bg-violet-500/22 dark:text-violet-300'
-    default:
-      return 'border-transparent bg-muted text-muted-foreground'
-  }
-}
 
 function usageRequestedModelTitle(
   log: UsageLog,
@@ -94,20 +59,6 @@ function usageRequestedModelTitle(
   return parts.join('\n')
 }
 
-function ReasoningEffortBadge({ effort }: { effort: string }) {
-  const label = effort.trim()
-  if (!label) return null
-  return (
-    <Badge
-      variant="outline"
-      title={`reasoning: ${label}`}
-      className={`text-[11px] font-semibold lowercase tracking-wide ${getReasoningEffortBadgeClassName(label)}`}
-    >
-      {label}
-    </Badge>
-  )
-}
-
 // ===== 上游响应模型审计(移植自 sub2api)=====
 // 变体归一化:剥掉 -latest 与日期后缀(-20250101 / -2025-01-01)后比较。
 // 归一化相等 → 「疑似变体」(琥珀);否则 → 「模型不一致」(橙)。
@@ -179,6 +130,7 @@ function UpstreamResponseModelBadge({
 // 网关自身发起的请求按 internal_reason 细分:测连 / 降智检测 / 超窗摘要,
 // 其余未知原因统一显示为"内部请求"。
 const INTERNAL_REQUEST_PRESENTATION: Record<string, { labelKey: string; tooltipKey: string; Icon: typeof Brain }> = {
+  scheduled_connection_test: { labelKey: 'usage.internalScheduledActivation', tooltipKey: 'usage.internalScheduledActivationTooltip', Icon: PlugZap },
   connection_test: { labelKey: 'usage.internalConnectionTest', tooltipKey: 'usage.internalConnectionTestTooltip', Icon: PlugZap },
   intelligence_check: { labelKey: 'usage.internalIntelligenceCheck', tooltipKey: 'usage.internalIntelligenceCheckTooltip', Icon: Brain },
   quality_test: { labelKey: 'usage.internalQualityTest', tooltipKey: 'usage.internalQualityTestTooltip', Icon: FlaskConical },
@@ -1292,53 +1244,6 @@ const usageTableTextClass = 'text-[14px]'
 const usageTableMonoClass = 'font-mono text-[13px] tabular-nums'
 const usageTableBadgeClass = 'text-[13px]'
 
-function UsageInputTokenCount({ log }: { log: UsageLog }) {
-  const { t } = useTranslation()
-  const tokens = getUsageTokenBreakdown(log)
-  const title = tokens.isClaude
-    ? t('usage.claudeInputTooltip', {
-      input: formatTokens(tokens.inputTokens, true),
-      total: formatTokens(tokens.totalInputTokens, true),
-      read: formatTokens(tokens.cacheReadTokens, true),
-      write: formatTokens(tokens.cacheWriteTokens, true),
-    })
-    : `${t('usage.inputTokens')}: ${formatTokens(tokens.inputTokens, true)}`
-
-  return <span className="text-blue-500" title={title}>↓{formatTokens(tokens.inputTokens, true)}</span>
-}
-
-function UsageCacheBadges({ log, align = 'end' }: { log: UsageLog; align?: 'start' | 'end' }) {
-  const { t } = useTranslation()
-  const tokens = getUsageTokenBreakdown(log)
-  if (tokens.cacheReadTokens === 0 && tokens.cacheWriteTokens === 0) {
-    return <span className={`${usageTableMonoClass} text-muted-foreground/50`}>-</span>
-  }
-  const readTitle = t('usage.cacheReadTooltip', { tokens: formatTokens(tokens.cacheReadTokens, true) })
-  const writeTitle = t('usage.cacheCreateTooltip', {
-    tokens: formatTokens(tokens.cacheWriteTokens, true),
-    m5: formatTokens(tokens.cacheWrite5mTokens, true),
-    h1: formatTokens(tokens.cacheWrite1hTokens, true),
-  })
-
-  // 读/写两枚徽章横排(空间不够再换行),不再竖着堆叠把整行撑高
-  return (
-    <div className={cn('flex flex-wrap items-center gap-1', align === 'start' ? 'justify-start' : 'justify-end')}>
-      {tokens.cacheReadTokens > 0 && (
-        <Badge variant="outline" title={readTitle} aria-label={readTitle} className={`${usageTableBadgeClass} gap-1 border-transparent bg-indigo-500/10 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400`}>
-          <DatabaseZap className="size-3.5" aria-hidden="true" />
-          {formatTokens(tokens.cacheReadTokens, true)}
-        </Badge>
-      )}
-      {tokens.cacheWriteTokens > 0 && (
-        <Badge variant="outline" title={writeTitle} aria-label={writeTitle} className={`${usageTableBadgeClass} gap-1 border-transparent bg-amber-500/10 text-amber-600 dark:bg-amber-500/20 dark:text-amber-400`}>
-          <DatabasePlus className="size-3.5" aria-hidden="true" />
-          {formatTokens(tokens.cacheWriteTokens, true)}
-        </Badge>
-      )}
-    </div>
-  )
-}
-
 // UsageTimeCell 时间列双色:日期弱化、时分秒突出,扫表时先看到时间;整串仍在 title。
 function UsageTimeCell({ value }: { value?: string | null }) {
   const full = formatBeijingTime(value)
@@ -1482,176 +1387,6 @@ const DEFAULT_USAGE_VISIBLE_COLUMNS: Record<UsageTableColumn, boolean> = {
   time: t

... diff truncated ...

frontend/src/types.ts

Kept the custom file. Upstream changes since the merge base that were not applied:

diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 146f3925..bc5d591a 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -123,6 +123,10 @@ export interface AntigravitySettingsResponse {
   quota_refresh_interval_minutes?: number
   /** 配额硬门禁开关:开启后配额耗尽账号直接 429,不进入排队。 */
   quota_gate_enabled?: boolean
+  /** 临期优先调度开关:周配额临近重置且仍有存量的账号优先承接新会话(仅 round_robin)。 */
+  expiry_priority_enabled?: boolean
+  /** 临期时间窗(小时),允许范围 1–168。 */
+  expiry_priority_horizon_hours?: number
   /** 渠道配置 CAS 版本号(只读,后端下发)。 */
   config_revision?: number
   choices: AntigravityRedirectChoice[]
@@ -2213,7 +2217,6 @@ export interface SystemSettings {
   codex_force_websocket: boolean
   codex_telemetry_enabled: boolean
   codex_telemetry_timing_debug: boolean
-  codex_unified_client_identity_enabled: boolean
   codex_request_compression: boolean
   codex_ws_weak_network_mode: boolean
   codex_ws_keepalive_enabled: boolean
@@ -2300,6 +2303,12 @@ export interface SystemSettings {
   response_cache_reconstruct_max_bytes: number
   response_cache_write_policy: ResponseCacheWritePolicy
   readonly response_cache_config_generation: number
+  output_guard_enabled: boolean
+  output_guard_max_tool_argument_bytes: number
+  output_guard_max_tool_argument_deltas: number
+  output_guard_max_channel_bytes: number
+  output_guard_max_request_bytes: number
+  readonly output_guard_config_generation: number
   relay_model_cooldown_mode: ModelCooldownMode
   relay_model_cooldown_seconds: number
   relay_model_cooldown_backoff_enabled: boolean
@@ -4089,6 +4098,8 @@ export interface PublicAPIKeyUsageLog {
   billed_image_count?: number
   id: number
   channel?: UpstreamChannel | ''
+  client_ip: string
+  client_user_agent: string
   endpoint: string
   model: string
   effective_model: string
@@ -4098,7 +4109,11 @@ export interface PublicAPIKeyUsageLog {
   first_token_ms: number
   input_tokens: number
   output_tokens: number
+  reasoning_tokens: number
+  reasoning_effort: string
   cached_tokens: number
+  cache_write_5m_tokens?: number
+  cache_write_1h_tokens?: number
   total_tokens: number
   user_billed: number
   input_cost: number
@@ -4425,3 +4440,27 @@ export interface ClaudeGlobalConfig {
   max_tool_count: number
   max_tool_schema_bytes: number
 }
+
+
+export interface ScheduledConnectionTestConfig {
+  enabled: boolean
+  model: string
+  content: string
+  weekday_times: string[]
+  weekend_times: string[]
+}
+export interface ScheduledConnectionTestRecord {
+  scheduled_at: string
+  attempted_at?: string
+  account_id: number
+  model?: string
+  status: 'success' | 'failed' | 'skipped' | 'unknown'
+  success: boolean
+  error?: string
+  next_reset_5h_at?: string
+}
+export interface ScheduledConnectionTestResponse {
+  config: ScheduledConnectionTestConfig
+  timezone: string
+  records: ScheduledConnectionTestRecord[]
+}

@JayHome137
JayHome137 merged commit 42355ca into codex2api-custom Nov 4, 2026
16 checks passed
@JayHome137
JayHome137 deleted the sync/upstream-v3.1.3 branch November 4, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preserve-conflict Preserve-list merge kept custom and dropped upstream hunks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant