Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/utils/streaming.ts
13406 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 { AsyncIterableObject } from '../../../../../util/vs/base/common/async';
7
8
export async function* replaceStringInStream(stream: AsyncIterable<string>, searchValue: string, replaceValue: string): AsyncIterable<string> {
9
let buffer = '';
10
11
const searchValuePrefixes = getPrefixes(searchValue);
12
searchValuePrefixes.reverse(); // longest first
13
14
for await (const chunk of stream) {
15
buffer += chunk;
16
17
let searchIndex: number;
18
let lastIndex = 0;
19
20
let textToYield = '';
21
22
// Process the buffer in chunks, but retain potential partial matches at the end.
23
while ((searchIndex = buffer.indexOf(searchValue, lastIndex)) !== -1) {
24
textToYield += buffer.slice(lastIndex, searchIndex) + replaceValue;
25
lastIndex = searchIndex + searchValue.length;
26
}
27
28
// Retain the remaining buffer that could be part of the next `searchValue`.
29
if (lastIndex !== 0) {
30
buffer = buffer.slice(lastIndex);
31
}
32
33
// At this point, buffer does not contain any `searchValue` anymore.
34
// However, there could be a future chunk c, such that `buffer + c.substring(0, searchValue.length - 1)` contains it,
35
// so we cannot yield the full buffer.
36
37
// longest prefix first
38
for (const p of searchValuePrefixes) {
39
if (buffer.endsWith(p)) {
40
const idx = buffer.length - p.length;
41
42
textToYield += buffer.slice(0, idx);
43
buffer = buffer.slice(idx);
44
45
break;
46
}
47
}
48
49
if (textToYield.length > 0) {
50
yield textToYield;
51
}
52
}
53
54
// Yield any remaining buffer content that didn't match `searchValue`.
55
if (buffer.length > 0) {
56
yield buffer;
57
}
58
}
59
60
function getPrefixes(value: string): string[] {
61
const prefixes: string[] = [];
62
for (let i = 0; i <= value.length; i++) {
63
prefixes.push(value.substring(0, i));
64
}
65
return prefixes;
66
}
67
68
export type StreamPipe<T> = (stream: AsyncIterable<T>) => AsyncIterable<T>;
69
70
export namespace StreamPipe {
71
export function identity<T>(): StreamPipe<T> {
72
return stream => stream;
73
}
74
75
export function discard<T>(): StreamPipe<T> {
76
return _stream => AsyncIterableObject.EMPTY;
77
}
78
79
export function chain<T>(...pipes: StreamPipe<T>[]): StreamPipe<T> {
80
return stream => pipes.reduce((s, pipe) => pipe(s), stream);
81
}
82
}
83
84
export function forEachStreamed<T>(stream: AsyncIterable<T>, fn: (item: T) => void): Promise<void> {
85
return (async () => {
86
for await (const item of stream) {
87
fn(item);
88
}
89
})();
90
}
91
92