@langchain/langgraph-sdk
Version:
Client library for interacting with the LangGraph API
164 lines (163 loc) • 6.58 kB
JavaScript
const require_message_coercion = require("../message-coercion.cjs");
const require_messages = require("../../client/stream/messages.cjs");
const require_namespace = require("../namespace.cjs");
const require_assembled_to_message = require("../assembled-to-message.cjs");
const require_message_reconciliation = require("../message-reconciliation.cjs");
const require_runtime = require("./runtime.cjs");
//#region src/stream/projections/messages.ts
function messagesProjection(namespace) {
const ns = [...namespace];
return {
key: `messages|${require_namespace.namespaceKey(ns)}`,
namespace: ns,
initial: [],
open({ thread, store, rootBus }) {
const assembler = new require_messages.MessageAssembler();
const roleByKey = /* @__PURE__ */ new Map();
const indexById = /* @__PURE__ */ new Map();
const streamMessageIds = /* @__PURE__ */ new Set();
let valuesMessageIds = /* @__PURE__ */ new Set();
const uncommittedIds = /* @__PURE__ */ new Set();
const finishedIds = /* @__PURE__ */ new Set();
const droppedIds = /* @__PURE__ */ new Set();
const rootShortCircuit = require_namespace.isRootNamespace(ns) && rootBus.channels.includes("messages");
let disposed = false;
const pendingMessages = [];
let dirty = false;
let flushScheduled = false;
const flushChannel = typeof MessageChannel !== "undefined" ? new MessageChannel() : null;
const flush = () => {
flushScheduled = false;
if (!dirty || disposed) return;
dirty = false;
store.setValue(pendingMessages.slice());
};
if (flushChannel != null) flushChannel.port1.onmessage = flush;
const scheduleFlush = () => {
dirty = true;
if (flushScheduled) return;
flushScheduled = true;
if (flushChannel != null) flushChannel.port2.postMessage(null);
else setTimeout(flush, 0);
};
const applyValuesEvent = (event) => {
const data = event.params.data;
if (data == null || typeof data !== "object" || Array.isArray(data)) return;
const rawMessages = data.messages;
if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;
const reconciliation = require_message_reconciliation.reconcileMessagesFromValues({
valueMessages: require_message_coercion.ensureMessageInstances(rawMessages),
currentMessages: pendingMessages,
currentIndexById: indexById,
previousValueMessageIds: valuesMessageIds,
streamedMessageIds: streamMessageIds,
preferValuesMessage: require_message_reconciliation.shouldPreferValuesMessage
});
valuesMessageIds = reconciliation.valueMessageIds;
for (const id of valuesMessageIds) {
uncommittedIds.delete(id);
finishedIds.delete(id);
}
const reconciledMessages = [...reconciliation.messages];
pendingMessages.length = 0;
for (const message of reconciledMessages) pendingMessages.push(message);
indexById.clear();
for (const [id, idx] of require_message_reconciliation.buildMessageIndex(pendingMessages)) indexById.set(id, idx);
scheduleFlush();
};
const applyEvent = (event) => {
const data = event.params.data;
if (data.event === "message-start") {
const startData = data;
const role = startData.role ?? "ai";
const extendedRole = startData.role ?? role;
const maybeToolCallId = startData.tool_call_id;
if (startData.id != null) roleByKey.set(startData.id, {
role: extendedRole,
toolCallId: maybeToolCallId
});
}
const update = assembler.consume(event);
if (update == null) return;
const msg = update.message;
const id = msg.id;
if (id == null || droppedIds.has(id)) return;
if (!valuesMessageIds.has(id)) uncommittedIds.add(id);
if (update.kind === "message-finish") finishedIds.add(id);
const captured = roleByKey.get(id) ?? { role: "ai" };
const base = require_assembled_to_message.assembledMessageToBaseMessage(msg, captured.role, { toolCallId: captured.toolCallId });
streamMessageIds.add(id);
const existingIdx = indexById.get(id);
if (existingIdx == null) {
indexById.set(id, pendingMessages.length);
pendingMessages.push(base);
} else pendingMessages[existingIdx] = base;
scheduleFlush();
};
const closeRun = (unfinishedOnly) => {
assembler.abandonActive();
const dropped = /* @__PURE__ */ new Set();
for (const id of uncommittedIds) {
if (unfinishedOnly && finishedIds.has(id)) continue;
dropped.add(id);
}
if (dropped.size === 0) return;
for (const id of dropped) {
uncommittedIds.delete(id);
finishedIds.delete(id);
droppedIds.add(id);
}
const kept = pendingMessages.filter((m) => m.id == null || !dropped.has(m.id));
pendingMessages.length = 0;
pendingMessages.push(...kept);
indexById.clear();
for (const [id, idx] of require_message_reconciliation.buildMessageIndex(pendingMessages)) indexById.set(id, idx);
scheduleFlush();
};
const unsubscribeLifecycle = rootBus.subscribe((event) => {
if (event.method !== "lifecycle") return;
if (!require_namespace.isRootNamespace(event.params.namespace)) return;
const lifecycle = event.params.data?.event;
if (lifecycle === "running") setTimeout(() => !disposed && closeRun(false), 0);
else if (lifecycle === "completed" || lifecycle === "interrupted" || lifecycle === "failed") setTimeout(() => !disposed && closeRun(true), 0);
});
let runtime;
const openSubscription = () => {
runtime = require_runtime.openProjectionSubscription({
thread,
channels: ["messages", "values"],
namespace: ns,
onEvent(event) {
if (event.method === "messages") applyEvent(event);
else if (event.method === "values") applyValuesEvent(event);
}
});
};
if (rootShortCircuit) runtime = { dispose: rootBus.subscribe((event) => {
if (!require_namespace.isRootNamespace(event.params.namespace)) return;
if (event.method === "messages") applyEvent(event);
else if (event.method === "values") applyValuesEvent(event);
}) };
else (async () => {
if (!(await rootBus.trySeedFromHistory?.({
kind: "messages",
namespace: ns,
store
}) === true) && !disposed) openSubscription();
})();
return { async dispose() {
disposed = true;
unsubscribeLifecycle();
if (flushChannel != null) {
flushChannel.port1.onmessage = null;
flushChannel.port1.close();
flushChannel.port2.close();
}
await runtime?.dispose();
} };
}
};
}
//#endregion
exports.messagesProjection = messagesProjection;
//# sourceMappingURL=messages.cjs.map