Skip to content

Debugger API

The Debugger API provides functionality for debugging applications and managing debug sessions in Trae.

Overview

The Debugger API enables you to:

  • Start and stop debug sessions
  • Set and manage breakpoints
  • Step through code execution
  • Inspect variables and call stacks
  • Evaluate expressions in debug context
  • Handle debug events and notifications
  • Configure debug adapters and protocols
  • Manage debug console output

Basic Usage

Debug Session Manager

typescript
import { TraeAPI } from '@trae/api';

// Debug session manager
class DebugSessionManager {
  private sessions: Map<string, TraeAPI.DebugSession> = new Map();
  private breakpoints: Map<string, TraeAPI.Breakpoint[]> = new Map();
  private eventEmitter = new TraeAPI.EventEmitter<DebugEvent>();
  private disposables: TraeAPI.Disposable[] = [];
  private debugAdapters: Map<string, DebugAdapterDescriptor> = new Map();
  private activeSession: TraeAPI.DebugSession | undefined;
  private debugConsole: DebugConsole;

  constructor() {
    this.debugConsole = new DebugConsole();
    this.setupBuiltinAdapters();
    this.setupEventHandlers();
  }

  // Start debug session
  async startDebugging(
    folder: TraeAPI.WorkspaceFolder | undefined,
    nameOrConfiguration: string | TraeAPI.DebugConfiguration,
    parentSessionOrOptions?: TraeAPI.DebugSession | TraeAPI.DebugSessionOptions
  ): Promise<boolean> {
    try {
      let config: TraeAPI.DebugConfiguration;
      
      if (typeof nameOrConfiguration === 'string') {
        // Get configuration by name
        const configs = TraeAPI.workspace.getConfiguration('launch', folder?.uri);
        const launchConfig = configs.get('configurations', []);
        const foundConfig = launchConfig.find((c: any) => c.name === nameOrConfiguration);
        
        if (!foundConfig) {
          throw new Error(`Debug configuration '${nameOrConfiguration}' not found`);
        }
        
        config = foundConfig;
      } else {
        config = nameOrConfiguration;
      }

      // Resolve configuration
      const resolvedConfig = await this.resolveDebugConfiguration(folder, config);
      if (!resolvedConfig) {
        throw new Error('Failed to resolve debug configuration');
      }

      // Create debug session
      const sessionId = this.generateSessionId();
      const session = await this.createDebugSession(sessionId, resolvedConfig, folder);
      
      this.sessions.set(sessionId, session);
      this.activeSession = session;
      
      // Start the session
      await session.start();
      
      this.eventEmitter.fire({ type: 'sessionStarted', session });
      
      return true;
    } catch (error) {
      this.eventEmitter.fire({ type: 'sessionStartError', error });
      TraeAPI.window.showErrorMessage(`Failed to start debugging: ${error}`);
      return false;
    }
  }

  // Stop debug session
  async stopDebugging(session?: TraeAPI.DebugSession): Promise<void> {
    const targetSession = session || this.activeSession;
    
    if (!targetSession) {
      return;
    }

    try {
      await targetSession.stop();
      
      this.sessions.delete(targetSession.id);
      
      if (this.activeSession === targetSession) {
        this.activeSession = undefined;
      }
      
      this.eventEmitter.fire({ type: 'sessionStopped', session: targetSession });
    } catch (error) {
      this.eventEmitter.fire({ type: 'sessionStopError', session: targetSession, error });
      throw error;
    }
  }

  // Breakpoint management
  async addBreakpoints(uri: TraeAPI.Uri, breakpoints: TraeAPI.SourceBreakpoint[]): Promise<TraeAPI.Breakpoint[]> {
    const uriString = uri.toString();
    const existingBreakpoints = this.breakpoints.get(uriString) || [];
    
    const newBreakpoints: TraeAPI.Breakpoint[] = [];
    
    for (const bp of breakpoints) {
      const breakpoint: TraeAPI.Breakpoint = {
        id: this.generateBreakpointId(),
        enabled: true,
        condition: bp.condition,
        hitCondition: bp.hitCondition,
        logMessage: bp.logMessage,
        location: new TraeAPI.Location(uri, new TraeAPI.Position(bp.line - 1, 0))
      };
      
      newBreakpoints.push(breakpoint);
    }
    
    const allBreakpoints = [...existingBreakpoints, ...newBreakpoints];
    this.breakpoints.set(uriString, allBreakpoints);
    
    // Update active session breakpoints
    if (this.activeSession) {
      await this.updateSessionBreakpoints(this.activeSession, uri, allBreakpoints);
    }
    
    this.eventEmitter.fire({ type: 'breakpointsAdded', uri, breakpoints: newBreakpoints });
    
    return newBreakpoints;
  }

  async removeBreakpoints(uri: TraeAPI.Uri, breakpoints: TraeAPI.Breakpoint[]): Promise<void> {
    const uriString = uri.toString();
    const existingBreakpoints = this.breakpoints.get(uriString) || [];
    
    const breakpointIds = new Set(breakpoints.map(bp => bp.id));
    const remainingBreakpoints = existingBreakpoints.filter(bp => !breakpointIds.has(bp.id));
    
    this.breakpoints.set(uriString, remainingBreakpoints);
    
    // Update active session breakpoints
    if (this.activeSession) {
      await this.updateSessionBreakpoints(this.activeSession, uri, remainingBreakpoints);
    }
    
    this.eventEmitter.fire({ type: 'breakpointsRemoved', uri, breakpoints });
  }

  async toggleBreakpoint(uri: TraeAPI.Uri, line: number): Promise<void> {
    const uriString = uri.toString();
    const breakpoints = this.breakpoints.get(uriString) || [];
    
    const existingIndex = breakpoints.findIndex(bp => 
      bp.location.range.start.line === line - 1
    );
    
    if (existingIndex >= 0) {
      // Remove existing breakpoint
      const removed = breakpoints.splice(existingIndex, 1);
      await this.removeBreakpoints(uri, removed);
    } else {
      // Add new breakpoint
      await this.addBreakpoints(uri, [{ line }]);
    }
  }

  getBreakpoints(uri?: TraeAPI.Uri): TraeAPI.Breakpoint[] {
    if (uri) {
      return this.breakpoints.get(uri.toString()) || [];
    }
    
    const allBreakpoints: TraeAPI.Breakpoint[] = [];
    for (const breakpoints of this.breakpoints.values()) {
      allBreakpoints.push(...breakpoints);
    }
    
    return allBreakpoints;
  }

  // Debug execution control
  async continue(session?: TraeAPI.DebugSession): Promise<void> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    await targetSession.customRequest('continue', {
      threadId: targetSession.threadId
    });
    
    this.eventEmitter.fire({ type: 'continued', session: targetSession });
  }

  async stepOver(session?: TraeAPI.DebugSession): Promise<void> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    await targetSession.customRequest('next', {
      threadId: targetSession.threadId
    });
    
    this.eventEmitter.fire({ type: 'stepOver', session: targetSession });
  }

  async stepInto(session?: TraeAPI.DebugSession): Promise<void> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    await targetSession.customRequest('stepIn', {
      threadId: targetSession.threadId
    });
    
    this.eventEmitter.fire({ type: 'stepInto', session: targetSession });
  }

  async stepOut(session?: TraeAPI.DebugSession): Promise<void> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    await targetSession.customRequest('stepOut', {
      threadId: targetSession.threadId
    });
    
    this.eventEmitter.fire({ type: 'stepOut', session: targetSession });
  }

  async pause(session?: TraeAPI.DebugSession): Promise<void> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    await targetSession.customRequest('pause', {
      threadId: targetSession.threadId
    });
    
    this.eventEmitter.fire({ type: 'paused', session: targetSession });
  }

  // Variable inspection
  async getVariables(session?: TraeAPI.DebugSession): Promise<DebugVariable[]> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    try {
      const stackTrace = await targetSession.customRequest('stackTrace', {
        threadId: targetSession.threadId
      });
      
      if (!stackTrace.stackFrames || stackTrace.stackFrames.length === 0) {
        return [];
      }
      
      const topFrame = stackTrace.stackFrames[0];
      const scopes = await targetSession.customRequest('scopes', {
        frameId: topFrame.id
      });
      
      const variables: DebugVariable[] = [];
      
      for (const scope of scopes.scopes) {
        const scopeVariables = await targetSession.customRequest('variables', {
          variablesReference: scope.variablesReference
        });
        
        for (const variable of scopeVariables.variables) {
          variables.push({
            name: variable.name,
            value: variable.value,
            type: variable.type,
            scope: scope.name,
            variablesReference: variable.variablesReference
          });
        }
      }
      
      return variables;
    } catch (error) {
      this.eventEmitter.fire({ type: 'variableInspectionError', session: targetSession, error });
      throw error;
    }
  }

  async evaluateExpression(
    expression: string,
    context: 'watch' | 'repl' | 'hover' = 'repl',
    session?: TraeAPI.DebugSession
  ): Promise<DebugEvaluationResult> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    try {
      const result = await targetSession.customRequest('evaluate', {
        expression,
        context,
        frameId: targetSession.frameId
      });
      
      return {
        result: result.result,
        type: result.type,
        variablesReference: result.variablesReference,
        namedVariables: result.namedVariables,
        indexedVariables: result.indexedVariables
      };
    } catch (error) {
      this.eventEmitter.fire({ type: 'evaluationError', expression, error });
      throw error;
    }
  }

  // Call stack
  async getCallStack(session?: TraeAPI.DebugSession): Promise<TraeAPI.StackFrame[]> {
    const targetSession = session || this.activeSession;
    if (!targetSession) {
      throw new Error('No active debug session');
    }

    try {
      const stackTrace = await targetSession.customRequest('stackTrace', {
        threadId: targetSession.threadId
      });
      
      return stackTrace.stackFrames || [];
    } catch (error) {
      this.eventEmitter.fire({ type: 'callStackError', session: targetSession, error });
      throw error;
    }
  }

  // Debug adapter management
  registerDebugAdapterDescriptorFactory(
    debugType: string,
    factory: TraeAPI.DebugAdapterDescriptorFactory
  ): TraeAPI.Disposable {
    this.debugAdapters.set(debugType, { factory, type: debugType });
    
    this.eventEmitter.fire({ type: 'adapterRegistered', debugType });
    
    return {
      dispose: () => {
        this.debugAdapters.delete(debugType);
        this.eventEmitter.fire({ type: 'adapterUnregistered', debugType });
      }
    };
  }

  // Debug configuration
  private async resolveDebugConfiguration(
    folder: TraeAPI.WorkspaceFolder | undefined,
    config: TraeAPI.DebugConfiguration
  ): Promise<TraeAPI.DebugConfiguration | undefined> {
    // Apply variable substitution
    const resolvedConfig = this.substituteVariables(config, folder);
    
    // Validate configuration
    if (!resolvedConfig.type) {
      throw new Error('Debug configuration must specify a type');
    }
    
    if (!resolvedConfig.request) {
      throw new Error('Debug configuration must specify a request type');
    }
    
    return resolvedConfig;
  }

  private substituteVariables(
    config: TraeAPI.DebugConfiguration,
    folder: TraeAPI.WorkspaceFolder | undefined
  ): TraeAPI.DebugConfiguration {
    const configString = JSON.stringify(config);
    const workspaceRoot = folder?.uri.fsPath || TraeAPI.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
    
    const substituted = configString
      .replace(/\$\{workspaceFolder\}/g, workspaceRoot)
      .replace(/\$\{workspaceRoot\}/g, workspaceRoot)
      .replace(/\$\{file\}/g, TraeAPI.window.activeTextEditor?.document.uri.fsPath || '')
      .replace(/\$\{fileBasename\}/g, TraeAPI.window.activeTextEditor?.document.fileName || '')
      .replace(/\$\{fileDirname\}/g, TraeAPI.window.activeTextEditor?.document.uri.fsPath.split('/').slice(0, -1).join('/') || '');
    
    return JSON.parse(substituted);
  }

  private async createDebugSession(
    id: string,
    config: TraeAPI.DebugConfiguration,
    folder: TraeAPI.WorkspaceFolder | undefined
  ): Promise<TraeAPI.DebugSession> {
    const adapter = this.debugAdapters.get(config.type);
    if (!adapter) {
      throw new Error(`No debug adapter found for type: ${config.type}`);
    }

    // Create debug session
    const session = new DebugSessionImpl(id, config, folder, adapter);
    
    // Setup session event handlers
    session.onDidTerminate(() => {
      this.sessions.delete(id);
      if (this.activeSession === session) {
        this.activeSession = undefined;
      }
      this.eventEmitter.fire({ type: 'sessionTerminated', session });
    });
    
    return session;
  }

  private async updateSessionBreakpoints(
    session: TraeAPI.DebugSession,
    uri: TraeAPI.Uri,
    breakpoints: TraeAPI.Breakpoint[]
  ): Promise<void> {
    try {
      await session.customRequest('setBreakpoints', {
        source: { path: uri.fsPath },
        breakpoints: breakpoints.map(bp => ({
          line: bp.location.range.start.line + 1,
          condition: bp.condition,
          hitCondition: bp.hitCondition,
          logMessage: bp.logMessage
        }))
      });
    } catch (error) {
      console.warn('Failed to update session breakpoints:', error);
    }
  }

  private setupBuiltinAdapters(): void {
    // Register built-in debug adapters
    this.registerDebugAdapterDescriptorFactory('node', {
      createDebugAdapterDescriptor: (session, executable) => {
        return new TraeAPI.DebugAdapterExecutable('node', [
          require.resolve('node-debug-adapter')
        ]);
      }
    });
  }

  private setupEventHandlers(): void {
    // Setup debug event handlers
  }

  private generateSessionId(): string {
    return `debug-session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  private generateBreakpointId(): string {
    return `breakpoint-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  // Getters
  get activeSessions(): TraeAPI.DebugSession[] {
    return Array.from(this.sessions.values());
  }

  get activeDebugSession(): TraeAPI.DebugSession | undefined {
    return this.activeSession;
  }

  // Event handling
  onDidStartDebugSession(listener: (session: TraeAPI.DebugSession) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(event => {
      if (event.type === 'sessionStarted') {
        listener(event.session!);
      }
    });
  }

  onDidTerminateDebugSession(listener: (session: TraeAPI.DebugSession) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(event => {
      if (event.type === 'sessionTerminated') {
        listener(event.session!);
      }
    });
  }

  onDidChangeBreakpoints(listener: (event: BreakpointEvent) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(event => {
      if (event.type === 'breakpointsAdded' || event.type === 'breakpointsRemoved') {
        listener(event as BreakpointEvent);
      }
    });
  }

  // Dispose
  dispose(): void {
    // Stop all sessions
    for (const session of this.sessions.values()) {
      session.stop().catch(console.error);
    }
    this.sessions.clear();
    
    // Clear breakpoints
    this.breakpoints.clear();
    
    // Dispose adapters
    this.debugAdapters.clear();
    
    // Dispose other resources
    this.disposables.forEach(d => d.dispose());
    this.disposables = [];
    
    this.eventEmitter.dispose();
    this.debugConsole.dispose();
  }
}

// Debug console implementation
class DebugConsole {
  private outputChannel: TraeAPI.OutputChannel;
  private history: string[] = [];
  private maxHistorySize = 1000;

  constructor() {
    this.outputChannel = TraeAPI.window.createOutputChannel('Debug Console');
  }

  append(text: string): void {
    this.outputChannel.append(text);
    this.addToHistory(text);
  }

  appendLine(text: string): void {
    this.outputChannel.appendLine(text);
    this.addToHistory(text + '\n');
  }

  clear(): void {
    this.outputChannel.clear();
    this.history = [];
  }

  show(): void {
    this.outputChannel.show();
  }

  private addToHistory(text: string): void {
    this.history.push(text);
    if (this.history.length > this.maxHistorySize) {
      this.history.shift();
    }
  }

  getHistory(): string[] {
    return [...this.history];
  }

  dispose(): void {
    this.outputChannel.dispose();
    this.history = [];
  }
}

// Debug session implementation
class DebugSessionImpl implements TraeAPI.DebugSession {
  private _onDidTerminate = new TraeAPI.EventEmitter<void>();
  private _threadId = 1;
  private _frameId = 0;

  constructor(
    public readonly id: string,
    public readonly configuration: TraeAPI.DebugConfiguration,
    public readonly workspaceFolder: TraeAPI.WorkspaceFolder | undefined,
    private adapter: DebugAdapterDescriptor
  ) {}

  get type(): string {
    return this.configuration.type;
  }

  get name(): string {
    return this.configuration.name;
  }

  get threadId(): number {
    return this._threadId;
  }

  get frameId(): number {
    return this._frameId;
  }

  get onDidTerminate(): TraeAPI.Event<void> {
    return this._onDidTerminate.event;
  }

  async start(): Promise<void> {
    // Start debug adapter and initialize session
    console.log(`Starting debug session: ${this.name}`);
  }

  async stop(): Promise<void> {
    // Stop debug session
    console.log(`Stopping debug session: ${this.name}`);
    this._onDidTerminate.fire();
  }

  async customRequest(command: string, args?: any): Promise<any> {
    // Send custom request to debug adapter
    console.log(`Debug request: ${command}`, args);
    return {};
  }
}

// Initialize debug session manager
const debugSessionManager = new DebugSessionManager();

Interface Definitions

typescript
// Debug event types
interface DebugEvent {
  type: 'sessionStarted' | 'sessionStopped' | 'sessionTerminated' | 'sessionStartError' | 'sessionStopError' |
        'breakpointsAdded' | 'breakpointsRemoved' | 'continued' | 'stepOver' | 'stepInto' | 'stepOut' | 'paused' |
        'variableInspectionError' | 'evaluationError' | 'callStackError' | 'adapterRegistered' | 'adapterUnregistered';
  session?: TraeAPI.DebugSession;
  uri?: TraeAPI.Uri;
  breakpoints?: TraeAPI.Breakpoint[];
  expression?: string;
  debugType?: string;
  error?: any;
}

// Breakpoint event
interface BreakpointEvent {
  type: 'breakpointsAdded' | 'breakpointsRemoved';
  uri: TraeAPI.Uri;
  breakpoints: TraeAPI.Breakpoint[];
}

// Debug variable
interface DebugVariable {
  name: string;
  value: string;
  type?: string;
  scope: string;
  variablesReference: number;
}

// Debug evaluation result
interface DebugEvaluationResult {
  result: string;
  type?: string;
  variablesReference: number;
  namedVariables?: number;
  indexedVariables?: number;
}

// Debug adapter descriptor
interface DebugAdapterDescriptor {
  factory: TraeAPI.DebugAdapterDescriptorFactory;
  type: string;
}

API Reference

DebugSessionManager

Methods

  • startDebugging(folder?, nameOrConfiguration, parentSessionOrOptions?): Promise<boolean> - Start debug session
  • stopDebugging(session?): Promise<void> - Stop debug session
  • addBreakpoints(uri, breakpoints): Promise<Breakpoint[]> - Add breakpoints
  • removeBreakpoints(uri, breakpoints): Promise<void> - Remove breakpoints
  • toggleBreakpoint(uri, line): Promise<void> - Toggle breakpoint
  • getBreakpoints(uri?): Breakpoint[] - Get breakpoints
  • continue(session?): Promise<void> - Continue execution
  • stepOver(session?): Promise<void> - Step over
  • stepInto(session?): Promise<void> - Step into
  • stepOut(session?): Promise<void> - Step out
  • pause(session?): Promise<void> - Pause execution
  • getVariables(session?): Promise<DebugVariable[]> - Get variables
  • evaluateExpression(expression, context?, session?): Promise<DebugEvaluationResult> - Evaluate expression
  • getCallStack(session?): Promise<StackFrame[]> - Get call stack
  • registerDebugAdapterDescriptorFactory(debugType, factory): Disposable - Register debug adapter

Properties

  • activeSessions: DebugSession[] - Active debug sessions
  • activeDebugSession: DebugSession | undefined - Current active session

Events

  • onDidStartDebugSession(listener): Disposable - Debug session started
  • onDidTerminateDebugSession(listener): Disposable - Debug session terminated
  • onDidChangeBreakpoints(listener): Disposable - Breakpoints changed

Best Practices

  1. Configuration: Use proper debug configurations for different project types
  2. Breakpoints: Set meaningful breakpoints and use conditional breakpoints
  3. Variable Inspection: Use watch expressions for monitoring specific variables
  4. Error Handling: Handle debug adapter errors gracefully
  5. Performance: Avoid excessive variable inspection in loops
  6. Security: Validate debug configurations and expressions
  7. Logging: Use debug console for structured logging
  8. Session Management: Properly clean up debug sessions
  9. Adapter Registration: Register debug adapters for custom languages
  10. User Experience: Provide clear feedback during debug operations

Your Ultimate AI-Powered IDE Learning Guide