@vscode/chat-lib 0.4.1-9 → 0.5.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/src/_internal/extension/byok/node/openAIEndpoint.d.ts +2 -1
  2. package/dist/src/_internal/extension/byok/node/openAIEndpoint.d.ts.map +1 -1
  3. package/dist/src/_internal/extension/byok/node/openAIEndpoint.js +6 -7
  4. package/dist/src/_internal/extension/byok/node/openAIEndpoint.js.map +1 -1
  5. package/dist/src/_internal/extension/common/constants.d.ts +0 -1
  6. package/dist/src/_internal/extension/common/constants.d.ts.map +1 -1
  7. package/dist/src/_internal/extension/common/constants.js.map +1 -1
  8. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/networking.d.ts.map +1 -1
  9. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/networking.js +6 -1
  10. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/networking.js.map +1 -1
  11. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/openai/model.d.ts +4 -0
  12. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/openai/model.d.ts.map +1 -1
  13. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/openai/model.js +4 -0
  14. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/openai/model.js.map +1 -1
  15. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/snippy/connectionState.d.ts.map +1 -1
  16. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/snippy/connectionState.js +1 -0
  17. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/snippy/connectionState.js.map +1 -1
  18. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/snippy/network.d.ts.map +1 -1
  19. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/snippy/network.js +1 -0
  20. package/dist/src/_internal/extension/completions-core/vscode-node/lib/src/snippy/network.js.map +1 -1
  21. package/dist/src/_internal/extension/inlineEdits/node/nextEditProvider.d.ts +9 -0
  22. package/dist/src/_internal/extension/inlineEdits/node/nextEditProvider.d.ts.map +1 -1
  23. package/dist/src/_internal/extension/inlineEdits/node/nextEditProvider.js +71 -11
  24. package/dist/src/_internal/extension/inlineEdits/node/nextEditProvider.js.map +1 -1
  25. package/dist/src/_internal/extension/inlineEdits/node/nextEditProviderTelemetry.d.ts.map +1 -1
  26. package/dist/src/_internal/extension/inlineEdits/node/nextEditProviderTelemetry.js +6 -3
  27. package/dist/src/_internal/extension/inlineEdits/node/nextEditProviderTelemetry.js.map +1 -1
  28. package/dist/src/_internal/extension/prompt/node/chatMLFetcher.d.ts +17 -1
  29. package/dist/src/_internal/extension/prompt/node/chatMLFetcher.d.ts.map +1 -1
  30. package/dist/src/_internal/extension/prompt/node/chatMLFetcher.js +373 -57
  31. package/dist/src/_internal/extension/prompt/node/chatMLFetcher.js.map +1 -1
  32. package/dist/src/_internal/extension/prompt/node/chatMLFetcherTelemetry.d.ts +6 -3
  33. package/dist/src/_internal/extension/prompt/node/chatMLFetcherTelemetry.d.ts.map +1 -1
  34. package/dist/src/_internal/extension/prompt/node/chatMLFetcherTelemetry.js +9 -3
  35. package/dist/src/_internal/extension/prompt/node/chatMLFetcherTelemetry.js.map +1 -1
  36. package/dist/src/_internal/extension/xtab/node/xtabEndpoint.d.ts +2 -1
  37. package/dist/src/_internal/extension/xtab/node/xtabEndpoint.d.ts.map +1 -1
  38. package/dist/src/_internal/extension/xtab/node/xtabEndpoint.js +6 -3
  39. package/dist/src/_internal/extension/xtab/node/xtabEndpoint.js.map +1 -1
  40. package/dist/src/_internal/extension/xtab/node/xtabNextCursorPredictor.d.ts.map +1 -1
  41. package/dist/src/_internal/extension/xtab/node/xtabNextCursorPredictor.js +1 -0
  42. package/dist/src/_internal/extension/xtab/node/xtabNextCursorPredictor.js.map +1 -1
  43. package/dist/src/_internal/extension/xtab/node/xtabProvider.d.ts +2 -0
  44. package/dist/src/_internal/extension/xtab/node/xtabProvider.d.ts.map +1 -1
  45. package/dist/src/_internal/extension/xtab/node/xtabProvider.js +24 -3
  46. package/dist/src/_internal/extension/xtab/node/xtabProvider.js.map +1 -1
  47. package/dist/src/_internal/platform/authentication/node/copilotTokenManager.d.ts.map +1 -1
  48. package/dist/src/_internal/platform/authentication/node/copilotTokenManager.js +3 -0
  49. package/dist/src/_internal/platform/authentication/node/copilotTokenManager.js.map +1 -1
  50. package/dist/src/_internal/platform/chat/common/commonTypes.d.ts +2 -1
  51. package/dist/src/_internal/platform/chat/common/commonTypes.d.ts.map +1 -1
  52. package/dist/src/_internal/platform/chat/common/commonTypes.js +61 -17
  53. package/dist/src/_internal/platform/chat/common/commonTypes.js.map +1 -1
  54. package/dist/src/_internal/platform/configuration/common/configurationService.d.ts +18 -36
  55. package/dist/src/_internal/platform/configuration/common/configurationService.d.ts.map +1 -1
  56. package/dist/src/_internal/platform/configuration/common/configurationService.js +37 -51
  57. package/dist/src/_internal/platform/configuration/common/configurationService.js.map +1 -1
  58. package/dist/src/_internal/platform/endpoint/common/capiClient.d.ts.map +1 -1
  59. package/dist/src/_internal/platform/endpoint/common/capiClient.js +1 -0
  60. package/dist/src/_internal/platform/endpoint/common/capiClient.js.map +1 -1
  61. package/dist/src/_internal/platform/endpoint/common/chatModelCapabilities.d.ts +2 -1
  62. package/dist/src/_internal/platform/endpoint/common/chatModelCapabilities.d.ts.map +1 -1
  63. package/dist/src/_internal/platform/endpoint/common/chatModelCapabilities.js +26 -7
  64. package/dist/src/_internal/platform/endpoint/common/chatModelCapabilities.js.map +1 -1
  65. package/dist/src/_internal/platform/endpoint/common/endpointProvider.d.ts +3 -1
  66. package/dist/src/_internal/platform/endpoint/common/endpointProvider.d.ts.map +1 -1
  67. package/dist/src/_internal/platform/endpoint/common/endpointProvider.js +1 -0
  68. package/dist/src/_internal/platform/endpoint/common/endpointProvider.js.map +1 -1
  69. package/dist/src/_internal/platform/endpoint/node/autoChatEndpoint.d.ts +2 -1
  70. package/dist/src/_internal/platform/endpoint/node/autoChatEndpoint.d.ts.map +1 -1
  71. package/dist/src/_internal/platform/endpoint/node/autoChatEndpoint.js +6 -3
  72. package/dist/src/_internal/platform/endpoint/node/autoChatEndpoint.js.map +1 -1
  73. package/dist/src/_internal/platform/endpoint/node/chatEndpoint.d.ts +6 -2
  74. package/dist/src/_internal/platform/endpoint/node/chatEndpoint.d.ts.map +1 -1
  75. package/dist/src/_internal/platform/endpoint/node/chatEndpoint.js +34 -12
  76. package/dist/src/_internal/platform/endpoint/node/chatEndpoint.js.map +1 -1
  77. package/dist/src/_internal/platform/endpoint/node/copilotChatEndpoint.d.ts +2 -1
  78. package/dist/src/_internal/platform/endpoint/node/copilotChatEndpoint.d.ts.map +1 -1
  79. package/dist/src/_internal/platform/endpoint/node/copilotChatEndpoint.js +5 -3
  80. package/dist/src/_internal/platform/endpoint/node/copilotChatEndpoint.js.map +1 -1
  81. package/dist/src/_internal/platform/endpoint/node/messagesApi.d.ts +1 -1
  82. package/dist/src/_internal/platform/endpoint/node/messagesApi.d.ts.map +1 -1
  83. package/dist/src/_internal/platform/endpoint/node/messagesApi.js +61 -7
  84. package/dist/src/_internal/platform/endpoint/node/messagesApi.js.map +1 -1
  85. package/dist/src/_internal/platform/endpoint/node/proxyXtabEndpoint.d.ts.map +1 -1
  86. package/dist/src/_internal/platform/endpoint/node/proxyXtabEndpoint.js +1 -0
  87. package/dist/src/_internal/platform/endpoint/node/proxyXtabEndpoint.js.map +1 -1
  88. package/dist/src/_internal/platform/endpoint/node/responsesApi.d.ts +1 -0
  89. package/dist/src/_internal/platform/endpoint/node/responsesApi.d.ts.map +1 -1
  90. package/dist/src/_internal/platform/endpoint/node/responsesApi.js +14 -10
  91. package/dist/src/_internal/platform/endpoint/node/responsesApi.js.map +1 -1
  92. package/dist/src/_internal/platform/git/common/gitService.d.ts +9 -1
  93. package/dist/src/_internal/platform/git/common/gitService.d.ts.map +1 -1
  94. package/dist/src/_internal/platform/git/common/gitService.js +17 -5
  95. package/dist/src/_internal/platform/git/common/gitService.js.map +1 -1
  96. package/dist/src/_internal/platform/github/common/githubAPI.d.ts +12 -2
  97. package/dist/src/_internal/platform/github/common/githubAPI.d.ts.map +1 -1
  98. package/dist/src/_internal/platform/github/common/githubAPI.js +23 -12
  99. package/dist/src/_internal/platform/github/common/githubAPI.js.map +1 -1
  100. package/dist/src/_internal/platform/github/common/githubService.d.ts +11 -1
  101. package/dist/src/_internal/platform/github/common/githubService.d.ts.map +1 -1
  102. package/dist/src/_internal/platform/github/common/githubService.js +52 -14
  103. package/dist/src/_internal/platform/github/common/githubService.js.map +1 -1
  104. package/dist/src/_internal/platform/github/common/nullOctokitServiceImpl.d.ts +5 -2
  105. package/dist/src/_internal/platform/github/common/nullOctokitServiceImpl.d.ts.map +1 -1
  106. package/dist/src/_internal/platform/github/common/nullOctokitServiceImpl.js +4 -1
  107. package/dist/src/_internal/platform/github/common/nullOctokitServiceImpl.js.map +1 -1
  108. package/dist/src/_internal/platform/inlineEdits/common/dataTypes/xtabPromptOptions.d.ts +1 -1
  109. package/dist/src/_internal/platform/inlineEdits/common/dataTypes/xtabPromptOptions.d.ts.map +1 -1
  110. package/dist/src/_internal/platform/inlineEdits/common/dataTypes/xtabPromptOptions.js +1 -1
  111. package/dist/src/_internal/platform/inlineEdits/common/dataTypes/xtabPromptOptions.js.map +1 -1
  112. package/dist/src/_internal/platform/inlineEdits/common/statelessNextEditProvider.d.ts +3 -0
  113. package/dist/src/_internal/platform/inlineEdits/common/statelessNextEditProvider.d.ts.map +1 -1
  114. package/dist/src/_internal/platform/inlineEdits/common/statelessNextEditProvider.js +5 -0
  115. package/dist/src/_internal/platform/inlineEdits/common/statelessNextEditProvider.js.map +1 -1
  116. package/dist/src/_internal/platform/nesFetch/node/completionsFetchServiceImpl.d.ts.map +1 -1
  117. package/dist/src/_internal/platform/nesFetch/node/completionsFetchServiceImpl.js +1 -0
  118. package/dist/src/_internal/platform/nesFetch/node/completionsFetchServiceImpl.js.map +1 -1
  119. package/dist/src/_internal/platform/networking/common/anthropic.d.ts +10 -11
  120. package/dist/src/_internal/platform/networking/common/anthropic.d.ts.map +1 -1
  121. package/dist/src/_internal/platform/networking/common/anthropic.js +28 -23
  122. package/dist/src/_internal/platform/networking/common/anthropic.js.map +1 -1
  123. package/dist/src/_internal/platform/networking/common/fetcherService.d.ts +29 -0
  124. package/dist/src/_internal/platform/networking/common/fetcherService.d.ts.map +1 -1
  125. package/dist/src/_internal/platform/networking/common/fetcherService.js +29 -1
  126. package/dist/src/_internal/platform/networking/common/fetcherService.js.map +1 -1
  127. package/dist/src/_internal/platform/networking/common/networking.d.ts +7 -0
  128. package/dist/src/_internal/platform/networking/common/networking.d.ts.map +1 -1
  129. package/dist/src/_internal/platform/networking/common/networking.js +5 -5
  130. package/dist/src/_internal/platform/networking/common/networking.js.map +1 -1
  131. package/dist/src/_internal/platform/networking/node/chatWebSocketManager.d.ts +83 -0
  132. package/dist/src/_internal/platform/networking/node/chatWebSocketManager.d.ts.map +1 -0
  133. package/dist/src/_internal/platform/networking/node/chatWebSocketManager.js +496 -0
  134. package/dist/src/_internal/platform/networking/node/chatWebSocketManager.js.map +1 -0
  135. package/dist/src/_internal/platform/networking/node/chatWebSocketTelemetry.d.ts +89 -0
  136. package/dist/src/_internal/platform/networking/node/chatWebSocketTelemetry.d.ts.map +1 -0
  137. package/dist/src/_internal/platform/networking/node/chatWebSocketTelemetry.js +269 -0
  138. package/dist/src/_internal/platform/networking/node/chatWebSocketTelemetry.js.map +1 -0
  139. package/dist/src/_internal/platform/otel/common/genAiAttributes.d.ts +104 -0
  140. package/dist/src/_internal/platform/otel/common/genAiAttributes.d.ts.map +1 -0
  141. package/dist/src/_internal/platform/otel/common/genAiAttributes.js +124 -0
  142. package/dist/src/_internal/platform/otel/common/genAiAttributes.js.map +1 -0
  143. package/dist/src/_internal/platform/otel/common/genAiEvents.d.ts +28 -0
  144. package/dist/src/_internal/platform/otel/common/genAiEvents.d.ts.map +1 -0
  145. package/dist/src/_internal/platform/otel/common/genAiEvents.js +91 -0
  146. package/dist/src/_internal/platform/otel/common/genAiEvents.js.map +1 -0
  147. package/dist/src/_internal/platform/otel/common/genAiMetrics.d.ts +30 -0
  148. package/dist/src/_internal/platform/otel/common/genAiMetrics.d.ts.map +1 -0
  149. package/dist/src/_internal/platform/otel/common/genAiMetrics.js +68 -0
  150. package/dist/src/_internal/platform/otel/common/genAiMetrics.js.map +1 -0
  151. package/dist/src/_internal/platform/otel/common/index.d.ts +8 -0
  152. package/dist/src/_internal/platform/otel/common/index.d.ts.map +1 -0
  153. package/dist/src/_internal/platform/otel/common/index.js +35 -0
  154. package/dist/src/_internal/platform/otel/common/index.js.map +1 -0
  155. package/dist/src/_internal/platform/otel/common/messageFormatters.d.ts +85 -0
  156. package/dist/src/_internal/platform/otel/common/messageFormatters.d.ts.map +1 -0
  157. package/dist/src/_internal/platform/otel/common/messageFormatters.js +122 -0
  158. package/dist/src/_internal/platform/otel/common/messageFormatters.js.map +1 -0
  159. package/dist/src/_internal/platform/otel/common/noopOtelService.d.ts +26 -0
  160. package/dist/src/_internal/platform/otel/common/noopOtelService.d.ts.map +1 -0
  161. package/dist/src/_internal/platform/otel/common/noopOtelService.js +51 -0
  162. package/dist/src/_internal/platform/otel/common/noopOtelService.js.map +1 -0
  163. package/dist/src/_internal/platform/otel/common/otelConfig.d.ts +35 -0
  164. package/dist/src/_internal/platform/otel/common/otelConfig.d.ts.map +1 -0
  165. package/dist/src/_internal/platform/otel/common/otelConfig.js +140 -0
  166. package/dist/src/_internal/platform/otel/common/otelConfig.js.map +1 -0
  167. package/dist/src/_internal/platform/otel/common/otelService.d.ts +160 -0
  168. package/dist/src/_internal/platform/otel/common/otelService.d.ts.map +1 -0
  169. package/dist/src/_internal/platform/otel/common/otelService.js +10 -0
  170. package/dist/src/_internal/platform/otel/common/otelService.js.map +1 -0
  171. package/dist/src/_internal/platform/proxyModels/node/proxyModelsService.d.ts.map +1 -1
  172. package/dist/src/_internal/platform/proxyModels/node/proxyModelsService.js +1 -0
  173. package/dist/src/_internal/platform/proxyModels/node/proxyModelsService.js.map +1 -1
  174. package/dist/src/main.d.ts.map +1 -1
  175. package/dist/src/main.js +10 -0
  176. package/dist/src/main.js.map +1 -1
  177. package/dist/src/package.json +267 -91
  178. package/package.json +4 -3
@@ -12,6 +12,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
12
12
  var __param = (this && this.__param) || function (paramIndex, decorator) {
13
13
  return function (target, key) { decorator(target, key, paramIndex); }
14
14
  };
15
+ var ChatMLFetcherImpl_1;
15
16
  Object.defineProperty(exports, "__esModule", { value: true });
16
17
  exports.ChatMLFetcherImpl = exports.AbstractChatMLFetcher = void 0;
17
18
  exports.createTelemetryData = createTelemetryData;
@@ -35,8 +36,11 @@ const fetcherService_1 = require("../../../platform/networking/common/fetcherSer
35
36
  const networking_1 = require("../../../platform/networking/common/networking");
36
37
  const openai_1 = require("../../../platform/networking/common/openai");
37
38
  const chatStream_1 = require("../../../platform/networking/node/chatStream");
39
+ const chatWebSocketManager_1 = require("../../../platform/networking/node/chatWebSocketManager");
38
40
  const stream_1 = require("../../../platform/networking/node/stream");
39
41
  const fetch_2 = require("../../../platform/openai/node/fetch");
42
+ const index_1 = require("../../../platform/otel/common/index");
43
+ const otelService_1 = require("../../../platform/otel/common/otelService");
40
44
  const requestLogger_1 = require("../../../platform/requestLogger/node/requestLogger");
41
45
  const nullExperimentationService_1 = require("../../../platform/telemetry/common/nullExperimentationService");
42
46
  const telemetry_1 = require("../../../platform/telemetry/common/telemetry");
@@ -83,7 +87,9 @@ class AbstractChatMLFetcher extends lifecycle_1.Disposable {
83
87
  }
84
88
  exports.AbstractChatMLFetcher = AbstractChatMLFetcher;
85
89
  let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
86
- constructor(_fetcherService, _telemetryService, _requestLogger, _logService, _authenticationService, _interactionService, _chatQuotaService, _capiClientService, options, _configurationService, _experimentationService, _powerService, _instantiationService) {
90
+ static { ChatMLFetcherImpl_1 = this; }
91
+ static { this._maxConsecutiveWebSocketFallbacks = 3; }
92
+ constructor(_fetcherService, _telemetryService, _requestLogger, _logService, _authenticationService, _interactionService, _chatQuotaService, _capiClientService, options, _configurationService, _experimentationService, _powerService, _instantiationService, _webSocketManager, _otelService) {
87
93
  super(options);
88
94
  this._fetcherService = _fetcherService;
89
95
  this._telemetryService = _telemetryService;
@@ -97,23 +103,36 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
97
103
  this._experimentationService = _experimentationService;
98
104
  this._powerService = _powerService;
99
105
  this._instantiationService = _instantiationService;
106
+ this._webSocketManager = _webSocketManager;
107
+ this._otelService = _otelService;
100
108
  /**
101
109
  * Delays (in ms) between connectivity check attempts before retrying a failed request.
102
110
  * Configurable for testing purposes.
103
111
  */
104
112
  this.connectivityCheckDelays = [1000, 10000, 10000];
113
+ /**
114
+ * Tracks consecutive WebSocket request failures where the HTTP retry succeeded.
115
+ * After {@link _maxConsecutiveWebSocketFallbacks} such failures, WebSocket requests are disabled entirely.
116
+ */
117
+ this._consecutiveWebSocketRetryFallbacks = 0;
105
118
  }
106
119
  /**
107
120
  * Note: the returned array of strings may be less than `n` (e.g., in case there were errors during streaming)
108
121
  */
109
122
  async fetchMany(opts, token) {
110
- let { debugName, endpoint: chatEndpoint, finishedCb, location, messages, requestOptions, source, telemetryProperties, userInitiatedRequest, requestKindOptions } = opts;
123
+ let { debugName, endpoint: chatEndpoint, finishedCb, location, messages, requestOptions, source, telemetryProperties, userInitiatedRequest, requestKindOptions, conversationId, turnId, useWebSocket, ignoreStatefulMarker } = opts;
124
+ if (useWebSocket && this._consecutiveWebSocketRetryFallbacks >= ChatMLFetcherImpl_1._maxConsecutiveWebSocketFallbacks) {
125
+ this._logService.debug(`[ChatWebSocketManager] Disabling WebSocket for request due to ${this._consecutiveWebSocketRetryFallbacks} consecutive WebSocket failures with successful HTTP fallback.`);
126
+ useWebSocket = false;
127
+ ignoreStatefulMarker = true;
128
+ }
111
129
  if (!telemetryProperties) {
112
130
  telemetryProperties = {};
113
131
  }
114
132
  if (!telemetryProperties.messageSource) {
115
133
  telemetryProperties.messageSource = debugName;
116
134
  }
135
+ const transport = useWebSocket ? 'websocket' : 'http';
117
136
  // TODO @lramos15 telemetry should not drive request ids
118
137
  const ourRequestId = telemetryProperties.requestId ?? telemetryProperties.messageId ?? (0, uuid_1.generateUuid)();
119
138
  const maxResponseTokens = chatEndpoint.maxOutputTokens;
@@ -127,6 +146,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
127
146
  const postOptions = this.preparePostOptions(requestOptions);
128
147
  const requestBody = chatEndpoint.createRequestBody({
129
148
  ...opts,
149
+ ignoreStatefulMarker,
130
150
  requestId: ourRequestId,
131
151
  postOptions
132
152
  });
@@ -141,7 +161,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
141
161
  ourRequestId,
142
162
  location: opts.location,
143
163
  body: requestBody,
144
- ignoreStatefulMarker: opts.ignoreStatefulMarker,
164
+ ignoreStatefulMarker,
145
165
  isConversationRequest: opts.isConversationRequest,
146
166
  customMetadata: opts.customMetadata
147
167
  });
@@ -155,6 +175,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
155
175
  let actualStatusCode;
156
176
  let suspendEventSeen;
157
177
  let resumeEventSeen;
178
+ let otelInferenceSpan;
158
179
  try {
159
180
  let response;
160
181
  const payloadValidationResult = isValidChatPayload(opts.messages, postOptions, chatEndpoint, this._configurationService, this._experimentationService);
@@ -169,13 +190,38 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
169
190
  else {
170
191
  const copilotToken = await this._authenticationService.getCopilotToken();
171
192
  usernameToScrub = copilotToken.username;
172
- const fetchResult = await this._fetchAndStreamChat(chatEndpoint, requestBody, baseTelemetry, streamRecorder.callback, requestOptions.secretKey, copilotToken, opts.location, ourRequestId, postOptions.n, token, userInitiatedRequest, telemetryProperties, opts.useFetcher, canRetryOnce, requestKindOptions);
193
+ const fetchResult = await this._fetchAndStreamChat(chatEndpoint, requestBody, baseTelemetry, streamRecorder.callback, requestOptions.secretKey, copilotToken, opts.location, ourRequestId, postOptions.n, token, userInitiatedRequest, useWebSocket, turnId, conversationId, telemetryProperties, opts.useFetcher, canRetryOnce, requestKindOptions);
173
194
  response = fetchResult.result;
174
195
  actualFetcher = fetchResult.fetcher;
175
196
  actualBytesReceived = fetchResult.bytesReceived;
176
197
  actualStatusCode = fetchResult.statusCode;
177
198
  suspendEventSeen = fetchResult.suspendEventSeen;
178
199
  resumeEventSeen = fetchResult.resumeEventSeen;
200
+ otelInferenceSpan = fetchResult.otelSpan;
201
+ // Tag span with debug name so orphaned spans (title, progressMessages, etc.) are identifiable
202
+ otelInferenceSpan?.setAttribute(index_1.GenAiAttr.AGENT_NAME, debugName);
203
+ // Extract and set structured prompt sections for the debug panel
204
+ if (otelInferenceSpan) {
205
+ const capiMessages = requestBody.messages;
206
+ // User request: last user-role message
207
+ const userMessages = capiMessages?.filter(m => m.role === 'user');
208
+ const lastUserMsg = userMessages?.[userMessages.length - 1];
209
+ if (lastUserMsg?.content) {
210
+ otelInferenceSpan.setAttribute(index_1.CopilotChatAttr.USER_REQUEST, lastUserMsg.content);
211
+ }
212
+ // System instructions (first system message) — raw text for debug panel sections
213
+ const systemMsg = capiMessages?.find(m => m.role === 'system');
214
+ if (systemMsg?.content) {
215
+ otelInferenceSpan.setAttribute(index_1.GenAiAttr.SYSTEM_INSTRUCTIONS, systemMsg.content);
216
+ }
217
+ }
218
+ // Always capture full request content for the debug panel
219
+ if (otelInferenceSpan) {
220
+ const capiMessages = requestBody.messages;
221
+ if (capiMessages) {
222
+ otelInferenceSpan.setAttribute(index_1.GenAiAttr.INPUT_MESSAGES, (0, index_1.truncateForOTel)(JSON.stringify((0, index_1.toInputMessages)(capiMessages))));
223
+ }
224
+ }
179
225
  tokenCount = await chatEndpoint.acquireTokenizer().countMessagesTokens(messages);
180
226
  const extensionId = source?.extensionId ?? constants_1.EXTENSION_ID;
181
227
  this._onDidMakeChatMLRequest.fire({
@@ -189,7 +235,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
189
235
  pendingLoggedChatRequest?.markTimeToFirstToken(timeToFirstToken);
190
236
  switch (response.type) {
191
237
  case fetch_2.FetchResponseKind.Success: {
192
- const result = await this.processSuccessfulResponse(response, messages, requestBody, ourRequestId, maxResponseTokens, tokenCount, timeToFirstToken, streamRecorder, baseTelemetry, chatEndpoint, userInitiatedRequest, actualFetcher, actualBytesReceived, suspendEventSeen, resumeEventSeen);
238
+ const result = await this.processSuccessfulResponse(response, messages, requestBody, ourRequestId, maxResponseTokens, tokenCount, timeToFirstToken, streamRecorder, baseTelemetry, chatEndpoint, userInitiatedRequest, transport, actualFetcher, actualBytesReceived, suspendEventSeen, resumeEventSeen);
193
239
  // Handle FilteredRetry case with augmented messages
194
240
  if (result.type === commonTypes_1.ChatFetchResponseType.FilteredRetry) {
195
241
  if (opts.enableRetryOnFilter) {
@@ -237,6 +283,76 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
237
283
  };
238
284
  }
239
285
  pendingLoggedChatRequest?.resolve(result, streamRecorder.deltas);
286
+ // Record OTel token usage metrics if available
287
+ if (result.type === commonTypes_1.ChatFetchResponseType.Success && result.usage) {
288
+ const metricAttrs = {
289
+ operationName: index_1.GenAiOperationName.CHAT,
290
+ providerName: index_1.GenAiProviderName.GITHUB,
291
+ requestModel: chatEndpoint.model,
292
+ responseModel: result.resolvedModel,
293
+ };
294
+ if (result.usage.prompt_tokens) {
295
+ index_1.GenAiMetrics.recordTokenUsage(this._otelService, result.usage.prompt_tokens, 'input', metricAttrs);
296
+ }
297
+ if (result.usage.completion_tokens) {
298
+ index_1.GenAiMetrics.recordTokenUsage(this._otelService, result.usage.completion_tokens, 'output', metricAttrs);
299
+ }
300
+ // Set token usage and response details on the chat span before ending it
301
+ otelInferenceSpan?.setAttributes({
302
+ [index_1.GenAiAttr.USAGE_INPUT_TOKENS]: result.usage.prompt_tokens ?? 0,
303
+ [index_1.GenAiAttr.USAGE_OUTPUT_TOKENS]: result.usage.completion_tokens ?? 0,
304
+ [index_1.GenAiAttr.RESPONSE_MODEL]: result.resolvedModel ?? chatEndpoint.model,
305
+ [index_1.GenAiAttr.RESPONSE_ID]: result.requestId,
306
+ [index_1.GenAiAttr.RESPONSE_FINISH_REASONS]: ['stop'],
307
+ ...(result.usage.prompt_tokens_details?.cached_tokens
308
+ ? { [index_1.GenAiAttr.USAGE_CACHE_READ_INPUT_TOKENS]: result.usage.prompt_tokens_details.cached_tokens }
309
+ : {}),
310
+ [index_1.CopilotChatAttr.TIME_TO_FIRST_TOKEN]: timeToFirstToken,
311
+ ...(result.serverRequestId ? { [index_1.CopilotChatAttr.SERVER_REQUEST_ID]: result.serverRequestId } : {}),
312
+ ...(result.usage.completion_tokens_details?.reasoning_tokens
313
+ ? { [index_1.GenAiAttr.USAGE_REASONING_TOKENS]: result.usage.completion_tokens_details.reasoning_tokens }
314
+ : {}),
315
+ });
316
+ }
317
+ // Always capture response content for the debug panel
318
+ if (otelInferenceSpan && result.type === commonTypes_1.ChatFetchResponseType.Success) {
319
+ const responseText = streamRecorder.deltas.map(d => d.text).join('');
320
+ const toolCalls = streamRecorder.deltas
321
+ .filter(d => d.copilotToolCalls?.length)
322
+ .flatMap(d => d.copilotToolCalls.map(tc => ({
323
+ type: 'tool_call', id: tc.id, name: tc.name, arguments: tc.arguments
324
+ })));
325
+ const parts = [];
326
+ if (responseText) {
327
+ parts.push({ type: 'text', content: responseText });
328
+ }
329
+ parts.push(...toolCalls);
330
+ if (parts.length > 0) {
331
+ otelInferenceSpan.setAttribute(index_1.GenAiAttr.OUTPUT_MESSAGES, (0, index_1.truncateForOTel)(JSON.stringify([{ role: 'assistant', parts }])));
332
+ }
333
+ }
334
+ // Emit OTel inference details event BEFORE ending the span
335
+ // so the log record inherits the active trace context
336
+ (0, index_1.emitInferenceDetailsEvent)(this._otelService, {
337
+ model: chatEndpoint.model,
338
+ temperature: requestOptions?.temperature,
339
+ maxTokens: requestOptions?.max_tokens,
340
+ }, result.type === commonTypes_1.ChatFetchResponseType.Success ? {
341
+ id: result.requestId,
342
+ model: result.resolvedModel,
343
+ finishReasons: ['stop'],
344
+ inputTokens: result.usage?.prompt_tokens,
345
+ outputTokens: result.usage?.completion_tokens,
346
+ } : undefined);
347
+ otelInferenceSpan?.end();
348
+ otelInferenceSpan = undefined;
349
+ // Record OTel time-to-first-token metric
350
+ if (timeToFirstToken > 0) {
351
+ index_1.GenAiMetrics.recordTimeToFirstToken(this._otelService, chatEndpoint.model, timeToFirstToken / 1000);
352
+ }
353
+ if (useWebSocket && result.type === commonTypes_1.ChatFetchResponseType.Success) {
354
+ this._consecutiveWebSocketRetryFallbacks = 0;
355
+ }
240
356
  return result;
241
357
  }
242
358
  case fetch_2.FetchResponseKind.Canceled:
@@ -245,6 +361,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
245
361
  requestId: ourRequestId,
246
362
  model: chatEndpoint.model,
247
363
  apiType: chatEndpoint.apiType,
364
+ transport,
248
365
  associatedRequestId: telemetryProperties.associatedRequestId,
249
366
  retryAfterError: telemetryProperties.retryAfterError,
250
367
  retryAfterErrorGitHubRequestId: telemetryProperties.retryAfterErrorGitHubRequestId,
@@ -268,6 +385,13 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
268
385
  issuedTime: baseTelemetry.issuedTime,
269
386
  });
270
387
  pendingLoggedChatRequest?.resolveWithCancelation();
388
+ // Set canceled status on OTel span
389
+ otelInferenceSpan?.setAttributes({
390
+ [index_1.GenAiAttr.RESPONSE_FINISH_REASONS]: ['cancelled'],
391
+ [index_1.CopilotChatAttr.CANCELED]: true,
392
+ });
393
+ otelInferenceSpan?.end();
394
+ otelInferenceSpan = undefined;
271
395
  return this.processCanceledResponse(response, ourRequestId, streamRecorder, telemetryProperties);
272
396
  case fetch_2.FetchResponseKind.Failed: {
273
397
  const processed = this.processFailedResponse(response, ourRequestId);
@@ -276,7 +400,9 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
276
400
  const statusCodesToRetry = retryServerErrorStatusCodes
277
401
  .split(',')
278
402
  .map(s => parseInt(s.trim(), 10));
279
- if (enableRetryOnError && actualStatusCode !== undefined && statusCodesToRetry.includes(actualStatusCode)) {
403
+ const retryAfterServerError = enableRetryOnError && actualStatusCode !== undefined && statusCodesToRetry.includes(actualStatusCode);
404
+ const retryWithoutWebSocket = enableRetryOnError && useWebSocket;
405
+ if (retryAfterServerError || retryWithoutWebSocket) {
280
406
  const { retryResult } = await this._retryAfterError({
281
407
  opts,
282
408
  processed,
@@ -285,6 +411,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
285
411
  tokenCount,
286
412
  maxResponseTokens,
287
413
  timeToError: timeToFirstToken,
414
+ transport,
288
415
  actualFetcher,
289
416
  bytesReceived: actualBytesReceived,
290
417
  baseTelemetry,
@@ -310,6 +437,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
310
437
  maxResponseTokens,
311
438
  timeToFirstToken,
312
439
  isVisionRequest: this.filterImageMessages(messages),
440
+ transport,
313
441
  fetcher: actualFetcher,
314
442
  bytesReceived: actualBytesReceived,
315
443
  issuedTime: baseTelemetry.issuedTime,
@@ -323,6 +451,14 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
323
451
  }
324
452
  }
325
453
  catch (err) {
454
+ // End OTel inference span on error if not already ended
455
+ if (otelInferenceSpan) {
456
+ otelInferenceSpan.setStatus(2 /* SpanStatusCode.ERROR */, err instanceof Error ? err.message : String(err));
457
+ otelInferenceSpan.setAttribute(index_1.StdAttr.ERROR_TYPE, err instanceof Error ? err.constructor.name : 'Error');
458
+ otelInferenceSpan.setAttribute(index_1.GenAiAttr.RESPONSE_FINISH_REASONS, ['error']);
459
+ otelInferenceSpan.recordException(err);
460
+ otelInferenceSpan.end();
461
+ }
326
462
  const timeToError = Date.now() - baseTelemetry.issuedTime;
327
463
  if (err.fetcherId) {
328
464
  actualFetcher = err.fetcherId;
@@ -334,34 +470,34 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
334
470
  resumeEventSeen = true;
335
471
  }
336
472
  const processed = this.processError(err, ourRequestId, err.gitHubRequestId, usernameToScrub);
337
- if (processed.type === commonTypes_1.ChatFetchResponseType.NetworkError && enableRetryOnError) {
338
- const isRetryNetworkErrorEnabled = this._configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.RetryNetworkErrors, this._experimentationService);
339
- if (isRetryNetworkErrorEnabled) {
340
- const { retryResult, connectivityTestError, connectivityTestErrorGitHubRequestId } = await this._retryAfterError({
341
- opts,
342
- processed,
343
- telemetryProperties,
344
- requestBody,
345
- tokenCount,
346
- maxResponseTokens,
347
- timeToError,
348
- actualFetcher,
349
- bytesReceived: err.bytesReceived,
350
- baseTelemetry,
351
- streamRecorder,
352
- retryReason: 'network_error',
353
- debugNamePrefix: 'retry-error-',
354
- pendingLoggedChatRequest,
355
- token,
356
- usernameToScrub,
357
- suspendEventSeen,
358
- resumeEventSeen,
359
- });
360
- if (retryResult) {
361
- return retryResult;
362
- }
363
- telemetryProperties = { ...telemetryProperties, connectivityTestError, connectivityTestErrorGitHubRequestId };
473
+ const retryNetworkError = enableRetryOnError && processed.type === commonTypes_1.ChatFetchResponseType.NetworkError && this._configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.RetryNetworkErrors, this._experimentationService);
474
+ const retryWithoutWebSocket = enableRetryOnError && useWebSocket && (processed.type === commonTypes_1.ChatFetchResponseType.NetworkError || processed.type === commonTypes_1.ChatFetchResponseType.Failed);
475
+ if (retryNetworkError || retryWithoutWebSocket) {
476
+ const { retryResult, connectivityTestError, connectivityTestErrorGitHubRequestId } = await this._retryAfterError({
477
+ opts,
478
+ processed,
479
+ telemetryProperties,
480
+ requestBody,
481
+ tokenCount,
482
+ maxResponseTokens,
483
+ timeToError,
484
+ transport,
485
+ actualFetcher,
486
+ bytesReceived: err.bytesReceived,
487
+ baseTelemetry,
488
+ streamRecorder,
489
+ retryReason: 'network_error',
490
+ debugNamePrefix: 'retry-error-',
491
+ pendingLoggedChatRequest,
492
+ token,
493
+ usernameToScrub,
494
+ suspendEventSeen,
495
+ resumeEventSeen,
496
+ });
497
+ if (retryResult) {
498
+ return retryResult;
364
499
  }
500
+ telemetryProperties = { ...telemetryProperties, connectivityTestError, connectivityTestErrorGitHubRequestId };
365
501
  }
366
502
  if (processed.type === commonTypes_1.ChatFetchResponseType.Canceled) {
367
503
  chatMLFetcherTelemetry_1.ChatMLFetcherTelemetrySender.sendCancellationTelemetry(this._telemetryService, {
@@ -369,6 +505,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
369
505
  requestId: ourRequestId,
370
506
  model: chatEndpoint.model,
371
507
  apiType: chatEndpoint.apiType,
508
+ transport,
372
509
  associatedRequestId: telemetryProperties.associatedRequestId,
373
510
  retryAfterError: telemetryProperties.retryAfterError,
374
511
  retryAfterErrorGitHubRequestId: telemetryProperties.retryAfterErrorGitHubRequestId,
@@ -401,6 +538,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
401
538
  maxResponseTokens,
402
539
  timeToFirstToken: timeToError,
403
540
  isVisionRequest: this.filterImageMessages(messages),
541
+ transport,
404
542
  fetcher: actualFetcher,
405
543
  bytesReceived: err.bytesReceived,
406
544
  issuedTime: baseTelemetry.issuedTime,
@@ -428,6 +566,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
428
566
  const res = await this._fetcherService.fetch(url, {
429
567
  headers,
430
568
  useFetcher,
569
+ callSite: 'capi-ping',
431
570
  });
432
571
  if (res.status >= 200 && res.status < 300) {
433
572
  this._logService.info(`CAPI ping successful, proceeding with chat request retry...`);
@@ -468,7 +607,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
468
607
  return authHeaders;
469
608
  }
470
609
  async _retryAfterError(params) {
471
- const { opts, processed, telemetryProperties, requestBody, tokenCount, maxResponseTokens, timeToError, actualFetcher, bytesReceived, baseTelemetry, streamRecorder, retryReason, debugNamePrefix, pendingLoggedChatRequest, token, usernameToScrub, suspendEventSeen, resumeEventSeen, } = params;
610
+ const { opts, processed, telemetryProperties, requestBody, tokenCount, maxResponseTokens, timeToError, transport, actualFetcher, bytesReceived, baseTelemetry, streamRecorder, retryReason, debugNamePrefix, pendingLoggedChatRequest, token, usernameToScrub, suspendEventSeen, resumeEventSeen, } = params;
472
611
  // net::ERR_NETWORK_CHANGED: https://github.com/microsoft/vscode/issues/260297
473
612
  const isNetworkChangedError = ['darwin', 'linux'].includes(process.platform) && processed.reason.indexOf('net::ERR_NETWORK_CHANGED') !== -1;
474
613
  // When Electron's network process crashes, all requests through it fail permanently.
@@ -495,6 +634,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
495
634
  maxResponseTokens,
496
635
  timeToFirstToken: timeToError,
497
636
  isVisionRequest: this.filterImageMessages(opts.messages),
637
+ transport,
498
638
  fetcher: actualFetcher,
499
639
  bytesReceived,
500
640
  issuedTime: baseTelemetry.issuedTime,
@@ -505,6 +645,8 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
505
645
  streamRecorder.callback('', 0, { text: '', retryReason });
506
646
  const retryResult = await this.fetchMany({
507
647
  ...opts,
648
+ useWebSocket: false,
649
+ ignoreStatefulMarker: opts.useWebSocket || opts.ignoreStatefulMarker,
508
650
  debugName: debugNamePrefix + opts.debugName,
509
651
  userInitiatedRequest: false, // do not mark the retry as user initiated
510
652
  telemetryProperties: {
@@ -518,9 +660,17 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
518
660
  useFetcher,
519
661
  }, token);
520
662
  pendingLoggedChatRequest?.resolve(retryResult, streamRecorder.deltas);
663
+ if (opts.useWebSocket && retryResult.type === commonTypes_1.ChatFetchResponseType.Success) {
664
+ this._consecutiveWebSocketRetryFallbacks++;
665
+ this._logService.info(`[ChatWebSocketManager] WebSocket request failed with successful HTTP fallback (${this._consecutiveWebSocketRetryFallbacks} consecutive).`);
666
+ if (opts.conversationId && opts.turnId) {
667
+ // Closing here because the retry is transparent.
668
+ this._webSocketManager.closeConnection(opts.conversationId, opts.turnId);
669
+ }
670
+ }
521
671
  return { retryResult, connectivityTestError, connectivityTestErrorGitHubRequestId };
522
672
  }
523
- async _fetchAndStreamChat(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, copilotToken, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions) {
673
+ async _fetchAndStreamChat(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, copilotToken, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, useWebSocket, turnId, conversationId, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions) {
524
674
  const isPowerSaveBlockerEnabled = this._configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.ChatRequestPowerSaveBlocker, this._experimentationService);
525
675
  const blockerHandle = isPowerSaveBlockerEnabled && location !== commonTypes_1.ChatLocation.Other ? this._powerService.acquirePowerSaveBlocker() : undefined;
526
676
  let suspendEventSeen = false;
@@ -534,7 +684,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
534
684
  this._logService.info(`System resumed during streaming request ${ourRequestId} (${commonTypes_1.ChatLocation.toString(location)})`);
535
685
  });
536
686
  try {
537
- const fetchResult = await this._doFetchAndStreamChat(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, copilotToken, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions);
687
+ const fetchResult = await this._doFetchAndStreamChat(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, copilotToken, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, useWebSocket, turnId, conversationId, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions);
538
688
  return { ...fetchResult, suspendEventSeen: suspendEventSeen || undefined, resumeEventSeen: resumeEventSeen || undefined };
539
689
  }
540
690
  catch (err) {
@@ -552,28 +702,190 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
552
702
  blockerHandle?.dispose();
553
703
  }
554
704
  }
555
- async _doFetchAndStreamChat(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, copilotToken, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions) {
705
+ async _doFetchAndStreamChat(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, copilotToken, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, useWebSocket, turnId, conversationId, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions) {
556
706
  if (cancellationToken.isCancellationRequested) {
557
707
  return { result: { type: fetch_2.FetchResponseKind.Canceled, reason: 'before fetch request' } };
558
708
  }
559
- this._logService.debug(`modelMaxPromptTokens ${chatEndpointInfo.modelMaxPromptTokens}`);
560
- this._logService.debug(`modelMaxResponseTokens ${request.max_tokens ?? 2048}`);
561
- this._logService.debug(`chat model ${chatEndpointInfo.model}`);
562
- secretKey ??= copilotToken.token;
563
- if (!secretKey) {
564
- // If no key is set we error
565
- const urlOrRequestMetadata = (0, networking_1.stringifyUrlOrRequestMetadata)(chatEndpointInfo.urlOrRequestMetadata);
566
- this._logService.error(`Failed to send request to ${urlOrRequestMetadata} due to missing key`);
567
- (0, stream_1.sendCommunicationErrorTelemetry)(this._telemetryService, `Failed to send request to ${urlOrRequestMetadata} due to missing key`);
568
- return {
569
- result: {
570
- type: fetch_2.FetchResponseKind.Failed,
571
- modelRequestId: undefined,
572
- failKind: fetch_2.ChatFailKind.TokenExpiredOrInvalid,
573
- reason: 'key is missing'
574
- }
575
- };
709
+ // OTel inference span for this LLM call
710
+ const serverAddress = typeof chatEndpointInfo.urlOrRequestMetadata === 'string'
711
+ ? (() => { try {
712
+ return new URL(chatEndpointInfo.urlOrRequestMetadata).hostname;
713
+ }
714
+ catch {
715
+ return undefined;
716
+ } })()
717
+ : undefined;
718
+ const chatSessionId = (0, requestLogger_1.getCurrentCapturingToken)()?.chatSessionId;
719
+ const otelSpan = this._otelService.startSpan(`chat ${chatEndpointInfo.model}`, {
720
+ kind: 2 /* SpanKind.CLIENT */,
721
+ attributes: {
722
+ [index_1.GenAiAttr.OPERATION_NAME]: index_1.GenAiOperationName.CHAT,
723
+ [index_1.GenAiAttr.PROVIDER_NAME]: index_1.GenAiProviderName.GITHUB,
724
+ [index_1.GenAiAttr.REQUEST_MODEL]: chatEndpointInfo.model,
725
+ [index_1.GenAiAttr.CONVERSATION_ID]: telemetryProperties?.requestId ?? ourRequestId,
726
+ [index_1.GenAiAttr.REQUEST_MAX_TOKENS]: request.max_tokens ?? request.max_output_tokens ?? request.max_completion_tokens ?? 2048,
727
+ ...(request.temperature !== undefined ? { [index_1.GenAiAttr.REQUEST_TEMPERATURE]: request.temperature } : {}),
728
+ ...(request.top_p !== undefined ? { [index_1.GenAiAttr.REQUEST_TOP_P]: request.top_p } : {}),
729
+ [index_1.CopilotChatAttr.MAX_PROMPT_TOKENS]: chatEndpointInfo.modelMaxPromptTokens,
730
+ ...(serverAddress ? { [index_1.StdAttr.SERVER_ADDRESS]: serverAddress } : {}),
731
+ ...(conversationId ? { [index_1.CopilotChatAttr.SESSION_ID]: conversationId } : {}),
732
+ ...(chatSessionId ? { [index_1.CopilotChatAttr.CHAT_SESSION_ID]: chatSessionId } : {}),
733
+ },
734
+ });
735
+ const otelStartTime = Date.now();
736
+ try {
737
+ this._logService.debug(`modelMaxPromptTokens ${chatEndpointInfo.modelMaxPromptTokens}`);
738
+ this._logService.debug(`modelMaxResponseTokens ${request.max_tokens ?? 2048}`);
739
+ this._logService.debug(`chat model ${chatEndpointInfo.model}`);
740
+ secretKey ??= copilotToken.token;
741
+ if (!secretKey) {
742
+ // If no key is set we error
743
+ const urlOrRequestMetadata = (0, networking_1.stringifyUrlOrRequestMetadata)(chatEndpointInfo.urlOrRequestMetadata);
744
+ this._logService.error(`Failed to send request to ${urlOrRequestMetadata} due to missing key`);
745
+ (0, stream_1.sendCommunicationErrorTelemetry)(this._telemetryService, `Failed to send request to ${urlOrRequestMetadata} due to missing key`);
746
+ return {
747
+ result: {
748
+ type: fetch_2.FetchResponseKind.Failed,
749
+ modelRequestId: undefined,
750
+ failKind: fetch_2.ChatFailKind.TokenExpiredOrInvalid,
751
+ reason: 'key is missing'
752
+ }
753
+ };
754
+ }
755
+ // WebSocket path: use persistent WebSocket connection for Responses API endpoints
756
+ if (useWebSocket && turnId && conversationId) {
757
+ const wsResult = await this._doFetchViaWebSocket(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, location, ourRequestId, turnId, conversationId, cancellationToken, userInitiatedRequest, telemetryProperties, requestKindOptions);
758
+ return { ...wsResult, otelSpan };
759
+ }
760
+ const httpResult = await this._doFetchViaHttp(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions);
761
+ return { ...httpResult, otelSpan };
762
+ }
763
+ catch (err) {
764
+ otelSpan.setStatus(2 /* SpanStatusCode.ERROR */, err instanceof Error ? err.message : String(err));
765
+ otelSpan.setAttribute(index_1.StdAttr.ERROR_TYPE, err instanceof Error ? err.constructor.name : 'Error');
766
+ otelSpan.recordException(err);
767
+ throw err;
768
+ }
769
+ finally {
770
+ const durationSec = (Date.now() - otelStartTime) / 1000;
771
+ index_1.GenAiMetrics.recordOperationDuration(this._otelService, durationSec, {
772
+ operationName: index_1.GenAiOperationName.CHAT,
773
+ providerName: index_1.GenAiProviderName.GITHUB,
774
+ requestModel: chatEndpointInfo.model,
775
+ });
776
+ // Span is NOT ended here — caller (fetchMany) will set token attributes and end it
777
+ }
778
+ }
779
+ /**
780
+ * Sends a chat request via a persistent WebSocket connection instead of HTTP POST.
781
+ * Events are the same Responses API streaming events, processed by OpenAIResponsesProcessor.
782
+ */
783
+ async _doFetchViaWebSocket(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, location, ourRequestId, turnId, conversationId, cancellationToken, userInitiatedRequest, telemetryProperties, requestKindOptions) {
784
+ const intent = locationToIntent(location);
785
+ const agentInteractionType = requestKindOptions?.kind === 'subagent' ?
786
+ 'conversation-subagent' :
787
+ requestKindOptions?.kind === 'background' ?
788
+ 'conversation-background' :
789
+ intent === 'conversation-agent' ? intent : undefined;
790
+ const additionalHeaders = {
791
+ 'Authorization': `Bearer ${secretKey}`,
792
+ 'X-Request-Id': ourRequestId,
793
+ 'OpenAI-Intent': intent,
794
+ 'X-GitHub-Api-Version': '2025-05-01',
795
+ 'X-Interaction-Id': this._interactionService.interactionId,
796
+ 'X-Initiator': userInitiatedRequest ? 'user' : 'agent',
797
+ ...(chatEndpointInfo.getExtraHeaders ? chatEndpointInfo.getExtraHeaders(location) : {}),
798
+ };
799
+ if (agentInteractionType) {
800
+ additionalHeaders['X-Interaction-Type'] = agentInteractionType;
801
+ additionalHeaders['X-Agent-Task-Id'] = ourRequestId;
802
+ }
803
+ if (request.messages?.some((m) => Array.isArray(m.content) ? m.content.some(c => 'image_url' in c) : false) && chatEndpointInfo.supportsVision) {
804
+ additionalHeaders['Copilot-Vision-Request'] = 'true';
576
805
  }
806
+ const connection = await this._webSocketManager.getOrCreateConnection(conversationId, turnId, additionalHeaders);
807
+ // Generate unique ID to link input and output messages
808
+ const modelCallId = (0, uuid_1.generateUuid)();
809
+ const telemetryData = telemetryData_1.TelemetryData.createAndMarkAsIssued({
810
+ endpoint: 'completions',
811
+ engineName: 'chat',
812
+ uiKind: commonTypes_1.ChatLocation.toString(location),
813
+ transport: 'websocket',
814
+ ...{ ...telemetryProperties, modelCallId },
815
+ }, {
816
+ maxTokenWindow: chatEndpointInfo.modelMaxPromptTokens
817
+ });
818
+ const modelRequestId = (0, fetch_1.getRequestId)(connection.responseHeaders);
819
+ telemetryData.extendWithRequestId(modelRequestId);
820
+ for (const [key, value] of Object.entries(request)) {
821
+ if (key === 'messages' || key === 'input') {
822
+ continue;
823
+ } // Skip messages (PII)
824
+ telemetryData.properties[`request.option.${key}`] = JSON.stringify(value) ?? 'undefined';
825
+ }
826
+ telemetryData.properties['headerRequestId'] = ourRequestId;
827
+ this._telemetryService.sendGHTelemetryEvent('request.sent', telemetryData.properties, telemetryData.measurements);
828
+ const requestStart = Date.now();
829
+ const handle = connection.sendRequest(request, cancellationToken);
830
+ const extendedBaseTelemetryData = baseTelemetryData.extendedBy({ modelCallId });
831
+ const processor = this._instantiationService.createInstance(responsesApi_1.OpenAIResponsesProcessor, extendedBaseTelemetryData, modelRequestId.headerRequestId, modelRequestId.gitHubRequestId);
832
+ const chatCompletions = new async_1.AsyncIterableObject(async (emitter) => {
833
+ try {
834
+ await new Promise((resolve, reject) => {
835
+ handle.onEvent(event => {
836
+ const completion = processor.push(event, finishedCb);
837
+ if (completion) {
838
+ (0, responsesApi_1.sendCompletionOutputTelemetry)(this._telemetryService, this._logService, completion, extendedBaseTelemetryData);
839
+ emitter.emitOne(completion);
840
+ }
841
+ });
842
+ handle.onError(error => {
843
+ error.gitHubRequestId = modelRequestId.gitHubRequestId;
844
+ if ((0, errors_2.isCancellationError)(error)) {
845
+ reject(error);
846
+ return;
847
+ }
848
+ const warningTelemetry = telemetryData.extendedBy({ error: error.message });
849
+ this._telemetryService.sendGHTelemetryEvent('request.shownWarning', warningTelemetry.properties, warningTelemetry.measurements);
850
+ const totalTimeMs = Date.now() - requestStart;
851
+ telemetryData.measurements.totalTimeMs = totalTimeMs;
852
+ telemetryData.properties.error = error.message;
853
+ this._logService.debug(`request.error: [websocket], took ${totalTimeMs} ms`);
854
+ this._telemetryService.sendGHTelemetryEvent('request.error', telemetryData.properties, telemetryData.measurements);
855
+ reject(error);
856
+ });
857
+ handle.onComplete(() => {
858
+ const totalTimeMs = Date.now() - requestStart;
859
+ telemetryData.measurements.totalTimeMs = totalTimeMs;
860
+ this._logService.debug(`request.response: [websocket], took ${totalTimeMs} ms`);
861
+ this._telemetryService.sendGHTelemetryEvent('request.response', telemetryData.properties, telemetryData.measurements);
862
+ resolve();
863
+ });
864
+ });
865
+ }
866
+ finally {
867
+ let messagesToLog = request.messages;
868
+ if ((!messagesToLog || messagesToLog.length === 0) && request.input) {
869
+ try {
870
+ const rawMessages = (0, responsesApi_1.responseApiInputToRawMessagesForLogging)(request);
871
+ messagesToLog = (0, openai_1.rawMessageToCAPI)(rawMessages);
872
+ }
873
+ catch (e) {
874
+ this._logService.error(`Failed to convert Response API input to messages for telemetry:`, e);
875
+ messagesToLog = [];
876
+ }
877
+ }
878
+ (0, chatStream_1.sendEngineMessagesTelemetry)(this._telemetryService, messagesToLog ?? [], telemetryData, false, this._logService);
879
+ }
880
+ });
881
+ return {
882
+ result: {
883
+ type: fetch_2.FetchResponseKind.Success,
884
+ chatCompletions,
885
+ }
886
+ };
887
+ }
888
+ async _doFetchViaHttp(chatEndpointInfo, request, baseTelemetryData, finishedCb, secretKey, location, ourRequestId, nChoices, cancellationToken, userInitiatedRequest, telemetryProperties, useFetcher, canRetryOnce, requestKindOptions) {
577
889
  // Generate unique ID to link input and output messages
578
890
  const modelCallId = (0, uuid_1.generateUuid)();
579
891
  const response = await this._fetchWithInstrumentation(chatEndpointInfo, ourRequestId, request, secretKey, location, cancellationToken, userInitiatedRequest, { ...telemetryProperties, modelCallId }, useFetcher, canRetryOnce, requestKindOptions);
@@ -660,6 +972,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
660
972
  endpoint: 'completions',
661
973
  engineName: 'chat',
662
974
  uiKind: commonTypes_1.ChatLocation.toString(location),
975
+ transport: 'http',
663
976
  ...telemetryProperties // This includes the modelCallId from fetchAndStreamChat
664
977
  }, {
665
978
  maxTokenWindow: chatEndpoint.modelMaxPromptTokens
@@ -946,7 +1259,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
946
1259
  reason: `Request Failed: ${response.status} ${text}`
947
1260
  };
948
1261
  }
949
- async processSuccessfulResponse(response, messages, requestBody, requestId, maxResponseTokens, promptTokenCount, timeToFirstToken, streamRecorder, baseTelemetry, chatEndpointInfo, userInitiatedRequest, fetcher, bytesReceived, suspendEventSeen, resumeEventSeen) {
1262
+ async processSuccessfulResponse(response, messages, requestBody, requestId, maxResponseTokens, promptTokenCount, timeToFirstToken, streamRecorder, baseTelemetry, chatEndpointInfo, userInitiatedRequest, transport, fetcher, bytesReceived, suspendEventSeen, resumeEventSeen) {
950
1263
  const completions = [];
951
1264
  for await (const chatCompletion of response.chatCompletions) {
952
1265
  chatMLFetcherTelemetry_1.ChatMLFetcherTelemetrySender.sendSuccessTelemetry(this._telemetryService, {
@@ -960,6 +1273,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
960
1273
  timeToFirstToken,
961
1274
  timeToFirstTokenEmitted: (baseTelemetry && streamRecorder.firstTokenEmittedTime) ? streamRecorder.firstTokenEmittedTime - baseTelemetry.issuedTime : -1,
962
1275
  hasImageMessages: this.filterImageMessages(messages),
1276
+ transport,
963
1277
  fetcher,
964
1278
  bytesReceived,
965
1279
  suspendEventSeen,
@@ -1225,7 +1539,7 @@ let ChatMLFetcherImpl = class ChatMLFetcherImpl extends AbstractChatMLFetcher {
1225
1539
  }
1226
1540
  };
1227
1541
  exports.ChatMLFetcherImpl = ChatMLFetcherImpl;
1228
- exports.ChatMLFetcherImpl = ChatMLFetcherImpl = __decorate([
1542
+ exports.ChatMLFetcherImpl = ChatMLFetcherImpl = ChatMLFetcherImpl_1 = __decorate([
1229
1543
  __param(0, fetcherService_1.IFetcherService),
1230
1544
  __param(1, telemetry_1.ITelemetryService),
1231
1545
  __param(2, requestLogger_1.IRequestLogger),
@@ -1238,7 +1552,9 @@ exports.ChatMLFetcherImpl = ChatMLFetcherImpl = __decorate([
1238
1552
  __param(9, configurationService_1.IConfigurationService),
1239
1553
  __param(10, nullExperimentationService_1.IExperimentationService),
1240
1554
  __param(11, powerService_1.IPowerService),
1241
- __param(12, instantiation_1.IInstantiationService)
1555
+ __param(12, instantiation_1.IInstantiationService),
1556
+ __param(13, chatWebSocketManager_1.IChatWebSocketManager),
1557
+ __param(14, otelService_1.IOTelService)
1242
1558
  ], ChatMLFetcherImpl);
1243
1559
  /**
1244
1560
  * Validates a chat request payload to ensure it is valid