Skip to main content

Models

ctx.ai.models exposes the model routes currently available to the workspace. The public catalog is intentionally small: it lists routes, names, and vendors.

Discover and select a model

import { function command<TId extends string>(commandValue: CommandInput<TId>): Command<TId>
Declare an action the user runs from the command palette or a keybinding. Reach for this when a person triggers the behavior. The `title` is what they read in the palette, `category` groups the entry, and `when` decides whether it shows up at all. The handler takes no argument, only a context. Two neighbours are easy to confuse. `slashCommand` is also user-triggered, but it is typed inside a chat session and receives free text plus the live AI session. `tool` is not user-triggered at all, the model decides to call it mid-turn. The result goes in the mod's top-level `commands` array, alongside any slash commands. The `id` is the address a keybinding or a `command:` hook targets, so keep it stable.
@categoryAuthoring
command
, function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): Mod
The 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.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
} from '@modular/sdk';
export default
defineMod<{
    readonly metadata: {
        readonly displayName: "Model Sessions";
        readonly description: "Create sessions from the available model catalog";
    };
    readonly commands: readonly [Command<string>];
}>(definition: {
    readonly metadata: {
        readonly displayName: "Model Sessions";
        readonly description: "Create sessions from the available model catalog";
    };
    readonly commands: readonly [Command<string>];
}): Mod
The 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.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
({
metadata: {
    readonly displayName: "Model Sessions";
    readonly description: "Create sessions from the available model catalog";
}
metadata
: {
displayName: "Model Sessions"displayName: 'Model Sessions', description: "Create sessions from the available model catalog"description: 'Create sessions from the available model catalog', }, commands: readonly [Command<string>]commands: [ command<string>(commandValue: CommandInput<string>): Command<string>
Declare an action the user runs from the command palette or a keybinding. Reach for this when a person triggers the behavior. The `title` is what they read in the palette, `category` groups the entry, and `when` decides whether it shows up at all. The handler takes no argument, only a context. Two neighbours are easy to confuse. `slashCommand` is also user-triggered, but it is typed inside a chat session and receives free text plus the live AI session. `tool` is not user-triggered at all, the model decides to call it mid-turn. The result goes in the mod's top-level `commands` array, alongside any slash commands. The `id` is the address a keybinding or a `command:` hook targets, so keep it stable.
@categoryAuthoring
command
({
id: stringid: 'models.start-first-available', title: stringtitle: 'Start Session With First Available Model', run: (ctx: CommandContext) => Awaitable<unknown>run: async ctx: CommandContextctx => { const const models: readonly AiModelInfo[]models = await ctx: CommandContextctx.ContextBase.ai: AiRuntimeApi
One off answers, agents, models, and sessions.
ai
.AiRuntimeApi.models: AiModelsApimodels.AiModelsApi.list(): Promise<readonly AiModelInfo[]>list();
const const model: AiModelInfomodel = const models: readonly AiModelInfo[]models[0]; if (const model: AiModelInfomodel === var undefinedundefined) { await ctx: CommandContextctx.ui: UiCapability
Notifications, dialogs, quick input, and progress.
ui
.UiCapability.notification: UiNotificationCapabilitynotification.UiNotificationCapability.warning(request: UiNotificationRequest): Promise<UiActionResult>warning({
UiNotificationRequest.message: stringmessage: 'No AI model is currently available.', }); return; } const const session: AiSessionHandlesession = await ctx: CommandContextctx.ContextBase.ai: AiRuntimeApi
One off answers, agents, models, and sessions.
ai
.AiRuntimeApi.sessions: AiSessionsApisessions.AiSessionsApi.create(config?: AiSessionConfig): Promise<AiSessionHandle>
Start a new root session.
create
({
AiSessionConfig.model?: string | undefinedmodel: const model: AiModelInfomodel.AiModelInfo.id: stringid, AiSessionConfig.title?: string | undefinedtitle: `Session with ${const model: AiModelInfomodel.AiModelInfo.name: stringname}`, AiSessionConfig.foreground?: boolean | undefinedforeground: true, }); await const session: AiSessionHandlesession.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
(`Reply with the active model vendor: ${const model: AiModelInfomodel.AiModelInfo.vendor: stringvendor}`);
}, }), ], });

AiModelInfo contains:

MemberMeaning
idExact route passed to session or agent configuration
nameHuman-readable model name
vendorProvider or vendor label

The API has list() only. It does not promise that a cached route remains available, and it has no public model-registration path.

Selection and inheritance

Model choice enters through session configuration:

explicit AiSessionConfig.model

            ├── ctx.ai.ask(prompt, attachments, config)
            ├── ctx.ai.sessions.create(config)
            ├── session.spawn(config)
            └── agentHandle.spawn(config)

omitted model
            └── inherited from the calling session, declared agent, or workspace

A declared agent may pin model. Callers can override that route through agentHandle.config(overrides) or agentHandle.spawn(config). A plain session created without a model uses the workspace's configured route.

Use catalog identities instead of parsing display names. name is for people; id is the executable address.