@sema-agent/core
Version:
Stateless, task-oriented AI agent core
76 lines (75 loc) • 2.67 kB
JavaScript
import { StringDecoder } from "node:string_decoder";
export const MAX_EXEC_OUTPUT_BYTES = 8 * 1024 * 1024;
export class RollingTailBuffer {
maxBytes;
chunks = [];
size = 0;
headDropped = 0;
sourceSkipped = 0;
constructor(maxBytes = MAX_EXEC_OUTPUT_BYTES) {
this.maxBytes = maxBytes;
}
push(chunk) {
if (chunk.length === 0)
return;
this.chunks.push(chunk);
this.size += chunk.length;
while (this.size > this.maxBytes) {
const over = this.size - this.maxBytes;
const head = this.chunks[0];
if (over >= head.length) {
this.chunks.shift();
this.size -= head.length;
this.headDropped += head.length;
}
else {
this.chunks[0] = head.subarray(over);
this.size -= over;
this.headDropped += over;
}
}
}
recordSkippedBytes(n) {
if (n > 0)
this.sourceSkipped += n;
}
result() {
return { text: Buffer.concat(this.chunks).toString("utf8"), droppedBytes: this.headDropped + this.sourceSkipped };
}
bytes() {
return Buffer.concat(this.chunks);
}
headDroppedBytes() {
return this.headDropped;
}
sourceSkippedBytes() {
return this.sourceSkipped;
}
}
export function newStreamCursorState(tail = new RollingTailBuffer()) {
return { tail, cursorBytes: 0, acceptedBytes: 0, disclosedSkippedBytes: 0, decoder: new StringDecoder("utf8") };
}
export function sliceStreamIncrement(s, terminal) {
const headDropped = s.tail.headDroppedBytes();
const sourceSkipped = s.tail.sourceSkippedBytes();
const newSkipped = Math.max(0, sourceSkipped - s.disclosedSkippedBytes);
s.disclosedSkippedBytes = sourceSkipped;
const droppedBeforeCursor = Math.max(0, headDropped - s.cursorBytes) + newSkipped;
const buf = s.tail.bytes();
const startInTail = Math.min(Math.max(s.cursorBytes, headDropped) - headDropped, buf.length);
const incBuf = startInTail <= 0 ? buf : buf.subarray(startInTail);
if (droppedBeforeCursor > 0)
s.decoder = new StringDecoder("utf8");
let inc = s.decoder.write(incBuf);
if (terminal) {
inc += s.decoder.end();
s.decoder = new StringDecoder("utf8");
}
s.cursorBytes = s.acceptedBytes;
return { inc, droppedBeforeCursor };
}
export function markTruncated(text, droppedBytes) {
return droppedBytes > 0
? `[... ${droppedBytes} earlier byte(s) truncated; showing last ${Buffer.byteLength(text, "utf8")} ...]\n${text}`
: text;
}