Skip to content

Commit 06237e2

Browse files
authored
Cleanup Response (microsoft#2876)
1 parent e9ae725 commit 06237e2

File tree

23 files changed

+206
-218
lines changed

23 files changed

+206
-218
lines changed

chat-lib/package-lock.json

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chat-lib/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"dependencies": {
1717
"@microsoft/tiktokenizer": "^1.0.10",
1818
"@sinclair/typebox": "^0.34.41",
19-
"@vscode/copilot-api": "^0.2.5",
19+
"@vscode/copilot-api": "^0.2.8",
2020
"@vscode/l10n": "^0.0.18",
21-
"@vscode/prompt-tsx": "^0.4.0-alpha.5",
21+
"@vscode/prompt-tsx": "^0.4.0-alpha.6",
2222
"@vscode/tree-sitter-wasm": "0.0.5-php.2",
2323
"applicationinsights": "^2.9.7",
2424
"jsonc-parser": "^3.3.1",
@@ -30,7 +30,7 @@
3030
"yaml": "^2.8.0"
3131
},
3232
"devDependencies": {
33-
"@anthropic-ai/sdk": "^0.68.0",
33+
"@anthropic-ai/sdk": "^0.71.2",
3434
"@octokit/types": "^14.1.0",
3535
"@types/node": "^22.16.3",
3636
"@types/vscode": "^1.102.0",

chat-lib/test/getInlineCompletions.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ dotenv.config({ path: '../.env' });
99

1010
import { readFile } from 'fs/promises';
1111
import { join } from 'path';
12-
import * as stream from 'stream';
1312
import { assert, describe, expect, it } from 'vitest';
1413
import type { AuthenticationGetSessionOptions, AuthenticationSession, LanguageModelChat } from 'vscode';
1514
import { ResultType } from '../src/_internal/extension/completions-core/vscode-node/lib/src/ghostText/ghostText';
@@ -51,13 +50,12 @@ class TestFetcher implements IFetcher {
5150
};
5251

5352
const found = typeof responseText === 'string';
54-
return new Response(
53+
const text = responseText || '';
54+
return Response.fromText(
5555
found ? 200 : 404,
5656
found ? 'OK' : 'Not Found',
5757
headers,
58-
async () => responseText || '',
59-
async () => JSON.parse(responseText || ''),
60-
async () => stream.Readable.from([responseText || '']),
58+
text,
6159
'node-http'
6260
);
6361
}

chat-lib/test/nesProvider.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ dotenv.config({ path: '../.env' });
1010
import { promises as fs } from 'fs';
1111
import { outdent } from 'outdent';
1212
import * as path from 'path';
13-
import * as stream from 'stream';
1413
import { assert, describe, expect, it } from 'vitest';
1514
import { CopilotToken, createTestExtendedTokenInfo } from '../src/_internal/platform/authentication/common/copilotToken';
1615
import { ICopilotTokenManager } from '../src/_internal/platform/authentication/common/copilotTokenManager';
@@ -51,13 +50,12 @@ class TestFetcher implements IFetcher {
5150
};
5251

5352
const found = typeof responseText === 'string';
54-
return new Response(
53+
const text = responseText || '';
54+
return Response.fromText(
5555
found ? 200 : 404,
5656
found ? 'OK' : 'Not Found',
5757
headers,
58-
async () => responseText || '',
59-
async () => JSON.parse(responseText || ''),
60-
async () => stream.Readable.from([responseText || '']),
58+
text,
6159
'node-http'
6260
);
6361
}

src/extension/agents/claude/node/claudeLanguageModelServer.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { MessageParam } from '@anthropic-ai/sdk/resources';
77
import { RequestMetadata, RequestType } from '@vscode/copilot-api';
88
import { Raw } from '@vscode/prompt-tsx';
99
import * as http from 'http';
10-
import { ClientHttp2Stream } from 'http2';
1110
import { IChatMLFetcher, Source } from '../../../../platform/chat/common/chatMLFetcher';
1211
import { ChatLocation, ChatResponse } from '../../../../platform/chat/common/commonTypes';
1312
import { CustomModel, EndpointEditToolName, IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
@@ -528,7 +527,7 @@ class ClaudeStreamingPassThroughEndpoint implements IChatEndpoint {
528527
telemetryData: TelemetryData,
529528
cancellationToken?: CancellationToken
530529
): Promise<AsyncIterableObject<ChatCompletion>> {
531-
const body = (await response.body()) as ClientHttp2Stream;
530+
const body = response.body;
532531
return new AsyncIterableObject<ChatCompletion>(async feed => {
533532
// We parse the stream just to return a correct ChatCompletion for logging the response and token usage details.
534533
const requestId = response.headers.get('X-Request-ID') ?? generateUuid();
@@ -566,9 +565,7 @@ class ClaudeStreamingPassThroughEndpoint implements IChatEndpoint {
566565
parser.feed(chunk);
567566
}
568567
} finally {
569-
if (!body.destroyed) {
570-
body.destroy();
571-
}
568+
await body.destroy();
572569
}
573570
});
574571
}

src/extension/completions-core/vscode-node/lib/src/openai/fetch.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { ClientHttp2Stream } from 'http2';
76
import { IAuthenticationService } from '../../../../../../platform/authentication/common/authentication';
87
import { CopilotAnnotations, StreamCopilotAnnotations } from '../../../../../../platform/completions-core/common/openai/copilotAnnotations';
98
import { IEnvService } from '../../../../../../platform/env/common/envService';
@@ -475,15 +474,10 @@ export class LiveOpenAIFetcher extends OpenAIFetcher {
475474
return { type: 'canceled', reason: 'before fetch request' };
476475
}
477476
if (cancel?.isCancellationRequested) {
478-
const body = await response.body();
479477
try {
480478
// Destroy the stream so that the server is hopefully notified we don't want any more data
481479
// and can cancel/forget about the request itself.
482-
if (body && typeof (body as ClientHttp2Stream).destroy === 'function') {
483-
(body as ClientHttp2Stream).destroy();
484-
} else if (body instanceof ReadableStream) {
485-
void body.cancel();
486-
}
480+
await response.body.destroy();
487481
} catch (e) {
488482
this.instantiationService.invokeFunction(acc => logger.exception(acc, e, `Error destroying stream`));
489483
}

src/extension/completions-core/vscode-node/lib/src/openai/stream.ts

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { ClientHttp2Stream } from 'http2';
76
import { CopilotAnnotation, CopilotAnnotations, CopilotNamedAnnotationList, StreamCopilotAnnotations } from '../../../../../../platform/completions-core/common/openai/copilotAnnotations';
87
import { getRequestId, RequestId } from '../../../../../../platform/networking/common/fetch';
8+
import { DestroyableStream } from '../../../../../../platform/networking/common/fetcherService';
99
import { IInstantiationService, ServicesAccessor } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
1010
import { CancellationToken as ICancellationToken } from '../../../types/src';
1111
import { ICompletionsLogTargetService, Logger } from '../logger';
@@ -211,7 +211,7 @@ export class SSEProcessor {
211211
private constructor(
212212
private readonly expectedNumChoices: number,
213213
private readonly response: Response,
214-
private readonly body: NodeJS.ReadableStream,
214+
private readonly body: DestroyableStream<string>,
215215
private readonly telemetryData: TelemetryWithExp,
216216
private readonly dropCompletionReasons: string[],
217217
private readonly cancellationToken: ICancellationToken | undefined = undefined,
@@ -236,23 +236,7 @@ export class SSEProcessor {
236236
const instantiationService = accessor.get(IInstantiationService);
237237
const logTargetService = accessor.get(ICompletionsLogTargetService);
238238

239-
// Handle both NodeJS.ReadableStream and ReadableStream for web. Once
240-
// helix fetcher is removed, the NodeJS.ReadableStream support can be
241-
let body = response.body() as unknown as NodeJS.ReadableStream;
242-
if (body === null) {
243-
throw new Error('No response body available');
244-
}
245-
// OLD
246-
/* if (typeof body.setEncoding === 'function') {
247-
body.setEncoding('utf8');
248-
} else {
249-
// Convert fetch response to utf-8 decoded stream
250-
body = (body as unknown as ReadableStream).pipeThrough(new TextDecoderStream()) as unknown as NodeJS.ReadableStream;
251-
} */
252-
253-
// NEW
254-
body = await body as NodeJS.ReadableStream;
255-
body.setEncoding('utf8');
239+
const body = response.body.pipeThrough(new TextDecoderStream());
256240

257241
// TODO@benibenj can we switch to our SSEProcessor implementation?
258242
// It seems like they build more on top of the shared impl
@@ -288,7 +272,7 @@ export class SSEProcessor {
288272
try {
289273
yield* this.processSSEInner(finishedCb);
290274
} finally {
291-
this.cancel();
275+
await this.cancel();
292276
streamChoicesLogger.debug(this.logTarget,
293277
`request done: headerRequestId: [${this.requestId.headerRequestId}] model deployment ID: [${this.requestId.deploymentId}]`
294278
);
@@ -307,7 +291,7 @@ export class SSEProcessor {
307291

308292
// Iterate over arbitrarily sized chunks coming in from the network.
309293
networkRead: for await (const chunk of this.body) {
310-
if (this.maybeCancel('after awaiting body chunk')) {
294+
if (await this.maybeCancel('after awaiting body chunk')) {
311295
return;
312296
}
313297

@@ -431,7 +415,7 @@ export class SSEProcessor {
431415
})
432416
);
433417

434-
if (this.maybeCancel('after awaiting finishedCb')) {
418+
if (await this.maybeCancel('after awaiting finishedCb')) {
435419
return;
436420
}
437421
}
@@ -486,7 +470,7 @@ export class SSEProcessor {
486470
solution.yielded = true;
487471
}
488472

489-
if (this.maybeCancel('after yielding finished choice')) {
473+
if (await this.maybeCancel('after yielding finished choice')) {
490474
return;
491475
}
492476

@@ -522,7 +506,7 @@ export class SSEProcessor {
522506
usage: usage,
523507
};
524508

525-
if (this.maybeCancel('after yielding after iteration done')) {
509+
if (await this.maybeCancel('after yielding after iteration done')) {
526510
return;
527511
}
528512
}
@@ -605,7 +589,7 @@ export class SSEProcessor {
605589
usage: usage,
606590
};
607591

608-
if (this.maybeCancel('after yielding on DONE')) {
592+
if (await this.maybeCancel('after yielding on DONE')) {
609593
return;
610594
}
611595
}
@@ -615,22 +599,18 @@ export class SSEProcessor {
615599
* Returns whether the cancellation token was cancelled and closes the
616600
* stream if it was.
617601
*/
618-
private maybeCancel(description: string) {
602+
private async maybeCancel(description: string) {
619603
if (this.cancellationToken?.isCancellationRequested) {
620604
streamChoicesLogger.debug(this.logTarget, 'Cancelled: ' + description);
621-
this.cancel();
605+
await this.cancel();
622606
return true;
623607
}
624608
return false;
625609
}
626610

627611
/** Cancels the network request to the proxy. */
628-
private cancel() {
629-
if (this.body && 'destroy' in this.body && typeof this.body.destroy === 'function') {
630-
(this.body as ClientHttp2Stream).destroy();
631-
} else if (this.body instanceof ReadableStream) {
632-
void this.body.cancel();
633-
}
612+
private async cancel() {
613+
await this.body.destroy();
634614
}
635615

636616
/** Returns whether we've finished receiving all expected solutions. */

src/extension/completions-core/vscode-node/lib/src/test/fetcher.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Readable } from 'stream';
76
import { CopilotNamedAnnotationList } from '../../../../../../platform/completions-core/common/openai/copilotAnnotations';
87
import { FetchOptions, IAbortController, ICompletionsFetcherService, IHeaders, Response } from '../networking';
98

@@ -15,13 +14,11 @@ export function createFakeResponse(statusCode: number, response?: string, header
1514
for (const [key, value] of Object.entries(headers || {})) {
1615
fakeHeaders.set(key, value);
1716
}
18-
return new Response(
17+
return Response.fromText(
1918
statusCode,
2019
'status text',
2120
fakeHeaders,
22-
() => Promise.resolve(response ?? ''),
23-
() => Promise.resolve(response ? JSON.parse(response) : {}),
24-
() => Promise.resolve(null),
21+
response ?? '',
2522
'test-stub'
2623
);
2724
}
@@ -37,13 +34,11 @@ export function createFakeJsonResponse(statusCode: number, response: string | ob
3734
}
3835

3936
export function createFakeStreamResponse(body: string): Response {
40-
return new Response(
37+
return Response.fromText(
4138
200,
4239
'Success',
4340
new FakeHeaders(),
44-
() => Promise.resolve(body),
45-
() => Promise.resolve(JSON.parse(body.replace(/^data: /gm, '').replace(/\n\[DONE\]\n$/, ''))),
46-
() => Promise.resolve(toStream(body)),
41+
body,
4742
'test-stub'
4843
);
4944
}
@@ -133,16 +128,6 @@ export class NoFetchFetcher extends FakeFetcher {
133128
}
134129
}
135130

136-
function toStream(...strings: string[]): NodeJS.ReadableStream {
137-
const stream = new Readable();
138-
stream._read = () => { };
139-
for (const s of strings) {
140-
stream.push(s);
141-
}
142-
stream.push(null);
143-
return stream;
144-
}
145-
146131
class FakeHeaders implements IHeaders {
147132
private readonly headers: Map<string, string> = new Map();
148133

0 commit comments

Comments
 (0)