@codingame/monaco-vscode-extensions-service-override
Version:
VSCode public API plugged on the monaco editor - extensions service-override
893 lines (889 loc) • 42.9 kB
JavaScript
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6';
import { raceCancellationError } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
import { CancellationToken } from '@codingame/monaco-vscode-api/vscode/vs/base/common/cancellation';
import { Emitter } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event';
import { markdownStringEqual, MarkdownString } from '@codingame/monaco-vscode-api/vscode/vs/base/common/htmlContent';
import { Disposable, DisposableResourceMap, DisposableMap, DisposableStore } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
import { ResourceMap } from '@codingame/monaco-vscode-api/vscode/vs/base/common/map';
import { revive } from '@codingame/monaco-vscode-api/vscode/vs/base/common/marshalling';
import { equals } from '@codingame/monaco-vscode-api/vscode/vs/base/common/objects';
import '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/index';
import { isEqual } from '@codingame/monaco-vscode-api/vscode/vs/base/common/resources';
import { URI } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uri';
import { localize } from '@codingame/monaco-vscode-api/vscode/vs/nls';
import { IDialogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/dialogs/common/dialogs.service';
import { IInstantiationService } from '@codingame/monaco-vscode-api/vscode/vs/platform/instantiation/common/instantiation';
import { ILogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/log/common/log.service';
import { hasValidDiff } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel';
import { IAgentSessionsService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsService.service';
import { isIChatViewViewContext } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/chat';
import { IChatWidgetService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/chat.service';
import { getInProgressSessionDescription } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/chatSessions/chatSessionDescription';
import { getSessionStatusForModel } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution';
import { ChatEditorInput } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditorInput';
import { IChatDebugService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatDebugService.service';
import { IChatService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatService/chatService.service';
import { ChatSessionOptionsMap } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatSessionsService';
import { IChatSessionsService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatSessionsService.service';
import { ChatAgentLocation } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/constants';
import { getChatSessionType } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/model/chatUri';
import { IChatArtifactsService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/tools/chatArtifactsService.service';
import { IChatTodoListService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/tools/chatTodoListService.service';
import { IEditorGroupsService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/editor/common/editorGroupsService.service';
import { IEditorService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/editor/common/editorService.service';
import { extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';
import { ExtHostContext, MainContext } from '@codingame/monaco-vscode-api/vscode/vs/workbench/api/common/extHost.protocol';
import { observableValue } from '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/observables/observableValue';
import { autorun } from '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/reactions/autorun';
import { observableSignalFromEvent } from '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/observables/observableSignalFromEvent';
function stringOrMarkdownEqual(a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
if (typeof a === "string" || typeof b === "string") {
return false;
}
return markdownStringEqual(a, b);
}
class ObservableChatSession extends Disposable {
get options() {
return this._options ? ( new Map(this._options)) : undefined;
}
get progressObs() {
return this._progressObservable;
}
get isCompleteObs() {
return this._isCompleteObservable;
}
constructor(resource, providerHandle, proxy, logService, dialogService) {
super();
this._progressObservable = observableValue(this, []);
this._isCompleteObservable = observableValue(this, false);
this._onWillDispose = ( new Emitter());
this.onWillDispose = this._onWillDispose.event;
this._pendingProgressChunks = ( new Map());
this._isInitialized = false;
this._interruptionWasCanceled = false;
this._disposalPending = false;
this.sessionResource = resource;
this.providerHandle = providerHandle;
this.history = [];
this._proxy = proxy;
this._providerHandle = providerHandle;
this._logService = logService;
this._dialogService = dialogService;
}
initialize(token, context) {
if (!this._initializationPromise) {
this._initializationPromise = this._doInitializeContent(token, context);
}
return this._initializationPromise;
}
async _doInitializeContent(token, context) {
try {
const sessionContent = await raceCancellationError(
this._proxy.$provideChatSessionContent(this._providerHandle, this.sessionResource, context, token),
token
);
this._options = sessionContent.options ? ChatSessionOptionsMap.fromRecord(sessionContent.options) : undefined;
this.title = sessionContent.title;
this.history.length = 0;
this.history.push(...( sessionContent.history.map(turn => {
if (turn.type === "request") {
const variables = turn.variableData?.variables.map(v => {
const entry = {
...v,
value: revive(v.value)
};
return entry;
});
return {
type: "request",
prompt: turn.prompt,
participant: turn.participant,
command: turn.command,
variableData: variables ? {
variables
} : undefined,
id: turn.id,
modelId: turn.modelId,
modeInstructions: turn.modeInstructions ? revive(turn.modeInstructions) : undefined
};
}
return {
type: "response",
parts: ( turn.parts.map(part => revive(part))),
participant: turn.participant,
details: turn.details
};
})));
if (sessionContent.hasActiveResponseCallback && !this.interruptActiveResponseCallback) {
this.interruptActiveResponseCallback = async () => {
const confirmInterrupt = () => {
if (this._disposalPending) {
this._proxy.$disposeChatSessionContent(this._providerHandle, this.sessionResource);
this._disposalPending = false;
}
this._proxy.$interruptChatSessionActiveResponse(this._providerHandle, this.sessionResource, "ongoing");
return true;
};
if (sessionContent.supportsInterruption) {
return confirmInterrupt();
}
return this._dialogService.confirm({
message: ( localize(2588, "Are you sure you want to interrupt the active session?"))
}).then(confirmed => {
if (confirmed.confirmed) {
return confirmInterrupt();
} else {
this._addProgress([{
kind: "progressMessage",
content: {
value: "",
isTrusted: false
}
}]);
this._interruptionWasCanceled = true;
if (this._disposalPending) {
this._logService.info(
`Canceling deferred disposal for session ${this.sessionResource} (user canceled interruption)`
);
this._disposalPending = false;
}
return false;
}
});
};
}
if (sessionContent.hasRequestHandler && !this.requestHandler) {
this.requestHandler = async (request, progress, history, token) => {
this._progressObservable.set([], undefined);
this._isCompleteObservable.set(false, undefined);
let lastProgressLength = 0;
const progressDisposable = autorun(reader => {
const progressArray = this._progressObservable.read(reader);
const isComplete = this._isCompleteObservable.read(reader);
if (progressArray.length > lastProgressLength) {
const newProgress = progressArray.slice(lastProgressLength);
progress(newProgress);
lastProgressLength = progressArray.length;
}
if (isComplete) {
progressDisposable.dispose();
}
});
try {
await this._proxy.$invokeChatSessionRequestHandler(this._providerHandle, this.sessionResource, request, history, token);
if (!this._isCompleteObservable.get() && !this.interruptActiveResponseCallback) {
this._markComplete();
}
} catch (error) {
const errorProgress = {
kind: "progressMessage",
content: {
value: `Error: ${error instanceof Error ? error.message : String(error)}`,
isTrusted: false
}
};
this._addProgress([errorProgress]);
this._markComplete();
throw error;
} finally {
progressDisposable.dispose();
}
};
}
if (sessionContent.hasForkHandler && !this.forkSession) {
this.forkSession = async (request, token) => {
const result = await this._proxy.$forkChatSession(
this._providerHandle,
this.sessionResource,
request ? this.toRequestDto(request) : undefined,
token
);
return revive(result);
};
}
this._isInitialized = true;
const hasActiveResponse = sessionContent.hasActiveResponseCallback;
const hasRequestHandler = sessionContent.hasRequestHandler;
const hasAnyCapability = hasActiveResponse || hasRequestHandler;
for (const [requestId, chunks] of this._pendingProgressChunks) {
this._logService.debug(
`Processing ${chunks.length} pending progress chunks for session ${this.sessionResource}, requestId ${requestId}`
);
this._addProgress(chunks);
}
this._pendingProgressChunks.clear();
if (!hasAnyCapability) {
this._isCompleteObservable.set(true, undefined);
}
} catch (error) {
this._logService.error(`Failed to initialize chat session ${this.sessionResource}:`, error);
throw error;
}
}
handleProgressChunk(requestId, progress) {
if (!this._isInitialized) {
const existing = this._pendingProgressChunks.get(requestId) || [];
this._pendingProgressChunks.set(requestId, [...existing, ...progress]);
this._logService.debug(
`Queuing ${progress.length} progress chunks for session ${this.sessionResource}, requestId ${requestId} (session not initialized)`
);
return;
}
this._addProgress(progress);
}
handleProgressComplete(requestId) {
this._pendingProgressChunks.delete(requestId);
if (this._isInitialized) {
if (!this._interruptionWasCanceled) {
this._markComplete();
} else {
this._interruptionWasCanceled = false;
}
}
}
_addProgress(progress) {
const currentProgress = this._progressObservable.get();
this._progressObservable.set([...currentProgress, ...progress], undefined);
}
_markComplete() {
if (!this._isCompleteObservable.get()) {
this._isCompleteObservable.set(true, undefined);
}
}
toRequestDto(request) {
return {
type: "request",
id: request.id,
prompt: request.prompt,
participant: request.participant,
command: request.command,
variableData: undefined,
modelId: request.modelId,
modeInstructions: request.modeInstructions
};
}
dispose() {
this._onWillDispose.fire();
this._onWillDispose.dispose();
this._pendingProgressChunks.clear();
if (this.interruptActiveResponseCallback && !this._interruptionWasCanceled) {
this._disposalPending = true;
} else {
this._proxy.$disposeChatSessionContent(this._providerHandle, this.sessionResource);
}
super.dispose();
}
}
let MainThreadChatSessionItemController = class MainThreadChatSessionItemController extends Disposable {
constructor(proxy, chatSessionType, handle, supportsResolve, _chatService) {
super();
this._chatService = _chatService;
this._onDidChangeChatSessionItems = this._register(( new Emitter()));
this.onDidChangeChatSessionItems = this._onDidChangeChatSessionItems.event;
this._modelListeners = this._register(( new DisposableResourceMap()));
this._resolveCache = ( new ResourceMap());
this._resolving = ( new ResourceMap());
this._isDisposed = false;
this._items = ( new ResourceMap());
this._proxy = proxy;
this._handle = handle;
this._supportsResolve = supportsResolve;
const addModelListeners = async model => {
if (getChatSessionType(model.sessionResource) !== chatSessionType) {
return;
}
await this.refresh(CancellationToken.None);
if (this._isDisposed) {
return;
}
this.tryUpdateItemForModel(model);
const requestChangeListener = ( model.lastRequestObs.map(
last => last?.response && observableSignalFromEvent("chatSessions.modelRequestChangeListener", last.response.onDidChange)
));
const modelChangeListener = observableSignalFromEvent("chatSessions.modelChangeListener", model.onDidChange);
this._modelListeners.set(model.sessionResource, autorun(reader => {
requestChangeListener.read(reader)?.read(reader);
modelChangeListener.read(reader);
this.tryUpdateItemForModel(model);
}));
};
this._register(_chatService.onDidCreateModel(model => addModelListeners(model)));
for (const model of _chatService.chatModels.get()) {
addModelListeners(model);
}
this._register(_chatService.onDidDisposeSession(e => {
for (const sessionResource of e.sessionResources) {
this._modelListeners.deleteAndDispose(sessionResource);
}
}));
}
dispose() {
this._isDisposed = true;
this._resolveCache.clear();
super.dispose();
}
get items() {
return Array.from(( this._items.values()));
}
refresh(token) {
return this._proxy.$refreshChatSessionItems(this._handle, token);
}
async newChatSessionItem(request, token) {
const dto = await raceCancellationError(this._proxy.$newChatSessionItem(this._handle, {
prompt: request.prompt,
command: request.command,
initialSessionOptions: request.initialSessionOptions ? ChatSessionOptionsMap.toStrValueArray(request.initialSessionOptions) : undefined
}, token), token);
if (!dto) {
return undefined;
}
const item = this.addOrUpdateItem(dto);
return item;
}
async acceptChange(change) {
const addedOrUpdatedItems = [];
for (const item of change.addedOrUpdated) {
const resource = URI.revive(item.resource);
if (!( this._resolving.has(resource))) {
this._resolveCache.delete(resource);
}
addedOrUpdatedItems.push(await this.addOrUpdateItem(item));
}
for (const uri of change.removed) {
this._resolveCache.delete(uri);
this._items.delete(uri);
}
this._onDidChangeChatSessionItems.fire({
addedOrUpdated: addedOrUpdatedItems,
removed: change.removed
});
}
async addOrUpdateItem(dto) {
const resource = URI.revive(dto.resource);
const existing = this._items.get(resource);
const updated = ( new MainThreadChatSessionItem(
dto,
this._chatService.getSession(resource),
await this._chatService.getMetadataForSession(resource)
));
if (existing?.isEqual(updated)) {
return existing;
}
if (existing && existing.label !== updated.label && this._chatService.getSession(resource)) {
this._chatService.setSessionTitle(resource, updated.label);
}
this._items.set(resource, updated);
this._onDidChangeChatSessionItems.fire({
addedOrUpdated: [updated]
});
return updated;
}
async tryUpdateItemForModel(model) {
const resource = model.sessionResource;
const existing = this._items.get(resource);
if (existing) {
this.addOrUpdateItem(existing);
}
}
async getNewChatSessionInputState(sessionResource, token) {
const optionGroups = await this._proxy.$provideChatSessionInputState(this._handle, sessionResource, token);
if (!optionGroups?.length) {
return undefined;
}
return optionGroups;
}
async resolveChatSessionItem(resource, token) {
if (!this._supportsResolve) {
return undefined;
}
const cached = this._resolveCache.get(resource);
if (cached) {
return cached;
}
const promise = this._doResolveItem(resource, token).catch(err => {
this._resolveCache.delete(resource);
throw err;
});
this._resolveCache.set(resource, promise);
return promise;
}
async _doResolveItem(resource, token) {
const expectedItem = this._items.get(resource);
this._resolving.set(resource, true);
let dto;
try {
dto = await raceCancellationError(this._proxy.$resolveChatSessionItem(this._handle, resource, token), token);
} finally {
this._resolving.delete(resource);
}
if (!dto) {
return undefined;
}
if (this._items.get(resource) !== expectedItem) {
return this._items.get(resource);
}
const updated = ( new MainThreadChatSessionItem(
dto,
this._chatService.getSession(resource),
await this._chatService.getMetadataForSession(resource)
));
if (this._items.get(resource) !== expectedItem) {
return this._items.get(resource);
}
if (expectedItem?.isEqual(updated)) {
return expectedItem;
}
this._items.set(resource, updated);
this._onDidChangeChatSessionItems.fire({
addedOrUpdated: [updated]
});
return updated;
}
setSupportsResolve(supportsResolve) {
if (this._supportsResolve === supportsResolve) {
return;
}
this._supportsResolve = supportsResolve;
if (supportsResolve) {
this._resolveCache.clear();
}
}
};
MainThreadChatSessionItemController = ( __decorate([( __param(4, IChatService))], MainThreadChatSessionItemController));
class MainThreadChatSessionItem {
constructor(dto, model, detailOverrides) {
this.resource = URI.revive(dto.resource);
this.label = dto.label;
this.timing = dto.timing;
this.iconPath = dto.iconPath;
this.badge = reviveMarkdownString(dto.badge);
this.tooltip = reviveMarkdownString(dto.tooltip);
this.archived = dto.archived;
this.metadata = dto.metadata;
this.description = (model && getInProgressSessionDescription(model)) ?? reviveMarkdownString(dto.description);
this.status = (model && getSessionStatusForModel(model)) ?? dto.status;
this.changes = revive(dto.changes);
if (detailOverrides && !this.changes) {
const diffs = {
files: detailOverrides.stats?.fileCount || 0,
insertions: detailOverrides.stats?.added || 0,
deletions: detailOverrides.stats?.removed || 0
};
if (hasValidDiff(diffs)) {
this.changes = diffs;
}
}
}
isEqual(other) {
return isEqual(this.resource, other.resource) && this.label === other.label && this.description === other.description && this.status === other.status && this.timing.created === other.timing.created && this.timing.lastRequestStarted === other.timing.lastRequestStarted && this.timing.lastRequestEnded === other.timing.lastRequestEnded && equals(this.changes, other.changes) && equals(this.iconPath, other.iconPath) && stringOrMarkdownEqual(this.badge, other.badge) && stringOrMarkdownEqual(this.tooltip, other.tooltip) && this.archived === other.archived && equals(this.metadata, other.metadata);
}
}
let MainThreadChatSessions = class MainThreadChatSessions extends Disposable {
constructor(
_extHostContext,
_agentSessionsService,
_chatSessionsService,
_chatService,
_chatWidgetService,
_chatTodoListService,
_chatArtifactsService,
_chatDebugService,
_dialogService,
_editorService,
editorGroupService,
_logService,
_instantiationService
) {
super();
this._extHostContext = _extHostContext;
this._agentSessionsService = _agentSessionsService;
this._chatSessionsService = _chatSessionsService;
this._chatService = _chatService;
this._chatWidgetService = _chatWidgetService;
this._chatTodoListService = _chatTodoListService;
this._chatArtifactsService = _chatArtifactsService;
this._chatDebugService = _chatDebugService;
this._dialogService = _dialogService;
this._editorService = _editorService;
this.editorGroupService = editorGroupService;
this._logService = _logService;
this._instantiationService = _instantiationService;
this._itemControllerRegistrations = this._register(( new DisposableMap()));
this._contentProvidersRegistrations = this._register(( new DisposableMap()));
this._sessionTypeToHandle = ( new Map());
this._activeSessions = ( new ResourceMap());
this._sessionDisposables = ( new ResourceMap());
this._proxy = ( this._extHostContext.getProxy(ExtHostContext.ExtHostChatSessions));
this._register(this._chatSessionsService.onDidChangeSessionOptions((
{
sessionResource,
updates
}
) => {
const sessionType = getChatSessionType(sessionResource);
const handle = this._getHandleForSessionType(sessionType);
this._logService.trace(
`[MainThreadChatSessions] onRequestNotifyExtension received: sessionType '${sessionType}', handle ${handle}, ${updates.size} update(s)`
);
if (handle !== undefined) {
this.notifyOptionsChange(handle, sessionResource, updates);
} else {
this._logService.warn(
`[MainThreadChatSessions] Cannot notify option change for sessionType '${sessionType}': no provider registered. Registered types: [${Array.from(( this._sessionTypeToHandle.keys())).join(", ")}]`
);
}
}));
this._register(
this._agentSessionsService.model.onDidChangeSessionArchivedState(session => {
for (const [handle, {
chatSessionType
}] of this._itemControllerRegistrations) {
if (chatSessionType === session.providerType) {
this._proxy.$onDidChangeChatSessionItemState(handle, session.resource, session.isArchived());
}
}
})
);
}
_getHandleForSessionType(chatSessionType) {
return this._sessionTypeToHandle.get(chatSessionType);
}
$registerChatSessionItemController(handle, chatSessionType, supportsResolve) {
const disposables = ( new DisposableStore());
const controller = disposables.add(this._instantiationService.createInstance(
MainThreadChatSessionItemController,
this._proxy,
chatSessionType,
handle,
supportsResolve
));
disposables.add(
this._chatSessionsService.registerChatSessionItemController(chatSessionType, controller)
);
this._itemControllerRegistrations.set(handle, {
chatSessionType,
controller,
dispose: () => disposables.dispose()
});
this._refreshControllerInputState(handle, chatSessionType);
}
$updateChatSessionItemControllerCapabilities(handle, supportsResolve) {
const registration = this._itemControllerRegistrations.get(handle);
if (!registration) {
this._logService.warn(`No chat session item controller found for handle ${handle}`);
return;
}
registration.controller.setSupportsResolve(supportsResolve);
}
_refreshControllerInputState(handle, chatSessionType) {
this._proxy.$provideChatSessionInputState(handle, undefined, CancellationToken.None).then(optionGroups => {
if (optionGroups?.length) {
this._applyOptionGroups(handle, chatSessionType, undefined, optionGroups);
}
}).catch(
err => this._logService.error("Error fetching chat session input state", err)
);
}
_applyOptionGroups(handle, chatSessionType, sessionResourceComponents, optionGroups) {
this._chatSessionsService.setOptionGroupsForSessionType(chatSessionType, handle, optionGroups);
if (sessionResourceComponents) {
const sessionResource = URI.revive(sessionResourceComponents);
optionGroups.forEach(group => {
if (group.selected) {
this._chatSessionsService.setSessionOption(sessionResource, group.id, group.selected);
}
});
}
}
getController(handle) {
const registration = this._itemControllerRegistrations.get(handle);
if (!registration) {
throw ( new Error(`No chat session controller registered for handle ${handle}`));
}
return registration.controller;
}
async $updateChatSessionItems(controllerHandle, change) {
const controller = this.getController(controllerHandle);
controller.acceptChange({
addedOrUpdated: change.addedOrUpdated,
removed: ( change.removed.map(uri => URI.revive(uri)))
});
}
async $addOrUpdateChatSessionItem(controllerHandle, item) {
const controller = this.getController(controllerHandle);
controller.acceptChange({
addedOrUpdated: [item],
removed: []
});
}
$onDidChangeChatSessionOptions(handle, sessionResourceComponents, updates) {
const sessionResource = URI.revive(sessionResourceComponents);
this._chatSessionsService.updateSessionOptions(sessionResource, ChatSessionOptionsMap.fromRecord(updates));
}
async $onDidCommitChatSessionItem(handle, originalComponents, modifiedCompoennts) {
const originalResource = URI.revive(originalComponents);
const modifiedResource = URI.revive(modifiedCompoennts);
this._logService.trace(
`$onDidCommitChatSessionItem: handle(${handle}), original(${originalResource}), modified(${modifiedResource})`
);
const chatSessionType = this._itemControllerRegistrations.get(handle)?.chatSessionType;
if (!chatSessionType) {
this._logService.error(`No chat session type found for provider handle ${handle}`);
return;
}
const originalEditor = this._editorService.editors.find(editor => editor.resource?.toString() === ( originalResource.toString()));
const originalModel = this._chatService.acquireExistingSession(originalResource);
const contribution = this._chatSessionsService.getAllChatSessionContributions().find(c => c.type === chatSessionType);
try {
this._chatTodoListService.migrateTodos(originalResource, modifiedResource);
this._chatArtifactsService.getArtifacts(originalResource).migrate(this._chatArtifactsService.getArtifacts(modifiedResource));
if (chatSessionType === "copilotcli") {
this._chatDebugService.invokeProviders(modifiedResource).catch(() => {});
}
const originalGroup = this.editorGroupService.groups.find(group => ( group.editors.some(editor => isEqual(editor.resource, originalResource)))) ?? this.editorGroupService.activeGroup;
const options = {
title: {
preferred: originalEditor?.getName() || undefined,
fallback: ( localize(2589, "{0}", contribution?.displayName))
}
};
const newSession = await this._chatSessionsService.getOrCreateChatSession(URI.revive(modifiedResource), CancellationToken.None);
if (originalEditor) {
newSession.transferredState = originalEditor instanceof ChatEditorInput ? {
editingSession: originalEditor.transferOutEditingSession(),
inputState: originalModel?.object?.inputModel.toJSON()
} : undefined;
await this._editorService.replaceEditors([{
editor: originalEditor,
replacement: {
resource: modifiedResource,
options
}
}], originalGroup);
this._resendPendingRequests(originalResource, modifiedResource);
return;
}
if (originalModel) {
newSession.transferredState = {
editingSession: originalModel.object.editingSession,
inputState: originalModel.object.inputModel.toJSON()
};
}
const chatViewWidget = this._chatWidgetService.getWidgetBySessionResource(originalResource);
if (chatViewWidget && isIChatViewViewContext(chatViewWidget.viewContext)) {
await this._chatWidgetService.openSession(modifiedResource, undefined, {
preserveFocus: true
});
} else {
const ref = await this._chatService.acquireOrLoadSession(modifiedResource, ChatAgentLocation.Chat, CancellationToken.None);
ref?.dispose();
}
this._resendPendingRequests(originalResource, modifiedResource);
this._chatSessionsService.fireSessionCommitted(originalResource, modifiedResource);
} finally {
originalModel?.dispose();
}
}
_resendPendingRequests(originalResource, modifiedResource) {
this._chatService.migrateRequests(originalResource, modifiedResource);
}
async _provideChatSessionContent(providerHandle, sessionResource, token) {
const t0 = Date.now();
this._logService.trace(
`[MainThreadChatSessions] _provideChatSessionContent start handle=${providerHandle} uri=${( sessionResource.toString())}`
);
let session = this._activeSessions.get(sessionResource);
if (!session) {
session = ( new ObservableChatSession(
sessionResource,
providerHandle,
this._proxy,
this._logService,
this._dialogService
));
this._activeSessions.set(sessionResource, session);
const disposable = session.onWillDispose(() => {
this._activeSessions.delete(sessionResource);
this._sessionDisposables.get(sessionResource)?.dispose();
this._sessionDisposables.delete(sessionResource);
});
this._sessionDisposables.set(sessionResource, disposable);
}
try {
const initialSessionOptions = this._chatSessionsService.getSessionOptions(sessionResource);
await session.initialize(token, {
initialSessionOptions: initialSessionOptions ? ( [...initialSessionOptions].map(([optionId, value]) => ({
optionId,
value: typeof value === "string" ? value : value?.id
}))) : undefined
});
if (session.options) {
for (const [_, handle] of this._sessionTypeToHandle) {
if (handle === providerHandle) {
for (const [optionId, value] of session.options) {
this._chatSessionsService.setSessionOption(sessionResource, optionId, value);
}
break;
}
}
}
this._logService.trace(
`[MainThreadChatSessions] _provideChatSessionContent done total=${Date.now() - t0}ms handle=${providerHandle} uri=${( sessionResource.toString())}`
);
return session;
} catch (error) {
session.dispose();
this._logService.error(
`Error providing chat session content for handle ${providerHandle} and resource ${( sessionResource.toString())}:`,
error
);
throw error;
}
}
$unregisterChatSessionItemController(handle) {
this._itemControllerRegistrations.deleteAndDispose(handle);
}
$registerChatSessionContentProvider(handle, chatSessionScheme) {
const provider = {
provideChatSessionContent: (resource, token) => this._provideChatSessionContent(handle, resource, token)
};
this._sessionTypeToHandle.set(chatSessionScheme, handle);
this._contentProvidersRegistrations.set(
handle,
this._chatSessionsService.registerChatSessionContentProvider(chatSessionScheme, provider)
);
this._refreshProviderOptions(handle, chatSessionScheme);
}
$unregisterChatSessionContentProvider(handle) {
this._contentProvidersRegistrations.deleteAndDispose(handle);
for (const [sessionType, h] of this._sessionTypeToHandle) {
if (h === handle) {
this._sessionTypeToHandle.delete(sessionType);
break;
}
}
for (const [key, session] of this._activeSessions) {
if (session.providerHandle === handle) {
session.dispose();
this._activeSessions.delete(key);
}
}
}
async $handleProgressChunk(handle, sessionResource, requestId, chunks) {
const resource = URI.revive(sessionResource);
const observableSession = this._activeSessions.get(resource);
if (!observableSession) {
this._logService.warn(
`No session found for progress chunks: handle ${handle}, sessionResource ${resource}, requestId ${requestId}`
);
return;
}
const chatProgressParts = ( chunks.map(chunk => {
const [progress] = Array.isArray(chunk) ? chunk : [chunk];
return revive(progress);
}));
observableSession.handleProgressChunk(requestId, chatProgressParts);
}
$handleProgressComplete(handle, sessionResource, requestId) {
const resource = URI.revive(sessionResource);
const observableSession = this._activeSessions.get(resource);
if (!observableSession) {
this._logService.warn(
`No session found for progress completion: handle ${handle}, sessionResource ${resource}, requestId ${requestId}`
);
return;
}
observableSession.handleProgressComplete(requestId);
}
$handleAnchorResolve(handle, sesssionResource, requestId, requestHandle, anchor) {}
$onDidChangeChatSessionProviderOptions(handle) {
let sessionType;
for (const [type, h] of this._sessionTypeToHandle) {
if (h === handle) {
sessionType = type;
break;
}
}
if (!sessionType) {
this._logService.warn(
`No session type found for chat session content provider handle ${handle} when refreshing provider options`
);
return;
}
this._refreshProviderOptions(handle, sessionType);
}
$updateChatSessionInputState(controllerHandle, sessionResource, optionGroups) {
const registration = this._itemControllerRegistrations.get(controllerHandle);
if (!registration) {
this._logService.warn(
`No controller found for handle ${controllerHandle} when updating input state`
);
return;
}
this._applyOptionGroups(
controllerHandle,
registration.chatSessionType,
sessionResource,
optionGroups
);
}
_refreshProviderOptions(handle, chatSessionScheme) {
this._proxy.$provideChatSessionProviderOptions(handle, CancellationToken.None).then(options => {
if (options?.optionGroups && options.optionGroups.length) {
this._chatSessionsService.setOptionGroupsForSessionType(chatSessionScheme, handle, [...options.optionGroups]);
}
}).catch(err => this._logService.error("Error fetching chat session options", err));
}
dispose() {
for (const session of ( this._activeSessions.values())) {
session.dispose();
}
this._activeSessions.clear();
for (const disposable of ( this._sessionDisposables.values())) {
disposable.dispose();
}
this._sessionDisposables.clear();
super.dispose();
}
async notifyOptionsChange(handle, sessionResource, updates) {
this._logService.trace(
`[MainThreadChatSessions] notifyOptionsChange: starting proxy call for handle ${handle}, sessionResource ${sessionResource}`
);
try {
await this._proxy.$provideHandleOptionsChange(
handle,
sessionResource,
Object.fromEntries(updates),
CancellationToken.None
);
this._logService.trace(
`[MainThreadChatSessions] notifyOptionsChange: proxy call completed for handle ${handle}, sessionResource ${sessionResource}`
);
} catch (error) {
this._logService.error(
`[MainThreadChatSessions] notifyOptionsChange: error for handle ${handle}, sessionResource ${sessionResource}:`,
error
);
}
}
};
MainThreadChatSessions = __decorate([extHostNamedCustomer(MainContext.MainThreadChatSessions), ( __param(1, IAgentSessionsService)), ( __param(2, IChatSessionsService)), ( __param(3, IChatService)), ( __param(4, IChatWidgetService)), ( __param(5, IChatTodoListService)), ( __param(6, IChatArtifactsService)), ( __param(7, IChatDebugService)), ( __param(8, IDialogService)), ( __param(9, IEditorService)), ( __param(10, IEditorGroupsService)), ( __param(11, ILogService)), ( __param(12, IInstantiationService))], MainThreadChatSessions);
function reviveMarkdownString(value) {
if (!value) {
return undefined;
}
if (typeof value === "string") {
return value;
}
if (typeof value === "object" && "value" in value) {
return MarkdownString.lift(value);
}
return undefined;
}
export { MainThreadChatSessions, ObservableChatSession };