Debugging API
The Debugging API provides comprehensive debugging capabilities for various programming languages and runtime environments.
Overview
The Debugging API enables you to:
- Create and manage debug configurations
- Start, stop, and control debug sessions
- Set and manage breakpoints
- Inspect variables and call stacks
- Evaluate expressions in debug context
- Handle debug events and state changes
- Integrate with language-specific debuggers
- Provide custom debug adapters
Basic Usage
Debug Configuration
typescript
import { TraeAPI } from '@trae/api';
// Define debug configuration
const debugConfig: TraeAPI.DebugConfiguration = {
type: 'node',
request: 'launch',
name: 'Launch Program',
program: '${workspaceFolder}/src/index.js',
args: ['--verbose'],
env: {
NODE_ENV: 'development'
},
console: 'integratedTerminal',
sourceMaps: true,
outFiles: ['${workspaceFolder}/dist/**/*.js']
};
// Start debugging session
async function startDebugging() {
const success = await TraeAPI.debug.startDebugging(
TraeAPI.workspace.workspaceFolders?.[0],
debugConfig
);
if (success) {
console.log('Debug session started successfully');
} else {
console.error('Failed to start debug session');
}
}
// Stop active debug session
async function stopDebugging() {
const activeSession = TraeAPI.debug.activeDebugSession;
if (activeSession) {
await activeSession.customRequest('disconnect');
console.log('Debug session stopped');
}
}Breakpoint Management
typescript
class BreakpointManager {
private breakpoints: Map<string, TraeAPI.Breakpoint[]> = new Map();
constructor() {
this.setupBreakpointListeners();
}
private setupBreakpointListeners() {
// Listen for breakpoint changes
TraeAPI.debug.onDidChangeBreakpoints(event => {
console.log('Breakpoints changed:', {
added: event.added,
removed: event.removed,
changed: event.changed
});
this.updateBreakpointCache(event);
});
}
private updateBreakpointCache(event: TraeAPI.BreakpointsChangeEvent) {
// Update added breakpoints
for (const breakpoint of event.added) {
if (breakpoint instanceof TraeAPI.SourceBreakpoint) {
const uri = breakpoint.location.uri.toString();
const existing = this.breakpoints.get(uri) || [];
existing.push(breakpoint);
this.breakpoints.set(uri, existing);
}
}
// Remove deleted breakpoints
for (const breakpoint of event.removed) {
if (breakpoint instanceof TraeAPI.SourceBreakpoint) {
const uri = breakpoint.location.uri.toString();
const existing = this.breakpoints.get(uri) || [];
const filtered = existing.filter(bp => bp.id !== breakpoint.id);
this.breakpoints.set(uri, filtered);
}
}
// Update changed breakpoints
for (const breakpoint of event.changed) {
if (breakpoint instanceof TraeAPI.SourceBreakpoint) {
const uri = breakpoint.location.uri.toString();
const existing = this.breakpoints.get(uri) || [];
const index = existing.findIndex(bp => bp.id === breakpoint.id);
if (index !== -1) {
existing[index] = breakpoint;
}
}
}
}
// Add breakpoint programmatically
addBreakpoint(uri: TraeAPI.Uri, line: number, condition?: string): TraeAPI.SourceBreakpoint {
const location = new TraeAPI.Location(uri, new TraeAPI.Position(line, 0));
const breakpoint = new TraeAPI.SourceBreakpoint(location, true, condition);
// Add to VS Code's breakpoint collection
const existing = TraeAPI.debug.breakpoints;
TraeAPI.debug.breakpoints = [...existing, breakpoint];
return breakpoint;
}
// Remove breakpoint
removeBreakpoint(breakpoint: TraeAPI.Breakpoint): void {
const existing = TraeAPI.debug.breakpoints;
TraeAPI.debug.breakpoints = existing.filter(bp => bp.id !== breakpoint.id);
}
// Toggle breakpoint
toggleBreakpoint(uri: TraeAPI.Uri, line: number): void {
const existing = this.findBreakpointAtLine(uri, line);
if (existing) {
this.removeBreakpoint(existing);
} else {
this.addBreakpoint(uri, line);
}
}
// Find breakpoint at specific line
findBreakpointAtLine(uri: TraeAPI.Uri, line: number): TraeAPI.SourceBreakpoint | undefined {
const uriString = uri.toString();
const breakpoints = this.breakpoints.get(uriString) || [];
return breakpoints.find(bp => {
if (bp instanceof TraeAPI.SourceBreakpoint) {
return bp.location.range.start.line === line;
}
return false;
}) as TraeAPI.SourceBreakpoint | undefined;
}
// Get all breakpoints for a file
getBreakpointsForFile(uri: TraeAPI.Uri): TraeAPI.Breakpoint[] {
return this.breakpoints.get(uri.toString()) || [];
}
// Set conditional breakpoint
setConditionalBreakpoint(uri: TraeAPI.Uri, line: number, condition: string): TraeAPI.SourceBreakpoint {
// Remove existing breakpoint at this line
const existing = this.findBreakpointAtLine(uri, line);
if (existing) {
this.removeBreakpoint(existing);
}
// Add new conditional breakpoint
return this.addBreakpoint(uri, line, condition);
}
// Set logpoint (breakpoint that logs instead of stopping)
setLogpoint(uri: TraeAPI.Uri, line: number, logMessage: string): TraeAPI.SourceBreakpoint {
const location = new TraeAPI.Location(uri, new TraeAPI.Position(line, 0));
const breakpoint = new TraeAPI.SourceBreakpoint(location, true);
breakpoint.logMessage = logMessage;
const existing = TraeAPI.debug.breakpoints;
TraeAPI.debug.breakpoints = [...existing, breakpoint];
return breakpoint;
}
}
// Initialize breakpoint manager
const breakpointManager = new BreakpointManager();Debug Session Management
typescript
class DebugSessionManager {
private activeSessions: Map<string, TraeAPI.DebugSession> = new Map();
private sessionListeners: Map<string, TraeAPI.Disposable[]> = new Map();
constructor() {
this.setupSessionListeners();
}
private setupSessionListeners() {
// Listen for session start
TraeAPI.debug.onDidStartDebugSession(session => {
console.log('Debug session started:', session.name);
this.activeSessions.set(session.id, session);
this.setupSessionEventListeners(session);
});
// Listen for session termination
TraeAPI.debug.onDidTerminateDebugSession(session => {
console.log('Debug session terminated:', session.name);
this.cleanupSession(session.id);
});
// Listen for active session changes
TraeAPI.debug.onDidChangeActiveDebugSession(session => {
if (session) {
console.log('Active debug session changed to:', session.name);
} else {
console.log('No active debug session');
}
});
}
private setupSessionEventListeners(session: TraeAPI.DebugSession) {
const listeners: TraeAPI.Disposable[] = [];
// Listen for custom events from debug adapter
const customEventListener = session.onDidReceiveDebugSessionCustomEvent(event => {
this.handleCustomEvent(session, event);
});
listeners.push(customEventListener);
this.sessionListeners.set(session.id, listeners);
}
private handleCustomEvent(session: TraeAPI.DebugSession, event: TraeAPI.DebugSessionCustomEvent) {
console.log('Custom debug event:', {
session: session.name,
event: event.event,
body: event.body
});
switch (event.event) {
case 'output':
this.handleOutputEvent(session, event.body);
break;
case 'stopped':
this.handleStoppedEvent(session, event.body);
break;
case 'continued':
this.handleContinuedEvent(session, event.body);
break;
case 'thread':
this.handleThreadEvent(session, event.body);
break;
case 'breakpoint':
this.handleBreakpointEvent(session, event.body);
break;
default:
console.log('Unknown debug event:', event.event);
}
}
private handleOutputEvent(session: TraeAPI.DebugSession, body: any) {
if (body.category === 'console') {
console.log(`[${session.name}] Console:`, body.output);
} else if (body.category === 'stderr') {
console.error(`[${session.name}] Error:`, body.output);
} else {
console.log(`[${session.name}] Output:`, body.output);
}
}
private handleStoppedEvent(session: TraeAPI.DebugSession, body: any) {
console.log(`[${session.name}] Stopped:`, {
reason: body.reason,
threadId: body.threadId,
text: body.text
});
// Automatically show call stack when stopped
this.showCallStack(session, body.threadId);
}
private handleContinuedEvent(session: TraeAPI.DebugSession, body: any) {
console.log(`[${session.name}] Continued:`, {
threadId: body.threadId,
allThreadsContinued: body.allThreadsContinued
});
}
private handleThreadEvent(session: TraeAPI.DebugSession, body: any) {
console.log(`[${session.name}] Thread event:`, {
reason: body.reason,
threadId: body.threadId
});
}
private handleBreakpointEvent(session: TraeAPI.DebugSession, body: any) {
console.log(`[${session.name}] Breakpoint event:`, {
reason: body.reason,
breakpoint: body.breakpoint
});
}
private async showCallStack(session: TraeAPI.DebugSession, threadId: number) {
try {
const stackTrace = await session.customRequest('stackTrace', {
threadId: threadId,
startFrame: 0,
levels: 20
});
console.log(`[${session.name}] Call Stack:`);
for (const frame of stackTrace.stackFrames) {
console.log(` ${frame.name} (${frame.source?.name}:${frame.line})`);
}
} catch (error) {
console.error('Failed to get stack trace:', error);
}
}
private cleanupSession(sessionId: string) {
// Dispose of event listeners
const listeners = this.sessionListeners.get(sessionId);
if (listeners) {
listeners.forEach(listener => listener.dispose());
this.sessionListeners.delete(sessionId);
}
// Remove from active sessions
this.activeSessions.delete(sessionId);
}
// Get active debug session
getActiveSession(): TraeAPI.DebugSession | undefined {
return TraeAPI.debug.activeDebugSession;
}
// Get all active sessions
getAllSessions(): TraeAPI.DebugSession[] {
return Array.from(this.activeSessions.values());
}
// Send custom request to session
async sendCustomRequest(sessionId: string, command: string, args?: any): Promise<any> {
const session = this.activeSessions.get(sessionId);
if (!session) {
throw new Error(`No active session with id: ${sessionId}`);
}
return await session.customRequest(command, args);
}
// Control session execution
async continueExecution(sessionId: string, threadId?: number): Promise<void> {
await this.sendCustomRequest(sessionId, 'continue', { threadId });
}
async stepOver(sessionId: string, threadId: number): Promise<void> {
await this.sendCustomRequest(sessionId, 'next', { threadId });
}
async stepInto(sessionId: string, threadId: number): Promise<void> {
await this.sendCustomRequest(sessionId, 'stepIn', { threadId });
}
async stepOut(sessionId: string, threadId: number): Promise<void> {
await this.sendCustomRequest(sessionId, 'stepOut', { threadId });
}
async pause(sessionId: string, threadId: number): Promise<void> {
await this.sendCustomRequest(sessionId, 'pause', { threadId });
}
async restart(sessionId: string): Promise<void> {
await this.sendCustomRequest(sessionId, 'restart');
}
async terminate(sessionId: string): Promise<void> {
await this.sendCustomRequest(sessionId, 'terminate');
}
}
// Initialize debug session manager
const debugSessionManager = new DebugSessionManager();Variable Inspection
typescript
class VariableInspector {
private variableCache: Map<string, any> = new Map();
constructor() {
this.setupVariableListeners();
}
private setupVariableListeners() {
TraeAPI.debug.onDidChangeActiveDebugSession(session => {
if (session) {
this.clearVariableCache();
}
});
}
private clearVariableCache() {
this.variableCache.clear();
}
// Get variables for a specific scope
async getVariables(sessionId: string, frameId: number, scopeId?: number): Promise<any[]> {
const session = debugSessionManager.getAllSessions().find(s => s.id === sessionId);
if (!session) {
throw new Error(`No session found with id: ${sessionId}`);
}
try {
// Get scopes for the frame
const scopes = await session.customRequest('scopes', { frameId });
if (scopeId !== undefined) {
// Get variables for specific scope
const scope = scopes.scopes.find((s: any) => s.variablesReference === scopeId);
if (scope) {
const variables = await session.customRequest('variables', {
variablesReference: scope.variablesReference
});
return variables.variables;
}
} else {
// Get variables for all scopes
const allVariables = [];
for (const scope of scopes.scopes) {
const variables = await session.customRequest('variables', {
variablesReference: scope.variablesReference
});
allVariables.push({
scope: scope.name,
variables: variables.variables
});
}
return allVariables;
}
} catch (error) {
console.error('Failed to get variables:', error);
return [];
}
return [];
}
// Get variable details
async getVariableDetails(sessionId: string, variableReference: number): Promise<any> {
const cacheKey = `${sessionId}-${variableReference}`;
if (this.variableCache.has(cacheKey)) {
return this.variableCache.get(cacheKey);
}
const session = debugSessionManager.getAllSessions().find(s => s.id === sessionId);
if (!session) {
throw new Error(`No session found with id: ${sessionId}`);
}
try {
const variables = await session.customRequest('variables', {
variablesReference
});
this.variableCache.set(cacheKey, variables.variables);
return variables.variables;
} catch (error) {
console.error('Failed to get variable details:', error);
return null;
}
}
// Evaluate expression
async evaluateExpression(
sessionId: string,
expression: string,
frameId?: number,
context: 'watch' | 'repl' | 'hover' = 'repl'
): Promise<any> {
const session = debugSessionManager.getAllSessions().find(s => s.id === sessionId);
if (!session) {
throw new Error(`No session found with id: ${sessionId}`);
}
try {
const result = await session.customRequest('evaluate', {
expression,
frameId,
context
});
return {
result: result.result,
type: result.type,
variablesReference: result.variablesReference,
namedVariables: result.namedVariables,
indexedVariables: result.indexedVariables
};
} catch (error) {
console.error('Failed to evaluate expression:', error);
throw error;
}
}
// Set variable value
async setVariableValue(
sessionId: string,
variablesReference: number,
name: string,
value: string
): Promise<any> {
const session = debugSessionManager.getAllSessions().find(s => s.id === sessionId);
if (!session) {
throw new Error(`No session found with id: ${sessionId}`);
}
try {
const result = await session.customRequest('setVariable', {
variablesReference,
name,
value
});
// Clear cache for this variable reference
const cacheKey = `${sessionId}-${variablesReference}`;
this.variableCache.delete(cacheKey);
return result;
} catch (error) {
console.error('Failed to set variable value:', error);
throw error;
}
}
// Watch expression
private watchExpressions: Map<string, string[]> = new Map();
addWatchExpression(sessionId: string, expression: string): void {
const existing = this.watchExpressions.get(sessionId) || [];
if (!existing.includes(expression)) {
existing.push(expression);
this.watchExpressions.set(sessionId, existing);
}
}
removeWatchExpression(sessionId: string, expression: string): void {
const existing = this.watchExpressions.get(sessionId) || [];
const filtered = existing.filter(expr => expr !== expression);
this.watchExpressions.set(sessionId, filtered);
}
async evaluateWatchExpressions(sessionId: string, frameId?: number): Promise<Array<{ expression: string; result: any }>> {
const expressions = this.watchExpressions.get(sessionId) || [];
const results = [];
for (const expression of expressions) {
try {
const result = await this.evaluateExpression(sessionId, expression, frameId, 'watch');
results.push({ expression, result });
} catch (error) {
results.push({
expression,
result: { error: error.message }
});
}
}
return results;
}
}
// Initialize variable inspector
const variableInspector = new VariableInspector();Custom Debug Adapter
typescript
import { DebugAdapterDescriptorFactory, DebugAdapterDescriptor, DebugSession, DebugAdapterExecutable } from 'vscode';
class CustomDebugAdapterFactory implements DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(
session: DebugSession,
executable: DebugAdapterExecutable | undefined
): ProviderResult<DebugAdapterDescriptor> {
// Return executable for external debug adapter
if (session.configuration.type === 'myCustomDebugger') {
return new DebugAdapterExecutable(
'node',
['/path/to/debug-adapter.js'],
{
env: {
...process.env,
DEBUG_MODE: 'true'
}
}
);
}
// Return inline debug adapter
return new DebugAdapterInlineImplementation(session);
}
}
class DebugAdapterInlineImplementation extends DebugAdapterDescriptor {
constructor(private session: DebugSession) {
super();
}
// Implement debug adapter protocol
async handleMessage(message: any): Promise<any> {
switch (message.command) {
case 'initialize':
return this.handleInitialize(message);
case 'launch':
return this.handleLaunch(message);
case 'setBreakpoints':
return this.handleSetBreakpoints(message);
case 'continue':
return this.handleContinue(message);
case 'next':
return this.handleNext(message);
case 'stepIn':
return this.handleStepIn(message);
case 'stepOut':
return this.handleStepOut(message);
case 'pause':
return this.handlePause(message);
case 'stackTrace':
return this.handleStackTrace(message);
case 'scopes':
return this.handleScopes(message);
case 'variables':
return this.handleVariables(message);
case 'evaluate':
return this.handleEvaluate(message);
case 'disconnect':
return this.handleDisconnect(message);
default:
throw new Error(`Unknown command: ${message.command}`);
}
}
private async handleInitialize(message: any): Promise<any> {
return {
seq: 0,
type: 'response',
request_seq: message.seq,
command: 'initialize',
success: true,
body: {
supportsConfigurationDoneRequest: true,
supportsEvaluateForHovers: true,
supportsStepBack: false,
supportsSetVariable: true,
supportsRestartFrame: false,
supportsGotoTargetsRequest: false,
supportsStepInTargetsRequest: false,
supportsCompletionsRequest: true,
supportsModulesRequest: false,
additionalModuleColumns: [],
supportedChecksumAlgorithms: [],
supportsRestartRequest: true,
supportsExceptionOptions: false,
supportsValueFormattingOptions: true,
supportsExceptionInfoRequest: false,
supportTerminateDebuggee: true,
supportsDelayedStackTraceLoading: false,
supportsLoadedSourcesRequest: false,
supportsLogPoints: true,
supportsTerminateThreadsRequest: false,
supportsSetExpression: false,
supportsTerminateRequest: true,
completionTriggerCharacters: ['.', '['],
supportsBreakpointLocationsRequest: false
}
};
}
private async handleLaunch(message: any): Promise<any> {
const config = message.arguments;
// Initialize debugger with configuration
console.log('Launching debug session with config:', config);
// Send initialized event
this.sendEvent({
seq: 0,
type: 'event',
event: 'initialized'
});
return {
seq: 0,
type: 'response',
request_seq: message.seq,
command: 'launch',
success: true
};
}
private async handleSetBreakpoints(message: any): Promise<any> {
const args = message.arguments;
const breakpoints = [];
for (const bp of args.breakpoints) {
breakpoints.push({
verified: true,
line: bp.line,
column: bp.column,
source: args.source
});
}
return {
seq: 0,
type: 'response',
request_seq: message.seq,
command: 'setBreakpoints',
success: true,
body: {
breakpoints
}
};
}
private async handleStackTrace(message: any): Promise<any> {
const args = message.arguments;
// Mock stack trace
const stackFrames = [
{
id: 1,
name: 'main',
source: {
name: 'index.js',
path: '/path/to/index.js'
},
line: 10,
column: 5
},
{
id: 2,
name: 'helper',
source: {
name: 'helper.js',
path: '/path/to/helper.js'
},
line: 25,
column: 12
}
];
return {
seq: 0,
type: 'response',
request_seq: message.seq,
command: 'stackTrace',
success: true,
body: {
stackFrames,
totalFrames: stackFrames.length
}
};
}
private sendEvent(event: any): void {
// Send event to debug session
console.log('Sending debug event:', event);
}
}
// Register debug adapter factory
TraeAPI.debug.registerDebugAdapterDescriptorFactory(
'myCustomDebugger',
new CustomDebugAdapterFactory()
);Debug Configuration Provider
typescript
class CustomDebugConfigurationProvider implements TraeAPI.DebugConfigurationProvider {
// Provide initial debug configurations
provideDebugConfigurations(
folder: TraeAPI.WorkspaceFolder | undefined,
token?: TraeAPI.CancellationToken
): TraeAPI.ProviderResult<TraeAPI.DebugConfiguration[]> {
return [
{
name: 'Launch Program',
type: 'myCustomDebugger',
request: 'launch',
program: '${workspaceFolder}/src/main.js',
args: [],
console: 'integratedTerminal',
internalConsoleOptions: 'neverOpen'
},
{
name: 'Attach to Process',
type: 'myCustomDebugger',
request: 'attach',
processId: '${command:pickProcess}',
console: 'integratedTerminal'
}
];
}
// Resolve debug configuration before starting
resolveDebugConfiguration(
folder: TraeAPI.WorkspaceFolder | undefined,
config: TraeAPI.DebugConfiguration,
token?: TraeAPI.CancellationToken
): TraeAPI.ProviderResult<TraeAPI.DebugConfiguration> {
// Validate and resolve configuration
if (!config.type && !config.request && !config.name) {
// Return null to show debug configuration picker
return null;
}
// Resolve variables in configuration
if (config.program) {
config.program = this.resolveVariables(config.program, folder);
}
if (config.cwd) {
config.cwd = this.resolveVariables(config.cwd, folder);
}
// Set default values
if (!config.console) {
config.console = 'integratedTerminal';
}
return config;
}
// Resolve configuration dynamically
resolveDebugConfigurationWithSubstitutedVariables(
folder: TraeAPI.WorkspaceFolder | undefined,
config: TraeAPI.DebugConfiguration,
token?: TraeAPI.CancellationToken
): TraeAPI.ProviderResult<TraeAPI.DebugConfiguration> {
// Final resolution after variable substitution
// Validate required fields
if (config.request === 'launch' && !config.program) {
TraeAPI.window.showErrorMessage('Missing "program" field in debug configuration');
return null;
}
if (config.request === 'attach' && !config.processId) {
TraeAPI.window.showErrorMessage('Missing "processId" field in debug configuration');
return null;
}
return config;
}
private resolveVariables(value: string, folder?: TraeAPI.WorkspaceFolder): string {
if (!value) return value;
// Resolve workspace folder
if (value.includes('${workspaceFolder}')) {
const workspacePath = folder?.uri.fsPath || TraeAPI.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
value = value.replace(/\$\{workspaceFolder\}/g, workspacePath);
}
// Resolve file paths
if (value.includes('${file}')) {
const activeFile = TraeAPI.window.activeTextEditor?.document.fileName || '';
value = value.replace(/\$\{file\}/g, activeFile);
}
// Resolve other variables as needed
return value;
}
}
// Register debug configuration provider
TraeAPI.debug.registerDebugConfigurationProvider(
'myCustomDebugger',
new CustomDebugConfigurationProvider()
);API Reference
Core Interfaces
typescript
interface DebugSession {
readonly id: string;
readonly type: string;
readonly name: string;
readonly workspaceFolder: WorkspaceFolder | undefined;
readonly configuration: DebugConfiguration;
customRequest(command: string, args?: any): Thenable<any>;
onDidReceiveDebugSessionCustomEvent: Event<DebugSessionCustomEvent>;
}
interface DebugConfiguration {
type: string;
name: string;
request: string;
[key: string]: any;
}
interface Breakpoint {
readonly id: string;
readonly enabled: boolean;
readonly condition?: string;
readonly hitCondition?: string;
readonly logMessage?: string;
}
interface SourceBreakpoint extends Breakpoint {
readonly location: Location;
}
interface DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(
session: DebugSession,
executable: DebugAdapterExecutable | undefined
): ProviderResult<DebugAdapterDescriptor>;
}
interface DebugConfigurationProvider {
provideDebugConfigurations?(
folder: WorkspaceFolder | undefined,
token?: CancellationToken
): ProviderResult<DebugConfiguration[]>;
resolveDebugConfiguration?(
folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
token?: CancellationToken
): ProviderResult<DebugConfiguration>;
resolveDebugConfigurationWithSubstitutedVariables?(
folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
token?: CancellationToken
): ProviderResult<DebugConfiguration>;
}Best Practices
- Configuration: Provide sensible defaults and clear error messages
- Performance: Cache debug information and use lazy loading
- User Experience: Show progress for long operations
- Error Handling: Handle debug adapter failures gracefully
- Security: Validate all debug configurations and inputs
- Compatibility: Support multiple debug adapter protocols
- Testing: Test with various debugging scenarios
- Documentation: Provide clear setup instructions
Related APIs
- Commands API - For debug commands
- Workspace API - For file operations
- Editor API - For editor integration
- UI API - For debug UI components