Raycast AI を OpenAI 互換 API にする

/ , , , , ,
簡体中文日本語
要約:
Raycast の更新で従来の中継プロジェクトが使えなくなった。新しいリクエスト署名を逆解析し、Cloudflare Workers、Docker、単体バイナリで動く raycast2api を作った。

Raycast には AI が組み込まれていて、さまざまなモデルが使える。これを OpenAI 互換 API に変換し、ほかのクライアントからも使えるようにする便利なプロジェクトが二つある。raycast-relay と、missuo の raycast2api だ。以前から両方を使っていた。

ところが Raycast の更新後、どちらも動かなくなった。新版のリクエストには署名が追加され、これがないと通らない。raycast-relay はすでにアーカイブされ、メンテナンスも終わっていたので、自分で作ることにした。新しい署名を逆解析して書き直し、名前は raycast2api を引き継いだ。リポジトリは xxxbrian/raycast2api にある。

何をするのか

このプロジェクトの仕事は一つだけだ。OpenAI 形式のリクエストを Raycast の形式に変換し、返ってきたレスポンスを元の形式に戻す。公開するエンドポイントも少ない。

エンドポイント

用途

GET /v1/models

モデル一覧

POST /v1/chat/completions

チャット。ストリーミング・非ストリーミングに対応

GET /health

生存確認

GET /ready

準備状態の確認

モデル一覧は毎回 Raycast から取得する。そのため、Raycast 側にモデルが追加されても、こちらを変更する必要はない。取得後は環境変数で絞り込み、ADVANCED で上位モデルを含めるか、INCLUDE_DEPRECATED で非推奨モデルを含めるかを決める。

会話の変換も複雑ではない。OpenAI のメッセージ一覧の先頭にある system メッセージは Raycast の system_instruction に、user と assistant のメッセージは 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 API だけを使っているので、追加の依存ライブラリなしで同じコードが 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 に push する。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 を設定しないと API は誰でも使える状態になり、他人が自分のアカウントを利用できてしまう。必ず設定することを勧める。クライアント側では OpenAI の API Key として使えばよい。

使い方

デプロイしたら、OpenAI の接続先を変更できるクライアントで、Base URL にデプロイ先の URL、API Key に設定した API_KEY を入力する。あとはモデルを選んでチャットできる。

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