@@ -11,20 +11,30 @@ import { Iterable } from 'vs/base/common/iterator';
11
11
import { Disposable , IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
12
12
import { StopWatch } from 'vs/base/common/stopwatch' ;
13
13
import { withNullAsUndefined } from 'vs/base/common/types' ;
14
+ import { URI , UriComponents } from 'vs/base/common/uri' ;
14
15
import { localize } from 'vs/nls' ;
15
16
import { CommandsRegistry } from 'vs/platform/commands/common/commands' ;
16
17
import { IContextKey , IContextKeyService } from 'vs/platform/contextkey/common/contextkey' ;
17
18
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
18
19
import { ILogService } from 'vs/platform/log/common/log' ;
19
20
import { IStorageService , StorageScope , StorageTarget } from 'vs/platform/storage/common/storage' ;
20
21
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
22
+ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace' ;
21
23
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys' ;
22
24
import { ChatModel , ChatWelcomeMessageModel , IChatModel , ISerializableChatData , ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel' ;
23
25
import { IChat , IChatCompleteResponse , IChatDetail , IChatDynamicRequest , IChatProgress , IChatProvider , IChatProviderInfo , IChatReplyFollowup , IChatService , IChatUserActionEvent , ISlashCommand , ISlashCommandProvider , InteractiveSessionCopyKind , InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService' ;
24
26
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' ;
25
27
26
28
const serializedChatKey = 'interactive.sessions' ;
27
29
30
+ const globalChatKey = 'chat.workspaceTransfer' ;
31
+ interface IChatTransfer {
32
+ toWorkspace : UriComponents ;
33
+ timestampInMilliseconds : number ;
34
+ chat : ISerializableChatData ;
35
+ }
36
+ const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60 ;
37
+
28
38
type ChatProviderInvokedEvent = {
29
39
providerId : string ;
30
40
timeToFirstProgress : number ;
@@ -115,6 +125,11 @@ export class ChatService extends Disposable implements IChatService {
115
125
private readonly _persistedSessions : ISerializableChatsData ;
116
126
private readonly _hasProvider : IContextKey < boolean > ;
117
127
128
+ private _transferred : ISerializableChatData | undefined ;
129
+ public get transferredSessionId ( ) : string | undefined {
130
+ return this . _transferred ?. sessionId ;
131
+ }
132
+
118
133
private readonly _onDidPerformUserAction = this . _register ( new Emitter < IChatUserActionEvent > ( ) ) ;
119
134
public readonly onDidPerformUserAction : Event < IChatUserActionEvent > = this . _onDidPerformUserAction . event ;
120
135
@@ -125,6 +140,7 @@ export class ChatService extends Disposable implements IChatService {
125
140
@IInstantiationService private readonly instantiationService : IInstantiationService ,
126
141
@ITelemetryService private readonly telemetryService : ITelemetryService ,
127
142
@IContextKeyService private readonly contextKeyService : IContextKeyService ,
143
+ @IWorkspaceContextService private readonly workspaceContextService : IWorkspaceContextService
128
144
) {
129
145
super ( ) ;
130
146
@@ -140,6 +156,12 @@ export class ChatService extends Disposable implements IChatService {
140
156
this . trace ( 'constructor' , 'No persisted sessions' ) ;
141
157
}
142
158
159
+ this . _transferred = this . getTransferredSession ( ) ;
160
+ if ( this . _transferred ) {
161
+ this . trace ( 'constructor' , `Transferred session ${ this . _transferred . sessionId } ` ) ;
162
+ this . _persistedSessions [ this . _transferred . sessionId ] = this . _transferred ;
163
+ }
164
+
143
165
this . _register ( storageService . onWillSaveState ( ( ) => this . saveState ( ) ) ) ;
144
166
}
145
167
@@ -218,6 +240,23 @@ export class ChatService extends Disposable implements IChatService {
218
240
}
219
241
}
220
242
243
+ private getTransferredSession ( ) : ISerializableChatData | undefined {
244
+ const data : IChatTransfer [ ] = this . storageService . getObject ( globalChatKey , StorageScope . PROFILE , [ ] ) ;
245
+ const workspaceUri = this . workspaceContextService . getWorkspace ( ) . folders [ 0 ] ?. uri ;
246
+ if ( ! workspaceUri ) {
247
+ return ;
248
+ }
249
+
250
+ const thisWorkspace = workspaceUri . toString ( ) ;
251
+ const currentTime = Date . now ( ) ;
252
+ // Only use transferred data if it was created recently
253
+ const transferred = data . find ( item => URI . revive ( item . toWorkspace ) . toString ( ) === thisWorkspace && ( currentTime - item . timestampInMilliseconds < SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS ) ) ;
254
+ // Keep data that isn't for the current workspace and that hasn't expired yet
255
+ const filtered = data . filter ( item => URI . revive ( item . toWorkspace ) . toString ( ) !== thisWorkspace && ( currentTime - item . timestampInMilliseconds < SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS ) ) ;
256
+ this . storageService . store ( globalChatKey , JSON . stringify ( filtered ) , StorageScope . PROFILE , StorageTarget . MACHINE ) ;
257
+ return transferred ?. chat ;
258
+ }
259
+
221
260
getHistory ( ) : IChatDetail [ ] {
222
261
const sessions = Object . values ( this . _persistedSessions )
223
262
. filter ( session => session . requests . length > 0 ) ;
@@ -307,6 +346,10 @@ export class ChatService extends Disposable implements IChatService {
307
346
return undefined ;
308
347
}
309
348
349
+ if ( sessionId === this . transferredSessionId ) {
350
+ this . _transferred = undefined ;
351
+ }
352
+
310
353
return this . _startSession ( sessionData . providerId , sessionData , CancellationToken . None ) ;
311
354
}
312
355
@@ -592,4 +635,21 @@ export class ChatService extends Disposable implements IChatService {
592
635
} ;
593
636
} ) ;
594
637
}
638
+
639
+ transferChatSession ( sessionProviderId : number , toWorkspace : URI ) : void {
640
+ const model = Iterable . find ( this . _sessionModels . values ( ) , model => model . session ?. id === sessionProviderId ) ;
641
+ if ( ! model ) {
642
+ throw new Error ( `Failed to transfer session. Unknown session provider ID: ${ sessionProviderId } ` ) ;
643
+ }
644
+
645
+ const existingRaw : IChatTransfer [ ] = this . storageService . getObject ( globalChatKey , StorageScope . PROFILE , [ ] ) ;
646
+ existingRaw . push ( {
647
+ chat : model . toJSON ( ) ,
648
+ timestampInMilliseconds : Date . now ( ) ,
649
+ toWorkspace : toWorkspace
650
+ } ) ;
651
+
652
+ this . storageService . store ( globalChatKey , JSON . stringify ( existingRaw ) , StorageScope . PROFILE , StorageTarget . MACHINE ) ;
653
+ this . trace ( 'transferChatSession' , `Transferred session ${ model . sessionId } to workspace ${ toWorkspace . toString ( ) } ` ) ;
654
+ }
595
655
}
0 commit comments