mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 增强 Gemini 桥接处理并添加 Antigravity 响应转储工具
This commit is contained in:
@@ -70,6 +70,8 @@ CLAUDE_BETA_HEADER=claude-code-20250219,oauth-2025-04-20,interleaved-thinking-20
|
|||||||
# - antigravity-upstream-requests-dump.jsonl
|
# - antigravity-upstream-requests-dump.jsonl
|
||||||
# ANTIGRAVITY_DEBUG_UPSTREAM_REQUEST_DUMP=true
|
# ANTIGRAVITY_DEBUG_UPSTREAM_REQUEST_DUMP=true
|
||||||
# ANTIGRAVITY_DEBUG_UPSTREAM_REQUEST_DUMP_MAX_BYTES=2097152
|
# ANTIGRAVITY_DEBUG_UPSTREAM_REQUEST_DUMP_MAX_BYTES=2097152
|
||||||
|
# 启用 Antigravity 上游响应日志(SSE 事件 + 流摘要)
|
||||||
|
# ANTIGRAVITY_DEBUG_UPSTREAM_RESPONSE_DUMP=true
|
||||||
|
|
||||||
# 🚫 529错误处理配置
|
# 🚫 529错误处理配置
|
||||||
# 启用529错误处理,0表示禁用,>0表示过载状态持续时间(分钟)
|
# 启用529错误处理,0表示禁用,>0表示过载状态持续时间(分钟)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
175
src/utils/antigravityUpstreamResponseDump.js
Normal file
175
src/utils/antigravityUpstreamResponseDump.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
const fs = require('fs/promises')
|
||||||
|
const path = require('path')
|
||||||
|
const logger = require('./logger')
|
||||||
|
const { getProjectRoot } = require('./projectPaths')
|
||||||
|
|
||||||
|
const UPSTREAM_RESPONSE_DUMP_ENV = 'ANTIGRAVITY_DEBUG_UPSTREAM_RESPONSE_DUMP'
|
||||||
|
const UPSTREAM_RESPONSE_DUMP_MAX_BYTES_ENV = 'ANTIGRAVITY_DEBUG_UPSTREAM_RESPONSE_DUMP_MAX_BYTES'
|
||||||
|
const UPSTREAM_RESPONSE_DUMP_FILENAME = 'antigravity-upstream-responses-dump.jsonl'
|
||||||
|
|
||||||
|
function isEnabled() {
|
||||||
|
const raw = process.env[UPSTREAM_RESPONSE_DUMP_ENV]
|
||||||
|
if (!raw) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const normalized = String(raw).trim().toLowerCase()
|
||||||
|
return normalized === '1' || normalized === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxBytes() {
|
||||||
|
const raw = process.env[UPSTREAM_RESPONSE_DUMP_MAX_BYTES_ENV]
|
||||||
|
if (!raw) {
|
||||||
|
return 2 * 1024 * 1024
|
||||||
|
}
|
||||||
|
const parsed = Number.parseInt(raw, 10)
|
||||||
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||||
|
return 2 * 1024 * 1024
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeJsonStringify(payload, maxBytes) {
|
||||||
|
let json = ''
|
||||||
|
try {
|
||||||
|
json = JSON.stringify(payload)
|
||||||
|
} catch (e) {
|
||||||
|
return JSON.stringify({
|
||||||
|
type: 'antigravity_upstream_response_dump_error',
|
||||||
|
error: 'JSON.stringify_failed',
|
||||||
|
message: e?.message || String(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer.byteLength(json, 'utf8') <= maxBytes) {
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
const truncated = Buffer.from(json, 'utf8').subarray(0, maxBytes).toString('utf8')
|
||||||
|
return JSON.stringify({
|
||||||
|
type: 'antigravity_upstream_response_dump_truncated',
|
||||||
|
maxBytes,
|
||||||
|
originalBytes: Buffer.byteLength(json, 'utf8'),
|
||||||
|
partialJson: truncated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录 Antigravity 上游 API 的响应
|
||||||
|
* @param {Object} responseInfo - 响应信息
|
||||||
|
* @param {string} responseInfo.requestId - 请求 ID
|
||||||
|
* @param {string} responseInfo.model - 模型名称
|
||||||
|
* @param {number} responseInfo.statusCode - HTTP 状态码
|
||||||
|
* @param {string} responseInfo.statusText - HTTP 状态文本
|
||||||
|
* @param {Object} responseInfo.headers - 响应头
|
||||||
|
* @param {string} responseInfo.responseType - 响应类型 (stream/non-stream/error)
|
||||||
|
* @param {Object} responseInfo.summary - 响应摘要
|
||||||
|
* @param {Object} responseInfo.error - 错误信息(如果有)
|
||||||
|
*/
|
||||||
|
async function dumpAntigravityUpstreamResponse(responseInfo) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxBytes = getMaxBytes()
|
||||||
|
const filename = path.join(getProjectRoot(), UPSTREAM_RESPONSE_DUMP_FILENAME)
|
||||||
|
|
||||||
|
const record = {
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
type: 'antigravity_upstream_response',
|
||||||
|
requestId: responseInfo?.requestId || null,
|
||||||
|
model: responseInfo?.model || null,
|
||||||
|
statusCode: responseInfo?.statusCode || null,
|
||||||
|
statusText: responseInfo?.statusText || null,
|
||||||
|
responseType: responseInfo?.responseType || null,
|
||||||
|
headers: responseInfo?.headers || null,
|
||||||
|
summary: responseInfo?.summary || null,
|
||||||
|
error: responseInfo?.error || null,
|
||||||
|
rawData: responseInfo?.rawData || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = `${safeJsonStringify(record, maxBytes)}\n`
|
||||||
|
try {
|
||||||
|
await fs.appendFile(filename, line, { encoding: 'utf8' })
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Failed to dump Antigravity upstream response', {
|
||||||
|
filename,
|
||||||
|
requestId: responseInfo?.requestId || null,
|
||||||
|
error: e?.message || String(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录 SSE 流中的每个事件(用于详细调试)
|
||||||
|
*/
|
||||||
|
async function dumpAntigravityStreamEvent(eventInfo) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxBytes = getMaxBytes()
|
||||||
|
const filename = path.join(getProjectRoot(), UPSTREAM_RESPONSE_DUMP_FILENAME)
|
||||||
|
|
||||||
|
const record = {
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
type: 'antigravity_stream_event',
|
||||||
|
requestId: eventInfo?.requestId || null,
|
||||||
|
eventIndex: eventInfo?.eventIndex || null,
|
||||||
|
eventType: eventInfo?.eventType || null,
|
||||||
|
data: eventInfo?.data || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = `${safeJsonStringify(record, maxBytes)}\n`
|
||||||
|
try {
|
||||||
|
await fs.appendFile(filename, line, { encoding: 'utf8' })
|
||||||
|
} catch (e) {
|
||||||
|
// 静默处理,避免日志过多
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录流式响应的最终摘要
|
||||||
|
*/
|
||||||
|
async function dumpAntigravityStreamSummary(summaryInfo) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxBytes = getMaxBytes()
|
||||||
|
const filename = path.join(getProjectRoot(), UPSTREAM_RESPONSE_DUMP_FILENAME)
|
||||||
|
|
||||||
|
const record = {
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
type: 'antigravity_stream_summary',
|
||||||
|
requestId: summaryInfo?.requestId || null,
|
||||||
|
model: summaryInfo?.model || null,
|
||||||
|
totalEvents: summaryInfo?.totalEvents || 0,
|
||||||
|
finishReason: summaryInfo?.finishReason || null,
|
||||||
|
hasThinking: summaryInfo?.hasThinking || false,
|
||||||
|
hasToolCalls: summaryInfo?.hasToolCalls || false,
|
||||||
|
toolCallNames: summaryInfo?.toolCallNames || [],
|
||||||
|
usage: summaryInfo?.usage || null,
|
||||||
|
error: summaryInfo?.error || null,
|
||||||
|
textPreview: summaryInfo?.textPreview || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = `${safeJsonStringify(record, maxBytes)}\n`
|
||||||
|
try {
|
||||||
|
await fs.appendFile(filename, line, { encoding: 'utf8' })
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Failed to dump Antigravity stream summary', {
|
||||||
|
filename,
|
||||||
|
requestId: summaryInfo?.requestId || null,
|
||||||
|
error: e?.message || String(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
dumpAntigravityUpstreamResponse,
|
||||||
|
dumpAntigravityStreamEvent,
|
||||||
|
dumpAntigravityStreamSummary,
|
||||||
|
UPSTREAM_RESPONSE_DUMP_ENV,
|
||||||
|
UPSTREAM_RESPONSE_DUMP_MAX_BYTES_ENV,
|
||||||
|
UPSTREAM_RESPONSE_DUMP_FILENAME
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user