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',
      },
    })
  }
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐