← Documentation Home

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