@vibes.diy/call-ai-v2 2.2.0 → 2.2.1-dev.1
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/README.md +46 -9
- package/apply-edits.d.ts +39 -0
- package/apply-edits.js +76 -0
- package/apply-edits.js.map +1 -0
- package/apply-edits.test.d.ts +1 -0
- package/apply-edits.test.js +85 -0
- package/apply-edits.test.js.map +1 -0
- package/block-stream-path.test.d.ts +1 -0
- package/block-stream-path.test.js +91 -0
- package/block-stream-path.test.js.map +1 -0
- package/block-stream.d.ts +14 -0
- package/block-stream.js +16 -0
- package/block-stream.js.map +1 -1
- package/fence-body-parser.d.ts +11 -0
- package/fence-body-parser.js +76 -0
- package/fence-body-parser.js.map +1 -0
- package/fence-body-parser.test.d.ts +1 -0
- package/fence-body-parser.test.js +130 -0
- package/fence-body-parser.test.js.map +1 -0
- package/filesystem-stream.d.ts +107 -0
- package/filesystem-stream.js +151 -0
- package/filesystem-stream.js.map +1 -0
- package/filesystem-stream.test.d.ts +1 -0
- package/filesystem-stream.test.js +170 -0
- package/filesystem-stream.test.js.map +1 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -179,6 +179,23 @@ Parses markdown structure from accumulated content, detecting code fences and im
|
|
|
179
179
|
**Input:** `DeltaStreamMsg`
|
|
180
180
|
**Messages:** `block.begin`, `block.toplevel.begin/line/end`, `block.code.begin/line/end`, `block.image`, `block.end`, `block.stats`
|
|
181
181
|
|
|
182
|
+
`block.code.*` carries an optional `path` field, derived aider-style from the most-recent non-blank toplevel line preceding the fence (if it looks like a relative path with a recognized extension). Falls back to `App.jsx` otherwise.
|
|
183
|
+
|
|
184
|
+
### fence-body-parser.ts
|
|
185
|
+
|
|
186
|
+
Pure function that turns the lines inside a code fence into `Edit[]`. A body with no markers is a single `create`. A body with `<<<<<<< SEARCH` / `=======` / `>>>>>>> REPLACE` markers becomes one or more `replace` edits (multiple sections allowed in one fence).
|
|
187
|
+
|
|
188
|
+
### apply-edits.ts
|
|
189
|
+
|
|
190
|
+
Pure helpers `applyReplace` and `applyEdits`. `applyReplace` first tries an exact match; on failure it falls back to a trailing-whitespace-tolerant match. Result reports `matchKind` (`exact` | `trailing-ws`).
|
|
191
|
+
|
|
192
|
+
### filesystem-stream.ts
|
|
193
|
+
|
|
194
|
+
Aider-style virtual filesystem stage. Sits after `sections-stream`. Owns a `VirtualFS = Map<path, string>` for the life of one streamed turn, seeded from the caller-supplied `seed` (typically the saved `App.jsx`). Each `block.code.end` is parsed via `parseFenceBody`; the resulting edits are applied with `applyEdits`. On success, emits `fs.file.snapshot`. Failed sections (parse errors, missing or ambiguous SEARCH) emit `fs.apply.error` and leave the VFS unchanged. On `block.end`, emits `fs.turn.end` with the final files map.
|
|
195
|
+
|
|
196
|
+
**Input:** `BlockStreamMsg`
|
|
197
|
+
**Messages added:** `fs.file.snapshot`, `fs.apply.error`, `fs.turn.end`
|
|
198
|
+
|
|
182
199
|
## The Passthrough Pattern
|
|
183
200
|
|
|
184
201
|
Streams use `passthrough()` to automatically forward all upstream messages while adding their own:
|
|
@@ -246,21 +263,41 @@ if (isDeltaStats(msg)) console.log("Delta stats:", msg.stats);
|
|
|
246
263
|
if (isBlockStats(msg)) console.log("Block stats:", msg.stats);
|
|
247
264
|
```
|
|
248
265
|
|
|
249
|
-
##
|
|
266
|
+
## Filesystem stage usage
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { createSectionsStream, createFileSystemStream, isFsFileSnapshot, isFsTurnEnd } from "call-ai/v2";
|
|
250
270
|
|
|
251
|
-
|
|
271
|
+
const seed = new Map([["App.jsx", priorAppJsx]]);
|
|
272
|
+
|
|
273
|
+
const pipeline = response.body
|
|
274
|
+
// …line/data/sse/delta stages…
|
|
275
|
+
.pipeThrough(createSectionsStream(streamId, createId))
|
|
276
|
+
.pipeThrough(createFileSystemStream({ streamId, createId, seed }));
|
|
277
|
+
|
|
278
|
+
for await (const msg of pipeline) {
|
|
279
|
+
if (isFsFileSnapshot(msg)) {
|
|
280
|
+
// Update live preview with msg.content for msg.path
|
|
281
|
+
}
|
|
282
|
+
if (isFsTurnEnd(msg)) {
|
|
283
|
+
// Persist msg.files to the session doc
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Current Status
|
|
252
289
|
|
|
253
|
-
|
|
254
|
-
-
|
|
290
|
+
Production: this pipeline is the live streaming path for vibes.diy chat
|
|
291
|
+
([`prompt-chat-section.ts`](../../vibes.diy/api/svc/public/prompt-chat-section.ts)
|
|
292
|
+
pipes the LLM response body through line → data → sse → delta → sections, and
|
|
293
|
+
the client reducer consumes the typed block messages directly).
|
|
255
294
|
|
|
256
|
-
|
|
295
|
+
Also used by:
|
|
257
296
|
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
- Generated vibes legacy callAI wrapper
|
|
297
|
+
- CLI tool (`cli.ts`) for replay/debugging captured SSE files
|
|
298
|
+
- Unit tests across the v2 modules
|
|
261
299
|
|
|
262
300
|
## TODO
|
|
263
301
|
|
|
264
302
|
- [ ] **Chunked image decoding**: Add `createImageDecodeStream` that fetches image URLs, decodes to bytes, and emits `image.begin`/`image.fragment`/`image.end` with shared `imageId` for streaming large images in fixed-size chunks
|
|
265
303
|
- [ ] **Production worker**: Deploy pipeline to Cloudflare Worker with events as network transport, client consumes typed events directly instead of raw SSE
|
|
266
|
-
- [ ] **Migrate vibes.diy chat**: Replace `call-ai` v1 + `segment-parser.ts` with v2 pipeline, consuming typed `isCodeLine`/`isToplevelLine` events instead of regex parsing
|
package/apply-edits.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ApplyReplaceInput {
|
|
2
|
+
readonly source: string;
|
|
3
|
+
readonly search: string;
|
|
4
|
+
readonly replace: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ApplyEditOk {
|
|
7
|
+
readonly ok: true;
|
|
8
|
+
readonly content: string;
|
|
9
|
+
readonly matchKind: "exact" | "trailing-ws";
|
|
10
|
+
}
|
|
11
|
+
export type ApplyEditErrReason = "no-match" | "multiple-match";
|
|
12
|
+
export interface ApplyEditErr {
|
|
13
|
+
readonly ok: false;
|
|
14
|
+
readonly reason: ApplyEditErrReason;
|
|
15
|
+
readonly matchCount: number;
|
|
16
|
+
}
|
|
17
|
+
export type ApplyEditResult = ApplyEditOk | ApplyEditErr;
|
|
18
|
+
export declare function applyReplace(input: ApplyReplaceInput): ApplyEditResult;
|
|
19
|
+
export interface ReplaceEdit {
|
|
20
|
+
readonly op: "replace";
|
|
21
|
+
readonly search: string;
|
|
22
|
+
readonly replace: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CreateEdit {
|
|
25
|
+
readonly op: "create";
|
|
26
|
+
readonly content: string;
|
|
27
|
+
}
|
|
28
|
+
export type Edit = ReplaceEdit | CreateEdit;
|
|
29
|
+
export interface ApplyEditsError {
|
|
30
|
+
readonly index: number;
|
|
31
|
+
readonly reason: ApplyEditErrReason;
|
|
32
|
+
readonly matchCount: number;
|
|
33
|
+
readonly search: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ApplyEditsResult {
|
|
36
|
+
readonly content: string;
|
|
37
|
+
readonly errors: readonly ApplyEditsError[];
|
|
38
|
+
}
|
|
39
|
+
export declare function applyEdits(seed: string, edits: readonly Edit[]): ApplyEditsResult;
|
package/apply-edits.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function rstripLines(s) {
|
|
2
|
+
return s
|
|
3
|
+
.split("\n")
|
|
4
|
+
.map((l) => l.replace(/[ \t]+$/, ""))
|
|
5
|
+
.join("\n");
|
|
6
|
+
}
|
|
7
|
+
function findAllOccurrences(haystack, needle) {
|
|
8
|
+
const hits = [];
|
|
9
|
+
if (needle.length === 0)
|
|
10
|
+
return hits;
|
|
11
|
+
let from = 0;
|
|
12
|
+
while (true) {
|
|
13
|
+
const idx = haystack.indexOf(needle, from);
|
|
14
|
+
if (idx === -1)
|
|
15
|
+
break;
|
|
16
|
+
hits.push(idx);
|
|
17
|
+
from = idx + needle.length;
|
|
18
|
+
}
|
|
19
|
+
return hits;
|
|
20
|
+
}
|
|
21
|
+
export function applyReplace(input) {
|
|
22
|
+
const { source, search, replace } = input;
|
|
23
|
+
if (search.length === 0) {
|
|
24
|
+
return { ok: false, reason: "no-match", matchCount: 0 };
|
|
25
|
+
}
|
|
26
|
+
const exact = findAllOccurrences(source, search);
|
|
27
|
+
if (exact.length === 1) {
|
|
28
|
+
const idx = exact[0];
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
matchKind: "exact",
|
|
32
|
+
content: source.slice(0, idx) + replace + source.slice(idx + search.length),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (exact.length > 1) {
|
|
36
|
+
return { ok: false, reason: "multiple-match", matchCount: exact.length };
|
|
37
|
+
}
|
|
38
|
+
const sourceTrimmed = rstripLines(source);
|
|
39
|
+
const searchTrimmed = rstripLines(search);
|
|
40
|
+
const tolerant = findAllOccurrences(sourceTrimmed, searchTrimmed);
|
|
41
|
+
if (tolerant.length === 1) {
|
|
42
|
+
const idx = tolerant[0];
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
matchKind: "trailing-ws",
|
|
46
|
+
content: sourceTrimmed.slice(0, idx) + replace + sourceTrimmed.slice(idx + searchTrimmed.length),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (tolerant.length > 1) {
|
|
50
|
+
return { ok: false, reason: "multiple-match", matchCount: tolerant.length };
|
|
51
|
+
}
|
|
52
|
+
return { ok: false, reason: "no-match", matchCount: 0 };
|
|
53
|
+
}
|
|
54
|
+
export function applyEdits(seed, edits) {
|
|
55
|
+
let content = seed;
|
|
56
|
+
const errors = [];
|
|
57
|
+
edits.forEach((edit, index) => {
|
|
58
|
+
if (edit.op === "create") {
|
|
59
|
+
content = edit.content;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const r = applyReplace({ source: content, search: edit.search, replace: edit.replace });
|
|
63
|
+
if (r.ok) {
|
|
64
|
+
content = r.content;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
errors.push({
|
|
68
|
+
index,
|
|
69
|
+
reason: r.reason,
|
|
70
|
+
matchCount: r.matchCount,
|
|
71
|
+
search: edit.search,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return { content, errors };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=apply-edits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-edits.js","sourceRoot":"","sources":["../jsr/apply-edits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { applyEdits, applyReplace } from "./apply-edits.js";
|
|
3
|
+
describe("applyReplace", () => {
|
|
4
|
+
it("replaces a unique exact match", () => {
|
|
5
|
+
const r = applyReplace({ source: "hello world", search: "world", replace: "there" });
|
|
6
|
+
expect(r).toEqual({ ok: true, matchKind: "exact", content: "hello there" });
|
|
7
|
+
});
|
|
8
|
+
it("fails with no-match when search is absent", () => {
|
|
9
|
+
const r = applyReplace({ source: "hello world", search: "xyz", replace: "abc" });
|
|
10
|
+
expect(r).toEqual({ ok: false, reason: "no-match", matchCount: 0 });
|
|
11
|
+
});
|
|
12
|
+
it("fails with multiple-match when search appears twice", () => {
|
|
13
|
+
const r = applyReplace({ source: "ab ab", search: "ab", replace: "cd" });
|
|
14
|
+
expect(r.ok).toBe(false);
|
|
15
|
+
if (r.ok === false) {
|
|
16
|
+
expect(r.reason).toBe("multiple-match");
|
|
17
|
+
expect(r.matchCount).toBe(2);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
it("treats empty search as no-match", () => {
|
|
21
|
+
const r = applyReplace({ source: "hello", search: "", replace: "x" });
|
|
22
|
+
expect(r).toEqual({ ok: false, reason: "no-match", matchCount: 0 });
|
|
23
|
+
});
|
|
24
|
+
it("preserves whitespace and indentation on exact match", () => {
|
|
25
|
+
const r = applyReplace({
|
|
26
|
+
source: "line1\n line2\nline3",
|
|
27
|
+
search: " line2",
|
|
28
|
+
replace: " LINE2",
|
|
29
|
+
});
|
|
30
|
+
expect(r).toEqual({ ok: true, matchKind: "exact", content: "line1\n LINE2\nline3" });
|
|
31
|
+
});
|
|
32
|
+
it("falls back to trailing-whitespace-tolerant match", () => {
|
|
33
|
+
const r = applyReplace({ source: "foo \nbar\nbaz", search: "foo\nbar", replace: "FOO\nBAR" });
|
|
34
|
+
expect(r.ok).toBe(true);
|
|
35
|
+
if (r.ok === true) {
|
|
36
|
+
expect(r.matchKind).toBe("trailing-ws");
|
|
37
|
+
expect(r.content).toBe("FOO\nBAR\nbaz");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
it("tolerant fallback still reports multiple-match", () => {
|
|
41
|
+
const r = applyReplace({ source: "foo \nfoo\t\nend", search: "foo", replace: "X" });
|
|
42
|
+
expect(r.ok).toBe(false);
|
|
43
|
+
if (r.ok === false)
|
|
44
|
+
expect(r.reason).toBe("multiple-match");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("applyEdits", () => {
|
|
48
|
+
it("applies a create then a sequence of replaces", () => {
|
|
49
|
+
const edits = [
|
|
50
|
+
{ op: "create", content: "const a = 1;\nconst b = 2;\n" },
|
|
51
|
+
{ op: "replace", search: "const a = 1;", replace: "const a = 10;" },
|
|
52
|
+
{ op: "replace", search: "const b = 2;", replace: "const b = 20;" },
|
|
53
|
+
];
|
|
54
|
+
const r = applyEdits("", edits);
|
|
55
|
+
expect(r.content).toBe("const a = 10;\nconst b = 20;\n");
|
|
56
|
+
expect(r.errors).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
it("uses seed when first edit is a replace", () => {
|
|
59
|
+
const seed = "hello world";
|
|
60
|
+
const r = applyEdits(seed, [{ op: "replace", search: "world", replace: "there" }]);
|
|
61
|
+
expect(r.content).toBe("hello there");
|
|
62
|
+
expect(r.errors).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
it("collects failures and continues with unchanged source", () => {
|
|
65
|
+
const seed = "one two three";
|
|
66
|
+
const r = applyEdits(seed, [
|
|
67
|
+
{ op: "replace", search: "missing", replace: "x" },
|
|
68
|
+
{ op: "replace", search: "two", replace: "TWO" },
|
|
69
|
+
{ op: "replace", search: "e", replace: "E" },
|
|
70
|
+
]);
|
|
71
|
+
expect(r.content).toBe("one TWO three");
|
|
72
|
+
expect(r.errors).toHaveLength(2);
|
|
73
|
+
expect(r.errors[0]).toMatchObject({ index: 0, reason: "no-match" });
|
|
74
|
+
expect(r.errors[1]).toMatchObject({ index: 2, reason: "multiple-match" });
|
|
75
|
+
});
|
|
76
|
+
it("create after replaces resets content", () => {
|
|
77
|
+
const r = applyEdits("original", [
|
|
78
|
+
{ op: "replace", search: "original", replace: "edited" },
|
|
79
|
+
{ op: "create", content: "fresh" },
|
|
80
|
+
]);
|
|
81
|
+
expect(r.content).toBe("fresh");
|
|
82
|
+
expect(r.errors).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=apply-edits.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-edits.test.js","sourceRoot":"","sources":["../jsr/apply-edits.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { stream2array } from "@adviser/cement";
|
|
3
|
+
import { createBlockStream, isCodeBegin, isCodeLine, isCodeEnd } from "./block-stream.js";
|
|
4
|
+
const innerStreamId = "inner";
|
|
5
|
+
const streamId = "test";
|
|
6
|
+
function makeLineEvents(lines) {
|
|
7
|
+
const events = [{ type: "line.begin", streamId: innerStreamId, timestamp: new Date() }];
|
|
8
|
+
lines.forEach((content, i) => {
|
|
9
|
+
events.push({
|
|
10
|
+
type: "line.line",
|
|
11
|
+
streamId: innerStreamId,
|
|
12
|
+
content,
|
|
13
|
+
lineNr: i + 1,
|
|
14
|
+
timestamp: new Date(),
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
events.push({ type: "line.end", streamId: innerStreamId, totalLines: lines.length, timestamp: new Date() });
|
|
18
|
+
return events;
|
|
19
|
+
}
|
|
20
|
+
async function runBlockStream(lines) {
|
|
21
|
+
const events = makeLineEvents(lines);
|
|
22
|
+
const input = new ReadableStream({
|
|
23
|
+
start(controller) {
|
|
24
|
+
events.forEach((e) => controller.enqueue(e));
|
|
25
|
+
controller.close();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
let idCounter = 0;
|
|
29
|
+
const createId = () => `id-${++idCounter}`;
|
|
30
|
+
return stream2array(input.pipeThrough(createBlockStream(streamId, innerStreamId, createId)));
|
|
31
|
+
}
|
|
32
|
+
describe("block-stream path-line tracking", () => {
|
|
33
|
+
it("attaches path 'App.jsx' (default) when no path line precedes the fence", async () => {
|
|
34
|
+
const chunks = await runBlockStream(["Some intro prose.", "```jsx", "const x = 1;", "```"]);
|
|
35
|
+
const begin = chunks.find((c) => isCodeBegin(c));
|
|
36
|
+
const end = chunks.find((c) => isCodeEnd(c));
|
|
37
|
+
expect(begin?.path).toBe("App.jsx");
|
|
38
|
+
expect(end?.path).toBe("App.jsx");
|
|
39
|
+
});
|
|
40
|
+
it("attaches the preceding path-line as the path", async () => {
|
|
41
|
+
const chunks = await runBlockStream(["Building a layout.", "App.jsx", "```jsx", "const x = 1;", "```"]);
|
|
42
|
+
const begin = chunks.find((c) => isCodeBegin(c));
|
|
43
|
+
const end = chunks.find((c) => isCodeEnd(c));
|
|
44
|
+
expect(begin?.path).toBe("App.jsx");
|
|
45
|
+
expect(end?.path).toBe("App.jsx");
|
|
46
|
+
});
|
|
47
|
+
it("recognizes nested-path filenames with allowed extensions", async () => {
|
|
48
|
+
const chunks = await runBlockStream(["src/components/Foo.tsx", "```tsx", "export const Foo = () => null;", "```"]);
|
|
49
|
+
const begin = chunks.find((c) => isCodeBegin(c));
|
|
50
|
+
expect(begin?.path).toBe("src/components/Foo.tsx");
|
|
51
|
+
});
|
|
52
|
+
it("ignores a non-path-looking preceding line", async () => {
|
|
53
|
+
const chunks = await runBlockStream(["Here is the code:", "```jsx", "const x = 1;", "```"]);
|
|
54
|
+
const begin = chunks.find((c) => isCodeBegin(c));
|
|
55
|
+
expect(begin?.path).toBe("App.jsx");
|
|
56
|
+
});
|
|
57
|
+
it("stamps path on every code.line within the block", async () => {
|
|
58
|
+
const chunks = await runBlockStream(["App.jsx", "```jsx", "const a = 1;", "const b = 2;", "```"]);
|
|
59
|
+
const lines = chunks.filter((c) => isCodeLine(c));
|
|
60
|
+
expect(lines).toHaveLength(2);
|
|
61
|
+
for (const l of lines)
|
|
62
|
+
expect(l.path).toBe("App.jsx");
|
|
63
|
+
});
|
|
64
|
+
it("uses the most recent non-blank toplevel line, not earlier ones", async () => {
|
|
65
|
+
const chunks = await runBlockStream(["First paragraph.", "App.jsx", "", "```jsx", "const x = 1;", "```"]);
|
|
66
|
+
const begin = chunks.find((c) => isCodeBegin(c));
|
|
67
|
+
expect(begin?.path).toBe("App.jsx");
|
|
68
|
+
});
|
|
69
|
+
it("does not carry a path-line forward across multiple blocks if the second has its own toplevel section", async () => {
|
|
70
|
+
const chunks = await runBlockStream([
|
|
71
|
+
"App.jsx",
|
|
72
|
+
"```jsx",
|
|
73
|
+
"const a = 1;",
|
|
74
|
+
"```",
|
|
75
|
+
"Now some more prose without a path line.",
|
|
76
|
+
"```jsx",
|
|
77
|
+
"const b = 2;",
|
|
78
|
+
"```",
|
|
79
|
+
]);
|
|
80
|
+
const begins = chunks.filter((c) => isCodeBegin(c));
|
|
81
|
+
expect(begins).toHaveLength(2);
|
|
82
|
+
expect(begins[0].path).toBe("App.jsx");
|
|
83
|
+
expect(begins[1].path).toBe("App.jsx");
|
|
84
|
+
});
|
|
85
|
+
it("rejects a path line whose extension is not in the allowed set", async () => {
|
|
86
|
+
const chunks = await runBlockStream(["foo.exe", "```", "binary", "```"]);
|
|
87
|
+
const begin = chunks.find((c) => isCodeBegin(c));
|
|
88
|
+
expect(begin?.path).toBe("App.jsx");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=block-stream-path.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-stream-path.test.js","sourceRoot":"","sources":["../jsr/block-stream-path.test.ts"],"names":[],"mappings":"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"}
|
package/block-stream.d.ts
CHANGED
|
@@ -180,6 +180,7 @@ export declare const CodeBeginMsg: import("arktype/internal/variants/object.ts")
|
|
|
180
180
|
type: "block.code.begin";
|
|
181
181
|
sectionId: string;
|
|
182
182
|
lang: string;
|
|
183
|
+
path?: string | undefined;
|
|
183
184
|
}, {}>;
|
|
184
185
|
export declare const CodeLineMsg: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
185
186
|
blockId: string;
|
|
@@ -192,6 +193,7 @@ export declare const CodeLineMsg: import("arktype/internal/variants/object.ts").
|
|
|
192
193
|
type: "block.code.line";
|
|
193
194
|
sectionId: string;
|
|
194
195
|
lang: string;
|
|
196
|
+
path?: string | undefined;
|
|
195
197
|
}, {}>;
|
|
196
198
|
export declare const CodeEndMsg: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
197
199
|
blockId: string;
|
|
@@ -207,6 +209,7 @@ export declare const CodeEndMsg: import("arktype/internal/variants/object.ts").O
|
|
|
207
209
|
type: "block.code.end";
|
|
208
210
|
sectionId: string;
|
|
209
211
|
lang: string;
|
|
212
|
+
path?: string | undefined;
|
|
210
213
|
}, {}>;
|
|
211
214
|
export declare const BlockImageMsg: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
212
215
|
blockId: string;
|
|
@@ -299,6 +302,7 @@ export declare const CodeMsg: import("arktype/internal/variants/object.ts").Obje
|
|
|
299
302
|
type: "block.code.begin";
|
|
300
303
|
sectionId: string;
|
|
301
304
|
lang: string;
|
|
305
|
+
path?: string | undefined;
|
|
302
306
|
} | {
|
|
303
307
|
blockId: string;
|
|
304
308
|
streamId: string;
|
|
@@ -310,6 +314,7 @@ export declare const CodeMsg: import("arktype/internal/variants/object.ts").Obje
|
|
|
310
314
|
type: "block.code.line";
|
|
311
315
|
sectionId: string;
|
|
312
316
|
lang: string;
|
|
317
|
+
path?: string | undefined;
|
|
313
318
|
} | {
|
|
314
319
|
blockId: string;
|
|
315
320
|
streamId: string;
|
|
@@ -324,6 +329,7 @@ export declare const CodeMsg: import("arktype/internal/variants/object.ts").Obje
|
|
|
324
329
|
type: "block.code.end";
|
|
325
330
|
sectionId: string;
|
|
326
331
|
lang: string;
|
|
332
|
+
path?: string | undefined;
|
|
327
333
|
}, {}>;
|
|
328
334
|
export declare const LineMsg: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
329
335
|
blockId: string;
|
|
@@ -346,6 +352,7 @@ export declare const LineMsg: import("arktype/internal/variants/object.ts").Obje
|
|
|
346
352
|
type: "block.code.line";
|
|
347
353
|
sectionId: string;
|
|
348
354
|
lang: string;
|
|
355
|
+
path?: string | undefined;
|
|
349
356
|
}, {}>;
|
|
350
357
|
export declare const BeginMsg: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
351
358
|
blockId: string;
|
|
@@ -364,6 +371,7 @@ export declare const BeginMsg: import("arktype/internal/variants/object.ts").Obj
|
|
|
364
371
|
type: "block.code.begin";
|
|
365
372
|
sectionId: string;
|
|
366
373
|
lang: string;
|
|
374
|
+
path?: string | undefined;
|
|
367
375
|
}, {}>;
|
|
368
376
|
export declare const BlockStreamMsg: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
369
377
|
blockId: string;
|
|
@@ -459,6 +467,7 @@ export declare const BlockStreamMsg: import("arktype/internal/variants/object.ts
|
|
|
459
467
|
type: "block.code.begin";
|
|
460
468
|
sectionId: string;
|
|
461
469
|
lang: string;
|
|
470
|
+
path?: string | undefined;
|
|
462
471
|
} | {
|
|
463
472
|
blockId: string;
|
|
464
473
|
streamId: string;
|
|
@@ -470,6 +479,7 @@ export declare const BlockStreamMsg: import("arktype/internal/variants/object.ts
|
|
|
470
479
|
type: "block.code.line";
|
|
471
480
|
sectionId: string;
|
|
472
481
|
lang: string;
|
|
482
|
+
path?: string | undefined;
|
|
473
483
|
} | {
|
|
474
484
|
blockId: string;
|
|
475
485
|
streamId: string;
|
|
@@ -484,6 +494,7 @@ export declare const BlockStreamMsg: import("arktype/internal/variants/object.ts
|
|
|
484
494
|
type: "block.code.end";
|
|
485
495
|
sectionId: string;
|
|
486
496
|
lang: string;
|
|
497
|
+
path?: string | undefined;
|
|
487
498
|
} | {
|
|
488
499
|
blockId: string;
|
|
489
500
|
streamId: string;
|
|
@@ -627,6 +638,7 @@ export declare const BlockMsgs: import("arktype/internal/variants/object.ts").Ob
|
|
|
627
638
|
type: "block.code.begin";
|
|
628
639
|
sectionId: string;
|
|
629
640
|
lang: string;
|
|
641
|
+
path?: string | undefined;
|
|
630
642
|
} | {
|
|
631
643
|
blockId: string;
|
|
632
644
|
streamId: string;
|
|
@@ -638,6 +650,7 @@ export declare const BlockMsgs: import("arktype/internal/variants/object.ts").Ob
|
|
|
638
650
|
type: "block.code.line";
|
|
639
651
|
sectionId: string;
|
|
640
652
|
lang: string;
|
|
653
|
+
path?: string | undefined;
|
|
641
654
|
} | {
|
|
642
655
|
blockId: string;
|
|
643
656
|
streamId: string;
|
|
@@ -652,6 +665,7 @@ export declare const BlockMsgs: import("arktype/internal/variants/object.ts").Ob
|
|
|
652
665
|
type: "block.code.end";
|
|
653
666
|
sectionId: string;
|
|
654
667
|
lang: string;
|
|
668
|
+
path?: string | undefined;
|
|
655
669
|
} | {
|
|
656
670
|
blockId: string;
|
|
657
671
|
streamId: string;
|
package/block-stream.js
CHANGED
|
@@ -87,11 +87,13 @@ export const CodeBeginMsg = type({
|
|
|
87
87
|
type: "'block.code.begin'",
|
|
88
88
|
sectionId: "string",
|
|
89
89
|
lang: "string",
|
|
90
|
+
"path?": "string",
|
|
90
91
|
}).and(BlockBase);
|
|
91
92
|
export const CodeLineMsg = type({
|
|
92
93
|
type: "'block.code.line'",
|
|
93
94
|
sectionId: "string",
|
|
94
95
|
lang: "string",
|
|
96
|
+
"path?": "string",
|
|
95
97
|
})
|
|
96
98
|
.and(BlockBase)
|
|
97
99
|
.and(BlockLine);
|
|
@@ -99,6 +101,7 @@ export const CodeEndMsg = type({
|
|
|
99
101
|
type: "'block.code.end'",
|
|
100
102
|
sectionId: "string",
|
|
101
103
|
lang: "string",
|
|
104
|
+
"path?": "string",
|
|
102
105
|
})
|
|
103
106
|
.and(BlockBase)
|
|
104
107
|
.and(BlockStatsBox);
|
|
@@ -138,6 +141,8 @@ export const isBlockImage = (msg, streamId) => !(BlockImageMsg(msg) instanceof t
|
|
|
138
141
|
export const isBlockStreamMsg = (msg, streamId) => !(BlockStreamMsg(msg) instanceof type.errors) && (!streamId || msg.streamId === streamId);
|
|
139
142
|
const CODE_FENCE_START = /^```(\w*)$/;
|
|
140
143
|
const CODE_FENCE_END = /^```$/;
|
|
144
|
+
const PATH_LINE = /^[\w\-./]+\.(?:jsx?|tsx?|mjs|cjs|md|json|html|css)$/;
|
|
145
|
+
const DEFAULT_PATH = "App.jsx";
|
|
141
146
|
function addStat(target, source) {
|
|
142
147
|
target.lines += source.lines;
|
|
143
148
|
target.bytes += source.bytes;
|
|
@@ -153,6 +158,8 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
153
158
|
let sectionStarted = false;
|
|
154
159
|
let currentLang = "";
|
|
155
160
|
let currentSectionId = "";
|
|
161
|
+
let currentPath = DEFAULT_PATH;
|
|
162
|
+
let lastToplevelLine = "";
|
|
156
163
|
const toplevelStat = { lines: 0, bytes: 0, cnt: 0 };
|
|
157
164
|
const codeStat = { lines: 0, bytes: 0, cnt: 0 };
|
|
158
165
|
const imageStat = { lines: 0, bytes: 0, cnt: 0 };
|
|
@@ -246,6 +253,9 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
246
253
|
}
|
|
247
254
|
mode = "code";
|
|
248
255
|
currentLang = fenceStartMatch[1] || "";
|
|
256
|
+
const trimmedPath = lastToplevelLine.trim();
|
|
257
|
+
currentPath = PATH_LINE.test(trimmedPath) ? trimmedPath : DEFAULT_PATH;
|
|
258
|
+
lastToplevelLine = "";
|
|
249
259
|
sectionStarted = true;
|
|
250
260
|
currentSectionId = createId();
|
|
251
261
|
blockStat = { lines: 0, bytes: 0, cnt: 0 };
|
|
@@ -253,6 +263,7 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
253
263
|
controller.enqueue({
|
|
254
264
|
type: "block.code.begin",
|
|
255
265
|
lang: currentLang.toLowerCase(),
|
|
266
|
+
path: currentPath,
|
|
256
267
|
timestamp: new Date(),
|
|
257
268
|
sectionId: currentSectionId,
|
|
258
269
|
blockId,
|
|
@@ -277,6 +288,8 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
277
288
|
});
|
|
278
289
|
}
|
|
279
290
|
blockStat.bytes += content.length;
|
|
291
|
+
if (content.trim().length > 0)
|
|
292
|
+
lastToplevelLine = content;
|
|
280
293
|
controller.enqueue({
|
|
281
294
|
type: "block.toplevel.line",
|
|
282
295
|
timestamp: new Date(),
|
|
@@ -306,6 +319,7 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
306
319
|
seq: seq++,
|
|
307
320
|
blockNr: blockNr++,
|
|
308
321
|
lang: currentLang.toLowerCase(),
|
|
322
|
+
path: currentPath,
|
|
309
323
|
stats: blockStat,
|
|
310
324
|
});
|
|
311
325
|
mode = "toplevel";
|
|
@@ -316,6 +330,7 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
316
330
|
controller.enqueue({
|
|
317
331
|
type: "block.code.line",
|
|
318
332
|
lang: currentLang.toLowerCase(),
|
|
333
|
+
path: currentPath,
|
|
319
334
|
timestamp: new Date(),
|
|
320
335
|
sectionId: currentSectionId,
|
|
321
336
|
lineNr: blockStat.lines++,
|
|
@@ -359,6 +374,7 @@ export function createBlockStream(streamId, innerStreamId, createId) {
|
|
|
359
374
|
blockId,
|
|
360
375
|
streamId,
|
|
361
376
|
lang: currentLang.toLowerCase(),
|
|
377
|
+
path: currentPath,
|
|
362
378
|
sectionId: currentSectionId,
|
|
363
379
|
seq: seq++,
|
|
364
380
|
blockNr: blockNr++,
|