把 Raycast AI 做成 OpenAI 兼容的 API

/ , , , , ,
摘要:
Raycast 更新之后,原来的转发项目都失效了。我逆向了新的请求签名,做了 raycast2api,可以部署在 Cloudflare Workers、Docker,也可以直接跑二进制。

Raycast 里自带了 AI,可以用到不少模型。网上有两个很好用的项目,能把它转成 OpenAI 兼容的 API,这样其他的客户端也可以直接用。一个是 raycast-relay,另一个是 missuo 的 raycast2api,我之前一直在用它们。

但是 Raycast 更新之后,这两个项目都用不了了。原因是新版的请求里多了一个签名,没有它,请求就过不了。其中 raycast-relay 已经归档,不再维护了,所以我干脆自己来做,把新的签名逆向出来,重新写了一个,名字就沿用 raycast2api,仓库在 xxxbrian/raycast2api。

它做了什么

整个项目其实只做一件事,就是把 OpenAI 格式的请求翻译成 Raycast 的请求,再把 Raycast 的响应翻译回来。对外只有几个接口。

接口

作用

GET /v1/models

模型列表

POST /v1/chat/completions

对话,支持流式和非流式

GET /health

存活检查

GET /ready

就绪检查

模型列表是每次都向 Raycast 去拉的,所以 Raycast 那边新增了模型,这里不用改任何东西。拉回来之后,我会按环境变量过滤一遍,ADVANCED 控制要不要包含高级模型,INCLUDE_DEPRECATED 控制要不要包含已经弃用的模型。

对话的转换也不复杂。OpenAI 消息列表开头的 system 消息,会变成 Raycast 的 system_instruction,user 和 assistant 的消息则换成 Raycast 的 author 加 content.text 的格式。请求的模型 ID 会去模型列表里查出对应的 provider 和内部模型名,每次请求还会生成一个新的 thread_id。

Raycast 返回的是 SSE,每一行 data: 里带着一小段文本和结束标记。流式的时候,我把每一段重新包装成 OpenAI 的 chat.completion.chunk 发出去,最后补上 [DONE]。非流式的时候,则是把所有的文本拼起来,一次性返回。因为 Raycast 不会给 token 的用量,所以非流式响应中的 usage 暂时填 0,流式响应则不提供用量统计。

V2 签名

新版 Raycast 的请求里,除了原来的 Authorization 和 X-Raycast-DeviceId,还多了 X-Raycast-Timestamp 和 X-Raycast-Signature-v2。前面几个都好办,难的只有最后这个签名要自己算出来。

签名的过程其实就是几步。首先对请求体做一次 SHA-256,然后把时间戳、设备 ID 和这个哈希值,各自做一遍 ROT13 加 ROT5 的编码,也就是字母按 ROT13 转换,数字按 ROT5 转换,其他字符保持不变。接着把三段用 . 连起来,最后用一个密钥做 HMAC-SHA256,得到的十六进制结果就是签名。

因为时间戳和设备 ID 都参与了签名,所以每次请求的签名都不一样,设备 ID 也和请求绑定在了一起。密钥在项目里有一个默认值,如果以后换了,也可以通过 RAYCAST_SIGNATURE_SECRET 这个环境变量覆盖。

这里全部用的是 Web Crypto 的接口,所以同一份代码在 Workers 和 Bun 里都能跑,不需要额外的依赖。

一份代码,三种部署

我想让这个项目尽量容易部署。有的人习惯用 Cloudflare Workers,有的人更喜欢 Docker,还有的人只想在自己的电脑上跑一下,所以我希望这三种方式都能支持。

幸好 Hono 本身就是跨运行时的框架,同一个 app 既可以直接作为 Worker 的入口,也可以交给 Bun.serve。环境变量也是通过 Hono 的 env() 去读,不管在哪个运行时里,写法都是一样的。

Mermaid diagram

Cloudflare Workers

入口就是 src/index.ts,wrangler.jsonc 里只写了名字和入口文件。README 里放了一个 Deploy to Cloudflare Workers 的按钮,点一下就能部署,再去后台把环境变量填上。

Docker

server.ts 只有几行,它把 Hono 的 app.fetch 交给 Bun.serve,端口默认是 3000。Dockerfile 分成两个阶段,第一个阶段用 bun build --compile 把它编译成一个独立的可执行文件,第二个阶段把这个文件拷进 debian:bookworm-slim,所以最后的镜像里只有一个二进制。

BASH
docker run -d \
  --name raycast2api \
  -p 3000:3000 \
  -e RAYCAST_BEARER_TOKEN=your-raycast-bearer-token \
  -e RAYCAST_DEVICE_ID=your-raycast-device-id \
  -e API_KEY=your-api-key \
  ghcr.io/xxxbrian/raycast2api:latest

镜像是在发布 Release 的时候,由 GitHub Actions 自动构建并推到 ghcr.io 的,同时支持 linux/amd64 和 linux/arm64。如果放在 Kubernetes 里,就可以把 /health 当作存活探针,把 /ready 当作就绪探针。前者只检查配置是不是齐全,后者除了检查配置,还会看最近对 Raycast 的请求是否正常;不正常的时候,会去试着拉一次模型列表,确认 Raycast 那边是不是通的。这个检查最多每 30 秒做一次。

Note

这一版健康探针会直接检查 RAYCAST_SIGNATURE_SECRET 是否已配置。即使签名逻辑本身有默认值,使用探针时也要显式设置这一项。

二进制

Docker 里用的那条编译命令,换一个 --target 就能编译出不同平台的版本。所以发布 Release 的时候,Actions 会把 Linux、macOS、Windows 乘以 x64、arm64 都编译一遍,其中 Windows 的 arm64 没有做,然后把结果上传到 Release 里。下载下来直接运行就行,不需要装任何运行时。

凭证

运行之前需要两个值,也就是 RAYCAST_BEARER_TOKEN 和 RAYCAST_DEVICE_ID。它们都可以从 Raycast 自己发出的请求里拿到:用 Proxyman 或者 Charles 这样的抓包工具,给 backend.raycast.com 开启 SSL 代理,然后在 Raycast 里随便发一条 AI 的对话,就能在请求头里看到 Authorization: Bearer ... 和 X-Raycast-DeviceId。

Warning

这两个值等于是你自己的 Raycast 账号,千万别泄露。如果没有设置 API_KEY,部署出来的接口就是对所有人开放的,别人也能拿你的账号去用。建议一定要设置;客户端那边用它当作 OpenAI 的 API Key 就行。

使用

部署好之后,在任何支持自定义 OpenAI 地址的客户端里,把 Base URL 填成部署的地址,API Key 填成自己设置的 API_KEY,就可以直接选模型对话了。

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...