@langchain/langgraph-sdk
Version:
Client library for interacting with the LangGraph API
188 lines (186 loc) • 5.86 kB
JavaScript
import { StreamError } from "./errors.js";
import { toMessageDict } from "./messages.js";
//#region src/ui/manager.ts
/**
* Special ID used by LangGraph's messagesStateReducer to signal
* that all messages should be removed from the state.
*/
const REMOVE_ALL_MESSAGES = "__remove_all__";
var StreamManager = class {
abortRef = new AbortController();
messages;
listeners = /* @__PURE__ */ new Set();
throttle;
queue = Promise.resolve();
queueSize = 0;
state;
constructor(messages, options) {
this.messages = messages;
this.state = {
isLoading: false,
values: null,
error: void 0
};
this.throttle = options.throttle;
}
setState = (newState) => {
this.state = {
...this.state,
...newState
};
this.notifyListeners();
};
notifyListeners = () => {
this.listeners.forEach((listener) => listener());
};
subscribe = (listener) => {
if (this.throttle === false) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
const timeoutMs = this.throttle === true ? 0 : this.throttle;
let timeoutId;
const throttledListener = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
clearTimeout(timeoutId);
listener();
}, timeoutMs);
};
this.listeners.add(throttledListener);
return () => {
clearTimeout(timeoutId);
this.listeners.delete(throttledListener);
};
};
getSnapshot = () => this.state;
get isLoading() {
return this.state.isLoading;
}
get values() {
return this.state.values?.[0] ?? null;
}
get error() {
return this.state.error;
}
setStreamValues = (values, kind = "stream") => {
if (typeof values === "function") {
const [prevValues, prevKind] = this.state.values ?? [null, "stream"];
const nextValues = values(prevValues, prevKind);
this.setState({ values: nextValues != null ? [nextValues, kind] : null });
} else {
const nextValues = values != null ? [values, kind] : null;
this.setState({ values: nextValues });
}
};
getMutateFn = (kind, historyValues) => {
return (update) => {
const prev = {
...historyValues,
...(this.state.values ?? [null, "stream"])[0]
};
const next = typeof update === "function" ? update(prev) : update;
this.setStreamValues({
...prev,
...next
}, kind);
};
};
matchEventType = (expected, actual, _data) => {
return expected === actual || actual.startsWith(`${expected}|`);
};
enqueue = async (action, options) => {
try {
this.queueSize = Math.max(0, this.queueSize - 1);
this.setState({
isLoading: true,
error: void 0
});
this.abortRef = new AbortController();
const run = await action(this.abortRef.signal);
let streamError;
for await (const { event, data } of run) {
if (event === "error") {
streamError = new StreamError(data);
break;
}
const namespace = event.includes("|") ? event.split("|").slice(1) : void 0;
const mutate = this.getMutateFn("stream", options.initialValues);
if (event === "metadata") options.callbacks.onMetadataEvent?.(data);
if (event === "events") options.callbacks.onLangChainEvent?.(data);
if (this.matchEventType("updates", event, data)) options.callbacks.onUpdateEvent?.(data, {
namespace,
mutate
});
if (this.matchEventType("custom", event, data)) options.callbacks.onCustomEvent?.(data, {
namespace,
mutate
});
if (this.matchEventType("checkpoints", event, data)) options.callbacks.onCheckpointEvent?.(data, { namespace });
if (this.matchEventType("tasks", event, data)) options.callbacks.onTaskEvent?.(data, { namespace });
if (this.matchEventType("debug", event, data)) options.callbacks.onDebugEvent?.(data, { namespace });
if (event === "values") if ("__interrupt__" in data) this.setStreamValues((prev) => ({
...prev,
...data
}));
else this.setStreamValues(data);
if (this.matchEventType("messages", event, data)) {
const [serialized, metadata] = data;
const messageId = this.messages.add(serialized, metadata);
if (!messageId) {
console.warn("Failed to add message to manager, no message ID found");
continue;
}
this.setStreamValues((streamValues) => {
const values$1 = {
...options.initialValues,
...streamValues
};
let messages = options.getMessages(values$1).slice();
const { chunk, index } = this.messages.get(messageId, messages.length) ?? {};
if (!chunk || index == null) return values$1;
if (chunk.getType() === "remove") if (chunk.id === REMOVE_ALL_MESSAGES) messages = [];
else messages.splice(index, 1);
else messages[index] = toMessageDict(chunk);
return options.setMessages(values$1, messages);
});
}
}
if (streamError != null) throw streamError;
const values = await options.onSuccess?.();
if (typeof values !== "undefined" && this.queueSize === 0) this.setStreamValues(values);
} catch (error) {
if (!(error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError"))) {
console.error(error);
this.setState({ error });
await options.onError?.(error);
}
} finally {
this.setState({ isLoading: false });
this.abortRef = new AbortController();
options.onFinish?.();
}
};
start = async (action, options) => {
this.queueSize += 1;
this.queue = this.queue.then(() => this.enqueue(action, options));
};
stop = async (historyValues, options) => {
this.abortRef.abort();
this.abortRef = new AbortController();
options.onStop?.({ mutate: this.getMutateFn("stop", historyValues) });
};
clear = () => {
this.abortRef.abort();
this.abortRef = new AbortController();
this.setState({
error: void 0,
values: null,
isLoading: false
});
this.messages.clear();
};
};
//#endregion
export { StreamManager };
//# sourceMappingURL=manager.js.map