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 sessionstopDebugging(session?): Promise<void>- Stop debug sessionaddBreakpoints(uri, breakpoints): Promise<Breakpoint[]>- Add breakpointsremoveBreakpoints(uri, breakpoints): Promise<void>- Remove breakpointstoggleBreakpoint(uri, line): Promise<void>- Toggle breakpointgetBreakpoints(uri?): Breakpoint[]- Get breakpointscontinue(session?): Promise<void>- Continue executionstepOver(session?): Promise<void>- Step overstepInto(session?): Promise<void>- Step intostepOut(session?): Promise<void>- Step outpause(session?): Promise<void>- Pause executiongetVariables(session?): Promise<DebugVariable[]>- Get variablesevaluateExpression(expression, context?, session?): Promise<DebugEvaluationResult>- Evaluate expressiongetCallStack(session?): Promise<StackFrame[]>- Get call stackregisterDebugAdapterDescriptorFactory(debugType, factory): Disposable- Register debug adapter
Properties
activeSessions: DebugSession[]- Active debug sessionsactiveDebugSession: DebugSession | undefined- Current active session
Events
onDidStartDebugSession(listener): Disposable- Debug session startedonDidTerminateDebugSession(listener): Disposable- Debug session terminatedonDidChangeBreakpoints(listener): Disposable- Breakpoints changed
Best Practices
- Configuration: Use proper debug configurations for different project types
- Breakpoints: Set meaningful breakpoints and use conditional breakpoints
- Variable Inspection: Use watch expressions for monitoring specific variables
- Error Handling: Handle debug adapter errors gracefully
- Performance: Avoid excessive variable inspection in loops
- Security: Validate debug configurations and expressions
- Logging: Use debug console for structured logging
- Session Management: Properly clean up debug sessions
- Adapter Registration: Register debug adapters for custom languages
- User Experience: Provide clear feedback during debug operations
Related APIs
- Editor API - Text editor debugging integration
- Workspace API - Workspace debug configurations
- Terminal API - Debug terminal integration
- Tasks API - Pre-debug task execution
- Extensions API - Debug extension development
- Language Services API - Language-specific debugging
- Testing API - Test debugging integration
- Settings API - Debug settings management