mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: claude账号新增支持拦截预热请求
This commit is contained in:
@@ -585,7 +585,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
unifiedClientId,
|
unifiedClientId,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
extInfo,
|
extInfo,
|
||||||
maxConcurrency
|
maxConcurrency,
|
||||||
|
interceptWarmup
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -631,7 +632,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
unifiedClientId: unifiedClientId || '', // 统一的客户端标识
|
unifiedClientId: unifiedClientId || '', // 统一的客户端标识
|
||||||
expiresAt: expiresAt || null, // 账户订阅到期时间
|
expiresAt: expiresAt || null, // 账户订阅到期时间
|
||||||
extInfo: extInfo || null,
|
extInfo: extInfo || null,
|
||||||
maxConcurrency: maxConcurrency || 0 // 账户级串行队列:0=使用全局配置,>0=强制启用
|
maxConcurrency: maxConcurrency || 0, // 账户级串行队列:0=使用全局配置,>0=强制启用
|
||||||
|
interceptWarmup: interceptWarmup === true // 拦截预热请求:默认为false
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果是分组类型,将账户添加到分组
|
// 如果是分组类型,将账户添加到分组
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ router.post('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
dailyQuota,
|
dailyQuota,
|
||||||
quotaResetTime,
|
quotaResetTime,
|
||||||
maxConcurrentTasks,
|
maxConcurrentTasks,
|
||||||
disableAutoProtection
|
disableAutoProtection,
|
||||||
|
interceptWarmup
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
if (!name || !apiUrl || !apiKey) {
|
if (!name || !apiUrl || !apiKey) {
|
||||||
@@ -186,7 +187,8 @@ router.post('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
maxConcurrentTasks !== undefined && maxConcurrentTasks !== null
|
maxConcurrentTasks !== undefined && maxConcurrentTasks !== null
|
||||||
? Number(maxConcurrentTasks)
|
? Number(maxConcurrentTasks)
|
||||||
: 0,
|
: 0,
|
||||||
disableAutoProtection: normalizedDisableAutoProtection
|
disableAutoProtection: normalizedDisableAutoProtection,
|
||||||
|
interceptWarmup: interceptWarmup === true || interceptWarmup === 'true'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果是分组类型,将账户添加到分组(CCR 归属 Claude 平台分组)
|
// 如果是分组类型,将账户添加到分组(CCR 归属 Claude 平台分组)
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ const { getEffectiveModel, parseVendorPrefixedModel } = require('../utils/modelH
|
|||||||
const sessionHelper = require('../utils/sessionHelper')
|
const sessionHelper = require('../utils/sessionHelper')
|
||||||
const { updateRateLimitCounters } = require('../utils/rateLimitHelper')
|
const { updateRateLimitCounters } = require('../utils/rateLimitHelper')
|
||||||
const claudeRelayConfigService = require('../services/claudeRelayConfigService')
|
const claudeRelayConfigService = require('../services/claudeRelayConfigService')
|
||||||
|
const claudeAccountService = require('../services/claudeAccountService')
|
||||||
|
const claudeConsoleAccountService = require('../services/claudeConsoleAccountService')
|
||||||
|
const {
|
||||||
|
isWarmupRequest,
|
||||||
|
buildMockWarmupResponse,
|
||||||
|
sendMockWarmupStream
|
||||||
|
} = require('../utils/warmupInterceptor')
|
||||||
const { sanitizeUpstreamError } = require('../utils/errorSanitizer')
|
const { sanitizeUpstreamError } = require('../utils/errorSanitizer')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
@@ -363,6 +370,23 @@ async function handleMessagesRequest(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 预热请求拦截检查(在转发之前)
|
||||||
|
if (accountType === 'claude-official' || accountType === 'claude-console') {
|
||||||
|
const account =
|
||||||
|
accountType === 'claude-official'
|
||||||
|
? await claudeAccountService.getAccount(accountId)
|
||||||
|
: await claudeConsoleAccountService.getAccount(accountId)
|
||||||
|
|
||||||
|
if (account?.interceptWarmup === 'true' && isWarmupRequest(req.body)) {
|
||||||
|
logger.api(`🔥 Warmup request intercepted for account: ${account.name} (${accountId})`)
|
||||||
|
if (isStream) {
|
||||||
|
return sendMockWarmupStream(res, req.body.model)
|
||||||
|
} else {
|
||||||
|
return res.json(buildMockWarmupResponse(req.body.model))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 根据账号类型选择对应的转发服务并调用
|
// 根据账号类型选择对应的转发服务并调用
|
||||||
if (accountType === 'claude-official') {
|
if (accountType === 'claude-official') {
|
||||||
// 官方Claude账号使用原有的转发服务(会自己选择账号)
|
// 官方Claude账号使用原有的转发服务(会自己选择账号)
|
||||||
@@ -862,6 +886,21 @@ async function handleMessagesRequest(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 预热请求拦截检查(非流式,在转发之前)
|
||||||
|
if (accountType === 'claude-official' || accountType === 'claude-console') {
|
||||||
|
const account =
|
||||||
|
accountType === 'claude-official'
|
||||||
|
? await claudeAccountService.getAccount(accountId)
|
||||||
|
: await claudeConsoleAccountService.getAccount(accountId)
|
||||||
|
|
||||||
|
if (account?.interceptWarmup === 'true' && isWarmupRequest(req.body)) {
|
||||||
|
logger.api(
|
||||||
|
`🔥 Warmup request intercepted (non-stream) for account: ${account.name} (${accountId})`
|
||||||
|
)
|
||||||
|
return res.json(buildMockWarmupResponse(req.body.model))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 根据账号类型选择对应的转发服务
|
// 根据账号类型选择对应的转发服务
|
||||||
let response
|
let response
|
||||||
logger.debug(`[DEBUG] Request query params: ${JSON.stringify(req.query)}`)
|
logger.debug(`[DEBUG] Request query params: ${JSON.stringify(req.query)}`)
|
||||||
@@ -1354,9 +1393,6 @@ router.post('/v1/messages/count_tokens', authenticateApiKey, async (req, res) =>
|
|||||||
const maxAttempts = 2
|
const maxAttempts = 2
|
||||||
let attempt = 0
|
let attempt = 0
|
||||||
|
|
||||||
// 引入 claudeConsoleAccountService 用于检查 count_tokens 可用性
|
|
||||||
const claudeConsoleAccountService = require('../services/claudeConsoleAccountService')
|
|
||||||
|
|
||||||
const processRequest = async () => {
|
const processRequest = async () => {
|
||||||
const { accountId, accountType } = await unifiedClaudeScheduler.selectAccountForApiKey(
|
const { accountId, accountType } = await unifiedClaudeScheduler.selectAccountForApiKey(
|
||||||
req.apiKey,
|
req.apiKey,
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ class ClaudeAccountService {
|
|||||||
unifiedClientId = '', // 统一的客户端标识
|
unifiedClientId = '', // 统一的客户端标识
|
||||||
expiresAt = null, // 账户订阅到期时间
|
expiresAt = null, // 账户订阅到期时间
|
||||||
extInfo = null, // 额外扩展信息
|
extInfo = null, // 额外扩展信息
|
||||||
maxConcurrency = 0 // 账户级用户消息串行队列:0=使用全局配置,>0=强制启用串行
|
maxConcurrency = 0, // 账户级用户消息串行队列:0=使用全局配置,>0=强制启用串行
|
||||||
|
interceptWarmup = false // 拦截预热请求(标题生成、Warmup等)
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const accountId = uuidv4()
|
const accountId = uuidv4()
|
||||||
@@ -139,7 +140,9 @@ class ClaudeAccountService {
|
|||||||
// 扩展信息
|
// 扩展信息
|
||||||
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : '',
|
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : '',
|
||||||
// 账户级用户消息串行队列限制
|
// 账户级用户消息串行队列限制
|
||||||
maxConcurrency: maxConcurrency.toString()
|
maxConcurrency: maxConcurrency.toString(),
|
||||||
|
// 拦截预热请求
|
||||||
|
interceptWarmup: interceptWarmup.toString()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 兼容旧格式
|
// 兼容旧格式
|
||||||
@@ -173,7 +176,9 @@ class ClaudeAccountService {
|
|||||||
// 扩展信息
|
// 扩展信息
|
||||||
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : '',
|
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : '',
|
||||||
// 账户级用户消息串行队列限制
|
// 账户级用户消息串行队列限制
|
||||||
maxConcurrency: maxConcurrency.toString()
|
maxConcurrency: maxConcurrency.toString(),
|
||||||
|
// 拦截预热请求
|
||||||
|
interceptWarmup: interceptWarmup.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +226,8 @@ class ClaudeAccountService {
|
|||||||
useUnifiedUserAgent,
|
useUnifiedUserAgent,
|
||||||
useUnifiedClientId,
|
useUnifiedClientId,
|
||||||
unifiedClientId,
|
unifiedClientId,
|
||||||
extInfo: normalizedExtInfo
|
extInfo: normalizedExtInfo,
|
||||||
|
interceptWarmup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,7 +587,9 @@ class ClaudeAccountService {
|
|||||||
// 扩展信息
|
// 扩展信息
|
||||||
extInfo: parsedExtInfo,
|
extInfo: parsedExtInfo,
|
||||||
// 账户级用户消息串行队列限制
|
// 账户级用户消息串行队列限制
|
||||||
maxConcurrency: parseInt(account.maxConcurrency || '0', 10)
|
maxConcurrency: parseInt(account.maxConcurrency || '0', 10),
|
||||||
|
// 拦截预热请求
|
||||||
|
interceptWarmup: account.interceptWarmup === 'true'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -674,7 +682,8 @@ class ClaudeAccountService {
|
|||||||
'unifiedClientId',
|
'unifiedClientId',
|
||||||
'subscriptionExpiresAt',
|
'subscriptionExpiresAt',
|
||||||
'extInfo',
|
'extInfo',
|
||||||
'maxConcurrency'
|
'maxConcurrency',
|
||||||
|
'interceptWarmup'
|
||||||
]
|
]
|
||||||
const updatedData = { ...accountData }
|
const updatedData = { ...accountData }
|
||||||
let shouldClearAutoStopFields = false
|
let shouldClearAutoStopFields = false
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ class ClaudeConsoleAccountService {
|
|||||||
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
||||||
quotaResetTime = '00:00', // 额度重置时间(HH:mm格式)
|
quotaResetTime = '00:00', // 额度重置时间(HH:mm格式)
|
||||||
maxConcurrentTasks = 0, // 最大并发任务数,0表示无限制
|
maxConcurrentTasks = 0, // 最大并发任务数,0表示无限制
|
||||||
disableAutoProtection = false // 是否关闭自动防护(429/401/400/529 不自动禁用)
|
disableAutoProtection = false, // 是否关闭自动防护(429/401/400/529 不自动禁用)
|
||||||
|
interceptWarmup = false // 拦截预热请求(标题生成、Warmup等)
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
@@ -117,7 +118,8 @@ class ClaudeConsoleAccountService {
|
|||||||
quotaResetTime, // 额度重置时间
|
quotaResetTime, // 额度重置时间
|
||||||
quotaStoppedAt: '', // 因额度停用的时间
|
quotaStoppedAt: '', // 因额度停用的时间
|
||||||
maxConcurrentTasks: maxConcurrentTasks.toString(), // 最大并发任务数,0表示无限制
|
maxConcurrentTasks: maxConcurrentTasks.toString(), // 最大并发任务数,0表示无限制
|
||||||
disableAutoProtection: disableAutoProtection.toString() // 关闭自动防护
|
disableAutoProtection: disableAutoProtection.toString(), // 关闭自动防护
|
||||||
|
interceptWarmup: interceptWarmup.toString() // 拦截预热请求
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = redis.getClientSafe()
|
const client = redis.getClientSafe()
|
||||||
@@ -156,6 +158,7 @@ class ClaudeConsoleAccountService {
|
|||||||
quotaStoppedAt: null,
|
quotaStoppedAt: null,
|
||||||
maxConcurrentTasks, // 新增:返回并发限制配置
|
maxConcurrentTasks, // 新增:返回并发限制配置
|
||||||
disableAutoProtection, // 新增:返回自动防护开关
|
disableAutoProtection, // 新增:返回自动防护开关
|
||||||
|
interceptWarmup, // 新增:返回预热请求拦截开关
|
||||||
activeTaskCount: 0 // 新增:新建账户当前并发数为0
|
activeTaskCount: 0 // 新增:新建账户当前并发数为0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +220,9 @@ class ClaudeConsoleAccountService {
|
|||||||
// 并发控制相关
|
// 并发控制相关
|
||||||
maxConcurrentTasks: parseInt(accountData.maxConcurrentTasks) || 0,
|
maxConcurrentTasks: parseInt(accountData.maxConcurrentTasks) || 0,
|
||||||
activeTaskCount,
|
activeTaskCount,
|
||||||
disableAutoProtection: accountData.disableAutoProtection === 'true'
|
disableAutoProtection: accountData.disableAutoProtection === 'true',
|
||||||
|
// 拦截预热请求
|
||||||
|
interceptWarmup: accountData.interceptWarmup === 'true'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,6 +380,9 @@ class ClaudeConsoleAccountService {
|
|||||||
if (updates.disableAutoProtection !== undefined) {
|
if (updates.disableAutoProtection !== undefined) {
|
||||||
updatedData.disableAutoProtection = updates.disableAutoProtection.toString()
|
updatedData.disableAutoProtection = updates.disableAutoProtection.toString()
|
||||||
}
|
}
|
||||||
|
if (updates.interceptWarmup !== undefined) {
|
||||||
|
updatedData.interceptWarmup = updates.interceptWarmup.toString()
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ 直接保存 subscriptionExpiresAt(如果提供)
|
// ✅ 直接保存 subscriptionExpiresAt(如果提供)
|
||||||
// Claude Console 没有 token 刷新逻辑,不会覆盖此字段
|
// Claude Console 没有 token 刷新逻辑,不会覆盖此字段
|
||||||
|
|||||||
202
src/utils/warmupInterceptor.js
Normal file
202
src/utils/warmupInterceptor.js
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const { v4: uuidv4 } = require('uuid')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预热请求拦截器
|
||||||
|
* 检测并拦截低价值请求(标题生成、Warmup等),直接返回模拟响应
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否为预热请求
|
||||||
|
* @param {Object} body - 请求体
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isWarmupRequest(body) {
|
||||||
|
if (!body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 messages
|
||||||
|
if (body.messages && Array.isArray(body.messages)) {
|
||||||
|
for (const msg of body.messages) {
|
||||||
|
// 处理 content 为数组的情况
|
||||||
|
if (Array.isArray(msg.content)) {
|
||||||
|
for (const content of msg.content) {
|
||||||
|
if (content.type === 'text' && typeof content.text === 'string') {
|
||||||
|
if (isTitleOrWarmupText(content.text)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理 content 为字符串的情况
|
||||||
|
if (typeof msg.content === 'string') {
|
||||||
|
if (isTitleOrWarmupText(msg.content)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 system prompt
|
||||||
|
if (body.system) {
|
||||||
|
const systemText = extractSystemText(body.system)
|
||||||
|
if (isTitleExtractionSystemPrompt(systemText)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文本是否为标题生成或Warmup请求
|
||||||
|
*/
|
||||||
|
function isTitleOrWarmupText(text) {
|
||||||
|
if (!text) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
text.includes('Please write a 5-10 word title for the following conversation:') ||
|
||||||
|
text === 'Warmup'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查system prompt是否为标题提取类型
|
||||||
|
*/
|
||||||
|
function isTitleExtractionSystemPrompt(systemText) {
|
||||||
|
if (!systemText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return systemText.includes(
|
||||||
|
'nalyze if this message indicates a new conversation topic. If it does, extract a 2-3 word title'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从system字段提取文本
|
||||||
|
*/
|
||||||
|
function extractSystemText(system) {
|
||||||
|
if (typeof system === 'string') {
|
||||||
|
return system
|
||||||
|
}
|
||||||
|
if (Array.isArray(system)) {
|
||||||
|
return system.map((s) => (typeof s === 'object' ? s.text || '' : String(s))).join('')
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成模拟的非流式响应
|
||||||
|
* @param {string} model - 模型名称
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function buildMockWarmupResponse(model) {
|
||||||
|
return {
|
||||||
|
id: `msg_warmup_${uuidv4().replace(/-/g, '').slice(0, 20)}`,
|
||||||
|
type: 'message',
|
||||||
|
role: 'assistant',
|
||||||
|
content: [{ type: 'text', text: 'New Conversation' }],
|
||||||
|
model: model || 'claude-3-5-sonnet-20241022',
|
||||||
|
stop_reason: 'end_turn',
|
||||||
|
stop_sequence: null,
|
||||||
|
usage: {
|
||||||
|
input_tokens: 10,
|
||||||
|
output_tokens: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送模拟的流式响应
|
||||||
|
* @param {Object} res - Express response对象
|
||||||
|
* @param {string} model - 模型名称
|
||||||
|
*/
|
||||||
|
function sendMockWarmupStream(res, model) {
|
||||||
|
const effectiveModel = model || 'claude-3-5-sonnet-20241022'
|
||||||
|
const messageId = `msg_warmup_${uuidv4().replace(/-/g, '').slice(0, 20)}`
|
||||||
|
|
||||||
|
const events = [
|
||||||
|
{
|
||||||
|
event: 'message_start',
|
||||||
|
data: {
|
||||||
|
message: {
|
||||||
|
content: [],
|
||||||
|
id: messageId,
|
||||||
|
model: effectiveModel,
|
||||||
|
role: 'assistant',
|
||||||
|
stop_reason: null,
|
||||||
|
stop_sequence: null,
|
||||||
|
type: 'message',
|
||||||
|
usage: { input_tokens: 10, output_tokens: 0 }
|
||||||
|
},
|
||||||
|
type: 'message_start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'content_block_start',
|
||||||
|
data: {
|
||||||
|
content_block: { text: '', type: 'text' },
|
||||||
|
index: 0,
|
||||||
|
type: 'content_block_start'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'content_block_delta',
|
||||||
|
data: {
|
||||||
|
delta: { text: 'New', type: 'text_delta' },
|
||||||
|
index: 0,
|
||||||
|
type: 'content_block_delta'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'content_block_delta',
|
||||||
|
data: {
|
||||||
|
delta: { text: ' Conversation', type: 'text_delta' },
|
||||||
|
index: 0,
|
||||||
|
type: 'content_block_delta'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'content_block_stop',
|
||||||
|
data: { index: 0, type: 'content_block_stop' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'message_delta',
|
||||||
|
data: {
|
||||||
|
delta: { stop_reason: 'end_turn', stop_sequence: null },
|
||||||
|
type: 'message_delta',
|
||||||
|
usage: { input_tokens: 10, output_tokens: 2 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'message_stop',
|
||||||
|
data: { type: 'message_stop' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let index = 0
|
||||||
|
const sendNext = () => {
|
||||||
|
if (index >= events.length) {
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { event, data } = events[index]
|
||||||
|
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
|
||||||
|
index++
|
||||||
|
|
||||||
|
// 模拟网络延迟
|
||||||
|
setTimeout(sendNext, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isWarmupRequest,
|
||||||
|
buildMockWarmupResponse,
|
||||||
|
sendMockWarmupStream
|
||||||
|
}
|
||||||
@@ -1651,6 +1651,28 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 拦截预热请求开关(Claude 和 Claude Console) -->
|
||||||
|
<div
|
||||||
|
v-if="form.platform === 'claude' || form.platform === 'claude-console'"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<label class="flex items-start">
|
||||||
|
<input
|
||||||
|
v-model="form.interceptWarmup"
|
||||||
|
class="mt-1 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<div class="ml-3">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
拦截预热请求
|
||||||
|
</span>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
启用后,对标题生成、Warmup 等低价值请求直接返回模拟响应,不消耗上游 API 额度
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Claude User-Agent 版本配置 -->
|
<!-- Claude User-Agent 版本配置 -->
|
||||||
<div v-if="form.platform === 'claude'" class="mt-4">
|
<div v-if="form.platform === 'claude'" class="mt-4">
|
||||||
<label class="flex items-start">
|
<label class="flex items-start">
|
||||||
@@ -2653,6 +2675,25 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 拦截预热请求开关(Claude 和 Claude Console 编辑模式) -->
|
||||||
|
<div v-if="form.platform === 'claude' || form.platform === 'claude-console'" class="mt-4">
|
||||||
|
<label class="flex items-start">
|
||||||
|
<input
|
||||||
|
v-model="form.interceptWarmup"
|
||||||
|
class="mt-1 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<div class="ml-3">
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
拦截预热请求
|
||||||
|
</span>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
启用后,对标题生成、Warmup 等低价值请求直接返回模拟响应,不消耗上游 API 额度
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Claude User-Agent 版本配置(编辑模式) -->
|
<!-- Claude User-Agent 版本配置(编辑模式) -->
|
||||||
<div v-if="form.platform === 'claude'" class="mt-4">
|
<div v-if="form.platform === 'claude'" class="mt-4">
|
||||||
<label class="flex items-start">
|
<label class="flex items-start">
|
||||||
@@ -3988,6 +4029,8 @@ const form = ref({
|
|||||||
useUnifiedClientId: props.account?.useUnifiedClientId || false, // 使用统一的客户端标识
|
useUnifiedClientId: props.account?.useUnifiedClientId || false, // 使用统一的客户端标识
|
||||||
unifiedClientId: props.account?.unifiedClientId || '', // 统一的客户端标识
|
unifiedClientId: props.account?.unifiedClientId || '', // 统一的客户端标识
|
||||||
serialQueueEnabled: (props.account?.maxConcurrency || 0) > 0, // 账户级串行队列开关
|
serialQueueEnabled: (props.account?.maxConcurrency || 0) > 0, // 账户级串行队列开关
|
||||||
|
interceptWarmup:
|
||||||
|
props.account?.interceptWarmup === true || props.account?.interceptWarmup === 'true', // 拦截预热请求
|
||||||
groupId: '',
|
groupId: '',
|
||||||
groupIds: [],
|
groupIds: [],
|
||||||
projectId: props.account?.projectId || '',
|
projectId: props.account?.projectId || '',
|
||||||
@@ -4574,6 +4617,7 @@ const buildClaudeAccountData = (tokenInfo, accountName, clientId) => {
|
|||||||
claudeAiOauth: claudeOauthPayload,
|
claudeAiOauth: claudeOauthPayload,
|
||||||
priority: form.value.priority || 50,
|
priority: form.value.priority || 50,
|
||||||
autoStopOnWarning: form.value.autoStopOnWarning || false,
|
autoStopOnWarning: form.value.autoStopOnWarning || false,
|
||||||
|
interceptWarmup: form.value.interceptWarmup || false,
|
||||||
useUnifiedUserAgent: form.value.useUnifiedUserAgent || false,
|
useUnifiedUserAgent: form.value.useUnifiedUserAgent || false,
|
||||||
useUnifiedClientId: form.value.useUnifiedClientId || false,
|
useUnifiedClientId: form.value.useUnifiedClientId || false,
|
||||||
unifiedClientId: clientId,
|
unifiedClientId: clientId,
|
||||||
@@ -5131,6 +5175,7 @@ const createAccount = async () => {
|
|||||||
// 上游错误处理(仅 Claude Console)
|
// 上游错误处理(仅 Claude Console)
|
||||||
if (form.value.platform === 'claude-console') {
|
if (form.value.platform === 'claude-console') {
|
||||||
data.disableAutoProtection = !!form.value.disableAutoProtection
|
data.disableAutoProtection = !!form.value.disableAutoProtection
|
||||||
|
data.interceptWarmup = !!form.value.interceptWarmup
|
||||||
}
|
}
|
||||||
// 额度管理字段
|
// 额度管理字段
|
||||||
data.dailyQuota = form.value.dailyQuota || 0
|
data.dailyQuota = form.value.dailyQuota || 0
|
||||||
@@ -5427,6 +5472,7 @@ const updateAccount = async () => {
|
|||||||
|
|
||||||
data.priority = form.value.priority || 50
|
data.priority = form.value.priority || 50
|
||||||
data.autoStopOnWarning = form.value.autoStopOnWarning || false
|
data.autoStopOnWarning = form.value.autoStopOnWarning || false
|
||||||
|
data.interceptWarmup = form.value.interceptWarmup || false
|
||||||
data.useUnifiedUserAgent = form.value.useUnifiedUserAgent || false
|
data.useUnifiedUserAgent = form.value.useUnifiedUserAgent || false
|
||||||
data.useUnifiedClientId = form.value.useUnifiedClientId || false
|
data.useUnifiedClientId = form.value.useUnifiedClientId || false
|
||||||
data.unifiedClientId = form.value.unifiedClientId || ''
|
data.unifiedClientId = form.value.unifiedClientId || ''
|
||||||
@@ -5463,6 +5509,8 @@ const updateAccount = async () => {
|
|||||||
data.rateLimitDuration = form.value.enableRateLimit ? form.value.rateLimitDuration || 60 : 0
|
data.rateLimitDuration = form.value.enableRateLimit ? form.value.rateLimitDuration || 60 : 0
|
||||||
// 上游错误处理
|
// 上游错误处理
|
||||||
data.disableAutoProtection = !!form.value.disableAutoProtection
|
data.disableAutoProtection = !!form.value.disableAutoProtection
|
||||||
|
// 拦截预热请求
|
||||||
|
data.interceptWarmup = !!form.value.interceptWarmup
|
||||||
// 额度管理字段
|
// 额度管理字段
|
||||||
data.dailyQuota = form.value.dailyQuota || 0
|
data.dailyQuota = form.value.dailyQuota || 0
|
||||||
data.quotaResetTime = form.value.quotaResetTime || '00:00'
|
data.quotaResetTime = form.value.quotaResetTime || '00:00'
|
||||||
@@ -6031,6 +6079,8 @@ watch(
|
|||||||
accountType: newAccount.accountType || 'shared',
|
accountType: newAccount.accountType || 'shared',
|
||||||
subscriptionType: subscriptionType,
|
subscriptionType: subscriptionType,
|
||||||
autoStopOnWarning: newAccount.autoStopOnWarning || false,
|
autoStopOnWarning: newAccount.autoStopOnWarning || false,
|
||||||
|
interceptWarmup:
|
||||||
|
newAccount.interceptWarmup === true || newAccount.interceptWarmup === 'true',
|
||||||
useUnifiedUserAgent: newAccount.useUnifiedUserAgent || false,
|
useUnifiedUserAgent: newAccount.useUnifiedUserAgent || false,
|
||||||
useUnifiedClientId: newAccount.useUnifiedClientId || false,
|
useUnifiedClientId: newAccount.useUnifiedClientId || false,
|
||||||
unifiedClientId: newAccount.unifiedClientId || '',
|
unifiedClientId: newAccount.unifiedClientId || '',
|
||||||
|
|||||||
Reference in New Issue
Block a user