# Viewer to External Editor JSON-RPC
Message Interfaces Documentation This document describes all the message interfaces defined for WebSocket communication between the Second Life viewer and an external editor such as a VSCode extension. ## Table of Contents - [Usage Flow](#usage-flow) - [VS Code Launch URI](#vs-code-launch-uri) - [JSON-RPC Method Summary](#json-rpc-method-summary) - [Session Management Interfaces](#session-management-interfaces) - [SessionHandshake](#sessionhandshake) - [SessionHandshakeResponse](#sessionhandshakeresponse) - [Session OK](#session-ok) - [SessionDisconnect](#sessiondisconnect) - [SessionPing](#sessionping) - [Language and Syntax Interfaces](#language-and-syntax-interfaces) - [SyntaxChange](#syntaxchange) - [Language Syntax ID Request](#language-syntax-id-request) - [Language Syntax Request](#language-syntax-request) - [Language Syntax Cache List](#language-syntax-cache-list) - [Language Syntax Cache Get](#language-syntax-cache-get) - [Script Subscription Interfaces](#script-subscription-interfaces) - [ScriptSubscribe](#scriptsubscribe) - [ScriptSubscribeResponse](#scriptsubscriberesponse) - [ScriptUnsubscribe](#scriptunsubscribe) - [ScriptList](#scriptlist) - [Compilation Interfaces](#compilation-interfaces) - [Diagnostic](#diagnostic) - [CompilationResult](#compilationresult) - [Runtime Event Interfaces](#runtime-event-interfaces) - [RuntimeDebug](#runtimedebug) - [RuntimeError](#runtimeerror) - [Handler and Configuration Interfaces](#handler-and-configuration-interfaces) - [WebSocketHandlers](#websockethandlers) - [ClientInfo](#clientinfo) - [Object Content Interfaces](#object-content-interfaces) - [Core Data Types](#core-data-types) - [ObjectPublish](#objectpublish) - [ObjectUnpublish](#objectunpublish) - [ObjectUpdate](#objectupdate) - [ObjectContentGet](#objectcontentget) - [ObjectContentSave](#objectcontentsave) - [ObjectItemCreate](#objectitemcreate) - [ObjectItemDelete](#objectitemdelete) - [ObjectScriptSetRunning](#objectscriptsetrunning) - [ObjectRequest](#objectrequest) - [ObjectModify](#objectmodify) - [ObjectItemModify](#objectitemmodify) - [Command Interfaces](#command-interfaces) - [CommandExecute](#commandexecute) - [CommandList](#commandlist) ## Usage Flow 1. **Connection Establishment:** - Viewer sends `session.handshake` call with `SessionHandshake` data - Extension responds with `SessionHandshakeResponse` - Viewer confirms with `session.ok` notification 2. **Language Information Exchange:** - Extension makes `language.syntax.id` call to get current syntax version - Extension makes `language.syntax` calls with different `kind` parameters to get specific language data - Viewer responds with a `LanguageInfo` object containing the requested definitions 3. **Script Subscription Management:** - Extension makes `script.subscribe` call with `ScriptSubscribe` data to request live synchronization for a script - Viewer responds with `ScriptSubscribeResponse` indicating success or failure - When subscription needs to be terminated, viewer sends `script.unsubscribe` notification with `ScriptUnsubscribe` data - Extension handles unsubscription by cleaning up local script tracking 4. **Object Content Publishing:** - Viewer sends `object.publish` notification when an in-world object's contents are made available for editing - Viewer sends `object.unpublish` notification when an object is removed or the owner stops publishing - Viewer sends `object.update` notification when object inventory changes (full replacement or delta) - Extension calls `object.content.get` to fetch an item's content on demand - Extension calls `object.content.save` to write modified content back to the viewer - Extension calls `object.item.create` / `object.item.delete` to manage inventory items - Extension calls `object.script.set_running` to start or stop a script 5. **Runtime Events:** - Viewer sends `language.syntax.change` notification with `SyntaxChange` when language changes - Viewer sends `script.compiled` notification with `CompilationResult` after script compilation - Viewer sends `runtime.debug` notification with `RuntimeDebug` for debug messages during script execution - Viewer sends `runtime.error` notification with `RuntimeError` when runtime errors occur 6. **Connection Termination:** - Either side can send `session.disconnect` notification with `SessionDisconnect` data - Connection is closed gracefully ## VS Code Launch URI The viewer can launch VS Code and trigger an automatic WebSocket connection by opening a `vscode://` URI via the operating system's default URI handler. The extension registers a URI handler for this scheme; VS Code will launch itself if not already running and deliver the URI to the extension. ### URI Format ``` vscode://lindenlab.sl-vscode-plugin/connect[?port=][&object=][&script=] ``` ### Parameters | Parameter | Required | Description | | --------- | -------- | ----------- | | `port` | No | Port number the viewer's WebSocket server is listening on. Overrides the user's configured port for this session. Defaults to the configured `slVscodeEdit.network.websocketPort` (default `9020`) if absent. Must be in range 1024-65535. | | `object` | No | UUID of a root prim. After the handshake completes the extension calls `object.request` to ask the viewer to publish this object. The viewer then sends an `object.publish` notification and the object appears as a workspace folder in the Explorer. | | `script` | No | UUID of a script. After the handshake completes the extension locates the corresponding temp file via `script.list` and opens it, triggering the normal `script.subscribe` + live-sync flow. | `object` and `script` are mutually exclusive in typical use but both may be supplied; the extension will process both. ### Examples ``` # Open VS Code and connect on default port vscode://lindenlab.sl-vscode-plugin/connect # Connect on a custom port vscode://lindenlab.sl-vscode-plugin/connect?port=9021 # Connect and immediately publish a specific object vscode://lindenlab.sl-vscode-plugin/connect?port=9020&object=550e8400-e29b-41d4-a716-446655440000 # Connect and open a specific script for editing vscode://lindenlab.sl-vscode-plugin/connect?port=9020&script=6ba7b810-9dad-11d1-80b4-00c04fd430c8 ``` ### Post-connection sequence When the URI contains an `object` or `script` parameter the extension acts only **after** the handshake is fully complete (`session.ok` received): ``` URI received by extension | v WebSocket connects -> session.handshake -> session.ok | |- object= -> object.request({ object_id }) call | | | v (async, when viewer is ready) | object.publish notification | \- script= -> script.list call -> open temp file | v script.subscribe + live-sync ``` ## JSON-RPC Method Summary | Method | Direction | Type | Interface/Parameters | | ------------------------------- | ------------------ | ------------ | -------------------------- | | `session.handshake` | Viewer -> Extension | Call | `SessionHandshake` | | `session.handshake` (response) | Extension -> Viewer | Response | `SessionHandshakeResponse` | | `session.ok` | Viewer -> Extension | Notification | _(no interface)_ | | `session.disconnect` | Bidirectional | Notification | `SessionDisconnect` | | `session.ping` | Bidirectional | Call | `SessionPing` | | `session.ping` (response) | Bidirectional | Response | `SessionPingResponse` | | `script.subscribe` | Extension -> Viewer | Call | `ScriptSubscribe` | | `script.subscribe` (response) | Viewer -> Extension | Response | `ScriptSubscribeResponse` | | `script.unsubscribe` | Viewer -> Extension | Notification | `ScriptUnsubscribe` | | `script.list` | Extension -> Viewer | Call | _(no parameters)_ | | `script.list` (response) | Viewer -> Extension | Response | `ScriptList` | | `language.syntax.id` | Extension -> Viewer | Call | _(no parameters)_ | | `language.syntax.id` (response) | Viewer -> Extension | Response | `{ id: string }` | | `language.syntax` | Extension -> Viewer | Call | `{ kind: string }` | | `language.syntax` (response) | Viewer -> Extension | Response | `LanguageInfo` | | `language.syntax.cache` | Extension -> Viewer | Call | _(no parameters)_ | | `language.syntax.cache` (response) | Viewer -> Extension | Response | `SyntaxCacheList` | | `language.syntax.get` | Extension -> Viewer | Call | `{ filename: string, as_json?: boolean }` | | `language.syntax.get` (response) | Viewer -> Extension | Response | `SyntaxCacheFile` | | `language.syntax.change` | Viewer -> Extension | Notification | `SyntaxChange` | | `script.compiled` | Viewer -> Extension | Notification | `CompilationResult` | | `runtime.debug` | Viewer -> Extension | Notification | `RuntimeDebug` | | `runtime.error` | Viewer -> Extension | Notification | `RuntimeError` | | `object.publish` | Viewer -> Extension | Notification | `ObjectPublishMessage` | | `object.unpublish` | Viewer -> Extension | Notification | `ObjectUnpublishMessage` || `object.unpublish` | Extension → Viewer | Call | `ObjectUnpublishParams` | | `object.unpublish` (response) | Viewer → Extension | Response | `ObjectUnpublishResponse` || `object.update` | Viewer -> Extension | Notification | `ObjectUpdateMessage` | | `object.content.get` | Extension -> Viewer | Call | `ObjectContentGetParams` | | `object.content.get` (response) | Viewer -> Extension | Response | `ObjectContentGetResponse` | | `object.content.save` | Extension -> Viewer | Call | `ObjectContentSaveParams` | | `object.content.save` (response)| Viewer -> Extension | Response | `ObjectContentSaveResponse`| | `object.item.create` | Extension -> Viewer | Call | `ObjectItemCreateParams` | | `object.item.create` (response) | Viewer -> Extension | Response | `ObjectItemCreateResponse` | | `object.item.delete` | Extension -> Viewer | Call | `ObjectItemDeleteParams` | | `object.item.delete` (response) | Viewer -> Extension | Response | `ObjectItemDeleteResponse` | | `object.script.set_running` | Extension -> Viewer | Call | `ObjectScriptSetRunningParams` | | `object.script.set_running` (response) | Viewer -> Extension | Response | `ObjectScriptSetRunningResponse` | | `object.script.reset` | Extension -> Viewer | Call | `ObjectScriptResetParams` | | `object.script.reset` (response)| Viewer -> Extension | Response | `ObjectScriptResetResponse` | | `object.request` | Extension -> Viewer | Call | `ObjectRequestParams` | | `object.request` (response) | Viewer -> Extension | Response | `ObjectRequestResponse` | | `object.list` | Extension -> Viewer | Call | `{}` (no params) | | `object.list` (response) | Viewer -> Extension | Response | `ObjectListResponse` | | `object.modify` | Extension -> Viewer | Call | `ObjectModifyParams` | | `object.modify` (response) | Viewer -> Extension | Response | `ObjectModifyResponse` | | `object.item.modify` | Extension -> Viewer | Call | `ObjectItemModifyParams` | | `object.item.modify` (response) | Viewer -> Extension | Response | `ObjectItemModifyResponse` | | `command.execute` | Bidirectional | Call | `CommandExecuteParams` | | `command.execute` (response) | Bidirectional | Response | `CommandExecuteResponse` | | `command.list` | Bidirectional | Call | _(no params)_ | | `command.list` (response) | Bidirectional | Response | `CommandListResponse` | ## Session Management Interfaces ### SessionHandshake **JSON-RPC Method:** `session.handshake` (call from viewer) The initial handshake call sent by the viewer to establish a session. ```typescript interface SessionHandshake { server_version: "1.0.0"; protocol_version: "1.0"; viewer_name: string; viewer_version: string; agent_id: string; agent_name: string; challenge?: string; languages: string[]; syntax_id: string; features: { [feature: string]: boolean }; } ``` **Fields:** - `server_version`: Fixed version "1.0.0" indicating the server API version - `protocol_version`: Fixed version "1.0" for the communication protocol - `viewer_name`: Name of the Second Life viewer application - `viewer_version`: Version string of the viewer - `agent_id`: Unique identifier for the user/agent - `agent_name`: Human-readable name of the agent - `challenge` (optional): Path to a temporary file on the local filesystem containing a UUID. The client must read this file and return the UUID as `challenge_response` to authenticate the connection. - `languages`: Array of supported scripting languages (e.g., `["lsl", "luau"]`) - `syntax_id`: Current active syntax identifier as a UUID string - `features`: Dictionary of feature flags indicating viewer capabilities. Known flags: - `live_sync`: Viewer supports live script synchronisation with the external editor - `compilation`: Viewer will forward compilation results via `script.compiled` - `syntax_cache`: Viewer supports `language.syntax.cache` and `language.syntax.get` for retrieving syntax definition files - `commands`: Both sides support `command.execute` and `command.list` ### SessionHandshakeResponse **JSON-RPC Method:** Response to `session.handshake` The response sent by the VS Code extension to complete the handshake. ```typescript interface SessionHandshakeResponse { client_name: string; client_version: "1.0"; protocol_version: string; challenge_response?: string; languages: string[]; features: { [feature: string]: boolean }; script_name?: string; script_language?: string; } ``` **Fields:** - `client_name`: Name of the client (VS Code extension) - `client_version`: Fixed version "1.0" of the client - `protocol_version`: Protocol version the client supports - `challenge_response` (optional): The UUID read from the temporary file identified by the `challenge` field in the handshake. Must be provided if `challenge` was present, otherwise the connection will be closed. - `languages`: Array of languages supported by the client - `features`: Dictionary of features supported by the client - `script_name` (optional): Name of the script currently open in the editor - `script_language` (optional): Language of the script currently open in the editor (e.g. `"lsl"`, `"luau"`) ### Session OK **JSON-RPC Method:** `session.ok` (notification from viewer) Confirmation notification sent by the viewer after successful handshake completion. No parameters are sent with this notification. ### SessionDisconnect **JSON-RPC Method:** `session.disconnect` (notification, bidirectional) Message sent when terminating the connection. ```typescript interface SessionDisconnect { reason: number; message: string; } ``` **Fields:** - `reason`: Numeric code indicating the reason for disconnection: - `0`: Normal closure - `1`: Editor closed - `2`: Protocol error - `3`: Connection timeout - `4`: Internal server error - `message`: Human-readable description of the disconnect reason ### SessionPing **JSON-RPC Method:** `session.ping` (call, bidirectional) Heartbeat call used to verify the connection is alive and measure latency. Either side can initiate a ping; the recipient responds with the original timestamp plus its own server time. ```typescript interface SessionPing { timestamp: number; } ``` **Fields:** - `timestamp`: Unix timestamp in milliseconds when the ping was sent **Response:** ```typescript interface SessionPingResponse { timestamp: number; server_time: number; } ``` **Response Fields:** - `timestamp`: The original timestamp from the request (echoed back) - `server_time`: Unix timestamp in milliseconds when the response was generated **Example Request:** ```json { "jsonrpc": "2.0", "method": "session.ping", "id": 42, "params": { "timestamp": 1721145600000 } } ``` **Example Response:** ```json { "jsonrpc": "2.0", "id": 42, "result": { "timestamp": 1721145600000, "server_time": 1721145600015 } } ``` ## Language and Syntax Interfaces ### SyntaxChange **JSON-RPC Method:** `language.syntax.change` (notification from viewer) Notification sent when the active language syntax changes in the viewer. ```typescript interface SyntaxChange { id: string; } ``` **Fields:** - `id`: UUID string identifying the new syntax version ### Language Syntax ID Request **JSON-RPC Method:** `language.syntax.id` (call from extension to viewer) Requests the current active language syntax identifier from the viewer. This method takes no parameters. **Response:** Returns `{ id: string }` where `id` is the current syntax version as a UUID string. ### Language Syntax Request **JSON-RPC Method:** `language.syntax` (call from extension to viewer) Requests the in-memory keyword definitions for a specific language. These definitions are the deserialized, viewer-processed form of the syntax data for the current region. **Parameters:** ```typescript { kind: string; // The language whose definitions to retrieve } ``` **Valid `kind` values:** | Value | Description | | ----------- | ----------------------------------------- | | `"defs.lsl"` | Returns the LSL keyword definitions | | `"defs.lua"` | Returns the Luau keyword definitions | **Response:** ```typescript interface LanguageInfo { id: string; defs?: object; // Present only on success success: boolean; error?: string; // Present only on failure } ``` **Response Fields:** - `id`: The current syntax version identifier - `defs` (optional): The keyword definitions object. Only present when `success` is `true`. Structure varies by language. - `success`: Whether the definitions were found and returned successfully - `error` (optional): Human-readable error description. Only present when `success` is `false` **Error cases:** - No `kind` parameter supplied: `success: false`, `error: "No syntax category specified"` - Unknown `kind` value: `success: false`, `error: "Unknown syntax category requested"` ### Language Syntax Cache List **JSON-RPC Method:** `language.syntax.cache` (call from extension to viewer) Requests the list of file names currently held in the `LLSyntaxDefCache`. This provides the extension with the available syntax definition file names that can subsequently be retrieved with `language.syntax.get`. This method takes no parameters. **Response:** ```typescript interface SyntaxCacheList { files: string[]; // Array of file names (e.g. ["lsl_keywords.xml", "slua_definitions.yaml"]) success: boolean; } ``` **Response Fields:** - `files`: Array of file name strings, each of which can be passed as the `filename` parameter to `language.syntax.get` - `success`: Whether the request was handled successfully **Known cache files:** | File name | Description | | -------------------------------- | ---------------------------------------------------- | | `builtins.txt` | LSL built-in keyword list in plain text format | | `lsl_definitions.yaml` | LSL language definitions in YAML format | | `lsl_keywords.xml` | LSL keyword definitions in LLSD XML format. Used by the viewer's script editor | | `lsl_keywords_pretty.xml` | LSL keyword definitions in formatted LLSD XML format | | `secondlife.d.luau` | Luau type definition file. Used by luau-lsp | | `secondlife.docs.json` | Luau documentation data in JSON format. Used by luau-lsp | | `slua_definitions.yaml` | Luau language definitions in YAML format | | `lua_keywords.xml` | Luau keyword definitions in LLSD XML format. Used by the viewer's script editor | | `lua_keywords_pretty.xml` | Luau keyword definitions in formatted LLSD XML format | | `secondlife_selene.yml` | Luau Selene linter configuration in YAML format | Not all files may be present in every cache - the actual list returned by `language.syntax.cache` reflects only what is available on the viewer's local filesystem at the time of the request. ### Language Syntax Cache Get **JSON-RPC Method:** `language.syntax.get` (call from extension to viewer) Requests the content of a specific file from the syntax definition cache. The file name must be one of the names returned by a prior `language.syntax.cache` call. Content is returned either as a raw text string or as a parsed JSON/LLSD object depending on the `as_json` parameter. **Parameters:** ```typescript { filename: string; // The file name to retrieve, as returned by language.syntax.cache as_json?: boolean; // Optional. If true, content is returned as a parsed object rather than raw text } ``` **Fields:** - `filename`: The file name to retrieve (e.g. `"lsl_keywords.xml"`, `"slua_definitions.yaml"`) - `as_json` (optional): When `true`, the file is deserialized and returned as a structured object in `content`. When omitted or `false`, `content` is the raw text of the file. **Response:** ```typescript interface SyntaxCacheFile { content?: string | object; // Present only on success. String if as_json is false/omitted, object if as_json is true success: boolean; error?: string; // Present only on failure } ``` **Response Fields:** - `content`: The file content. Only present when `success` is `true`. Is a raw text string when `as_json` is omitted or `false`; is a parsed object when `as_json` is `true`. - `success`: Whether the file was found and read successfully - `error` (optional): Human-readable error description. Only present when `success` is `false` **Error cases:** - No `filename` parameter supplied: `success: false`, `error: "No filename specified"` - Name not found in cache: `success: false`, `error: "Requested syntax cache file not found"` - File could not be loaded: `success: false`, `error: "Failed to load syntax cache file"` (or `"Failed to load and format syntax cache file."` when `as_json` is `true`) ## Script Subscription Interfaces ### ScriptSubscribe **JSON-RPC Method:** `script.subscribe` (call from extension to viewer) Requests subscription to a script for live synchronization between the editor and viewer. ```typescript interface ScriptSubscribe { script_id: string; script_name: string; script_language: string; } ``` **Fields:** - `script_id`: Unique identifier for the script to subscribe to - `script_name`: Display name of the script file - `script_language`: Programming language of the script (e.g., "lsl", "luau") ### ScriptSubscribeResponse **JSON-RPC Method:** Response to `script.subscribe` Response from the viewer indicating whether script subscription was successful. ```typescript interface ScriptSubscribeResponse { script_id: string; success: boolean; status: number; object_id?: string; item_id?: string; message?: string; } ``` **Fields:** - `script_id`: The script identifier that was subscribed to - `success`: Whether the subscription was successful - `status`: Numeric status code indicating the result: - `0`: Success - `1`: Invalid editor - the script editor panel is no longer open - `2`: Invalid subscription - no subscription found for the given `script_id` - `3`: Already subscribed - another connection is already subscribed to this script - `4`: Internal server error - `object_id` (optional): The in-world UUID of the object containing the script - `item_id` (optional): The inventory item UUID of the script within the object - `message` (optional): Additional information about the subscription result ### ScriptUnsubscribe **JSON-RPC Method:** `script.unsubscribe` (notification from viewer) Notification sent by the viewer when a script subscription should be terminated. ```typescript interface ScriptUnsubscribe { script_id: string; } ``` **Fields:** - `script_id`: Unique identifier for the script to unsubscribe from ### ScriptList **JSON-RPC Method:** `script.list` (call from extension to viewer) Requests the list of all scripts currently open and tracked by the viewer, along with the viewer's temp directory. This is intended for use by a file watcher tool that needs to discover which script temp files are active without going through the full `script.subscribe` flow. This method takes no parameters. **Response:** ```typescript interface ScriptList { temp_dir: string; script_ids: string[]; success: boolean; } ``` **Response Fields:** - `temp_dir`: The absolute path to the viewer's temp directory where live-sync script files are written. Combined with a `script_id`, the caller can locate the corresponding temp file on disk. - `script_ids`: Array of script ID strings for all currently subscribed scripts, across all active connections. - `success`: Always `true`. ## Compilation Interfaces ### Diagnostic Individual compilation error record. ```typescript interface Diagnostic { row: number; column: number; level: string; message: string; format?: "lsl"; // Present only for LSL compilation errors } ``` **Fields:** - `row`: Line number where the error occurred (1-based for both LSL and Luau) - `column`: Column position of the error (1-based for LSL; always `0` for Luau as the compiler does not provide column information) - `level`: Compiler severity string (e.g. `"ERROR"`, `"WARNING"`) - `message`: Error description - `format` (optional): Present and set to `"lsl"` for LSL compilation errors; absent for Luau errors ### CompilationResult **JSON-RPC Method:** `script.compiled` (notification from viewer) Result of a compilation operation in the viewer. ```typescript interface CompilationResult { /** Deprecated: retained during migration to item-based routing. */ script_id: string; success: boolean; running: boolean; diagnostics?: Diagnostic[]; } ``` **Fields:** - `script_id`: Unique identifier for the script that was compiled - `success`: Whether the compilation was successful - `running`: Whether the compiled script is currently running - `diagnostics` (optional): Array of `Diagnostic` records if any occurred ## Runtime Event Interfaces ### RuntimeDebug **JSON-RPC Method:** `runtime.debug` (notification from viewer) Debug message notification sent by the viewer during script execution. ```typescript interface RuntimeDebug { /** Deprecated: use item.item_id when available. */ script_id: string; object_id: string; prim_id?: string; item_id?: string; object_name: string; message: string; channel?: "debug" | "owner_say"; item?: ItemRef; } ``` **Fields:** - `script_id`: Unique identifier for the script generating the debug message - `object_id`: Unique identifier for the object containing the script - `object_name`: Human-readable name of the object - `message`: The debug message content ### RuntimeError **JSON-RPC Method:** `runtime.error` (notification from viewer) Runtime error notification sent by the viewer when a script encounters an error during execution. ```typescript interface RuntimeError { /** Deprecated: use item.item_id when available. */ script_id: string; object_id: string; prim_id?: string; item_id?: string; object_name: string; message: string; error: string; line: number; column?: number; stack?: string[]; channel?: "debug" | "owner_say"; item?: ItemRef; } interface ItemRef { root_id: string; prim_id?: string; item_id?: string; name?: string; language?: "lsl" | "luau"; } ``` **Fields:** - `script_id`: Unique identifier for the script that encountered the error - `object_id`: Unique identifier for the object containing the script - `object_name`: Human-readable name of the object - `message`: The full raw chat text of the runtime error message as received from the simulator - `error`: Extracted runtime error description. This remains a top-level compatibility field while the protocol stays on version `1.0`. - `line`: Line number where the error occurred when the runtime format can be parsed; otherwise `0`. - `stack` (optional): Stack trace lines if they could be extracted from the error message ## Handler and Configuration Interfaces ### WebSocketHandlers Event handler interface for WebSocket events. ```typescript interface WebSocketHandlers { onHandshake?: (message: SessionHandshake) => SessionHandshakeResponse; onHandshakeOk?: () => void; onDisconnect?: (message: SessionDisconnect) => void; onSubscribe?: (message: ScriptSubscribe) => ScriptSubscribeResponse; onUnsubscribe?: (message: ScriptUnsubscribe) => void; onSyntaxChange?: (message: SyntaxChange) => void; onConnectionClosed?: () => void; onCompilationResult?: (message: CompilationResult) => void; onRuntimeDebug?: (message: RuntimeDebug) => void; onRuntimeError?: (message: RuntimeError) => void; } ``` **Methods:** - `onHandshake`: Handler for initial handshake message, returns handshake response - `onHandshakeOk`: Handler called when handshake is successfully completed - `onDisconnect`: Handler for disconnect notifications - `onSubscribe`: Handler called when the extension sends a `script.subscribe` request, returns subscription response - `onUnsubscribe`: Handler for script unsubscription notifications from viewer - `onSyntaxChange`: Handler for syntax change notifications - `onConnectionClosed`: Handler called when connection is closed - `onCompilationResult`: Handler for compilation result notifications - `onRuntimeDebug`: Handler for runtime debug message notifications - `onRuntimeError`: Handler for runtime error notifications ### ClientInfo Client information used in handshake responses. ```typescript interface ClientInfo { scriptName: string; scriptId: string; extension: string; } ``` **Fields:** - `scriptName`: Name of the script being edited - `scriptId`: Unique identifier for the script - `extension`: File extension or script type --- ## Object Content Interfaces These interfaces support publishing in-world object inventories (scripts and notecards) to the external editor as a browseable virtual filesystem. The extension exposes published objects under the `sl://objects/` URI scheme. ### Core Data Types ```typescript type InventoryItemType = "script" | "notecard"; type ScriptVM = "lsl2" | "mono" | "luau"; /** Permission mask fields. Only owner and next_owner are transmitted. */ interface ItemPermissions { owner: number; // e.g. PERM_MODIFY=0x4000, PERM_COPY=0x8000, PERM_TRANSFER=0x2000 next_owner: number; } /** * Inventory item within an object or linked prim. * asset_id is intentionally never transmitted. */ interface ObjectInventoryItem { item_id: string; // Inventory item UUID name: string; // Display name (no file extension) description?: string; type: InventoryItemType; subtype?: number; // Scripts only: language from II_FLAGS_SUBTYPE_MASK (0=LSL, 1=Luau) vm?: ScriptVM; // Scripts only: which VM the script targets running?: boolean; // Scripts only: whether the script is running faulted?: boolean; // Scripts only: whether the script has a runtime fault permissions?: ItemPermissions; creator_id?: string; } /** A linked (child) prim within a linkset */ interface LinkedObject { link_id: string; // UUID of the linked prim link_number: number; // Link number (root=1, children>=2) link_name: string; link_description?: string; inventory: ObjectInventoryItem[]; } interface ObjectPermissions { owner: number; next_owner: number; } /** Root of a linkset, as published to the extension */ interface PublishedObject { object_id: string; // UUID of the root prim object_name: string; object_description?: string; region?: string; owner_id?: string; permissions?: ObjectPermissions; inventory: ObjectInventoryItem[]; // Root prim's scripts and notecards linked_objects?: LinkedObject[]; // Child prims } ``` **Script display extensions** (synthetic, derived from `subtype`): | `subtype` | Extension | | --------- | --------- | | `0` (LSL) | `.lsl` | | `1` (Luau)| `.luau` | | notecard | `.txt` | --- ### ObjectPublish **JSON-RPC Method:** `object.publish` (notification from viewer) Sent when the viewer publishes an in-world object's inventory for external editing. Triggers creation of a virtual filesystem workspace folder in the extension. ```typescript interface ObjectPublishMessage { object: PublishedObject; } ``` **Fields:** - `object`: The full published object tree, including root prim inventory and all linked prim inventories. --- ### ObjectUnpublish **JSON-RPC Method:** `object.unpublish` (notification from viewer) Sent when the viewer removes a previously published object - for example when the owner deselects it, moves away, or the object is deleted. ```typescript interface ObjectUnpublishMessage { object_id: string; reason?: string; } ``` **Fields:** - `object_id`: UUID of the root prim that is being unpublished - `reason` (optional): Human-readable explanation (e.g. `"object deleted"`, `"out of range"`) **JSON-RPC Method:** `object.unpublish` (call from extension to viewer) The extension may also call `object.unpublish` to manually stop tracking an object. The viewer will stop publishing it and send a corresponding `object.unpublish` notification back to the caller. ```typescript interface ObjectUnpublishParams { object_id: string; // UUID of the root prim to unpublish } interface ObjectUnpublishResponse { success: boolean; object_id?: string; } ``` **Fields:** - `object_id`: UUID of the root prim to unpublish. - `success`: `true` if the object was published and has been removed. **Note:** The viewer also sends an `object.unpublish` notification to the caller immediately after responding. Extensions should handle that notification idempotently. --- ### ObjectUpdate **JSON-RPC Method:** `object.update` (notification from viewer) Sent when the inventory of a published object changes. Supports two modes: - **Full replacement**: `inventory` and/or `linked_objects` fields replace the entire prior state. - **Delta update**: `changes` field describes only what changed. Takes precedence over full replacement fields when present. ```typescript interface InventoryChanges { added?: ObjectInventoryItem[]; removed?: string[]; // item_ids removed modified?: ObjectInventoryItem[]; // metadata-only changes content_changed?: string[]; // item_ids whose content changed (invalidates cache) running_changed?: { item_id: string; running: boolean }[]; // running state toggled } interface LinkedObjectChanges { added?: LinkedObject[]; removed?: string[]; // link_ids removed modified?: { link_id: string; link_name?: string; inventory?: InventoryChanges; }[]; } interface ObjectUpdateMessage { object_id: string; object_name?: string; // Full replacement (used when changes is absent) inventory?: ObjectInventoryItem[]; linked_objects?: LinkedObject[]; // Delta (takes precedence when present) changes?: { inventory?: InventoryChanges; linked_objects?: LinkedObjectChanges; }; } ``` --- ### ObjectContentGet **JSON-RPC Method:** `object.content.get` (call from extension to viewer) Requests the text content of a script or notecard. The extension calls this lazily when the user opens a file in the virtual filesystem. ```typescript interface ObjectContentGetParams { prim_id: string; // UUID of any prim (root or child) - no object_id + link_id needed item_id: string; } interface ObjectContentGetResponse { success: boolean; prim_id: string; item_id: string; content: string; // Raw text content (UTF-8). Notecard envelope is unwrapped automatically. } ``` **Fields:** - `prim_id`: UUID of the prim that owns the item. Child prims are addressable directly by UUID without knowing the root object_id. - `item_id`: Inventory item UUID. - `success`: `true` on success. - `content`: The raw text content of the item. For notecards, the `Linden text version 2` envelope is stripped - only the body text is returned. --- ### ObjectContentSave **JSON-RPC Method:** `object.content.save` (call from extension to viewer) Writes modified content back to the viewer. For scripts, the viewer will attempt to compile the updated source. ```typescript interface ObjectContentSaveParams { prim_id: string; item_id: string; content: string; vm?: "mono" | "lsl2" | "luau"; } interface ObjectContentSaveResponse { success: boolean; prim_id?: string; item_id?: string; compiled?: boolean; diagnostics?: Diagnostic[]; message?: string; } ``` **Fields:** - `prim_id`: UUID of the prim that owns the saved item. - `item_id`: UUID of the saved inventory item. - `content`: Raw script/notecard source text to store. - `vm` (optional): Scripts only compile target. Accepted values are `"mono"`, `"lsl2"`, `"luau"`. When `"luau"` is specified for an LSL script (as opposed to a native Luau script), the viewer automatically selects the correct LSL-on-Luau compile path. If omitted, inferred from item metadata or content analysis. - `success`: Whether the upload/save operation succeeded. - `compiled` (optional): Scripts only. `true` when compilation succeeded, `false` when source saved but compile failed. - `diagnostics` (optional): Scripts only. Array of `Diagnostic` records when `compiled` is `false`. - `message` (optional): Error description on failure. --- ### ObjectItemCreate **JSON-RPC Method:** `object.item.create` (call from extension to viewer) Creates a new script in a prim's inventory. The call is asynchronous - the viewer sends `RezScript` to the simulator and waits for the inventory-changed callback before returning the created item's details. The simulator may rename the item if a duplicate name exists. Notecard creation is not yet supported and will return an error. ```typescript interface ObjectItemCreateParams { prim_id: string; // UUID of the prim to create the item in name: string; // Pure SL inventory name - no file extension type: InventoryItemType; // "script" ("notecard" reserved for future) vm: ScriptVM; // Required for scripts: "luau" | "mono" | "lsl2" } // On success, returns an ObjectInventoryItem with prim_id: interface ObjectItemCreateResponse extends ObjectInventoryItem { prim_id: string; // Echoed prim UUID } ``` **Notes:** - The response matches the `ObjectInventoryItem` structure (same fields as items in `object.publish` and `object.update` notifications). - The `name` in the response may differ from the request if the simulator renamed it. - An `object.update` notification will also fire for the prim (since inventory changed). - Timeout: 30 seconds. Returns a JSON-RPC internal error if the simulator does not respond. --- ### ObjectItemDelete **JSON-RPC Method:** `object.item.delete` (call from extension to viewer) Deletes a script or notecard from a prim's inventory. Requires `PERM_MODIFY` on the item. ```typescript interface ObjectItemDeleteParams { prim_id: string; item_id: string; } interface ObjectItemDeleteResponse { success: boolean; prim_id: string; // Echoed back from request item_id: string; // Echoed back from request } ``` --- ### ObjectScriptSetRunning **JSON-RPC Method:** `object.script.set_running` (call from extension to viewer) Starts or stops a script within a prim. ```typescript interface ObjectScriptSetRunningParams { prim_id: string; item_id: string; running: boolean; // true = start, false = stop } interface ObjectScriptSetRunningResponse { success: boolean; message?: string; } ``` --- ### ObjectScriptReset **JSON-RPC Method:** `object.script.reset` (call from extension to viewer) Resets a script within a prim, clearing its state and restarting from the default state entry. ```typescript interface ObjectScriptResetParams { prim_id: string; item_id: string; } interface ObjectScriptResetResponse { success: boolean; message?: string; } ``` --- ### ObjectRequest **JSON-RPC Method:** `object.request` (call from extension to viewer) Requests the viewer to publish a specific in-world object. The viewer responds synchronously to confirm the request was accepted, then asynchronously sends an `object.publish` notification with the full object tree. This is typically called immediately after the handshake completes when the extension was launched by the viewer with an `object=` URI parameter. ```typescript interface ObjectRequestParams { object_id: string; // UUID of the root prim to request publishing for } interface ObjectRequestResponse { success: boolean; message?: string; // reason on failure (e.g. "object not found", "permission denied") } ``` **Fields:** - `object_id`: UUID of the root prim of the linkset to publish. - `success`: Whether the viewer accepted the request. A `true` response does not mean `object.publish` has been sent yet - it means the viewer will send it. - `message` (optional): Human-readable failure reason. Only present when `success` is `false`. **Sequence:** 1. Extension calls `object.request` 2. Viewer responds with `{ success: true }` (or error) 3. Viewer sends `object.publish` notification (asynchronously, when ready) --- ### ObjectList **JSON-RPC Method:** `object.list` (call from extension to viewer) Requests the complete list of currently published objects. Called by the extension immediately after the handshake completes (`session.ok`) to restore state for any objects the viewer already has published. The viewer responds synchronously with all published objects in the same format as `object.publish` notifications. No follow-up notifications are sent. ```typescript // No request parameters interface ObjectListResponse { objects: PublishedObject[]; // All currently published objects; empty array if none } ``` **Fields:** - `objects`: Array of `PublishedObject` records (same shape as the `object` field in `object.publish`). Empty array when no objects are currently published. **Sequence:** 1. Viewer sends `session.ok` 2. Extension calls `object.list` (no params) 3. Viewer responds with `{ objects: [...] }` synchronously --- ### ObjectModify **JSON-RPC Method:** `object.modify` (call from extension to viewer) Modifies properties of a prim (root or linked) such as name, description, or permissions. Only specified fields are modified; omitted fields remain unchanged. Requires `PERM_MODIFY` on the object. ```typescript interface ObjectModifyParams { prim_id: string; // UUID of any prim (root or child) name?: string; // New display name description?: string; // New description permissions?: { next_owner?: number; // Permission mask applied on transfer }; } interface ObjectModifyResponse { success: boolean; prim_id: string; // Echoed back from request message?: string; // Error description on failure } ``` **Fields:** - `prim_id`: UUID of the prim to modify. Child prims are addressable directly by UUID. - `name` (optional): New display name for the prim. If omitted, name remains unchanged. - `description` (optional): New description for the prim. If omitted, description remains unchanged. - `permissions` (optional): Permission changes. - `next_owner`: Permission mask applied when the object is transferred. Uses same bit flags as `ItemPermissions` (e.g., `PERM_MODIFY=0x4000`, `PERM_COPY=0x8000`, `PERM_TRANSFER=0x2000`). - `success`: Whether the update operation succeeded. - `message` (optional): Error description. Only present when `success` is `false`. **Notes:** - At least one property field (`name`, `description`, or `permissions`) must be specified. - An `object.update` notification will fire after successful modification. - Owner permissions cannot be modified directly — only `next_owner` can be changed. --- ### ObjectItemModify **JSON-RPC Method:** `object.item.modify` (call from extension to viewer) Modifies properties of an inventory item such as name, description, or permissions. Only specified fields are modified; omitted fields remain unchanged. Requires `PERM_MODIFY` on the item. ```typescript interface ObjectItemModifyParams { prim_id: string; // UUID of any prim (root or child) item_id: string; // Inventory item UUID name?: string; // New display name (no file extension) description?: string; // New description permissions?: { next_owner?: number; // Permission mask applied on transfer }; } interface ObjectItemModifyResponse { success: boolean; prim_id: string; // Echoed back from request item_id: string; // Echoed back from request message?: string; // Error description on failure } ``` **Fields:** - `prim_id`: UUID of the prim that owns the item. Child prims are addressable directly by UUID. - `item_id`: Inventory item UUID. - `name` (optional): New display name for the item. Should not include file extension (e.g., `.lsl`, `.luau`). If omitted, name remains unchanged. - `description` (optional): New description for the item. If omitted, description remains unchanged. - `permissions` (optional): Permission changes. - `next_owner`: Permission mask applied when the item is transferred. Uses same bit flags as `ItemPermissions` (e.g., `PERM_MODIFY=0x4000`, `PERM_COPY=0x8000`, `PERM_TRANSFER=0x2000`). - `success`: Whether the update operation succeeded. - `message` (optional): Error description. Only present when `success` is `false`. **Notes:** - At least one property field (`name`, `description`, or `permissions`) must be specified. - An `object.update` notification will fire after successful modification. - Owner permissions cannot be modified directly — only `next_owner` can be changed. - If the item is renamed, the virtual filesystem path will change and the extension must handle the rename appropriately. --- ## Command Interfaces These interfaces provide a general-purpose, bidirectional command channel. Either side may invoke a named command on the other side and receive a structured result. The feature is optional and must be negotiated via the `commands` flag in the session handshake. ### CommandExecute **JSON-RPC Method:** `command.execute` (call, bidirectional) Invokes a named command on the receiving side. Commands are identified by a namespaced string and carry an optional freeform parameter map. ```typescript interface CommandExecuteParams { command: string; // namespaced command id, e.g. "viewer.teleport" params?: Record; // command-specific arguments } interface CommandExecuteResponse { success: boolean; result?: unknown; // optional command-specific return value error_code?: number; message?: string; } ``` **Fields:** - `command`: Namespaced command identifier. The prefix before the first `.` identifies the side that owns and executes the command: - `viewer.*` - commands executed by the viewer (e.g. `viewer.teleport`, `viewer.script.recompile_all`) - `editor.*` - commands executed by the extension (e.g. `editor.open_file`, `editor.show_message`) - `params` (optional): Command-specific argument map. Structure varies by command. - `success`: Whether the command was found and executed without error. - `result` (optional): Command-specific return value. Only present when `success` is `true` and the command produces output. - `error_code` (optional): Numeric failure code. Only present when `success` is `false`: - `1` - Unknown command - `2` - Invalid or missing parameters - `3` - Not permitted - `4` - Execution error - `message` (optional): Human-readable error description. Only present when `success` is `false`. **Capability gate:** A side MUST NOT send `command.execute` unless the peer advertised `commands: true` in the handshake. A receiver that receives the call without having negotiated the feature MUST respond with `success: false, error_code: 1`. **Example - extension asks viewer to teleport:** ```json { "jsonrpc": "2.0", "method": "command.execute", "id": 7, "params": { "command": "viewer.teleport", "params": { "region": "Aditi", "position": [128, 128, 25] } } } ``` ```json { "jsonrpc": "2.0", "id": 7, "result": { "success": true } } ``` **Example - viewer asks extension to show a message:** ```json { "jsonrpc": "2.0", "method": "command.execute", "id": 8, "params": { "command": "editor.show_message", "params": { "message": "Script reset complete", "level": "info" } } } ``` --- ### CommandList **JSON-RPC Method:** `command.list` (call, bidirectional) Requests the list of commands the receiving side supports. Intended for tooling and autocomplete; implementations may omit this method and return an error response if discovery is not needed. This method takes no parameters. **Response:** ```typescript interface CommandListResponse { commands: CommandInfo[]; } interface CommandInfo { command: string; description?: string; params?: Record; } interface CommandParamInfo { type: "string" | "number" | "boolean" | "object" | "array"; required?: boolean; description?: string; } ``` **Response Fields:** - `commands`: Array of commands the responder supports. Each entry describes one command. - `command`: The namespaced command identifier. - `description` (optional): Human-readable description of what the command does. - `params` (optional): Map of parameter names to their type descriptors. **Known viewer commands:** | Command | Required params | Description | |---------|----------------|-------------| | `viewer.teleport` | `region: string` | Teleport agent to a region. Optional `position: [x, y, z]`. | | `viewer.script.recompile_all` | `object_id: string` | Recompile all scripts in an object. | | `viewer.script.reset_all` | `object_id: string` | Reset all scripts in an object. | | `viewer.camera.focus` | `object_id: string` | Move camera focus to an in-world object. | **Known extension commands:** | Command | Required params | Description | |---------|----------------|-------------| | `editor.open_file` | `path: string` | Open a file in the editor. Optional `line: number`. | | `editor.show_message` | `message: string` | Show a notification. Optional `level: "info" | "warn" | "error"`. |