Prism-Shadow/model-message-stream-protocol

MMSP自体の開発のための固定ワークフロー — モデルサポートの追加や更新、ページの変更をカバーします。このリポジトリで新しいモデルやプロトコルバージョンのサポートを求められたとき、llmsdk_docsの同期、プロバイダクライアントの実装、プレイグラウンド、トレーサー、サイトの変更を行う際に使用します。ドキュメント同期、ライブAPIキャプチャ、Python/TypeScriptペア実装、モデルスコープのE2Eテスト、3つのページのUIルールをカバーします。

model-message-stream-protocol とは?

これはMMSP(Model Message Stream Protocol)リポジトリ自体の開発ワークフローを定義したスキルです。開発者が新しいモデルプロバイダのサポート追加や既存モデルの更新を求められた際に、公式ドキュメントのスナップショット取得から、APIのライブキャプチャ、Python/TypeScriptクライアントの実装、E2Eテスト、UIページの変更までの一連のプロセスを段階的に指示します。このワークフローはClaude CodeなどのAIエージェント向けに設計されており、各段階でユーザーへの確認が必要な場合は「停止して尋ねる(stop and ask)」と明示され、AIが勝手に推測して進むことを防ぎます。特に、実装の唯一の正しい参照源は公式ドキュメントと実際のAPIキャプチャであり、AIの内部知識やサードパーティの情報源に頼らないことを厳格に定めています。また、セキュリティモデルとして、認証情報を明確に分離し、プロバイダごとに適切な環境変数から読み取ることを強制します。

対応✓Claude Code✓Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Prism-Shadow/model-message-stream-protocol/tree/HEAD/.agents/skills/mmsp-dev

Installed? Explore more 生産性&コラボレーション skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

MMSP 開発ワークフロー

モデルサポートの追加または更新は、以下のステージを順に実行します。各ステージで停止して尋ねると記載されている場合は、一時停止してユーザーに確認し、問題が解決するまで続行せず、自分でギャップを埋めてはいけません。

ディレクトリマップ

llmsdk_docs/<model_version>/      公式ドキュメントのスナップショット、モデル世代ごとに1フォルダ(README.md + docs/)
api_captures/<protocol>/          Git管理外の生APIキャプチャ:リクエストペイロード + ストリームイベント
src_py/mmsp/<client_type>/        Pythonクライアント、クライアントタイプごとに1フォルダ:<vendor>_official/ または <protocol>/
src_py/mmsp/auto_client.py        クライアントタイプ名からクライアントを作成します。モデルIDのファミリーがその公式クライアントを指定します
src_ts/src/<client_type>/         TypeScriptクライアント。Pythonフォルダをミラーリングします
src_ts/src/autoClient.ts          TypeScriptルーティング。auto_client.py をミラーリングします
src_py/tests/test_client.py       パラメータ化されたE2Eテスト(環境変数でゲートされる AVAILABLE_MODELS)
src_ts/tests/client.test.ts       TypeScript向けの同上
changelog/unreleased/             未リリースの変更が保存される場所。リリース時にバージョン名にリネーム
changelog/<version>/              リリースサマリー(README.md)とエントリごとの詳細ファイル1つ
changelog/README.md               エントリ形式:メタデータブロック、本文ルール、バイリンガルペアリング
CHANGELOG.md                      リリースごとに1行の簡潔なリンクで changelog/ にリンク
src_py/mmsp/integration/          プレイグラウンド、トレーサーページ、MMSPサーバー。src_ts/src/integration/ にミラーリング
site/                             mmsp.penguin.ooo のAstroサイト
.agents/skills/mmsp-dev/kill-ai-slop/   以下のUI作業用のベンダー版AIスロップガイドとスキャナー
<name>.zh.md                      Chinese counterpart, required for every file in changelog/

ステージ 1 — 公式ドキュメントを llmsdk_docs/ に同期する

  • ソースはモデルベンダーの公式ドキュメントサイト(例:docs.anthropic.com、platform.openai.com、ai.google.dev)でなければなりません。サードパーティのミラー、ブログ記事、モデルの記憶を決して使用しないでください。
  • スナップショットを llmsdk_docs/<model_version>/ 以下に既存のフォルダ規約に従って保存し、そのフォルダを llmsdk_docs/README.md にリストアップします。このワークフローを実行することは、リポジトリの llmsdk_docs/ の編集禁止ルールに対する明示的な許可要求となります。
  • 取得したドキュメントが既存のスナップショットと異なる場合、新しい公式ドキュメントが優先されます:古いファイルをその場で更新します。
  • スナップショットは実装に十分な完全性を持つ必要があります:リクエスト/レスポンススキーマ、ストリーミングイベントシーケンス、思考出力、ツール呼び出し、使用量フィールド、エラーレスポンス。
  • 停止して尋ねる:公式URLが不明な場合、またはページがフェッチできない場合。ユーザーが手動で内容を貼り付けることができます。

ステージ 2 — ライブAPI交換を api_captures/ にキャプチャする

  • ゲート:プロバイダのAPIキー環境変数が設定され、使用可能でなければなりません。src_py/tests/test_client.py (AVAILABLE_MODELS ゲーティングと _create_client)と同じ環境変数とベースURLを使用します。キーが不足している場合は停止して尋ねるようにユーザーに依頼します。キーなしではワークフローを続行してはいけません。
  • プロバイダの公式SDK、またはドキュメントに正確に従った生のHTTPを使用して、思考機能を有効にした1つのストリーミングツールコールリクエストを実行し、ツール結果を送り返して、アシスタントターンがどのように再送信されるかをキャプチャに示します。
  • 完全な交換を修正せずに api_captures/<protocol>/ 以下に保存します(Git管理外)。例:round1.request.json と round1.stream.jsonl にすべての生ストリームイベントを順に含めます。決して認証情報を保存しないでください。
  • 交換の一部がJSONにシリアル化できない場合(バイナリペイロード、非JSON値を保持するSDKオブジェクト)、その str() 形式を保存し、そこから分析します。コンテナはパース可能に保ちます:テキストをJSONオブジェクトでラップして .jsonl が1行に1つのJSON値を保つようにします({"unserializable_str": "<str(event)>"}、イベントをストリーム順に保持)、または交換全体を何もシリアル化できない場合は兄弟の .txt に置きます。str() キャプチャはAPIが返したものの信頼できる記録です。イベントを削除したり、有効なJSONに手動編集したり、シリアル化に失敗したからといってドキュメントにフォールバックしたりしないでください。
  • 停止して尋ねる:APIエラー(無効なキー、クォータ不足、レート制限)が発生した場合。レスポンスをモックしたり、ドキュメントだけで続行したりしないでください。
  • キャプチャは主要な実装リファレンスであり、ドキュメントよりも優先されます:両者が矛盾する場合は、APIが実際に返したものを実装します。
  • 次にリプレイを調査します。キャプチャはAPIが送信するものだけを示し、受け取る必要があるものは示さないからです。アシスタントターンを繰り返し再送信し、毎回1つの候補フィールド(推論項目のid、そのコンテンツ、推論項目全体、保持したくなる各フィールド)を削除し、どの削除がモデルの動作を維持したままAPIにまだ受け入れられるかを記録します。その結果が、ステージ3が保存するものを決定します。調査結果をキャプチャの隣に保存します。

ステージ 3 — PythonおよびTypeScriptクライアントを実装する

  • クライアントタイプごとに1フォルダ、そのタイプにちなんで命名します:ベンダーごとの公式クライアント(openai_official/、anthropic_official/、google_official/、zai_official/、moonshot_official/、deepseek_official/、minimax_official/)はベンダー自身のAPIを話し、ベンダーのすべてのモデル世代を知っており、ベンダーのキーを環境から読み取ります;ワイヤープロトコルごとの互換クライアント(openai_responses/、openai_chat/、openai_chat_vllm_adapter/、openai_embedding/、ant_messages/、google_genai/、mmsp/)は任意のエンドポイントに対してそのプロトコルを話します。クラス名はフォルダ名にちなんで付けられ(OpenAIOfficialClient、OpenaiChatClient)、クライアントタイプはハイフン付きのフォルダ名です(openai-official、openai-chat)。
    • ベンダーのモデルの新しい世代は、ベンダーの公式クライアントに入ります。新しいプロトコル(キャプチャ + ドキュメント)をクライアントが現在送信しているものと比較します。世代が異なる場合(1つのキー名でも)、クライアント内で明示的なバージョン("4-6" in self._model、"gpt-6" in self._model)で世代を区別し、決して "claude" in model のような生のサブストリングで区別せず、古い世代も動作し続けるようにします。ユーザーが明示的に指示しない限り、世代を削除しないでください。
    • 新しいベンダーには、新しい公式クライアントフォルダ、auto_client.py / autoClient.ts の新しい <vendor>-official クライアントタイプ、およびモデルファミリーテーブルの新しい行が必要です。ファミリーは、ベンダーのモデルのすべてのIDが始まるプレフィックス(glm-、kimi-)で、大文字小文字を区別せずに一致します。
    • 複数のベンダーが提供する新しいワイヤープロトコルには、互換クライアントフォルダとクライアントタイプが必要です。互換クライアントはベンダーの癖を一切持ちません。
  • auto_client.py / autoClient.ts は、クライアントタイプの1つのテーブルから client_type が指定するクライアントを作成します。client_type がない場合、モデルIDが始まるファミリーがその公式クライアントを指定します。既知のファミリーに属さないIDはエラーを発生させ、入力を求めます。ルーティングには他のルールはありません:モデルIDのサブストリング一致はなく、公式クライアント外でのバージョン一致もありません。
  • 認証情報は、与えられた場所にのみ使用されます。 これがセキュリティモデルであり、すべてのクライアントコンストラクタがこれに従います。api_key として渡されたキーはそのまま使用されます。環境の汎用キー(openai_official およびOpenAIプロトコル互換クライアントの場合は OPENAI_API_KEY、anthropic_official および ant_messages の場合は ANTHROPIC_API_KEY、google_genai の場合は GEMINI_API_KEY)を読み取るクライアントは、エンドポイントも環境から来た場合にのみ読み取ります(OPENAI_BASE_URL / ANTHROPIC_BASE_URL / GEMINI_BASE_URL、またはプロバイダ自身のデフォルト):base_url が与えられて api_key がない場合、そのキーを別のエンドポイントに送信する代わりに、構築時にエラーを発生させます。それが utils の resolve_credentials / resolveCredentials です。それを呼び出し、再実装しないでください。その他の公式クライアント(deepseek_official、zai_official、moonshot_official、minimax_official、google_official)は、与えられたベースURLに関係なく自身の変数(DEEPSEEK_API_KEY、ZAI_API_KEY、MOONSHOT_API_KEY、MINIMAX_API_KEY、GEMINI_API_KEY)を読み取り、汎用のものは決して読み取りません。どちらもない場合は <VAR> is required for <Client>. というエラーを発生させます。すべての認証情報をベンダーSDKに明示的に渡し、「none」(TypeScriptでは authToken: null、Pythonではクリアされた auth_token)を含めます。SDKは渡されなかったものを自身の環境変数から埋めるからです。tests/env-credentials.test.ts / tests/test_env_credentials.py のオフラインテストは、制御された環境下ですべてのクライアントを構築し、SDKインスタンスが保持する認証情報を読み取ります。新しいクライアントをそれらに追加します。
  • リプレイは最小限である必要があり、網羅的ではありません。再生されたアシスタントターンは、モデルの動作を維持したままAPIに受け入れられなければなりません。ワイヤーアイテムをフィールドごとに再現する必要はありません。fidelity は例外チャネルであり、ペイロードのミラーではありません:フィールドがそこに場所を得るのは、ステージ2の調査で、それがなければリクエストが失敗するか、モデルが劣化することを示した場合のみです。APIが再生成または無視するプロバイダ生成ID(推論項目id、出力項目id)は含めないでください。APIが要求するフィールドは含めます:GPT-5.5の phase、Claudeの思考 signature、厳格な上流が要求する正確な推論フィールド名。
  • ユニバーサルフィールドがすでに持っているもの(思考テキスト、ツールコールの名前やパース済み引数)を決して fidelity にコピーしないでください。代わりにユニバーサルフィールドからワイヤーアイテムを再構築します。
  • tool_call_id はオプションではありません:常にプロバイダのコールIDをキャプチャしてリプレイします。ワイヤーフォーマットが項目IDとコールIDの両方を運ぶ場合、コールIDは結果をそのコールに関連付けるものです。
  • リファレンスクライアントに従い、再設計しないでください。 openai_official/ はResponsesスタイルのプロトコルの形状であり、openai_chat/ はChat Completionsの形状です:同じメソッド順序、同じ制御フロー、同じ名前。新しいクライアントはリファレンスに対する差分として読めるべきであり、それがレビューの方法だからです。
  • 上から下まで読みやすく保ちます。 読者は定義間を行き来することなく、1つのリクエストまたはレスポンスをファイルを通してまっすぐ追えるべきです。したがって、インライン化がデフォルトです:SDKの属性を直接読み取り(model_output.delta、model_output.item.call_id)、フィールドアクセス、使用量演算、エラーテキストを使用場所に保持します。ヘルパーを抽出するのは、本当に大きく自己完結したブロック(インライン化するとメインフローを埋もれさせる種類、例えば画像の取得とデコード)に対してのみであり、数行のために抽出してはいけません。リファレンスクライアント自身のプライベートメソッド(_convert_thinking_level_*、_convert_tool_choice)をミラーリングし、それらの隣に別のレイヤーを発明しないでください。dictとSDKオブジェクトの両方を受け入れるシムは決してその1つではありません。イベントはSDK自身の型で型付けされているからです。
  • クライアントはデルタのみを生成します。ベースクラスがアイテムをクローズします。 transform_model_output_to_uni_event / transformModelOutputToUniEvent は1つのワイヤーイベントを1つの UniEvent に変換し、その content_items はそのイベントが運ぶ .delta アイテムをワイヤー順に含み、決して .done アイテムを含みません。_streaming_response_internal / _streamingResponseInternal はリクエストを構築し、ストリームを開き、ワイヤーイベントごとに1つのイベントを生成します:プレーンなループで、アイテムの簿記、アキュムレータ、引数パース、使用量マージ、合成された停止はありません。usage_metadata / finish_reason は、ワイヤーがそれらを報告する場所(Anthropicの message_start と message_delta、Chat Completionsの終了と使用量チャンク、response.completed)で stop イベントに乗せられ、ベースクラスによってフィールドごとにマージされます。クライアントの stop イベントは決してパブリックストリームを終了しません。ユニバーサルなものがないワイヤーイベントは空のデルタイベントです:アイテムなし、nullの使用量と終了理由。ベースクラスはすべてのデルタを StreamItems(stream_items.py / streamItems.ts)に渡し、それらは到着時に送信し、次のアイテムが開始するかクライアントのストリームが終了したときに各アイテムをその .done アイテムでクローズします。その後ベースクラスは1つの停止イベントを発行するか、EmptyResponseError を発生させます。プロトコルを破るイベントはすべてのモードで StreamProtocolError を発生させ、共有のE2Eテストは assert_stream_grammar / assertStreamGrammar ですべてのクライアントのストリームをチェックします。
  • 1つのアイテムのデルタは連続しており、1つの fidelity.item_id を共有します。 モデル出力はシリアルなので、一度に1つのアイテムがストリーミングされ、プロバイダのアイテム終了イベント(content_block_stop、response.output_item.done、step.stop)はno-opとしてリストされます:アイテムを遅くまたは順序不同でクローズするゲートウェイは何も変えません。プロバイダ自身のIDを文字列化して fidelity.item_id として渡します:Anthropicのブロック index、Responsesイベントの item_id または item.id or item.call_id(ゲートウェイが送信しない場合は None / undefined)、Interactionsのステップ index、Chat Completionsのワイヤーフィールド(reasoning_content、reasoning、content、tool_calls)、generateContentのパートの種類(function_call、thought、inline_thinking、inline_data、text);埋め込みベクトルは何も持ちません。StreamItems はそれを取り除くため、コンシューマー、トレース、履歴に到達することはありません。
  • StreamItems がアイテムの終了を決定します。クライアントは決して決定しません。 デルタは、別の item_id を持つ場合、別の種類である場合(Interactionsの思考ステップがテキスト、画像、テキストと進むのは1つのインデックスの下で3つのアイテム)、またはアイテムをそれ自体で開始する場合(名前を運ぶ tool_call.delta(すべてのプロバイダがコールの最初のデルタで1回送信するため、Chat Completionsのコールは独自のIDを必要としません)、画像(inline_thinking、または image/* MIMEタイプの inline_data。一方、オーディオは1つのアイテムのチャンクでストリーミングされます)、埋め込みベクトル)に次のアイテムに属します。それ以外の場合は、現在ストリーミング中のアイテムを継続します:IDのないデルタもそうであり、ゲートウェイがそれらに付けるIDにかかわらずコールの引数もそうです。アイテムのIDの下で単独で送信されたフィデリティは、その種類が何であれそのアイテムのものです(画像思考の後のInteractionsの thought_signature)。そのファイルの種類ごとのテーブル(成長するフィールド、最初のデルタのヘッダーフィールド、チャンクの結合方法)がストリーム内の種類ごとの唯一のコードです:完了アイテムは、アイテムの最初のデルタで、成長するフィールドをすべてのチャンクの結合に置き換えたものです。

関連スキル