fix(hub): fence close must reject info strings per CommonMark spec
The closing fence regex was not checking for an empty info string, allowing e.g. ```python to incorrectly close an open fence. Also adds missing test for tool_execution_update passthrough. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fc0c1781c3
commit
efb1495326
3 changed files with 27 additions and 2 deletions
|
|
@ -223,6 +223,23 @@ describe("BlockChunker", () => {
|
|||
expect(chunk + remainder).toBe(text);
|
||||
});
|
||||
|
||||
it("does not treat fence with info string as closing fence", () => {
|
||||
const chunker = new BlockChunker(cfg({ minChars: 10, maxChars: 500 }));
|
||||
// The second ```python should NOT close the first fence (CommonMark: closing fence has no info string)
|
||||
const text = "Before.\n\n```python\ncode line 1\n```python\ncode line 2\n```\n\nAfter text here.";
|
||||
const result = chunker.tryChunk(text);
|
||||
expect(result).not.toBeNull();
|
||||
const chunk = result!.chunk;
|
||||
// The split should not land between ```python and ```python (inside fence)
|
||||
// It should either be before the fence or after the closing ```
|
||||
const fenceOpens = (chunk.match(/```python/g) || []).length;
|
||||
const fenceCloses = (chunk.match(/^```$/gm) || []).length;
|
||||
if (fenceOpens > 0) {
|
||||
// If chunk includes opening fences, it must include the real close
|
||||
expect(fenceCloses).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
});
|
||||
|
||||
it("handles multiple sequential code blocks", () => {
|
||||
const chunker = new BlockChunker(cfg({ minChars: 10, maxChars: 500 }));
|
||||
const text = "```js\nfoo()\n```\n\n```py\nbar()\n```\n\nEnd.";
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ function detectFenceAt(text: string, upTo: number): FenceInfo | null {
|
|||
if (openFence === null) {
|
||||
// Opening a new fence
|
||||
openFence = { marker, lang };
|
||||
} else if (markerChar === openFence.marker[0] && marker.length >= openFence.marker.length) {
|
||||
// Closing the current fence (same char, at least as many chars)
|
||||
} else if (markerChar === openFence.marker[0] && marker.length >= openFence.marker.length && lang === "") {
|
||||
// Closing the current fence (same char, at least as many chars, no info string per CommonMark)
|
||||
openFence = null;
|
||||
}
|
||||
// Otherwise: different char or shorter marker, not a close — ignore
|
||||
|
|
|
|||
|
|
@ -112,6 +112,14 @@ describe("MessageAggregator", () => {
|
|||
expect(onBlock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes through tool_execution_update immediately", () => {
|
||||
const agg = new MessageAggregator(smallConfig(), onBlock, onPassthrough);
|
||||
const event = { type: "tool_execution_update", toolCallId: "tool-1", content: "output" } as unknown as AgentEvent;
|
||||
agg.handleEvent(event);
|
||||
expect(onPassthrough).toHaveBeenCalledWith(event);
|
||||
expect(onBlock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes through compaction_start immediately", () => {
|
||||
const agg = new MessageAggregator(smallConfig(), onBlock, onPassthrough);
|
||||
const event = makeCompactionStart();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue