diff --git a/extensions/copilot/src/lib/node/chatLibMain.ts b/extensions/copilot/src/lib/node/chatLibMain.ts index 282f3ee98ae..a157b113fce 100644 --- a/extensions/copilot/src/lib/node/chatLibMain.ts +++ b/extensions/copilot/src/lib/node/chatLibMain.ts @@ -552,7 +552,7 @@ class SingleFetcherService implements IFetcherService { return this._fetcher.fetch(url, options); } createWebSocket(url: string, options?: WebSocketConnectOptions): WebSocketConnection { - return { webSocket: new WebSocket(url, options), responseHeaders: new HeadersImpl({}), responseStatusCode: undefined, responseStatusText: undefined }; + return { webSocket: new WebSocket(url, options), responseHeaders: new HeadersImpl({}), responseStatusCode: undefined, responseStatusText: undefined, networkError: undefined }; } disconnectAll(): Promise { return this._fetcher.disconnectAll(); diff --git a/extensions/copilot/src/platform/networking/common/fetcherService.ts b/extensions/copilot/src/platform/networking/common/fetcherService.ts index 2a3d0c0d3f0..ae22b33ad8d 100644 --- a/extensions/copilot/src/platform/networking/common/fetcherService.ts +++ b/extensions/copilot/src/platform/networking/common/fetcherService.ts @@ -187,6 +187,7 @@ export interface WebSocketConnection { readonly responseHeaders: IHeaders; readonly responseStatusCode: number | undefined; readonly responseStatusText: string | undefined; + readonly networkError: Error | undefined; } export interface IAbortSignal { diff --git a/extensions/copilot/src/platform/networking/node/chatWebSocketManager.ts b/extensions/copilot/src/platform/networking/node/chatWebSocketManager.ts index 26898518f25..de7ec2e551a 100644 --- a/extensions/copilot/src/platform/networking/node/chatWebSocketManager.ts +++ b/extensions/copilot/src/platform/networking/node/chatWebSocketManager.ts @@ -369,8 +369,10 @@ class ChatWebSocketConnection extends Disposable implements IChatWebSocketConnec this._responseStatusCode = connection.responseStatusCode; this._responseStatusText = connection.responseStatusText; const errorMessage = event.error ? `${event.message}: ${collectSingleLineErrorMessage(event.error)}` : event.message || 'WebSocket error'; + const networkError = event.error?.cause ?? connection.networkError; + const networkErrorMessage = networkError ? collectSingleLineErrorMessage(networkError) : undefined; const connectDurationMs = Date.now() - (this._connectStartTime ?? Date.now()); - this._logService.error(`[ChatWebSocketManager] Connection error for conversation ${this._conversationId}: ${errorMessage}`); + this._logService.error(`[ChatWebSocketManager] Connection error for conversation ${this._conversationId}: ${errorMessage}${networkErrorMessage ? ` (cause: ${networkErrorMessage})` : ''}`); ChatWebSocketTelemetrySender.sendConnectErrorTelemetry(this._telemetryService, { conversationId: this._conversationId, requestId: this.requestId, @@ -379,6 +381,7 @@ class ChatWebSocketConnection extends Disposable implements IChatWebSocketConnec connectDurationMs, responseStatusCode: this._responseStatusCode, responseStatusText: this._responseStatusText, + networkError: networkErrorMessage, }); reject(new Error(errorMessage)); }; diff --git a/extensions/copilot/src/platform/networking/node/chatWebSocketTelemetry.ts b/extensions/copilot/src/platform/networking/node/chatWebSocketTelemetry.ts index a73cdf46d65..fcb7cb0ef36 100644 --- a/extensions/copilot/src/platform/networking/node/chatWebSocketTelemetry.ts +++ b/extensions/copilot/src/platform/networking/node/chatWebSocketTelemetry.ts @@ -26,6 +26,7 @@ export interface IChatWebSocketConnectErrorTelemetryProperties extends IChatWebS connectDurationMs: number; responseStatusCode: number | undefined; responseStatusText: string | undefined; + networkError: string | undefined; } export interface IChatWebSocketCloseTelemetryProperties extends IChatWebSocketRequestTelemetryProperties { @@ -141,7 +142,8 @@ export class ChatWebSocketTelemetrySender { "error": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Error message for the failed connection" }, "connectDurationMs": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Time until the connection error in milliseconds", "isMeasurement": true }, "responseStatusCode": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "HTTP response status code from the failed connection attempt", "isMeasurement": true }, - "responseStatusText": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "HTTP response status text from the failed connection attempt" } + "responseStatusText": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "HTTP response status text from the failed connection attempt" }, + "networkError": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The underlying network error code and message from the dispatch layer" } } */ telemetryService.sendTelemetryErrorEvent('websocket.connectError', { github: true, microsoft: true }, { @@ -150,6 +152,7 @@ export class ChatWebSocketTelemetrySender { gitHubRequestId: properties.gitHubRequestId, error: properties.error, responseStatusText: properties.responseStatusText, + networkError: properties.networkError, }, { connectDurationMs: properties.connectDurationMs, responseStatusCode: properties.responseStatusCode, diff --git a/extensions/copilot/src/platform/networking/node/nodeFetchFetcher.ts b/extensions/copilot/src/platform/networking/node/nodeFetchFetcher.ts index 539b2f46528..fd0fc99410f 100644 --- a/extensions/copilot/src/platform/networking/node/nodeFetchFetcher.ts +++ b/extensions/copilot/src/platform/networking/node/nodeFetchFetcher.ts @@ -93,6 +93,9 @@ export function createWebSocket(url: string, options?: WebSocketConnectOptions): }, get responseStatusText() { return (webSocket as { responseStatusText?: string }).responseStatusText ?? responseStatusText; + }, + get networkError() { + return (webSocket as { networkError?: Error }).networkError ?? undefined; } }; } diff --git a/extensions/copilot/src/platform/networking/node/test/chatWebSocketManager.spec.ts b/extensions/copilot/src/platform/networking/node/test/chatWebSocketManager.spec.ts index caf1f5af91b..6780c035505 100644 --- a/extensions/copilot/src/platform/networking/node/test/chatWebSocketManager.spec.ts +++ b/extensions/copilot/src/platform/networking/node/test/chatWebSocketManager.spec.ts @@ -51,6 +51,7 @@ function createFakeCAPIClientService(ws: FakeWebSocket): ICAPIClientService { responseHeaders: new HeadersImpl({}), responseStatusCode: 101, responseStatusText: 'Switching Protocols', + networkError: undefined, } satisfies WebSocketConnection as unknown as WebSocketConnection), } as unknown as ICAPIClientService; } diff --git a/package-lock.json b/package-lock.json index dff4c7d96a0..9af8f0b3bd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", "@vscode/policy-watcher": "^1.3.2", - "@vscode/proxy-agent": "^0.40.0", + "@vscode/proxy-agent": "^0.41.0", "@vscode/ripgrep": "^1.17.1", "@vscode/spdlog": "^0.15.8", "@vscode/sqlite3": "5.1.12-vscode", @@ -3863,9 +3863,9 @@ } }, "node_modules/@vscode/proxy-agent": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.40.0.tgz", - "integrity": "sha512-G2OUy5b2vxYXoRWo38BwxBKW1GCjwno9tivcshJNBWkeHjwcidLkL6KFaVRgIDDxJjojPkoxy9AivTDU/ksJ6g==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.41.0.tgz", + "integrity": "sha512-xdjSPUu6DyC7+RBRftrj06OBG/xVLc0dsxhhwMzwfd9/pOGm8j4Zc70arq1jQb0s7EF4m9dAFoNjmSigfzN25A==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/package.json b/package.json index 3da91a3e90f..5229795d94d 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", "@vscode/policy-watcher": "^1.3.2", - "@vscode/proxy-agent": "^0.40.0", + "@vscode/proxy-agent": "^0.41.0", "@vscode/ripgrep": "^1.17.1", "@vscode/spdlog": "^0.15.8", "@vscode/sqlite3": "5.1.12-vscode", diff --git a/remote/package-lock.json b/remote/package-lock.json index 2e90f9f802f..88679db0d64 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -17,7 +17,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", - "@vscode/proxy-agent": "^0.40.0", + "@vscode/proxy-agent": "^0.41.0", "@vscode/ripgrep": "^1.17.1", "@vscode/spdlog": "^0.15.8", "@vscode/sqlite3": "5.1.12-vscode", @@ -622,9 +622,9 @@ "license": "MIT" }, "node_modules/@vscode/proxy-agent": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.40.0.tgz", - "integrity": "sha512-G2OUy5b2vxYXoRWo38BwxBKW1GCjwno9tivcshJNBWkeHjwcidLkL6KFaVRgIDDxJjojPkoxy9AivTDU/ksJ6g==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.41.0.tgz", + "integrity": "sha512-xdjSPUu6DyC7+RBRftrj06OBG/xVLc0dsxhhwMzwfd9/pOGm8j4Zc70arq1jQb0s7EF4m9dAFoNjmSigfzN25A==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/remote/package.json b/remote/package.json index f3d58daf8b1..89316ec375c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -12,7 +12,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", - "@vscode/proxy-agent": "^0.40.0", + "@vscode/proxy-agent": "^0.41.0", "@vscode/ripgrep": "^1.17.1", "@vscode/spdlog": "^0.15.8", "@vscode/sqlite3": "5.1.12-vscode",