Slash commands
A person types it inside a chat session. Same trigger as a command — a human — but a different place and a different handler shape.
import { function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): ModThe entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.defineMod, function slashCommand<TId extends string>(slashCommandValue: SlashCommandInput<TId>): SlashCommand<TId>Declare a command the user types inside a chat session. The `id` is the name typed after the slash, `description` is the line shown in the typeahead, and `argumentHint` is the placeholder for whatever the user types after the name. The handler receives that text, or `undefined` when the user typed nothing, plus a context carrying the AI session the command was typed into. Use this instead of `command` when the behavior belongs to a conversation and needs the session. Use `command` when it belongs to the workbench and runs from the palette. Use `tool` when the model, not the user, should decide to run it. Slash commands go in the same top-level `commands` array as palette commands.slashCommand } from '@modular/sdk'; export defaultdefineMod<{ readonly metadata: { readonly displayName: "Hello"; readonly description: "A first mod"; }; readonly commands: readonly [SlashCommand<string>]; }>(definition: { readonly metadata: { readonly displayName: "Hello"; readonly description: "A first mod"; }; readonly commands: readonly [SlashCommand<string>]; }): ModThe entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.defineMod({metadata: { readonly displayName: "Hello"; readonly description: "A first mod"; }metadata: { displayName: "Hello"displayName: 'Hello', description: "A first mod"description: 'A first mod' }, commands: readonly [SlashCommand<string>]commands: [ slashCommand<string>(slashCommandValue: SlashCommandInput<string>): SlashCommand<string>Declare a command the user types inside a chat session. The `id` is the name typed after the slash, `description` is the line shown in the typeahead, and `argumentHint` is the placeholder for whatever the user types after the name. The handler receives that text, or `undefined` when the user typed nothing, plus a context carrying the AI session the command was typed into. Use this instead of `command` when the behavior belongs to a conversation and needs the session. Use `command` when it belongs to the workbench and runs from the palette. Use `tool` when the model, not the user, should decide to run it. Slash commands go in the same top-level `commands` array as palette commands.slashCommand({ id: stringid: 'ask', description: stringdescription: 'Ask something', argumentHint?: string | undefinedargumentHint: '<question>', run: (argument: string | undefined, ctx: SlashCommandContext) => Awaitable<unknown>run: async (argument: string | undefinedargument, ctx: SlashCommandContextctx) => { if (argument: string | undefinedargument === var undefinedundefined) { return; } await ctx: SlashCommandContextctx.session: AiSessionHandleThe chat session the command was typed into.session.AiSessionHandle.run(prompt: string, options?: AiSendOptions): Promise<AiRunResult> (+1 overload)Send a prompt and wait for the finished answer. The generic overload validates the reply against `T` and rejects if the model returns something that does not match, so a caller never has to parse prose.run(argument: stringargument); }, }), ], });
The handler takes two arguments: the text the person typed after the command
name, which is undefined when they typed nothing, and the context.
Slash commands go in the same commands array as commands.
ModCommand is Command | SlashCommand.
Members
SlashCommand — id, description,
run, argumentHint, when, kind.
The differences from Command are the interesting part:
- No
title. In a chat session theidis what a person types, so a separate display title would be a second name for the same thing. descriptioninstead — it is what a person reads in the autocomplete list, not what they type.argumentHinttells them what to type after the id.
Because a slash command runs inside a live session, its handler receives free text and the session alongside the usual context.