{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://moji.kaitoranma.com/docs/schemas/common.json",
  "title": "mojiokoshi wire format: common definitions",
  "description": "Ingest と Subscribe で共通の値を定義する。文字おこしくんは、文字列の長さを、Unicode のコードポイントの数で数える（JSON Schema の maxLength と同じ数え方）。送り手は任意フィールドを省くか null にする（どちらも「無し」を表す）。受け手は未知のフィールドを無視する。",
  "$defs": {
    "ClientId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 256,
      "description": "クライアントが送る ID（Ingest の trackId / speakerId）を表す。"
    },
    "ServerId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 1024,
      "description": "文字おこしくんが送る ID（sessionId / trackId / speakerId / utteranceId）を表す。文字おこしくんはクライアントの ID から ID を派生させるので、ServerId は ClientId より長くなりうる。"
    },
    "Token": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4096,
      "description": "セッション単位のトークン（Ingest は ingest:write、Subscribe は transcript:read）を表す。トークンは API キーではない。"
    },
    "ProtocolVersion": {
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991,
      "description": "プロトコルのバージョンを表す。現行のバージョンは、Ingest が 1、Subscribe が 1 である。"
    },
    "Timestamp": {
      "type": "number",
      "minimum": 0,
      "description": "エポックミリ秒（UTC）の時刻を表す。"
    },
    "DisplayName": {
      "type": "string",
      "maxLength": 100,
      "description": "表示名を表す。表示名は空文字のこともある（表示側が代替名にする）。表示名は利用者が入力した文字列なので、受け手は、表示名を HTML として埋め込まないこと。"
    },
    "AvatarUrl": {
      "type": "string",
      "maxLength": 2048,
      "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://[^\\s/?#]+",
      "description": "アバター画像の絶対 URL を表す。スキームには http: か https: だけを使える（javascript: や data: は使えない）。受け手の検証（WHATWG URL の解析）は少し広い形も受け付けるが、送る側は http(s)://host/... の形にすること。"
    },
    "ClientName": {
      "type": "string",
      "maxLength": 100,
      "description": "クライアントの識別名（ログ用）を表す。"
    },
    "ClientSpeaker": {
      "type": "object",
      "description": "Ingest でクライアントが宣言する話者を表す。",
      "required": [
        "speakerId",
        "displayName"
      ],
      "properties": {
        "speakerId": {
          "$ref": "common.json#/$defs/ClientId"
        },
        "displayName": {
          "$ref": "common.json#/$defs/DisplayName"
        },
        "avatarUrl": {
          "anyOf": [
            {
              "$ref": "common.json#/$defs/AvatarUrl"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "ServerSpeaker": {
      "type": "object",
      "description": "Subscribe で文字おこしくんが送る話者を表す。",
      "required": [
        "speakerId",
        "displayName"
      ],
      "properties": {
        "speakerId": {
          "$ref": "common.json#/$defs/ServerId"
        },
        "displayName": {
          "$ref": "common.json#/$defs/DisplayName"
        },
        "avatarUrl": {
          "anyOf": [
            {
              "$ref": "common.json#/$defs/AvatarUrl"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "AudioFormat": {
      "type": "object",
      "description": "音声の形式を表す。opus では、sampleRate に 48000 だけを使える（1/2ch）。pcm_s16le は、符号付き 16 bit リトルエンディアンの PCM を表す。公式の文字おこしくん（moji.kaitoranma.com）は、pcm_s16le で 8000〜48000 Hz と 1/2ch を受け付ける（2ch はインターリーブする）。扱えない組み合わせには、文字おこしくんが unsupported_format を返す。",
      "required": [
        "codec",
        "sampleRate",
        "channels"
      ],
      "properties": {
        "codec": {
          "enum": [
            "opus",
            "pcm_s16le"
          ]
        },
        "sampleRate": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991,
          "description": "サンプリングレート（Hz）を表す。"
        },
        "channels": {
          "enum": [
            1,
            2
          ]
        }
      }
    },
    "ErrorCode": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "examples": [
        "unauthorized",
        "protocol_version_unsupported",
        "invalid_message",
        "unknown_track",
        "unknown_speaker",
        "channel_in_use",
        "track_limit_exceeded",
        "unsupported_format",
        "rate_limited",
        "session_ended",
        "superseded",
        "quota_exceeded",
        "internal"
      ],
      "description": "エラーコードを表す。既知のコードは errors.json にある。文字おこしくんはコードを互換性のある変更として追加するので、受け手は未知のコードも受け入れ、fatal に従って扱うこと。"
    },
    "WireError": {
      "type": "object",
      "description": "エラーの内容を表す。fatal が true なら、文字おこしくんはこの後に接続を閉じる。",
      "required": [
        "code",
        "message",
        "fatal"
      ],
      "properties": {
        "code": {
          "$ref": "common.json#/$defs/ErrorCode"
        },
        "message": {
          "type": "string",
          "description": "人が読むための説明（英語）を表す。文言は互換性の対象外である。"
        },
        "fatal": {
          "type": "boolean"
        }
      }
    }
  }
}
