Skip to main content

API Reference

This page provides complete API documentation for the extension system.

Extension Interface​

The main interface that all extensions must implement. All methods are optional - implement only what you need.

interface Extension {
// Lifecycle
onLoad?(context: ExtensionContext): void | Promise<void>;
onUnload?(): void | Promise<void>;

// Registration
getTools?(context: ExtensionContext, mode: string, agentProfile: AgentProfile): ToolDefinition[];

getCommands?(context: ExtensionContext): CommandDefinition[];
getModes?(context: ExtensionContext): ModeDefinition[];
getAgents?(context: ExtensionContext): AgentProfile[];

// Providers
getProviders?(context: ExtensionContext): ProviderDefinition[];

// UI Components
getUIComponents?(context: ExtensionContext): UIComponentDefinition[];
getUIComponentsLibraries?(): Record<string, string>;
getUIExtensionData?(componentId: string, context: ExtensionContext): Promise<unknown>;
executeUIExtensionAction?(componentId: string, action: string, args: unknown[], context: ExtensionContext): Promise<unknown>;

// Agent Profile Updates
onAgentProfileUpdated?(context: ExtensionContext, agentId: string, updatedProfile: AgentProfile): Promise<AgentProfile>;

// Event Handlers - See Events Reference for details
onTaskCreated?(event, context): Promise<void | Partial<Event>>;
onTaskDeleted?(event, context): Promise<void | Partial<Event>>;
onPromptTemplate?(event, context): Promise<void | Partial<PromptTemplateEvent>>;
onToolsetCreated?(event, context): Promise<void | Partial<ToolsetCreatedEvent>>;
// ... and more event handlers
}

Model Call Settings​

Extensions can override AI SDK model-call parameters from onAgentStarted by returning modelCallSettings. The event receives the agent's computed defaults (including maxRetries and abortSignal); returned settings are merged with those defaults, so provide only the fields to override.

async onAgentStarted(event: AgentStartedEvent): Promise<Partial<AgentStartedEvent>> {
return {
modelCallSettings: {
temperature: 0.2,
maxOutputTokens: 4096,
timeout: { totalMs: 120_000 },
},
};
}
type Reasoning = 'provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';

interface ModelCallTimeout {
totalMs?: number;
stepMs?: number;
chunkMs?: number;
toolMs?: number;
tools?: Record<string, number>;
}

interface ModelCallSettings {
maxOutputTokens?: number;
temperature?: number;
topP?: number;
topK?: number;
presencePenalty?: number;
frequencyPenalty?: number;
stopSequences?: string[];
seed?: number;
reasoning?: Reasoning;
maxRetries?: number;
abortSignal?: AbortSignal;
timeout?: number | ModelCallTimeout;
headers?: Record<string, string>;
}

Use either temperature or topP for sampling. Setting reasoning uses AI SDK's top-level reasoning control and takes precedence over provider-specific reasoning options.

ExtensionContext​

Passed to all extension methods, providing access to AiderDesk APIs.

interface ExtensionContext {
// Disposable resource management
addDisposable(setup: () => (() => void | Promise<void>) | void): void;

// Logging
log(message: string, type?: 'info' | 'error' | 'warn' | 'debug'): void;

// Project access
getProjectDir(): string;
getProjectContext(): ProjectContext;
getOpenProjectDirs(): string[];

// Task access
getTaskContext(): TaskContext | null;

// Memory access
getMemoryContext(): MemoryContext;

// Model access
getModelConfigs(): Promise<Model[]>;

// Settings access
getSetting(key: string): Promise<unknown>;
updateSettings(updates: Partial<SettingsData>): Promise<void>;

// Providers
getProviders(): ProviderProfile[];

// MCP Servers
getMcpServers(projectDir?: string): Promise<Record<string, McpServerConfig>>;

// UI refresh
triggerUIDataRefresh(componentId?: string, taskId?: string): void;
triggerUIComponentsReload(): void;

// Navigation
openUrl(url: string, target?: 'external' | 'window' | 'modal-overlay'): Promise<void>;
openPath(path: string): Promise<boolean>;

// Electron access
getElectronApp(): Promise<ElectronApp | null>;

// Advanced
truncateToolResult(
content: string,
maxLines?: number,
maxSizeKB?: number,
maxTokens?: number,
saveToFile?: boolean,
truncationSuffix?: string,
): Promise<string>;
}

Methods​

MethodDescription
addDisposable(setup)Register a setup function whose returned cleanup function is called automatically on extension unload (LIFO order). Setup runs immediately. The cleanup may be synchronous or asynchronous (return a Promise) — async cleanups are awaited during unload, and errors are logged without blocking other cleanups. If it returns void, no cleanup is registered.
log(message, type?)Log a message to AiderDesk console and log files
getProjectDir()Get the current project directory path
getOpenProjectDirs()Get the base directories of all currently open projects
getTaskContext()Get the current task context (null if no task active)
getProjectContext()Get the project context for project operations
getMemoryContext()Get the memory context for store/retrieve/delete memory operations
getModelConfigs()Get all available model configurations
getSetting(key)Get a setting value (supports dot-notation)
updateSettings(updates)Update multiple settings at once
getMcpServers(projectDir?)Get merged MCP server configurations (global overridden by project-specific)
triggerUIDataRefresh(componentId?, taskId?)Trigger UI component data refresh
triggerUIComponentsReload()Reload all UI component definitions for this extension
openUrl(url, target?)Open URL in external browser, new window, or modal overlay
openPath(path)Open file or directory in system's default application
getProviders()Get configured provider profiles (read-only snapshots from ModelManager including API keys; empty array when not available)
getElectronApp()Get a narrowed Electron App object for host-level APIs (e.g., getAppMetrics()); returns null when not running in Electron
truncateToolResult(content, maxLines?, maxSizeKB?, maxTokens?, saveToFile?, truncationSuffix?)Truncate a tool-result string exceeding line/size/token limits, preserving head and tail (defaults: 1000 lines, 50 KB, 50000 tokens; saves full content to a temp file unless saveToFile: false)

TaskContext​

Safe subset of Task capabilities exposed to extensions.

interface TaskContext {
readonly data: TaskData;

// Context Files
getContextFiles(): Promise<ContextFile[]>;
addFile(path: string, readOnly?: boolean): Promise<void>;
addFiles(...files: ContextFile[]): Promise<void>;
dropFile(path: string): Promise<void>;
getAddableFiles(searchRegex?: string): Promise<string[]>;
getAllFiles(useGit?: boolean): Promise<string[]>;
getUpdatedFiles(): Promise<UpdatedFile[]>;
getUpdatedFileDiff(filePath: string, commitHash?: string, previousPath?: string): Promise<string>;

// Context Messages
getContextMessages(): Promise<ContextMessage[]>;
addContextMessage(message: ContextMessage, updateContextInfo?: boolean): Promise<void>;
removeMessage(messageId: string): Promise<void>;
removeLastMessage(): Promise<void>;
removeMessagesUpTo(messageId: string): Promise<void>;
loadContextMessages(messages: ContextMessage[]): Promise<void>;

// Message Helpers
addUserMessage(id: string, content: string, promptContext?: PromptContext): void;
addToolMessage(id: string, serverName: string, toolName: string, input?: unknown, response?: string, usageReport?: UsageReportData, promptContext?: PromptContext, saveToDb?: boolean, finished?: boolean): void;
addResponseMessage(message: ResponseMessage, saveToDb?: boolean): Promise<void>;

// Execution
runPrompt(prompt: string, mode?: string): Promise<void>;
runCustomCommand(name: string, args?: string[], mode?: string): Promise<void>;
runSubagent(agentProfile: AgentProfile, prompt: string): Promise<void>;
runCommand(command: string): Promise<void>;
interruptResponse(): Promise<void>;
generateText(modelId: string, systemPrompt: string, prompt: string): Promise<string | undefined>;
generateObject<T>(modelId: string, systemPrompt: string, prompt: string, schema: z.ZodType<T>): Promise<T | undefined>;
generateTextFromContext(prompt: string, options?: ContextGenerationOptions): Promise<ContextGenerationResult<string>>;
generateObjectFromContext<T>(prompt: string, schema: z.ZodType<T>, options?: ContextGenerationOptions): Promise<ContextGenerationResult<T>>;

// User Interaction
askQuestion(text: string, options?: QuestionOptions): Promise<string>;
addLogMessage(level: 'info' | 'error' | 'warning', message?: string): void;
addLoadingMessage(message?: string, finished?: boolean): void;

// Todos
getTodos(): Promise<TodoItem[]>;
addTodo(name: string): Promise<TodoItem[]>;
updateTodo(name: string, updates: Partial<TodoItem>): Promise<TodoItem[]>;
deleteTodo(name: string): Promise<TodoItem[]>;
clearAllTodos(): Promise<TodoItem[]>;
setTodos(items: TodoItem[], initialUserPrompt?: string): Promise<void>;

// Task Management
updateTask(updates: Partial<TaskData>): Promise<TaskData>;
getTaskDir(): string;
getTaskAgentProfile(): Promise<AgentProfile | null>;
isInitialized(): boolean;

// Context Operations
getRepoMap(): string;
generateContextMarkdown(): Promise<string | null>;
clearContext(): Promise<void>;
resetContext(): Promise<void>;
compactConversation(instructions?: string): Promise<void>;
smartCompact(options?: SmartCompactionOptions): Promise<SmartCompactionResult>;
getEstimatedTokens(): Promise<number>;
handoffConversation(focus?: string, execute?: boolean): Promise<void>;
updateAutocompletionWords(words?: string[]): Promise<void>;

// Git
addToGit(path: string): Promise<void>;

// Questions
answerQuestion(answer: string, userInput?: string): Promise<boolean>;

// Queued Prompts
getQueuedPrompts(): QueuedPromptData[];
sendQueuedPromptNow(promptId: string): Promise<void>;
removeQueuedPrompt(promptId: string): void;

// Redo
redoLastUserPrompt(mode?: string, updatedPrompt?: string): Promise<void>;

// Resume
resumeTask(): Promise<void>;
}

Read-only context generation​

generateTextFromContext and generateObjectFromContext make one model call using the task's selected agent profile, normal system prompt, current context messages/files, tool schemas, and message preparation/cache-control pipeline. They append the supplied prompt to a local copy and return { output, usage, providerMetadata, finishReason } without saving the prompt or response to task context or emitting a main-agent response.

const result = await taskContext.generateObjectFromContext(
'Review the existing evidence. Has the objective been met? Do not use tools.',
z.object({ complete: z.boolean(), reason: z.string() }),
{ abortSignal: controller.signal, maxOutputTokens: 2048 },
);
context.log(JSON.stringify(result.usage));

ContextGenerationOptions accepts an optional abortSignal and maxOutputTokens. Calls use independent cancellation: interruptResponse() does not cancel them. Errors, cancellation, and invalid structured output reject the promise. Extensions should abort their outstanding calls on unload.

Tool definitions remain in the request with toolChoice: 'none', but local execute handlers are removed and provider-native tool approval is denied. A returned tool call is rejected rather than executed. Provider sessions use a fresh ID and do not resume the main agent's native session.

These helpers rebuild current task context, not an exact snapshot of a previous request: transient onAgentStarted/onAgentStepStarted overrides are not replayed, while message transformation/optimization/cache-control hooks still run. Keep the task idle when comparing repeated requests. Preparation hooks should not mutate task state during side generation.

Usage includes inputTokens, outputTokens, totalTokens, inputTokenDetails (noCacheTokens, cacheReadTokens, cacheWriteTokens), outputTokenDetails (textTokens, reasoningTokens), and optional provider raw usage. Missing counts remain undefined, not zero. These calls are billable; usage is returned to the caller rather than added to the main agent's cost totals.

Preserving the request prefix makes provider caching possible, not guaranteed. Structured-output schemas, provider routing, minimum token thresholds, and cache expiry may affect reuse. Some adapters implement structured output through a synthetic output tool; check provider behavior and measured cache-read tokens rather than assuming request equivalence. The tools-plus-none serialization is covered for OpenAI Chat/Responses and Anthropic.

These calls use non-streaming generation unless the model requires streaming, in which case the stream is collected silently. New APIs require an updated main process; reloading an extension alone cannot update an older core.

ProjectContext​

Safe subset of Project capabilities exposed to extensions.

interface ProjectContext {
readonly baseDir: string;

// Task Management
createTask(params: CreateTaskParams): Promise<TaskData>;
getTask(taskId: string): TaskContext | null;
getTasks(): Promise<TaskData[]>;
reloadTasks(): Promise<TaskData[]>;
getMostRecentTask(): TaskContext | null;
forkTask(taskId: string, messageId: string): Promise<TaskData>;
duplicateTask(taskId: string): Promise<TaskData>;
deleteTask(taskId: string): Promise<void>;

// Configuration
getAgentProfiles(): AgentProfile[];
getCommands(): CommandsData;
getProjectSettings(): ProjectSettings;

// History
getInputHistory(): Promise<string[]>;
}

ToolDefinition​

Define custom tools that the AI can use.

interface ToolDefinition<TSchema extends z.ZodType = z.ZodType<Record<string, unknown>>> {
name: string; // Tool identifier in kebab-case
description: string; // Description for the LLM
inputSchema: TSchema; // Zod schema for parameter validation
execute: ( // Execute function
input: z.infer<TSchema>,
signal: AbortSignal | undefined,
context: ExtensionContext
) => Promise<unknown>;
}

Example​

const myTool: ToolDefinition = {
name: 'run-linter',
description: 'Run the project linter',
inputSchema: z.object({
fix: z.boolean().optional().describe('Auto-fix issues'),
files: z.array(z.string()).optional().describe('Files to lint'),
}),
async execute(input, signal, context) {
// Your implementation
return { results: '...' };
},
};

CommandDefinition​

Define custom slash commands.

interface CommandDefinition {
name: string; // Command name in kebab-case (colons allowed for namespacing, e.g. 'impl:tweak')
description: string; // Description shown in autocomplete
arguments?: CommandArgument[]; // Optional command arguments
execute: (args: string[], context: ExtensionContext) => Promise<void>;
}

interface CommandArgument {
description: string;
required?: boolean;
options?: string[];
}

Example​

const myCommand: CommandDefinition = {
name: 'generate-tests',
description: 'Generate unit tests for a file',
arguments: [
{ description: 'File path', required: true },
{ description: 'Framework (jest, vitest)', required: false },
],
async execute(args, context) {
const filePath = args[0];
const framework = args[1] || 'vitest';
// Your implementation
},
};

ModeDefinition​

Define custom chat modes.

interface ModeDefinition {
name: Mode; // Mode identifier
label: string; // Display name
description?: string; // Optional description
icon?: string; // Optional icon from react-icons (e.g., 'FiCode')
}

Example​

const planMode: ModeDefinition = {
name: 'plan',
label: 'Plan',
description: 'Plan before coding - no file modifications',
icon: 'FiClipboard',
};

UIComponentDefinition​

Define custom React components that render in AiderDesk's UI.

Important: The jsx property must be a string containing the component code, not a function. The React object is globally available within components (not passed as a prop).

interface UIComponentDefinition {
id: string; // Unique component identifier
placement: UIComponentPlacement; // Where to render the component
jsx: string; // JSX/TSX component as string
loadData?: boolean; // Enable data loading via getUIExtensionData (default: false)
noDataCache?: boolean; // Always fetch fresh data on render (default: false)
}

Example​

const myComponent: UIComponentDefinition = {
id: 'my-status-indicator',
placement: 'task-status-bar-right',
loadData: true,
jsx: `
(props) => {
const { ui, data, task } = props;
const { useState } = React;
const { Tooltip } = ui;

const [isHovered, setIsHovered] = useState(false);

return (
<Tooltip content="Task status">
<div
className="flex items-center gap-1 text-xs"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<span className="w-2 h-2 rounded-full bg-success" />
<span>{task?.name || 'No task'}</span>
</div>
</Tooltip>
);
}
`,
};

Properties​

PropertyTypeRequiredDescription
idstringYesUnique identifier for the component
placementUIComponentPlacementYesWhere to render the component (see placements below)
jsxstringYesJSX/TSX component code as a string
loadDatabooleanNoEnable data loading via getUIExtensionData()
noDataCachebooleanNoDisable caching - always fetch fresh data

UIComponentPlacement​

Available placement locations for UI components (28 total).

type UIComponentPlacement =
// Task Status Bar
| 'task-status-bar-left' // Left side of task status bar (top of task page)
| 'task-status-bar-right' // Right side of task status bar

// Task Top Bar
| 'task-top-bar-left' // Left side of task top bar (above messages)
| 'task-top-bar-right' // Right side of task top bar

// Task Messages
| 'task-messages-top' // Above all messages
| 'task-messages-bottom' // Below all messages
| 'task-message' // Replace default message rendering (requires messageFilter)
| 'task-message-above' // Above each message (receives message prop)
| 'task-message-below' // Below each message (receives message prop)
| 'task-message-bar' // In message action bar (receives message prop)
| 'task-message-bar-menu' // In message bar dropdown menu (receives message prop)

// Task Usage Info
| 'task-usage-info-bottom' // Below usage info (tokens, costs)

// Task Input
| 'task-input-above' // Above input field
| 'task-input-toolbar-left' // Left side of input toolbar
| 'task-input-toolbar-right' // Right side of input toolbar

// Task State Actions
| 'task-state-actions' // Action buttons (when stopped/waiting)
| 'task-state-actions-all' // Action buttons (all states)

// Sidebar
| 'tasks-sidebar-header' // Header of tasks sidebar
| 'tasks-sidebar-actions-left' // Left side of tasks sidebar action area
| 'tasks-sidebar-actions-right' // Right side of tasks sidebar action area
| 'tasks-sidebar-bottom' // Bottom of tasks sidebar
| 'task-sidebar-item-badges' // Badges within each task sidebar item (per-task)

// Header
| 'header-left' // Left side of main header
| 'header-right' // Right side of main header

// Welcome Page
| 'welcome-page' // Full welcome page (no task open)

// Floating Panels
| 'task-floating' // Draggable floating panel scoped to task
| 'project-floating' // Draggable floating panel scoped to project
| 'app-floating'; // Draggable floating panel at app level

UI Component Props​

Important: The React object is globally available in all UI components (not passed as a prop). Access hooks via React.useState, React.useEffect, etc.

Props passed to UI component functions:

interface UIComponentProps {
// Context data
projectDir?: string; // Project directory path
task?: TaskData; // Current task data
agentProfile?: AgentProfile; // Current agent profile
models: Model[]; // Available AI models
providers: ProviderProfile[]; // Available provider profiles

// UI library
ui: UIComponents; // Pre-built UI components

// Icons library (organized by icon set)
icons: Record<string, Record<string, IconComponent>>;

// External libraries (loaded via getUIComponentsLibraries)
libraries: Record<string, Record<string, unknown>>;

// Extension integration
executeExtensionAction: (action: string, ...args: unknown[]) => Promise<unknown>;

// Data from getUIExtensionData() (if loadData: true)
data?: unknown;

// Message-specific (for message placements)
message?: MessageData;
}

Using React Hooks​

(props) => {
const { useState, useEffect, useCallback } = React;
const [count, setCount] = useState(0);

const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);

return <button onClick={handleClick}>Count: {count}</button>;
}

Using Icons​

The icons prop provides access to all react-icons libraries:

(props) => {
const { icons } = props;
const FiSettings = icons.Fi.FiSettings;
const HiCheck = icons.Hi.HiCheck;

return (
<div>
<FiSettings className="w-4 h-4" />
<HiCheck className="w-5 h-5 text-success" />
</div>
);
}

Available icon sets: Ai, Bi, Bs, Cg, Ci, Di, Fa, Fc, Fi, Gi, Go, Gr, Hi, Im, Io, Io5, Lu, Md, Pi, Ri, Rx, Si, Sl, Tb, Tfi, Ti, Vsc, Wi

UIComponents​

Pre-built UI components available via props.ui:

interface UIComponents {
Button: UIComponent; // Standard button with variants
IconButton: UIComponent; // Button with icon only
Checkbox: UIComponent; // Checkbox input with label
Input: UIComponent; // Text input field
Select: UIComponent; // Dropdown select
MultiSelect: UIComponent; // Multi-value select
TextArea: UIComponent; // Multi-line text input
RadioButton: UIComponent; // Radio button input
Slider: UIComponent; // Range slider
DatePicker: UIComponent; // Date picker
Chip: UIComponent; // Tag/chip component
ModelSelector: UIComponent; // Accepts ModelSelectorProps (see creating-extensions)
Tooltip: UIComponent; // Tooltip wrapper
LoadingOverlay: UIComponent; // Loading spinner with message
ConfirmDialog: UIComponent; // Confirmation dialog modal
ModalOverlayLayout: UIComponent; // Modal layout and title bar
CodeBlock: UIComponent; // Syntax-highlighted code and diffs
ExpandableMessageBlock: UIComponent; // Collapsible tool-style message block
}

ExtensionMetadata​

Metadata describing an extension.

interface ExtensionMetadata {
name: string; // Display name
version: string; // Semantic version (e.g., "1.0.0")
description?: string; // Brief description
author?: string; // Author name or organization
capabilities?: string[]; // Optional capabilities list
}

ProviderDefinition​

Defines a custom LLM provider that an extension can register. Provider configuration is fully owned by the extension and is not UI-editable.

interface ProviderDefinition {
id: string;
name: string;
provider: { name: string; [key: string]: unknown };
strategy: ExtensionProviderStrategy;
headers?: Record<string, string>;
}

Properties​

PropertyTypeRequiredDescription
idstringYesUnique provider identifier (e.g., 'my-provider')
namestringYesProvider name — can be a new name or a built-in provider name to override it
providerobjectYesProvider configuration. Must contain name, extension can add any other fields
strategyExtensionProviderStrategyYesProvider strategy implementation (see below)
headersRecord<string, string>NoOptional HTTP headers for API requests

ExtensionProviderStrategy​

The strategy implementation for a custom LLM provider. Only createLlm and loadModels are required — all other methods are optional and will use built-in defaults when omitted.

interface ExtensionProviderStrategy {
createLlm: (
profile: ProviderProfile,
model: Model,
settings: SettingsData,
projectDir: string,
toolSet?: unknown,
systemPrompt?: string,
providerMetadata?: unknown,
tlsRegistrar?: TlsPolicyRegistrar,
sessionId?: string,
approveTool?: ProviderToolApprovalHandler,
) => unknown | Promise<unknown>;
loadModels: (profile: ProviderProfile, settings: SettingsData, tlsRegistrar?: TlsPolicyRegistrar) => Promise<LoadModelsResponse>;

getAiderMapping?: (provider: ProviderProfile, modelId: string, settings: SettingsData, projectDir: string) => AiderModelMapping;
getUsageReport?: (task: unknown, provider: ProviderProfile, model: Model, usage: unknown, providerMetadata?: unknown) => UsageReportData;
getProviderOptions?: (model: Model, reasoning?: Reasoning) => Record<string, Record<string, JSONValue | undefined>> | undefined;
getCacheControl?: (model: Model) => CacheControl | undefined;
getProviderTools?: (model: Model) => Record<string, Tool> | Promise<Record<string, Tool>>;
getProviderParameters?: (model: Model, reasoning?: Reasoning) => Record<string, unknown>;
createVoiceSession?: (profile: ProviderProfile, settings: SettingsData) => Promise<VoiceSession>;
isRetryable?: (error: unknown) => boolean;
}

Methods​

MethodRequiredDescription
createLlm(profile, model, settings, projectDir, toolSet?, systemPrompt?, providerMetadata?, tlsRegistrar?, sessionId?, approveTool?)YesCreate and return an LLM instance for the given model. The returned type depends on the AI SDK provider being used. toolSet contains available tools, systemPrompt is the agent's system prompt, and providerMetadata carries state from previous provider calls. tlsRegistrar supports scoped TLS policies, sessionId identifies the AiderDesk task (not a native provider session), and approveTool checks policy for externally executed tools without executing them.
loadModels(profile, settings, tlsRegistrar?)YesLoad available models for this provider. Returns a LoadModelsResponse.
getAiderMapping(provider, modelId, settings, projectDir)NoReturn Aider environment variable mappings for the given model (e.g., API keys, base URLs).
getUsageReport(task, provider, model, usage, providerMetadata?)NoCompute and return a usage report with token counts and cost information.
getProviderOptions(model, reasoning?)NoReturn provider-specific options for AI SDK calls (e.g., reasoning effort, search grounding).
getCacheControl(model)NoReturn cache control configuration for the provider.
getProviderTools(model)NoReturn additional provider-specific tools (e.g., Google search grounding).
getProviderParameters(model, reasoning?)NoReturn additional parameters to pass to the AI SDK provider.
createVoiceSession(profile, settings)NoCreate a voice session for real-time audio conversation.
isRetryable(error)NoDetermine if a given error is retryable.

Streaming requirements​

Models returned by loadModels can declare requireStreaming: true when their endpoint rejects non-streaming requests, or disableStreaming: true when streaming is unsupported:

const model: Model = {
id: 'streaming-only-model',
providerId: profile.id,
requireStreaming: true,
};

Both flags are also available in provider settings and model overrides in Model Library. Resolution follows model-declared defaults, then provider settings, then model providerOverrides (highest priority). Enabling either flag clears the opposite inherited flag. Explicit false overrides that flag without enabling the opposite behavior. If both flags are true at the same level in imported configuration, requireStreaming wins.

The main agent streams normally unless streaming is disabled. One-shot generation (generateText, generateObject, generateTextFromContext, and generateObjectFromContext) uses non-streaming calls unless streaming is required. For required streaming, the host consumes the stream silently and returns the final output and, for context generation, usage and provider metadata. It does not publish partial responses to the task.

ProviderToolApprovalHandler​

The optional final createLlm argument lets an external-agent provider check native-tool execution against the current run's AiderDesk approval policy:

type ProviderToolApprovalHandler = (
toolName: string,
input: Record<string, unknown>,
text: string,
subject?: string,
) => Promise<[approved: boolean, reason?: string]>;

Map the native call to its AiderDesk tool name and input schema first (for example, OpenCode read → power---file_read with { filePath }, or shell → power---bash with { command, cwd }). Await approveTool before replying to the external agent's permission request. The result is a decision and optional rejection reason, not tool output; the callback does not execute the tool.

  • A missing/disabled tool or Never approval state is denied in every autonomy mode. Power bash denied-command patterns are also enforced.
  • In Manual mode, Always skips the question and Ask prompts. Existing approval keys and “Always for This Run” decisions are shared with ordinary Power tools. Matching allowed bash patterns also skip the question.
  • In Guided/Autonomous mode, enabled tools do not prompt. For requests not rejected by the disabled-tool or denied-pattern checks, onToolApproval and onHandleApproval hooks still run before automatic/pre-approved decisions and can veto them.
  • This does not run Power's executor or automatically apply its onToolCalled/onToolFinished hooks, execution guards, sandboxing, or output processing. Document native-execution differences in the extension README.
  • The argument is optional for compatibility and may be absent outside a full agent run or in an older host. A native-tool adapter should reject execution with an actionable reason when it is absent, rather than silently auto-approve or reimplement policy with TaskContext.askQuestion.

For example, this helper checks a native read request; the adapter must send the returned decision back to its native agent:

import type { ProviderToolApprovalHandler } from '@aiderdesk/extensions';

const approveNativeRead = async (
approveTool: ProviderToolApprovalHandler | undefined,
filePath: string,
): Promise<[boolean, string?]> => {
if (!approveTool) {
return [false, 'This provider needs an AiderDesk build with provider tool approvals. Update and restart AiderDesk.'];
}
return approveTool('power---file_read', { filePath }, 'Approve native file read?', filePath);
};

External-agent providers​

Providers that wrap another coding agent have additional responsibilities:

  • One execution owner: native tools execute in the external agent; MCP-bridged AiderDesk tools execute through their supplied toolSet executors. Set providerExecuted: true on observed tool calls in the AI SDK adapter so AiderDesk does not execute them again. Approval continuations can replay tool calls; deduplicate by call ID and retain the original arguments.
  • Output and streaming: preserve the original wrapped-tool result for AiderDesk UI/history, even if MCP uses a different toModelOutput representation. Forward coherent text/reasoning block IDs and ordering; do not append replayed content as new content. AiderDesk owns message grouping for standard AI SDK streaming providers: tool/non-assistant boundaries close assistant groups, not every text-end, and step finalization follows consumption of finish-step rather than the raw SDK callback. An extension that renders its own messages must keep streamed and finalized messages aligned too.
  • Session lifetime: an external session ID is useful only while its backing store exists. Persist native session data separately from temporary server/bridge files, store scoped session metadata, and stop processes on completion/interruption/unload. Validate the workspace, model and completed history before resuming; rebuild from full AiderDesk history after edits, clearing, compaction, or a missing session. Do not label a pre-created empty session as an existing conversation if that prevents the SDK from importing prior history.
  • Parent/subagent isolation: the sessionId argument currently carries the AiderDesk task ID; subagents may share it with their parent. Scope native sessions and resource ownership per conversation/run rather than treating that ID as a unique native session.
  • Host compatibility: when the extension depends on a new core callback or streaming fix, test with the matching AiderDesk core. Hot-reloading the extension or refreshing the renderer does not update an older main process; update/build and restart the application.

See OpenCode Free for a native-tool/MCP adapter and Cursor SDK for extension-owned agent/message orchestration.

Example​

import type {
Extension,
ProviderDefinition,
ProviderProfile,
Model,
LoadModelsResponse,
} from '@aiderdesk/extensions';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const MY_PROVIDER_NAME = 'my-custom-provider';

export default class MyProviderExtension implements Extension {
getProviders(): ProviderDefinition[] {
return [
{
id: MY_PROVIDER_NAME,
name: MY_PROVIDER_NAME,
provider: { name: MY_PROVIDER_NAME, apiKey: '' },
strategy: {
createLlm: (profile: ProviderProfile, model: Model) => {
const providerConfig = profile.provider as { apiKey?: string; baseUrl?: string };
const customProvider = createOpenAICompatible({
name: MY_PROVIDER_NAME,
apiKey: providerConfig.apiKey,
baseURL: providerConfig.baseUrl ?? 'https://example.com/v1',
});
return customProvider.languageModel(model.id);
},

loadModels: async (profile: ProviderProfile): Promise<LoadModelsResponse> => {
return {
success: true,
models: [
{
id: 'my-model-v1',
providerId: profile.id,
maxInputTokens: 128000,
maxOutputTokensLimit: 4096,
inputCostPerToken: 0.5,
outputCostPerToken: 1.5,
},
],
};
},
},
},
];
}
}

LoadModelsResponse​

Response from loading models for a provider.

interface LoadModelsResponse {
models: Model[];
success: boolean;
error?: string;
}

ToolResult​

Result returned by tool execution.

interface ToolResult {
content: Array<
| { type: 'text'; text: string }
| { type: 'image'; source: unknown }
>;
details?: Record<string, unknown>;
isError?: boolean;
}

MemoryContext​

Provides access to AiderDesk's memory system — the same underlying vector store used by the built-in memory MCP tools. Available via context.getMemoryContext() in any extension lifecycle hook or method.

Memories are scoped by the first parameter of storeMemory/retrieveMemories. Use the project directory (context.getProjectDir()) for project-scoped memories, or agent-profile:{profileId} to share memories across projects for agents with memory isolation (useAgentMemoryScope) enabled.

interface MemoryContext {
storeMemory(scopeId: string, taskId: string, type: MemoryEntryType, content: string): Promise<string>;
retrieveMemories(scopeId: string, query: string, limit?: number): Promise<MemoryEntry[]>;
getMemory(id: string): Promise<MemoryEntry | null>;
deleteMemory(id: string): Promise<boolean>;
updateMemory(id: string, content: string): Promise<boolean>;
getAllMemories(): Promise<MemoryEntry[]>;
isMemoryEnabled(): boolean;
setMemoryEnabled(enabled: boolean): void;
}

Methods​

MethodReturnsDescription
storeMemory(scopeId, taskId, type, content)Promise<string>Store a new memory entry. Returns the created memory ID.
retrieveMemories(scopeId, query, limit?)Promise<MemoryEntry[]>Retrieve memories by semantic similarity, ranked by relevance.
getMemory(id)Promise<MemoryEntry | null>Get a single memory by ID.
deleteMemory(id)Promise<boolean>Delete a specific memory by ID. Returns true if successful.
updateMemory(id, content)Promise<boolean>Update the content of an existing memory. Returns true if successful.
getAllMemories()Promise<MemoryEntry[]>Get all stored memories.
isMemoryEnabled()booleanCheck if the memory system is enabled and initialized.
setMemoryEnabled(enabled)voidEnable or disable the memory system. Persists the setting.

MemoryEntryType​

Memory entries are categorized by type:

enum MemoryEntryType {
Task = 'task', // Task-specific outcomes and observations
UserPreference = 'user-preference', // Durable user preferences and conventions
CodePattern = 'code-pattern', // Reusable codebase patterns and practices
}

MemoryEntry​

interface MemoryEntry {
id: string; // Unique memory identifier
content: string; // Memory content text
type: MemoryEntryType; // Category of memory
taskId?: string; // Associated task ID (if applicable)
projectId?: string; // Memory scope: project directory path, or agent profile scope (`agent-profile:{id}`)
timestamp: number; // Creation timestamp
}

Example​

import type { Extension, ExtensionContext, AgentFinishedEvent } from '@aiderdesk/extensions';

export default class MemoryExtension implements Extension {
async onAgentFinished(event: AgentFinishedEvent, context: ExtensionContext) {
const memory = context.getMemoryContext();
if (!memory.isMemoryEnabled()) return;

const scopeId = context.getProjectDir();
const taskId = context.getTaskContext()?.data.id ?? '';

// Store a code pattern discovered during the task
await memory.storeMemory(
scopeId,
taskId,
'code-pattern',
'Always use clsx for conditional class names in React components',
);

// Retrieve relevant memories
const memories = await memory.retrieveMemories(scopeId, 'React class naming conventions');
context.log(`Found ${memories.length} relevant memories`, 'info');
}
}