Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/sessionParser/claudeCodeSessionService.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
/**
7
* Claude Code Session Service
8
*
9
* This service provides access to Claude Code chat sessions using the
10
* `@anthropic-ai/claude-agent-sdk` session APIs. It handles:
11
* - Listing sessions via `listSessions()`
12
* - Loading full session content via `getSessionInfo()` + `getSessionMessages()`
13
* - Subagent loading via `listSubagents()` + `getSubagentMessages()`
14
*/
15
16
import type { CancellationToken } from 'vscode';
17
import { ILogService } from '../../../../../platform/log/common/logService';
18
import { IWorkspaceService } from '../../../../../platform/workspace/common/workspaceService';
19
import { createServiceIdentifier } from '../../../../../util/common/services';
20
import { basename } from '../../../../../util/vs/base/common/resources';
21
import { URI } from '../../../../../util/vs/base/common/uri';
22
import { IAgentSessionsWorkspace } from '../../../../chatSessions/common/agentSessionsWorkspace';
23
import { IFolderRepositoryManager } from '../../../../chatSessions/common/folderRepositoryManager';
24
import { ClaudeSessionUri } from '../../common/claudeSessionUri';
25
import { IClaudeCodeSdkService } from '../claudeCodeSdkService';
26
import { getProjectFolders } from '../claudeProjectFolders';
27
import {
28
IClaudeCodeSession,
29
IClaudeCodeSessionInfo,
30
ISubagentSession,
31
} from './claudeSessionSchema';
32
import { buildClaudeCodeSession, sdkSessionInfoToSessionInfo, sdkSubagentMessagesToSubagentSession } from './sdkSessionAdapter';
33
import { toErrorMessage } from '../../../../../util/common/errorMessage';
34
35
// #region Service Interface
36
37
export const IClaudeCodeSessionService = createServiceIdentifier<IClaudeCodeSessionService>('IClaudeCodeSessionService');
38
39
/**
40
* Service to load and manage Claude Code chat sessions.
41
*/
42
export interface IClaudeCodeSessionService {
43
readonly _serviceBrand: undefined;
44
45
/**
46
* Get lightweight metadata for all sessions in the current workspace.
47
* This is optimized for listing sessions without loading full content.
48
*/
49
getAllSessions(token: CancellationToken): Promise<readonly IClaudeCodeSessionInfo[]>;
50
51
/**
52
* Get a specific session with full content by its resource URI.
53
* This loads the complete message history and subagents.
54
*/
55
getSession(resource: URI, token: CancellationToken): Promise<IClaudeCodeSession | undefined>;
56
}
57
58
// #endregion
59
60
// #region Service Implementation
61
62
export class ClaudeCodeSessionService implements IClaudeCodeSessionService {
63
declare _serviceBrand: undefined;
64
65
constructor(
66
@IClaudeCodeSdkService private readonly _sdkService: IClaudeCodeSdkService,
67
@ILogService private readonly _logService: ILogService,
68
@IWorkspaceService private readonly _workspace: IWorkspaceService,
69
@IFolderRepositoryManager private readonly _folderRepositoryManager: IFolderRepositoryManager,
70
@IAgentSessionsWorkspace private readonly _agentSessionsWorkspace: IAgentSessionsWorkspace,
71
) { }
72
73
/**
74
* Get lightweight metadata for all sessions in the current workspace.
75
* Delegates to the SDK's `listSessions()` and converts results.
76
*/
77
async getAllSessions(token: CancellationToken): Promise<readonly IClaudeCodeSessionInfo[]> {
78
if (this._agentSessionsWorkspace.isAgentSessionsWorkspace) {
79
try {
80
const sdkSessions = await this._sdkService.listSessions();
81
return sdkSessions.map(sdkInfo => sdkSessionInfoToSessionInfo(sdkInfo));
82
} catch (e) {
83
this._logService.debug(`[ClaudeCodeSessionService] Failed to list all sessions: ${e}`);
84
return [];
85
}
86
}
87
88
const items: IClaudeCodeSessionInfo[] = [];
89
const projectFolders = await this._getProjectFolders();
90
91
for (const { slug, folderUri } of projectFolders) {
92
if (token.isCancellationRequested) {
93
return items;
94
}
95
96
const folderName = basename(folderUri);
97
98
try {
99
const sdkSessions = await this._sdkService.listSessions(folderUri.fsPath);
100
for (const sdkInfo of sdkSessions) {
101
items.push(sdkSessionInfoToSessionInfo(sdkInfo, folderName));
102
}
103
} catch (e) {
104
this._logService.debug(`[ClaudeCodeSessionService] Failed to list sessions for slug ${slug}: ${e}`);
105
}
106
}
107
108
return items;
109
}
110
111
/**
112
* Get a specific session with full content by its resource URI.
113
* Uses SDK APIs for metadata, messages, and subagent transcripts.
114
*/
115
async getSession(resource: URI, token: CancellationToken): Promise<IClaudeCodeSession | undefined> {
116
const sessionId = ClaudeSessionUri.getSessionId(resource);
117
118
if (this._agentSessionsWorkspace.isAgentSessionsWorkspace) {
119
try {
120
const info = await this._sdkService.getSessionInfo(sessionId);
121
if (!info) {
122
return undefined;
123
}
124
125
const messages = await this._sdkService.getSessionMessages(sessionId, info.cwd);
126
if (token.isCancellationRequested) {
127
return undefined;
128
}
129
130
const subagents = await this._loadSubagents(sessionId, info.cwd, token);
131
return buildClaudeCodeSession(info, messages, subagents);
132
} catch (e) {
133
this._logService.debug(`[ClaudeCodeSessionService] Failed to load session ${sessionId}: ${e}`);
134
return undefined;
135
}
136
}
137
138
const projectFolders = await this._getProjectFolders();
139
140
for (const { slug, folderUri } of projectFolders) {
141
if (token.isCancellationRequested) {
142
return undefined;
143
}
144
145
const dir = folderUri.fsPath;
146
147
try {
148
const info = await this._sdkService.getSessionInfo(sessionId, dir);
149
if (!info) {
150
continue;
151
}
152
153
const sessionDir = info.cwd ?? dir;
154
const messages = await this._sdkService.getSessionMessages(sessionId, sessionDir);
155
if (token.isCancellationRequested) {
156
return undefined;
157
}
158
159
const subagents = await this._loadSubagents(sessionId, sessionDir, token);
160
161
const folderName = basename(folderUri);
162
return buildClaudeCodeSession(info, messages, subagents, folderName);
163
} catch (e) {
164
this._logService.debug(`[ClaudeCodeSessionService] Failed to load session ${sessionId} from slug ${slug}: ${e}`);
165
}
166
}
167
168
return undefined;
169
}
170
171
// #region Directory Discovery
172
173
/**
174
* Get the project directory slugs to scan for sessions, along with their
175
* original folder URIs (needed for badge display).
176
*/
177
private _getProjectFolders() {
178
return getProjectFolders(this._workspace, this._folderRepositoryManager);
179
}
180
181
// #endregion
182
183
// #region Subagent Loading
184
185
private async _loadSubagents(
186
sessionId: string,
187
cwd: string | undefined,
188
token: CancellationToken,
189
): Promise<readonly ISubagentSession[]> {
190
let agentIds: string[];
191
try {
192
agentIds = await this._sdkService.listSubagents(sessionId, cwd ? { dir: cwd } : undefined);
193
} catch (error) {
194
this._logService.warn(`[ClaudeCodeSessionService] listSubagents failed: ${toErrorMessage(error)}`);
195
return [];
196
}
197
198
if (agentIds.length === 0 || token.isCancellationRequested) {
199
return [];
200
}
201
202
const results = await Promise.allSettled(
203
agentIds.map(agentId => this._loadSubagentFromSdk(sessionId, agentId, cwd))
204
);
205
206
if (token.isCancellationRequested) {
207
return [];
208
}
209
210
const subagents: ISubagentSession[] = [];
211
for (const r of results) {
212
if (r.status === 'fulfilled' && r.value !== null) {
213
subagents.push(r.value);
214
}
215
}
216
217
subagents.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
218
219
return subagents;
220
}
221
222
private async _loadSubagentFromSdk(
223
sessionId: string,
224
agentId: string,
225
cwd: string | undefined,
226
): Promise<ISubagentSession | null> {
227
try {
228
const messages = await this._sdkService.getSubagentMessages(sessionId, agentId, cwd ? { dir: cwd } : undefined);
229
return sdkSubagentMessagesToSubagentSession(agentId, messages);
230
} catch (error) {
231
this._logService.warn(`[ClaudeCodeSessionService] Failed to load subagent ${agentId} for session ${sessionId}: ${toErrorMessage(error)}`);
232
return null;
233
}
234
}
235
236
// #endregion
237
}
238
239
// #endregion
240
241