@oh-my-pi/pi-coding-agent 15.5.2 → 15.5.4
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.
- package/CHANGELOG.md +38 -0
- package/dist/types/config/settings-schema.d.ts +27 -0
- package/dist/types/config.d.ts +31 -5
- package/dist/types/edit/file-snapshot-store.d.ts +18 -0
- package/dist/types/edit/hashline/diff.d.ts +30 -0
- package/dist/types/edit/hashline/execute.d.ts +29 -0
- package/dist/types/edit/hashline/filesystem.d.ts +57 -0
- package/dist/types/edit/hashline/index.d.ts +4 -0
- package/dist/types/edit/hashline/params.d.ts +12 -0
- package/dist/types/edit/index.d.ts +4 -3
- package/dist/types/edit/normalize.d.ts +4 -16
- package/dist/types/index.d.ts +0 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/index.d.ts +6 -5
- package/dist/types/tools/path-utils.d.ts +18 -0
- package/dist/types/utils/changelog.d.ts +8 -3
- package/package.json +8 -15
- package/src/config/settings-schema.ts +32 -0
- package/src/config.ts +42 -15
- package/src/edit/file-snapshot-store.ts +22 -0
- package/src/edit/hashline/diff.ts +88 -0
- package/src/edit/hashline/execute.ts +188 -0
- package/src/edit/hashline/filesystem.ts +129 -0
- package/src/edit/hashline/index.ts +4 -0
- package/src/edit/hashline/params.ts +11 -0
- package/src/edit/index.ts +7 -15
- package/src/edit/normalize.ts +11 -41
- package/src/edit/renderer.ts +1 -1
- package/src/edit/streaming.ts +8 -9
- package/src/index.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/sdk.ts +8 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +3 -3
- package/src/tools/bash.ts +74 -10
- package/src/tools/index.ts +6 -5
- package/src/tools/path-utils.ts +81 -0
- package/src/tools/read.ts +14 -72
- package/src/tools/search.ts +136 -17
- package/src/tools/write.ts +3 -3
- package/src/utils/changelog.ts +11 -3
- package/src/utils/file-mentions.ts +1 -1
- package/dist/types/edit/file-read-cache.d.ts +0 -36
- package/dist/types/hashline/anchors.d.ts +0 -26
- package/dist/types/hashline/apply.d.ts +0 -14
- package/dist/types/hashline/constants.d.ts +0 -40
- package/dist/types/hashline/diff-preview.d.ts +0 -2
- package/dist/types/hashline/diff.d.ts +0 -16
- package/dist/types/hashline/execute.d.ts +0 -4
- package/dist/types/hashline/executor.d.ts +0 -56
- package/dist/types/hashline/hash.d.ts +0 -76
- package/dist/types/hashline/index.d.ts +0 -14
- package/dist/types/hashline/input.d.ts +0 -4
- package/dist/types/hashline/prefixes.d.ts +0 -7
- package/dist/types/hashline/recovery.d.ts +0 -21
- package/dist/types/hashline/stream.d.ts +0 -2
- package/dist/types/hashline/tokenizer.d.ts +0 -94
- package/dist/types/hashline/types.d.ts +0 -75
- package/src/edit/file-read-cache.ts +0 -138
- package/src/hashline/anchors.ts +0 -104
- package/src/hashline/apply.ts +0 -790
- package/src/hashline/bigrams.json +0 -649
- package/src/hashline/constants.ts +0 -51
- package/src/hashline/diff-preview.ts +0 -42
- package/src/hashline/diff.ts +0 -82
- package/src/hashline/execute.ts +0 -334
- package/src/hashline/executor.ts +0 -334
- package/src/hashline/grammar.lark +0 -23
- package/src/hashline/hash.ts +0 -131
- package/src/hashline/index.ts +0 -14
- package/src/hashline/input.ts +0 -137
- package/src/hashline/prefixes.ts +0 -111
- package/src/hashline/recovery.ts +0 -139
- package/src/hashline/stream.ts +0 -123
- package/src/hashline/tokenizer.ts +0 -473
- package/src/hashline/types.ts +0 -66
- package/src/prompts/tools/hashline.md +0 -63
package/src/hashline/prefixes.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
const HL_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:[+*-]\s*)?\d+:/;
|
|
2
|
-
const HL_PREFIX_PLUS_RE = /^\s*(?:>>>|>>)?\s*\+\s*\d+:/;
|
|
3
|
-
const HL_HEADER_RE = /^\s*¶\S+#[0-9a-f]{4}\s*$/;
|
|
4
|
-
const DIFF_PLUS_RE = /^[+](?![+])/;
|
|
5
|
-
const READ_TRUNCATION_NOTICE_RE = /^\[(?:Showing lines \d+-\d+ of \d+|\d+ more lines? in (?:file|\S+))\b.*\bUse :L?\d+/;
|
|
6
|
-
|
|
7
|
-
function stripLeadingHashlinePrefixes(line: string): string {
|
|
8
|
-
let result = line;
|
|
9
|
-
let previous: string;
|
|
10
|
-
do {
|
|
11
|
-
previous = result;
|
|
12
|
-
result = result.replace(HL_PREFIX_RE, "");
|
|
13
|
-
} while (result !== previous);
|
|
14
|
-
return result;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
18
|
-
// 5. Read-output prefix stripping
|
|
19
|
-
//
|
|
20
|
-
// When a model echoes back content from a `read` or `search` response, every
|
|
21
|
-
// line is prefixed with either a hashline-mode line number (`123:`) or, for
|
|
22
|
-
// diff-style echoes, a leading `+`. These helpers detect that and recover the
|
|
23
|
-
// raw text.
|
|
24
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
type LinePrefixStats = {
|
|
27
|
-
nonEmpty: number;
|
|
28
|
-
headerCount: number;
|
|
29
|
-
hashPrefixCount: number;
|
|
30
|
-
diffPlusHashPrefixCount: number;
|
|
31
|
-
diffPlusCount: number;
|
|
32
|
-
truncationNoticeCount: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function collectLinePrefixStats(lines: string[]): LinePrefixStats {
|
|
36
|
-
const stats: LinePrefixStats = {
|
|
37
|
-
nonEmpty: 0,
|
|
38
|
-
headerCount: 0,
|
|
39
|
-
hashPrefixCount: 0,
|
|
40
|
-
diffPlusHashPrefixCount: 0,
|
|
41
|
-
diffPlusCount: 0,
|
|
42
|
-
truncationNoticeCount: 0,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
for (const line of lines) {
|
|
46
|
-
if (line.length === 0) continue;
|
|
47
|
-
if (READ_TRUNCATION_NOTICE_RE.test(line)) {
|
|
48
|
-
stats.truncationNoticeCount++;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (HL_HEADER_RE.test(line)) {
|
|
52
|
-
stats.nonEmpty++;
|
|
53
|
-
stats.headerCount++;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
stats.nonEmpty++;
|
|
57
|
-
if (HL_PREFIX_RE.test(line)) stats.hashPrefixCount++;
|
|
58
|
-
if (HL_PREFIX_PLUS_RE.test(line)) stats.diffPlusHashPrefixCount++;
|
|
59
|
-
if (DIFF_PLUS_RE.test(line)) stats.diffPlusCount++;
|
|
60
|
-
}
|
|
61
|
-
return stats;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function stripNewLinePrefixes(lines: string[]): string[] {
|
|
65
|
-
const stats = collectLinePrefixStats(lines);
|
|
66
|
-
if (stats.nonEmpty === 0) return lines;
|
|
67
|
-
|
|
68
|
-
const contentLineCount = stats.nonEmpty - stats.headerCount;
|
|
69
|
-
const stripHash = contentLineCount > 0 && stats.hashPrefixCount === contentLineCount;
|
|
70
|
-
const stripPlus =
|
|
71
|
-
!stripHash &&
|
|
72
|
-
stats.diffPlusHashPrefixCount === 0 &&
|
|
73
|
-
stats.diffPlusCount > 0 &&
|
|
74
|
-
stats.diffPlusCount >= stats.nonEmpty * 0.5;
|
|
75
|
-
|
|
76
|
-
if (!stripHash && !stripPlus && stats.diffPlusHashPrefixCount === 0) return lines;
|
|
77
|
-
|
|
78
|
-
return lines
|
|
79
|
-
.filter(line => !READ_TRUNCATION_NOTICE_RE.test(line) && !(stripHash && HL_HEADER_RE.test(line)))
|
|
80
|
-
.map(line => {
|
|
81
|
-
if (stripHash) return stripLeadingHashlinePrefixes(line);
|
|
82
|
-
if (stripPlus) return line.replace(DIFF_PLUS_RE, "");
|
|
83
|
-
if (stats.diffPlusHashPrefixCount > 0 && HL_PREFIX_PLUS_RE.test(line)) {
|
|
84
|
-
return line.replace(HL_PREFIX_RE, "");
|
|
85
|
-
}
|
|
86
|
-
return line;
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function stripHashlinePrefixes(lines: string[]): string[] {
|
|
91
|
-
const stats = collectLinePrefixStats(lines);
|
|
92
|
-
if (stats.nonEmpty === 0) return lines;
|
|
93
|
-
const contentLineCount = stats.nonEmpty - stats.headerCount;
|
|
94
|
-
if (contentLineCount === 0 || stats.hashPrefixCount !== contentLineCount) return lines;
|
|
95
|
-
return lines
|
|
96
|
-
.filter(line => !READ_TRUNCATION_NOTICE_RE.test(line) && !HL_HEADER_RE.test(line))
|
|
97
|
-
.map(line => stripLeadingHashlinePrefixes(line));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Normalize line payloads by stripping read/search line prefixes. `null` /
|
|
102
|
-
* `undefined` yield `[]`; a single multiline string is split on `\n`.
|
|
103
|
-
*/
|
|
104
|
-
export function hashlineParseText(edit: string[] | string | null | undefined): string[] {
|
|
105
|
-
if (edit == null) return [];
|
|
106
|
-
if (typeof edit === "string") {
|
|
107
|
-
const trimmed = edit.endsWith("\n") ? edit.slice(0, -1) : edit;
|
|
108
|
-
edit = trimmed.replaceAll("\r", "").split("\n");
|
|
109
|
-
}
|
|
110
|
-
return stripNewLinePrefixes(edit);
|
|
111
|
-
}
|
package/src/hashline/recovery.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import * as Diff from "diff";
|
|
2
|
-
import { generateDiffString } from "../edit/diff";
|
|
3
|
-
import type { FileReadCache, FileReadSnapshot } from "../edit/file-read-cache";
|
|
4
|
-
import { applyHashlineEdits, type HashlineApplyResult } from "./apply";
|
|
5
|
-
import { computeFileHash } from "./hash";
|
|
6
|
-
import type { HashlineApplyOptions, HashlineEdit } from "./types";
|
|
7
|
-
|
|
8
|
-
export interface HashlineRecoveryArgs {
|
|
9
|
-
cache: FileReadCache;
|
|
10
|
-
absolutePath: string;
|
|
11
|
-
currentText: string;
|
|
12
|
-
fileHash: string;
|
|
13
|
-
edits: HashlineEdit[];
|
|
14
|
-
options: HashlineApplyOptions;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface HashlineRecoveryResult {
|
|
18
|
-
lines: string;
|
|
19
|
-
firstChangedLine: number | undefined;
|
|
20
|
-
warnings: string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Section hashes are line-precise; never let Diff.applyPatch slide a hunk onto a
|
|
24
|
-
// duplicate closer 100+ lines away. If snapshot replay does not align exactly,
|
|
25
|
-
// refuse and let the model re-read.
|
|
26
|
-
const HASHLINE_RECOVERY_FUZZ_FACTOR = 0;
|
|
27
|
-
|
|
28
|
-
const HASHLINE_RECOVERY_EXTERNAL_WARNING =
|
|
29
|
-
"Recovered from a stale file hash using a previous read snapshot (file changed externally between read and edit).";
|
|
30
|
-
const HASHLINE_RECOVERY_SESSION_CHAIN_WARNING =
|
|
31
|
-
"Recovered from a stale file hash using an earlier in-session snapshot (the file hash advanced after a prior edit in this session).";
|
|
32
|
-
const HASHLINE_RECOVERY_SESSION_REPLAY_WARNING =
|
|
33
|
-
"Recovered by replaying your edits onto the current file content — your previous edit in this session changed line(s) you re-targeted with a stale hash. Verify the diff matches your intent before continuing.";
|
|
34
|
-
|
|
35
|
-
function applyEditsToSnapshot(
|
|
36
|
-
previousText: string,
|
|
37
|
-
currentText: string,
|
|
38
|
-
edits: HashlineEdit[],
|
|
39
|
-
options: HashlineApplyOptions,
|
|
40
|
-
recoveryWarning: string,
|
|
41
|
-
): HashlineRecoveryResult | null {
|
|
42
|
-
let applied: HashlineApplyResult;
|
|
43
|
-
try {
|
|
44
|
-
applied = applyHashlineEdits(previousText, edits, options);
|
|
45
|
-
} catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
if (applied.lines === previousText) return null;
|
|
49
|
-
|
|
50
|
-
const patch = Diff.structuredPatch("file", "file", previousText, applied.lines, "", "", { context: 3 });
|
|
51
|
-
const merged = Diff.applyPatch(currentText, patch, { fuzzFactor: HASHLINE_RECOVERY_FUZZ_FACTOR });
|
|
52
|
-
if (typeof merged !== "string" || merged === currentText) return null;
|
|
53
|
-
|
|
54
|
-
const mergedDiff = generateDiffString(currentText, merged);
|
|
55
|
-
const hasNetChange = mergedDiff.firstChangedLine !== undefined;
|
|
56
|
-
const recoveryWarnings = hasNetChange
|
|
57
|
-
? [recoveryWarning, ...(applied.warnings ?? [])]
|
|
58
|
-
: [...(applied.warnings ?? [])];
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
lines: merged,
|
|
62
|
-
firstChangedLine: mergedDiff.firstChangedLine ?? applied.firstChangedLine,
|
|
63
|
-
warnings: recoveryWarnings,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function replaySessionChainOnCurrent(
|
|
68
|
-
previousText: string,
|
|
69
|
-
currentText: string,
|
|
70
|
-
edits: HashlineEdit[],
|
|
71
|
-
options: HashlineApplyOptions,
|
|
72
|
-
): HashlineRecoveryResult | null {
|
|
73
|
-
// Only safe when no insert/delete shifted line counts in the prior edit
|
|
74
|
-
// chain: if total line counts match, every line number in `edits` still
|
|
75
|
-
// resolves to the same logical row.
|
|
76
|
-
if (previousText.split("\n").length !== currentText.split("\n").length) return null;
|
|
77
|
-
let applied: HashlineApplyResult;
|
|
78
|
-
try {
|
|
79
|
-
applied = applyHashlineEdits(currentText, edits, options);
|
|
80
|
-
} catch {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
if (applied.lines === currentText) return null;
|
|
84
|
-
return {
|
|
85
|
-
lines: applied.lines,
|
|
86
|
-
firstChangedLine: applied.firstChangedLine,
|
|
87
|
-
warnings: [HASHLINE_RECOVERY_SESSION_REPLAY_WARNING, ...(applied.warnings ?? [])],
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function buildSparseOverlayText(currentText: string, snapshotLines: ReadonlyMap<number, string>): string {
|
|
92
|
-
const overlaid = currentText.split("\n");
|
|
93
|
-
let maxCachedLine = 0;
|
|
94
|
-
for (const lineNum of snapshotLines.keys()) {
|
|
95
|
-
if (lineNum > maxCachedLine) maxCachedLine = lineNum;
|
|
96
|
-
}
|
|
97
|
-
while (overlaid.length < maxCachedLine) overlaid.push("");
|
|
98
|
-
for (const [lineNum, content] of snapshotLines) {
|
|
99
|
-
overlaid[lineNum - 1] = content;
|
|
100
|
-
}
|
|
101
|
-
return overlaid.join("\n");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function isHeadSnapshot(head: FileReadSnapshot | null, snapshot: FileReadSnapshot): boolean {
|
|
105
|
-
return head === snapshot;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function resolveRecoveryWarning(head: FileReadSnapshot | null, snapshot: FileReadSnapshot): string {
|
|
109
|
-
return isHeadSnapshot(head, snapshot) ? HASHLINE_RECOVERY_EXTERNAL_WARNING : HASHLINE_RECOVERY_SESSION_CHAIN_WARNING;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Attempt to recover from a section file-hash mismatch by replaying the edits
|
|
114
|
-
* against a cached pre-edit snapshot of the file and 3-way-merging the result
|
|
115
|
-
* onto the current on-disk content. Returns `null` when no recovery is possible.
|
|
116
|
-
*/
|
|
117
|
-
export function tryRecoverHashlineWithCache(args: HashlineRecoveryArgs): HashlineRecoveryResult | null {
|
|
118
|
-
const { cache, absolutePath, currentText, fileHash, edits, options } = args;
|
|
119
|
-
const head = cache.get(absolutePath);
|
|
120
|
-
const snapshot = cache.getByHash(absolutePath, fileHash);
|
|
121
|
-
if (!snapshot || snapshot.lines.size === 0) return null;
|
|
122
|
-
|
|
123
|
-
const recoveryWarning = resolveRecoveryWarning(head, snapshot);
|
|
124
|
-
const isSessionChain = !isHeadSnapshot(head, snapshot);
|
|
125
|
-
if (snapshot.fullText !== undefined) {
|
|
126
|
-
const merged = applyEditsToSnapshot(snapshot.fullText, currentText, edits, options, recoveryWarning);
|
|
127
|
-
if (merged !== null) return merged;
|
|
128
|
-
// Session-chain fast-path: prior in-session edit changed the same line(s)
|
|
129
|
-
// the model is now re-targeting with the stale hash. When line counts
|
|
130
|
-
// match, the edits' line numbers still resolve to the right rows — replay
|
|
131
|
-
// onto the current text directly.
|
|
132
|
-
if (isSessionChain) return replaySessionChainOnCurrent(snapshot.fullText, currentText, edits, options);
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const overlayText = buildSparseOverlayText(currentText, snapshot.lines);
|
|
137
|
-
if (computeFileHash(overlayText) !== fileHash) return null;
|
|
138
|
-
return applyEditsToSnapshot(overlayText, currentText, edits, options, recoveryWarning);
|
|
139
|
-
}
|
package/src/hashline/stream.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { formatNumberedLine } from "./hash";
|
|
2
|
-
import type { HashlineStreamOptions } from "./types";
|
|
3
|
-
|
|
4
|
-
interface ResolvedHashlineStreamOptions {
|
|
5
|
-
startLine: number;
|
|
6
|
-
maxChunkLines: number;
|
|
7
|
-
maxChunkBytes: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function resolveHashlineStreamOptions(options: HashlineStreamOptions): ResolvedHashlineStreamOptions {
|
|
11
|
-
return {
|
|
12
|
-
startLine: options.startLine ?? 1,
|
|
13
|
-
maxChunkLines: options.maxChunkLines ?? 200,
|
|
14
|
-
maxChunkBytes: options.maxChunkBytes ?? 64 * 1024,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface HashlineChunkEmitter {
|
|
19
|
-
pushLine: (line: string) => string[];
|
|
20
|
-
flush: () => string | undefined;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function createHashlineChunkEmitter(options: ResolvedHashlineStreamOptions): HashlineChunkEmitter {
|
|
24
|
-
let lineNumber = options.startLine;
|
|
25
|
-
let outLines: string[] = [];
|
|
26
|
-
let outBytes = 0;
|
|
27
|
-
|
|
28
|
-
const flush = (): string | undefined => {
|
|
29
|
-
if (outLines.length === 0) return undefined;
|
|
30
|
-
const chunk = outLines.join("\n");
|
|
31
|
-
outLines = [];
|
|
32
|
-
outBytes = 0;
|
|
33
|
-
return chunk;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const pushLine = (line: string): string[] => {
|
|
37
|
-
const formatted = formatNumberedLine(lineNumber, line);
|
|
38
|
-
lineNumber++;
|
|
39
|
-
|
|
40
|
-
const chunks: string[] = [];
|
|
41
|
-
const sepBytes = outLines.length === 0 ? 0 : 1;
|
|
42
|
-
const lineBytes = Buffer.byteLength(formatted, "utf-8");
|
|
43
|
-
const wouldOverflow =
|
|
44
|
-
outLines.length >= options.maxChunkLines || outBytes + sepBytes + lineBytes > options.maxChunkBytes;
|
|
45
|
-
|
|
46
|
-
if (outLines.length > 0 && wouldOverflow) {
|
|
47
|
-
const flushed = flush();
|
|
48
|
-
if (flushed) chunks.push(flushed);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
outLines.push(formatted);
|
|
52
|
-
outBytes += (outLines.length === 1 ? 0 : 1) + lineBytes;
|
|
53
|
-
|
|
54
|
-
if (outLines.length >= options.maxChunkLines || outBytes >= options.maxChunkBytes) {
|
|
55
|
-
const flushed = flush();
|
|
56
|
-
if (flushed) chunks.push(flushed);
|
|
57
|
-
}
|
|
58
|
-
return chunks;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return { pushLine, flush };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
|
65
|
-
return (
|
|
66
|
-
typeof value === "object" &&
|
|
67
|
-
value !== null &&
|
|
68
|
-
"getReader" in value &&
|
|
69
|
-
typeof (value as { getReader?: unknown }).getReader === "function"
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function* bytesFromReadableStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {
|
|
74
|
-
const reader = stream.getReader();
|
|
75
|
-
try {
|
|
76
|
-
while (true) {
|
|
77
|
-
const { done, value } = await reader.read();
|
|
78
|
-
if (done) return;
|
|
79
|
-
if (value) yield value;
|
|
80
|
-
}
|
|
81
|
-
} finally {
|
|
82
|
-
reader.releaseLock();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function* streamHashLinesFromUtf8(
|
|
87
|
-
source: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,
|
|
88
|
-
options: HashlineStreamOptions = {},
|
|
89
|
-
): AsyncGenerator<string> {
|
|
90
|
-
const resolved = resolveHashlineStreamOptions(options);
|
|
91
|
-
const decoder = new TextDecoder("utf-8");
|
|
92
|
-
const chunks = isReadableStream(source) ? bytesFromReadableStream(source) : source;
|
|
93
|
-
const emitter = createHashlineChunkEmitter(resolved);
|
|
94
|
-
|
|
95
|
-
let pending = "";
|
|
96
|
-
let sawAnyLine = false;
|
|
97
|
-
|
|
98
|
-
for await (const chunk of chunks) {
|
|
99
|
-
pending += decoder.decode(chunk, { stream: true });
|
|
100
|
-
let nl = pending.indexOf("\n");
|
|
101
|
-
while (nl !== -1) {
|
|
102
|
-
const raw = pending.slice(0, nl);
|
|
103
|
-
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
104
|
-
sawAnyLine = true;
|
|
105
|
-
for (const out of emitter.pushLine(line)) yield out;
|
|
106
|
-
pending = pending.slice(nl + 1);
|
|
107
|
-
nl = pending.indexOf("\n");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
pending += decoder.decode();
|
|
112
|
-
if (pending.length > 0) {
|
|
113
|
-
sawAnyLine = true;
|
|
114
|
-
const tail = pending.endsWith("\r") ? pending.slice(0, -1) : pending;
|
|
115
|
-
for (const out of emitter.pushLine(tail)) yield out;
|
|
116
|
-
}
|
|
117
|
-
if (!sawAnyLine) {
|
|
118
|
-
for (const out of emitter.pushLine("")) yield out;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const last = emitter.flush();
|
|
122
|
-
if (last) yield last;
|
|
123
|
-
}
|