mcpField
A
add-voice

add-voice

github.com/cursor開発14.5k323 回インストール今日更新
リポジトリ

add-voice について

add-voice は、既存アプリに Grok Speech to Speech を組み込むスキルです。双方向音声機能を開発する人向けに、リアルタイム WebSocket セッション、クライアント側の音声処理、安全な認証方法を説明します。

主な機能

  1. 01リアルタイム双方向音声セッション
  2. 02エフェメラルトークンによるクライアント保護
  3. 03マイク音声の取得と送信
  4. 04音声応答の受信と再生
  5. 05関数ツール呼び出しへの対応

活用シーン

  1. 01アプリにボイスモードを追加
  2. 02チャット UI に音声対話を接続
  3. 03既存の音声カスケードを置き換え
  4. 04リアルタイム文字起こしで会話を表示

MCP 対応

セッションで `mcp` ツールを設定する方法を説明しています。`web_search`、`x_search`、`file_search`、カスタム関数にも対応します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/cursor/plugins --skill add-voice

インストール後、Claude Code で/add-voiceを入力して読み込まれたか確認します。

詳細

音声を追加する

既存のアプリに Grok Speech to Speech を追加します。/add-voice を実行するか、Voice Mode と入力するか、「add Grok voice」と明確に指定してください。

目標

双方向の音声経路を構築します。ユーザーアプリのマイク入力と音声出力を、wss://api.x.ai/v1/realtime?model=grok-voice-latest と安全な認証でつなぎます。Cursor にはネイティブのマイク機能がないため、IDE ではなくアプリ(またはサンプルクライアント)を接続してください。

プロトコルを優先

言語に依存しないイベントループです。**TypeScript のサンプルを標準とします。**クライアント API が異なる場合(例:ws と websockets)に限り、短い Python 版も用意します。

ドキュメント

手順

  1. アプリの構成を確認する

    • スタック:なし、OpenAI Realtime、STT→LLM→TTS のカスケード、TTS/STT のみ。
    • クライアント:Web/Node/iOS/Android/サーバー。
    • カスケードまたは OpenAI Realtime がある場合は、以下の単一の双方向ループに置き換えます(URL、モデル、音声、イベントの差分)。エージェント以外で単発の聞き取りや発話が引き続き必要な場合に限り、単独の /v1/stt または /v1/tts を残してください。
  2. 認証

    • サーバー:Bearer XAI_API_KEY。
    • ブラウザー/モバイル:バックエンドから POST https://api.x.ai/v1/realtime/client_secrets を呼び出し、クライアントではエフェメラルトークンを使用します(Bearer またはブラウザーの sec-websocket-protocol:xai-client-secret.<token>)。
    • 有効期間の長いキーをクライアントバンドルに含めないでください。キーをチャットに貼り付けないでください。
  3. 接続とセッション

    • URL:wss://api.x.ai/v1/realtime?model=grok-voice-latest
    • 接続時:voice(デフォルトは eve)、instructions、turn_detection: { type: "server_vad" }(またはプッシュ・トゥ・トークの場合は null)を指定して session.update を送信します。アプリですでに別のサンプリングレートを標準化している場合を除き、PCM は 24 kHz を使用します。
    • audio.input.transcription.model: "grok-transcribe" を設定してください。設定しないとユーザーのトランスクリプトが届きません(conversation.item.input_audio_transcription.updated は差分ではなく累積です)。
    • 必要に応じてツールを設定します:web_search、x_search、file_search、mcp、カスタムの function。
  4. 音声 I/O(アプリ側)

    • 収音と再生には、セッションごとに 1 つの AudioContext を使用し、ユーザー操作中に作成します(自動再生ポリシーに対応するため)。24 kHz を要求し、ブラウザーから別のサンプリングレートが指定された場合は、送信前にリサンプリングします。
    • マイク → 約 100 ms のチャンクで AudioWorklet → input_audio_buffer.append(またはバイナリ転送)。WS とマイクを並行して開始し、先行して届いた音声をバッファーに入れて、接続時にフラッシュします。
    • response.output_audio.delta を受信したらすぐに再生します。チャンクが途切れずつながるよう、約 150 ms 先行させてスケジュールします。input_audio_buffer.speech_started を受信したら、キューに入っている音声をすべて停止します(割り込み)。
    • トランスクリプトの行:input_audio_buffer.committed(item_id)でユーザーの行を作成し、…transcription.updated で内容を更新します。アシスタントのテキストは response.output_audio_transcript.delta/.done から取得し、response.done でターンを終了します。
    • 関数ツールの場合:function_call_output を送信し、再生を完了してから response.create を送信します。
  5. Composer UI の規約

    • プライマリボタンは 1 つ、composer の右側に配置します。composer が空のとき → waveform アイコン(ストローク、例: Phosphor WaveformIcon weight="bold"。fill ウェイトは 16 px では塊のように描画されるため、決して使用しないでください)で、音声モードを開始します。テキストが入力されているとき → 従来の send 矢印。音声モードではそのテキストがライブセッションに送られます(conversation.item.create + response.create)。テキスト返信のストリーミング中 → 停止スクエア。
    • 音声がライブの間、同じボタンは アニメーションする波形(4 本のバー、幅約 3 px、間隔 2 px、高さ約 16 px、最小スケール 0.4 で 28 px のボタン内でも判読可能)を表示し、クリックでセッションを終了します。アニメーションはフェーズによって変化します。リスニングは遅く、スピーキングは速く、接続中/思考中はより遅く、やや暗く(不透明度 ≥ 0.75)。prefers-reduced-motion を尊重してください。X ボタンやパルスするリングは使用しません。
    • ステータスは composer の中に、その周囲ではなく 表示します。プレースホルダーは Connecting… / Listening… / Thinking… / Speaking… と表示し、さらに sr-only の role="status" を付けます。独立したステータス行は設けません。
    • マイクアイコンはディクテーション専用(/add-dictation)です。音声モードには決して使用しないでください。
  6. TS スケルトン(デフォルト)

const url = "wss://api.x.ai/v1/realtime?model=grok-voice-latest";
// Node: pass Authorization header. Browser: use xai-client-secret.<token> protocol.
const ws = new WebSocket(url /* , { headers: { Authorization: `Bearer ${token}` } } */);

ws.addEventListener("open", () => {
  ws.send(JSON.stringify({
    type: "session.update",
    session: {
      voice: "eve",
      instructions: "You are a helpful voice agent.",
      turn_detection: { type: "server_vad" },
    },
  }));
});

ws.addEventListener("message", (ev) => {
  const event = JSON.parse(String(ev.data));
  if (event.type === "response.output_audio.delta") {
    // decode base64 PCM and play
  }
});
  1. Python 版(アプリが Python の場合のみ)
import json, os, websockets

url = "wss://api.x.ai/v1/realtime?model=grok-voice-latest"
headers = {"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"}

async with websockets.connect(url, additional_headers=headers) as ws:
    await ws.send(json.dumps({
        "type": "session.update",
        "session": {
            "voice": "eve",
            "instructions": "You are a helpful voice agent.",
            "turn_detection": {"type": "server_vad"},
        },
    }))
    async for raw in ws:
        event = json.loads(raw)
        if event.get("type") == "response.output_audio.delta":
            pass  # decode and play
  1. 計測(最初の人間によるテストの前に)

    • /debug-voice を実行します。これは計画を提案したうえで、開発専用のログシンク(POST /api/voice/log → .voice-logs/<sessionId>.ndjson、gitignore 対象)、音声をバイト数のみに削減するクライアントロガー、および UI 内のセッション ID を、アプリ自身の言語で導入します。
    • 同じスキルに修正ループと、症状 → ログのシグネチャ → 修正の表が含まれています。
  2. スモークテスト

    • conversation.item.create + response.create によるテキストターン。音声またはトランスクリプトのイベントを確認します。
    • クライアントに長期間有効なキーが存在しないことを確認します(ビルド済みクライアントバンドルで環境変数名と client_secrets を grep します)。
    • ユーザーには ヘッドフォン を付けてアプリを渡してください。スピーカーの場合、マイクが返信を拾い、モデルが自分自身に応答します。これはエコーであり、ループのバグではありません。
    • /debug-voice で反復します。

対象外

  • 音声認識のみ(/add-dictation)、テキストの読み上げ(/add-read-aloud)
  • 画像生成およびテキストのみの推論
  • 実在しないエンドポイント、イベント、CLI フラグ

コメント