{
  "asyncapi": "3.0.0",
  "id": "urn:app:gemtavern:game-link:v1",
  "info": {
    "title": "GemTavern Game Link Protocol",
    "version": "1.0.0",
    "description": "Local WebSocket protocol used by game mods to send character cards, live scene events, and prompt directives to GemTavern."
  },
  "defaultContentType": "application/json",
  "servers": {
    "local": {
      "host": "{deviceHost}:38471",
      "pathname": "/game-link/socket",
      "protocol": "ws",
      "description": "Local GemTavern listener on the user's device.",
      "variables": {
        "deviceHost": {
          "default": "127.0.0.1",
          "description": "GemTavern device hostname or LAN IP address."
        }
      }
    }
  },
  "channels": {
    "gameLinkSocket": {
      "address": "/game-link/socket",
      "title": "Game Link WebSocket",
      "summary": "Game mods send one JSON object per WebSocket message.",
      "servers": [
        {
          "$ref": "#/servers/local"
        }
      ],
      "messages": {
        "characterCardUpsert": {
          "$ref": "#/components/messages/CharacterCardUpsert"
        },
        "liveEvent": {
          "$ref": "#/components/messages/LiveEvent"
        }
      },
      "bindings": {
        "ws": {
          "method": "GET"
        }
      }
    }
  },
  "operations": {
    "sendCharacterCardUpsert": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/gameLinkSocket"
      },
      "title": "Send character card",
      "summary": "Create or update a Game Link character in GemTavern.",
      "messages": [
        {
          "$ref": "#/channels/gameLinkSocket/messages/characterCardUpsert"
        }
      ]
    },
    "sendLiveEvent": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/gameLinkSocket"
      },
      "title": "Send live event",
      "summary": "Send a live game moment that may update state or trigger a character reply.",
      "messages": [
        {
          "$ref": "#/channels/gameLinkSocket/messages/liveEvent"
        }
      ]
    }
  },
  "components": {
    "messages": {
      "CharacterCardUpsert": {
        "name": "character_card_upsert",
        "title": "Character card upsert",
        "summary": "Creates or updates a Game Link character in GemTavern.",
        "payload": {
          "$ref": "#/components/schemas/CharacterCardUpsertPayload"
        },
        "examples": [
          {
            "name": "Fake character card upsert",
            "payload": {
              "protocol": "gemtavern.game_link",
              "protocolVersion": 1,
              "type": "character_card_upsert",
              "integration": {
                "id": "example_game",
                "gameId": "example_game",
                "gameName": "Example Game",
                "modName": "Example Game Link",
                "modVersion": "0.1.0"
              },
              "character": {
                "id": "save-123:npc-45",
                "name": "Mira",
                "cardJSON": "{\"spec\":\"chara_card_v2\",\"data\":{\"name\":\"Mira\",\"description\":\"...\"}}"
              },
              "promptDirective": {
                "protocolVersion": 1,
                "sceneContext": "[Game scene context]\\nMira is standing near the old gate.",
                "autoEventGuide": "[Game event reply guide]\\nReply as Mira about this moment.",
                "directUserGuide": "[Game direct user message]\\nReply as Mira to the user's message first.",
                "promptVersion": "example-prompts-0.1.0"
              },
              "state": {}
            }
          }
        ]
      },
      "LiveEvent": {
        "name": "live_event",
        "title": "Live event",
        "summary": "Sends a live game moment to GemTavern.",
        "payload": {
          "$ref": "#/components/schemas/LiveEventPayload"
        },
        "examples": [
          {
            "name": "Fake live event",
            "payload": {
              "protocol": "gemtavern.game_link",
              "protocolVersion": 1,
              "type": "live_event",
              "integration": {
                "id": "example_game",
                "gameId": "example_game",
                "gameName": "Example Game",
                "modName": "Example Game Link",
                "modVersion": "0.1.0"
              },
              "character": {
                "id": "save-123:npc-45",
                "name": "Mira"
              },
              "scene": {
                "kind": "ambush",
                "family": "danger",
                "priority": 80,
                "line": "Mira hears footsteps behind the old gate."
              },
              "promptDirective": {
                "protocolVersion": 1,
                "sceneContext": "[Game scene context]\\nMira hears footsteps behind the old gate.",
                "autoEventGuide": "[Game event reply guide]\\nSpeak as Mira reacting to this danger.",
                "directUserGuide": "[Game direct user message]\\nReply to the user first, using the scene as context.",
                "promptVersion": "example-prompts-0.1.0"
              },
              "event": {
                "id": "save-123:event-999",
                "visibleText": "Mira hears footsteps behind the old gate.",
                "replyPolicy": "auto",
                "createdAtUnix": 1780000000
              },
              "state": {}
            }
          }
        ]
      }
    },
    "schemas": {
      "BaseEnvelope": {
        "type": "object",
        "additionalProperties": true,
        "required": ["protocol", "protocolVersion", "type", "integration", "character", "promptDirective"],
        "properties": {
          "protocol": {
            "const": "gemtavern.game_link"
          },
          "protocolVersion": {
            "const": 1
          },
          "type": {
            "type": "string",
            "enum": ["character_card_upsert", "live_event"]
          },
          "integration": {
            "$ref": "#/components/schemas/Integration"
          },
          "character": {
            "$ref": "#/components/schemas/Character"
          },
          "scene": {
            "$ref": "#/components/schemas/Scene"
          },
          "promptDirective": {
            "$ref": "#/components/schemas/PromptDirective"
          },
          "event": {
            "$ref": "#/components/schemas/Event"
          },
          "state": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CharacterCardUpsertPayload": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseEnvelope"
          },
          {
            "type": "object",
            "required": ["character"],
            "properties": {
              "type": {
                "const": "character_card_upsert"
              },
              "character": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Character"
                  },
                  {
                    "type": "object",
                    "required": ["id", "name", "cardJSON"]
                  }
                ]
              }
            }
          }
        ]
      },
      "LiveEventPayload": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseEnvelope"
          },
          {
            "type": "object",
            "required": ["event"],
            "properties": {
              "type": {
                "const": "live_event"
              },
              "event": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Event"
                  },
                  {
                    "type": "object",
                    "required": ["visibleText", "replyPolicy"]
                  }
                ]
              }
            }
          }
        ]
      },
      "Integration": {
        "type": "object",
        "additionalProperties": true,
        "required": ["id", "gameId", "gameName"],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Stable lowercase integration identifier, such as rimworld."
          },
          "gameId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "gameName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "modName": {
            "type": "string",
            "maxLength": 120
          },
          "modVersion": {
            "type": "string",
            "maxLength": 80
          }
        }
      },
      "Character": {
        "type": "object",
        "additionalProperties": true,
        "required": ["id", "name"],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160,
            "description": "Stable ID from the game save, not a display name."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "cardJSON": {
            "type": "string",
            "maxLength": 500000,
            "description": "SillyTavern-compatible character card JSON as a string."
          },
          "avatarPNGBase64": {
            "type": "string",
            "maxLength": 3000000
          }
        }
      },
      "Scene": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "kind": {
            "type": "string",
            "maxLength": 80
          },
          "family": {
            "type": "string",
            "maxLength": 80
          },
          "priority": {
            "type": "number"
          },
          "line": {
            "type": "string",
            "maxLength": 1000,
            "description": "Natural user-facing scene sentence."
          }
        }
      },
      "PromptDirective": {
        "type": "object",
        "additionalProperties": true,
        "required": ["protocolVersion", "sceneContext", "autoEventGuide", "directUserGuide"],
        "properties": {
          "protocolVersion": {
            "type": "integer",
            "minimum": 1
          },
          "sceneContext": {
            "type": "string",
            "minLength": 1,
            "maxLength": 12000
          },
          "autoEventGuide": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16000
          },
          "directUserGuide": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16000
          },
          "promptVersion": {
            "type": "string",
            "maxLength": 120
          }
        }
      },
      "Event": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "visibleText": {
            "type": "string",
            "maxLength": 2000
          },
          "replyPolicy": {
            "type": "string",
            "enum": ["auto", "silent"]
          },
          "createdAtUnix": {
            "type": "number"
          }
        }
      },
      "PairingQrPayload": {
        "type": "object",
        "required": ["type", "protocol", "protocolVersion", "integration", "token", "hosts", "responsePort", "expiresAtUnix"],
        "properties": {
          "type": {
            "const": "gemtavern_game_link_pair_v1"
          },
          "protocol": {
            "const": "gemtavern.game_link"
          },
          "protocolVersion": {
            "const": 1
          },
          "integration": {
            "$ref": "#/components/schemas/Integration"
          },
          "token": {
            "type": "string",
            "minLength": 1
          },
          "hosts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "port": {
            "type": "integer",
            "default": 38473
          },
          "responsePort": {
            "type": "integer",
            "default": 38473
          },
          "expiresAtUnix": {
            "type": "number"
          }
        }
      }
    }
  }
}
