braiin
Version:
Behavioral Reasoning AI for Intelligent Navigation
164 lines (163 loc) • 5.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAnswerStreamFilter = exports.STREAM_END_MARKER = void 0;
exports.STREAM_END_MARKER = '[[END]]';
/**
* Builds a stateful delta filter that streams the value of the top-level
* `"answer"` key of a `finish` action, decoding JSON string escapes on the fly.
*
* The model keeps emitting strict JSON (`{"action":"finish","answer":"..."}`),
* so `enforceJsonOutput` can stay ON — there is no plain-text "tail" format and
* no way for protocol JSON or reasoning to leak into the stream: only the
* characters inside the `answer` string are forwarded to `onToken`, as soon as
* each delta arrives (the only buffered part is the `{"action":..,"answer":"`
* envelope prefix).
*
* Steps without a top-level `answer` key (describe/call/abort) emit nothing,
* which makes the filter self-gating across the whole reasoning chain.
*
* Create a fresh filter per LLM call — state is not meant to be reused.
*/
const createAnswerStreamFilter = (onToken) => {
// Structural scan state (everything outside the answer value).
const stack = [];
let expectKey = false; // at the current object scope, the next string is a key
let inString = false; // scanning a non-answer string token
let strEscaped = false; // previous char inside that string was a backslash
let curString = ''; // accumulates the current string token (key candidate)
let pendingAnswer = false; // root "answer" key seen; its value string is the answer
// Answer-streaming state (inside the answer value string).
let streaming = false;
let ansEscaped = false;
let uniRemaining = 0; // hex digits left to read for a \uXXXX escape
let uniHex = '';
let done = false;
const emitEscaped = (c) => {
ansEscaped = false;
switch (c) {
case 'n':
onToken('\n');
break;
case 't':
onToken('\t');
break;
case 'r':
onToken('\r');
break;
case 'b':
onToken('\b');
break;
case 'f':
onToken('\f');
break;
case '"':
onToken('"');
break;
case '\\':
onToken('\\');
break;
case '/':
onToken('/');
break;
case 'u':
uniRemaining = 4;
uniHex = '';
break;
default: onToken(c); // unknown escape: forward literally
}
};
const feedChar = (c) => {
if (done)
return;
if (streaming) {
if (uniRemaining > 0) {
uniHex += c;
uniRemaining--;
if (uniRemaining === 0) {
const code = parseInt(uniHex, 16);
if (!Number.isNaN(code))
onToken(String.fromCharCode(code));
}
return;
}
if (ansEscaped) {
emitEscaped(c);
return;
}
if (c === '\\') {
ansEscaped = true;
return;
}
if (c === '"') {
streaming = false;
done = true;
return;
} // answer value closed
onToken(c);
return;
}
if (inString) {
if (strEscaped) {
curString += c;
strEscaped = false;
return;
}
if (c === '\\') {
strEscaped = true;
return;
}
if (c === '"') {
inString = false;
const atRoot = stack.length === 1 && stack[0] === '{';
if (atRoot && expectKey) {
// The string just closed was a key of the root object.
pendingAnswer = curString === 'answer';
expectKey = false;
}
return;
}
curString += c;
return;
}
switch (c) {
case '"':
if (pendingAnswer) {
streaming = true;
pendingAnswer = false;
}
else {
inString = true;
curString = '';
}
return;
case '{':
stack.push('{');
expectKey = true;
pendingAnswer = false;
return;
case '[':
stack.push('[');
pendingAnswer = false;
return;
case '}':
stack.pop();
return;
case ']':
stack.pop();
return;
case ':': return; // keep pendingAnswer as set by the key
case ',':
expectKey = stack[stack.length - 1] === '{';
pendingAnswer = false;
return;
default: return; // whitespace and structural noise
}
};
return (delta) => {
if (delta === exports.STREAM_END_MARKER)
return;
for (const ch of delta)
feedChar(ch);
};
};
exports.createAnswerStreamFilter = createAnswerStreamFilter;