API Reference
TypeScript API reference for @nimbalyst/extension-sdk, covering ExtensionContext, custom editors, AI tools, panels, storage, and manifest types.
This document summarizes the main TypeScript exports from @nimbalyst/extension-sdk.
Main Imports
import type {
ExtensionContext,
ExtensionManifest,
ExtensionModule,
EditorHostProps,
ExtensionAITool,
AIToolContext,
ExtensionToolResult,
PanelHostProps,
SettingsPanelProps,
SettingsRouteContribution,
SettingsRouteProjectTarget,
ResolvedTrackerReference,
} from '@nimbalyst/extension-sdk';
import {
REQUIRED_EXTERNALS,
TrackerReferenceChip,
TrackerReferencePicker,
navigateToTrackerReference,
useEditorLifecycle,
useResolvedTrackerReference,
validateExtensionBundle,
} from '@nimbalyst/extension-sdk';
import { createExtensionConfig } from '@nimbalyst/extension-sdk/vite';
Extension Entry Point
Your extension module can export any subset of these fields:
interface ExtensionModule {
activate?: (context: ExtensionContext) => void | Promise<void>;
deactivate?: () => void | Promise<void>;
components?: Record<string, React.ComponentType<EditorHostProps>>;
aiTools?: ExtensionAITool[];
slashCommandHandlers?: Record<string, () => void>;
nodes?: Record<string, unknown>;
transformers?: Record<string, unknown>;
lexicalExtensions?: Record<string, unknown>;
hostComponents?: Record<string, React.ComponentType>;
panels?: Record<string, PanelExport>;
settingsPanel?: Record<string, React.ComponentType<SettingsPanelProps>>;
}
ExtensionContext
Passed to activate() and available inside AIToolContext.extensionContext.
interface ExtensionContext {
manifest: ExtensionManifest;
extensionPath: string;
services: ExtensionServices;
subscriptions: Disposable[];
}
ExtensionServices
interface ExtensionServices {
filesystem: ExtensionFileSystemService;
ui: ExtensionUIService;
ai?: ExtensionAIService;
configuration?: ExtensionConfigurationService;
}
interface ExtensionFileSystemService {
readFile(path: string): Promise<string>;
writeFile(path: string, content: string | Uint8Array): Promise<void>;
fileExists(path: string): Promise<boolean>;
findFiles(pattern: string): Promise<string[]>;
}
interface ExtensionUIService {
showInfo(message: string): void;
showWarning(message: string): void;
showError(message: string): void;
}
interface ExtensionConfigurationService {
get<T>(key: string, defaultValue?: T): T;
update(key: string, value: unknown, scope?: 'user' | 'workspace'): Promise<void>;
getAll(): Record<string, unknown>;
}
ExtensionAIService
Available when permissions.ai is true. Provides AI tool registration and direct access to chat/completion models.
interface ExtensionAIService {
// Register AI tools that Claude can call
registerTool(tool: ExtensionAITool): Disposable;
registerContextProvider(provider: ExtensionContextProvider): Disposable;
// Session-backed prompt (creates a session in history)
sendPrompt(options: {
prompt: string;
sessionName?: string;
provider?: 'claude-code' | 'claude' | 'openai';
model?: string;
}): Promise<{ sessionId: string; response: string }>;
// List available chat models (Claude, OpenAI, LM Studio)
listModels(): Promise<ExtensionAIModel[]>;
// Stateless chat completion (no session created)
chatCompletion(options: ChatCompletionOptions): Promise<ChatCompletionResult>;
// Streaming chat completion (no session created)
chatCompletionStream(options: ChatCompletionStreamOptions): Promise<ChatCompletionStreamHandle>;
}
Chat Completion Types
interface ExtensionAIModel {
id: string; // e.g. "claude:claude-sonnet-4-6-20250514"
name: string; // e.g. "Claude Sonnet 4.6"
provider: string; // "claude" | "openai" | "lmstudio"
}
interface ChatCompletionMessage {
role: 'user' | 'assistant' | 'system';
content: string;
}
interface ChatCompletionOptions {
messages: ChatCompletionMessage[];
model?: string; // Model ID from listModels(). Provider default if omitted.
maxTokens?: number;
temperature?: number; // 0-1
systemPrompt?: string; // Prepended as system message
responseFormat?: ResponseFormat; // Constrain output format (JSON, JSON schema)
}
type ResponseFormat =
| { type: 'text' } // Default: plain text
| { type: 'json_object' } // Valid JSON output
| { type: 'json_schema'; // JSON matching a schema
schema: JSONSchema;
name?: string; // Optional schema name (default: 'response')
strict?: boolean; // default: true for OpenAI
};
interface ChatCompletionResult {
content: string; // Assistant response
model: string; // Model that was used
usage?: {
inputTokens: number;
outputTokens: number;
};
}
interface ChatCompletionStreamChunk {
type: 'text' | 'error' | 'done';
content?: string; // Text delta (when type is 'text')
error?: string; // Error message (when type is 'error')
}
interface ChatCompletionStreamOptions extends ChatCompletionOptions {
onChunk: (chunk: ChatCompletionStreamChunk) => void;
}
interface ChatCompletionStreamHandle {
abort(): void; // Cancel the stream
result: Promise<ChatCompletionResult>; // Resolves on completion
}
Custom Editors
Custom editors receive a single host prop. Use the useEditorLifecycle hook (from @nimbalyst/extension-sdk) to handle all lifecycle concerns.
useEditorLifecycle Hook
import { useEditorLifecycle } from '@nimbalyst/extension-sdk';
function useEditorLifecycle<T = string>(
host: EditorHost,
options: UseEditorLifecycleOptions<T>
): UseEditorLifecycleResult<T>;
interface UseEditorLifecycleOptions<T> {
applyContent: (content: T) => void; // Push content into the editor
getCurrentContent?: () => T; // Pull content from the editor (omit for read-only)
parse?: (raw: string) => T; // Parse raw file string into editor format
serialize?: (content: T) => string; // Serialize editor format to string
binary?: boolean; // Use loadBinaryContent() for binary files
onLoaded?: () => void; // Called after initial load
onExternalChange?: (content: T) => void; // Called on external file changes (not echoes)
onSave?: () => Promise<void>; // Custom save flow (replaces default)
onDiffRequested?: (config: DiffConfig) => void; // Custom diff handling
onDiffCleared?: () => Promise<void>; // Custom diff cleanup
}
interface UseEditorLifecycleResult<T> {
isLoading: boolean; // True until initial content loads
error: Error | null; // Load error
theme: string; // Current theme (reactive)
markDirty: () => void; // Call on user edit
isDirty: boolean; // Unsaved changes exist
diffState: DiffState<T> | null; // AI edit diff (null when inactive)
toggleSourceMode: (() => void) | undefined;
isSourceMode: boolean;
}
interface DiffState<T> {
original: T; // Content before AI edit
modified: T; // Content after AI edit
tagId: string; // History tag ID
sessionId: string; // AI session that made the edit
accept: () => void; // Accept changes
reject: () => void; // Revert to original
}
EditorHost Interface
The useEditorLifecycle hook wraps this interface. You rarely need to use it directly.
interface EditorHostProps {
host: EditorHost;
}
interface EditorHost {
readonly filePath: string;
readonly fileName: string;
readonly theme: string;
readonly isActive: boolean;
readonly workspaceId?: string;
readonly supportsSourceMode?: boolean;
readonly storage: ExtensionStorage;
readonly fs?: EditorHostFileSystem;
onThemeChanged(callback: (theme: string) => void): () => void;
loadContent(): Promise<string>;
loadBinaryContent(): Promise<ArrayBuffer>;
onFileChanged(callback: (newContent: string) => void): () => void;
setDirty(isDirty: boolean): void;
saveContent(content: string | ArrayBuffer): Promise<void>;
onSaveRequested(callback: () => void): () => void;
openHistory(): void;
openExternal?(url: string): Promise<void>;
onDiffRequested?(callback: (config: DiffConfig) => void): () => void;
reportDiffResult?(result: DiffResult): void;
isDiffModeActive?(): boolean;
onDiffCleared?(callback: () => void): () => void;
toggleSourceMode?(): void;
onSourceModeChanged?(callback: (isSourceMode: boolean) => void): () => void;
isSourceModeActive?(): boolean;
getConfig?<T>(key: string, defaultValue?: T): T;
registerMenuItems(items: EditorMenuItem[]): void;
}
Supporting editor types:
interface EditorMenuItem {
label: string;
icon?: string;
onClick: () => void;
}
interface DiffConfig {
originalContent: string;
modifiedContent: string;
tagId: string;
sessionId: string;
}
interface DiffResult {
content: string;
action: 'accept' | 'reject';
}
Project Filesystem
EditorHost.fs provides workspace-bounded, versioned access to additional project files:
interface ProjectFileSnapshot {
path: string;
exists: boolean;
content: string | null;
sha256: string | null;
}
interface ProjectFileChange {
path: string;
expectedSha256: string | null;
content: string | null;
}
interface ProjectFileEdit {
label: string;
actor: 'user' | 'agent';
changes: ProjectFileChange[];
}
interface ProjectFileWriteReceipt {
id: string;
label: string;
actor: 'user' | 'agent';
timestamp: number;
files: Array<{
path: string;
beforeSha256: string | null;
afterSha256: string | null;
}>;
atomic: false;
}
interface EditorHostFileSystem {
read(paths: string[]): Promise<ProjectFileSnapshot[]>;
write(edit: ProjectFileEdit): Promise<ProjectFileWriteReceipt>;
onChanged(callback: (paths: string[]) => void): () => void;
}
The service is optional and is unavailable for hosts without local project-file semantics. Writes use the SHA-256 returned by read() to prevent stale overwrites.
Tracker References
The SDK exports host-owned Tracker UI so extensions can store portable issue keys while Nimbalyst owns search, live resolution, display, and navigation.
import {
TrackerReferenceChip,
TrackerReferencePicker,
useResolvedTrackerReference,
navigateToTrackerReference,
} from '@nimbalyst/extension-sdk';
function TrackerField({
value,
onChange,
}: {
value: string[];
onChange(value: string[]): void;
}) {
return (
<TrackerReferencePicker
value={value}
onChange={onChange}
multiple
placeholder="Link Tracker items"
/>
);
}
interface ResolvedTrackerReference {
id: string;
issueKey?: string;
title: string;
status?: string;
type?: string;
priority?: string;
owner?: string;
updatedAt?: string;
}
TrackerReferencePickerprovides the canonical typed search and selection UI.TrackerReferenceChiprenders a live reference in default or compact form.useResolvedTrackerReference(referenceKey)resolves a stored key reactively.navigateToTrackerReference(reference)opens the item in Nimbalyst.
Persist the issue key or reference key, not a copied title or workflow state. The host resolves mutable fields when it renders the reference.
AI Tools
interface ExtensionAITool {
name: string;
description: string;
inputSchema?: JSONSchema;
parameters?: JSONSchema; // legacy alias
scope?: 'global' | 'editor';
editorFilePatterns?: string[];
handler: (
params: Record<string, unknown>,
context: AIToolContext
) => Promise<ExtensionToolResult>;
}
interface AIToolContext {
workspacePath?: string;
activeFilePath?: string;
extensionContext: ExtensionContext;
}
interface ExtensionToolResult {
success: boolean;
message?: string;
data?: unknown;
error?: string;
extensionId?: string;
toolName?: string;
stack?: string;
errorContext?: Record<string, unknown>;
}
JSON Schema Types
interface JSONSchema {
type: 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
properties?: Record<string, JSONSchemaProperty>;
required?: string[];
items?: JSONSchema;
description?: string;
}
interface JSONSchemaProperty {
type: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'null';
description?: string;
enum?: Array<string | number>;
items?: JSONSchemaProperty;
properties?: Record<string, JSONSchemaProperty>;
required?: string[];
default?: unknown;
}
Panels
Panels are non-file-based extension UIs.
interface PanelExport {
component: React.ComponentType<PanelHostProps>;
gutterButton?: React.ComponentType<PanelGutterButtonProps>;
settingsComponent?: React.ComponentType<PanelHostProps>;
}
interface PanelHostProps {
host: PanelHost;
}
interface PanelHost {
readonly panelId: string;
readonly extensionId: string;
readonly theme: string;
readonly workspacePath: string;
readonly isSettingsOpen: boolean;
readonly ai?: PanelAIContext;
readonly storage: ExtensionStorage;
onThemeChanged(callback: (theme: string) => void): () => void;
openFile(path: string): void;
openPanel(panelId: string): void;
close(): void;
openSettings(): void;
closeSettings(): void;
}
interface PanelAIContext {
setContext(context: Record<string, unknown>): void;
getContext(): Record<string, unknown>;
clearContext(): void;
notifyChange(event: string, data?: unknown): void;
onContextChanged(callback: (context: Record<string, unknown>) => void): () => void;
}
interface SettingsPanelProps {
storage: ExtensionStorage;
theme: string;
callBackendTool?: (
toolName: string,
args?: Record<string, unknown>
) => Promise<unknown>;
workspacePath?: string;
projectTarget?: SettingsRouteProjectTarget;
}
workspacePath and projectTarget are provided only for first-class project settings routes. Application routes and legacy nested settings panels do not receive project context.
type SettingsRouteProjectTarget =
| { kind: 'workspace'; workspacePath: string }
| { kind: 'organizationProject'; orgId: string; projectId: string };
interface SettingsRouteContribution {
id: string;
scope: 'application' | 'project';
label: string;
group?: string;
icon?: string;
order?: number;
component: string;
}
Extension Storage
ExtensionStorage is available to custom editors, panels, and settings panels.
interface ExtensionStorage {
get<T>(key: string): T | undefined;
set<T>(key: string, value: T): Promise<void>;
delete(key: string): Promise<void>;
getGlobal<T>(key: string): T | undefined;
setGlobal<T>(key: string, value: T): Promise<void>;
deleteGlobal(key: string): Promise<void>;
getSecret(key: string): Promise<string | undefined>;
setSecret(key: string, value: string): Promise<void>;
deleteSecret(key: string): Promise<void>;
}
Manifest Types
The manifest shape is defined by ExtensionManifest and ExtensionContributions.
See Manifest Reference for field-by-field guidance.
interface ExtensionManifest {
id: string;
name: string;
version: string;
description?: string;
author?: string;
main: string;
styles?: string;
apiVersion?: string;
permissions?: ExtensionPermissions;
contributions?: ExtensionContributions;
requiredReleaseChannel?: 'stable' | 'alpha';
defaultEnabled?: boolean;
}
interface ExtensionContributions {
customEditors?: CustomEditorContribution[];
fileIcons?: Record<string, string>;
aiTools?: string[];
newFileMenu?: NewFileMenuContribution[];
commands?: CommandContribution[];
keybindings?: KeybindingContribution[];
slashCommands?: SlashCommandContribution[];
nodes?: string[];
transformers?: string[];
lexicalExtensions?: string[];
hostComponents?: string[];
configuration?: ExtensionConfigurationContribution;
claudePlugin?: ClaudePluginContribution;
panels?: PanelContribution[];
settingsPanel?: SettingsPanelContribution;
settingsRoutes?: SettingsRouteContribution[];
documentHeaders?: DocumentHeaderContribution[];
themes?: ThemeContribution[];
agentWorkflows?: AgentWorkflowsContribution;
backendModules?: BackendModuleContribution[];
trackerImporters?: TrackerImporterContribution[];
aiAgentProviders?: AiAgentProviderContribution[];
}
The interfaces above are a working map of the common surface, not a substitute for your installed SDK's declarations. Advanced provider, backend-module, collaboration, and tracker-importer APIs evolve with the host; import their types from the package rather than copying these abbreviated declarations into an extension.
Vite Helper
Use createExtensionConfig() to get the correct externalization and output shape for extensions.
import react from '@vitejs/plugin-react';
import { createExtensionConfig } from '@nimbalyst/extension-sdk/vite';
export default createExtensionConfig({
entry: './src/index.tsx',
plugins: [react()],
});
Validation Helpers
import { validateExtensionBundle } from '@nimbalyst/extension-sdk';
const result = await validateExtensionBundle('/path/to/extension');
interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
manifest?: ExtensionManifest;
}
Required Externals
REQUIRED_EXTERNALS exports the package names that must stay external in your build because Nimbalyst provides them at runtime.