Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
9301dae6
Commit
9301dae6
authored
Mar 08, 2026
by
神乐
Browse files
fix: 修复 OpenAI WS 用量刷新遗漏场景
parent
be4e49e6
Changes
4
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/account_test_service.go
View file @
9301dae6
...
...
@@ -406,13 +406,22 @@ func (s *AccountTestService) testOpenAIAccountConnection(c *gin.Context, account
}
defer
func
()
{
_
=
resp
.
Body
.
Close
()
}()
if
isOAuth
&&
s
.
accountRepo
!=
nil
{
if
updates
,
err
:=
extractOpenAICodexProbeUpdates
(
resp
);
err
==
nil
&&
len
(
updates
)
>
0
{
_
=
s
.
accountRepo
.
UpdateExtra
(
ctx
,
account
.
ID
,
updates
)
mergeAccountExtra
(
account
,
updates
)
}
if
snapshot
:=
ParseCodexRateLimitHeaders
(
resp
.
Header
);
snapshot
!=
nil
{
if
resetAt
:=
codexRateLimitResetAtFromSnapshot
(
snapshot
,
time
.
Now
());
resetAt
!=
nil
{
_
=
s
.
accountRepo
.
SetRateLimited
(
ctx
,
account
.
ID
,
*
resetAt
)
account
.
RateLimitResetAt
=
resetAt
}
}
}
if
resp
.
StatusCode
!=
http
.
StatusOK
{
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
if
isOAuth
&&
s
.
accountRepo
!=
nil
{
if
updates
,
err
:=
extractOpenAICodexProbeUpdates
(
resp
);
err
==
nil
&&
len
(
updates
)
>
0
{
_
=
s
.
accountRepo
.
UpdateExtra
(
ctx
,
account
.
ID
,
updates
)
mergeAccountExtra
(
account
,
updates
)
}
if
resetAt
:=
(
&
RateLimitService
{})
.
calculateOpenAI429ResetTime
(
resp
.
Header
);
resetAt
!=
nil
{
_
=
s
.
accountRepo
.
SetRateLimited
(
ctx
,
account
.
ID
,
*
resetAt
)
account
.
RateLimitResetAt
=
resetAt
...
...
backend/internal/service/account_test_service_openai_test.go
View file @
9301dae6
...
...
@@ -4,7 +4,9 @@ package service
import
(
"context"
"io"
"net/http"
"strings"
"testing"
"time"
...
...
@@ -30,6 +32,40 @@ func (r *openAIAccountTestRepo) SetRateLimited(_ context.Context, id int64, rese
return
nil
}
func
TestAccountTestService_OpenAISuccessPersistsSnapshotFromHeaders
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
ctx
,
recorder
:=
newSoraTestContext
()
resp
:=
newJSONResponse
(
http
.
StatusOK
,
""
)
resp
.
Body
=
io
.
NopCloser
(
strings
.
NewReader
(
`data: {"type":"response.completed"}
`
))
resp
.
Header
.
Set
(
"x-codex-primary-used-percent"
,
"88"
)
resp
.
Header
.
Set
(
"x-codex-primary-reset-after-seconds"
,
"604800"
)
resp
.
Header
.
Set
(
"x-codex-primary-window-minutes"
,
"10080"
)
resp
.
Header
.
Set
(
"x-codex-secondary-used-percent"
,
"42"
)
resp
.
Header
.
Set
(
"x-codex-secondary-reset-after-seconds"
,
"18000"
)
resp
.
Header
.
Set
(
"x-codex-secondary-window-minutes"
,
"300"
)
repo
:=
&
openAIAccountTestRepo
{}
upstream
:=
&
queuedHTTPUpstream
{
responses
:
[]
*
http
.
Response
{
resp
}}
svc
:=
&
AccountTestService
{
accountRepo
:
repo
,
httpUpstream
:
upstream
}
account
:=
&
Account
{
ID
:
89
,
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeOAuth
,
Concurrency
:
1
,
Credentials
:
map
[
string
]
any
{
"access_token"
:
"test-token"
},
}
err
:=
svc
.
testOpenAIAccountConnection
(
ctx
,
account
,
"gpt-5.4"
)
require
.
NoError
(
t
,
err
)
require
.
NotEmpty
(
t
,
repo
.
updatedExtra
)
require
.
Equal
(
t
,
42.0
,
repo
.
updatedExtra
[
"codex_5h_used_percent"
])
require
.
Equal
(
t
,
88.0
,
repo
.
updatedExtra
[
"codex_7d_used_percent"
])
require
.
Contains
(
t
,
recorder
.
Body
.
String
(),
"test_complete"
)
}
func
TestAccountTestService_OpenAI429PersistsSnapshotAndRateLimit
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
ctx
,
_
:=
newSoraTestContext
()
...
...
frontend/src/components/account/AccountUsageCell.vue
View file @
9301dae6
...
...
@@ -412,14 +412,24 @@ const isActiveOpenAIRateLimited = computed(() => {
})
const
preferFetchedOpenAIUsage
=
computed
(()
=>
{
return
isActiveOpenAIRateLimited
.
value
&&
hasOpenAIUsageFallback
.
value
return
(
isActiveOpenAIRateLimited
.
value
||
isOpenAICodexSnapshotStale
.
value
)
&&
hasOpenAIUsageFallback
.
value
})
const
openAIUsageRefreshKey
=
computed
(()
=>
buildOpenAIUsageRefreshKey
(
props
.
account
))
const
isOpenAICodexSnapshotStale
=
computed
(()
=>
{
if
(
props
.
account
.
platform
!==
'
openai
'
||
props
.
account
.
type
!==
'
oauth
'
)
return
false
const
extra
=
props
.
account
.
extra
as
Record
<
string
,
unknown
>
|
undefined
const
updatedAtRaw
=
extra
?.
codex_usage_updated_at
if
(
!
updatedAtRaw
)
return
true
const
updatedAt
=
Date
.
parse
(
String
(
updatedAtRaw
))
if
(
Number
.
isNaN
(
updatedAt
))
return
true
return
Date
.
now
()
-
updatedAt
>=
10
*
60
*
1000
})
const
shouldAutoLoadUsageOnMount
=
computed
(()
=>
{
if
(
props
.
account
.
platform
===
'
openai
'
&&
props
.
account
.
type
===
'
oauth
'
)
{
return
isActiveOpenAIRateLimited
.
value
||
!
hasCodexUsage
.
value
return
isActiveOpenAIRateLimited
.
value
||
!
hasCodexUsage
.
value
||
isOpenAICodexSnapshotStale
.
value
}
return
shouldFetchUsage
.
value
})
...
...
@@ -807,7 +817,7 @@ onMounted(() => {
watch
(
openAIUsageRefreshKey
,
(
nextKey
,
prevKey
)
=>
{
if
(
!
prevKey
||
nextKey
===
prevKey
)
return
if
(
props
.
account
.
platform
!==
'
openai
'
||
props
.
account
.
type
!==
'
oauth
'
)
return
if
(
!
isActiveOpenAIRateLimited
.
value
&&
hasCodexUsage
.
value
)
return
if
(
!
isActiveOpenAIRateLimited
.
value
&&
hasCodexUsage
.
value
&&
!
isOpenAICodexSnapshotStale
.
value
)
return
loadUsage
().
catch
((
e
)
=>
{
console
.
error
(
'
Failed to refresh OpenAI usage:
'
,
e
)
...
...
frontend/src/components/account/__tests__/AccountUsageCell.spec.ts
View file @
9301dae6
...
...
@@ -69,6 +69,67 @@ describe('AccountUsageCell', () => {
})
it
(
'
OpenAI OAuth 快照已过期时首屏会重新请求 usage
'
,
async
()
=>
{
getUsage
.
mockResolvedValue
({
five_hour
:
{
utilization
:
15
,
resets_at
:
'
2026-03-08T12:00:00Z
'
,
remaining_seconds
:
3600
,
window_stats
:
{
requests
:
3
,
tokens
:
300
,
cost
:
0.03
,
standard_cost
:
0.03
,
user_cost
:
0.03
}
},
seven_day
:
{
utilization
:
77
,
resets_at
:
'
2026-03-13T12:00:00Z
'
,
remaining_seconds
:
3600
,
window_stats
:
{
requests
:
3
,
tokens
:
300
,
cost
:
0.03
,
standard_cost
:
0.03
,
user_cost
:
0.03
}
}
})
const
wrapper
=
mount
(
AccountUsageCell
,
{
props
:
{
account
:
{
id
:
2000
,
platform
:
'
openai
'
,
type
:
'
oauth
'
,
extra
:
{
codex_usage_updated_at
:
'
2026-03-07T00:00:00Z
'
,
codex_5h_used_percent
:
12
,
codex_5h_reset_at
:
'
2026-03-08T12:00:00Z
'
,
codex_7d_used_percent
:
34
,
codex_7d_reset_at
:
'
2026-03-13T12:00:00Z
'
}
}
as
any
},
global
:
{
stubs
:
{
UsageProgressBar
:
{
props
:
[
'
label
'
,
'
utilization
'
,
'
resetsAt
'
,
'
windowStats
'
,
'
color
'
],
template
:
'
<div class="usage-bar">{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}</div>
'
},
AccountQuotaInfo
:
true
}
}
})
await
flushPromises
()
expect
(
getUsage
).
toHaveBeenCalledWith
(
2000
)
expect
(
wrapper
.
text
()).
toContain
(
'
5h|15|300
'
)
expect
(
wrapper
.
text
()).
toContain
(
'
7d|77|300
'
)
})
it
(
'
OpenAI OAuth 有现成快照且未限额时不会首屏请求 usage
'
,
async
()
=>
{
const
wrapper
=
mount
(
AccountUsageCell
,
{
props
:
{
...
...
@@ -77,6 +138,7 @@ describe('AccountUsageCell', () => {
platform
:
'
openai
'
,
type
:
'
oauth
'
,
extra
:
{
codex_usage_updated_at
:
'
2099-03-07T10:00:00Z
'
,
codex_5h_used_percent
:
12
,
codex_5h_reset_at
:
'
2099-03-07T12:00:00Z
'
,
codex_7d_used_percent
:
34
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment