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
dd7f2124
Commit
dd7f2124
authored
Jan 19, 2026
by
cyhhao
Browse files
merge: resolve conflicts with main
parents
49be9d08
bba5b3c0
Changes
42
Hide whitespace changes
Inline
Side-by-side
backend/cmd/server/wire_gen.go
View file @
dd7f2124
...
@@ -118,7 +118,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -118,7 +118,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
concurrencyCache
:=
repository
.
ProvideConcurrencyCache
(
redisClient
,
configConfig
)
concurrencyCache
:=
repository
.
ProvideConcurrencyCache
(
redisClient
,
configConfig
)
concurrencyService
:=
service
.
ProvideConcurrencyService
(
concurrencyCache
,
accountRepository
,
configConfig
)
concurrencyService
:=
service
.
ProvideConcurrencyService
(
concurrencyCache
,
accountRepository
,
configConfig
)
crsSyncService
:=
service
.
NewCRSSyncService
(
accountRepository
,
proxyRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
configConfig
)
crsSyncService
:=
service
.
NewCRSSyncService
(
accountRepository
,
proxyRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
configConfig
)
accountHandler
:=
admin
.
NewAccountHandler
(
adminService
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
rateLimitService
,
accountUsageService
,
accountTestService
,
concurrencyService
,
crsSyncService
)
sessionLimitCache
:=
repository
.
ProvideSessionLimitCache
(
redisClient
,
configConfig
)
accountHandler
:=
admin
.
NewAccountHandler
(
adminService
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
rateLimitService
,
accountUsageService
,
accountTestService
,
concurrencyService
,
crsSyncService
,
sessionLimitCache
)
oAuthHandler
:=
admin
.
NewOAuthHandler
(
oAuthService
)
oAuthHandler
:=
admin
.
NewOAuthHandler
(
oAuthService
)
openAIOAuthHandler
:=
admin
.
NewOpenAIOAuthHandler
(
openAIOAuthService
,
adminService
)
openAIOAuthHandler
:=
admin
.
NewOpenAIOAuthHandler
(
openAIOAuthService
,
adminService
)
geminiOAuthHandler
:=
admin
.
NewGeminiOAuthHandler
(
geminiOAuthService
)
geminiOAuthHandler
:=
admin
.
NewGeminiOAuthHandler
(
geminiOAuthService
)
...
@@ -140,7 +141,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -140,7 +141,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
identityService
:=
service
.
NewIdentityService
(
identityCache
)
identityService
:=
service
.
NewIdentityService
(
identityCache
)
deferredService
:=
service
.
ProvideDeferredService
(
accountRepository
,
timingWheelService
)
deferredService
:=
service
.
ProvideDeferredService
(
accountRepository
,
timingWheelService
)
claudeTokenProvider
:=
service
.
NewClaudeTokenProvider
(
accountRepository
,
geminiTokenCache
,
oAuthService
)
claudeTokenProvider
:=
service
.
NewClaudeTokenProvider
(
accountRepository
,
geminiTokenCache
,
oAuthService
)
gatewayService
:=
service
.
NewGatewayService
(
accountRepository
,
groupRepository
,
usageLogRepository
,
userRepository
,
userSubscriptionRepository
,
gatewayCache
,
configConfig
,
schedulerSnapshotService
,
concurrencyService
,
billingService
,
rateLimitService
,
billingCacheService
,
identityService
,
httpUpstream
,
deferredService
,
claudeTokenProvider
)
gatewayService
:=
service
.
NewGatewayService
(
accountRepository
,
groupRepository
,
usageLogRepository
,
userRepository
,
userSubscriptionRepository
,
gatewayCache
,
configConfig
,
schedulerSnapshotService
,
concurrencyService
,
billingService
,
rateLimitService
,
billingCacheService
,
identityService
,
httpUpstream
,
deferredService
,
claudeTokenProvider
,
sessionLimitCache
)
openAITokenProvider
:=
service
.
NewOpenAITokenProvider
(
accountRepository
,
geminiTokenCache
,
openAIOAuthService
)
openAITokenProvider
:=
service
.
NewOpenAITokenProvider
(
accountRepository
,
geminiTokenCache
,
openAIOAuthService
)
openAIGatewayService
:=
service
.
NewOpenAIGatewayService
(
accountRepository
,
usageLogRepository
,
userRepository
,
userSubscriptionRepository
,
gatewayCache
,
configConfig
,
schedulerSnapshotService
,
concurrencyService
,
billingService
,
rateLimitService
,
billingCacheService
,
httpUpstream
,
deferredService
,
openAITokenProvider
)
openAIGatewayService
:=
service
.
NewOpenAIGatewayService
(
accountRepository
,
usageLogRepository
,
userRepository
,
userSubscriptionRepository
,
gatewayCache
,
configConfig
,
schedulerSnapshotService
,
concurrencyService
,
billingService
,
rateLimitService
,
billingCacheService
,
httpUpstream
,
deferredService
,
openAITokenProvider
)
geminiMessagesCompatService
:=
service
.
NewGeminiMessagesCompatService
(
accountRepository
,
groupRepository
,
gatewayCache
,
schedulerSnapshotService
,
geminiTokenProvider
,
rateLimitService
,
httpUpstream
,
antigravityGatewayService
,
configConfig
)
geminiMessagesCompatService
:=
service
.
NewGeminiMessagesCompatService
(
accountRepository
,
groupRepository
,
gatewayCache
,
schedulerSnapshotService
,
geminiTokenProvider
,
rateLimitService
,
httpUpstream
,
antigravityGatewayService
,
configConfig
)
...
...
backend/internal/config/config.go
View file @
dd7f2124
...
@@ -234,6 +234,10 @@ type GatewayConfig struct {
...
@@ -234,6 +234,10 @@ type GatewayConfig struct {
// ConcurrencySlotTTLMinutes: 并发槽位过期时间(分钟)
// ConcurrencySlotTTLMinutes: 并发槽位过期时间(分钟)
// 应大于最长 LLM 请求时间,防止请求完成前槽位过期
// 应大于最长 LLM 请求时间,防止请求完成前槽位过期
ConcurrencySlotTTLMinutes
int
`mapstructure:"concurrency_slot_ttl_minutes"`
ConcurrencySlotTTLMinutes
int
`mapstructure:"concurrency_slot_ttl_minutes"`
// SessionIdleTimeoutMinutes: 会话空闲超时时间(分钟),默认 5 分钟
// 用于 Anthropic OAuth/SetupToken 账号的会话数量限制功能
// 空闲超过此时间的会话将被自动释放
SessionIdleTimeoutMinutes
int
`mapstructure:"session_idle_timeout_minutes"`
// StreamDataIntervalTimeout: 流数据间隔超时(秒),0表示禁用
// StreamDataIntervalTimeout: 流数据间隔超时(秒),0表示禁用
StreamDataIntervalTimeout
int
`mapstructure:"stream_data_interval_timeout"`
StreamDataIntervalTimeout
int
`mapstructure:"stream_data_interval_timeout"`
...
...
backend/internal/handler/admin/account_handler.go
View file @
dd7f2124
...
@@ -44,6 +44,7 @@ type AccountHandler struct {
...
@@ -44,6 +44,7 @@ type AccountHandler struct {
accountTestService
*
service
.
AccountTestService
accountTestService
*
service
.
AccountTestService
concurrencyService
*
service
.
ConcurrencyService
concurrencyService
*
service
.
ConcurrencyService
crsSyncService
*
service
.
CRSSyncService
crsSyncService
*
service
.
CRSSyncService
sessionLimitCache
service
.
SessionLimitCache
}
}
// NewAccountHandler creates a new admin account handler
// NewAccountHandler creates a new admin account handler
...
@@ -58,6 +59,7 @@ func NewAccountHandler(
...
@@ -58,6 +59,7 @@ func NewAccountHandler(
accountTestService
*
service
.
AccountTestService
,
accountTestService
*
service
.
AccountTestService
,
concurrencyService
*
service
.
ConcurrencyService
,
concurrencyService
*
service
.
ConcurrencyService
,
crsSyncService
*
service
.
CRSSyncService
,
crsSyncService
*
service
.
CRSSyncService
,
sessionLimitCache
service
.
SessionLimitCache
,
)
*
AccountHandler
{
)
*
AccountHandler
{
return
&
AccountHandler
{
return
&
AccountHandler
{
adminService
:
adminService
,
adminService
:
adminService
,
...
@@ -70,6 +72,7 @@ func NewAccountHandler(
...
@@ -70,6 +72,7 @@ func NewAccountHandler(
accountTestService
:
accountTestService
,
accountTestService
:
accountTestService
,
concurrencyService
:
concurrencyService
,
concurrencyService
:
concurrencyService
,
crsSyncService
:
crsSyncService
,
crsSyncService
:
crsSyncService
,
sessionLimitCache
:
sessionLimitCache
,
}
}
}
}
...
@@ -130,6 +133,9 @@ type BulkUpdateAccountsRequest struct {
...
@@ -130,6 +133,9 @@ type BulkUpdateAccountsRequest struct {
type
AccountWithConcurrency
struct
{
type
AccountWithConcurrency
struct
{
*
dto
.
Account
*
dto
.
Account
CurrentConcurrency
int
`json:"current_concurrency"`
CurrentConcurrency
int
`json:"current_concurrency"`
// 以下字段仅对 Anthropic OAuth/SetupToken 账号有效,且仅在启用相应功能时返回
CurrentWindowCost
*
float64
`json:"current_window_cost,omitempty"`
// 当前窗口费用
ActiveSessions
*
int
`json:"active_sessions,omitempty"`
// 当前活跃会话数
}
}
// List handles listing all accounts with pagination
// List handles listing all accounts with pagination
...
@@ -164,13 +170,89 @@ func (h *AccountHandler) List(c *gin.Context) {
...
@@ -164,13 +170,89 @@ func (h *AccountHandler) List(c *gin.Context) {
concurrencyCounts
=
make
(
map
[
int64
]
int
)
concurrencyCounts
=
make
(
map
[
int64
]
int
)
}
}
// 识别需要查询窗口费用和会话数的账号(Anthropic OAuth/SetupToken 且启用了相应功能)
windowCostAccountIDs
:=
make
([]
int64
,
0
)
sessionLimitAccountIDs
:=
make
([]
int64
,
0
)
for
i
:=
range
accounts
{
acc
:=
&
accounts
[
i
]
if
acc
.
IsAnthropicOAuthOrSetupToken
()
{
if
acc
.
GetWindowCostLimit
()
>
0
{
windowCostAccountIDs
=
append
(
windowCostAccountIDs
,
acc
.
ID
)
}
if
acc
.
GetMaxSessions
()
>
0
{
sessionLimitAccountIDs
=
append
(
sessionLimitAccountIDs
,
acc
.
ID
)
}
}
}
// 并行获取窗口费用和活跃会话数
var
windowCosts
map
[
int64
]
float64
var
activeSessions
map
[
int64
]
int
// 获取活跃会话数(批量查询)
if
len
(
sessionLimitAccountIDs
)
>
0
&&
h
.
sessionLimitCache
!=
nil
{
activeSessions
,
_
=
h
.
sessionLimitCache
.
GetActiveSessionCountBatch
(
c
.
Request
.
Context
(),
sessionLimitAccountIDs
)
if
activeSessions
==
nil
{
activeSessions
=
make
(
map
[
int64
]
int
)
}
}
// 获取窗口费用(并行查询)
if
len
(
windowCostAccountIDs
)
>
0
{
windowCosts
=
make
(
map
[
int64
]
float64
)
var
mu
sync
.
Mutex
g
,
gctx
:=
errgroup
.
WithContext
(
c
.
Request
.
Context
())
g
.
SetLimit
(
10
)
// 限制并发数
for
i
:=
range
accounts
{
acc
:=
&
accounts
[
i
]
if
!
acc
.
IsAnthropicOAuthOrSetupToken
()
||
acc
.
GetWindowCostLimit
()
<=
0
{
continue
}
accCopy
:=
acc
// 闭包捕获
g
.
Go
(
func
()
error
{
var
startTime
time
.
Time
if
accCopy
.
SessionWindowStart
!=
nil
{
startTime
=
*
accCopy
.
SessionWindowStart
}
else
{
startTime
=
time
.
Now
()
.
Add
(
-
5
*
time
.
Hour
)
}
stats
,
err
:=
h
.
accountUsageService
.
GetAccountWindowStats
(
gctx
,
accCopy
.
ID
,
startTime
)
if
err
==
nil
&&
stats
!=
nil
{
mu
.
Lock
()
windowCosts
[
accCopy
.
ID
]
=
stats
.
StandardCost
// 使用标准费用
mu
.
Unlock
()
}
return
nil
// 不返回错误,允许部分失败
})
}
_
=
g
.
Wait
()
}
// Build response with concurrency info
// Build response with concurrency info
result
:=
make
([]
AccountWithConcurrency
,
len
(
accounts
))
result
:=
make
([]
AccountWithConcurrency
,
len
(
accounts
))
for
i
:=
range
accounts
{
for
i
:=
range
accounts
{
result
[
i
]
=
AccountWithConcurrency
{
acc
:=
&
accounts
[
i
]
Account
:
dto
.
AccountFromService
(
&
accounts
[
i
]),
item
:=
AccountWithConcurrency
{
CurrentConcurrency
:
concurrencyCounts
[
accounts
[
i
]
.
ID
],
Account
:
dto
.
AccountFromService
(
acc
),
CurrentConcurrency
:
concurrencyCounts
[
acc
.
ID
],
}
}
// 添加窗口费用(仅当启用时)
if
windowCosts
!=
nil
{
if
cost
,
ok
:=
windowCosts
[
acc
.
ID
];
ok
{
item
.
CurrentWindowCost
=
&
cost
}
}
// 添加活跃会话数(仅当启用时)
if
activeSessions
!=
nil
{
if
count
,
ok
:=
activeSessions
[
acc
.
ID
];
ok
{
item
.
ActiveSessions
=
&
count
}
}
result
[
i
]
=
item
}
}
response
.
Paginated
(
c
,
result
,
total
,
page
,
pageSize
)
response
.
Paginated
(
c
,
result
,
total
,
page
,
pageSize
)
...
...
backend/internal/handler/dto/mappers.go
View file @
dd7f2124
...
@@ -116,7 +116,7 @@ func AccountFromServiceShallow(a *service.Account) *Account {
...
@@ -116,7 +116,7 @@ func AccountFromServiceShallow(a *service.Account) *Account {
if
a
==
nil
{
if
a
==
nil
{
return
nil
return
nil
}
}
return
&
Account
{
out
:=
&
Account
{
ID
:
a
.
ID
,
ID
:
a
.
ID
,
Name
:
a
.
Name
,
Name
:
a
.
Name
,
Notes
:
a
.
Notes
,
Notes
:
a
.
Notes
,
...
@@ -146,6 +146,24 @@ func AccountFromServiceShallow(a *service.Account) *Account {
...
@@ -146,6 +146,24 @@ func AccountFromServiceShallow(a *service.Account) *Account {
SessionWindowStatus
:
a
.
SessionWindowStatus
,
SessionWindowStatus
:
a
.
SessionWindowStatus
,
GroupIDs
:
a
.
GroupIDs
,
GroupIDs
:
a
.
GroupIDs
,
}
}
// 提取 5h 窗口费用控制和会话数量控制配置(仅 Anthropic OAuth/SetupToken 账号有效)
if
a
.
IsAnthropicOAuthOrSetupToken
()
{
if
limit
:=
a
.
GetWindowCostLimit
();
limit
>
0
{
out
.
WindowCostLimit
=
&
limit
}
if
reserve
:=
a
.
GetWindowCostStickyReserve
();
reserve
>
0
{
out
.
WindowCostStickyReserve
=
&
reserve
}
if
maxSessions
:=
a
.
GetMaxSessions
();
maxSessions
>
0
{
out
.
MaxSessions
=
&
maxSessions
}
if
idleTimeout
:=
a
.
GetSessionIdleTimeoutMinutes
();
idleTimeout
>
0
{
out
.
SessionIdleTimeoutMin
=
&
idleTimeout
}
}
return
out
}
}
func
AccountFromService
(
a
*
service
.
Account
)
*
Account
{
func
AccountFromService
(
a
*
service
.
Account
)
*
Account
{
...
...
backend/internal/handler/dto/types.go
View file @
dd7f2124
...
@@ -102,6 +102,16 @@ type Account struct {
...
@@ -102,6 +102,16 @@ type Account struct {
SessionWindowEnd
*
time
.
Time
`json:"session_window_end"`
SessionWindowEnd
*
time
.
Time
`json:"session_window_end"`
SessionWindowStatus
string
`json:"session_window_status"`
SessionWindowStatus
string
`json:"session_window_status"`
// 5h窗口费用控制(仅 Anthropic OAuth/SetupToken 账号有效)
// 从 extra 字段提取,方便前端显示和编辑
WindowCostLimit
*
float64
`json:"window_cost_limit,omitempty"`
WindowCostStickyReserve
*
float64
`json:"window_cost_sticky_reserve,omitempty"`
// 会话数量控制(仅 Anthropic OAuth/SetupToken 账号有效)
// 从 extra 字段提取,方便前端显示和编辑
MaxSessions
*
int
`json:"max_sessions,omitempty"`
SessionIdleTimeoutMin
*
int
`json:"session_idle_timeout_minutes,omitempty"`
Proxy
*
Proxy
`json:"proxy,omitempty"`
Proxy
*
Proxy
`json:"proxy,omitempty"`
AccountGroups
[]
AccountGroup
`json:"account_groups,omitempty"`
AccountGroups
[]
AccountGroup
`json:"account_groups,omitempty"`
...
...
backend/internal/handler/gateway_handler.go
View file @
dd7f2124
...
@@ -185,7 +185,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -185,7 +185,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
lastFailoverStatus
:=
0
lastFailoverStatus
:=
0
for
{
for
{
selection
,
err
:=
h
.
gatewayService
.
SelectAccountWithLoadAwareness
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionKey
,
reqModel
,
failedAccountIDs
)
selection
,
err
:=
h
.
gatewayService
.
SelectAccountWithLoadAwareness
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionKey
,
reqModel
,
failedAccountIDs
,
""
)
// Gemini 不使用会话限制
if
err
!=
nil
{
if
err
!=
nil
{
if
len
(
failedAccountIDs
)
==
0
{
if
len
(
failedAccountIDs
)
==
0
{
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
...
@@ -320,7 +320,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
...
@@ -320,7 +320,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
for
{
for
{
// 选择支持该模型的账号
// 选择支持该模型的账号
selection
,
err
:=
h
.
gatewayService
.
SelectAccountWithLoadAwareness
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionKey
,
reqModel
,
failedAccountIDs
)
selection
,
err
:=
h
.
gatewayService
.
SelectAccountWithLoadAwareness
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionKey
,
reqModel
,
failedAccountIDs
,
parsedReq
.
MetadataUserID
)
if
err
!=
nil
{
if
err
!=
nil
{
if
len
(
failedAccountIDs
)
==
0
{
if
len
(
failedAccountIDs
)
==
0
{
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
h
.
handleStreamingAwareError
(
c
,
http
.
StatusServiceUnavailable
,
"api_error"
,
"No available accounts: "
+
err
.
Error
(),
streamStarted
)
...
...
backend/internal/handler/gemini_v1beta_handler.go
View file @
dd7f2124
...
@@ -226,7 +226,7 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
...
@@ -226,7 +226,7 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
lastFailoverStatus
:=
0
lastFailoverStatus
:=
0
for
{
for
{
selection
,
err
:=
h
.
gatewayService
.
SelectAccountWithLoadAwareness
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionKey
,
modelName
,
failedAccountIDs
)
selection
,
err
:=
h
.
gatewayService
.
SelectAccountWithLoadAwareness
(
c
.
Request
.
Context
(),
apiKey
.
GroupID
,
sessionKey
,
modelName
,
failedAccountIDs
,
""
)
// Gemini 不使用会话限制
if
err
!=
nil
{
if
err
!=
nil
{
if
len
(
failedAccountIDs
)
==
0
{
if
len
(
failedAccountIDs
)
==
0
{
googleError
(
c
,
http
.
StatusServiceUnavailable
,
"No available Gemini accounts: "
+
err
.
Error
())
googleError
(
c
,
http
.
StatusServiceUnavailable
,
"No available Gemini accounts: "
+
err
.
Error
())
...
...
backend/internal/repository/session_limit_cache.go
0 → 100644
View file @
dd7f2124
package
repository
import
(
"context"
"fmt"
"strconv"
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/redis/go-redis/v9"
)
// 会话限制缓存常量定义
//
// 设计说明:
// 使用 Redis 有序集合(Sorted Set)跟踪每个账号的活跃会话:
// - Key: session_limit:account:{accountID}
// - Member: sessionUUID(从 metadata.user_id 中提取)
// - Score: Unix 时间戳(会话最后活跃时间)
//
// 通过 ZREMRANGEBYSCORE 自动清理过期会话,无需手动管理 TTL
const
(
// 会话限制键前缀
// 格式: session_limit:account:{accountID}
sessionLimitKeyPrefix
=
"session_limit:account:"
// 窗口费用缓存键前缀
// 格式: window_cost:account:{accountID}
windowCostKeyPrefix
=
"window_cost:account:"
// 窗口费用缓存 TTL(30秒)
windowCostCacheTTL
=
30
*
time
.
Second
)
var
(
// registerSessionScript 注册会话活动
// 使用 Redis TIME 命令获取服务器时间,避免多实例时钟不同步
// KEYS[1] = session_limit:account:{accountID}
// ARGV[1] = maxSessions
// ARGV[2] = idleTimeout(秒)
// ARGV[3] = sessionUUID
// 返回: 1 = 允许, 0 = 拒绝
registerSessionScript
=
redis
.
NewScript
(
`
local key = KEYS[1]
local maxSessions = tonumber(ARGV[1])
local idleTimeout = tonumber(ARGV[2])
local sessionUUID = ARGV[3]
-- 使用 Redis 服务器时间,确保多实例时钟一致
local timeResult = redis.call('TIME')
local now = tonumber(timeResult[1])
local expireBefore = now - idleTimeout
-- 清理过期会话
redis.call('ZREMRANGEBYSCORE', key, '-inf', expireBefore)
-- 检查会话是否已存在(支持刷新时间戳)
local exists = redis.call('ZSCORE', key, sessionUUID)
if exists ~= false then
-- 会话已存在,刷新时间戳
redis.call('ZADD', key, now, sessionUUID)
redis.call('EXPIRE', key, idleTimeout + 60)
return 1
end
-- 检查是否达到会话数量上限
local count = redis.call('ZCARD', key)
if count < maxSessions then
-- 未达上限,添加新会话
redis.call('ZADD', key, now, sessionUUID)
redis.call('EXPIRE', key, idleTimeout + 60)
return 1
end
-- 达到上限,拒绝新会话
return 0
`
)
// refreshSessionScript 刷新会话时间戳
// KEYS[1] = session_limit:account:{accountID}
// ARGV[1] = idleTimeout(秒)
// ARGV[2] = sessionUUID
refreshSessionScript
=
redis
.
NewScript
(
`
local key = KEYS[1]
local idleTimeout = tonumber(ARGV[1])
local sessionUUID = ARGV[2]
local timeResult = redis.call('TIME')
local now = tonumber(timeResult[1])
-- 检查会话是否存在
local exists = redis.call('ZSCORE', key, sessionUUID)
if exists ~= false then
redis.call('ZADD', key, now, sessionUUID)
redis.call('EXPIRE', key, idleTimeout + 60)
end
return 1
`
)
// getActiveSessionCountScript 获取活跃会话数
// KEYS[1] = session_limit:account:{accountID}
// ARGV[1] = idleTimeout(秒)
getActiveSessionCountScript
=
redis
.
NewScript
(
`
local key = KEYS[1]
local idleTimeout = tonumber(ARGV[1])
local timeResult = redis.call('TIME')
local now = tonumber(timeResult[1])
local expireBefore = now - idleTimeout
-- 清理过期会话
redis.call('ZREMRANGEBYSCORE', key, '-inf', expireBefore)
return redis.call('ZCARD', key)
`
)
// isSessionActiveScript 检查会话是否活跃
// KEYS[1] = session_limit:account:{accountID}
// ARGV[1] = idleTimeout(秒)
// ARGV[2] = sessionUUID
isSessionActiveScript
=
redis
.
NewScript
(
`
local key = KEYS[1]
local idleTimeout = tonumber(ARGV[1])
local sessionUUID = ARGV[2]
local timeResult = redis.call('TIME')
local now = tonumber(timeResult[1])
local expireBefore = now - idleTimeout
-- 获取会话的时间戳
local score = redis.call('ZSCORE', key, sessionUUID)
if score == false then
return 0
end
-- 检查是否过期
if tonumber(score) <= expireBefore then
return 0
end
return 1
`
)
)
type
sessionLimitCache
struct
{
rdb
*
redis
.
Client
defaultIdleTimeout
time
.
Duration
// 默认空闲超时(用于 GetActiveSessionCount)
}
// NewSessionLimitCache 创建会话限制缓存
// defaultIdleTimeoutMinutes: 默认空闲超时时间(分钟),用于无参数查询
func
NewSessionLimitCache
(
rdb
*
redis
.
Client
,
defaultIdleTimeoutMinutes
int
)
service
.
SessionLimitCache
{
if
defaultIdleTimeoutMinutes
<=
0
{
defaultIdleTimeoutMinutes
=
5
// 默认 5 分钟
}
return
&
sessionLimitCache
{
rdb
:
rdb
,
defaultIdleTimeout
:
time
.
Duration
(
defaultIdleTimeoutMinutes
)
*
time
.
Minute
,
}
}
// sessionLimitKey 生成会话限制的 Redis 键
func
sessionLimitKey
(
accountID
int64
)
string
{
return
fmt
.
Sprintf
(
"%s%d"
,
sessionLimitKeyPrefix
,
accountID
)
}
// windowCostKey 生成窗口费用缓存的 Redis 键
func
windowCostKey
(
accountID
int64
)
string
{
return
fmt
.
Sprintf
(
"%s%d"
,
windowCostKeyPrefix
,
accountID
)
}
// RegisterSession 注册会话活动
func
(
c
*
sessionLimitCache
)
RegisterSession
(
ctx
context
.
Context
,
accountID
int64
,
sessionUUID
string
,
maxSessions
int
,
idleTimeout
time
.
Duration
)
(
bool
,
error
)
{
if
sessionUUID
==
""
||
maxSessions
<=
0
{
return
true
,
nil
// 无效参数,默认允许
}
key
:=
sessionLimitKey
(
accountID
)
idleTimeoutSeconds
:=
int
(
idleTimeout
.
Seconds
())
if
idleTimeoutSeconds
<=
0
{
idleTimeoutSeconds
=
int
(
c
.
defaultIdleTimeout
.
Seconds
())
}
result
,
err
:=
registerSessionScript
.
Run
(
ctx
,
c
.
rdb
,
[]
string
{
key
},
maxSessions
,
idleTimeoutSeconds
,
sessionUUID
)
.
Int
()
if
err
!=
nil
{
return
true
,
err
// 失败开放:缓存错误时允许请求通过
}
return
result
==
1
,
nil
}
// RefreshSession 刷新会话时间戳
func
(
c
*
sessionLimitCache
)
RefreshSession
(
ctx
context
.
Context
,
accountID
int64
,
sessionUUID
string
,
idleTimeout
time
.
Duration
)
error
{
if
sessionUUID
==
""
{
return
nil
}
key
:=
sessionLimitKey
(
accountID
)
idleTimeoutSeconds
:=
int
(
idleTimeout
.
Seconds
())
if
idleTimeoutSeconds
<=
0
{
idleTimeoutSeconds
=
int
(
c
.
defaultIdleTimeout
.
Seconds
())
}
_
,
err
:=
refreshSessionScript
.
Run
(
ctx
,
c
.
rdb
,
[]
string
{
key
},
idleTimeoutSeconds
,
sessionUUID
)
.
Result
()
return
err
}
// GetActiveSessionCount 获取活跃会话数
func
(
c
*
sessionLimitCache
)
GetActiveSessionCount
(
ctx
context
.
Context
,
accountID
int64
)
(
int
,
error
)
{
key
:=
sessionLimitKey
(
accountID
)
idleTimeoutSeconds
:=
int
(
c
.
defaultIdleTimeout
.
Seconds
())
result
,
err
:=
getActiveSessionCountScript
.
Run
(
ctx
,
c
.
rdb
,
[]
string
{
key
},
idleTimeoutSeconds
)
.
Int
()
if
err
!=
nil
{
return
0
,
err
}
return
result
,
nil
}
// GetActiveSessionCountBatch 批量获取多个账号的活跃会话数
func
(
c
*
sessionLimitCache
)
GetActiveSessionCountBatch
(
ctx
context
.
Context
,
accountIDs
[]
int64
)
(
map
[
int64
]
int
,
error
)
{
if
len
(
accountIDs
)
==
0
{
return
make
(
map
[
int64
]
int
),
nil
}
results
:=
make
(
map
[
int64
]
int
,
len
(
accountIDs
))
// 使用 pipeline 批量执行
pipe
:=
c
.
rdb
.
Pipeline
()
idleTimeoutSeconds
:=
int
(
c
.
defaultIdleTimeout
.
Seconds
())
cmds
:=
make
(
map
[
int64
]
*
redis
.
Cmd
,
len
(
accountIDs
))
for
_
,
accountID
:=
range
accountIDs
{
key
:=
sessionLimitKey
(
accountID
)
cmds
[
accountID
]
=
getActiveSessionCountScript
.
Run
(
ctx
,
pipe
,
[]
string
{
key
},
idleTimeoutSeconds
)
}
// 执行 pipeline,即使部分失败也尝试获取成功的结果
_
,
_
=
pipe
.
Exec
(
ctx
)
for
accountID
,
cmd
:=
range
cmds
{
if
result
,
err
:=
cmd
.
Int
();
err
==
nil
{
results
[
accountID
]
=
result
}
}
return
results
,
nil
}
// IsSessionActive 检查会话是否活跃
func
(
c
*
sessionLimitCache
)
IsSessionActive
(
ctx
context
.
Context
,
accountID
int64
,
sessionUUID
string
)
(
bool
,
error
)
{
if
sessionUUID
==
""
{
return
false
,
nil
}
key
:=
sessionLimitKey
(
accountID
)
idleTimeoutSeconds
:=
int
(
c
.
defaultIdleTimeout
.
Seconds
())
result
,
err
:=
isSessionActiveScript
.
Run
(
ctx
,
c
.
rdb
,
[]
string
{
key
},
idleTimeoutSeconds
,
sessionUUID
)
.
Int
()
if
err
!=
nil
{
return
false
,
err
}
return
result
==
1
,
nil
}
// ========== 5h窗口费用缓存实现 ==========
// GetWindowCost 获取缓存的窗口费用
func
(
c
*
sessionLimitCache
)
GetWindowCost
(
ctx
context
.
Context
,
accountID
int64
)
(
float64
,
bool
,
error
)
{
key
:=
windowCostKey
(
accountID
)
val
,
err
:=
c
.
rdb
.
Get
(
ctx
,
key
)
.
Float64
()
if
err
==
redis
.
Nil
{
return
0
,
false
,
nil
// 缓存未命中
}
if
err
!=
nil
{
return
0
,
false
,
err
}
return
val
,
true
,
nil
}
// SetWindowCost 设置窗口费用缓存
func
(
c
*
sessionLimitCache
)
SetWindowCost
(
ctx
context
.
Context
,
accountID
int64
,
cost
float64
)
error
{
key
:=
windowCostKey
(
accountID
)
return
c
.
rdb
.
Set
(
ctx
,
key
,
cost
,
windowCostCacheTTL
)
.
Err
()
}
// GetWindowCostBatch 批量获取窗口费用缓存
func
(
c
*
sessionLimitCache
)
GetWindowCostBatch
(
ctx
context
.
Context
,
accountIDs
[]
int64
)
(
map
[
int64
]
float64
,
error
)
{
if
len
(
accountIDs
)
==
0
{
return
make
(
map
[
int64
]
float64
),
nil
}
// 构建批量查询的 keys
keys
:=
make
([]
string
,
len
(
accountIDs
))
for
i
,
accountID
:=
range
accountIDs
{
keys
[
i
]
=
windowCostKey
(
accountID
)
}
// 使用 MGET 批量获取
vals
,
err
:=
c
.
rdb
.
MGet
(
ctx
,
keys
...
)
.
Result
()
if
err
!=
nil
{
return
nil
,
err
}
results
:=
make
(
map
[
int64
]
float64
,
len
(
accountIDs
))
for
i
,
val
:=
range
vals
{
if
val
==
nil
{
continue
// 缓存未命中
}
// 尝试解析为 float64
switch
v
:=
val
.
(
type
)
{
case
string
:
if
cost
,
err
:=
strconv
.
ParseFloat
(
v
,
64
);
err
==
nil
{
results
[
accountIDs
[
i
]]
=
cost
}
case
float64
:
results
[
accountIDs
[
i
]]
=
v
}
}
return
results
,
nil
}
backend/internal/repository/wire.go
View file @
dd7f2124
...
@@ -37,6 +37,16 @@ func ProvidePricingRemoteClient(cfg *config.Config) service.PricingRemoteClient
...
@@ -37,6 +37,16 @@ func ProvidePricingRemoteClient(cfg *config.Config) service.PricingRemoteClient
return
NewPricingRemoteClient
(
cfg
.
Update
.
ProxyURL
)
return
NewPricingRemoteClient
(
cfg
.
Update
.
ProxyURL
)
}
}
// ProvideSessionLimitCache 创建会话限制缓存
// 用于 Anthropic OAuth/SetupToken 账号的并发会话数量控制
func
ProvideSessionLimitCache
(
rdb
*
redis
.
Client
,
cfg
*
config
.
Config
)
service
.
SessionLimitCache
{
defaultIdleTimeoutMinutes
:=
5
// 默认 5 分钟空闲超时
if
cfg
!=
nil
&&
cfg
.
Gateway
.
SessionIdleTimeoutMinutes
>
0
{
defaultIdleTimeoutMinutes
=
cfg
.
Gateway
.
SessionIdleTimeoutMinutes
}
return
NewSessionLimitCache
(
rdb
,
defaultIdleTimeoutMinutes
)
}
// ProviderSet is the Wire provider set for all repositories
// ProviderSet is the Wire provider set for all repositories
var
ProviderSet
=
wire
.
NewSet
(
var
ProviderSet
=
wire
.
NewSet
(
NewUserRepository
,
NewUserRepository
,
...
@@ -61,6 +71,7 @@ var ProviderSet = wire.NewSet(
...
@@ -61,6 +71,7 @@ var ProviderSet = wire.NewSet(
NewTempUnschedCache
,
NewTempUnschedCache
,
NewTimeoutCounterCache
,
NewTimeoutCounterCache
,
ProvideConcurrencyCache
,
ProvideConcurrencyCache
,
ProvideSessionLimitCache
,
NewDashboardCache
,
NewDashboardCache
,
NewEmailCache
,
NewEmailCache
,
NewIdentityCache
,
NewIdentityCache
,
...
...
backend/internal/server/api_contract_test.go
View file @
dd7f2124
...
@@ -441,7 +441,7 @@ func newContractDeps(t *testing.T) *contractDeps {
...
@@ -441,7 +441,7 @@ func newContractDeps(t *testing.T) *contractDeps {
apiKeyHandler
:=
handler
.
NewAPIKeyHandler
(
apiKeyService
)
apiKeyHandler
:=
handler
.
NewAPIKeyHandler
(
apiKeyService
)
usageHandler
:=
handler
.
NewUsageHandler
(
usageService
,
apiKeyService
)
usageHandler
:=
handler
.
NewUsageHandler
(
usageService
,
apiKeyService
)
adminSettingHandler
:=
adminhandler
.
NewSettingHandler
(
settingService
,
nil
,
nil
,
nil
)
adminSettingHandler
:=
adminhandler
.
NewSettingHandler
(
settingService
,
nil
,
nil
,
nil
)
adminAccountHandler
:=
adminhandler
.
NewAccountHandler
(
adminService
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
)
adminAccountHandler
:=
adminhandler
.
NewAccountHandler
(
adminService
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
,
nil
)
jwtAuth
:=
func
(
c
*
gin
.
Context
)
{
jwtAuth
:=
func
(
c
*
gin
.
Context
)
{
c
.
Set
(
string
(
middleware
.
ContextKeyUser
),
middleware
.
AuthSubject
{
c
.
Set
(
string
(
middleware
.
ContextKeyUser
),
middleware
.
AuthSubject
{
...
...
backend/internal/service/account.go
View file @
dd7f2124
...
@@ -573,3 +573,141 @@ func (a *Account) IsMixedSchedulingEnabled() bool {
...
@@ -573,3 +573,141 @@ func (a *Account) IsMixedSchedulingEnabled() bool {
}
}
return
false
return
false
}
}
// WindowCostSchedulability 窗口费用调度状态
type
WindowCostSchedulability
int
const
(
// WindowCostSchedulable 可正常调度
WindowCostSchedulable
WindowCostSchedulability
=
iota
// WindowCostStickyOnly 仅允许粘性会话
WindowCostStickyOnly
// WindowCostNotSchedulable 完全不可调度
WindowCostNotSchedulable
)
// IsAnthropicOAuthOrSetupToken 判断是否为 Anthropic OAuth 或 SetupToken 类型账号
// 仅这两类账号支持 5h 窗口额度控制和会话数量控制
func
(
a
*
Account
)
IsAnthropicOAuthOrSetupToken
()
bool
{
return
a
.
Platform
==
PlatformAnthropic
&&
(
a
.
Type
==
AccountTypeOAuth
||
a
.
Type
==
AccountTypeSetupToken
)
}
// GetWindowCostLimit 获取 5h 窗口费用阈值(美元)
// 返回 0 表示未启用
func
(
a
*
Account
)
GetWindowCostLimit
()
float64
{
if
a
.
Extra
==
nil
{
return
0
}
if
v
,
ok
:=
a
.
Extra
[
"window_cost_limit"
];
ok
{
return
parseExtraFloat64
(
v
)
}
return
0
}
// GetWindowCostStickyReserve 获取粘性会话预留额度(美元)
// 默认值为 10
func
(
a
*
Account
)
GetWindowCostStickyReserve
()
float64
{
if
a
.
Extra
==
nil
{
return
10.0
}
if
v
,
ok
:=
a
.
Extra
[
"window_cost_sticky_reserve"
];
ok
{
val
:=
parseExtraFloat64
(
v
)
if
val
>
0
{
return
val
}
}
return
10.0
}
// GetMaxSessions 获取最大并发会话数
// 返回 0 表示未启用
func
(
a
*
Account
)
GetMaxSessions
()
int
{
if
a
.
Extra
==
nil
{
return
0
}
if
v
,
ok
:=
a
.
Extra
[
"max_sessions"
];
ok
{
return
parseExtraInt
(
v
)
}
return
0
}
// GetSessionIdleTimeoutMinutes 获取会话空闲超时分钟数
// 默认值为 5 分钟
func
(
a
*
Account
)
GetSessionIdleTimeoutMinutes
()
int
{
if
a
.
Extra
==
nil
{
return
5
}
if
v
,
ok
:=
a
.
Extra
[
"session_idle_timeout_minutes"
];
ok
{
val
:=
parseExtraInt
(
v
)
if
val
>
0
{
return
val
}
}
return
5
}
// CheckWindowCostSchedulability 根据当前窗口费用检查调度状态
// - 费用 < 阈值: WindowCostSchedulable(可正常调度)
// - 费用 >= 阈值 且 < 阈值+预留: WindowCostStickyOnly(仅粘性会话)
// - 费用 >= 阈值+预留: WindowCostNotSchedulable(不可调度)
func
(
a
*
Account
)
CheckWindowCostSchedulability
(
currentWindowCost
float64
)
WindowCostSchedulability
{
limit
:=
a
.
GetWindowCostLimit
()
if
limit
<=
0
{
return
WindowCostSchedulable
}
if
currentWindowCost
<
limit
{
return
WindowCostSchedulable
}
stickyReserve
:=
a
.
GetWindowCostStickyReserve
()
if
currentWindowCost
<
limit
+
stickyReserve
{
return
WindowCostStickyOnly
}
return
WindowCostNotSchedulable
}
// parseExtraFloat64 从 extra 字段解析 float64 值
func
parseExtraFloat64
(
value
any
)
float64
{
switch
v
:=
value
.
(
type
)
{
case
float64
:
return
v
case
float32
:
return
float64
(
v
)
case
int
:
return
float64
(
v
)
case
int64
:
return
float64
(
v
)
case
json
.
Number
:
if
f
,
err
:=
v
.
Float64
();
err
==
nil
{
return
f
}
case
string
:
if
f
,
err
:=
strconv
.
ParseFloat
(
strings
.
TrimSpace
(
v
),
64
);
err
==
nil
{
return
f
}
}
return
0
}
// parseExtraInt 从 extra 字段解析 int 值
func
parseExtraInt
(
value
any
)
int
{
switch
v
:=
value
.
(
type
)
{
case
int
:
return
v
case
int64
:
return
int
(
v
)
case
float64
:
return
int
(
v
)
case
json
.
Number
:
if
i
,
err
:=
v
.
Int64
();
err
==
nil
{
return
int
(
i
)
}
case
string
:
if
i
,
err
:=
strconv
.
Atoi
(
strings
.
TrimSpace
(
v
));
err
==
nil
{
return
i
}
}
return
0
}
backend/internal/service/account_usage_service.go
View file @
dd7f2124
...
@@ -575,3 +575,9 @@ func buildGeminiUsageProgress(used, limit int64, resetAt time.Time, tokens int64
...
@@ -575,3 +575,9 @@ func buildGeminiUsageProgress(used, limit int64, resetAt time.Time, tokens int64
},
},
}
}
}
}
// GetAccountWindowStats 获取账号在指定时间窗口内的使用统计
// 用于账号列表页面显示当前窗口费用
func
(
s
*
AccountUsageService
)
GetAccountWindowStats
(
ctx
context
.
Context
,
accountID
int64
,
startTime
time
.
Time
)
(
*
usagestats
.
AccountStats
,
error
)
{
return
s
.
usageLogRepo
.
GetAccountWindowStats
(
ctx
,
accountID
,
startTime
)
}
backend/internal/service/gateway_multiplatform_test.go
View file @
dd7f2124
...
@@ -1052,7 +1052,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1052,7 +1052,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
nil
,
// No concurrency service
concurrencyService
:
nil
,
// No concurrency service
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1105,7 +1105,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1105,7 +1105,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
nil
,
// legacy path
concurrencyService
:
nil
,
// legacy path
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
&
groupID
,
sessionHash
,
"claude-b"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
&
groupID
,
sessionHash
,
"claude-b"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1137,7 +1137,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1137,7 +1137,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
nil
,
concurrencyService
:
nil
,
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1169,7 +1169,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1169,7 +1169,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
}
}
excludedIDs
:=
map
[
int64
]
struct
{}{
1
:
{}}
excludedIDs
:=
map
[
int64
]
struct
{}{
1
:
{}}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
excludedIDs
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
excludedIDs
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1203,7 +1203,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1203,7 +1203,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
NewConcurrencyService
(
concurrencyCache
),
concurrencyService
:
NewConcurrencyService
(
concurrencyCache
),
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
"sticky"
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
"sticky"
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1239,7 +1239,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1239,7 +1239,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
NewConcurrencyService
(
concurrencyCache
),
concurrencyService
:
NewConcurrencyService
(
concurrencyCache
),
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
"sticky"
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
"sticky"
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1266,7 +1266,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1266,7 +1266,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
nil
,
concurrencyService
:
nil
,
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
Error
(
t
,
err
)
require
.
Error
(
t
,
err
)
require
.
Nil
(
t
,
result
)
require
.
Nil
(
t
,
result
)
require
.
Contains
(
t
,
err
.
Error
(),
"no available accounts"
)
require
.
Contains
(
t
,
err
.
Error
(),
"no available accounts"
)
...
@@ -1298,7 +1298,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1298,7 +1298,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
nil
,
concurrencyService
:
nil
,
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
@@ -1331,7 +1331,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
...
@@ -1331,7 +1331,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
concurrencyService
:
nil
,
concurrencyService
:
nil
,
}
}
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
)
result
,
err
:=
svc
.
SelectAccountWithLoadAwareness
(
ctx
,
nil
,
""
,
"claude-3-5-sonnet-20241022"
,
nil
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
result
.
Account
)
require
.
NotNil
(
t
,
result
.
Account
)
...
...
backend/internal/service/gateway_service.go
View file @
dd7f2124
...
@@ -213,6 +213,7 @@ type GatewayService struct {
...
@@ -213,6 +213,7 @@ type GatewayService struct {
deferredService
*
DeferredService
deferredService
*
DeferredService
concurrencyService
*
ConcurrencyService
concurrencyService
*
ConcurrencyService
claudeTokenProvider
*
ClaudeTokenProvider
claudeTokenProvider
*
ClaudeTokenProvider
sessionLimitCache
SessionLimitCache
// 会话数量限制缓存(仅 Anthropic OAuth/SetupToken)
}
}
// NewGatewayService creates a new GatewayService
// NewGatewayService creates a new GatewayService
...
@@ -233,6 +234,7 @@ func NewGatewayService(
...
@@ -233,6 +234,7 @@ func NewGatewayService(
httpUpstream
HTTPUpstream
,
httpUpstream
HTTPUpstream
,
deferredService
*
DeferredService
,
deferredService
*
DeferredService
,
claudeTokenProvider
*
ClaudeTokenProvider
,
claudeTokenProvider
*
ClaudeTokenProvider
,
sessionLimitCache
SessionLimitCache
,
)
*
GatewayService
{
)
*
GatewayService
{
return
&
GatewayService
{
return
&
GatewayService
{
accountRepo
:
accountRepo
,
accountRepo
:
accountRepo
,
...
@@ -251,6 +253,7 @@ func NewGatewayService(
...
@@ -251,6 +253,7 @@ func NewGatewayService(
httpUpstream
:
httpUpstream
,
httpUpstream
:
httpUpstream
,
deferredService
:
deferredService
,
deferredService
:
deferredService
,
claudeTokenProvider
:
claudeTokenProvider
,
claudeTokenProvider
:
claudeTokenProvider
,
sessionLimitCache
:
sessionLimitCache
,
}
}
}
}
...
@@ -816,8 +819,12 @@ func (s *GatewayService) SelectAccountForModelWithExclusions(ctx context.Context
...
@@ -816,8 +819,12 @@ func (s *GatewayService) SelectAccountForModelWithExclusions(ctx context.Context
}
}
// SelectAccountWithLoadAwareness selects account with load-awareness and wait plan.
// SelectAccountWithLoadAwareness selects account with load-awareness and wait plan.
func
(
s
*
GatewayService
)
SelectAccountWithLoadAwareness
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
,
excludedIDs
map
[
int64
]
struct
{})
(
*
AccountSelectionResult
,
error
)
{
// metadataUserID: 原始 metadata.user_id 字段(用于提取会话 UUID 进行会话数量限制)
func
(
s
*
GatewayService
)
SelectAccountWithLoadAwareness
(
ctx
context
.
Context
,
groupID
*
int64
,
sessionHash
string
,
requestedModel
string
,
excludedIDs
map
[
int64
]
struct
{},
metadataUserID
string
)
(
*
AccountSelectionResult
,
error
)
{
cfg
:=
s
.
schedulingConfig
()
cfg
:=
s
.
schedulingConfig
()
// 提取会话 UUID(用于会话数量限制)
sessionUUID
:=
extractSessionUUID
(
metadataUserID
)
var
stickyAccountID
int64
var
stickyAccountID
int64
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
if
accountID
,
err
:=
s
.
cache
.
GetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
);
err
==
nil
{
if
accountID
,
err
:=
s
.
cache
.
GetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
);
err
==
nil
{
...
@@ -936,7 +943,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -936,7 +943,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if
len
(
routingAccountIDs
)
>
0
&&
s
.
concurrencyService
!=
nil
{
if
len
(
routingAccountIDs
)
>
0
&&
s
.
concurrencyService
!=
nil
{
// 1. 过滤出路由列表中可调度的账号
// 1. 过滤出路由列表中可调度的账号
var
routingCandidates
[]
*
Account
var
routingCandidates
[]
*
Account
var
filteredExcluded
,
filteredMissing
,
filteredUnsched
,
filteredPlatform
,
filteredModelScope
,
filteredModelMapping
int
var
filteredExcluded
,
filteredMissing
,
filteredUnsched
,
filteredPlatform
,
filteredModelScope
,
filteredModelMapping
,
filteredWindowCost
int
for
_
,
routingAccountID
:=
range
routingAccountIDs
{
for
_
,
routingAccountID
:=
range
routingAccountIDs
{
if
isExcluded
(
routingAccountID
)
{
if
isExcluded
(
routingAccountID
)
{
filteredExcluded
++
filteredExcluded
++
...
@@ -963,13 +970,18 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -963,13 +970,18 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
filteredModelMapping
++
filteredModelMapping
++
continue
continue
}
}
// 窗口费用检查(非粘性会话路径)
if
!
s
.
isAccountSchedulableForWindowCost
(
ctx
,
account
,
false
)
{
filteredWindowCost
++
continue
}
routingCandidates
=
append
(
routingCandidates
,
account
)
routingCandidates
=
append
(
routingCandidates
,
account
)
}
}
if
s
.
debugModelRoutingEnabled
()
{
if
s
.
debugModelRoutingEnabled
()
{
log
.
Printf
(
"[ModelRoutingDebug] routed candidates: group_id=%v model=%s routed=%d candidates=%d filtered(excluded=%d missing=%d unsched=%d platform=%d model_scope=%d model_mapping=%d)"
,
log
.
Printf
(
"[ModelRoutingDebug] routed candidates: group_id=%v model=%s routed=%d candidates=%d filtered(excluded=%d missing=%d unsched=%d platform=%d model_scope=%d model_mapping=%d
window_cost=%d
)"
,
derefGroupID
(
groupID
),
requestedModel
,
len
(
routingAccountIDs
),
len
(
routingCandidates
),
derefGroupID
(
groupID
),
requestedModel
,
len
(
routingAccountIDs
),
len
(
routingCandidates
),
filteredExcluded
,
filteredMissing
,
filteredUnsched
,
filteredPlatform
,
filteredModelScope
,
filteredModelMapping
)
filteredExcluded
,
filteredMissing
,
filteredUnsched
,
filteredPlatform
,
filteredModelScope
,
filteredModelMapping
,
filteredWindowCost
)
}
}
if
len
(
routingCandidates
)
>
0
{
if
len
(
routingCandidates
)
>
0
{
...
@@ -982,18 +994,25 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -982,18 +994,25 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if
stickyAccount
.
IsSchedulable
()
&&
if
stickyAccount
.
IsSchedulable
()
&&
s
.
isAccountAllowedForPlatform
(
stickyAccount
,
platform
,
useMixed
)
&&
s
.
isAccountAllowedForPlatform
(
stickyAccount
,
platform
,
useMixed
)
&&
stickyAccount
.
IsSchedulableForModel
(
requestedModel
)
&&
stickyAccount
.
IsSchedulableForModel
(
requestedModel
)
&&
(
requestedModel
==
""
||
s
.
isModelSupportedByAccount
(
stickyAccount
,
requestedModel
))
{
(
requestedModel
==
""
||
s
.
isModelSupportedByAccount
(
stickyAccount
,
requestedModel
))
&&
s
.
isAccountSchedulableForWindowCost
(
ctx
,
stickyAccount
,
true
)
{
// 粘性会话窗口费用检查
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
stickyAccountID
,
stickyAccount
.
Concurrency
)
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
stickyAccountID
,
stickyAccount
.
Concurrency
)
if
err
==
nil
&&
result
.
Acquired
{
if
err
==
nil
&&
result
.
Acquired
{
_
=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
stickySessionTTL
)
// 会话数量限制检查
if
s
.
debugModelRoutingEnabled
()
{
if
!
s
.
checkAndRegisterSession
(
ctx
,
stickyAccount
,
sessionUUID
)
{
log
.
Printf
(
"[ModelRoutingDebug] routed sticky hit: group_id=%v model=%s session=%s account=%d"
,
derefGroupID
(
groupID
),
requestedModel
,
shortSessionHash
(
sessionHash
),
stickyAccountID
)
result
.
ReleaseFunc
()
// 释放槽位
// 继续到负载感知选择
}
else
{
_
=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
stickySessionTTL
)
if
s
.
debugModelRoutingEnabled
()
{
log
.
Printf
(
"[ModelRoutingDebug] routed sticky hit: group_id=%v model=%s session=%s account=%d"
,
derefGroupID
(
groupID
),
requestedModel
,
shortSessionHash
(
sessionHash
),
stickyAccountID
)
}
return
&
AccountSelectionResult
{
Account
:
stickyAccount
,
Acquired
:
true
,
ReleaseFunc
:
result
.
ReleaseFunc
,
},
nil
}
}
return
&
AccountSelectionResult
{
Account
:
stickyAccount
,
Acquired
:
true
,
ReleaseFunc
:
result
.
ReleaseFunc
,
},
nil
}
}
waitingCount
,
_
:=
s
.
concurrencyService
.
GetAccountWaitingCount
(
ctx
,
stickyAccountID
)
waitingCount
,
_
:=
s
.
concurrencyService
.
GetAccountWaitingCount
(
ctx
,
stickyAccountID
)
...
@@ -1066,6 +1085,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -1066,6 +1085,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
for
_
,
item
:=
range
routingAvailable
{
for
_
,
item
:=
range
routingAvailable
{
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
item
.
account
.
ID
,
item
.
account
.
Concurrency
)
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
item
.
account
.
ID
,
item
.
account
.
Concurrency
)
if
err
==
nil
&&
result
.
Acquired
{
if
err
==
nil
&&
result
.
Acquired
{
// 会话数量限制检查
if
!
s
.
checkAndRegisterSession
(
ctx
,
item
.
account
,
sessionUUID
)
{
result
.
ReleaseFunc
()
// 释放槽位,继续尝试下一个账号
continue
}
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
_
=
s
.
cache
.
SetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
item
.
account
.
ID
,
stickySessionTTL
)
_
=
s
.
cache
.
SetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
item
.
account
.
ID
,
stickySessionTTL
)
}
}
...
@@ -1108,15 +1132,21 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -1108,15 +1132,21 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if
ok
&&
s
.
isAccountInGroup
(
account
,
groupID
)
&&
if
ok
&&
s
.
isAccountInGroup
(
account
,
groupID
)
&&
s
.
isAccountAllowedForPlatform
(
account
,
platform
,
useMixed
)
&&
s
.
isAccountAllowedForPlatform
(
account
,
platform
,
useMixed
)
&&
account
.
IsSchedulableForModel
(
requestedModel
)
&&
account
.
IsSchedulableForModel
(
requestedModel
)
&&
(
requestedModel
==
""
||
s
.
isModelSupportedByAccount
(
account
,
requestedModel
))
{
(
requestedModel
==
""
||
s
.
isModelSupportedByAccount
(
account
,
requestedModel
))
&&
s
.
isAccountSchedulableForWindowCost
(
ctx
,
account
,
true
)
{
// 粘性会话窗口费用检查
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
accountID
,
account
.
Concurrency
)
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
accountID
,
account
.
Concurrency
)
if
err
==
nil
&&
result
.
Acquired
{
if
err
==
nil
&&
result
.
Acquired
{
_
=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
stickySessionTTL
)
// 会话数量限制检查
return
&
AccountSelectionResult
{
if
!
s
.
checkAndRegisterSession
(
ctx
,
account
,
sessionUUID
)
{
Account
:
account
,
result
.
ReleaseFunc
()
// 释放槽位,继续到 Layer 2
Acquired
:
true
,
}
else
{
ReleaseFunc
:
result
.
ReleaseFunc
,
_
=
s
.
cache
.
RefreshSessionTTL
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
stickySessionTTL
)
},
nil
return
&
AccountSelectionResult
{
Account
:
account
,
Acquired
:
true
,
ReleaseFunc
:
result
.
ReleaseFunc
,
},
nil
}
}
}
waitingCount
,
_
:=
s
.
concurrencyService
.
GetAccountWaitingCount
(
ctx
,
accountID
)
waitingCount
,
_
:=
s
.
concurrencyService
.
GetAccountWaitingCount
(
ctx
,
accountID
)
...
@@ -1157,6 +1187,10 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -1157,6 +1187,10 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if
requestedModel
!=
""
&&
!
s
.
isModelSupportedByAccount
(
acc
,
requestedModel
)
{
if
requestedModel
!=
""
&&
!
s
.
isModelSupportedByAccount
(
acc
,
requestedModel
)
{
continue
continue
}
}
// 窗口费用检查(非粘性会话路径)
if
!
s
.
isAccountSchedulableForWindowCost
(
ctx
,
acc
,
false
)
{
continue
}
candidates
=
append
(
candidates
,
acc
)
candidates
=
append
(
candidates
,
acc
)
}
}
...
@@ -1174,7 +1208,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -1174,7 +1208,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
loadMap
,
err
:=
s
.
concurrencyService
.
GetAccountsLoadBatch
(
ctx
,
accountLoads
)
loadMap
,
err
:=
s
.
concurrencyService
.
GetAccountsLoadBatch
(
ctx
,
accountLoads
)
if
err
!=
nil
{
if
err
!=
nil
{
if
result
,
ok
:=
s
.
tryAcquireByLegacyOrder
(
ctx
,
candidates
,
groupID
,
sessionHash
,
preferOAuth
);
ok
{
if
result
,
ok
:=
s
.
tryAcquireByLegacyOrder
(
ctx
,
candidates
,
groupID
,
sessionHash
,
preferOAuth
,
sessionUUID
);
ok
{
return
result
,
nil
return
result
,
nil
}
}
}
else
{
}
else
{
...
@@ -1223,6 +1257,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -1223,6 +1257,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
for
_
,
item
:=
range
available
{
for
_
,
item
:=
range
available
{
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
item
.
account
.
ID
,
item
.
account
.
Concurrency
)
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
item
.
account
.
ID
,
item
.
account
.
Concurrency
)
if
err
==
nil
&&
result
.
Acquired
{
if
err
==
nil
&&
result
.
Acquired
{
// 会话数量限制检查
if
!
s
.
checkAndRegisterSession
(
ctx
,
item
.
account
,
sessionUUID
)
{
result
.
ReleaseFunc
()
// 释放槽位,继续尝试下一个账号
continue
}
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
_
=
s
.
cache
.
SetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
item
.
account
.
ID
,
stickySessionTTL
)
_
=
s
.
cache
.
SetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
item
.
account
.
ID
,
stickySessionTTL
)
}
}
...
@@ -1252,13 +1291,18 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
...
@@ -1252,13 +1291,18 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
return
nil
,
errors
.
New
(
"no available accounts"
)
return
nil
,
errors
.
New
(
"no available accounts"
)
}
}
func
(
s
*
GatewayService
)
tryAcquireByLegacyOrder
(
ctx
context
.
Context
,
candidates
[]
*
Account
,
groupID
*
int64
,
sessionHash
string
,
preferOAuth
bool
)
(
*
AccountSelectionResult
,
bool
)
{
func
(
s
*
GatewayService
)
tryAcquireByLegacyOrder
(
ctx
context
.
Context
,
candidates
[]
*
Account
,
groupID
*
int64
,
sessionHash
string
,
preferOAuth
bool
,
sessionUUID
string
)
(
*
AccountSelectionResult
,
bool
)
{
ordered
:=
append
([]
*
Account
(
nil
),
candidates
...
)
ordered
:=
append
([]
*
Account
(
nil
),
candidates
...
)
sortAccountsByPriorityAndLastUsed
(
ordered
,
preferOAuth
)
sortAccountsByPriorityAndLastUsed
(
ordered
,
preferOAuth
)
for
_
,
acc
:=
range
ordered
{
for
_
,
acc
:=
range
ordered
{
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
acc
.
ID
,
acc
.
Concurrency
)
result
,
err
:=
s
.
tryAcquireAccountSlot
(
ctx
,
acc
.
ID
,
acc
.
Concurrency
)
if
err
==
nil
&&
result
.
Acquired
{
if
err
==
nil
&&
result
.
Acquired
{
// 会话数量限制检查
if
!
s
.
checkAndRegisterSession
(
ctx
,
acc
,
sessionUUID
)
{
result
.
ReleaseFunc
()
// 释放槽位,继续尝试下一个账号
continue
}
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
if
sessionHash
!=
""
&&
s
.
cache
!=
nil
{
_
=
s
.
cache
.
SetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
acc
.
ID
,
stickySessionTTL
)
_
=
s
.
cache
.
SetSessionAccountID
(
ctx
,
derefGroupID
(
groupID
),
sessionHash
,
acc
.
ID
,
stickySessionTTL
)
}
}
...
@@ -1490,6 +1534,107 @@ func (s *GatewayService) tryAcquireAccountSlot(ctx context.Context, accountID in
...
@@ -1490,6 +1534,107 @@ func (s *GatewayService) tryAcquireAccountSlot(ctx context.Context, accountID in
return
s
.
concurrencyService
.
AcquireAccountSlot
(
ctx
,
accountID
,
maxConcurrency
)
return
s
.
concurrencyService
.
AcquireAccountSlot
(
ctx
,
accountID
,
maxConcurrency
)
}
}
// isAccountSchedulableForWindowCost 检查账号是否可根据窗口费用进行调度
// 仅适用于 Anthropic OAuth/SetupToken 账号
// 返回 true 表示可调度,false 表示不可调度
func
(
s
*
GatewayService
)
isAccountSchedulableForWindowCost
(
ctx
context
.
Context
,
account
*
Account
,
isSticky
bool
)
bool
{
// 只检查 Anthropic OAuth/SetupToken 账号
if
!
account
.
IsAnthropicOAuthOrSetupToken
()
{
return
true
}
limit
:=
account
.
GetWindowCostLimit
()
if
limit
<=
0
{
return
true
// 未启用窗口费用限制
}
// 尝试从缓存获取窗口费用
var
currentCost
float64
if
s
.
sessionLimitCache
!=
nil
{
if
cost
,
hit
,
err
:=
s
.
sessionLimitCache
.
GetWindowCost
(
ctx
,
account
.
ID
);
err
==
nil
&&
hit
{
currentCost
=
cost
goto
checkSchedulability
}
}
// 缓存未命中,从数据库查询
{
var
startTime
time
.
Time
if
account
.
SessionWindowStart
!=
nil
{
startTime
=
*
account
.
SessionWindowStart
}
else
{
startTime
=
time
.
Now
()
.
Add
(
-
5
*
time
.
Hour
)
}
stats
,
err
:=
s
.
usageLogRepo
.
GetAccountWindowStats
(
ctx
,
account
.
ID
,
startTime
)
if
err
!=
nil
{
// 失败开放:查询失败时允许调度
return
true
}
// 使用标准费用(不含账号倍率)
currentCost
=
stats
.
StandardCost
// 设置缓存(忽略错误)
if
s
.
sessionLimitCache
!=
nil
{
_
=
s
.
sessionLimitCache
.
SetWindowCost
(
ctx
,
account
.
ID
,
currentCost
)
}
}
checkSchedulability
:
schedulability
:=
account
.
CheckWindowCostSchedulability
(
currentCost
)
switch
schedulability
{
case
WindowCostSchedulable
:
return
true
case
WindowCostStickyOnly
:
return
isSticky
case
WindowCostNotSchedulable
:
return
false
}
return
true
}
// checkAndRegisterSession 检查并注册会话,用于会话数量限制
// 仅适用于 Anthropic OAuth/SetupToken 账号
// 返回 true 表示允许(在限制内或会话已存在),false 表示拒绝(超出限制且是新会话)
func
(
s
*
GatewayService
)
checkAndRegisterSession
(
ctx
context
.
Context
,
account
*
Account
,
sessionUUID
string
)
bool
{
// 只检查 Anthropic OAuth/SetupToken 账号
if
!
account
.
IsAnthropicOAuthOrSetupToken
()
{
return
true
}
maxSessions
:=
account
.
GetMaxSessions
()
if
maxSessions
<=
0
||
sessionUUID
==
""
{
return
true
// 未启用会话限制或无会话ID
}
if
s
.
sessionLimitCache
==
nil
{
return
true
// 缓存不可用时允许通过
}
idleTimeout
:=
time
.
Duration
(
account
.
GetSessionIdleTimeoutMinutes
())
*
time
.
Minute
allowed
,
err
:=
s
.
sessionLimitCache
.
RegisterSession
(
ctx
,
account
.
ID
,
sessionUUID
,
maxSessions
,
idleTimeout
)
if
err
!=
nil
{
// 失败开放:缓存错误时允许通过
return
true
}
return
allowed
}
// extractSessionUUID 从 metadata.user_id 中提取会话 UUID
// 格式: user_{64位hex}_account__session_{uuid}
func
extractSessionUUID
(
metadataUserID
string
)
string
{
if
metadataUserID
==
""
{
return
""
}
if
match
:=
sessionIDRegex
.
FindStringSubmatch
(
metadataUserID
);
len
(
match
)
>
1
{
return
match
[
1
]
}
return
""
}
func
(
s
*
GatewayService
)
getSchedulableAccount
(
ctx
context
.
Context
,
accountID
int64
)
(
*
Account
,
error
)
{
func
(
s
*
GatewayService
)
getSchedulableAccount
(
ctx
context
.
Context
,
accountID
int64
)
(
*
Account
,
error
)
{
if
s
.
schedulerSnapshot
!=
nil
{
if
s
.
schedulerSnapshot
!=
nil
{
return
s
.
schedulerSnapshot
.
GetAccount
(
ctx
,
accountID
)
return
s
.
schedulerSnapshot
.
GetAccount
(
ctx
,
accountID
)
...
@@ -2384,9 +2529,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
...
@@ -2384,9 +2529,9 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
retryStart
:=
time
.
Now
()
retryStart
:=
time
.
Now
()
for
attempt
:=
1
;
attempt
<=
maxRetryAttempts
;
attempt
++
{
for
attempt
:=
1
;
attempt
<=
maxRetryAttempts
;
attempt
++
{
// 构建上游请求(每次重试需要重新构建,因为请求体需要重新读取)
// 构建上游请求(每次重试需要重新构建,因为请求体需要重新读取)
upstreamReq
,
err
:=
s
.
buildUpstreamRequest
(
ctx
,
c
,
account
,
body
,
token
,
tokenType
,
reqModel
,
reqStream
,
shouldMimicClaudeCode
)
// Capture upstream request body for ops retry of this attempt.
// Capture upstream request body for ops retry of this attempt.
c
.
Set
(
OpsUpstreamRequestBodyKey
,
string
(
body
))
c
.
Set
(
OpsUpstreamRequestBodyKey
,
string
(
body
))
upstreamReq
,
err
:=
s
.
buildUpstreamRequest
(
ctx
,
c
,
account
,
body
,
token
,
tokenType
,
reqModel
,
reqStream
,
shouldMimicClaudeCode
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
...
backend/internal/service/openai_gateway_service.go
View file @
dd7f2124
...
@@ -1067,15 +1067,29 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
...
@@ -1067,15 +1067,29 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
// 记录上次收到上游数据的时间,用于控制 keepalive 发送频率
// 记录上次收到上游数据的时间,用于控制 keepalive 发送频率
lastDataAt
:=
time
.
Now
()
lastDataAt
:=
time
.
Now
()
// 仅发送一次错误事件,避免多次写入导致协议混乱(写失败时尽力通知客户端)
// 仅发送一次错误事件,避免多次写入导致协议混乱。
// 注意:OpenAI `/v1/responses` streaming 事件必须符合 OpenAI Responses schema;
// 否则下游 SDK(例如 OpenCode)会因为类型校验失败而报错。
errorEventSent
:=
false
errorEventSent
:=
false
clientDisconnected
:=
false
// 客户端断开后继续 drain 上游以收集 usage
sendErrorEvent
:=
func
(
reason
string
)
{
sendErrorEvent
:=
func
(
reason
string
)
{
if
errorEventSent
{
if
errorEventSent
||
clientDisconnected
{
return
return
}
}
errorEventSent
=
true
errorEventSent
=
true
_
,
_
=
fmt
.
Fprintf
(
w
,
"event: error
\n
data: {
\"
error
\"
:
\"
%s
\"
}
\n\n
"
,
reason
)
payload
:=
map
[
string
]
any
{
flusher
.
Flush
()
"type"
:
"error"
,
"sequence_number"
:
0
,
"error"
:
map
[
string
]
any
{
"type"
:
"upstream_error"
,
"message"
:
reason
,
"code"
:
reason
,
},
}
if
b
,
err
:=
json
.
Marshal
(
payload
);
err
==
nil
{
_
,
_
=
fmt
.
Fprintf
(
w
,
"data: %s
\n\n
"
,
b
)
flusher
.
Flush
()
}
}
}
needModelReplace
:=
originalModel
!=
mappedModel
needModelReplace
:=
originalModel
!=
mappedModel
...
@@ -1087,6 +1101,17 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
...
@@ -1087,6 +1101,17 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
nil
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
nil
}
}
if
ev
.
err
!=
nil
{
if
ev
.
err
!=
nil
{
// 客户端断开/取消请求时,上游读取往往会返回 context canceled。
// /v1/responses 的 SSE 事件必须符合 OpenAI 协议;这里不注入自定义 error event,避免下游 SDK 解析失败。
if
errors
.
Is
(
ev
.
err
,
context
.
Canceled
)
||
errors
.
Is
(
ev
.
err
,
context
.
DeadlineExceeded
)
{
log
.
Printf
(
"Context canceled during streaming, returning collected usage"
)
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
nil
}
// 客户端已断开时,上游出错仅影响体验,不影响计费;返回已收集 usage
if
clientDisconnected
{
log
.
Printf
(
"Upstream read error after client disconnect: %v, returning collected usage"
,
ev
.
err
)
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
nil
}
if
errors
.
Is
(
ev
.
err
,
bufio
.
ErrTooLong
)
{
if
errors
.
Is
(
ev
.
err
,
bufio
.
ErrTooLong
)
{
log
.
Printf
(
"SSE line too long: account=%d max_size=%d error=%v"
,
account
.
ID
,
maxLineSize
,
ev
.
err
)
log
.
Printf
(
"SSE line too long: account=%d max_size=%d error=%v"
,
account
.
ID
,
maxLineSize
,
ev
.
err
)
sendErrorEvent
(
"response_too_large"
)
sendErrorEvent
(
"response_too_large"
)
...
@@ -1110,15 +1135,19 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
...
@@ -1110,15 +1135,19 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
// Correct Codex tool calls if needed (apply_patch -> edit, etc.)
// Correct Codex tool calls if needed (apply_patch -> edit, etc.)
if
correctedData
,
corrected
:=
s
.
toolCorrector
.
CorrectToolCallsInSSEData
(
data
);
corrected
{
if
correctedData
,
corrected
:=
s
.
toolCorrector
.
CorrectToolCallsInSSEData
(
data
);
corrected
{
data
=
correctedData
line
=
"data: "
+
correctedData
line
=
"data: "
+
correctedData
}
}
// Forward line
// 写入客户端(客户端断开后继续 drain 上游)
if
_
,
err
:=
fmt
.
Fprintf
(
w
,
"%s
\n
"
,
line
);
err
!=
nil
{
if
!
clientDisconnected
{
sendErrorEvent
(
"write_failed"
)
if
_
,
err
:=
fmt
.
Fprintf
(
w
,
"%s
\n
"
,
line
);
err
!=
nil
{
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
err
clientDisconnected
=
true
log
.
Printf
(
"Client disconnected during streaming, continuing to drain upstream for billing"
)
}
else
{
flusher
.
Flush
()
}
}
}
flusher
.
Flush
()
// Record first token time
// Record first token time
if
firstTokenMs
==
nil
&&
data
!=
""
&&
data
!=
"[DONE]"
{
if
firstTokenMs
==
nil
&&
data
!=
""
&&
data
!=
"[DONE]"
{
...
@@ -1128,11 +1157,14 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
...
@@ -1128,11 +1157,14 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
s
.
parseSSEUsage
(
data
,
usage
)
s
.
parseSSEUsage
(
data
,
usage
)
}
else
{
}
else
{
// Forward non-data lines as-is
// Forward non-data lines as-is
if
_
,
err
:=
fmt
.
Fprintf
(
w
,
"%s
\n
"
,
line
);
err
!=
nil
{
if
!
clientDisconnected
{
sendErrorEvent
(
"write_failed"
)
if
_
,
err
:=
fmt
.
Fprintf
(
w
,
"%s
\n
"
,
line
);
err
!=
nil
{
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
err
clientDisconnected
=
true
log
.
Printf
(
"Client disconnected during streaming, continuing to drain upstream for billing"
)
}
else
{
flusher
.
Flush
()
}
}
}
flusher
.
Flush
()
}
}
case
<-
intervalCh
:
case
<-
intervalCh
:
...
@@ -1140,6 +1172,10 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
...
@@ -1140,6 +1172,10 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
if
time
.
Since
(
lastRead
)
<
streamInterval
{
if
time
.
Since
(
lastRead
)
<
streamInterval
{
continue
continue
}
}
if
clientDisconnected
{
log
.
Printf
(
"Upstream timeout after client disconnect, returning collected usage"
)
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
nil
}
log
.
Printf
(
"Stream data interval timeout: account=%d model=%s interval=%s"
,
account
.
ID
,
originalModel
,
streamInterval
)
log
.
Printf
(
"Stream data interval timeout: account=%d model=%s interval=%s"
,
account
.
ID
,
originalModel
,
streamInterval
)
// 处理流超时,可能标记账户为临时不可调度或错误状态
// 处理流超时,可能标记账户为临时不可调度或错误状态
if
s
.
rateLimitService
!=
nil
{
if
s
.
rateLimitService
!=
nil
{
...
@@ -1149,11 +1185,16 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
...
@@ -1149,11 +1185,16 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
fmt
.
Errorf
(
"stream data interval timeout"
)
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
fmt
.
Errorf
(
"stream data interval timeout"
)
case
<-
keepaliveCh
:
case
<-
keepaliveCh
:
if
clientDisconnected
{
continue
}
if
time
.
Since
(
lastDataAt
)
<
keepaliveInterval
{
if
time
.
Since
(
lastDataAt
)
<
keepaliveInterval
{
continue
continue
}
}
if
_
,
err
:=
fmt
.
Fprint
(
w
,
":
\n\n
"
);
err
!=
nil
{
if
_
,
err
:=
fmt
.
Fprint
(
w
,
":
\n\n
"
);
err
!=
nil
{
return
&
openaiStreamingResult
{
usage
:
usage
,
firstTokenMs
:
firstTokenMs
},
err
clientDisconnected
=
true
log
.
Printf
(
"Client disconnected during streaming, continuing to drain upstream for billing"
)
continue
}
}
flusher
.
Flush
()
flusher
.
Flush
()
}
}
...
...
backend/internal/service/openai_gateway_service_test.go
View file @
dd7f2124
...
@@ -33,6 +33,25 @@ type stubConcurrencyCache struct {
...
@@ -33,6 +33,25 @@ type stubConcurrencyCache struct {
ConcurrencyCache
ConcurrencyCache
}
}
type
cancelReadCloser
struct
{}
func
(
c
cancelReadCloser
)
Read
(
p
[]
byte
)
(
int
,
error
)
{
return
0
,
context
.
Canceled
}
func
(
c
cancelReadCloser
)
Close
()
error
{
return
nil
}
type
failingGinWriter
struct
{
gin
.
ResponseWriter
failAfter
int
writes
int
}
func
(
w
*
failingGinWriter
)
Write
(
p
[]
byte
)
(
int
,
error
)
{
if
w
.
writes
>=
w
.
failAfter
{
return
0
,
errors
.
New
(
"write failed"
)
}
w
.
writes
++
return
w
.
ResponseWriter
.
Write
(
p
)
}
func
(
c
stubConcurrencyCache
)
AcquireAccountSlot
(
ctx
context
.
Context
,
accountID
int64
,
maxConcurrency
int
,
requestID
string
)
(
bool
,
error
)
{
func
(
c
stubConcurrencyCache
)
AcquireAccountSlot
(
ctx
context
.
Context
,
accountID
int64
,
maxConcurrency
int
,
requestID
string
)
(
bool
,
error
)
{
return
true
,
nil
return
true
,
nil
}
}
...
@@ -169,8 +188,85 @@ func TestOpenAIStreamingTimeout(t *testing.T) {
...
@@ -169,8 +188,85 @@ func TestOpenAIStreamingTimeout(t *testing.T) {
if
err
==
nil
||
!
strings
.
Contains
(
err
.
Error
(),
"stream data interval timeout"
)
{
if
err
==
nil
||
!
strings
.
Contains
(
err
.
Error
(),
"stream data interval timeout"
)
{
t
.
Fatalf
(
"expected stream timeout error, got %v"
,
err
)
t
.
Fatalf
(
"expected stream timeout error, got %v"
,
err
)
}
}
if
!
strings
.
Contains
(
rec
.
Body
.
String
(),
"stream_timeout"
)
{
if
!
strings
.
Contains
(
rec
.
Body
.
String
(),
"
\"
type
\"
:
\"
error
\"
"
)
||
!
strings
.
Contains
(
rec
.
Body
.
String
(),
"stream_timeout"
)
{
t
.
Fatalf
(
"expected stream_timeout SSE error, got %q"
,
rec
.
Body
.
String
())
t
.
Fatalf
(
"expected OpenAI-compatible error SSE event, got %q"
,
rec
.
Body
.
String
())
}
}
func
TestOpenAIStreamingContextCanceledDoesNotInjectErrorEvent
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
cfg
:=
&
config
.
Config
{
Gateway
:
config
.
GatewayConfig
{
StreamDataIntervalTimeout
:
0
,
StreamKeepaliveInterval
:
0
,
MaxLineSize
:
defaultMaxLineSize
,
},
}
svc
:=
&
OpenAIGatewayService
{
cfg
:
cfg
}
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
ctx
,
cancel
:=
context
.
WithCancel
(
context
.
Background
())
cancel
()
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/"
,
nil
)
.
WithContext
(
ctx
)
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
Body
:
cancelReadCloser
{},
Header
:
http
.
Header
{},
}
_
,
err
:=
svc
.
handleStreamingResponse
(
c
.
Request
.
Context
(),
resp
,
c
,
&
Account
{
ID
:
1
},
time
.
Now
(),
"model"
,
"model"
)
if
err
!=
nil
{
t
.
Fatalf
(
"expected nil error, got %v"
,
err
)
}
if
strings
.
Contains
(
rec
.
Body
.
String
(),
"event: error"
)
||
strings
.
Contains
(
rec
.
Body
.
String
(),
"stream_read_error"
)
{
t
.
Fatalf
(
"expected no injected SSE error event, got %q"
,
rec
.
Body
.
String
())
}
}
func
TestOpenAIStreamingClientDisconnectDrainsUpstreamUsage
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
cfg
:=
&
config
.
Config
{
Gateway
:
config
.
GatewayConfig
{
StreamDataIntervalTimeout
:
0
,
StreamKeepaliveInterval
:
0
,
MaxLineSize
:
defaultMaxLineSize
,
},
}
svc
:=
&
OpenAIGatewayService
{
cfg
:
cfg
}
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/"
,
nil
)
c
.
Writer
=
&
failingGinWriter
{
ResponseWriter
:
c
.
Writer
,
failAfter
:
0
}
pr
,
pw
:=
io
.
Pipe
()
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
Body
:
pr
,
Header
:
http
.
Header
{},
}
go
func
()
{
defer
func
()
{
_
=
pw
.
Close
()
}()
_
,
_
=
pw
.
Write
([]
byte
(
"data: {
\"
type
\"
:
\"
response.in_progress
\"
,
\"
response
\"
:{}}
\n\n
"
))
_
,
_
=
pw
.
Write
([]
byte
(
"data: {
\"
type
\"
:
\"
response.completed
\"
,
\"
response
\"
:{
\"
usage
\"
:{
\"
input_tokens
\"
:3,
\"
output_tokens
\"
:5,
\"
input_tokens_details
\"
:{
\"
cached_tokens
\"
:1}}}}
\n\n
"
))
}()
result
,
err
:=
svc
.
handleStreamingResponse
(
c
.
Request
.
Context
(),
resp
,
c
,
&
Account
{
ID
:
1
},
time
.
Now
(),
"model"
,
"model"
)
_
=
pr
.
Close
()
if
err
!=
nil
{
t
.
Fatalf
(
"expected nil error, got %v"
,
err
)
}
if
result
==
nil
||
result
.
usage
==
nil
{
t
.
Fatalf
(
"expected usage result"
)
}
if
result
.
usage
.
InputTokens
!=
3
||
result
.
usage
.
OutputTokens
!=
5
||
result
.
usage
.
CacheReadInputTokens
!=
1
{
t
.
Fatalf
(
"unexpected usage: %+v"
,
*
result
.
usage
)
}
if
strings
.
Contains
(
rec
.
Body
.
String
(),
"event: error"
)
||
strings
.
Contains
(
rec
.
Body
.
String
(),
"write_failed"
)
{
t
.
Fatalf
(
"expected no injected SSE error event, got %q"
,
rec
.
Body
.
String
())
}
}
}
}
...
@@ -209,8 +305,8 @@ func TestOpenAIStreamingTooLong(t *testing.T) {
...
@@ -209,8 +305,8 @@ func TestOpenAIStreamingTooLong(t *testing.T) {
if
!
errors
.
Is
(
err
,
bufio
.
ErrTooLong
)
{
if
!
errors
.
Is
(
err
,
bufio
.
ErrTooLong
)
{
t
.
Fatalf
(
"expected ErrTooLong, got %v"
,
err
)
t
.
Fatalf
(
"expected ErrTooLong, got %v"
,
err
)
}
}
if
!
strings
.
Contains
(
rec
.
Body
.
String
(),
"response_too_large"
)
{
if
!
strings
.
Contains
(
rec
.
Body
.
String
(),
"
\"
type
\"
:
\"
error
\"
"
)
||
!
strings
.
Contains
(
rec
.
Body
.
String
(),
"response_too_large"
)
{
t
.
Fatalf
(
"expected
response_too_large
SSE e
rror
, got %q"
,
rec
.
Body
.
String
())
t
.
Fatalf
(
"expected
OpenAI-compatible error
SSE e
vent
, got %q"
,
rec
.
Body
.
String
())
}
}
}
}
...
...
backend/internal/service/ops_retry.go
View file @
dd7f2124
...
@@ -514,7 +514,7 @@ func (s *OpsService) selectAccountForRetry(ctx context.Context, reqType opsRetry
...
@@ -514,7 +514,7 @@ func (s *OpsService) selectAccountForRetry(ctx context.Context, reqType opsRetry
if
s
.
gatewayService
==
nil
{
if
s
.
gatewayService
==
nil
{
return
nil
,
fmt
.
Errorf
(
"gateway service not available"
)
return
nil
,
fmt
.
Errorf
(
"gateway service not available"
)
}
}
return
s
.
gatewayService
.
SelectAccountWithLoadAwareness
(
ctx
,
groupID
,
""
,
model
,
excludedIDs
)
return
s
.
gatewayService
.
SelectAccountWithLoadAwareness
(
ctx
,
groupID
,
""
,
model
,
excludedIDs
,
""
)
// 重试不使用会话限制
default
:
default
:
return
nil
,
fmt
.
Errorf
(
"unsupported retry type: %s"
,
reqType
)
return
nil
,
fmt
.
Errorf
(
"unsupported retry type: %s"
,
reqType
)
}
}
...
...
backend/internal/service/session_limit_cache.go
0 → 100644
View file @
dd7f2124
package
service
import
(
"context"
"time"
)
// SessionLimitCache 管理账号级别的活跃会话跟踪
// 用于 Anthropic OAuth/SetupToken 账号的会话数量限制
//
// Key 格式: session_limit:account:{accountID}
// 数据结构: Sorted Set (member=sessionUUID, score=timestamp)
//
// 会话在空闲超时后自动过期,无需手动清理
type
SessionLimitCache
interface
{
// RegisterSession 注册会话活动
// - 如果会话已存在,刷新其时间戳并返回 true
// - 如果会话不存在且活跃会话数 < maxSessions,添加新会话并返回 true
// - 如果会话不存在且活跃会话数 >= maxSessions,返回 false(拒绝)
//
// 参数:
// accountID: 账号 ID
// sessionUUID: 从 metadata.user_id 中提取的会话 UUID
// maxSessions: 最大并发会话数限制
// idleTimeout: 会话空闲超时时间
//
// 返回:
// allowed: true 表示允许(在限制内或会话已存在),false 表示拒绝(超出限制且是新会话)
// error: 操作错误
RegisterSession
(
ctx
context
.
Context
,
accountID
int64
,
sessionUUID
string
,
maxSessions
int
,
idleTimeout
time
.
Duration
)
(
allowed
bool
,
err
error
)
// RefreshSession 刷新现有会话的时间戳
// 用于活跃会话保持活动状态
RefreshSession
(
ctx
context
.
Context
,
accountID
int64
,
sessionUUID
string
,
idleTimeout
time
.
Duration
)
error
// GetActiveSessionCount 获取当前活跃会话数
// 返回未过期的会话数量
GetActiveSessionCount
(
ctx
context
.
Context
,
accountID
int64
)
(
int
,
error
)
// GetActiveSessionCountBatch 批量获取多个账号的活跃会话数
// 返回 map[accountID]count,查询失败的账号不在 map 中
GetActiveSessionCountBatch
(
ctx
context
.
Context
,
accountIDs
[]
int64
)
(
map
[
int64
]
int
,
error
)
// IsSessionActive 检查特定会话是否活跃(未过期)
IsSessionActive
(
ctx
context
.
Context
,
accountID
int64
,
sessionUUID
string
)
(
bool
,
error
)
// ========== 5h窗口费用缓存 ==========
// Key 格式: window_cost:account:{accountID}
// 用于缓存账号在当前5h窗口内的标准费用,减少数据库聚合查询压力
// GetWindowCost 获取缓存的窗口费用
// 返回 (cost, true, nil) 如果缓存命中
// 返回 (0, false, nil) 如果缓存未命中
// 返回 (0, false, err) 如果发生错误
GetWindowCost
(
ctx
context
.
Context
,
accountID
int64
)
(
cost
float64
,
hit
bool
,
err
error
)
// SetWindowCost 设置窗口费用缓存
SetWindowCost
(
ctx
context
.
Context
,
accountID
int64
,
cost
float64
)
error
// GetWindowCostBatch 批量获取窗口费用缓存
// 返回 map[accountID]cost,缓存未命中的账号不在 map 中
GetWindowCostBatch
(
ctx
context
.
Context
,
accountIDs
[]
int64
)
(
map
[
int64
]
float64
,
error
)
}
deploy/Caddyfile
View file @
dd7f2124
...
@@ -33,6 +33,22 @@
...
@@ -33,6 +33,22 @@
# 修改为你的域名
# 修改为你的域名
example.com {
example.com {
# =========================================================================
# 静态资源长期缓存(高优先级,放在最前面)
# 带 hash 的文件可以永久缓存,浏览器和 CDN 都会缓存
# =========================================================================
@static {
path /assets/*
path /logo.png
path /favicon.ico
}
header @static {
Cache-Control "public, max-age=31536000, immutable"
# 移除可能干扰缓存的头
-Pragma
-Expires
}
# =========================================================================
# =========================================================================
# TLS 安全配置
# TLS 安全配置
# =========================================================================
# =========================================================================
...
...
frontend/package-lock.json
View file @
dd7f2124
...
@@ -28,14 +28,18 @@
...
@@ -28,14 +28,18 @@
"@typescript-eslint/eslint-plugin"
:
"^7.18.0"
,
"@typescript-eslint/eslint-plugin"
:
"^7.18.0"
,
"@typescript-eslint/parser"
:
"^7.18.0"
,
"@typescript-eslint/parser"
:
"^7.18.0"
,
"@vitejs/plugin-vue"
:
"^5.2.3"
,
"@vitejs/plugin-vue"
:
"^5.2.3"
,
"@vitest/coverage-v8"
:
"^2.1.9"
,
"@vue/test-utils"
:
"^2.4.6"
,
"autoprefixer"
:
"^10.4.16"
,
"autoprefixer"
:
"^10.4.16"
,
"eslint"
:
"^8.57.0"
,
"eslint"
:
"^8.57.0"
,
"eslint-plugin-vue"
:
"^9.25.0"
,
"eslint-plugin-vue"
:
"^9.25.0"
,
"jsdom"
:
"^24.1.3"
,
"postcss"
:
"^8.4.32"
,
"postcss"
:
"^8.4.32"
,
"tailwindcss"
:
"^3.4.0"
,
"tailwindcss"
:
"^3.4.0"
,
"typescript"
:
"~5.6.0"
,
"typescript"
:
"~5.6.0"
,
"vite"
:
"^5.0.10"
,
"vite"
:
"^5.0.10"
,
"vite-plugin-checker"
:
"^0.9.1"
,
"vite-plugin-checker"
:
"^0.9.1"
,
"vitest"
:
"^2.1.9"
,
"vue-tsc"
:
"^2.2.0"
"vue-tsc"
:
"^2.2.0"
}
}
},
},
...
@@ -52,6 +56,20 @@
...
@@ -52,6 +56,20 @@
"url"
:
"https://github.com/sponsors/sindresorhus"
"url"
:
"https://github.com/sponsors/sindresorhus"
}
}
},
},
"node_modules/@ampproject/remapping"
:
{
"version"
:
"2.3.0"
,
"resolved"
:
"https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz"
,
"integrity"
:
"sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="
,
"dev"
:
true
,
"license"
:
"Apache-2.0"
,
"dependencies"
:
{
"@jridgewell/gen-mapping"
:
"^0.3.5"
,
"@jridgewell/trace-mapping"
:
"^0.3.24"
},
"engines"
:
{
"node"
:
">=6.0.0"
}
},
"node_modules/@ant-design/cssinjs"
:
{
"node_modules/@ant-design/cssinjs"
:
{
"version"
:
"2.0.2"
,
"version"
:
"2.0.2"
,
"resolved"
:
"https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.0.2.tgz"
,
...
@@ -71,6 +89,20 @@
...
@@ -71,6 +89,20 @@
"react-dom"
:
">=16.0.0"
"react-dom"
:
">=16.0.0"
}
}
},
},
"node_modules/@asamuzakjp/css-color"
:
{
"version"
:
"3.2.0"
,
"resolved"
:
"https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz"
,
"integrity"
:
"sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@csstools/css-calc"
:
"^2.1.3"
,
"@csstools/css-color-parser"
:
"^3.0.9"
,
"@csstools/css-parser-algorithms"
:
"^3.0.4"
,
"@csstools/css-tokenizer"
:
"^3.0.3"
,
"lru-cache"
:
"^10.4.3"
}
},
"node_modules/@babel/code-frame"
:
{
"node_modules/@babel/code-frame"
:
{
"version"
:
"7.27.1"
,
"version"
:
"7.27.1"
,
"resolved"
:
"https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
,
...
@@ -210,6 +242,128 @@
...
@@ -210,6 +242,128 @@
"node"
:
">=6.9.0"
"node"
:
">=6.9.0"
}
}
},
},
"node_modules/@bcoe/v8-coverage"
:
{
"version"
:
"0.2.3"
,
"resolved"
:
"https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
,
"integrity"
:
"sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/@csstools/color-helpers"
:
{
"version"
:
"5.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz"
,
"integrity"
:
"sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="
,
"dev"
:
true
,
"funding"
:
[
{
"type"
:
"github"
,
"url"
:
"https://github.com/sponsors/csstools"
},
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/csstools"
}
],
"license"
:
"MIT-0"
,
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/@csstools/css-calc"
:
{
"version"
:
"2.1.4"
,
"resolved"
:
"https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz"
,
"integrity"
:
"sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="
,
"dev"
:
true
,
"funding"
:
[
{
"type"
:
"github"
,
"url"
:
"https://github.com/sponsors/csstools"
},
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/csstools"
}
],
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@csstools/css-parser-algorithms"
:
"^3.0.5"
,
"@csstools/css-tokenizer"
:
"^3.0.4"
}
},
"node_modules/@csstools/css-color-parser"
:
{
"version"
:
"3.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz"
,
"integrity"
:
"sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA=="
,
"dev"
:
true
,
"funding"
:
[
{
"type"
:
"github"
,
"url"
:
"https://github.com/sponsors/csstools"
},
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/csstools"
}
],
"license"
:
"MIT"
,
"dependencies"
:
{
"@csstools/color-helpers"
:
"^5.1.0"
,
"@csstools/css-calc"
:
"^2.1.4"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@csstools/css-parser-algorithms"
:
"^3.0.5"
,
"@csstools/css-tokenizer"
:
"^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms"
:
{
"version"
:
"3.0.5"
,
"resolved"
:
"https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz"
,
"integrity"
:
"sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="
,
"dev"
:
true
,
"funding"
:
[
{
"type"
:
"github"
,
"url"
:
"https://github.com/sponsors/csstools"
},
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/csstools"
}
],
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@csstools/css-tokenizer"
:
"^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer"
:
{
"version"
:
"3.0.4"
,
"resolved"
:
"https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz"
,
"integrity"
:
"sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="
,
"dev"
:
true
,
"funding"
:
[
{
"type"
:
"github"
,
"url"
:
"https://github.com/sponsors/csstools"
},
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/csstools"
}
],
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/@emotion/babel-plugin"
:
{
"node_modules/@emotion/babel-plugin"
:
{
"version"
:
"11.13.5"
,
"version"
:
"11.13.5"
,
"resolved"
:
"https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz"
,
...
@@ -951,6 +1105,63 @@
...
@@ -951,6 +1105,63 @@
"url"
:
"https://github.com/sponsors/kazupon"
"url"
:
"https://github.com/sponsors/kazupon"
}
}
},
},
"node_modules/@isaacs/cliui"
:
{
"version"
:
"8.0.2"
,
"resolved"
:
"https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
,
"integrity"
:
"sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"string-width"
:
"^5.1.2"
,
"string-width-cjs"
:
"npm:string-width@^4.2.0"
,
"strip-ansi"
:
"^7.0.1"
,
"strip-ansi-cjs"
:
"npm:strip-ansi@^6.0.1"
,
"wrap-ansi"
:
"^8.1.0"
,
"wrap-ansi-cjs"
:
"npm:wrap-ansi@^7.0.0"
},
"engines"
:
{
"node"
:
">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex"
:
{
"version"
:
"6.2.2"
,
"resolved"
:
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz"
,
"integrity"
:
"sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi"
:
{
"version"
:
"7.1.2"
,
"resolved"
:
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz"
,
"integrity"
:
"sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ansi-regex"
:
"^6.0.1"
},
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@istanbuljs/schema"
:
{
"version"
:
"0.1.3"
,
"resolved"
:
"https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz"
,
"integrity"
:
"sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/@jridgewell/gen-mapping"
:
{
"node_modules/@jridgewell/gen-mapping"
:
{
"version"
:
"0.3.13"
,
"version"
:
"0.3.13"
,
"resolved"
:
"https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz"
,
...
@@ -1050,6 +1261,24 @@
...
@@ -1050,6 +1261,24 @@
"node"
:
">= 8"
"node"
:
">= 8"
}
}
},
},
"node_modules/@one-ini/wasm"
:
{
"version"
:
"0.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz"
,
"integrity"
:
"sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/@pkgjs/parseargs"
:
{
"version"
:
"0.11.0"
,
"resolved"
:
"https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
,
"integrity"
:
"sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"engines"
:
{
"node"
:
">=14"
}
},
"node_modules/@rc-component/util"
:
{
"node_modules/@rc-component/util"
:
{
"version"
:
"1.7.0"
,
"version"
:
"1.7.0"
,
"resolved"
:
"https://registry.npmjs.org/@rc-component/util/-/util-1.7.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rc-component/util/-/util-1.7.0.tgz"
,
...
@@ -1671,6 +1900,162 @@
...
@@ -1671,6 +1900,162 @@
"vue"
:
"^3.2.25"
"vue"
:
"^3.2.25"
}
}
},
},
"node_modules/@vitest/coverage-v8"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz"
,
"integrity"
:
"sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@ampproject/remapping"
:
"^2.3.0"
,
"@bcoe/v8-coverage"
:
"^0.2.3"
,
"debug"
:
"^4.3.7"
,
"istanbul-lib-coverage"
:
"^3.2.2"
,
"istanbul-lib-report"
:
"^3.0.1"
,
"istanbul-lib-source-maps"
:
"^5.0.6"
,
"istanbul-reports"
:
"^3.1.7"
,
"magic-string"
:
"^0.30.12"
,
"magicast"
:
"^0.3.5"
,
"std-env"
:
"^3.8.0"
,
"test-exclude"
:
"^7.0.1"
,
"tinyrainbow"
:
"^1.2.0"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
},
"peerDependencies"
:
{
"@vitest/browser"
:
"2.1.9"
,
"vitest"
:
"2.1.9"
},
"peerDependenciesMeta"
:
{
"@vitest/browser"
:
{
"optional"
:
true
}
}
},
"node_modules/@vitest/expect"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz"
,
"integrity"
:
"sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@vitest/spy"
:
"2.1.9"
,
"@vitest/utils"
:
"2.1.9"
,
"chai"
:
"^5.1.2"
,
"tinyrainbow"
:
"^1.2.0"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz"
,
"integrity"
:
"sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@vitest/spy"
:
"2.1.9"
,
"estree-walker"
:
"^3.0.3"
,
"magic-string"
:
"^0.30.12"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
},
"peerDependencies"
:
{
"msw"
:
"^2.4.9"
,
"vite"
:
"^5.0.0"
},
"peerDependenciesMeta"
:
{
"msw"
:
{
"optional"
:
true
},
"vite"
:
{
"optional"
:
true
}
}
},
"node_modules/@vitest/mocker/node_modules/estree-walker"
:
{
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz"
,
"integrity"
:
"sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@types/estree"
:
"^1.0.0"
}
},
"node_modules/@vitest/pretty-format"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz"
,
"integrity"
:
"sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"tinyrainbow"
:
"^1.2.0"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz"
,
"integrity"
:
"sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@vitest/utils"
:
"2.1.9"
,
"pathe"
:
"^1.1.2"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz"
,
"integrity"
:
"sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@vitest/pretty-format"
:
"2.1.9"
,
"magic-string"
:
"^0.30.12"
,
"pathe"
:
"^1.1.2"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz"
,
"integrity"
:
"sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"tinyspy"
:
"^3.0.2"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz"
,
"integrity"
:
"sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@vitest/pretty-format"
:
"2.1.9"
,
"loupe"
:
"^3.1.2"
,
"tinyrainbow"
:
"^1.2.0"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/@volar/language-core"
:
{
"node_modules/@volar/language-core"
:
{
"version"
:
"2.4.15"
,
"version"
:
"2.4.15"
,
"resolved"
:
"https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz"
,
...
@@ -1842,6 +2227,17 @@
...
@@ -1842,6 +2227,17 @@
"integrity"
:
"sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="
,
"integrity"
:
"sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/@vue/test-utils"
:
{
"version"
:
"2.4.6"
,
"resolved"
:
"https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz"
,
"integrity"
:
"sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"js-beautify"
:
"^1.14.9"
,
"vue-component-type-helpers"
:
"^2.0.0"
}
},
"node_modules/@vueuse/core"
:
{
"node_modules/@vueuse/core"
:
{
"version"
:
"10.11.1"
,
"version"
:
"10.11.1"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz"
,
...
@@ -1878,6 +2274,16 @@
...
@@ -1878,6 +2274,16 @@
"url"
:
"https://github.com/sponsors/antfu"
"url"
:
"https://github.com/sponsors/antfu"
}
}
},
},
"node_modules/abbrev"
:
{
"version"
:
"2.0.0"
,
"resolved"
:
"https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz"
,
"integrity"
:
"sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"engines"
:
{
"node"
:
"^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/acorn"
:
{
"node_modules/acorn"
:
{
"version"
:
"8.15.0"
,
"version"
:
"8.15.0"
,
"resolved"
:
"https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
,
...
@@ -1910,6 +2316,16 @@
...
@@ -1910,6 +2316,16 @@
"node"
:
">=0.8"
"node"
:
">=0.8"
}
}
},
},
"node_modules/agent-base"
:
{
"version"
:
"7.1.4"
,
"resolved"
:
"https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
,
"integrity"
:
"sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">= 14"
}
},
"node_modules/ajv"
:
{
"node_modules/ajv"
:
{
"version"
:
"6.12.6"
,
"version"
:
"6.12.6"
,
"resolved"
:
"https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
,
"resolved"
:
"https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
,
...
@@ -2025,6 +2441,16 @@
...
@@ -2025,6 +2441,16 @@
"node"
:
">=8"
"node"
:
">=8"
}
}
},
},
"node_modules/assertion-error"
:
{
"version"
:
"2.0.1"
,
"resolved"
:
"https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz"
,
"integrity"
:
"sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=12"
}
},
"node_modules/asynckit"
:
{
"node_modules/asynckit"
:
{
"version"
:
"0.4.0"
,
"version"
:
"0.4.0"
,
"resolved"
:
"https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
,
...
@@ -2188,6 +2614,16 @@
...
@@ -2188,6 +2614,16 @@
"node"
:
"^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
"node"
:
"^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
}
},
},
"node_modules/cac"
:
{
"version"
:
"6.7.14"
,
"resolved"
:
"https://registry.npmjs.org/cac/-/cac-6.7.14.tgz"
,
"integrity"
:
"sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/call-bind-apply-helpers"
:
{
"node_modules/call-bind-apply-helpers"
:
{
"version"
:
"1.0.2"
,
"version"
:
"1.0.2"
,
"resolved"
:
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
,
...
@@ -2254,8 +2690,25 @@
...
@@ -2254,8 +2690,25 @@
"node"
:
">=0.8"
"node"
:
">=0.8"
}
}
},
},
"node_modules/chalk"
:
{
"node_modules/chai"
:
{
"version"
:
"4.1.2"
,
"version"
:
"5.3.3"
,
"resolved"
:
"https://registry.npmjs.org/chai/-/chai-5.3.3.tgz"
,
"integrity"
:
"sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"assertion-error"
:
"^2.0.1"
,
"check-error"
:
"^2.1.1"
,
"deep-eql"
:
"^5.0.1"
,
"loupe"
:
"^3.1.0"
,
"pathval"
:
"^2.0.0"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/chalk"
:
{
"version"
:
"4.1.2"
,
"resolved"
:
"https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
,
"integrity"
:
"sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="
,
"integrity"
:
"sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="
,
"dev"
:
true
,
"dev"
:
true
,
...
@@ -2283,6 +2736,16 @@
...
@@ -2283,6 +2736,16 @@
"pnpm"
:
">=8"
"pnpm"
:
">=8"
}
}
},
},
"node_modules/check-error"
:
{
"version"
:
"2.1.3"
,
"resolved"
:
"https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz"
,
"integrity"
:
"sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">= 16"
}
},
"node_modules/chokidar"
:
{
"node_modules/chokidar"
:
{
"version"
:
"3.6.0"
,
"version"
:
"3.6.0"
,
"resolved"
:
"https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
,
...
@@ -2388,6 +2851,17 @@
...
@@ -2388,6 +2851,17 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/config-chain"
:
{
"version"
:
"1.1.13"
,
"resolved"
:
"https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz"
,
"integrity"
:
"sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ini"
:
"^1.3.4"
,
"proto-list"
:
"~1.2.1"
}
},
"node_modules/convert-source-map"
:
{
"node_modules/convert-source-map"
:
{
"version"
:
"1.9.0"
,
"version"
:
"1.9.0"
,
"resolved"
:
"https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
,
...
@@ -2450,12 +2924,47 @@
...
@@ -2450,12 +2924,47 @@
"node"
:
">=4"
"node"
:
">=4"
}
}
},
},
"node_modules/cssstyle"
:
{
"version"
:
"4.6.0"
,
"resolved"
:
"https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz"
,
"integrity"
:
"sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@asamuzakjp/css-color"
:
"^3.2.0"
,
"rrweb-cssom"
:
"^0.8.0"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/cssstyle/node_modules/rrweb-cssom"
:
{
"version"
:
"0.8.0"
,
"resolved"
:
"https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz"
,
"integrity"
:
"sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/csstype"
:
{
"node_modules/csstype"
:
{
"version"
:
"3.2.3"
,
"version"
:
"3.2.3"
,
"resolved"
:
"https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
,
"integrity"
:
"sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
,
"integrity"
:
"sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/data-urls"
:
{
"version"
:
"5.0.0"
,
"resolved"
:
"https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz"
,
"integrity"
:
"sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"whatwg-mimetype"
:
"^4.0.0"
,
"whatwg-url"
:
"^14.0.0"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/de-indent"
:
{
"node_modules/de-indent"
:
{
"version"
:
"1.0.2"
,
"version"
:
"1.0.2"
,
"resolved"
:
"https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz"
,
...
@@ -2480,6 +2989,23 @@
...
@@ -2480,6 +2989,23 @@
}
}
}
}
},
},
"node_modules/decimal.js"
:
{
"version"
:
"10.6.0"
,
"resolved"
:
"https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz"
,
"integrity"
:
"sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/deep-eql"
:
{
"version"
:
"5.0.2"
,
"resolved"
:
"https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz"
,
"integrity"
:
"sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=6"
}
},
"node_modules/deep-is"
:
{
"node_modules/deep-is"
:
{
"version"
:
"0.1.4"
,
"version"
:
"0.1.4"
,
"resolved"
:
"https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
,
"resolved"
:
"https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
,
...
@@ -2556,6 +3082,58 @@
...
@@ -2556,6 +3082,58 @@
"node"
:
">= 0.4"
"node"
:
">= 0.4"
}
}
},
},
"node_modules/eastasianwidth"
:
{
"version"
:
"0.2.0"
,
"resolved"
:
"https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
,
"integrity"
:
"sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/editorconfig"
:
{
"version"
:
"1.0.4"
,
"resolved"
:
"https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz"
,
"integrity"
:
"sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@one-ini/wasm"
:
"0.1.1"
,
"commander"
:
"^10.0.0"
,
"minimatch"
:
"9.0.1"
,
"semver"
:
"^7.5.3"
},
"bin"
:
{
"editorconfig"
:
"bin/editorconfig"
},
"engines"
:
{
"node"
:
">=14"
}
},
"node_modules/editorconfig/node_modules/commander"
:
{
"version"
:
"10.0.1"
,
"resolved"
:
"https://registry.npmjs.org/commander/-/commander-10.0.1.tgz"
,
"integrity"
:
"sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=14"
}
},
"node_modules/editorconfig/node_modules/minimatch"
:
{
"version"
:
"9.0.1"
,
"resolved"
:
"https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz"
,
"integrity"
:
"sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"brace-expansion"
:
"^2.0.1"
},
"engines"
:
{
"node"
:
">=16 || 14 >=14.17"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
}
},
"node_modules/electron-to-chromium"
:
{
"node_modules/electron-to-chromium"
:
{
"version"
:
"1.5.267"
,
"version"
:
"1.5.267"
,
"resolved"
:
"https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz"
,
"resolved"
:
"https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz"
,
...
@@ -2563,6 +3141,13 @@
...
@@ -2563,6 +3141,13 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
"license"
:
"ISC"
},
},
"node_modules/emoji-regex"
:
{
"version"
:
"9.2.2"
,
"resolved"
:
"https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
,
"integrity"
:
"sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/entities"
:
{
"node_modules/entities"
:
{
"version"
:
"7.0.0"
,
"version"
:
"7.0.0"
,
"resolved"
:
"https://registry.npmjs.org/entities/-/entities-7.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/entities/-/entities-7.0.0.tgz"
,
...
@@ -2602,6 +3187,13 @@
...
@@ -2602,6 +3187,13 @@
"node"
:
">= 0.4"
"node"
:
">= 0.4"
}
}
},
},
"node_modules/es-module-lexer"
:
{
"version"
:
"1.7.0"
,
"resolved"
:
"https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
,
"integrity"
:
"sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/es-object-atoms"
:
{
"node_modules/es-object-atoms"
:
{
"version"
:
"1.1.1"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
,
...
@@ -2894,6 +3486,16 @@
...
@@ -2894,6 +3486,16 @@
"node"
:
">=0.10.0"
"node"
:
">=0.10.0"
}
}
},
},
"node_modules/expect-type"
:
{
"version"
:
"1.3.0"
,
"resolved"
:
"https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz"
,
"integrity"
:
"sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="
,
"dev"
:
true
,
"license"
:
"Apache-2.0"
,
"engines"
:
{
"node"
:
">=12.0.0"
}
},
"node_modules/fast-deep-equal"
:
{
"node_modules/fast-deep-equal"
:
{
"version"
:
"3.1.3"
,
"version"
:
"3.1.3"
,
"resolved"
:
"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
,
...
@@ -3070,6 +3672,23 @@
...
@@ -3070,6 +3672,23 @@
}
}
}
}
},
},
"node_modules/foreground-child"
:
{
"version"
:
"3.3.1"
,
"resolved"
:
"https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz"
,
"integrity"
:
"sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"cross-spawn"
:
"^7.0.6"
,
"signal-exit"
:
"^4.0.1"
},
"engines"
:
{
"node"
:
">=14"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data"
:
{
"node_modules/form-data"
:
{
"version"
:
"4.0.5"
,
"version"
:
"4.0.5"
,
"resolved"
:
"https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz"
,
"resolved"
:
"https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz"
,
...
@@ -3366,6 +3985,67 @@
...
@@ -3366,6 +3985,67 @@
"integrity"
:
"sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
,
"integrity"
:
"sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/html-encoding-sniffer"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz"
,
"integrity"
:
"sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"whatwg-encoding"
:
"^3.1.1"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/html-escaper"
:
{
"version"
:
"2.0.2"
,
"resolved"
:
"https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
,
"integrity"
:
"sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/http-proxy-agent"
:
{
"version"
:
"7.0.2"
,
"resolved"
:
"https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
,
"integrity"
:
"sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"agent-base"
:
"^7.1.0"
,
"debug"
:
"^4.3.4"
},
"engines"
:
{
"node"
:
">= 14"
}
},
"node_modules/https-proxy-agent"
:
{
"version"
:
"7.0.6"
,
"resolved"
:
"https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
,
"integrity"
:
"sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"agent-base"
:
"^7.1.2"
,
"debug"
:
"4"
},
"engines"
:
{
"node"
:
">= 14"
}
},
"node_modules/iconv-lite"
:
{
"version"
:
"0.6.3"
,
"resolved"
:
"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
,
"integrity"
:
"sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"safer-buffer"
:
">= 2.1.2 < 3.0.0"
},
"engines"
:
{
"node"
:
">=0.10.0"
}
},
"node_modules/ignore"
:
{
"node_modules/ignore"
:
{
"version"
:
"5.3.2"
,
"version"
:
"5.3.2"
,
"resolved"
:
"https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
,
...
@@ -3421,6 +4101,13 @@
...
@@ -3421,6 +4101,13 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
"license"
:
"ISC"
},
},
"node_modules/ini"
:
{
"version"
:
"1.3.8"
,
"resolved"
:
"https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
,
"integrity"
:
"sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
,
"dev"
:
true
,
"license"
:
"ISC"
},
"node_modules/is-arrayish"
:
{
"node_modules/is-arrayish"
:
{
"version"
:
"0.2.1"
,
"version"
:
"0.2.1"
,
"resolved"
:
"https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
,
...
@@ -3465,6 +4152,16 @@
...
@@ -3465,6 +4152,16 @@
"node"
:
">=0.10.0"
"node"
:
">=0.10.0"
}
}
},
},
"node_modules/is-fullwidth-code-point"
:
{
"version"
:
"3.0.0"
,
"resolved"
:
"https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
,
"integrity"
:
"sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/is-glob"
:
{
"node_modules/is-glob"
:
{
"version"
:
"4.0.3"
,
"version"
:
"4.0.3"
,
"resolved"
:
"https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
,
...
@@ -3504,6 +4201,13 @@
...
@@ -3504,6 +4201,13 @@
"node"
:
">=8"
"node"
:
">=8"
}
}
},
},
"node_modules/is-potential-custom-element-name"
:
{
"version"
:
"1.0.1"
,
"resolved"
:
"https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz"
,
"integrity"
:
"sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/isexe"
:
{
"node_modules/isexe"
:
{
"version"
:
"2.0.0"
,
"version"
:
"2.0.0"
,
"resolved"
:
"https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
,
...
@@ -3511,6 +4215,76 @@
...
@@ -3511,6 +4215,76 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
"license"
:
"ISC"
},
},
"node_modules/istanbul-lib-coverage"
:
{
"version"
:
"3.2.2"
,
"resolved"
:
"https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz"
,
"integrity"
:
"sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="
,
"dev"
:
true
,
"license"
:
"BSD-3-Clause"
,
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/istanbul-lib-report"
:
{
"version"
:
"3.0.1"
,
"resolved"
:
"https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz"
,
"integrity"
:
"sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="
,
"dev"
:
true
,
"license"
:
"BSD-3-Clause"
,
"dependencies"
:
{
"istanbul-lib-coverage"
:
"^3.0.0"
,
"make-dir"
:
"^4.0.0"
,
"supports-color"
:
"^7.1.0"
},
"engines"
:
{
"node"
:
">=10"
}
},
"node_modules/istanbul-lib-source-maps"
:
{
"version"
:
"5.0.6"
,
"resolved"
:
"https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz"
,
"integrity"
:
"sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="
,
"dev"
:
true
,
"license"
:
"BSD-3-Clause"
,
"dependencies"
:
{
"@jridgewell/trace-mapping"
:
"^0.3.23"
,
"debug"
:
"^4.1.1"
,
"istanbul-lib-coverage"
:
"^3.0.0"
},
"engines"
:
{
"node"
:
">=10"
}
},
"node_modules/istanbul-reports"
:
{
"version"
:
"3.2.0"
,
"resolved"
:
"https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz"
,
"integrity"
:
"sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="
,
"dev"
:
true
,
"license"
:
"BSD-3-Clause"
,
"dependencies"
:
{
"html-escaper"
:
"^2.0.0"
,
"istanbul-lib-report"
:
"^3.0.0"
},
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/jackspeak"
:
{
"version"
:
"3.4.3"
,
"resolved"
:
"https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz"
,
"integrity"
:
"sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"dependencies"
:
{
"@isaacs/cliui"
:
"^8.0.2"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
},
"optionalDependencies"
:
{
"@pkgjs/parseargs"
:
"^0.11.0"
}
},
"node_modules/jiti"
:
{
"node_modules/jiti"
:
{
"version"
:
"1.21.7"
,
"version"
:
"1.21.7"
,
"resolved"
:
"https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz"
,
"resolved"
:
"https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz"
,
...
@@ -3521,6 +4295,59 @@
...
@@ -3521,6 +4295,59 @@
"jiti"
:
"bin/jiti.js"
"jiti"
:
"bin/jiti.js"
}
}
},
},
"node_modules/js-beautify"
:
{
"version"
:
"1.15.4"
,
"resolved"
:
"https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz"
,
"integrity"
:
"sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"config-chain"
:
"^1.1.13"
,
"editorconfig"
:
"^1.0.4"
,
"glob"
:
"^10.4.2"
,
"js-cookie"
:
"^3.0.5"
,
"nopt"
:
"^7.2.1"
},
"bin"
:
{
"css-beautify"
:
"js/bin/css-beautify.js"
,
"html-beautify"
:
"js/bin/html-beautify.js"
,
"js-beautify"
:
"js/bin/js-beautify.js"
},
"engines"
:
{
"node"
:
">=14"
}
},
"node_modules/js-beautify/node_modules/glob"
:
{
"version"
:
"10.5.0"
,
"resolved"
:
"https://registry.npmjs.org/glob/-/glob-10.5.0.tgz"
,
"integrity"
:
"sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"foreground-child"
:
"^3.1.0"
,
"jackspeak"
:
"^3.1.2"
,
"minimatch"
:
"^9.0.4"
,
"minipass"
:
"^7.1.2"
,
"package-json-from-dist"
:
"^1.0.0"
,
"path-scurry"
:
"^1.11.1"
},
"bin"
:
{
"glob"
:
"dist/esm/bin.mjs"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
}
},
"node_modules/js-cookie"
:
{
"version"
:
"3.0.5"
,
"resolved"
:
"https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz"
,
"integrity"
:
"sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=14"
}
},
"node_modules/js-tokens"
:
{
"node_modules/js-tokens"
:
{
"version"
:
"4.0.0"
,
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
,
...
@@ -3540,6 +4367,57 @@
...
@@ -3540,6 +4367,57 @@
"js-yaml"
:
"bin/js-yaml.js"
"js-yaml"
:
"bin/js-yaml.js"
}
}
},
},
"node_modules/jsdom"
:
{
"version"
:
"24.1.3"
,
"resolved"
:
"https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz"
,
"integrity"
:
"sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"cssstyle"
:
"^4.0.1"
,
"data-urls"
:
"^5.0.0"
,
"decimal.js"
:
"^10.4.3"
,
"form-data"
:
"^4.0.0"
,
"html-encoding-sniffer"
:
"^4.0.0"
,
"http-proxy-agent"
:
"^7.0.2"
,
"https-proxy-agent"
:
"^7.0.5"
,
"is-potential-custom-element-name"
:
"^1.0.1"
,
"nwsapi"
:
"^2.2.12"
,
"parse5"
:
"^7.1.2"
,
"rrweb-cssom"
:
"^0.7.1"
,
"saxes"
:
"^6.0.0"
,
"symbol-tree"
:
"^3.2.4"
,
"tough-cookie"
:
"^4.1.4"
,
"w3c-xmlserializer"
:
"^5.0.0"
,
"webidl-conversions"
:
"^7.0.0"
,
"whatwg-encoding"
:
"^3.1.1"
,
"whatwg-mimetype"
:
"^4.0.0"
,
"whatwg-url"
:
"^14.0.0"
,
"ws"
:
"^8.18.0"
,
"xml-name-validator"
:
"^5.0.0"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"canvas"
:
"^2.11.2"
},
"peerDependenciesMeta"
:
{
"canvas"
:
{
"optional"
:
true
}
}
},
"node_modules/jsdom/node_modules/xml-name-validator"
:
{
"version"
:
"5.0.0"
,
"resolved"
:
"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz"
,
"integrity"
:
"sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="
,
"dev"
:
true
,
"license"
:
"Apache-2.0"
,
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/jsesc"
:
{
"node_modules/jsesc"
:
{
"version"
:
"3.1.0"
,
"version"
:
"3.1.0"
,
"resolved"
:
"https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz"
,
...
@@ -3652,11 +4530,25 @@
...
@@ -3652,11 +4530,25 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/lucide-react"
:
{
"node_modules/loupe"
:
{
"version"
:
"0.469.0"
,
"version"
:
"3.2.1"
,
"resolved"
:
"https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz"
,
"integrity"
:
"sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="
,
"integrity"
:
"sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="
,
"license"
:
"ISC"
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/lru-cache"
:
{
"version"
:
"10.4.3"
,
"resolved"
:
"https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
,
"integrity"
:
"sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
,
"dev"
:
true
,
"license"
:
"ISC"
},
"node_modules/lucide-react"
:
{
"version"
:
"0.469.0"
,
"resolved"
:
"https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz"
,
"integrity"
:
"sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="
,
"license"
:
"ISC"
,
"peerDependencies"
:
{
"peerDependencies"
:
{
"react"
:
"^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
"react"
:
"^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
...
@@ -3670,6 +4562,34 @@
...
@@ -3670,6 +4562,34 @@
"@jridgewell/sourcemap-codec"
:
"^1.5.5"
"@jridgewell/sourcemap-codec"
:
"^1.5.5"
}
}
},
},
"node_modules/magicast"
:
{
"version"
:
"0.3.5"
,
"resolved"
:
"https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz"
,
"integrity"
:
"sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@babel/parser"
:
"^7.25.4"
,
"@babel/types"
:
"^7.25.4"
,
"source-map-js"
:
"^1.2.0"
}
},
"node_modules/make-dir"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz"
,
"integrity"
:
"sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"semver"
:
"^7.5.3"
},
"engines"
:
{
"node"
:
">=10"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/sindresorhus"
}
},
"node_modules/math-intrinsics"
:
{
"node_modules/math-intrinsics"
:
{
"version"
:
"1.1.0"
,
"version"
:
"1.1.0"
,
"resolved"
:
"https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
,
...
@@ -3740,6 +4660,16 @@
...
@@ -3740,6 +4660,16 @@
"url"
:
"https://github.com/sponsors/isaacs"
"url"
:
"https://github.com/sponsors/isaacs"
}
}
},
},
"node_modules/minipass"
:
{
"version"
:
"7.1.2"
,
"resolved"
:
"https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
,
"integrity"
:
"sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"engines"
:
{
"node"
:
">=16 || 14 >=14.17"
}
},
"node_modules/ms"
:
{
"node_modules/ms"
:
{
"version"
:
"2.1.3"
,
"version"
:
"2.1.3"
,
"resolved"
:
"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
,
...
@@ -3797,6 +4727,22 @@
...
@@ -3797,6 +4727,22 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/nopt"
:
{
"version"
:
"7.2.1"
,
"resolved"
:
"https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz"
,
"integrity"
:
"sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"abbrev"
:
"^2.0.0"
},
"bin"
:
{
"nopt"
:
"bin/nopt.js"
},
"engines"
:
{
"node"
:
"^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/normalize-path"
:
{
"node_modules/normalize-path"
:
{
"version"
:
"3.0.0"
,
"version"
:
"3.0.0"
,
"resolved"
:
"https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
,
...
@@ -3850,6 +4796,13 @@
...
@@ -3850,6 +4796,13 @@
"url"
:
"https://github.com/fb55/nth-check?sponsor=1"
"url"
:
"https://github.com/fb55/nth-check?sponsor=1"
}
}
},
},
"node_modules/nwsapi"
:
{
"version"
:
"2.2.23"
,
"resolved"
:
"https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz"
,
"integrity"
:
"sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/object-assign"
:
{
"node_modules/object-assign"
:
{
"version"
:
"4.1.1"
,
"version"
:
"4.1.1"
,
"resolved"
:
"https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
,
...
@@ -3930,6 +4883,13 @@
...
@@ -3930,6 +4883,13 @@
"url"
:
"https://github.com/sponsors/sindresorhus"
"url"
:
"https://github.com/sponsors/sindresorhus"
}
}
},
},
"node_modules/package-json-from-dist"
:
{
"version"
:
"1.0.1"
,
"resolved"
:
"https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz"
,
"integrity"
:
"sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
},
"node_modules/parent-module"
:
{
"node_modules/parent-module"
:
{
"version"
:
"1.0.1"
,
"version"
:
"1.0.1"
,
"resolved"
:
"https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
,
...
@@ -3960,6 +4920,32 @@
...
@@ -3960,6 +4920,32 @@
"url"
:
"https://github.com/sponsors/sindresorhus"
"url"
:
"https://github.com/sponsors/sindresorhus"
}
}
},
},
"node_modules/parse5"
:
{
"version"
:
"7.3.0"
,
"resolved"
:
"https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz"
,
"integrity"
:
"sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"entities"
:
"^6.0.0"
},
"funding"
:
{
"url"
:
"https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities"
:
{
"version"
:
"6.0.1"
,
"resolved"
:
"https://registry.npmjs.org/entities/-/entities-6.0.1.tgz"
,
"integrity"
:
"sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="
,
"dev"
:
true
,
"license"
:
"BSD-2-Clause"
,
"engines"
:
{
"node"
:
">=0.12"
},
"funding"
:
{
"url"
:
"https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/path-browserify"
:
{
"node_modules/path-browserify"
:
{
"version"
:
"1.0.1"
,
"version"
:
"1.0.1"
,
"resolved"
:
"https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz"
,
...
@@ -4003,6 +4989,23 @@
...
@@ -4003,6 +4989,23 @@
"integrity"
:
"sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
,
"integrity"
:
"sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/path-scurry"
:
{
"version"
:
"1.11.1"
,
"resolved"
:
"https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz"
,
"integrity"
:
"sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"dependencies"
:
{
"lru-cache"
:
"^10.2.0"
,
"minipass"
:
"^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines"
:
{
"node"
:
">=16 || 14 >=14.18"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
}
},
"node_modules/path-type"
:
{
"node_modules/path-type"
:
{
"version"
:
"4.0.0"
,
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
,
...
@@ -4012,6 +5015,23 @@
...
@@ -4012,6 +5015,23 @@
"node"
:
">=8"
"node"
:
">=8"
}
}
},
},
"node_modules/pathe"
:
{
"version"
:
"1.1.2"
,
"resolved"
:
"https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz"
,
"integrity"
:
"sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/pathval"
:
{
"version"
:
"2.0.1"
,
"resolved"
:
"https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz"
,
"integrity"
:
"sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">= 14.16"
}
},
"node_modules/picocolors"
:
{
"node_modules/picocolors"
:
{
"version"
:
"1.1.1"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
,
...
@@ -4257,12 +5277,32 @@
...
@@ -4257,12 +5277,32 @@
"node"
:
">= 0.8.0"
"node"
:
">= 0.8.0"
}
}
},
},
"node_modules/proto-list"
:
{
"version"
:
"1.2.4"
,
"resolved"
:
"https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz"
,
"integrity"
:
"sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="
,
"dev"
:
true
,
"license"
:
"ISC"
},
"node_modules/proxy-from-env"
:
{
"node_modules/proxy-from-env"
:
{
"version"
:
"1.1.0"
,
"version"
:
"1.1.0"
,
"resolved"
:
"https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
,
"integrity"
:
"sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
,
"integrity"
:
"sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/psl"
:
{
"version"
:
"1.15.0"
,
"resolved"
:
"https://registry.npmjs.org/psl/-/psl-1.15.0.tgz"
,
"integrity"
:
"sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"punycode"
:
"^2.3.1"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/lupomontero"
}
},
"node_modules/punycode"
:
{
"node_modules/punycode"
:
{
"version"
:
"2.3.1"
,
"version"
:
"2.3.1"
,
"resolved"
:
"https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
,
...
@@ -4273,6 +5313,13 @@
...
@@ -4273,6 +5313,13 @@
"node"
:
">=6"
"node"
:
">=6"
}
}
},
},
"node_modules/querystringify"
:
{
"version"
:
"2.2.0"
,
"resolved"
:
"https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz"
,
"integrity"
:
"sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/queue-microtask"
:
{
"node_modules/queue-microtask"
:
{
"version"
:
"1.2.3"
,
"version"
:
"1.2.3"
,
"resolved"
:
"https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
,
...
@@ -4323,6 +5370,13 @@
...
@@ -4323,6 +5370,13 @@
"node"
:
">=8.10.0"
"node"
:
">=8.10.0"
}
}
},
},
"node_modules/requires-port"
:
{
"version"
:
"1.0.0"
,
"resolved"
:
"https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
,
"integrity"
:
"sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/resolve"
:
{
"node_modules/resolve"
:
{
"version"
:
"1.22.11"
,
"version"
:
"1.22.11"
,
"resolved"
:
"https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz"
,
"resolved"
:
"https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz"
,
...
@@ -4425,6 +5479,13 @@
...
@@ -4425,6 +5479,13 @@
"fsevents"
:
"~2.3.2"
"fsevents"
:
"~2.3.2"
}
}
},
},
"node_modules/rrweb-cssom"
:
{
"version"
:
"0.7.1"
,
"resolved"
:
"https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz"
,
"integrity"
:
"sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/run-parallel"
:
{
"node_modules/run-parallel"
:
{
"version"
:
"1.2.0"
,
"version"
:
"1.2.0"
,
"resolved"
:
"https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
,
...
@@ -4449,6 +5510,26 @@
...
@@ -4449,6 +5510,26 @@
"queue-microtask"
:
"^1.2.2"
"queue-microtask"
:
"^1.2.2"
}
}
},
},
"node_modules/safer-buffer"
:
{
"version"
:
"2.1.2"
,
"resolved"
:
"https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
,
"integrity"
:
"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/saxes"
:
{
"version"
:
"6.0.0"
,
"resolved"
:
"https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz"
,
"integrity"
:
"sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"xmlchars"
:
"^2.2.0"
},
"engines"
:
{
"node"
:
">=v12.22.7"
}
},
"node_modules/semver"
:
{
"node_modules/semver"
:
{
"version"
:
"7.7.3"
,
"version"
:
"7.7.3"
,
"resolved"
:
"https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
,
...
@@ -4485,6 +5566,26 @@
...
@@ -4485,6 +5566,26 @@
"node"
:
">=8"
"node"
:
">=8"
}
}
},
},
"node_modules/siginfo"
:
{
"version"
:
"2.0.0"
,
"resolved"
:
"https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz"
,
"integrity"
:
"sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="
,
"dev"
:
true
,
"license"
:
"ISC"
},
"node_modules/signal-exit"
:
{
"version"
:
"4.1.0"
,
"resolved"
:
"https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz"
,
"integrity"
:
"sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"engines"
:
{
"node"
:
">=14"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
}
},
"node_modules/slash"
:
{
"node_modules/slash"
:
{
"version"
:
"3.0.0"
,
"version"
:
"3.0.0"
,
"resolved"
:
"https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
,
...
@@ -4525,6 +5626,90 @@
...
@@ -4525,6 +5626,90 @@
"node"
:
">=0.8"
"node"
:
">=0.8"
}
}
},
},
"node_modules/stackback"
:
{
"version"
:
"0.0.2"
,
"resolved"
:
"https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz"
,
"integrity"
:
"sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/std-env"
:
{
"version"
:
"3.10.0"
,
"resolved"
:
"https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz"
,
"integrity"
:
"sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/string-width"
:
{
"version"
:
"5.1.2"
,
"resolved"
:
"https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
,
"integrity"
:
"sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"eastasianwidth"
:
"^0.2.0"
,
"emoji-regex"
:
"^9.2.2"
,
"strip-ansi"
:
"^7.0.1"
},
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs"
:
{
"name"
:
"string-width"
,
"version"
:
"4.2.3"
,
"resolved"
:
"https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
,
"integrity"
:
"sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"emoji-regex"
:
"^8.0.0"
,
"is-fullwidth-code-point"
:
"^3.0.0"
,
"strip-ansi"
:
"^6.0.1"
},
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex"
:
{
"version"
:
"8.0.0"
,
"resolved"
:
"https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
,
"integrity"
:
"sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/string-width/node_modules/ansi-regex"
:
{
"version"
:
"6.2.2"
,
"resolved"
:
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz"
,
"integrity"
:
"sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/string-width/node_modules/strip-ansi"
:
{
"version"
:
"7.1.2"
,
"resolved"
:
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz"
,
"integrity"
:
"sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ansi-regex"
:
"^6.0.1"
},
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi"
:
{
"node_modules/strip-ansi"
:
{
"version"
:
"6.0.1"
,
"version"
:
"6.0.1"
,
"resolved"
:
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
,
...
@@ -4538,6 +5723,20 @@
...
@@ -4538,6 +5723,20 @@
"node"
:
">=8"
"node"
:
">=8"
}
}
},
},
"node_modules/strip-ansi-cjs"
:
{
"name"
:
"strip-ansi"
,
"version"
:
"6.0.1"
,
"resolved"
:
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
,
"integrity"
:
"sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ansi-regex"
:
"^5.0.1"
},
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/strip-json-comments"
:
{
"node_modules/strip-json-comments"
:
{
"version"
:
"3.1.1"
,
"version"
:
"3.1.1"
,
"resolved"
:
"https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
,
...
@@ -4605,6 +5804,13 @@
...
@@ -4605,6 +5804,13 @@
"url"
:
"https://github.com/sponsors/ljharb"
"url"
:
"https://github.com/sponsors/ljharb"
}
}
},
},
"node_modules/symbol-tree"
:
{
"version"
:
"3.2.4"
,
"resolved"
:
"https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
,
"integrity"
:
"sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/tailwindcss"
:
{
"node_modules/tailwindcss"
:
{
"version"
:
"3.4.19"
,
"version"
:
"3.4.19"
,
"resolved"
:
"https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz"
,
"resolved"
:
"https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz"
,
...
@@ -4643,6 +5849,42 @@
...
@@ -4643,6 +5849,42 @@
"node"
:
">=14.0.0"
"node"
:
">=14.0.0"
}
}
},
},
"node_modules/test-exclude"
:
{
"version"
:
"7.0.1"
,
"resolved"
:
"https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz"
,
"integrity"
:
"sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"@istanbuljs/schema"
:
"^0.1.2"
,
"glob"
:
"^10.4.1"
,
"minimatch"
:
"^9.0.4"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/test-exclude/node_modules/glob"
:
{
"version"
:
"10.5.0"
,
"resolved"
:
"https://registry.npmjs.org/glob/-/glob-10.5.0.tgz"
,
"integrity"
:
"sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"foreground-child"
:
"^3.1.0"
,
"jackspeak"
:
"^3.1.2"
,
"minimatch"
:
"^9.0.4"
,
"minipass"
:
"^7.1.2"
,
"package-json-from-dist"
:
"^1.0.0"
,
"path-scurry"
:
"^1.11.1"
},
"bin"
:
{
"glob"
:
"dist/esm/bin.mjs"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/isaacs"
}
},
"node_modules/text-table"
:
{
"node_modules/text-table"
:
{
"version"
:
"0.2.0"
,
"version"
:
"0.2.0"
,
"resolved"
:
"https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
,
...
@@ -4680,6 +5922,20 @@
...
@@ -4680,6 +5922,20 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
"license"
:
"MIT"
},
},
"node_modules/tinybench"
:
{
"version"
:
"2.9.0"
,
"resolved"
:
"https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz"
,
"integrity"
:
"sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/tinyexec"
:
{
"version"
:
"0.3.2"
,
"resolved"
:
"https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz"
,
"integrity"
:
"sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/tinyglobby"
:
{
"node_modules/tinyglobby"
:
{
"version"
:
"0.2.15"
,
"version"
:
"0.2.15"
,
"resolved"
:
"https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz"
,
"resolved"
:
"https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz"
,
...
@@ -4710,6 +5966,36 @@
...
@@ -4710,6 +5966,36 @@
"url"
:
"https://github.com/sponsors/jonschlinkert"
"url"
:
"https://github.com/sponsors/jonschlinkert"
}
}
},
},
"node_modules/tinypool"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz"
,
"integrity"
:
"sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
"^18.0.0 || >=20.0.0"
}
},
"node_modules/tinyrainbow"
:
{
"version"
:
"1.2.0"
,
"resolved"
:
"https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz"
,
"integrity"
:
"sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=14.0.0"
}
},
"node_modules/tinyspy"
:
{
"version"
:
"3.0.2"
,
"resolved"
:
"https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz"
,
"integrity"
:
"sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=14.0.0"
}
},
"node_modules/to-regex-range"
:
{
"node_modules/to-regex-range"
:
{
"version"
:
"5.0.1"
,
"version"
:
"5.0.1"
,
"resolved"
:
"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
,
...
@@ -4723,6 +6009,35 @@
...
@@ -4723,6 +6009,35 @@
"node"
:
">=8.0"
"node"
:
">=8.0"
}
}
},
},
"node_modules/tough-cookie"
:
{
"version"
:
"4.1.4"
,
"resolved"
:
"https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz"
,
"integrity"
:
"sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="
,
"dev"
:
true
,
"license"
:
"BSD-3-Clause"
,
"dependencies"
:
{
"psl"
:
"^1.1.33"
,
"punycode"
:
"^2.1.1"
,
"universalify"
:
"^0.2.0"
,
"url-parse"
:
"^1.5.3"
},
"engines"
:
{
"node"
:
">=6"
}
},
"node_modules/tr46"
:
{
"version"
:
"5.1.1"
,
"resolved"
:
"https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz"
,
"integrity"
:
"sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"punycode"
:
"^2.3.1"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/ts-api-utils"
:
{
"node_modules/ts-api-utils"
:
{
"version"
:
"1.4.3"
,
"version"
:
"1.4.3"
,
"resolved"
:
"https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz"
,
...
@@ -4803,6 +6118,16 @@
...
@@ -4803,6 +6118,16 @@
"url"
:
"https://github.com/sponsors/sindresorhus"
"url"
:
"https://github.com/sponsors/sindresorhus"
}
}
},
},
"node_modules/universalify"
:
{
"version"
:
"0.2.0"
,
"resolved"
:
"https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz"
,
"integrity"
:
"sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">= 4.0.0"
}
},
"node_modules/update-browserslist-db"
:
{
"node_modules/update-browserslist-db"
:
{
"version"
:
"1.2.3"
,
"version"
:
"1.2.3"
,
"resolved"
:
"https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz"
,
...
@@ -4844,6 +6169,17 @@
...
@@ -4844,6 +6169,17 @@
"punycode"
:
"^2.1.0"
"punycode"
:
"^2.1.0"
}
}
},
},
"node_modules/url-parse"
:
{
"version"
:
"1.5.10"
,
"resolved"
:
"https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz"
,
"integrity"
:
"sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"querystringify"
:
"^2.1.1"
,
"requires-port"
:
"^1.0.0"
}
},
"node_modules/use-merge-value"
:
{
"node_modules/use-merge-value"
:
{
"version"
:
"1.2.0"
,
"version"
:
"1.2.0"
,
"resolved"
:
"https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz"
,
...
@@ -4920,6 +6256,29 @@
...
@@ -4920,6 +6256,29 @@
}
}
}
}
},
},
"node_modules/vite-node"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz"
,
"integrity"
:
"sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"cac"
:
"^6.7.14"
,
"debug"
:
"^4.3.7"
,
"es-module-lexer"
:
"^1.5.4"
,
"pathe"
:
"^1.1.2"
,
"vite"
:
"^5.0.0"
},
"bin"
:
{
"vite-node"
:
"vite-node.mjs"
},
"engines"
:
{
"node"
:
"^18.0.0 || >=20.0.0"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
}
},
"node_modules/vite-plugin-checker"
:
{
"node_modules/vite-plugin-checker"
:
{
"version"
:
"0.9.3"
,
"version"
:
"0.9.3"
,
"resolved"
:
"https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.9.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.9.3.tgz"
,
...
@@ -5054,6 +6413,72 @@
...
@@ -5054,6 +6413,72 @@
"url"
:
"https://github.com/chalk/strip-ansi?sponsor=1"
"url"
:
"https://github.com/chalk/strip-ansi?sponsor=1"
}
}
},
},
"node_modules/vitest"
:
{
"version"
:
"2.1.9"
,
"resolved"
:
"https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz"
,
"integrity"
:
"sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@vitest/expect"
:
"2.1.9"
,
"@vitest/mocker"
:
"2.1.9"
,
"@vitest/pretty-format"
:
"^2.1.9"
,
"@vitest/runner"
:
"2.1.9"
,
"@vitest/snapshot"
:
"2.1.9"
,
"@vitest/spy"
:
"2.1.9"
,
"@vitest/utils"
:
"2.1.9"
,
"chai"
:
"^5.1.2"
,
"debug"
:
"^4.3.7"
,
"expect-type"
:
"^1.1.0"
,
"magic-string"
:
"^0.30.12"
,
"pathe"
:
"^1.1.2"
,
"std-env"
:
"^3.8.0"
,
"tinybench"
:
"^2.9.0"
,
"tinyexec"
:
"^0.3.1"
,
"tinypool"
:
"^1.0.1"
,
"tinyrainbow"
:
"^1.2.0"
,
"vite"
:
"^5.0.0"
,
"vite-node"
:
"2.1.9"
,
"why-is-node-running"
:
"^2.3.0"
},
"bin"
:
{
"vitest"
:
"vitest.mjs"
},
"engines"
:
{
"node"
:
"^18.0.0 || >=20.0.0"
},
"funding"
:
{
"url"
:
"https://opencollective.com/vitest"
},
"peerDependencies"
:
{
"@edge-runtime/vm"
:
"*"
,
"@types/node"
:
"^18.0.0 || >=20.0.0"
,
"@vitest/browser"
:
"2.1.9"
,
"@vitest/ui"
:
"2.1.9"
,
"happy-dom"
:
"*"
,
"jsdom"
:
"*"
},
"peerDependenciesMeta"
:
{
"@edge-runtime/vm"
:
{
"optional"
:
true
},
"@types/node"
:
{
"optional"
:
true
},
"@vitest/browser"
:
{
"optional"
:
true
},
"@vitest/ui"
:
{
"optional"
:
true
},
"happy-dom"
:
{
"optional"
:
true
},
"jsdom"
:
{
"optional"
:
true
}
}
},
"node_modules/vscode-uri"
:
{
"node_modules/vscode-uri"
:
{
"version"
:
"3.1.0"
,
"version"
:
"3.1.0"
,
"resolved"
:
"https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz"
,
...
@@ -5092,6 +6517,13 @@
...
@@ -5092,6 +6517,13 @@
"vue"
:
"^3.0.0-0 || ^2.7.0"
"vue"
:
"^3.0.0-0 || ^2.7.0"
}
}
},
},
"node_modules/vue-component-type-helpers"
:
{
"version"
:
"2.2.12"
,
"resolved"
:
"https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz"
,
"integrity"
:
"sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/vue-demi"
:
{
"node_modules/vue-demi"
:
{
"version"
:
"0.14.10"
,
"version"
:
"0.14.10"
,
"resolved"
:
"https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz"
,
"resolved"
:
"https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz"
,
...
@@ -5196,6 +6628,77 @@
...
@@ -5196,6 +6628,77 @@
"typescript"
:
">=5.0.0"
"typescript"
:
">=5.0.0"
}
}
},
},
"node_modules/w3c-xmlserializer"
:
{
"version"
:
"5.0.0"
,
"resolved"
:
"https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz"
,
"integrity"
:
"sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"xml-name-validator"
:
"^5.0.0"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/w3c-xmlserializer/node_modules/xml-name-validator"
:
{
"version"
:
"5.0.0"
,
"resolved"
:
"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz"
,
"integrity"
:
"sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="
,
"dev"
:
true
,
"license"
:
"Apache-2.0"
,
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/webidl-conversions"
:
{
"version"
:
"7.0.0"
,
"resolved"
:
"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz"
,
"integrity"
:
"sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
,
"dev"
:
true
,
"license"
:
"BSD-2-Clause"
,
"engines"
:
{
"node"
:
">=12"
}
},
"node_modules/whatwg-encoding"
:
{
"version"
:
"3.1.1"
,
"resolved"
:
"https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz"
,
"integrity"
:
"sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="
,
"deprecated"
:
"Use @exodus/bytes instead for a more spec-conformant and faster implementation"
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"iconv-lite"
:
"0.6.3"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/whatwg-mimetype"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz"
,
"integrity"
:
"sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/whatwg-url"
:
{
"version"
:
"14.2.0"
,
"resolved"
:
"https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz"
,
"integrity"
:
"sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"tr46"
:
"^5.1.0"
,
"webidl-conversions"
:
"^7.0.0"
},
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/which"
:
{
"node_modules/which"
:
{
"version"
:
"2.0.2"
,
"version"
:
"2.0.2"
,
"resolved"
:
"https://registry.npmjs.org/which/-/which-2.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/which/-/which-2.0.2.tgz"
,
...
@@ -5212,6 +6715,23 @@
...
@@ -5212,6 +6715,23 @@
"node"
:
">= 8"
"node"
:
">= 8"
}
}
},
},
"node_modules/why-is-node-running"
:
{
"version"
:
"2.3.0"
,
"resolved"
:
"https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz"
,
"integrity"
:
"sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"siginfo"
:
"^2.0.0"
,
"stackback"
:
"0.0.2"
},
"bin"
:
{
"why-is-node-running"
:
"cli.js"
},
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/wmf"
:
{
"node_modules/wmf"
:
{
"version"
:
"1.0.2"
,
"version"
:
"1.0.2"
,
"resolved"
:
"https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz"
,
...
@@ -5240,6 +6760,107 @@
...
@@ -5240,6 +6760,107 @@
"node"
:
">=0.10.0"
"node"
:
">=0.10.0"
}
}
},
},
"node_modules/wrap-ansi"
:
{
"version"
:
"8.1.0"
,
"resolved"
:
"https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
,
"integrity"
:
"sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ansi-styles"
:
"^6.1.0"
,
"string-width"
:
"^5.0.1"
,
"strip-ansi"
:
"^7.0.1"
},
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs"
:
{
"name"
:
"wrap-ansi"
,
"version"
:
"7.0.0"
,
"resolved"
:
"https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
,
"integrity"
:
"sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ansi-styles"
:
"^4.0.0"
,
"string-width"
:
"^4.1.0"
,
"strip-ansi"
:
"^6.0.0"
},
"engines"
:
{
"node"
:
">=10"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex"
:
{
"version"
:
"8.0.0"
,
"resolved"
:
"https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
,
"integrity"
:
"sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width"
:
{
"version"
:
"4.2.3"
,
"resolved"
:
"https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
,
"integrity"
:
"sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"emoji-regex"
:
"^8.0.0"
,
"is-fullwidth-code-point"
:
"^3.0.0"
,
"strip-ansi"
:
"^6.0.1"
},
"engines"
:
{
"node"
:
">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex"
:
{
"version"
:
"6.2.2"
,
"resolved"
:
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz"
,
"integrity"
:
"sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles"
:
{
"version"
:
"6.2.3"
,
"resolved"
:
"https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz"
,
"integrity"
:
"sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi"
:
{
"version"
:
"7.1.2"
,
"resolved"
:
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz"
,
"integrity"
:
"sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"ansi-regex"
:
"^6.0.1"
},
"engines"
:
{
"node"
:
">=12"
},
"funding"
:
{
"url"
:
"https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/wrappy"
:
{
"node_modules/wrappy"
:
{
"version"
:
"1.0.2"
,
"version"
:
"1.0.2"
,
"resolved"
:
"https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
,
...
@@ -5247,6 +6868,28 @@
...
@@ -5247,6 +6868,28 @@
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
"license"
:
"ISC"
},
},
"node_modules/ws"
:
{
"version"
:
"8.19.0"
,
"resolved"
:
"https://registry.npmjs.org/ws/-/ws-8.19.0.tgz"
,
"integrity"
:
"sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=10.0.0"
},
"peerDependencies"
:
{
"bufferutil"
:
"^4.0.1"
,
"utf-8-validate"
:
">=5.0.2"
},
"peerDependenciesMeta"
:
{
"bufferutil"
:
{
"optional"
:
true
},
"utf-8-validate"
:
{
"optional"
:
true
}
}
},
"node_modules/xlsx"
:
{
"node_modules/xlsx"
:
{
"version"
:
"0.18.5"
,
"version"
:
"0.18.5"
,
"resolved"
:
"https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz"
,
"resolved"
:
"https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz"
,
...
@@ -5278,6 +6921,13 @@
...
@@ -5278,6 +6921,13 @@
"node"
:
">=12"
"node"
:
">=12"
}
}
},
},
"node_modules/xmlchars"
:
{
"version"
:
"2.2.0"
,
"resolved"
:
"https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz"
,
"integrity"
:
"sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/yaml"
:
{
"node_modules/yaml"
:
{
"version"
:
"1.10.2"
,
"version"
:
"1.10.2"
,
"resolved"
:
"https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
,
...
...
Prev
1
2
3
Next
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