Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/vs/workbench/api/browser/mainThreadChatSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
dispose: () => disposables.dispose(),
});

disposables.add(this._chatSessionsService.registerChatModelChangeListeners(
this._chatService,
disposables.add(this._chatService.registerChatModelChangeListeners(
chatSessionType,
() => controller.fireOnDidChangeChatSessionItems()
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class LocalAgentsSessionsController extends Disposable implements IChatSe
this._onDidChangeChatSessionItems.fire();
};

this._register(this.chatSessionsService.registerChatModelChangeListeners(this.chatService, Schemas.vscodeLocalChatSession, refreshItems));
this._register(this.chatService.registerChatModelChangeListeners(Schemas.vscodeLocalChatSession, refreshItems));

this._register(this.chatService.onDidDisposeSession(e => {
const session = e.sessionResource.filter(resource => getChatSessionType(resource) === this.chatSessionType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { raceCancellationError } from '../../../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { AsyncEmitter, Emitter, Event } from '../../../../../base/common/event.js';
import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
import { ResourceMap } from '../../../../../base/common/map.js';
import { Schemas } from '../../../../../base/common/network.js';
import * as resources from '../../../../../base/common/resources.js';
Expand Down Expand Up @@ -37,7 +37,7 @@ import { CHAT_CATEGORY } from '../actions/chatActions.js';
import { IChatEditorOptions } from '../widgetHosts/editor/chatEditor.js';
import { IChatModel } from '../../common/model/chatModel.js';
import { IChatService, IChatToolInvocation } from '../../common/chatService/chatService.js';
import { autorun, autorunIterableDelta, observableFromEvent, observableSignalFromEvent } from '../../../../../base/common/observable.js';
import { autorun, observableFromEvent } from '../../../../../base/common/observable.js';
import { IChatRequestVariableEntry } from '../../common/attachments/chatVariableEntries.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { IMarkdownString } from '../../../../../base/common/htmlContent.js';
Expand Down Expand Up @@ -915,46 +915,6 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
};
}

public registerChatModelChangeListeners(
chatService: IChatService,
chatSessionType: string,
onChange: () => void
): IDisposable {
const disposableStore = new DisposableStore();
const chatModelsICareAbout = chatService.chatModels.map(models =>
Array.from(models).filter((model: IChatModel) => model.sessionResource.scheme === chatSessionType)
);

const listeners = new ResourceMap<IDisposable>();
const autoRunDisposable = autorunIterableDelta(
reader => chatModelsICareAbout.read(reader),
({ addedValues, removedValues }) => {
removedValues.forEach((removed) => {
const listener = listeners.get(removed.sessionResource);
if (listener) {
listeners.delete(removed.sessionResource);
listener.dispose();
}
});
addedValues.forEach((added) => {
const requestChangeListener = added.lastRequestObs.map(last => last?.response && observableSignalFromEvent('chatSessions.modelRequestChangeListener', last.response.onDidChange));
const modelChangeListener = observableSignalFromEvent('chatSessions.modelChangeListener', added.onDidChange);
listeners.set(added.sessionResource, autorun(reader => {
requestChangeListener.read(reader)?.read(reader);
modelChangeListener.read(reader);
onChange();
}));
});
}
);
disposableStore.add(toDisposable(() => {
for (const listener of listeners.values()) { listener.dispose(); }
}));
disposableStore.add(autoRunDisposable);
return disposableStore;
}


public getInProgressSessionDescription(chatModel: IChatModel): string | undefined {
const requests = chatModel.getRequests();
if (requests.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DeferredPromise } from '../../../../../base/common/async.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { Event } from '../../../../../base/common/event.js';
import { IMarkdownString } from '../../../../../base/common/htmlContent.js';
import { DisposableStore, IReference } from '../../../../../base/common/lifecycle.js';
import { DisposableStore, IDisposable, IReference } from '../../../../../base/common/lifecycle.js';
import { autorun, autorunSelfDisposable, IObservable, IReader } from '../../../../../base/common/observable.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { hasKey } from '../../../../../base/common/types.js';
Expand Down Expand Up @@ -1470,6 +1470,11 @@ export interface IChatService {

readonly requestInProgressObs: IObservable<boolean>;

/**
* @deprecated
*/
registerChatModelChangeListeners(chatSessionType: string, onChange: () => void): IDisposable;

/**
* For tests only!
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { BugIndicatingError, ErrorNoTelemetry } from '../../../../../base/common
import { Emitter, Event } from '../../../../../base/common/event.js';
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
import { Iterable } from '../../../../../base/common/iterator.js';
import { Disposable, DisposableResourceMap, DisposableStore, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js';
import { Disposable, DisposableResourceMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
import { revive } from '../../../../../base/common/marshalling.js';
import { Schemas } from '../../../../../base/common/network.js';
import { autorun, derived, IObservable, ISettableObservable, observableValue } from '../../../../../base/common/observable.js';
import { autorun, autorunIterableDelta, derived, IObservable, ISettableObservable, observableSignalFromEvent, observableValue } from '../../../../../base/common/observable.js';
import { isEqual } from '../../../../../base/common/resources.js';
import { StopWatch } from '../../../../../base/common/stopwatch.js';
import { isDefined } from '../../../../../base/common/types.js';
Expand Down Expand Up @@ -54,6 +54,7 @@ import { ILanguageModelToolsService } from '../tools/languageModelToolsService.j
import { ChatSessionOperationLog } from '../model/chatSessionOperationLog.js';
import { IPromptsService } from '../promptSyntax/service/promptsService.js';
import { ChatRequestHooks, mergeHooks } from '../promptSyntax/hookSchema.js';
import { ResourceMap } from '../../../../../base/common/map.js';

const serializedChatKey = 'interactive.sessions';

Expand Down Expand Up @@ -1613,4 +1614,39 @@ export class ChatService extends Disposable implements IChatService {
}
return localSessionId;
}

public registerChatModelChangeListeners(chatSessionType: string, onChange: () => void): IDisposable {
const disposableStore = new DisposableStore();
const chatModelsICareAbout = this.chatModels.map(models =>
Array.from(models).filter((model: IChatModel) => model.sessionResource.scheme === chatSessionType)
);

const listeners = new ResourceMap<IDisposable>();
const autoRunDisposable = autorunIterableDelta(
reader => chatModelsICareAbout.read(reader),
({ addedValues, removedValues }) => {
removedValues.forEach((removed) => {
const listener = listeners.get(removed.sessionResource);
if (listener) {
listeners.delete(removed.sessionResource);
listener.dispose();
}
});
addedValues.forEach((added) => {
const requestChangeListener = added.lastRequestObs.map(last => last?.response && observableSignalFromEvent('chatSessions.modelRequestChangeListener', last.response.onDidChange));
const modelChangeListener = observableSignalFromEvent('chatSessions.modelChangeListener', added.onDidChange);
listeners.set(added.sessionResource, autorun(reader => {
requestChangeListener.read(reader)?.read(reader);
modelChangeListener.read(reader);
onChange();
}));
});
}
);
disposableStore.add(toDisposable(() => {
for (const listener of listeners.values()) { listener.dispose(); }
}));
disposableStore.add(autoRunDisposable);
return disposableStore;
}
}
3 changes: 1 addition & 2 deletions src/vs/workbench/contrib/chat/common/chatSessionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './participants/chatAgents.js';
import { IChatEditingSession } from './editing/chatEditingService.js';
import { IChatModel, IChatRequestVariableData, ISerializableChatModelInputState } from './model/chatModel.js';
import { IChatProgress, IChatService, IChatSessionTiming } from './chatService/chatService.js';
import { IChatProgress, IChatSessionTiming } from './chatService/chatService.js';
import { Target } from './promptSyntax/promptTypes.js';

export const enum ChatSessionStatus {
Expand Down Expand Up @@ -298,7 +298,6 @@ export interface IChatSessionsService {
readonly onRequestNotifyExtension: Event<IChatSessionOptionsWillNotifyExtensionEvent>;
notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string | IChatSessionProviderOptionItem }>): Promise<void>;

registerChatModelChangeListeners(chatService: IChatService, chatSessionType: string, onChange: () => void): IDisposable;
getInProgressSessionDescription(chatModel: IChatModel): string | undefined;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import assert from 'assert';
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { Codicon } from '../../../../../../base/common/codicons.js';
import { Emitter, Event } from '../../../../../../base/common/event.js';
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
import { DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js';
import { ISettableObservable, observableValue } from '../../../../../../base/common/observable.js';
import { URI } from '../../../../../../base/common/uri.js';
import { runWithFakedTimers } from '../../../../../../base/test/common/timeTravelScheduler.js';
Expand Down Expand Up @@ -212,6 +212,26 @@ class MockChatService implements IChatService {
getMetadataForSession(sessionResource: URI): Promise<IChatDetail | undefined> {
throw new Error('Method not implemented.');
}


private onChange?: () => void;

registerChatModelChangeListeners(chatSessionType: string, onChange: () => void): IDisposable {
// Store the emitter so tests can trigger it
this.onChange = onChange;
return {
dispose: () => {
this.onChange = undefined;
}
};
}

// Helper method for tests to trigger progress events
triggerProgressEvent(): void {
if (this.onChange) {
this.onChange();
}
}
}

function createMockChatModel(options: {
Expand Down Expand Up @@ -767,7 +787,7 @@ suite('LocalAgentsSessionsController', () => {
}));

// Simulate progress change by triggering the progress listener
mockChatSessionsService.triggerProgressEvent();
mockChatService.triggerProgressEvent();

assert.strictEqual(changeEventCount, 1);
});
Expand All @@ -793,7 +813,7 @@ suite('LocalAgentsSessionsController', () => {
}));

// Simulate progress change by triggering the progress listener
mockChatSessionsService.triggerProgressEvent();
mockChatService.triggerProgressEvent();

assert.strictEqual(changeEventCount, 1);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IChatModel, IChatRequestModel, IChatRequestVariableData, ISerializableC
import { IParsedChatRequest } from '../../../common/requestParser/chatParserTypes.js';
import { ChatRequestQueueKind, ChatSendResult, IChatCompleteResponse, IChatDetail, IChatModelReference, IChatProgress, IChatProviderInfo, IChatSendRequestOptions, IChatService, IChatSessionContext, IChatSessionStartOptions, IChatUserActionEvent } from '../../../common/chatService/chatService.js';
import { ChatAgentLocation } from '../../../common/constants.js';
import { IDisposable } from '../../../../../../base/common/lifecycle.js';

export class MockChatService implements IChatService {
chatModels: IObservable<Iterable<IChatModel>> = observableValue('chatModels', []);
Expand Down Expand Up @@ -164,4 +165,23 @@ export class MockChatService implements IChatService {
getMetadataForSession(sessionResource: URI): Promise<IChatDetail | undefined> {
throw new Error('Method not implemented.');
}

private onChange?: () => void;

registerChatModelChangeListeners(chatSessionType: string, onChange: () => void): IDisposable {
// Store the emitter so tests can trigger it
this.onChange = onChange;
return {
dispose: () => {
this.onChange = undefined;
}
};
}

// Helper method for tests to trigger progress events
triggerProgressEvent(): void {
if (this.onChange) {
this.onChange();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ThemeIcon } from '../../../../../base/common/themables.js';
import { URI } from '../../../../../base/common/uri.js';
import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from '../../common/participants/chatAgents.js';
import { IChatModel } from '../../common/model/chatModel.js';
import { IChatService } from '../../common/chatService/chatService.js';
import { IChatSession, IChatSessionContentProvider, IChatSessionItemController, IChatSessionItem, IChatSessionOptionsWillNotifyExtensionEvent, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
import { Target } from '../../common/promptSyntax/promptTypes.js';

Expand Down Expand Up @@ -47,7 +46,6 @@ export class MockChatSessionsService implements IChatSessionsService {
private optionGroups = new Map<string, IChatSessionProviderOptionGroup[]>();
private sessionOptions = new ResourceMap<Map<string, string>>();
private inProgress = new Map<string, number>();
private onChange = () => { };

// For testing: allow triggering events
fireDidChangeItemsProviders(event: { chatSessionType: string }): void {
Expand Down Expand Up @@ -232,20 +230,4 @@ export class MockChatSessionsService implements IChatSessionsService {
registerSessionResourceAlias(_untitledResource: URI, _realResource: URI): void {
// noop
}

registerChatModelChangeListeners(chatService: IChatService, chatSessionType: string, onChange: () => void): IDisposable {
// Store the emitter so tests can trigger it
this.onChange = onChange;
return {
dispose: () => {
}
};
}

// Helper method for tests to trigger progress events
triggerProgressEvent(): void {
if (this.onChange) {
this.onChange();
}
}
}
Loading