#shell/remote_protocol

    MoonBit-owned JSON contract between the reference browser workbench and native server. Protocol version 5 is carried by every packet and must match exactly.

    #Wire contract

    • Client requests: ResolveDirectory, OpenDocument, WatchDocument, CloseDocument, Hover, Definition, References, and DocumentSymbols.
    • Server packets: directory/document results, watched DocumentChanged pushes, pushed Diagnostics, semantic feature results, and RemoteError.
    • Position requests carry a document revision and UTF-16 offset; whole-document feature requests carry a revision. Request IDs correlate replies. Diagnostics deliberately have no request ID; watch pushes reuse the watch request ID.
    • Definition results carry an ordered locations array; an empty array means that no definition was found.
    • Decoders return structured errors for invalid JSON, version, packet shape, URI, or provider failure. provider_code preserves the lower-level category.
    • Reference results add line, column, and line-text preview fields. There is no semantic-token packet.

    The public surface is the packet/payload types plus protocol_version, negotiate_protocol_version, decode_client_packet, and decode_server_packet; see pkg.generated.mbti for exact fields.

    #Version negotiation

    Protocol version 5 is carried by every packet and must match exactly. There is no forward or backward compatibility window: a mismatch is a negotiation failure, not a downgrade.

    sequenceDiagram participant C as browser workbench participant S as host server C->>S: ClientPacket { version: protocol_version() } alt version matches S-->>C: ServerPacket { version, payload } else version differs S-->>C: RemoteError (negotiation failed) end

    ///|
    test "only the exact protocol version negotiates" {
    debug_inspect(
    (
    @remote_protocol.protocol_version(),
    @remote_protocol.negotiate_protocol_version(
    @remote_protocol.protocol_version(),
    ),
    @remote_protocol.negotiate_protocol_version(
    @remote_protocol.protocol_version() - 1,
    ),
    @remote_protocol.negotiate_protocol_version(999),
    ),
    content=(
    #|(
    #| 6,
    #| ProtocolAccepted(6),
    #| ProtocolRejected(
    #| {
    #| code: ProtocolUnsupportedVersion,
    #| message: "Unsupported remote protocol version 5",
    #| request_id: "",
    #| provider_code: "",
    #| },
    #| ),
    #| ProtocolRejected(
    #| {
    #| code: ProtocolUnsupportedVersion,
    #| message: "Unsupported remote protocol version 999",
    #| request_id: "",
    #| provider_code: "",
    #| },
    #| ),
    #|)
    ),
    )
    }

    #Decoding

    Decoders return structured errors for invalid JSON, version, packet shape, URI, or provider failure. Nothing raises: a malformed packet from the network is an ordinary value the caller matches on.

    ///|
    test "malformed input decodes to a structured error, never a raise" {
    debug_inspect(
    ["", "not json at all", "{}", "{\"version\":1,\"kind\":\"hover\"}"].map(raw => {
    match @remote_protocol.decode_client_packet(raw) {
    DecodedClientPacket(_) => "decoded"
    ClientPacketDecodeError(error) => "error:" + error.code.to_string()
    }
    }),
    content=(
    #|[
    #| "error:InvalidJson",
    #| "error:InvalidJson",
    #| "error:UnsupportedVersion",
    #| "error:UnsupportedVersion",
    #|]
    ),
    )
    }

    ProtocolErrorCode round-trips through its wire string, so an error category survives the hop without a lookup table on each side. The wire form is the CamelCase name, and anything unrecognized decodes to ProtocolInvalidPacket rather than raising — an unknown category from a newer peer is still a usable error.

    ///|
    test "error codes round-trip, and unknown input falls back" {
    let codes : Array[@remote_protocol.ProtocolErrorCode] = [
    ProtocolInvalidJson,
    ProtocolUnsupportedVersion,
    ProtocolUnknownPacket,
    ProtocolInvalidPacket,
    ProtocolInvalidUri,
    ProtocolProviderError,
    ]
    debug_inspect(
    (
    codes.map(code => {
    (
    code.to_string(),
    @remote_protocol.ProtocolErrorCode::from_string(code.to_string()) ==
    code,
    )
    }),
    @remote_protocol.ProtocolErrorCode::from_string("SomethingNewer"),
    ),
    content=(
    #|(
    #| [
    #| ("InvalidJson", true),
    #| ("UnsupportedVersion", true),
    #| ("UnknownPacket", true),
    #| ("InvalidPacket", true),
    #| ("InvalidUri", true),
    #| ("ProviderError", true),
    #| ],
    #| ProtocolInvalidPacket,
    #|)
    ),
    )
    }

    #Boundary and validation

    This package may depend on domain types and shell/workspace, but not on browser, viewer implementation, server routing, or native effects.

    Run moon test shell/remote_protocol --target js, then the read-only check gate: moon check --target all --warn-list +73 --deny-warn and moon fmt --check. See docs/quality.md for the complete required checks.

    ClientPacket

    pub(all) enum ClientPacket {
    ResolveDirectory(DocumentRequest)
    OpenDocument(DocumentRequest)
    WatchDocument(DocumentRequest)
    CloseDocument(DocumentRequest)
    Hover(DocumentPositionRequest)
    Definition(DocumentPositionRequest)
    References(DocumentPositionRequest)
    DocumentSymbols(DocumentRevisionRequest)
    ListHostDirectory(HostPathRequest)
    SwitchWorkspace(HostPathRequest)
    } derive(
    Debug
    )

    Browser-to-server request packet variants.

    ClientPacket::to_json

    fn ClientPacket::to_json(self : ClientPacket) -> Json

    ClientPacketDecodeResult

    pub enum ClientPacketDecodeResult {
    DecodedClientPacket(ClientPacket)
    ClientPacketDecodeError(ProtocolError)
    } derive(
    Debug
    )

    Client-packet decode result that preserves structured protocol errors.

    DefinitionResultPayload

    pub(all) struct DefinitionResultPayload {
    id : String
    uri :
    Uri

    revision : String
    locations : Array[
    Location
    ]
    } derive(
    Debug
    )

    Definition response for a specific URI, document revision, and request id.

    DiagnosticsPayload

    pub(all) struct DiagnosticsPayload {
    uri :
    Uri

    revision : String
    diagnostics : Array[
    Diagnostic
    ]
    } derive(
    Debug
    )

    Server-initiated diagnostics push for a URI at a document revision. The backend publishes these from moon check runs, so the packet carries no request id.

    DirectoryResolvedPayload

    pub(all) struct DirectoryResolvedPayload {
    id : String
    stat :
    WorkspaceStat

    } derive(
    Debug
    )

    One stat for the requested directory URI with exactly one level of children resolved; nested directories arrive with children: None.

    DocumentPayload

    Loaded or changed document payload from the remote server.

    DocumentPositionRequest

    pub(all) struct DocumentPositionRequest {
    id : String
    uri :
    Uri

    document_revision : String
    offset : Int
    } derive(
    Debug
    )

    Request addressed by URI, expected document revision, and UTF-16 offset. Servers use the revision to drop stale feature requests.

    DocumentRequest

    pub(all) struct DocumentRequest {
    id : String
    uri :
    Uri

    } derive(
    Debug
    )

    Request addressed only by document URI.

    DocumentRevisionRequest

    pub(all) struct DocumentRevisionRequest {
    id : String
    uri :
    Uri

    document_revision : String
    } derive(
    Debug
    )

    Request addressed by URI and expected document revision, for whole-document feature refreshes.

    DocumentSymbolsResultPayload

    pub(all) struct DocumentSymbolsResultPayload {
    id : String
    uri :
    Uri

    revision : String
    symbols : Array[
    DocumentSymbol
    ]
    } derive(
    Debug
    )

    Document symbols response for a specific URI and document revision.

    HostDirectoryEntry

    pub(all) struct HostDirectoryEntry {
    name : String
    path : String
    is_workspace : Bool
    } derive(Eq,
    Debug
    )

    One child directory in a host filesystem listing. is_workspace marks directories that look like a repository root (a moon.mod, moon.mod.json, or .git entry).

    HostDirectoryEntry::equal

    HostDirectoryEntry::not_equal

    HostDirectoryListedPayload

    pub(all) struct HostDirectoryListedPayload {
    id : String
    listing : HostDirectoryListing
    } derive(
    Debug
    )

    Response payload for ListHostDirectory.

    HostDirectoryListing

    pub(all) struct HostDirectoryListing {
    path : String
    parent : String?
    entries : Array[HostDirectoryEntry]
    } derive(Eq,
    Debug
    )

    One resolved host directory level: the absolute listed path, its parent (None at the filesystem root), and its child directories.

    HostDirectoryListing::equal

    HostDirectoryListing::not_equal

    HostPathRequest

    pub(all) struct HostPathRequest {
    id : String
    path : String
    } derive(
    Debug
    )

    Request addressed by an absolute host filesystem path. An empty path asks the server to start from the current workspace root.

    HoverResultPayload

    pub(all) struct HoverResultPayload {
    id : String
    uri :
    Uri

    revision : String
    hover :
    Hover
    ?
    } derive(
    Debug
    )

    Hover response for a specific URI, document revision, and request id.

    ProtocolError

    pub struct ProtocolError {
    code : ProtocolErrorCode
    message : String
    request_id : String
    provider_code : String
    } derive(
    Debug
    )

    Remote protocol error packet payload. request_id correlates failures to a request; provider_code preserves lower-level filesystem/provider categories.

    ProtocolError::ProtocolError

    fn ProtocolError::ProtocolError(code : ProtocolErrorCode, message : String, request_id? : String, provider_code? : String) -> ProtocolError

    Builds a protocol error, defaulting an empty message to the wire code.

    ProtocolError::from_file_system_error

    Converts a filesystem-provider failure into a protocol error without losing the provider error code.

    ProtocolError::to_json

    fn ProtocolError::to_json(self : ProtocolError) -> Json

    ProtocolErrorCode

    pub(all) enum ProtocolErrorCode {
    ProtocolInvalidJson
    ProtocolUnsupportedVersion
    ProtocolUnknownPacket
    ProtocolInvalidPacket
    ProtocolInvalidUri
    ProtocolProviderError
    } derive(Eq,
    Debug
    )

    Structured error categories used on the wire.

    ProtocolErrorCode::equal

    ProtocolErrorCode::from_string

    fn ProtocolErrorCode::from_string(code : String) -> ProtocolErrorCode

    Decodes a wire error code, failing closed to InvalidPacket for unknown values.

    ProtocolErrorCode::label

    fn ProtocolErrorCode::label(self : ProtocolErrorCode) -> String

    Debug label that keeps the local enum prefix visible.

    ProtocolErrorCode::not_equal

    fn ProtocolErrorCode::not_equal(x : ProtocolErrorCode, y : ProtocolErrorCode) -> Bool

    ProtocolErrorCode::to_string

    fn ProtocolErrorCode::to_string(self : ProtocolErrorCode) -> String

    Stable compact code used in JSON payloads.

    ProtocolNegotiation

    pub enum ProtocolNegotiation {
    ProtocolAccepted(Int)
    ProtocolRejected(ProtocolError)
    } derive(
    Debug
    )

    Version-negotiation result used before opening a long-lived connection.

    ReferenceItem

    pub(all) struct ReferenceItem {
    uri :
    Uri

    range :
    Range

    line : Int
    column : Int
    line_text : String
    is_test : Bool?
    } derive(
    Debug
    )

    A reference location enriched by the server with one-based line/column and the text of the containing line, so clients can list cross-file references without fetching file contents.

    ReferencesResultPayload

    pub(all) struct ReferencesResultPayload {
    id : String
    uri :
    Uri

    revision : String
    references : Array[ReferenceItem]
    } derive(
    Debug
    )

    References response for a specific URI and document revision.

    ServerPacket

    pub(all) enum ServerPacket {
    DirectoryResolved(DirectoryResolvedPayload)
    DocumentLoaded(DocumentPayload)
    DocumentChanged(DocumentPayload)
    Diagnostics(DiagnosticsPayload)
    HoverResult(HoverResultPayload)
    DefinitionResult(DefinitionResultPayload)
    ReferencesResult(ReferencesResultPayload)
    DocumentSymbolsResult(DocumentSymbolsResultPayload)
    HostDirectoryListed(HostDirectoryListedPayload)
    WorkspaceSwitched(WorkspaceSwitchedPayload)
    RemoteError(ProtocolError)
    } derive(
    Debug
    )

    Server-to-browser response, event, and error packet variants.

    ServerPacket::to_json

    fn ServerPacket::to_json(self : ServerPacket) -> Json

    ServerPacketDecodeResult

    pub enum ServerPacketDecodeResult {
    DecodedServerPacket(ServerPacket)
    ServerPacketDecodeError(ProtocolError)
    } derive(
    Debug
    )

    Server-packet decode result that preserves structured protocol errors.

    WorkspaceSwitchedPayload

    pub(all) struct WorkspaceSwitchedPayload {
    id : String
    root : String
    name : String
    } derive(
    Debug
    )

    The served workspace root changed. Carries the requester's id on the direct response and an empty id on the broadcast to every other session.

    decode_client_packet

    fn decode_client_packet(raw : String) -> ClientPacketDecodeResult

    Decodes and validates a client packet, returning structured errors instead of raising on malformed JSON or packet shape.

    decode_server_packet

    fn decode_server_packet(raw : String) -> ServerPacketDecodeResult

    Decodes and validates a server packet, returning structured errors instead of raising on malformed JSON or packet shape.

    negotiate_protocol_version

    fn negotiate_protocol_version(version : Int) -> ProtocolNegotiation

    Accepts only the exact current protocol version.

    protocol_version

    fn protocol_version() -> Int

    Current client/server wire-protocol version.