openclaw(龙虾)如何通过本地的node接口请求国外的服务器,并通过国外的服务器请求claude
·


openclaw.json

{
"wizard": {
"lastRunAt": "2026-03-11T04:06:44.170Z",
"lastRunVersion": "0.1.8-fix.3",
"lastRunCommand": "onboard",
"lastRunMode": "local"
},
"auth": {
"profiles": {
"deepseek:default": {
"provider": "deepseek",
"mode": "api_key"
}
},
"order": {
"deepseek": ["deepseek:default"]
}
},
"models": {
"mode": "merge",
"providers": {
"anthropic": {
"baseUrl": "http://localhost:85/api/light/chat/claudeCodeNew",
"apiKey": "sk-xxx",
"api": "anthropic-messages",
"models": [
{
"id": "claude-sonnet-4-5",
"name": "Claude Sonnet 4.5",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 8192
}
]
}
}
},
"agents": {
"defaults": {
"model": {
"primary": "anthropic/claude-sonnet-4-5"
},
"models": {
"anthropic/claude-sonnet-4-5": {
"alias": "Claude Sonnet 4.5"
}
},
"workspace": "C:\\Users\\13796\\.openclaw\\workspace",
"compaction": {
"mode": "safeguard"
},
"maxConcurrent": 4,
"subagents": {
"maxConcurrent": 8
}
}
},
"messages": {
"ackReactionScope": "group-mentions"
},
"commands": {
"native": "auto",
"nativeSkills": "auto"
},
"hooks": {
"internal": {
"enabled": true,
"entries": {
"session-memory": {
"enabled": true
},
"command-logger": {
"enabled": true
},
"boot-md": {
"enabled": true
}
}
}
},
"gateway": {
"port": 18789,
"mode": "local",
"bind": "loopback",
"auth": {
"mode": "token",
"token": "25091c229ea32bd85220164f97053d2acaee731932954d2f"
},
"tailscale": {
"mode": "off",
"resetOnExit": false
}
},
"skills": {
"install": {
"nodeManager": "npm"
}
},
"meta": {
"lastTouchedVersion": "0.1.8-fix.3",
"lastTouchedAt": "2026-03-11T04:06:44.201Z"
}
}
node接口,非流式,转流式
url是
/api/light/chat/claudeCodeNew/v1/messages
openclaw.json里配置的是这个,v1/messages会自动拼接上
"baseUrl": "http://localhost:85/api/light/chat/claudeCodeNew",
const chatClaudeCodeV1Completions = async (req, res) => {
let token = req.headers['authorization']
if (token) {
token = token.replace('Bearer ', '')
const userRedis = await redisClient.get('user')
let userList = JSON.parse(userRedis)
let resultIndex = userList.findIndex((item) => {
let info = {}
try {
if (item.info) {
info = JSON.parse(item.info)
}
} catch (error) {
console.log(error)
}
return info.token === token
})
let userDb
if (resultIndex >= 0) {
let user = userList[resultIndex]
userDb = userList[resultIndex]
let info = {}
try {
if (user.info) {
info = JSON.parse(user.info)
}
} catch (error) {
console.log(error)
}
let apiDate = moment(Date.now()).format('YYYY-MM-DD')
let count = 100
if (info.numOfOneDayCanCallApi > 100) {
count = info.numOfOneDayCanCallApi
}
if (info.apiDate !== apiDate || info.numOfOneDayAlreadyCallApi < count) {
let completionRes = {}
// eslint-disable-next-line
if (global.isLocal && false) {
completionRes = {
data: {
id: 'd254213e-dd1d-43a2-948d-5178fe2ffb75',
type: 'message',
role: 'assistant',
model: 'deepseek-chat',
content: [
{
type: 'text',
text: '【模拟数据】Hello! 👋 How can I help you today?',
},
],
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 5,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
output_tokens: 11,
service_tier: 'standard',
},
},
}
} else {
completionRes = await axios.post(
`${baseURL}/api/light/chat/claudeCodeOnAzure`,
{
apiKey: process.env.apiKeyOnServer,
...req.body,
}
)
}
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
// 通过请求参数或环境变量切换输出格式: 'openai' | 'anthropic'
const streamFormat = 'anthropic'
const content = completionRes.data?.content?.[0]?.text || ''
const modelName = completionRes.data?.model || 'claude-sonnet-4-5'
const messageId = completionRes.data?.id || `chatcmpl-${uuidv4()}`
if (streamFormat === 'anthropic') {
// Anthropic 流式格式
// 1. message_start 事件
const messageStart = {
type: 'message_start',
message: {
id: messageId,
type: 'message',
role: 'assistant',
content: [],
model: modelName,
stop_reason: null,
stop_sequence: null,
usage: {
input_tokens: 0,
output_tokens: 0
}
}
}
res.write(`event: message_start\ndata: ${JSON.stringify(messageStart)}\n\n`)
// 2. content_block_start 事件
const contentBlockStart = {
type: 'content_block_start',
index: 0,
content_block: {
type: 'text',
text: ''
}
}
res.write(`event: content_block_start\ndata: ${JSON.stringify(contentBlockStart)}\n\n`)
// 3. content_block_delta 事件(发送实际内容)
if (content) {
const contentBlockDelta = {
type: 'content_block_delta',
index: 0,
delta: {
type: 'text_delta',
text: content
}
}
res.write(`event: content_block_delta\ndata: ${JSON.stringify(contentBlockDelta)}\n\n`)
}
// 4. content_block_stop 事件
const contentBlockStop = {
type: 'content_block_stop',
index: 0
}
res.write(`event: content_block_stop\ndata: ${JSON.stringify(contentBlockStop)}\n\n`)
// 5. message_delta 事件(包含 stop_reason)
const messageDelta = {
type: 'message_delta',
delta: {
stop_reason: 'end_turn',
stop_sequence: null
},
usage: {
output_tokens: 0
}
}
res.write(`event: message_delta\ndata: ${JSON.stringify(messageDelta)}\n\n`)
// 6. message_stop 事件
const messageStop = {
type: 'message_stop'
}
res.write(`event: message_stop\ndata: ${JSON.stringify(messageStop)}\n\n`)
res.end()
} else {
// OpenAI 流式格式
if (content) {
const chunk = {
id: messageId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: modelName,
choices: [
{
index: 0,
delta: {
content: content
},
finish_reason: null
}
]
}
res.write(`data: ${JSON.stringify(chunk)}\n\n`)
}
// 发送结束标记
const finalChunk = {
id: messageId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: modelName,
choices: [
{
index: 0,
delta: {},
finish_reason: 'stop'
}
]
}
res.write(`data: ${JSON.stringify(finalChunk)}\n\n`)
res.write('data: [DONE]\n\n')
res.end()
}
// 将计数更新逻辑放入任务队列,不阻塞响应
userCountQueue
.enqueue(async () => {
// 关键:在队列任务中,先从数据库刷新Redis,确保获取最新数据
await refreshRedisUser()
// 从Redis获取最新的用户数据
let userRedisLatest = await redisClient.get('user')
let userListLatest = JSON.parse(userRedisLatest)
let resultIndexLatest = userListLatest.findIndex((item) => {
let infoLatest = {}
try {
if (item.info) {
infoLatest = JSON.parse(item.info)
}
} catch (error) {
console.log(error)
}
return infoLatest.token === token
})
if (resultIndexLatest >= 0) {
let userLatest = userListLatest[resultIndexLatest]
let infoLatest = {}
try {
if (userLatest.info) {
infoLatest = JSON.parse(userLatest.info)
}
} catch (error) {
console.log(error)
}
// 基于最新的数据更新计数
if (apiDate === infoLatest.apiDate) {
infoLatest.numOfOneDayAlreadyCallApi =
typeof infoLatest.numOfOneDayAlreadyCallApi === 'number'
? infoLatest.numOfOneDayAlreadyCallApi + 1
: 1
} else {
infoLatest.apiDate = apiDate
infoLatest.numOfOneDayAlreadyCallApi = 1
}
console.log(
'info.numOfOneDayAlreadyCallApi',
infoLatest.numOfOneDayAlreadyCallApi
)
infoLatest = JSON.stringify(infoLatest)
let gptVersion = '4'
// 等待 editUserCount 完全执行完成(包括内部的数据库更新和 refreshRedisUser)
await editUserCount({
user: userLatest,
gptVersion,
info: infoLatest,
})
//#region 测试:验证数据是否正确写入
// 直接从 Redis 读取(editUserCount 内部已经刷新过了)
userRedisLatest = await redisClient.get('user')
userListLatest = JSON.parse(userRedisLatest)
resultIndexLatest = userListLatest.findIndex((item) => {
let infoLatest = {}
try {
if (item.info) {
infoLatest = JSON.parse(item.info)
}
} catch (error) {
console.log(error)
}
return infoLatest.token === token
})
if (resultIndexLatest >= 0) {
let userLatest = userListLatest[resultIndexLatest]
let infoLatest = {}
try {
if (userLatest.info) {
infoLatest = JSON.parse(userLatest.info)
}
} catch (error) {
console.log(error)
}
console.log(
'info.numOfOneDayAlreadyCallApi测试',
infoLatest.numOfOneDayAlreadyCallApi
)
}
//#endregion
}
})
.catch((error) => {
console.error('更新用户计数失败:', error)
})
//#endregion
//#region 保存消息到数据库(异步执行,不阻塞响应)
let message = ''
if (Array.isArray(req.body.messages) && req.body.messages.length > 0) {
message = req.body.messages[0]?.content[0]?.text
}
const uidForRobot = uuidv4()
const uidForFuntionCalling = uuidv4()
let functionContent = ''
const uid = uuidv4()
const now = Date.now()
let robotMessage = ''
if (
Array.isArray(completionRes.data.content) &&
completionRes.data.content.length > 0
) {
robotMessage = completionRes.data.content[0].text
}
req.headers = {
...req.headers,
version: '1',
platformos: 'claude code',
}
let talkId = ''
const talkRedis = await redisClient.get('talk')
let talkList = JSON.parse(talkRedis)
talkList = talkList ? talkList : []
const resultIndex2 = talkList.findIndex(
(item) => item.userId === userDb.uid
)
if (resultIndex2 >= 0) {
talkId = talkList[resultIndex2].uid
}
addMessageToDbV2025({
robotMessage,
uidForRobot,
uidForFuntionCalling,
functionContent,
threadId: '',
mjResponse: '',
mjPrompt: '',
//#region 新增参数
userDb,
gptVersion: 'claude3',
uid,
now,
assistantName: '',
talkId,
name: '',
messageType: '1',
message,
req,
platform: 'anthropicAi',
isVision: false,
audioUrlForRecorder: '',
chatGPTVersion: 'claude3',
})
//#endregion
} else {
res.send({
id: 'd254213e-dd1d-43a2-948d-5178fe2ffb75',
type: 'message',
role: 'assistant',
content: [
{
type: 'text',
text: `失败,请联系管理员,微信:xu1183391880,详情:api调用次数已经达到上限。如需增加调用次数,请联系管理员,微信号:xu1183391880`,
},
],
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 5,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
output_tokens: 11,
service_tier: 'standard',
},
})
}
} else {
res.send({
id: 'd254213e-dd1d-43a2-948d-5178fe2ffb75',
type: 'message',
role: 'assistant',
content: [
{
type: 'text',
text: `失败,请联系管理员,微信:xu1183391880,详情:您在环境变量里配置的ANTHROPIC_AUTH_TOKEN【无效】`,
},
],
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 5,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
output_tokens: 11,
service_tier: 'standard',
},
})
}
} else {
res.send({
id: 'd254213e-dd1d-43a2-948d-5178fe2ffb75',
type: 'message',
role: 'assistant',
content: [
{
type: 'text',
text: `失败,请联系管理员,微信:xu1183391880,详情:需要环境变量里配置ANTHROPIC_AUTH_TOKEN`,
},
],
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 5,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
output_tokens: 11,
service_tier: 'standard',
},
})
}
}
更多推荐

所有评论(0)