Path: blob/main/src/vs/workbench/api/browser/mainThreadChatDebug.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';6import { URI } from '../../../base/common/uri.js';7import { VSBuffer } from '../../../base/common/buffer.js';8import { ChatDebugHookResult, ChatDebugLogLevel, IChatDebugEvent, IChatDebugResolvedEventContent, IChatDebugService } from '../../contrib/chat/common/chatDebugService.js';9import { IChatService } from '../../contrib/chat/common/chatService/chatService.js';10import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';11import { ExtHostChatDebugShape, ExtHostContext, IChatDebugEventDto, IChatDebugResolvedEventContentDto, MainContext, MainThreadChatDebugShape } from '../common/extHost.protocol.js';12import { Proxied } from '../../services/extensions/common/proxyIdentifier.js';1314@extHostNamedCustomer(MainContext.MainThreadChatDebug)15export class MainThreadChatDebug extends Disposable implements MainThreadChatDebugShape {16private readonly _proxy: Proxied<ExtHostChatDebugShape>;17private readonly _providerDisposables = new Map<number, DisposableStore>();18private readonly _activeSessionResources = new Map<number, URI>();19private readonly _coreEventForwarder = this._register(new MutableDisposable());2021constructor(22extHostContext: IExtHostContext,23@IChatDebugService private readonly _chatDebugService: IChatDebugService,24@IChatService private readonly _chatService: IChatService,25) {26super();27this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatDebug);28}2930$subscribeToCoreDebugEvents(): void {31this._coreEventForwarder.value = this._chatDebugService.onDidAddEvent(event => {32if (this._chatDebugService.isCoreEvent(event)) {33this._proxy.$onCoreDebugEvent(this._serializeEvent(event));34}35});36}3738$unsubscribeFromCoreDebugEvents(): void {39this._coreEventForwarder.clear();40}4142$registerChatDebugLogProvider(handle: number): void {43const disposables = new DisposableStore();44this._providerDisposables.set(handle, disposables);4546disposables.add(this._chatDebugService.registerProvider({47provideChatDebugLog: async (sessionResource, token) => {48this._activeSessionResources.set(handle, sessionResource);49const dtos = await this._proxy.$provideChatDebugLog(handle, sessionResource, token);50return dtos?.map(dto => this._reviveEvent(dto, sessionResource));51},52resolveChatDebugLogEvent: async (eventId, token) => {53const dto = await this._proxy.$resolveChatDebugLogEvent(handle, eventId, token);54return dto ? this._reviveResolvedContent(dto) : undefined;55},56provideChatDebugLogExport: async (sessionResource, token) => {57// Gather core events and session title to pass to the extension.58const coreEventDtos = this._chatDebugService.getEvents(sessionResource)59.filter(e => this._chatDebugService.isCoreEvent(e))60.map(e => this._serializeEvent(e));61const sessionTitle = this._chatService.getSessionTitle(sessionResource);62const result = await this._proxy.$exportChatDebugLog(handle, sessionResource, coreEventDtos, sessionTitle, token);63return result?.buffer;64},65resolveChatDebugLogImport: async (data, token) => {66const result = await this._proxy.$importChatDebugLog(handle, VSBuffer.wrap(data), token);67if (!result) {68return undefined;69}70const uri = URI.revive(result.uri);71if (result.sessionTitle) {72this._chatDebugService.setImportedSessionTitle(uri, result.sessionTitle);73}74return uri;75}76}));7778// Register a lazy fetcher so historical sessions are loaded from the79// extension only when the debug panel home page first needs them.80this._chatDebugService.registerAvailableSessionsFetcher(async (token) => {81const entries = await this._proxy.$getAvailableDebugSessionResources(handle, token);82return entries.map(e => ({ uri: URI.revive(e.uri), title: e.title }));83});84}8586$unregisterChatDebugLogProvider(handle: number): void {87const disposables = this._providerDisposables.get(handle);88disposables?.dispose();89this._providerDisposables.delete(handle);90this._activeSessionResources.delete(handle);91}9293$acceptChatDebugEvent(handle: number, dto: IChatDebugEventDto): void {94const sessionResource = (dto.sessionResource ? URI.revive(dto.sessionResource) : undefined)95?? this._activeSessionResources.get(handle)96?? this._chatDebugService.activeSessionResource;97if (!sessionResource) {98return;99}100const revived = this._reviveEvent(dto, sessionResource);101this._chatDebugService.addProviderEvent(revived);102}103104private _serializeEvent(event: IChatDebugEvent): IChatDebugEventDto {105const base = {106id: event.id,107sessionResource: event.sessionResource,108created: event.created.getTime(),109parentEventId: event.parentEventId,110};111112switch (event.kind) {113case 'toolCall':114return { ...base, kind: 'toolCall', toolName: event.toolName, toolCallId: event.toolCallId, input: event.input, output: event.output, result: event.result, durationInMillis: event.durationInMillis };115case 'modelTurn':116return { ...base, kind: 'modelTurn', model: event.model, requestName: event.requestName, inputTokens: event.inputTokens, outputTokens: event.outputTokens, cachedTokens: event.cachedTokens, totalTokens: event.totalTokens, durationInMillis: event.durationInMillis };117case 'generic':118return { ...base, kind: 'generic', name: event.name, details: event.details, level: event.level, category: event.category };119case 'subagentInvocation':120return { ...base, kind: 'subagentInvocation', agentName: event.agentName, description: event.description, status: event.status, durationInMillis: event.durationInMillis, toolCallCount: event.toolCallCount, modelTurnCount: event.modelTurnCount };121case 'userMessage':122return { ...base, kind: 'userMessage', message: event.message, sections: event.sections.map(s => ({ name: s.name, content: s.content })) };123case 'agentResponse':124return { ...base, kind: 'agentResponse', message: event.message, sections: event.sections.map(s => ({ name: s.name, content: s.content })) };125}126}127128private _reviveEvent(dto: IChatDebugEventDto, sessionResource: URI): IChatDebugEvent {129const base = {130id: dto.id,131sessionResource,132created: new Date(dto.created),133parentEventId: dto.parentEventId,134};135136switch (dto.kind) {137case 'toolCall':138return {139...base,140kind: 'toolCall',141toolName: dto.toolName,142toolCallId: dto.toolCallId,143input: dto.input,144output: dto.output,145result: dto.result,146durationInMillis: dto.durationInMillis,147};148case 'modelTurn':149return {150...base,151kind: 'modelTurn',152model: dto.model,153requestName: dto.requestName,154inputTokens: dto.inputTokens,155outputTokens: dto.outputTokens,156cachedTokens: dto.cachedTokens,157totalTokens: dto.totalTokens,158durationInMillis: dto.durationInMillis,159};160case 'generic':161return {162...base,163kind: 'generic',164name: dto.name,165details: dto.details,166level: dto.level as ChatDebugLogLevel,167category: dto.category,168};169case 'subagentInvocation':170return {171...base,172kind: 'subagentInvocation',173agentName: dto.agentName,174description: dto.description,175status: dto.status,176durationInMillis: dto.durationInMillis,177toolCallCount: dto.toolCallCount,178modelTurnCount: dto.modelTurnCount,179};180case 'userMessage':181return {182...base,183kind: 'userMessage',184message: dto.message,185sections: dto.sections,186};187case 'agentResponse':188return {189...base,190kind: 'agentResponse',191message: dto.message,192sections: dto.sections,193};194}195}196197private _reviveResolvedContent(dto: IChatDebugResolvedEventContentDto): IChatDebugResolvedEventContent {198switch (dto.kind) {199case 'text':200return { kind: 'text', value: dto.value };201case 'message':202return {203kind: 'message',204type: dto.type,205message: dto.message,206sections: dto.sections,207};208case 'toolCall':209return {210kind: 'toolCall',211toolName: dto.toolName,212result: dto.result,213durationInMillis: dto.durationInMillis,214input: dto.input,215output: dto.output,216};217case 'modelTurn':218return {219kind: 'modelTurn',220requestName: dto.requestName,221model: dto.model,222status: dto.status,223durationInMillis: dto.durationInMillis,224inputTokens: dto.inputTokens,225outputTokens: dto.outputTokens,226cachedTokens: dto.cachedTokens,227totalTokens: dto.totalTokens,228errorMessage: dto.errorMessage,229sections: dto.sections,230};231case 'hook':232return {233kind: 'hook',234hookType: dto.hookType,235command: dto.command,236result: dto.result === 'success' ? ChatDebugHookResult.Success237: dto.result === 'error' ? ChatDebugHookResult.Error238: dto.result === 'nonBlockingError' ? ChatDebugHookResult.NonBlockingError239: undefined,240durationInMillis: dto.durationInMillis,241input: dto.input,242output: dto.output,243exitCode: dto.exitCode,244errorMessage: dto.errorMessage,245};246}247}248}249250251