mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: droid转发增加runtimeAddon调试插件
This commit is contained in:
@@ -7,8 +7,10 @@ const apiKeyService = require('./apiKeyService')
|
||||
const redis = require('../models/redis')
|
||||
const { updateRateLimitCounters } = require('../utils/rateLimitHelper')
|
||||
const logger = require('../utils/logger')
|
||||
const runtimeAddon = require('../utils/runtimeAddon')
|
||||
|
||||
const SYSTEM_PROMPT = 'You are Droid, an AI software engineering agent built by Factory.'
|
||||
const RUNTIME_EVENT_FMT_PAYLOAD = 'fmtPayload'
|
||||
|
||||
/**
|
||||
* Droid API 转发服务
|
||||
@@ -246,11 +248,34 @@ class DroidRelayService {
|
||||
// 处理请求体(注入 system prompt 等)
|
||||
const streamRequested = !disableStreaming && this._isStreamRequested(normalizedRequestBody)
|
||||
|
||||
const processedBody = this._processRequestBody(normalizedRequestBody, normalizedEndpoint, {
|
||||
let processedBody = this._processRequestBody(normalizedRequestBody, normalizedEndpoint, {
|
||||
disableStreaming,
|
||||
streamRequested
|
||||
})
|
||||
|
||||
const extensionPayload = {
|
||||
body: processedBody,
|
||||
endpoint: normalizedEndpoint,
|
||||
rawRequest: normalizedRequestBody,
|
||||
originalRequest: requestBody
|
||||
}
|
||||
|
||||
const extensionResult = runtimeAddon.emitSync(RUNTIME_EVENT_FMT_PAYLOAD, extensionPayload)
|
||||
const resolvedPayload =
|
||||
extensionResult && typeof extensionResult === 'object' ? extensionResult : extensionPayload
|
||||
|
||||
if (resolvedPayload && typeof resolvedPayload === 'object') {
|
||||
if (resolvedPayload.abortResponse && typeof resolvedPayload.abortResponse === 'object') {
|
||||
return resolvedPayload.abortResponse
|
||||
}
|
||||
|
||||
if (resolvedPayload.body && typeof resolvedPayload.body === 'object') {
|
||||
processedBody = resolvedPayload.body
|
||||
} else if (resolvedPayload !== extensionPayload) {
|
||||
processedBody = resolvedPayload
|
||||
}
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
const isStreaming = streamRequested
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ class DroidScheduler {
|
||||
|
||||
if (filtered.length === 0) {
|
||||
throw new Error(
|
||||
`No available Droid accounts for endpoint ${normalizedEndpoint}${apiKeyData?.droidAccountId ? ' (respecting binding)' : ''}`
|
||||
`No available accounts for endpoint ${normalizedEndpoint}${apiKeyData?.droidAccountId ? ' (respecting binding)' : ''}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ class DroidScheduler {
|
||||
|
||||
if (!selected) {
|
||||
throw new Error(
|
||||
`No schedulable Droid account available after sorting (${normalizedEndpoint})`
|
||||
`No schedulable account available after sorting (${normalizedEndpoint})`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
121
src/utils/runtimeAddon.js
Normal file
121
src/utils/runtimeAddon.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const logger = require('./logger')
|
||||
|
||||
const ADDON_DIRECTORIES = [
|
||||
path.join(process.cwd(), '.local', 'ext'),
|
||||
path.join(process.cwd(), '.local', 'extensions')
|
||||
]
|
||||
|
||||
class RuntimeAddonBus {
|
||||
constructor() {
|
||||
this._handlers = new Map()
|
||||
this._initialized = false
|
||||
}
|
||||
|
||||
register(eventId, handler) {
|
||||
if (!eventId || typeof handler !== 'function') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._handlers.has(eventId)) {
|
||||
this._handlers.set(eventId, [])
|
||||
}
|
||||
|
||||
this._handlers.get(eventId).push(handler)
|
||||
}
|
||||
|
||||
emitSync(eventId, payload) {
|
||||
this._ensureInitialized()
|
||||
|
||||
if (!eventId) {
|
||||
return payload
|
||||
}
|
||||
|
||||
const handlers = this._handlers.get(eventId)
|
||||
if (!handlers || handlers.length === 0) {
|
||||
return payload
|
||||
}
|
||||
|
||||
let current = payload
|
||||
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
const result = handler(current)
|
||||
if (typeof result !== 'undefined') {
|
||||
current = result
|
||||
}
|
||||
} catch (error) {
|
||||
this._log('warn', `本地扩展处理 ${eventId} 失败: ${error.message}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
_ensureInitialized() {
|
||||
if (this._initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
this._initialized = true
|
||||
const loadedPaths = new Set()
|
||||
|
||||
for (const dir of ADDON_DIRECTORIES) {
|
||||
if (!dir || !fs.existsSync(dir)) {
|
||||
continue
|
||||
}
|
||||
|
||||
let entries = []
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true })
|
||||
} catch (error) {
|
||||
this._log('warn', `读取本地扩展目录 ${dir} 失败: ${error.message}`, error)
|
||||
continue
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!entry.name.endsWith('.js')) {
|
||||
continue
|
||||
}
|
||||
|
||||
const targetPath = path.join(dir, entry.name)
|
||||
|
||||
if (loadedPaths.has(targetPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
loadedPaths.add(targetPath)
|
||||
|
||||
try {
|
||||
const registrar = require(targetPath)
|
||||
if (typeof registrar === 'function') {
|
||||
registrar(this)
|
||||
}
|
||||
} catch (error) {
|
||||
this._log('warn', `加载本地扩展 ${entry.name} 失败: ${error.message}`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_log(level, message, error) {
|
||||
const targetLevel = typeof level === 'string' ? level : 'info'
|
||||
const loggerMethod =
|
||||
logger && typeof logger[targetLevel] === 'function' ? logger[targetLevel].bind(logger) : null
|
||||
|
||||
if (loggerMethod) {
|
||||
loggerMethod(message, error)
|
||||
} else if (targetLevel === 'error') {
|
||||
console.error(message, error)
|
||||
} else {
|
||||
console.log(message, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new RuntimeAddonBus()
|
||||
Reference in New Issue
Block a user