Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chronicle/node/cloudSessionApiClient.ts
13399 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
import { IAuthenticationService } from '../../../platform/authentication/common/authentication';
7
import { ICopilotTokenManager } from '../../../platform/authentication/common/copilotTokenManager';
8
import { INTEGRATION_ID } from '../../../platform/endpoint/common/licenseAgreement';
9
import { IFetcherService } from '../../../platform/networking/common/fetcherService';
10
import type { CreateSessionFailureReason, CreateSessionResult, CloudSession, SessionEvent } from '../common/cloudSessionTypes';
11
12
/** Timeout for individual cloud API requests (ms). */
13
const REQUEST_TIMEOUT_MS = 10_000;
14
15
/** Cloud sessions endpoint path. */
16
const SESSIONS_PATH = '/agents/sessions';
17
18
/**
19
* HTTP client for the cloud session API.
20
*
21
* Creates sessions and submits event batches. All methods are non-blocking:
22
* failures are logged but never thrown to avoid disrupting the chat session.
23
*/
24
export class CloudSessionApiClient {
25
26
constructor(
27
private readonly _tokenManager: ICopilotTokenManager,
28
private readonly _authService: IAuthenticationService,
29
private readonly _fetcherService: IFetcherService,
30
) { }
31
32
/**
33
* Create a session in the cloud.
34
*
35
* The response includes both the session ID and the associated task ID.
36
*/
37
async createSession(
38
ownerId: number,
39
repoId: number,
40
sessionId: string,
41
indexingLevel: 'user' | 'repo_and_user' = 'user',
42
): Promise<CreateSessionResult> {
43
try {
44
const { url, headers } = await this._buildRequest(SESSIONS_PATH);
45
if (!url) {
46
return { ok: false, reason: 'error' };
47
}
48
49
const body = {
50
owner_id: ownerId,
51
repo_id: repoId,
52
agent_task_id: sessionId,
53
indexing_level: indexingLevel,
54
};
55
56
const res = await this._fetcherService.fetch(url, {
57
callSite: 'chronicle.cloudCreateSession',
58
method: 'POST',
59
headers,
60
json: body,
61
timeout: REQUEST_TIMEOUT_MS,
62
});
63
64
if (!res.ok) {
65
const reason: CreateSessionFailureReason = res.status === 403 ? 'policy_blocked' : 'error';
66
return { ok: false, reason };
67
}
68
69
const response = await res.json() as { id: string; task_id?: string; agent_task_id?: string };
70
return { ok: true, response };
71
} catch (err) {
72
return { ok: false, reason: 'error' };
73
}
74
}
75
76
/**
77
* Submit a batch of events to a session.
78
* @returns true if the submission succeeded.
79
*/
80
async submitSessionEvents(
81
sessionId: string,
82
events: SessionEvent[],
83
): Promise<boolean> {
84
try {
85
const { url, headers } = await this._buildRequest(`${SESSIONS_PATH}/${sessionId}/events`);
86
if (!url) {
87
return false;
88
}
89
90
const res = await this._fetcherService.fetch(url, {
91
callSite: 'chronicle.cloudSubmitEvents',
92
method: 'POST',
93
headers,
94
json: { events },
95
timeout: REQUEST_TIMEOUT_MS,
96
});
97
98
if (!res.ok) {
99
return false;
100
}
101
102
return true;
103
} catch (err) {
104
return false;
105
}
106
}
107
108
/**
109
* Get a session by ID (used for reattach verification).
110
*/
111
async getSession(sessionId: string): Promise<CloudSession | undefined> {
112
try {
113
const { url, headers } = await this._buildRequest(`${SESSIONS_PATH}/${sessionId}`);
114
if (!url) {
115
return undefined;
116
}
117
118
const res = await this._fetcherService.fetch(url, {
119
callSite: 'chronicle.cloudGetSession',
120
method: 'GET',
121
headers,
122
timeout: REQUEST_TIMEOUT_MS,
123
});
124
125
if (!res.ok) {
126
return undefined;
127
}
128
129
return (await res.json()) as CloudSession;
130
} catch {
131
return undefined;
132
}
133
}
134
135
/**
136
* Build the full URL and auth headers for a cloud API request.
137
*/
138
private async _buildRequest(path: string): Promise<{ url: string | undefined; headers: Record<string, string> }> {
139
try {
140
const copilotToken = await this._tokenManager.getCopilotToken();
141
const baseUrl = copilotToken.endpoints?.api;
142
if (!baseUrl) {
143
return { url: undefined, headers: {} };
144
}
145
146
// Prefer GitHub OAuth token, fallback to Copilot token
147
const githubToken = this._authService.anyGitHubSession?.accessToken;
148
const bearerToken = githubToken ?? copilotToken.token;
149
150
const url = `${baseUrl.replace(/\/+$/, '')}${path}`;
151
const headers: Record<string, string> = {
152
'Content-Type': 'application/json',
153
'Authorization': `Bearer ${bearerToken}`,
154
'Copilot-Integration-Id': INTEGRATION_ID,
155
};
156
157
return { url, headers };
158
} catch {
159
return { url: undefined, headers: {} };
160
}
161
}
162
}
163
164