Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions cli/bin/javachat.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,24 @@ function renderEnrichmentMarker(markerBody) {
return `\n${title}: ${markerBody.slice(separatorIndex + 1).trim()}\n`;
}

/**
* Resolves the marker close index from a run of closing braces.
*
* For a trailing run like `}}}` this selects the final `}}` so a content brace
* ending the marker body stays in the body instead of leaking as a stray.
* Mirrors the backend's EnrichmentPlaceholderizer.resolveCloseIndexFromBraceRun.
*/
function resolveCloseIndexFromBraceRun(text, runStart) {
let runLength = 0;
while (runStart + runLength < text.length && text[runStart + runLength] === "}") {
runLength++;
}
if (runLength < MARKER_CLOSE.length) {
return runStart;
}
return runStart + (runLength - MARKER_CLOSE.length);
}

/**
* Converts enrichment markers to terminal blocks while text is still streaming.
*
Expand All @@ -593,15 +611,16 @@ function createEnrichmentMarkerRenderer() {
}
printable += heldText.slice(0, openIndex);
heldText = heldText.slice(openIndex);
const closeIndex = heldText.indexOf(MARKER_CLOSE, MARKER_OPEN.length);
if (closeIndex === -1) {
const firstCloseIndex = heldText.indexOf(MARKER_CLOSE, MARKER_OPEN.length);
if (firstCloseIndex === -1) {
if (heldText.length > MARKER_MAX_LENGTH) {
printable += heldText;
heldText = "";
continue;
}
return printable;
}
const closeIndex = resolveCloseIndexFromBraceRun(heldText, firstCloseIndex);
printable += renderEnrichmentMarker(heldText.slice(MARKER_OPEN.length, closeIndex));
heldText = heldText.slice(closeIndex + MARKER_CLOSE.length);
}
Expand Down
61 changes: 61 additions & 0 deletions cli/test/javachat.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,67 @@ test("releases an unterminated enrichment marker instead of swallowing it", asyn
assert.match(cliExecution.standardOutput, /never closed/);
});

test("keeps a trailing content brace inside an enrichment marker body", async (testContext) => {
const apiServer = createServer((request, response) => {
if (request.url === "/api/me") {
response.writeHead(200, { "content-type": "application/json" });
response.end('{"userId":"user_cli"}');
return;
}
response.writeHead(200, { "content-type": "text/event-stream" });
response.end(
'event: text\ndata: {"text":"{{example:an empty object literal is {}}}"}\n\n',
);
});
apiServer.listen(0, "127.0.0.1");
await once(apiServer, "listening");
testContext.after(() => apiServer.close());
const address = apiServer.address();

const cliExecution = await runCli(
["--host", `http://127.0.0.1:${address.port}`, "ask", "What is an empty object?"],
{ JAVACHAT_API_KEY: TEST_API_KEY },
);

assert.equal(cliExecution.exitCode, 0);
assert.match(cliExecution.standardOutput, /Example: an empty object literal is \{\}\n/);
assert.doesNotMatch(cliExecution.standardOutput, /literal is \{\n\}/);
});

test("keeps a brace run's content brace when the marker spans stream chunks", async (testContext) => {
const apiServer = createServer((request, response) => {
if (request.url === "/api/me") {
response.writeHead(200, { "content-type": "application/json" });
response.end('{"userId":"user_cli"}');
return;
}
response.writeHead(200, { "content-type": "text/event-stream" });
response.end(
'event: text\ndata: {"text":"Intro. {{example:try (var scope"}\n\n' +
'event: text\ndata: {"text":" = open()) { doWork(); }}}"}\n\n' +
'event: text\ndata: {"text":" Outro."}\n\n',
);
});
apiServer.listen(0, "127.0.0.1");
await once(apiServer, "listening");
testContext.after(() => apiServer.close());
const address = apiServer.address();

const cliExecution = await runCli(
["--host", `http://127.0.0.1:${address.port}`, "ask", "What is try-with-resources?"],
{ JAVACHAT_API_KEY: TEST_API_KEY },
);

assert.equal(cliExecution.exitCode, 0);
assert.match(cliExecution.standardOutput, /Intro\./);
assert.match(cliExecution.standardOutput, /Outro\./);
assert.match(
cliExecution.standardOutput,
/Example: try \(var scope = open\(\)\) \{ doWork\(\); \}\n/,
);
assert.doesNotMatch(cliExecution.standardOutput, /doWork\(\);\n\}/);
});

test("rejects a successful non-SSE response", async (testContext) => {
const apiServer = createServer((request, response) => {
if (request.url === "/api/me") {
Expand Down