Skip to content

Commit a6ea398

Browse files
author
Bertan Ari
committed
test(vscode-lm): cover narration newline variant and pin failure-path behaviour
1 parent 930674d commit a6ea398

1 file changed

Lines changed: 84 additions & 9 deletions

File tree

‎src/api/providers/__tests__/vscode-lm.spec.ts‎

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,81 @@ describe("VsCodeLmHandler", () => {
414414
])
415415
})
416416

417+
it("discards buffered text and calls on stream failure but preserves earlier text", async () => {
418+
const streamError = new Error("Stream failed")
419+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
420+
stream: (async function* () {
421+
yield new vscode.LanguageModelTextPart("Thinking. ")
422+
yield new vscode.LanguageModelTextPart(
423+
'<function_calls><invoke name="calculator"><parameter name="operation">add</parameter></invoke></function_calls>',
424+
)
425+
yield new vscode.LanguageModelTextPart("Buffered narration.")
426+
throw streamError
427+
})(),
428+
})
429+
430+
const stream = handler.createMessage("system", [{ role: "user", content: "hi" }], {
431+
taskId: "test-task",
432+
tools: salvageTools,
433+
})
434+
435+
await expect(stream.next()).resolves.toEqual({
436+
done: false,
437+
value: { type: "text", text: "Thinking. " },
438+
})
439+
await expect(stream.next()).rejects.toBe(streamError)
440+
await expect(stream.next()).resolves.toEqual({ done: true, value: undefined })
441+
})
442+
443+
it("discards buffered text and calls when the stream is cancelled", async () => {
444+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
445+
stream: (async function* () {
446+
yield new vscode.LanguageModelTextPart(
447+
'<function_calls><invoke name="calculator"><parameter name="operation">add</parameter></invoke></function_calls>',
448+
)
449+
yield new vscode.LanguageModelTextPart("Buffered narration.")
450+
throw new vscode.CancellationError()
451+
})(),
452+
})
453+
454+
const stream = handler.createMessage("system", [{ role: "user", content: "hi" }], {
455+
taskId: "test-task",
456+
tools: salvageTools,
457+
})
458+
459+
await expect(stream.next()).rejects.toThrow(
460+
new Error("Zoo Code <Language Model API>: Request cancelled by user"),
461+
)
462+
await expect(stream.next()).resolves.toEqual({ done: true, value: undefined })
463+
})
464+
465+
it("yields a native tool call before a subsequent stream failure", async () => {
466+
const streamError = new Error("Stream failed after native call")
467+
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
468+
stream: (async function* () {
469+
yield new vscode.LanguageModelToolCallPart("native-1", "calculator", { operation: "add" })
470+
throw streamError
471+
})(),
472+
})
473+
474+
const stream = handler.createMessage("system", [{ role: "user", content: "hi" }], {
475+
taskId: "test-task",
476+
tools: salvageTools,
477+
})
478+
479+
await expect(stream.next()).resolves.toEqual({
480+
done: false,
481+
value: {
482+
type: "tool_call",
483+
id: "native-1",
484+
name: "calculator",
485+
arguments: '{"operation":"add"}',
486+
},
487+
})
488+
await expect(stream.next()).rejects.toBe(streamError)
489+
await expect(stream.next()).resolves.toEqual({ done: true, value: undefined })
490+
})
491+
417492
it("recovers a null-only declared parameter as JSON null through createMessage", async () => {
418493
mockLanguageModelChat.sendRequest.mockResolvedValueOnce({
419494
stream: (async function* () {
@@ -586,20 +661,20 @@ describe("VsCodeLmHandler", () => {
586661
expect(streamedText).toBe(parts.join(""))
587662
})
588663

589-
it("keeps a narrated block quoted when the cap splits the stream after its close tag", async () => {
590-
// Cutting the drained span at `</invoke>` hid the trailing cue, so the streaming path
591-
// recovered a call the one-shot path refuses.
664+
it.each(["", "\n"])("preserves a narrated block and later text with line ending %j", async (lineEnding) => {
665+
// The trailing cue must remain visible when deciding whether the block is quoted.
592666
const block = `<function${"_calls"}><in${"voke"} name="calculator"><parameter name="operation">add</parameter></in${"voke"}>`
593667
const narration = ` is what you must never emit`
594-
const chunks = await collect([block + narration, "x".repeat(270 * 1024)])
668+
const filler = "x".repeat(270 * 1024)
669+
const chunks = await collect([block + narration + lineEnding, filler])
595670

596671
expect(chunks.filter((chunk) => chunk.type === "tool_call")).toEqual([])
597672
expect(
598-
extractLeakedToolCalls(
599-
block + narration,
600-
new Map([["calculator", salvageTools[0].function.parameters]]),
601-
).calls,
602-
).toEqual([])
673+
chunks
674+
.filter((chunk) => chunk.type === "text")
675+
.map((chunk) => chunk.text)
676+
.join(""),
677+
).toBe(block + narration + lineEnding + filler)
603678
})
604679

605680
it.each([

0 commit comments

Comments
 (0)