assistant-stream
Version:
Streaming utilities for AI assistants
360 lines (359 loc) • 8.73 kB
JavaScript
import { asAsyncIterableStream } from "../../utils/AsyncIterableStream.js";
import { getPartialJsonObjectFieldState } from "../../utils/json/parse-partial-json-object.js";
import { IncrementalJsonObjectParser } from "../../utils/json/incremental-json-object-parser.js";
import { promiseWithResolvers } from "../../utils/promiseWithResolvers.js";
// src/core/tool/ToolCallReader.ts
function getField(obj, fieldPath) {
let current = obj;
for (const key of fieldPath) {
if (current === void 0 || current === null || !Object.hasOwn(current, key)) return;
current = current[key];
}
return current;
}
var GetHandle = class {
resolve;
reject;
disposed = false;
fieldPath;
get isDisposed() {
return this.disposed;
}
constructor(resolve, reject, fieldPath) {
this.resolve = resolve;
this.reject = reject;
this.fieldPath = fieldPath;
}
update(args) {
if (this.disposed) return;
try {
if (getPartialJsonObjectFieldState(args, this.fieldPath) === "complete") {
const value = getField(args, this.fieldPath);
if (value !== void 0) {
this.resolve(value);
this.dispose();
}
}
} catch (e) {
this.reject(e);
this.dispose();
}
}
end(args) {
if (this.disposed) return;
try {
const value = getField(args, this.fieldPath);
this.resolve(value);
} catch (e) {
this.reject(e);
} finally {
this.dispose();
}
}
error(reason) {
if (this.disposed) return;
this.reject(reason);
this.dispose();
}
dispose() {
this.disposed = true;
}
};
var StreamValuesHandle = class {
controller;
disposed = false;
fieldPath;
get isDisposed() {
return this.disposed;
}
constructor(controller, fieldPath) {
this.controller = controller;
this.fieldPath = fieldPath;
}
update(args) {
if (this.disposed) return;
try {
const value = getField(args, this.fieldPath);
if (value !== void 0) this.controller.enqueue(value);
if (getPartialJsonObjectFieldState(args, this.fieldPath) === "complete") {
this.controller.close();
this.dispose();
}
} catch (e) {
this.controller.error(e);
this.dispose();
}
}
end() {
if (this.disposed) return;
this.controller.close();
this.dispose();
}
error(reason) {
if (this.disposed) return;
this.controller.error(reason);
this.dispose();
}
dispose() {
this.disposed = true;
}
};
var StreamTextHandle = class {
controller;
disposed = false;
fieldPath;
lastValue = void 0;
get isDisposed() {
return this.disposed;
}
constructor(controller, fieldPath) {
this.controller = controller;
this.fieldPath = fieldPath;
}
update(args) {
if (this.disposed) return;
try {
const value = getField(args, this.fieldPath);
if (typeof value === "string" && value !== this.lastValue) {
const delta = value.substring(this.lastValue?.length || 0);
this.lastValue = value;
this.controller.enqueue(delta);
}
if (getPartialJsonObjectFieldState(args, this.fieldPath) === "complete") {
this.controller.close();
this.dispose();
}
} catch (e) {
this.controller.error(e);
this.dispose();
}
}
end() {
if (this.disposed) return;
this.controller.close();
this.dispose();
}
error(reason) {
if (this.disposed) return;
this.controller.error(reason);
this.dispose();
}
dispose() {
this.disposed = true;
}
};
var ForEachHandle = class {
controller;
disposed = false;
fieldPath;
nextIndex = 0;
get isDisposed() {
return this.disposed;
}
constructor(controller, fieldPath) {
this.controller = controller;
this.fieldPath = fieldPath;
}
update(args) {
if (this.disposed) return;
try {
const array = getField(args, this.fieldPath);
if (!Array.isArray(array)) return;
for (; this.nextIndex < array.length; this.nextIndex++) {
const elementPath = [...this.fieldPath, this.nextIndex];
if (getPartialJsonObjectFieldState(args, elementPath) !== "complete") break;
this.controller.enqueue(array[this.nextIndex]);
}
if (getPartialJsonObjectFieldState(args, this.fieldPath) === "complete") {
this.controller.close();
this.dispose();
}
} catch (e) {
this.controller.error(e);
this.dispose();
}
}
end() {
if (this.disposed) return;
this.controller.close();
this.dispose();
}
error(reason) {
if (this.disposed) return;
this.controller.error(reason);
this.dispose();
}
dispose() {
this.disposed = true;
}
};
var ToolCallArgsReaderImpl = class {
argTextDeltas;
handles = /* @__PURE__ */ new Set();
accumulatedText = "";
parser;
args = void 0;
finished = false;
failure = void 0;
constructor(argTextDeltas) {
this.argTextDeltas = argTextDeltas;
this.processStream();
}
async processStream() {
try {
const reader = this.argTextDeltas.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
this.accumulatedText += value;
if (this.handles.size === 0) continue;
if (this.parseCurrentArgs(value)) this.updateHandles();
}
} catch (error) {
this.failure = { reason: error };
} finally {
this.finished = true;
for (const handle of this.handles) this.settleHandle(handle);
this.handles.clear();
}
}
settleHandle(handle) {
if (this.failure) handle.error(this.failure.reason);
else handle.end(this.args);
}
parseCurrentArgs(delta) {
if (this.parser?.currentTextLength === this.accumulatedText.length) return false;
const previousArgs = this.args;
if (!this.parser) this.parser = IncrementalJsonObjectParser.from(this.accumulatedText, this.args);
else if (delta !== void 0 && this.parser.currentTextLength + delta.length === this.accumulatedText.length) this.parser = this.parser.append(delta);
else this.parser = IncrementalJsonObjectParser.from(this.accumulatedText, this.args);
this.args = this.parser.currentArgs;
return this.args !== previousArgs;
}
updateHandles() {
for (const handle of this.handles) {
handle.update(this.args);
if (handle.isDisposed) this.handles.delete(handle);
}
}
activateHandle(handle) {
this.parseCurrentArgs();
handle.update(this.args);
if (handle.isDisposed) return;
if (this.finished) {
this.settleHandle(handle);
return;
}
this.handles.add(handle);
}
get(...fieldPath) {
return new Promise((resolve, reject) => {
const handle = new GetHandle(resolve, reject, fieldPath);
this.activateHandle(handle);
});
}
streamValues(...fieldPath) {
const simplePath = fieldPath;
let handle;
const stream = new ReadableStream({
start: (controller) => {
handle = new StreamValuesHandle(controller, simplePath);
this.activateHandle(handle);
},
cancel: () => {
if (handle) {
handle.dispose();
this.handles.delete(handle);
}
}
});
return asAsyncIterableStream(stream);
}
streamText(...fieldPath) {
const simplePath = fieldPath;
let handle;
const stream = new ReadableStream({
start: (controller) => {
handle = new StreamTextHandle(controller, simplePath);
this.activateHandle(handle);
},
cancel: () => {
if (handle) {
handle.dispose();
this.handles.delete(handle);
}
}
});
return asAsyncIterableStream(stream);
}
forEach(...fieldPath) {
const simplePath = fieldPath;
let handle;
const stream = new ReadableStream({
start: (controller) => {
handle = new ForEachHandle(controller, simplePath);
this.activateHandle(handle);
},
cancel: () => {
if (handle) {
handle.dispose();
this.handles.delete(handle);
}
}
});
return asAsyncIterableStream(stream);
}
};
var ToolCallResponseReaderImpl = class {
promise;
constructor(promise) {
this.promise = promise;
}
get() {
return this.promise;
}
};
var ToolCallReaderImpl = class {
args;
response;
writable;
resolve;
argsText = "";
constructor() {
const stream = new TransformStream();
this.writable = stream.writable;
this.args = new ToolCallArgsReaderImpl(stream.readable);
const { promise, resolve } = promiseWithResolvers();
this.resolve = resolve;
this.response = new ToolCallResponseReaderImpl(promise);
}
async appendArgsTextDelta(text) {
const writer = this.writable.getWriter();
try {
await writer.write(text);
} catch (err) {
console.warn(err);
} finally {
writer.releaseLock();
}
this.argsText += text;
}
async finishArgsText() {
const writer = this.writable.getWriter();
try {
await writer.close();
} catch (err) {
console.warn(err);
} finally {
writer.releaseLock();
}
}
setResponse(value) {
this.resolve(value);
}
result = { get: async () => {
return (await this.response.get()).result;
} };
};
export { ToolCallArgsReaderImpl, ToolCallReaderImpl, ToolCallResponseReaderImpl };
//# sourceMappingURL=ToolCallReader.js.map