File size: 2,453 Bytes
ff49634
 
 
df94b74
ff49634
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df94b74
 
 
 
 
 
 
ff49634
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { describe, expect, it } from "bun:test";
import {
  extractContentFromCompletionBody,
  isLikelyTruncatedJson,
  parseAiJsonResponse,
  stripReasoningBlocks,
} from "../parse-response";

describe("stripReasoningBlocks", () => {
  it("removes closed redacted_thinking blocks", () => {
    const input = "<think>plan</think>\n{\"ok\":true}";
    expect(stripReasoningBlocks(input)).toBe('{"ok":true}');
  });

  it("removes think blocks", () => {
    const input = ["<", "think", ">planning</", "think", ">\n", '{"ok":true}'].join("");
    expect(stripReasoningBlocks(input)).toBe('{"ok":true}');
  });

  it("removes unclosed thinking at start", () => {
    const input = "<think>still streaming...";
    expect(stripReasoningBlocks(input)).toBe("");
  });

  it("does not treat XML-like tags in JSON as reasoning", () => {
    const input = '{"questionText":"<b>highlight</b>"}';
    expect(stripReasoningBlocks(input)).toBe(input);
  });
});

describe("parseAiJsonResponse", () => {
  it("parses JSON after reasoning preamble", () => {
    const parsed = parseAiJsonResponse(
      "<think>planning</think>\n```json\n{\"questions\":[]}\n```",
    );
    expect(parsed).toEqual({ questions: [] });
  });

  it("extracts JSON object embedded in prose", () => {
    const parsed = parseAiJsonResponse(
      "Here is the result:\n{\"questions\":[{\"format\":\"multiple_choice\"}]}\nThanks!",
    );
    expect(parsed).toEqual({ questions: [{ format: "multiple_choice" }] });
  });

  it("throws on empty content after stripping", () => {
    expect(() => parseAiJsonResponse("<think>only thinking")).toThrow(
      "Empty response from AI",
    );
  });

  it("detects truncated JSON and throws a clear truncation error", () => {
    const truncated =
      '{"title":"The Global Rise of Coffee Culture","passage":"Coffee has a rich history in the highl';
    expect(isLikelyTruncatedJson(truncated)).toBe(true);
    expect(() => parseAiJsonResponse(truncated)).toThrow("JSON response was truncated");
  });
});

describe("extractContentFromCompletionBody", () => {
  it("reads non-stream chat completion JSON", () => {
    const body = JSON.stringify({
      choices: [{ message: { content: '{"questions":[]}' } }],
    });
    expect(extractContentFromCompletionBody(body)).toBe('{"questions":[]}');
  });

  it("returns null for non-JSON bodies", () => {
    expect(extractContentFromCompletionBody("<html>error</html>")).toBeNull();
  });
});