Skip to content

Plugin API

Mia’s plugin system is designed for extensibility. Each AI backend implements the CodingPlugin interface, and the daemon handles dispatch, fallback, and lifecycle management.

interface CodingPlugin {
/** Plugin identifier (e.g., "claude-code", "gemini") */
name: string
/** Plugin version string */
version: string
/** Initialize with configuration */
initialize(config: PluginConfig): Promise<void>
/** Dispatch a prompt and stream response */
dispatch(
prompt: string,
context: PluginContext,
options: DispatchOptions,
callbacks: DispatchCallbacks
): Promise<PluginDispatchResult>
/** Check if plugin binary/API is accessible */
isAvailable(): Promise<boolean>
/** Get session ID for a conversation */
getSession(conversationId: string): string | undefined
/** Clear session state for a conversation */
clearSession(conversationId: string): void
/** Abort a running dispatch */
abort(taskId: string): Promise<void>
/** Number of currently running dispatches */
getRunningTaskCount(): number
/** Clean up stale sessions */
cleanup(maxAgeMs?: number): number
}
interface PluginConfig {
name: string // Plugin identifier
enabled: boolean // Whether plugin is active
binary?: string // Path to CLI binary
model?: string // Default model
maxConcurrency: number // Max concurrent dispatches
timeoutMs: number // Dispatch timeout
}

Assembled by ContextPreparer before each dispatch:

interface PluginContext {
/** Relevant facts from memory store */
memoryFacts: string[]
/** Compressed codebase summary (languages, frameworks) */
codebaseContext: string
/** Git branch, recent commits, staged changes */
gitContext: string
/** File structure, recently modified files */
workspaceSnapshot: string
/** Contents of .claude-code-instructions if present */
projectInstructions: string
/** Compacted summary of prior conversation turns */
conversationSummary?: string
}

Plugins stream responses via typed callbacks:

interface DispatchCallbacks {
/** Called for each streaming token */
onToken(token: string): void
/** Called when a tool is invoked */
onToolCall(tool: ToolCallInfo): void
/** Called when a tool returns a result */
onToolResult(result: ToolResultInfo): void
/** Called when dispatch completes */
onComplete(result: PluginDispatchResult): void
/** Called on error */
onError(error: PluginError): void
}
interface PluginDispatchResult {
/** Full accumulated response text */
content: string
/** Token usage statistics */
usage?: {
inputTokens: number
outputTokens: number
cacheCreation?: number
cacheRead?: number
}
/** Session ID for conversation continuity */
sessionId?: string
/** List of tool calls made during dispatch */
toolCalls?: ToolCallInfo[]
/** Duration in milliseconds */
durationMs: number
}

All plugins throw standardized errors:

class PluginError extends Error {
code: PluginErrorCode
pluginName: string
detail?: string
}
type PluginErrorCode =
| 'TIMEOUT'
| 'SPAWN_FAILURE'
| 'PROCESS_EXIT'
| 'BUFFER_OVERFLOW'
| 'CONCURRENCY_LIMIT'
| 'PROVIDER_ERROR'
| 'SESSION_ERROR'
| 'UNKNOWN'

CLI-based plugins (Claude Code, Codex) extend BaseSpawnPlugin, which provides:

  • Process spawning — Starts the CLI binary with correct args and environment
  • NDJSON streaming — Parses stdout as newline-delimited JSON
  • Session management — Maps conversation IDs to session IDs for resume
  • Timeout handling — Kills process after configurable duration
  • Stall detection — Kills process if no output for 120 seconds
  • Concurrency limiting — Rejects dispatch if at max concurrent tasks

To add a new CLI-based plugin:

import { BaseSpawnPlugin } from '../base-spawn-plugin.js'
export class MyPlugin extends BaseSpawnPlugin {
name = 'my-plugin'
version = '1.0.0'
protected buildArgs(prompt: string, context: PluginContext): string[] {
return ['--model', this.config.model, '--prompt', prompt]
}
protected parseOutput(line: string): ParsedOutput {
// Parse your CLI's output format
const data = JSON.parse(line)
return { type: data.type, content: data.content }
}
}

Plugins are registered in the daemon’s plugin registry during initialization:

registry.register('my-plugin', new MyPlugin())

The plugin then becomes available for selection via:

Terminal window
mia plugin switch my-plugin

For plugins that call HTTP APIs directly (like Gemini), implement CodingPlugin directly without extending BaseSpawnPlugin. Handle HTTP requests, authentication, and streaming within the dispatch() method.