チャットプロトコル
Minara ゲートウェイ上でサードパーティ製チャットクライアントを構築するための完全な仕様。ターンループ、ストリーミングイベント、履歴の再構築、インタラクション、添付ファイル、モデル選択を網羅します。
このページは、Minara チャットプロトコルの統合仕様書です。 コミュニティ製 Web ターミナル、モバイルアプリ、メッセージングチャネルブリッジ、自動化スクリプトなど、独自 UI でゲートウェイとの完全な会話を実行するために必要な内容をすべて記述しています。組み込みの Web UI は、プライベートエンドポイントを一切使用せず、まったく同じ仕様に従っています。
準拠クライアントが扱う主要なインターフェースは 4 つです。
| インターフェース | 役割 |
|---|---|
POST /v1/chat/stream | エージェントのターンを開始する |
GET /v1/stream(WebSocket) | chat チャネルでそのターンのイベントを受信する |
GET /v1/sessions* | 永続化された履歴の一覧表示、読み取り、検索を行う |
POST /v1/interactions/:id/answer | エージェントから返ってきた質問に回答する |
この仕様をラップする npm パッケージが 2 つあり、TypeScript クライアントでは配管部分を自前で実装する必要がありません。
@minara/typesは、ワイヤー型(ChatStreamEvent、ChatAttachment、質問ペイロード)を提供します。@minara/gateway-clientは、型付き HTTP クライアント、多重化 WebSocket クライアント、後述のターンモデル SDK(reduceAgentEvent、sessionRowsToTurns)を提供します。
どちらのパッケージも UI 依存ゼロの純粋な ESM です。TypeScript 以外のクライアントは、同じ JSON の仕様を直接実装してください。すべてのエンドポイントは OpenAPI spec で確認できます。
認証
ゲートウェイが GATEWAY_AUTH_TOKEN を設定した状態で動作する場合、HTTP リクエストには Authorization: Bearer <token> を付与し、WebSocket のアップグレードリクエストには ?token=<token> を付与します(ブラウザの WebSocket はヘッダーを設定できないため)。この環境変数が設定されていない場合、ゲートウェイはローカルからの匿名リクエストを受け付けます。
ターンループ
1 回の会話交換は 3 つのステップで構成されます。
- ユーザーメッセージを
POST /v1/chat/streamに送信します。ゲートウェイは即座に{ session_id, kind, is_new }を返し、バックグラウンドでターンを実行します。 - 返された
session_idをキーとして、多重化 WebSocket のchatチャネルをサブスクライブします。ターンのイベントはチャネルごとのシーケンス番号付きで順番に届きます。再接続時は最後に受信したseqから再開できます。フレームフォーマット、サブスクライブのハンドシェイク、リプレイ、コントロールフレームの仕様は ストリームプロトコルのページ を参照してください。 doneまたはerrorが届くまで、各イベントを処理中のアシスタントターンに反映していきます。
理論上、WebSocket をサブスクライブする前にレスポンスが届く可能性がありますが、ゲートウェイはセッションごとに進行中のターンのすべてのイベントをバッファリングしています。fromSeq: 0 で subscribe すると、バッファの先頭からリプレイされます。したがって、POST の返却後にサブスクライブしても常に安全です。
リクエストフィールド
message は唯一の必須フィールドです。リクエストボディの全フィールドは以下のとおりです。
| フィールド | 目的 |
|---|---|
message | ユーザーのテキスト(または音声の文字起こし) |
session_id | 既存の会話を継続する。新規セッションを開始する場合は省略 |
session_kind | 新規セッションの種別(デフォルトは chat。institution、markets、workflow、strategy-studio、data-studio、messaging も指定可能) |
attachments | アップロード済みファイルの参照。添付ファイルと音声 を参照 |
voice_input_key | 元のマイク録音の FileStore キー |
model | このターンを特定のモデル ID で実行する。モデル選択 を参照 |
reasoning_effort | このターンの思考ティア(minimal / low / medium / high) |
retry | true にすると、重複ターンを積み上げる代わりに、セッションの最新ターンを置き換える |
surface | web(デフォルト)または cli。cli はシステムプロンプトからカスタム URI のワイヤープロトコルを省略する |
prompt_mode | full(デフォルト)または minimal。軽量なプロンプトスケルトンを必要とするヘッドレス呼び出し元向け |
各パラメーターの詳細なセマンティクスは エンドポイントリファレンス を参照してください。
ストリームイベント
すべてのイベントは { type, data } の形式です。公式のユニオン型は packages/types/src/chat-stream.ts の ChatStreamEvent で定義されており、SDK からは AgentEvent として再エクスポートされます。関心事別に分類すると以下のとおりです。
| グループ | イベント | クライアントの責務 |
|---|---|---|
| ハンドシェイク | start | protocol_version を確認する(バージョニング を参照)。supported_blocks を読み取る |
| セッション識別 | session、session_title | セッション ID をバインドし、タイトルを更新する |
| ターンテキスト | text_delta、reasoning_delta、response | テキストを追記する。response は text_delta が 1 件も届かなかった場合のフォールバック |
| ツール呼び出し | tool_call_start、tool_call_result | 呼び出しカードをレンダリングする。テキストと並行して発生することがある |
| サブツールの進捗 | sub_tool_call、sub_tool_text_delta、sub_tool_thinking_delta、tool_progress | 任意。長時間実行ツールがその内部状態をここにストリームする |
| リッチ UI | ui_block | レンダリングするか無視する。UI Block プロトコル を参照 |
| インタラクション | pending_question_added、pending_question_resolved | インタラクション:エージェントからの質問 を参照 |
| ステアリング | user_interjection、context_compacted | 注入されたユーザーバブルを挿入し、コンパクション通知を表示する |
| 進捗 | phase、skill_activated、todo_snapshot、goal | 任意のステータス表示 |
| 終了 | done、error | 必ずどちらか 1 つが届く。ターンを最終状態に切り替える |
text_delta を追記し、done / error で停止するだけで準拠クライアントとして成立します。他のグループはすべて、なくても動作する範囲でエクスペリエンスを向上させるものです。ユニオン型は今後も拡張されるため、未知のイベントタイプはスキップするだけにし、エラーとして扱ってはいけません。
done には注目すべき省略可能フィールドが 2 つあります。POST /v1/chat/interrupt でユーザーがターンを停止した場合は interrupted: true、注入が間に合わなかったステアリングメッセージの一覧は pending_interjections: string[] として届きます(これらは通常のメッセージとして再送してください)。
SDK リデューサー
@minara/gateway-client は、組み込み Web UI が実際に使っているリデューサーをそのままエクスポートしています。イベントを 1 件ずつ AssistantTurn(テキスト、ツールカード、UI ブロックの順序付きセグメントとツール呼び出し状態)に反映し、オプションのコールバックを通じてサイドエフェクトをルーティングします。
import {
createMultiplexClient,
createAgentStreamState,
reduceAgentEvent,
type AssistantTurn,
} from "@minara/gateway-client";
let turn: AssistantTurn = {
id: "t1", toolCalls: [], segments: [], skills: null,
todos: [], status: "streaming",
};
const state = createAgentStreamState();
const ws = createMultiplexClient({ baseUrl, apiKey });
const sub = ws.subscribe("chat", sessionId, {
onEvent: (ev) =>
reduceAgentEvent("t1", ev, {
sessionKind: "chat",
patchAssistant: (fn) => { turn = fn(turn); render(turn); },
onPendingQuestionAdded: (q) => showQuestionPanel(q),
onDone: () => sub.unsubscribe(),
}, state),
});このリデューサーを使うと、リプレイ時の重複抑制されたツール開始や、インターリーブされたセグメントの順序といったエッジケースを含め、ターン状態が Web UI とバイト単位で一致することを保証できます。
履歴
永続化されたセッションは、リロードや再接続後の正式な情報源です。
GET /v1/sessions?kind=&origin=&limit=&offset=はセッション一覧を最終更新日時の降順で返します。GET /v1/sessions/:idはメッセージ行として完全な会話ログを返します。アシスタント行にはストリームが生成した順序付きセグメントとツール呼び出しレコードを含むmetadataブロブが付属しており、再構築時にユーザーがライブで見た内容を忠実に再現できます。レスポンスにはis_streaming(ライブ WebSocket バッファに再接続する)とpending_questions(未解決の質問を再レンダリングする)も含まれます。GET /v1/sessions/search?q=はセッションをまたいでメッセージ内容の全文検索を実行し、最もマッチするスニペットを返します。
SDK の sessionRowsToTurns(detail.messages) は行データをライブのリデューサーが構築するのと同じ Turn[] 形式に変換します。これにより、ライブストリーミングと履歴の両方で同一のレンダリングパスを使用できます。
import { sessionRowsToTurns, type SessionDetail } from "@minara/gateway-client";
const detail: SessionDetail = await fetch(`${baseUrl}/v1/sessions/${id}`)
.then((r) => r.json());
const turns = sessionRowsToTurns(detail.messages);
if (detail.is_streaming) {
// 再接続:fromSeq を 0 にして chat チャネルをサブスクライブし、リデュースを継続する
}インタラクション:エージェントからの質問
ターンの途中でエージェントが入力を必要とする場合(確認のための質問、選択肢の提示、資金移動アクション前の確認など)、pending_question_added を発行しつつ、他の処理を続行します。ペイロードには、質問 ID、リクエストの種別(select、confirm、input、secret)、型付き選択肢を含む質問リスト、および asked_by の出所情報が含まれます。
クライアントはプロンプトをレンダリングし、HTTP 経由で回答します。各質問は 0 始まりの questionIndex をキーとした answers[] エントリで返します。
POST /v1/interactions/:id/answer
{ "answers": [ { "questionIndex": 0, "selected_labels": ["Confirm"] } ] }SDK はこのエンドポイントを answerInteraction としてラップしています。
import { createGatewayClient } from "@minara/gateway-client";
const gateway = createGatewayClient({ baseUrl, apiKey });
const outcome = await gateway.answerInteraction(q.id, [
{ questionIndex: 0, selected_labels: ["Confirm"] },
]);
if (outcome === "not_pending") closeWidget(); // 他所で回答済み、またはタイムアウト"not_pending"(ワイヤ上は HTTP 404)は、複数のクライアントが同じ質問を開いている場合に想定される結果であり、エラーではありません。
select と confirm の回答は選択した選択肢の label テキストを selected_labels に入れます。自由入力および input の回答は代わりに free_text を使います。secret リクエストはこのエンドポイントでは回答しません。クライアントはペイロードの writeEndpoint に指定された認証情報ルートに値を書き込み、その結果のみを回答します。完全なセマンティクスは エンドポイントリファレンス を参照してください。
有効な回答が送信されると、ゲートウェイはすべてのサブスクライバーに pending_question_resolved を発行します。これにより、複数のクライアントがポーリングなしで同期されます。リロード時には、未解決の質問が GET /v1/sessions/:id の pending_questions として返ってきます。
資金移動アクションの確認も kind: "confirm" として同じ仕組みで処理されます。確認 UI をレンダリングできないクライアントは、プログラムで自動回答してはいけません。回答されなかった確認はタイムアウトし、アクションはキャンセルされます。これが安全なデフォルト動作です。
添付ファイルと音声
ファイル入力は 2 ステップのフローです。まずバイトデータをアップロードします。
POST /v1/files (multipart/form-data, field name "file")
→ { "key": "chat/files/2026/07/chart.png", "url": "/v1/files/…",
"mediaType": "image/png", "size": 48213, "uploaded_at": "2026-07-17T…" }次に返されたキーをターンリクエストで参照します。
{
"message": "what does this chart show?",
"attachments": [
{ "key": "chat/files/2026/07/chart.png", "filename": "chart.png",
"media_type": "image/png", "size": 48213, "kind": "image" }
]
}kind は image、pdf、spreadsheet、text、office のいずれかです。制限事項:1 ターンあたり最大 8 件、画像 1 件あたり最大 5 MiB、その他のファイル 1 件あたり最大 20 MiB。GET /v1/files/:key でバイトデータを取得でき、履歴での画像添付ファイルのレンダリングにも同じエンドポイントを使用します。
音声入力も同じストアを使います。POST /v1/voice/transcribe?persist は録音を文字起こしして、テキストと FileStore キーの両方を返します。テキストを message に、キーを voice_input_key に設定して送信することで、履歴から元の音声を再生できます。
モデル選択
GET /v1/llm/available-models は、アクティブなプロバイダー接続が提供するモデル一覧を返します。ターンリクエストで model を指定すると特定のモデルを使用でき、reasoning_effort で思考バジェットを調整できます。この設定はそのターンのみに適用され、デプロイのデフォルト設定(PUT /v1/llm/default-model)は変更されません。したがって、同一ゲートウェイに接続している複数のクライアントはそれぞれ独立した選択を維持できます。無効な値はターン開始前に 400 エラーとなります。
バージョニングと互換性
プロトコルは追加方式で進化します。新しいイベントタイプや新しい省略可能フィールドは今後も追加されます。準拠クライアントは未知のイベントタイプをスキップし、未知のフィールドを無視します。同一プロトコルバージョン内では、既存のイベントとフィールドの意味は変わりません。
start イベントがバージョンを明示的に宣言します。
{ "type": "start", "data": { "session_id": "chat_...", "protocol_version": 1 } }protocol_version は既存のイベントやフィールドに破壊的変更が入った場合にのみ増加します。追加的な変更では増加しません。このフィールドを持たない start イベントはバージョン 1 とみなします。ストリームを拒否するのは、宣言されたバージョンがクライアントのビルド時の対応バージョンより大きい場合だけです。同じか小さいバージョンは常に安全に消費できます。
npm パッケージも同じ規律に従います。ワイヤ層の破壊的変更は @minara/types と @minara/gateway-client のメジャーバージョンを引き上げます。TypeScript クライアントはメジャーを固定し、マイナーを自由に更新できます。追加的な変更は SDK リデューサーが吸収するため、クライアントコードの変更は不要です。
準拠チェックリスト
最小限の準拠クライアントは以下の 3 点を満たします。
POST /v1/chat/streamを送信し、返されたsession_idをバインドする。/v1/stream上でchat:<session_id>をサブスクライブし、text_deltaをレンダリングして、done/errorで終了する。未知のイベントタイプはスキップする。- ロード時に
GET /v1/sessions/:idから履歴を再構築する。
フル機能のクライアントは、ツール呼び出しカード、ui_block レンダリング、インタラクション回答フロー、添付ファイル、音声、ターンごとのモデル上書き、セッション検索を追加します。各機能は独立しているため、任意の順序で導入できます。