@langchain/langgraph 0.2.45 → 0.2.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -157,10 +157,15 @@ function* mapOutputValues(outputChannels, pendingWrites, channels
157
157
  exports.mapOutputValues = mapOutputValues;
158
158
  /**
159
159
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
160
+ * @internal
161
+ *
162
+ * @param outputChannels - The channels to output.
163
+ * @param tasks - The tasks to output.
164
+ * @param cached - Whether the output is cached.
165
+ *
166
+ * @returns A generator that yields the output chunk (if any).
160
167
  */
161
- function* mapOutputUpdates(outputChannels, tasks, cached
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- ) {
168
+ function* mapOutputUpdates(outputChannels, tasks, cached) {
164
169
  const outputTasks = tasks.filter(([task, ww]) => {
165
170
  return ((task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN)) &&
166
171
  ww[0][0] !== constants_js_1.ERROR &&
@@ -169,47 +174,67 @@ function* mapOutputUpdates(outputChannels, tasks, cached
169
174
  if (!outputTasks.length) {
170
175
  return;
171
176
  }
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
177
  let updated;
174
178
  if (outputTasks.some(([task]) => task.writes.some(([chan, _]) => chan === constants_js_1.RETURN))) {
179
+ // TODO: probably should assert that RETURN is the only "non-special" channel (starts with "__")
175
180
  updated = outputTasks.flatMap(([task]) => task.writes
176
181
  .filter(([chan, _]) => chan === constants_js_1.RETURN)
177
182
  .map(([_, value]) => [task.name, value]));
178
183
  }
179
184
  else if (!Array.isArray(outputChannels)) {
185
+ // special case where graph state is a single channel (MessageGraph)
186
+ // probably using this in functional API, too
180
187
  updated = outputTasks.flatMap(([task]) => task.writes
181
188
  .filter(([chan, _]) => chan === outputChannels)
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
189
  .map(([_, value]) => [task.name, value]));
184
190
  }
185
191
  else {
186
- updated = outputTasks
187
- .filter(([task]) => task.writes.some(([chan]) => outputChannels.includes(chan)))
188
- .map(([task]) => [
189
- task.name,
190
- Object.fromEntries(task.writes.filter(([chan]) => outputChannels.includes(chan))),
191
- ]);
192
- }
193
- const grouped = Object.fromEntries(outputTasks.map(([t]) => [t.name, []])
194
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
- );
192
+ updated = outputTasks.flatMap(([task]) => {
193
+ const { writes } = task;
194
+ const counts = {};
195
+ for (const [chan] of writes) {
196
+ if (outputChannels.includes(chan)) {
197
+ counts[chan] = (counts[chan] || 0) + 1;
198
+ }
199
+ }
200
+ if (Object.values(counts).some((count) => count > 1)) {
201
+ // Multiple writes to the same channel: create separate entries
202
+ return writes
203
+ .filter(([chan]) => outputChannels.includes(chan))
204
+ .map(([chan, value]) => [task.name, { [chan]: value }]);
205
+ }
206
+ else {
207
+ // Single write to each channel: create a single combined entry
208
+ return [
209
+ [
210
+ task.name,
211
+ Object.fromEntries(writes.filter(([chan]) => outputChannels.includes(chan))),
212
+ ],
213
+ ];
214
+ }
215
+ });
216
+ }
217
+ const grouped = {};
196
218
  for (const [node, value] of updated) {
219
+ if (!(node in grouped)) {
220
+ grouped[node] = [];
221
+ }
197
222
  grouped[node].push(value);
198
223
  }
199
- for (const [node, value] of Object.entries(grouped)) {
200
- if (value.length === 0) {
201
- delete grouped[node];
224
+ const flattened = {};
225
+ for (const node in grouped) {
226
+ if (grouped[node].length === 1) {
227
+ const [write] = grouped[node];
228
+ flattened[node] = write;
202
229
  }
203
- else if (value.length === 1) {
204
- // TODO: Fix incorrect cast here
205
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
- grouped[node] = value[0];
230
+ else {
231
+ flattened[node] = grouped[node];
207
232
  }
208
233
  }
209
234
  if (cached) {
210
- grouped["__metadata__"] = { cached };
235
+ flattened["__metadata__"] = { cached };
211
236
  }
212
- yield grouped;
237
+ yield flattened;
213
238
  }
214
239
  exports.mapOutputUpdates = mapOutputUpdates;
215
240
  function single(iter) {
@@ -18,6 +18,13 @@ export declare function mapInput<C extends PropertyKey>(inputChannels: C | Array
18
18
  export declare function mapOutputValues<C extends PropertyKey>(outputChannels: C | Array<C>, pendingWrites: readonly PendingWrite<C>[] | true, channels: Record<C, BaseChannel>): Generator<Record<string, any>, any>;
19
19
  /**
20
20
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
21
+ * @internal
22
+ *
23
+ * @param outputChannels - The channels to output.
24
+ * @param tasks - The tasks to output.
25
+ * @param cached - Whether the output is cached.
26
+ *
27
+ * @returns A generator that yields the output chunk (if any).
21
28
  */
22
- export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly [PregelExecutableTask<N, C>, PendingWrite<C>[]][], cached?: boolean): Generator<Record<N, Record<string, any> | any>>;
29
+ export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly [PregelExecutableTask<N, C>, PendingWrite<C>[]][], cached?: boolean): Generator<Record<N, Record<string, unknown> | unknown>>;
23
30
  export declare function single<T>(iter: IterableIterator<T>): T | null;
package/dist/pregel/io.js CHANGED
@@ -149,10 +149,15 @@ export function* mapOutputValues(outputChannels, pendingWrites, channels
149
149
  }
150
150
  /**
151
151
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
152
+ * @internal
153
+ *
154
+ * @param outputChannels - The channels to output.
155
+ * @param tasks - The tasks to output.
156
+ * @param cached - Whether the output is cached.
157
+ *
158
+ * @returns A generator that yields the output chunk (if any).
152
159
  */
153
- export function* mapOutputUpdates(outputChannels, tasks, cached
154
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
- ) {
160
+ export function* mapOutputUpdates(outputChannels, tasks, cached) {
156
161
  const outputTasks = tasks.filter(([task, ww]) => {
157
162
  return ((task.config === undefined || !task.config.tags?.includes(TAG_HIDDEN)) &&
158
163
  ww[0][0] !== ERROR &&
@@ -161,47 +166,67 @@ export function* mapOutputUpdates(outputChannels, tasks, cached
161
166
  if (!outputTasks.length) {
162
167
  return;
163
168
  }
164
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
169
  let updated;
166
170
  if (outputTasks.some(([task]) => task.writes.some(([chan, _]) => chan === RETURN))) {
171
+ // TODO: probably should assert that RETURN is the only "non-special" channel (starts with "__")
167
172
  updated = outputTasks.flatMap(([task]) => task.writes
168
173
  .filter(([chan, _]) => chan === RETURN)
169
174
  .map(([_, value]) => [task.name, value]));
170
175
  }
171
176
  else if (!Array.isArray(outputChannels)) {
177
+ // special case where graph state is a single channel (MessageGraph)
178
+ // probably using this in functional API, too
172
179
  updated = outputTasks.flatMap(([task]) => task.writes
173
180
  .filter(([chan, _]) => chan === outputChannels)
174
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
181
  .map(([_, value]) => [task.name, value]));
176
182
  }
177
183
  else {
178
- updated = outputTasks
179
- .filter(([task]) => task.writes.some(([chan]) => outputChannels.includes(chan)))
180
- .map(([task]) => [
181
- task.name,
182
- Object.fromEntries(task.writes.filter(([chan]) => outputChannels.includes(chan))),
183
- ]);
184
- }
185
- const grouped = Object.fromEntries(outputTasks.map(([t]) => [t.name, []])
186
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
- );
184
+ updated = outputTasks.flatMap(([task]) => {
185
+ const { writes } = task;
186
+ const counts = {};
187
+ for (const [chan] of writes) {
188
+ if (outputChannels.includes(chan)) {
189
+ counts[chan] = (counts[chan] || 0) + 1;
190
+ }
191
+ }
192
+ if (Object.values(counts).some((count) => count > 1)) {
193
+ // Multiple writes to the same channel: create separate entries
194
+ return writes
195
+ .filter(([chan]) => outputChannels.includes(chan))
196
+ .map(([chan, value]) => [task.name, { [chan]: value }]);
197
+ }
198
+ else {
199
+ // Single write to each channel: create a single combined entry
200
+ return [
201
+ [
202
+ task.name,
203
+ Object.fromEntries(writes.filter(([chan]) => outputChannels.includes(chan))),
204
+ ],
205
+ ];
206
+ }
207
+ });
208
+ }
209
+ const grouped = {};
188
210
  for (const [node, value] of updated) {
211
+ if (!(node in grouped)) {
212
+ grouped[node] = [];
213
+ }
189
214
  grouped[node].push(value);
190
215
  }
191
- for (const [node, value] of Object.entries(grouped)) {
192
- if (value.length === 0) {
193
- delete grouped[node];
216
+ const flattened = {};
217
+ for (const node in grouped) {
218
+ if (grouped[node].length === 1) {
219
+ const [write] = grouped[node];
220
+ flattened[node] = write;
194
221
  }
195
- else if (value.length === 1) {
196
- // TODO: Fix incorrect cast here
197
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
198
- grouped[node] = value[0];
222
+ else {
223
+ flattened[node] = grouped[node];
199
224
  }
200
225
  }
201
226
  if (cached) {
202
- grouped["__metadata__"] = { cached };
227
+ flattened["__metadata__"] = { cached };
203
228
  }
204
- yield grouped;
229
+ yield flattened;
205
230
  }
206
231
  export function single(iter) {
207
232
  // eslint-disable-next-line no-unreachable-loop
@@ -527,6 +527,7 @@ class PregelLoop {
527
527
  return true;
528
528
  }
529
529
  async finishAndHandleError(error) {
530
+ this._syncStateOnParentCommand(error);
530
531
  const suppress = this._suppressInterrupt(error);
531
532
  if (suppress || error === undefined) {
532
533
  this.output = (0, io_js_1.readChannels)(this.channels, this.outputKeys);
@@ -751,5 +752,15 @@ class PregelLoop {
751
752
  }
752
753
  }
753
754
  }
755
+ _syncStateOnParentCommand(error) {
756
+ if ((0, errors_js_1.isParentCommand)(error)) {
757
+ const state = Object.entries((0, io_js_1.readChannels)(this.channels, typeof this.outputKeys === "string"
758
+ ? [this.outputKeys]
759
+ : this.outputKeys));
760
+ const update = [...state, ...error.command._updateAsTuples()];
761
+ // eslint-disable-next-line no-param-reassign
762
+ error.command.update = update;
763
+ }
764
+ }
754
765
  }
755
766
  exports.PregelLoop = PregelLoop;
@@ -116,5 +116,6 @@ export declare class PregelLoop {
116
116
  protected _emit(values: [StreamMode, unknown][]): void;
117
117
  protected _putCheckpoint(inputMetadata: Omit<CheckpointMetadata, "step" | "parents">): Promise<void>;
118
118
  protected _matchWrites(tasks: Record<string, PregelExecutableTask<string, string>>): void;
119
+ _syncStateOnParentCommand(error: unknown): void;
119
120
  }
120
121
  export {};
@@ -4,7 +4,7 @@ import { isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, C
4
4
  import { _applyWrites, _prepareNextTasks, _prepareSingleTask, increment, shouldInterrupt, } from "./algo.js";
5
5
  import { gatherIterator, gatherIteratorSync, prefixGenerator, } from "../utils.js";
6
6
  import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels, } from "./io.js";
7
- import { getSubgraphsSeenSet, EmptyInputError, GraphInterrupt, isGraphInterrupt, MultipleSubgraphsError, } from "../errors.js";
7
+ import { getSubgraphsSeenSet, EmptyInputError, GraphInterrupt, isGraphInterrupt, MultipleSubgraphsError, isParentCommand, } from "../errors.js";
8
8
  import { getNewChannelVersions, patchConfigurable } from "./utils/index.js";
9
9
  import { mapDebugTasks, mapDebugCheckpoint, mapDebugTaskResults, printStepTasks, } from "./debug.js";
10
10
  import { IterableReadableWritableStream } from "./stream.js";
@@ -524,6 +524,7 @@ export class PregelLoop {
524
524
  return true;
525
525
  }
526
526
  async finishAndHandleError(error) {
527
+ this._syncStateOnParentCommand(error);
527
528
  const suppress = this._suppressInterrupt(error);
528
529
  if (suppress || error === undefined) {
529
530
  this.output = readChannels(this.channels, this.outputKeys);
@@ -748,4 +749,14 @@ export class PregelLoop {
748
749
  }
749
750
  }
750
751
  }
752
+ _syncStateOnParentCommand(error) {
753
+ if (isParentCommand(error)) {
754
+ const state = Object.entries(readChannels(this.channels, typeof this.outputKeys === "string"
755
+ ? [this.outputKeys]
756
+ : this.outputKeys));
757
+ const update = [...state, ...error.command._updateAsTuples()];
758
+ // eslint-disable-next-line no-param-reassign
759
+ error.command.update = update;
760
+ }
761
+ }
751
762
  }
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StreamMessagesHandler = void 0;
4
- const uuid_1 = require("uuid");
5
4
  const base_1 = require("@langchain/core/callbacks/base");
6
5
  const messages_1 = require("@langchain/core/messages");
7
6
  const constants_js_1 = require("../constants.cjs");
@@ -47,6 +46,12 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
47
46
  writable: true,
48
47
  value: {}
49
48
  });
49
+ Object.defineProperty(this, "stableMessageIdMap", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: {}
54
+ });
50
55
  Object.defineProperty(this, "lc_prefer_streaming", {
51
56
  enumerable: true,
52
57
  configurable: true,
@@ -55,18 +60,26 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
55
60
  });
56
61
  this.streamFn = streamFn;
57
62
  }
58
- _emit(meta, message, dedupe = false) {
63
+ _emit(meta, message, runId, dedupe = false) {
59
64
  if (dedupe &&
60
65
  message.id !== undefined &&
61
66
  this.seen[message.id] !== undefined) {
62
67
  return;
63
68
  }
64
- if (message.id === undefined) {
65
- const id = (0, uuid_1.v4)();
69
+ // For instance in ChatAnthropic, the first chunk has an message ID
70
+ // but the subsequent chunks do not. To avoid clients seeing two messages
71
+ // we rename the message ID if it's being auto-set to `run-${runId}`
72
+ // (see https://github.com/langchain-ai/langchainjs/pull/6646).
73
+ let messageId = message.id;
74
+ if (messageId == null || messageId === `run-${runId}`) {
75
+ messageId = this.stableMessageIdMap[runId] ?? messageId ?? `run-${runId}`;
76
+ }
77
+ this.stableMessageIdMap[runId] ??= messageId;
78
+ if (messageId !== message.id) {
66
79
  // eslint-disable-next-line no-param-reassign
67
- message.id = id;
80
+ message.id = messageId;
68
81
  // eslint-disable-next-line no-param-reassign
69
- message.lc_kwargs.id = id;
82
+ message.lc_kwargs.id = messageId;
70
83
  }
71
84
  this.seen[message.id] = message;
72
85
  this.streamFn([meta[0], "messages", [message, meta[1]]]);
@@ -86,12 +99,10 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
86
99
  this.emittedChatModelRunIds[runId] = true;
87
100
  if (this.metadatas[runId] !== undefined) {
88
101
  if (isChatGenerationChunk(chunk)) {
89
- this._emit(this.metadatas[runId], chunk.message);
102
+ this._emit(this.metadatas[runId], chunk.message, runId);
90
103
  }
91
104
  else {
92
- this._emit(this.metadatas[runId], new messages_1.AIMessageChunk({
93
- content: token,
94
- }));
105
+ this._emit(this.metadatas[runId], new messages_1.AIMessageChunk({ content: token }), runId);
95
106
  }
96
107
  }
97
108
  }
@@ -100,11 +111,12 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
100
111
  if (!this.emittedChatModelRunIds[runId]) {
101
112
  const chatGeneration = output.generations?.[0]?.[0];
102
113
  if ((0, messages_1.isBaseMessage)(chatGeneration?.message)) {
103
- this._emit(this.metadatas[runId], chatGeneration?.message, true);
114
+ this._emit(this.metadatas[runId], chatGeneration?.message, runId, true);
104
115
  }
105
116
  delete this.emittedChatModelRunIds[runId];
106
117
  }
107
118
  delete this.metadatas[runId];
119
+ delete this.stableMessageIdMap[runId];
108
120
  }
109
121
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
122
  handleLLMError(_err, runId) {
@@ -125,24 +137,24 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
125
137
  delete this.metadatas[runId];
126
138
  if (metadata !== undefined) {
127
139
  if ((0, messages_1.isBaseMessage)(outputs)) {
128
- this._emit(metadata, outputs, true);
140
+ this._emit(metadata, outputs, runId, true);
129
141
  }
130
142
  else if (Array.isArray(outputs)) {
131
143
  for (const value of outputs) {
132
144
  if ((0, messages_1.isBaseMessage)(value)) {
133
- this._emit(metadata, value, true);
145
+ this._emit(metadata, value, runId, true);
134
146
  }
135
147
  }
136
148
  }
137
149
  else if (outputs != null && typeof outputs === "object") {
138
150
  for (const value of Object.values(outputs)) {
139
151
  if ((0, messages_1.isBaseMessage)(value)) {
140
- this._emit(metadata, value, true);
152
+ this._emit(metadata, value, runId, true);
141
153
  }
142
154
  else if (Array.isArray(value)) {
143
155
  for (const item of value) {
144
156
  if ((0, messages_1.isBaseMessage)(item)) {
145
- this._emit(metadata, item, true);
157
+ this._emit(metadata, item, runId, true);
146
158
  }
147
159
  }
148
160
  }
@@ -15,9 +15,10 @@ export declare class StreamMessagesHandler extends BaseCallbackHandler {
15
15
  metadatas: Record<string, Meta>;
16
16
  seen: Record<string, BaseMessage>;
17
17
  emittedChatModelRunIds: Record<string, boolean>;
18
+ stableMessageIdMap: Record<string, string>;
18
19
  lc_prefer_streaming: boolean;
19
20
  constructor(streamFn: (streamChunk: StreamChunk) => void);
20
- _emit(meta: Meta, message: BaseMessage, dedupe?: boolean): void;
21
+ _emit(meta: Meta, message: BaseMessage, runId: string, dedupe?: boolean): void;
21
22
  handleChatModelStart(_llm: Serialized, _messages: BaseMessage[][], runId: string, _parentRunId?: string, _extraParams?: Record<string, unknown>, tags?: string[], metadata?: Record<string, unknown>, name?: string): void;
22
23
  handleLLMNewToken(token: string, _idx: NewTokenIndices, runId: string, _parentRunId?: string, _tags?: string[], fields?: HandleLLMNewTokenCallbackFields): void;
23
24
  handleLLMEnd(output: LLMResult, runId: string): void;
@@ -1,4 +1,3 @@
1
- import { v4 } from "uuid";
2
1
  import { BaseCallbackHandler, } from "@langchain/core/callbacks/base";
3
2
  import { AIMessageChunk, isBaseMessage, } from "@langchain/core/messages";
4
3
  import { TAG_HIDDEN, TAG_NOSTREAM } from "../constants.js";
@@ -44,6 +43,12 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
44
43
  writable: true,
45
44
  value: {}
46
45
  });
46
+ Object.defineProperty(this, "stableMessageIdMap", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: {}
51
+ });
47
52
  Object.defineProperty(this, "lc_prefer_streaming", {
48
53
  enumerable: true,
49
54
  configurable: true,
@@ -52,18 +57,26 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
52
57
  });
53
58
  this.streamFn = streamFn;
54
59
  }
55
- _emit(meta, message, dedupe = false) {
60
+ _emit(meta, message, runId, dedupe = false) {
56
61
  if (dedupe &&
57
62
  message.id !== undefined &&
58
63
  this.seen[message.id] !== undefined) {
59
64
  return;
60
65
  }
61
- if (message.id === undefined) {
62
- const id = v4();
66
+ // For instance in ChatAnthropic, the first chunk has an message ID
67
+ // but the subsequent chunks do not. To avoid clients seeing two messages
68
+ // we rename the message ID if it's being auto-set to `run-${runId}`
69
+ // (see https://github.com/langchain-ai/langchainjs/pull/6646).
70
+ let messageId = message.id;
71
+ if (messageId == null || messageId === `run-${runId}`) {
72
+ messageId = this.stableMessageIdMap[runId] ?? messageId ?? `run-${runId}`;
73
+ }
74
+ this.stableMessageIdMap[runId] ??= messageId;
75
+ if (messageId !== message.id) {
63
76
  // eslint-disable-next-line no-param-reassign
64
- message.id = id;
77
+ message.id = messageId;
65
78
  // eslint-disable-next-line no-param-reassign
66
- message.lc_kwargs.id = id;
79
+ message.lc_kwargs.id = messageId;
67
80
  }
68
81
  this.seen[message.id] = message;
69
82
  this.streamFn([meta[0], "messages", [message, meta[1]]]);
@@ -83,12 +96,10 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
83
96
  this.emittedChatModelRunIds[runId] = true;
84
97
  if (this.metadatas[runId] !== undefined) {
85
98
  if (isChatGenerationChunk(chunk)) {
86
- this._emit(this.metadatas[runId], chunk.message);
99
+ this._emit(this.metadatas[runId], chunk.message, runId);
87
100
  }
88
101
  else {
89
- this._emit(this.metadatas[runId], new AIMessageChunk({
90
- content: token,
91
- }));
102
+ this._emit(this.metadatas[runId], new AIMessageChunk({ content: token }), runId);
92
103
  }
93
104
  }
94
105
  }
@@ -97,11 +108,12 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
97
108
  if (!this.emittedChatModelRunIds[runId]) {
98
109
  const chatGeneration = output.generations?.[0]?.[0];
99
110
  if (isBaseMessage(chatGeneration?.message)) {
100
- this._emit(this.metadatas[runId], chatGeneration?.message, true);
111
+ this._emit(this.metadatas[runId], chatGeneration?.message, runId, true);
101
112
  }
102
113
  delete this.emittedChatModelRunIds[runId];
103
114
  }
104
115
  delete this.metadatas[runId];
116
+ delete this.stableMessageIdMap[runId];
105
117
  }
106
118
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
119
  handleLLMError(_err, runId) {
@@ -122,24 +134,24 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
122
134
  delete this.metadatas[runId];
123
135
  if (metadata !== undefined) {
124
136
  if (isBaseMessage(outputs)) {
125
- this._emit(metadata, outputs, true);
137
+ this._emit(metadata, outputs, runId, true);
126
138
  }
127
139
  else if (Array.isArray(outputs)) {
128
140
  for (const value of outputs) {
129
141
  if (isBaseMessage(value)) {
130
- this._emit(metadata, value, true);
142
+ this._emit(metadata, value, runId, true);
131
143
  }
132
144
  }
133
145
  }
134
146
  else if (outputs != null && typeof outputs === "object") {
135
147
  for (const value of Object.values(outputs)) {
136
148
  if (isBaseMessage(value)) {
137
- this._emit(metadata, value, true);
149
+ this._emit(metadata, value, runId, true);
138
150
  }
139
151
  else if (Array.isArray(value)) {
140
152
  for (const item of value) {
141
153
  if (isBaseMessage(item)) {
142
- this._emit(metadata, item, true);
154
+ this._emit(metadata, item, runId, true);
143
155
  }
144
156
  }
145
157
  }
@@ -41,18 +41,13 @@ const getStreamModes = (streamMode, defaultStreamMode = "updates") => {
41
41
  if (streamMode !== undefined &&
42
42
  (typeof streamMode === "string" ||
43
43
  (Array.isArray(streamMode) && streamMode.length > 0))) {
44
- if (typeof streamMode === "string") {
45
- updatedStreamModes.push(streamMode);
46
- }
47
- else {
48
- reqSingle = false;
49
- updatedStreamModes.push(...streamMode);
50
- }
44
+ reqSingle = typeof streamMode === "string";
45
+ const mapped = Array.isArray(streamMode) ? streamMode : [streamMode];
46
+ updatedStreamModes.push(...mapped);
51
47
  }
52
48
  else {
53
49
  updatedStreamModes.push(defaultStreamMode);
54
50
  }
55
- // TODO: Map messages to messages-tuple
56
51
  if (updatedStreamModes.includes("updates")) {
57
52
  reqUpdates = true;
58
53
  }
@@ -303,7 +298,11 @@ class RemoteGraph extends runnables_1.Runnable {
303
298
  ...updatedStreamModes,
304
299
  ...(streamProtocolInstance?.modes ?? new Set()),
305
300
  ]),
306
- ];
301
+ ].map((mode) => {
302
+ if (mode === "messages")
303
+ return "messages-tuple";
304
+ return mode;
305
+ });
307
306
  let command;
308
307
  let serializedInput;
309
308
  if ((0, constants_js_1.isCommand)(input)) {
@@ -38,18 +38,13 @@ const getStreamModes = (streamMode, defaultStreamMode = "updates") => {
38
38
  if (streamMode !== undefined &&
39
39
  (typeof streamMode === "string" ||
40
40
  (Array.isArray(streamMode) && streamMode.length > 0))) {
41
- if (typeof streamMode === "string") {
42
- updatedStreamModes.push(streamMode);
43
- }
44
- else {
45
- reqSingle = false;
46
- updatedStreamModes.push(...streamMode);
47
- }
41
+ reqSingle = typeof streamMode === "string";
42
+ const mapped = Array.isArray(streamMode) ? streamMode : [streamMode];
43
+ updatedStreamModes.push(...mapped);
48
44
  }
49
45
  else {
50
46
  updatedStreamModes.push(defaultStreamMode);
51
47
  }
52
- // TODO: Map messages to messages-tuple
53
48
  if (updatedStreamModes.includes("updates")) {
54
49
  reqUpdates = true;
55
50
  }
@@ -300,7 +295,11 @@ export class RemoteGraph extends Runnable {
300
295
  ...updatedStreamModes,
301
296
  ...(streamProtocolInstance?.modes ?? new Set()),
302
297
  ]),
303
- ];
298
+ ].map((mode) => {
299
+ if (mode === "messages")
300
+ return "messages-tuple";
301
+ return mode;
302
+ });
304
303
  let command;
305
304
  let serializedInput;
306
305
  if (isCommand(input)) {