{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://moji.kaitoranma.com/docs/schemas/ingest-client-message.json",
  "title": "IngestClientMessage",
  "description": "Ingest（/v1/ingest）でクライアントが送る制御メッセージを表す。クライアントは、WebSocket のテキストフレーム 1 個に、JSON オブジェクトを 1 個入れる。音声はバイナリフレームで送るので、このスキーマの対象外である。",
  "oneOf": [
    {
      "$ref": "#/$defs/Hello"
    },
    {
      "$ref": "#/$defs/SpeakerUpsert"
    },
    {
      "$ref": "#/$defs/SpeakerRemove"
    },
    {
      "$ref": "#/$defs/TrackOpen"
    },
    {
      "$ref": "#/$defs/TrackClose"
    },
    {
      "$ref": "#/$defs/StateSynced"
    },
    {
      "$ref": "#/$defs/SpeechStart"
    },
    {
      "$ref": "#/$defs/SpeechEnd"
    },
    {
      "$ref": "#/$defs/Ping"
    }
  ],
  "$defs": {
    "Hello": {
      "title": "hello",
      "description": "クライアントは、接続の直後に hello を 1 回だけ送る。welcome を受け取るまで、ほかのメッセージを送らない。",
      "type": "object",
      "required": [
        "type",
        "protocolVersion",
        "token"
      ],
      "properties": {
        "type": {
          "const": "hello"
        },
        "protocolVersion": {
          "$ref": "common.json#/$defs/ProtocolVersion"
        },
        "token": {
          "$ref": "common.json#/$defs/Token"
        },
        "clientName": {
          "anyOf": [
            {
              "$ref": "common.json#/$defs/ClientName"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "SpeakerUpsert": {
      "title": "speaker_upsert",
      "description": "クライアントは、このメッセージで話者を追加・更新する。single トラックの話者の speaker_upsert は、そのトラックの track_open より前に送る。",
      "type": "object",
      "required": [
        "type",
        "speaker"
      ],
      "properties": {
        "type": {
          "const": "speaker_upsert"
        },
        "speaker": {
          "$ref": "common.json#/$defs/ClientSpeaker"
        }
      }
    },
    "SpeakerRemove": {
      "title": "speaker_remove",
      "description": "クライアントは、このメッセージで話者を削除する。その話者の single トラックは、先に track_close しておく。",
      "type": "object",
      "required": [
        "type",
        "speakerId"
      ],
      "properties": {
        "type": {
          "const": "speaker_remove"
        },
        "speakerId": {
          "$ref": "common.json#/$defs/ClientId"
        }
      }
    },
    "TrackOpen": {
      "title": "track_open",
      "description": "クライアントは、このメッセージでトラックを開き、バイナリフレームで使う channel 番号を割り当てる。",
      "type": "object",
      "required": [
        "type",
        "trackId",
        "channel",
        "format",
        "track"
      ],
      "properties": {
        "type": {
          "const": "track_open"
        },
        "trackId": {
          "$ref": "common.json#/$defs/ClientId"
        },
        "channel": {
          "type": "integer",
          "minimum": 0,
          "maximum": 65535,
          "description": "バイナリフレームのヘッダで、このトラックを表す番号である。クライアントは、番号を接続の中で一意にする（track_close の後は再利用してよい）。"
        },
        "format": {
          "$ref": "common.json#/$defs/AudioFormat"
        },
        "track": {
          "$ref": "#/$defs/IngestTrackMode"
        }
      }
    },
    "TrackClose": {
      "title": "track_close",
      "description": "クライアントは、このメッセージでトラックを閉じる。",
      "type": "object",
      "required": [
        "type",
        "trackId"
      ],
      "properties": {
        "type": {
          "const": "track_close"
        },
        "trackId": {
          "$ref": "common.json#/$defs/ClientId"
        }
      }
    },
    "StateSynced": {
      "title": "state_synced",
      "description": "クライアントは、接続の直後の状態の送り直し（全話者の speaker_upsert と全トラックの track_open）が終わったことを、このメッセージで知らせる。文字おこしくんは、クライアントが state_synced までに宣言しなかったトラックを閉じる。",
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "const": "state_synced"
        }
      }
    },
    "SpeechStart": {
      "title": "speech_start",
      "description": "クライアントは、このメッセージで発話区間の開始の目安を知らせる。capturedAt は、送信側の時計による参考値である。",
      "type": "object",
      "required": [
        "type",
        "trackId"
      ],
      "properties": {
        "type": {
          "const": "speech_start"
        },
        "trackId": {
          "$ref": "common.json#/$defs/ClientId"
        },
        "capturedAt": {
          "anyOf": [
            {
              "$ref": "common.json#/$defs/Timestamp"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "SpeechEnd": {
      "title": "speech_end",
      "description": "クライアントは、このメッセージで発話区間の終わりの目安を知らせる。文字おこしくんに speech_end が届くと、確定（final）が早く出る。",
      "type": "object",
      "required": [
        "type",
        "trackId"
      ],
      "properties": {
        "type": {
          "const": "speech_end"
        },
        "trackId": {
          "$ref": "common.json#/$defs/ClientId"
        },
        "capturedAt": {
          "anyOf": [
            {
              "$ref": "common.json#/$defs/Timestamp"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "Ping": {
      "title": "ping",
      "description": "クライアントは、生存確認のために ping を送る。文字おこしくんは pong を返す。",
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "const": "ping"
        }
      }
    },
    "IngestTrackMode": {
      "description": "single は、1 人の話者だけのトラックを表す（クライアントは、speakerId の話者を先に宣言しておく）。mixed は、複数の話者の声が入ったトラックを表す（diarize が true なら、文字おこしくんが話者を分離する）。",
      "oneOf": [
        {
          "type": "object",
          "required": [
            "mode",
            "speakerId"
          ],
          "properties": {
            "mode": {
              "const": "single"
            },
            "speakerId": {
              "$ref": "common.json#/$defs/ClientId"
            }
          }
        },
        {
          "type": "object",
          "required": [
            "mode",
            "diarize"
          ],
          "properties": {
            "mode": {
              "const": "mixed"
            },
            "diarize": {
              "type": "boolean"
            }
          }
        }
      ]
    }
  }
}
