@posthog/agent
Version:
TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog
159 lines (156 loc) • 4.71 kB
JavaScript
import 'node:fs';
import 'node:os';
import { WritableStream, ReadableStream } from 'node:stream/web';
// A pushable async iterable: allows you to push items and consume them with for-await.
// Useful for bridging push-based and async-iterator-based code.
class Pushable {
queue = [];
resolvers = [];
done = false;
push(item) {
const resolve = this.resolvers.shift();
if (resolve) {
resolve({ value: item, done: false });
}
else {
this.queue.push(item);
}
}
end() {
this.done = true;
for (const resolve of this.resolvers) {
resolve({ value: undefined, done: true });
}
this.resolvers = [];
}
[Symbol.asyncIterator]() {
return {
next: () => {
if (this.queue.length > 0) {
const value = this.queue.shift();
return Promise.resolve({ value, done: false });
}
if (this.done) {
return Promise.resolve({
value: undefined,
done: true,
});
}
return new Promise((resolve) => {
this.resolvers.push(resolve);
});
},
};
}
}
function unreachable(value, logger) {
let valueAsString;
try {
valueAsString = JSON.stringify(value);
}
catch {
valueAsString = value;
}
logger.error(`Unexpected case: ${valueAsString}`);
}
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function pushableToReadableStream(pushable) {
const iterator = pushable[Symbol.asyncIterator]();
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();
if (done) {
controller.close();
}
else {
controller.enqueue(value);
}
},
});
}
function createBidirectionalStreams() {
const clientToAgentPushable = new Pushable();
const agentToClientPushable = new Pushable();
const clientToAgentReadable = pushableToReadableStream(clientToAgentPushable);
const agentToClientReadable = pushableToReadableStream(agentToClientPushable);
const clientToAgentWritable = new WritableStream({
write(chunk) {
clientToAgentPushable.push(chunk);
},
close() {
clientToAgentPushable.end();
},
});
const agentToClientWritable = new WritableStream({
write(chunk) {
agentToClientPushable.push(chunk);
},
close() {
agentToClientPushable.end();
},
});
return {
client: {
readable: agentToClientReadable,
writable: clientToAgentWritable,
},
agent: {
readable: clientToAgentReadable,
writable: agentToClientWritable,
},
};
}
/**
* Extracts lines from file content with byte limit enforcement.
*
* @param fullContent - The complete file content
* @param maxContentLength - Maximum number of UTF-16 Code Units to return
* @returns Object containing extracted content and metadata
*/
function extractLinesWithByteLimit(fullContent, maxContentLength) {
if (fullContent === "") {
return {
content: "",
wasLimited: false,
linesRead: 1,
};
}
let linesSeen = 0;
let index = 0;
linesSeen = 0;
let contentLength = 0;
let wasLimited = false;
while (true) {
const nextIndex = fullContent.indexOf("\n", index);
if (nextIndex < 0) {
// Last line in file (no trailing newline)
if (linesSeen > 0 && fullContent.length > maxContentLength) {
wasLimited = true;
break;
}
linesSeen += 1;
contentLength = fullContent.length;
break;
}
else {
// Line with newline - include up to the newline
const newContentLength = nextIndex + 1;
if (linesSeen > 0 && newContentLength > maxContentLength) {
wasLimited = true;
break;
}
linesSeen += 1;
contentLength = newContentLength;
index = newContentLength;
}
}
return {
content: fullContent.slice(0, contentLength),
wasLimited,
linesRead: linesSeen,
};
}
export { Pushable, createBidirectionalStreams, extractLinesWithByteLimit, sleep, unreachable };
//# sourceMappingURL=utils.js.map