Skip to content

WebSocket Message Types

Client text frames use action; server text frames use type. The lists below are reverse-enumerated from the main handler and the bundled frontend. Fields marked internal belong to first-party UI flows and can change without a public protocol version bump.

Client → server actions

Conversation actions

start_session

json
{ "action": "start_session", "input_type": "audio", "new_session": false }

Valid input_type: audio, screen, camera, text, avatar_drop_image, user_image.

For an ordinary audio session, new clients are strongly encouraged to send the complete voice_input_control snapshot below before start_session. A legacy client that has sent no control message receives a one-time generation-0 Core lease when the ordinary audio session starts. Game audio never uses that compatibility path.

voice_input_control

json
{
  "action": "voice_input_control",
  "event": "lease_sync",
  "engaged": true,
  "owner": "core",
  "hard_muted": false,
  "focus_suppressed": false,
  "lease_generation": 1
}

event is one of lease_sync, hard_mute, hard_unmute, focus_suppress, focus_resume, game_takeover, or game_release. lease_sync requires the complete owner, hard_muted, and focus_suppressed snapshot. owner is none, core, or game. engaged uses this exact claim matrix:

JSON valueWindow stateVoice-connection claim
trueActive recording or reconnecting an active recordingClaims
falsePassive auxiliary windowDoes not claim
omittedLegacy-client compatibilityClaims

Only the literal JSON value false suppresses the claim. New passive clients must send it explicitly.

Generations are scoped to one WebSocket, strictly increase, and restart after reconnection. Any explicit control attempt permanently selects this strict path for the connection: an invalid or stale message is rejected and cannot fall back to the legacy generation-0 lease.

stream_data

Text:

json
{
  "action": "stream_data",
  "input_type": "text",
  "data": "Hello",
  "request_id": "client-turn-id",
  "memory_text": "optional text recorded instead of a scaffold",
  "source": "optional-source"
}

Image (screen, camera, avatar_drop_image, or user_image):

json
{
  "action": "stream_data",
  "input_type": "user_image",
  "data": "data:image/jpeg;base64,...",
  "request_id": "client-turn-id",
  "avatar_position": { "x": 10, "y": 20, "width": 300, "height": 500 }
}

The bundled client sends microphone audio as a binary frame:

text
bytes 0..3   ASCII "NEKO"
bytes 4..7   uint32 little-endian sample rate (16000 or 48000)
bytes 8..N   mono signed PCM16, little-endian

The PCM payload must be non-empty, even-sized, and no longer than 120 ms. Normal clients should send 10-32 ms frames. The server treats this frame as stream_data with input_type: "audio".

For compatibility, clients may send a JSON array of signed 16-bit PCM sample values instead. Audio is not base64:

json
{
  "action": "stream_data",
  "input_type": "audio",
  "data": [0, -12, 48, 103]
}

avatar_position is optional metadata paired with a fresh screen/image frame. Omitting it clears the previously cached position.

end_session and pause_session

json
{ "action": "end_session", "reason": "user_stop", "goodbye_active": false }
json
{ "action": "pause_session" }

Both end the current provider session; pause_session additionally marks the manager idle. The application WebSocket remains connected.

avatar_interaction

Ephemeral avatar gesture/touch request. The first-party payload includes interaction_id, tool_id, action_id, target: "avatar", timestamp, intensity, and when applicable touch_zone/pointer. Completion is reported by avatar_interaction_ack.

UI and lifecycle actions

ActionKey fieldsBehavior
pingReturns pong.
language_updatelanguageNo-op dispatch after the universal language update.
greeting_checkis_switch, reason, languageTriggers greeting only for a character switch or a reconnect gap over 15 seconds; also resynchronizes first-party focus/agent state.
cat_greeting_checkcat_duration_seconds, tier, was_autoRequests the return-from-cat-form greeting; duration is clamped to 0–7 days.
goodbye_stateactive, reasonEnables/clears the silent-goodbye delivery gate.
voice_play_startturnId/turn_id, sourceReports that buffered frontend audio actually began playing.
voice_play_endturnId/turn_id, sourceReports that the frontend audio queue fully drained.

Playback boundary events are important for proactive-chat arbitration: upstream generation completion is earlier than audible playback completion.

Capture bridge actions (internal)

ActionPurpose
capture_bridge_statusRegister/update the connected frontend capture client and its capabilities.
capture_bridge_responseResolve a capture-bridge request by its correlation fields.
screenshot_responseResolve the legacy request_screenshot flow. data is a data URL/base64 image; avatar_position is optional.

telemetry (internal, best effort)

json
{ "action": "telemetry", "kind": "counter", "name": "chat_sent", "value": 1, "dims": { "surface": "index_wide" } }

kind is counter, histogram, or event (fields replaces dims for event). The backend caps names, keys, values, and field count, drops unsupported types/non-finite values, and does not acknowledge delivery. Do not put user text or character names in telemetry fields.

Any action may also include language.

Server → client events

Session lifecycle

TypeFieldsMeaning
session_preparinginput_modeProvider startup is in progress.
session_startedinput_modeRequested audio or text Provider mode is ready. For audio, MicLease authorization is still independently enforced.
session_failedinput_modeStartup failed; a status event normally carries detail.
session_ended_by_serverinput_modeBackend/upstream ended the provider session.
catgirl_switchednew_catgirl, old_catgirlReconnect to the new character route.
pongReply to ping.

Text, audio, and recovery

gemini_response

The name is historical and is used for streamed assistant text from multiple providers:

json
{
  "type": "gemini_response",
  "text": "Hello",
  "isNewMessage": true,
  "turn_id": "server-turn-id",
  "request_id": "client-turn-id",
  "metadata": { "source": "optional" }
}

isNewMessage is true on the first visible chunk; subsequent chunks append to the same turn_id. request_id may be null for proactive or server-originated turns.

audio_chunk

json
{ "type": "audio_chunk", "speech_id": "speech-id" }

Exactly one binary audio frame follows the header. Correlate it with speech_id; see Audio Streaming.

Recovery and transcript events

TypeImportant fieldsPurpose
response_discardedreason, attempt, max_attempts, will_retry, message, request_idRoll back/clear a rejected partial response or prepare a retry. message can itself contain structured JSON.
user_transcripttranscript/turn metadataFirst-party live transcription display.
user_activityturn/interruption metadataBarge-in and user-activity coordination.
auto_close_micreason_code, api_type, messageSilence timeout closed the voice session.
repetition_warningnameRepetition recovery reset conversation state.

Status and display state

TypeImportant fieldsPurpose
statusmessagemessage is a JSON-encoded string containing { code, details? }; parse it again.
expressionexpression payloadDrive Live2D/VRM/MMD/PNGTuber expression state.
focus_stateactiveEnter/leave focused cognition display.
focus_chargecharge, timing/mode fieldsUpdate focus edge-glow charge.
focus_thinkingactiveToggle the transient thinking indicator.
topic_hintauthor, turn_idFrontend-only prelude bubble, not chat memory.
cancel_topic_hintturn_idRemove an orphaned prelude.
reload_pagemessageConfiguration changed; message is another status-style encoded JSON string.

Relevant microphone-control status codes are:

CodeMeaning
VOICE_INPUT_CONTROL_REJECTEDThe explicit control message was invalid or its generation was not newer. The connection remains on the strict control path.
VOICE_INPUT_LEASE_REQUIREDAn ordinary audio session was not authorized, so the Provider session was not started.

First-party workflow events

These are current UI integration events, not a stable external contract:

  • Agent: agent_notification, agent_task_update, agent_status_update.
  • Capture: request_screenshot, capture_bridge_request, screen_share_error.
  • Mini-games: mini_game_invite_options, mini_game_invite_resolved, game_window_state_change.
  • Music/tools: music_play_url, music_allowlist_add.
  • Activity/onboarding: activity_context_prompt.
  • Legacy/synchronization: system, cozy_audio.

avatar_interaction_ack is also first-party but has a small explicit envelope:

json
{
  "type": "avatar_interaction_ack",
  "interaction_id": "id",
  "accepted": true,
  "reason": "accepted",
  "turn_id": "turn-id"
}

Unknown server type values must be ignored safely so additive UI events do not break clients.