Path: blob/main/extensions/copilot/src/extension/chronicle/node/cloudSessionStoreClient.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IAuthenticationService } from '../../../platform/authentication/common/authentication';6import { ICopilotTokenManager } from '../../../platform/authentication/common/copilotTokenManager';7import { INTEGRATION_ID } from '../../../platform/endpoint/common/licenseAgreement';8import { IFetcherService } from '../../../platform/networking/common/fetcherService';910/** Cloud query endpoint path. */11const QUERY_PATH = '/agents/analytics/query';1213/** Timeout for cloud query requests (ms). */14const REQUEST_TIMEOUT_MS = 30_000;1516/**17* Response format from the cloud query API.18* Data comes as columnar arrays, not row objects.19*/20interface CloudQueryResponse {21columns: string[];22column_types: string[];23data: unknown[][];24row_count: number;25truncated: boolean;26}2728/**29* Convert a columnar cloud response to an array of record objects.30*/31function columnarToRecords(response: CloudQueryResponse): Record<string, unknown>[] {32const { columns, data } = response;33if (!data || !columns) {34return [];35}36return data.map(row => {37const record: Record<string, unknown> = {};38for (let i = 0; i < columns.length; i++) {39record[columns[i]] = row[i];40}41return record;42});43}4445/**46* HTTP client for querying session data from the cloud.47*/48export class CloudSessionStoreClient {4950constructor(51private readonly _tokenManager: ICopilotTokenManager,52private readonly _authService: IAuthenticationService,53private readonly _fetcherService: IFetcherService,54) { }5556/**57* Execute a SQL query against the cloud session store (user-scoped).58* Returns an array of row objects on success, or undefined on failure.59*/60async executeQuery(sql: string): Promise<{ rows: Record<string, unknown>[]; truncated: boolean } | undefined> {61try {62const copilotToken = await this._tokenManager.getCopilotToken();63const baseUrl = copilotToken.endpoints?.api;64if (!baseUrl) {65return undefined;66}6768// The cloud endpoint expects a GitHub OAuth token,69// not the Copilot proxy token.70const githubToken = this._authService.anyGitHubSession?.accessToken;71const bearerToken = githubToken ?? copilotToken.token;7273const url = `${baseUrl.replace(/\/+$/, '')}${QUERY_PATH}`;7475const res = await this._fetcherService.fetch(url, {76callSite: 'chronicle.cloudQuery',77method: 'POST',78headers: {79'Authorization': `Bearer ${bearerToken}`,80'Copilot-Integration-Id': INTEGRATION_ID,81},82json: { query: sql },83timeout: REQUEST_TIMEOUT_MS,84});8586if (!res.ok) {87return undefined;88}8990const data = await res.json() as CloudQueryResponse;91const rows = columnarToRecords(data);92return { rows, truncated: data.truncated ?? false };93} catch (err) {94return undefined;95}96}97}9899100