Appearance
CommandRunnerService
An optional facade over the first-class SDK API, shaped so something outside the SDK can drive it by name. The product is a machine-readable command catalog (tool schemas) plus a reliable execute path. There is no LLM in the SDK. The LLM, when there is one, lives in the host application and calls the runner from outside.
Two consumers are expected:
- An external LLM using a tool-calling API. It reads
getCatalog()for the tool list and callsexecute()with the name and parameters the model picked. - A human typing slash commands.
SlashCommandPluginparses text into invocations and runs them through the sameexecute/executeBatchpath, so the system can be exercised without integrating an LLM.
Everything the runner can do is also reachable through plugin and service APIs directly. The runner adds discovery (catalog) and a uniform, never-throwing result envelope on top.
Access
Registered under the name "commands" on ViewerCore. Initialized as a core service.
ts
import type { ICommandRunnerService } from "@optellix/xviewr-sdk";
await viewer.ready();
const commands = viewer.getService<ICommandRunnerService>("commands");Plugin authors do not call this service directly. They declare commands via getCommands() and BasePlugin registers them.
Types
CommandDefinition
What a plugin registers for one command.
| Field | Type | Description |
|---|---|---|
name | string | Unique command name. |
description | string | What the command does. Shown in the catalog. |
category | string | Grouping key. Used by getCommandsByCategory and the catalog filter. |
parameters | ParameterSchema[] | Parameter schemas. |
execute | (params) => Promise<unknown> | Handler. Takes the parameter object only. Its return value becomes CommandResult.data. |
examples? | string[] | Optional natural-language usage examples. |
execute takes only params. There is no context argument. Handlers are plugin closures with full service access, so cross-command state (selection, loaded models) flows through services like SelectionService, not through a passed context.
ParameterSchema
| Field | Type | Description |
|---|---|---|
name | string | Parameter name. |
type | "number" | "string" | "boolean" | "object" | "array" | Expected type. |
required | boolean | Whether the parameter must be present. |
description | string | Human-readable description. |
default? | unknown | Value applied when the parameter is omitted. |
enum? | Array<string | number> | Allowed values. When set, the value must be one of these. |
min? | number | Minimum (numbers only). |
max? | number | Maximum (numbers only). |
validation? | (value) => boolean | Custom check. Return false to reject. |
enum, min, and max are the main levers for making a command easy for an external LLM to call correctly. They are enforced at validation time and also emitted into the catalog as JSON Schema enum/minimum/maximum, so the model sees the same constraints the runner enforces.
CommandInvocation
ts
interface CommandInvocation {
command: string;
parameters?: Record<string, any>;
}CommandResult<T>
The envelope returned for every execution.
| Field | Type | Description |
|---|---|---|
ok | boolean | True if the command ran without error. |
command | string | Name of the command that ran. |
message? | string | Lifted from data.message when the handler returns an object with a string message. On failure, the error message. |
data? | T | Raw return value from the handler. |
error? | { name; message; details? } | Populated when ok is false. |
BatchResult
ts
interface BatchResult {
ok: boolean; // true if every step succeeded
results: CommandResult[]; // per-step results in invocation order
failedAt?: number; // index of the step that failed, when stopped early
}CommandTool / CommandToolProperty
JSON-Schema-shaped description of a command, generated from the registry for LLM tool-calling APIs. CommandTool has name, description, category, a parameters object (type: "object", properties, required), and optional examples. Each property carries type, description, and the optional enum, minimum, maximum, default lifted from the parameter schema.
Public API
Registry
registerCommand(command: CommandDefinition): void— throws if a command with the same name already exists. Emitscommand:registered.unregisterCommand(name: string): void— emitscommand:unregistered.hasCommand(name: string): booleangetCommand(name: string): CommandDefinition | undefinedgetAllCommands(): CommandDefinition[]getCommandsByCategory(category: string): CommandDefinition[]
Validation
validate(invocation: CommandInvocation): ValidationResult— checks the invocation against the definition without executing. Returns{ valid, error?, details? }.
Validation enforces, in order: presence of required parameters, applies defaults for omitted optionals, type, enum membership, numeric min/max, and any custom validation function. The first failure stops with a message naming the parameter.
Execution
execute(invocation: CommandInvocation): Promise<CommandResult>— never throws. Unknown command, validation failure, and a handler that throws all come back asok: falsewitherrorpopulated. On success, emitscommand:beforethencommand:success; on a handler throw, emitscommand:beforethencommand:error.
ts
const result = await commands.execute({
command: "set-view",
parameters: { preset: "iso", animated: true },
});
if (result.ok) {
console.log(result.message); // "Camera set to iso view"
} else {
console.error(result.error?.name, result.error?.message);
}executeBatch(invocations, options?): Promise<BatchResult>— runs invocations in order.options.stopOnErrordefaults to true: on the first failure it recordsfailedAtand stops. Pass{ stopOnError: false }to run every step regardless and collect all results.
ts
const batch = await commands.executeBatch([
{ command: "set-view", parameters: { preset: "front" } },
{ command: "zoom-camera", parameters: { factor: 0.5 } },
]);
// batch.ok, batch.results[i].ok, batch.failedAtCatalog
getCatalog(options?: { category? }): CommandTool[]— returns the tool list, optionally filtered by category. Generated from the registry bytoTool(src/services/commands/catalog.ts), so it cannot drift from what actually executes.
ts
const tools = commands.getCatalog();
// hand `tools` to a host-side LLM tool-calling APIEvents
| Event | Payload |
|---|---|
command:registered | { name, category } |
command:unregistered | { name } |
command:before | { command, parameters } |
command:success | { command, parameters, result } |
command:error | { command, parameters, error } |
SlashCommandPlugin emits the slash:* events (slash:suggestions, slash:executed, slash:error, slash:chain-completed) for the human-typed path.