Agent Studio - Plugin Development Guide
Extend Agent Studio with Custom Extensions and Plugins
Overview
Agent Studio provides a comprehensive plugin system that allows developers to extend functionality across all platforms (VSCode, Electron, macOS, Web). This guide covers plugin architecture, API reference, development workflows, and best practices for creating custom extensions.
Plugin Architecture
Supported Plugin Types
1. VSCode Extensions - Command palette commands - Tree view providers - Webview panels - Language support - Code actions and completions
2. Electron Plugins - Main process extensions - Renderer process UI components - IPC handlers - Native menu items
3. macOS Extensions - Swift Package Plugins - App extensions (Share, Action, etc.) - Native menu items - System integration hooks
4. Web Extensions - React components - API route extensions - Custom dashboards - Webhook integrations
VSCode Extension Development
Extension Manifest
package.json Configuration:
{
"name": "agent-studio-custom-extension",
"displayName": "Agent Studio Custom Extension",
"version": "1.0.0",
"publisher": "your-publisher",
"engines": {
"vscode": "^1.85.0"
},
"categories": ["Programming Languages"],
"activationEvents": [
"onCommand:yourExtension.command",
"onView:yourTreeView"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "yourExtension.helloWorld",
"title": "Hello World",
"category": "Your Extension"
}
],
"viewsContainers": {
"activitybar": [
{
"id": "yourExtensionView",
"title": "Your Extension",
"icon": "resources/icon.svg"
}
]
},
"views": {
"yourExtensionView": [
{
"id": "yourTreeView",
"name": "Custom View"
}
]
}
}
}
Extension Entry Point
src/extension.ts:
import * as vscode from 'vscode';
import { AgentStudioAPI } from '@agentstudio/sdk';
export function activate(context: vscode.ExtensionContext) {
console.log('Your extension is now active!');
// Connect to Agent Studio API
const agentStudio = new AgentStudioAPI({
endpoint: vscode.workspace.getConfiguration('agentStudio').get('apiEndpoint')
});
// Register command
const helloCommand = vscode.commands.registerCommand(
'yourExtension.helloWorld',
async () => {
// Fetch agents from Agent Studio
const agents = await agentStudio.agents.list();
vscode.window.showInformationMessage(
`Found ${agents.length} agents!`
);
}
);
context.subscriptions.push(helloCommand);
// Register tree view provider
const treeProvider = new CustomTreeProvider(agentStudio);
vscode.window.registerTreeDataProvider('yourTreeView', treeProvider);
// Register webview panel
const webviewCommand = vscode.commands.registerCommand(
'yourExtension.openPanel',
() => {
const panel = vscode.window.createWebviewPanel(
'customPanel',
'Custom Panel',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true
}
);
panel.webview.html = getWebviewContent();
// Handle messages from webview
panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) {
case 'executeTask':
const result = await agentStudio.agents.executeTask(
message.agentId,
message.task
);
panel.webview.postMessage({
command: 'taskResult',
result
});
break;
}
},
undefined,
context.subscriptions
);
}
);
context.subscriptions.push(webviewCommand);
}
export function deactivate() {
console.log('Extension deactivated');
}
Tree View Provider
Custom tree data provider:
import * as vscode from 'vscode';
class CustomTreeProvider implements vscode.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
constructor(private agentStudio: AgentStudioAPI) {
// Listen for agent updates
this.agentStudio.on('agent:updated', () => {
this.refresh();
});
}
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(element: TreeItem): vscode.TreeItem {
return element;
}
async getChildren(element?: TreeItem): Promise<TreeItem[]> {
if (!element) {
// Root level - show agents
const agents = await this.agentStudio.agents.list();
return agents.map(
(agent) =>
new TreeItem(
agent.name,
vscode.TreeItemCollapsibleState.Collapsed,
{ id: agent.id, type: 'agent' }
)
);
} else if (element.data.type === 'agent') {
// Show agent tasks
const tasks = await this.agentStudio.agents.getTasks(element.data.id);
return tasks.map(
(task) =>
new TreeItem(
`${task.type} - ${task.status}`,
vscode.TreeItemCollapsibleState.None,
{ id: task.id, type: 'task' }
)
);
}
return [];
}
}
class TreeItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly data: any
) {
super(label, collapsibleState);
if (data.type === 'agent') {
this.iconPath = new vscode.ThemeIcon('robot');
this.contextValue = 'agent';
} else if (data.type === 'task') {
this.iconPath = new vscode.ThemeIcon('tasks');
this.contextValue = 'task';
}
}
}
Webview Panel
Webview HTML content:
function getWebviewContent(): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom Panel</title>
<style>
body {
padding: 20px;
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
}
button {
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
padding: 10px 20px;
cursor: pointer;
}
button:hover {
background-color: var(--vscode-button-hoverBackground);
}
</style>
</head>
<body>
<h1>Agent Task Executor</h1>
<label>Agent ID:</label>
<input type="text" id="agentId" placeholder="agent-123" />
<label>Task Type:</label>
<select id="taskType">
<option value="code_generation">Code Generation</option>
<option value="refactor">Refactor</option>
<option value="test">Test</option>
</select>
<button onclick="executeTask()">Execute Task</button>
<div id="result"></div>
<script>
const vscode = acquireVsCodeApi();
function executeTask() {
const agentId = document.getElementById('agentId').value;
const taskType = document.getElementById('taskType').value;
vscode.postMessage({
command: 'executeTask',
agentId,
task: { type: taskType }
});
}
window.addEventListener('message', (event) => {
const message = event.data;
if (message.command === 'taskResult') {
document.getElementById('result').innerText =
JSON.stringify(message.result, null, 2);
}
});
</script>
</body>
</html>
`;
}
Code Actions and Completions
Provide code actions:
class CustomCodeActionProvider implements vscode.CodeActionProvider {
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext
): vscode.CodeAction[] {
const actions: vscode.CodeAction[] = [];
// Add "Refactor with AI" action
const refactorAction = new vscode.CodeAction(
'Refactor with AI',
vscode.CodeActionKind.RefactorRewrite
);
refactorAction.command = {
command: 'yourExtension.refactorWithAI',
title: 'Refactor with AI',
arguments: [document, range]
};
actions.push(refactorAction);
return actions;
}
}
// Register provider
vscode.languages.registerCodeActionsProvider(
{ scheme: 'file', language: 'typescript' },
new CustomCodeActionProvider()
);
Agent Studio SDK
Installation
npm install @agentstudio/sdk
API Client
Initialize SDK:
import { AgentStudioAPI } from '@agentstudio/sdk';
const client = new AgentStudioAPI({
endpoint: 'http://localhost:3000',
apiKey: process.env.AGENT_STUDIO_API_KEY
});
// Or use default config from environment
const client = new AgentStudioAPI();
Agent Management
List agents:
// Get all agents
const agents = await client.agents.list();
// Filter by status
const healthyAgents = await client.agents.list({
filter: { status: 'healthy' }
});
// Get agent details
const agent = await client.agents.get('agent-id');
// Agent health check
const health = await client.agents.healthCheck('agent-id');
Execute tasks:
// Execute agent task
const task = await client.agents.executeTask('agent-id', {
type: 'code_generation',
payload: {
prompt: 'Create a user authentication service',
language: 'typescript'
}
});
// Get task status
const status = await client.agents.getTaskStatus('agent-id', task.id);
// Wait for completion
const result = await client.agents.waitForTask('agent-id', task.id, {
timeout: 60000 // 60 seconds
});
console.log('Generated code:', result.output);
Spawn agents:
// Spawn agent on demand
const newAgent = await client.agents.spawn({
type: 'worker',
capabilities: ['code_generation', 'testing'],
resources: {
cpu: '2',
memory: '4Gi'
},
runtime: 'kubernetes', // or 'local', 'docker'
ttl: 3600 // seconds
});
// Delete agent
await client.agents.delete(newAgent.id);
Workspace Management
Create and manage workspaces:
// Create workspace
const workspace = await client.workspaces.create({
name: 'My Project',
description: 'Project workspace'
});
// Add file to workspace
await client.workspaces.createFile(workspace.id, {
path: 'src/index.ts',
content: 'console.log("Hello");'
});
// List files
const files = await client.workspaces.listFiles(workspace.id);
// Sync workspace
await client.workspaces.sync(workspace.id);
Real-Time Events
Subscribe to events:
// WebSocket connection
const ws = client.stream.connect();
ws.on('agent:status', (data) => {
console.log('Agent status changed:', data);
});
ws.on('task:completed', (data) => {
console.log('Task completed:', data);
});
ws.on('sync:file_change', (data) => {
console.log('File changed:', data);
});
// Unsubscribe
ws.close();
Electron Plugin Development
Main Process Plugin
Create plugin in src/plugins/custom-plugin.ts:
import { app, BrowserWindow, ipcMain } from 'electron';
export class CustomPlugin {
private mainWindow: BrowserWindow | null = null;
constructor() {
this.registerIpcHandlers();
}
setMainWindow(window: BrowserWindow): void {
this.mainWindow = window;
}
private registerIpcHandlers(): void {
// Handle custom IPC events
ipcMain.handle('custom:action', async (event, data) => {
// Perform action
const result = await this.performCustomAction(data);
return result;
});
ipcMain.on('custom:notify', (event, message) => {
// Send notification
this.mainWindow?.webContents.send('notification', message);
});
}
private async performCustomAction(data: any): Promise<any> {
// Custom logic here
return { success: true, data: 'processed' };
}
}
// Export plugin
export function activate() {
return new CustomPlugin();
}
Register plugin in main.ts:
import { activate as activateCustomPlugin } from './plugins/custom-plugin';
const customPlugin = activateCustomPlugin();
customPlugin.setMainWindow(mainWindow);
Renderer Process Plugin
React component plugin:
// src/plugins/CustomPanel.tsx
import React, { useEffect, useState } from 'react';
import { ipcRenderer } from 'electron';
export const CustomPanel: React.FC = () => {
const [result, setResult] = useState<string>('');
useEffect(() => {
// Listen for notifications
const handler = (event: any, message: string) => {
console.log('Notification:', message);
};
ipcRenderer.on('notification', handler);
return () => {
ipcRenderer.removeListener('notification', handler);
};
}, []);
const handleAction = async () => {
// Call main process
const response = await ipcRenderer.invoke('custom:action', {
data: 'test'
});
setResult(JSON.stringify(response));
};
return (
<div>
<h2>Custom Panel</h2>
<button onClick={handleAction}>Execute Action</button>
<pre>{result}</pre>
</div>
);
};
macOS Plugin Development
Swift Package Plugin
Create plugin manifest:
// Package.swift
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "CustomAgentStudioPlugin",
platforms: [.macOS(.v14)],
products: [
.library(
name: "CustomAgentStudioPlugin",
targets: ["CustomAgentStudioPlugin"]
)
],
dependencies: [
.package(url: "https://github.com/agent-studio/plugin-api", from: "1.0.0")
],
targets: [
.target(
name: "CustomAgentStudioPlugin",
dependencies: ["AgentStudioPluginAPI"]
)
]
)
Plugin implementation:
import Foundation
import AgentStudioPluginAPI
@objc public class CustomPlugin: NSObject, AgentStudioPlugin {
public var name: String { "Custom Plugin" }
public var version: String { "1.0.0" }
public func activate(context: PluginContext) {
print("Custom plugin activated")
// Register menu item
context.registerMenuItem(
title: "Custom Action",
action: #selector(customAction)
)
// Register command
context.registerCommand(
name: "custom.action",
handler: handleCommand
)
}
@objc func customAction() {
// Perform custom action
print("Custom action executed")
}
func handleCommand(args: [String: Any]) -> Any? {
// Handle command
return ["success": true]
}
public func deactivate() {
print("Custom plugin deactivated")
}
}
Web Dashboard Extensions
React Component Extension
Create custom dashboard component:
// app/plugins/custom-dashboard/page.tsx
'use client';
import React, { useEffect, useState } from 'react';
import { useAgentStudio } from '@/lib/hooks/useAgentStudio';
export default function CustomDashboard() {
const { client } = useAgentStudio();
const [data, setData] = useState<any[]>([]);
useEffect(() => {
const fetchData = async () => {
const agents = await client.agents.list();
setData(agents);
};
fetchData();
// Subscribe to updates
const ws = client.stream.connect();
ws.on('agent:updated', fetchData);
return () => ws.close();
}, []);
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Custom Dashboard</h1>
<div className="grid grid-cols-3 gap-4">
{data.map((agent) => (
<div key={agent.id} className="border p-4 rounded">
<h3 className="font-semibold">{agent.name}</h3>
<p className="text-sm text-gray-600">{agent.status}</p>
</div>
))}
</div>
</div>
);
}
API Route Extension
Add custom API endpoint:
// app/api/plugins/custom/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { AgentStudioAPI } from '@agentstudio/sdk';
export async function POST(request: NextRequest) {
const body = await request.json();
const client = new AgentStudioAPI();
try {
const result = await client.agents.executeTask(body.agentId, {
type: body.taskType,
payload: body.payload
});
return NextResponse.json(result);
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Plugin Distribution
VSCode Marketplace
Package and publish:
# Package extension
npm run package
# Publish to marketplace
npm run publish
# Or manually with vsce
npx vsce publish
NPM Package
Publish plugin to npm:
# Build plugin
npm run build
# Publish to npm
npm publish
# Or to GitLab package registry
npm publish --registry=https://gitlab.bluefly.io/api/v4/projects/<project_id>/packages/npm/
Private Registry
Configure .npmrc:
@your-scope:registry=https://your-registry.com/
//your-registry.com/:_authToken=${NPM_TOKEN}
Best Practices
Plugin Security
1. Validate Input:
import { z } from 'zod';
const TaskSchema = z.object({
agentId: z.string().uuid(),
type: z.enum(['code_generation', 'refactor', 'test']),
payload: z.object({}).passthrough()
});
function executeTask(input: unknown) {
const validated = TaskSchema.parse(input);
// Safe to use validated data
}
2. Sanitize Output:
import DOMPurify from 'dompurify';
const sanitized = DOMPurify.sanitize(untrustedHtml);
3. Use Secrets Safely:
// ❌ Bad - hardcoded secret
const apiKey = 'sk-1234567890';
// ✅ Good - environment variable
const apiKey = process.env.AGENT_STUDIO_API_KEY;
Performance Optimization
1. Debounce Expensive Operations:
import debounce from 'lodash/debounce';
const debouncedUpdate = debounce(async () => {
await updateAgentList();
}, 500);
2. Cache Results:
const cache = new Map<string, any>();
async function getAgent(id: string) {
if (cache.has(id)) {
return cache.get(id);
}
const agent = await client.agents.get(id);
cache.set(id, agent);
return agent;
}
3. Virtual Scrolling for Large Lists:
import { FixedSizeList } from 'react-window';
<FixedSizeList
height={500}
itemCount={agents.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>{agents[index].name}</div>
)}
</FixedSizeList>
Error Handling
Comprehensive error handling:
try {
const result = await client.agents.executeTask(agentId, task);
return result;
} catch (error) {
if (error instanceof AgentNotFoundError) {
vscode.window.showErrorMessage(`Agent ${agentId} not found`);
} else if (error instanceof TaskFailedError) {
vscode.window.showErrorMessage(`Task failed: ${error.message}`);
} else {
vscode.window.showErrorMessage('An unexpected error occurred');
console.error(error);
}
}
Debugging Plugins
VSCode Extension Debugging
Launch configuration (.vscode/launch.json):
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"]
}
]
}
Electron Plugin Debugging
# Enable inspector
export ELECTRON_ENABLE_LOGGING=true
npm run dev:electron
# DevTools: Cmd+Option+I
Examples
Complete examples available in:
- /Users/flux423/Sites/LLM/common_npm/agent-studio/apps/AgentStudio-vscode/src/ - VSCode extension examples
- /Users/flux423/Sites/LLM/common_npm/agent-studio/apps/AgentStudio-electron/src/plugins/ - Electron plugins
- /Users/flux423/Sites/LLM/common_npm/agent-studio/apps/AgentStudio-web/app/plugins/ - Web extensions
See Also: - Home - Back to wiki home - Features - Available features - Architecture - System architecture - Development - Developer setup guide
Last Updated: 2025-01-10 Maintainer: LLM Platform Team