Skip to content

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 calls execute() with the name and parameters the model picked.
  • A human typing slash commands. SlashCommandPlugin parses text into invocations and runs them through the same execute/executeBatch path, 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.

FieldTypeDescription
namestringUnique command name.
descriptionstringWhat the command does. Shown in the catalog.
categorystringGrouping key. Used by getCommandsByCategory and the catalog filter.
parametersParameterSchema[]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

FieldTypeDescription
namestringParameter name.
type"number" | "string" | "boolean" | "object" | "array"Expected type.
requiredbooleanWhether the parameter must be present.
descriptionstringHuman-readable description.
default?unknownValue applied when the parameter is omitted.
enum?Array<string | number>Allowed values. When set, the value must be one of these.
min?numberMinimum (numbers only).
max?numberMaximum (numbers only).
validation?(value) => booleanCustom 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.

FieldTypeDescription
okbooleanTrue if the command ran without error.
commandstringName of the command that ran.
message?stringLifted from data.message when the handler returns an object with a string message. On failure, the error message.
data?TRaw 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. Emits command:registered.
  • unregisterCommand(name: string): void — emits command:unregistered.
  • hasCommand(name: string): boolean
  • getCommand(name: string): CommandDefinition | undefined
  • getAllCommands(): 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 as ok: false with error populated. On success, emits command:before then command:success; on a handler throw, emits command:before then command: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.stopOnError defaults to true: on the first failure it records failedAt and 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.failedAt

Catalog

  • getCatalog(options?: { category? }): CommandTool[] — returns the tool list, optionally filtered by category. Generated from the registry by toTool (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 API

Events

EventPayload
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.

XViewr SDK and xConvert CLI documentation.