@langgraph-js/sdk 1.1.3 → 1.1.4

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.
@@ -1,465 +1,469 @@
1
- import { Client, Thread, Message, Assistant, HumanMessage, AIMessage, ToolMessage, Command } from "@langchain/langgraph-sdk";
2
- import { ToolManager } from "./ToolManager";
3
- import { CallToolResult } from "./tool";
4
- import { AsyncCallerParams } from "@langchain/langgraph-sdk/dist/utils/async_caller";
5
-
6
- export type RenderMessage = Message & {
7
- /** 工具入参 ,聚合而来*/
8
- tool_input?: string;
9
- additional_kwargs?: {
10
- done?: boolean;
11
- tool_calls?: {
12
- function: {
13
- arguments: string;
14
- };
15
- }[];
16
- };
17
- usage_metadata?: {
18
- total_tokens: number;
19
- input_tokens: number;
20
- output_tokens: number;
21
- };
22
- response_metadata?: {
23
- create_time: string;
24
- };
25
- /** 耗时 */
26
- spend_time?: number;
27
- /** 渲染时的唯一 id,聚合而来*/
28
- unique_id?: string;
29
- };
30
- export type SendMessageOptions = {
31
- extraParams?: Record<string, any>;
32
- _debug?: { streamResponse?: any };
33
- command?: Command;
34
- };
35
- export interface LangGraphClientConfig {
36
- apiUrl?: string;
37
- apiKey?: string;
38
- callerOptions?: AsyncCallerParams;
39
- timeoutMs?: number;
40
- defaultHeaders?: Record<string, string | null | undefined>;
41
- }
42
-
43
- export class StreamingMessageType {
44
- static isUser(m: Message) {
45
- return m.type === "human";
46
- }
47
- static isTool(m: Message) {
48
- return m.type === "tool";
49
- }
50
- static isAssistant(m: Message) {
51
- return m.type === "ai" && !this.isToolAssistant(m);
52
- }
53
- static isToolAssistant(m: Message) {
54
- /** @ts-ignore */
55
- return m.type === "ai" && (m.tool_calls?.length || m.tool_call_chunks?.length);
56
- }
57
- }
58
-
59
- type StreamingUpdateEvent = {
60
- type: "message" | "value" | "update" | "error" | "thread" | "done";
61
- data: any;
62
- };
63
-
64
- type StreamingUpdateCallback = (event: StreamingUpdateEvent) => void;
65
-
66
- export class LangGraphClient extends Client {
67
- private currentAssistant: Assistant | null = null;
68
- private currentThread: Thread | null = null;
69
- private streamingCallbacks: Set<StreamingUpdateCallback> = new Set();
70
- tools: ToolManager = new ToolManager();
71
- stopController: AbortController | null = null;
72
-
73
- constructor(config: LangGraphClientConfig) {
74
- super(config);
75
- }
76
- availableAssistants: Assistant[] = [];
77
- private listAssistants() {
78
- return this.assistants.search({
79
- metadata: null,
80
- offset: 0,
81
- limit: 100,
82
- });
83
- }
84
- async initAssistant(agentName: string) {
85
- try {
86
- const assistants = await this.listAssistants();
87
- this.availableAssistants = assistants;
88
- if (assistants.length > 0) {
89
- this.currentAssistant = assistants.find((assistant) => assistant.name === agentName) || null;
90
- if (!this.currentAssistant) {
91
- throw new Error("Agent not found");
92
- }
93
- } else {
94
- throw new Error("No assistants found");
95
- }
96
- } catch (error) {
97
- console.error("Failed to initialize LangGraphClient:", error);
98
- throw error;
99
- }
100
- }
101
-
102
- async createThread({
103
- threadId,
104
- }: {
105
- threadId?: string;
106
- } = {}) {
107
- try {
108
- this.currentThread = await this.threads.create({
109
- threadId,
110
- });
111
- return this.currentThread;
112
- } catch (error) {
113
- console.error("Failed to create new thread:", error);
114
- throw error;
115
- }
116
- }
117
- async listThreads<T>() {
118
- return this.threads.search<T>({
119
- sortOrder: "desc",
120
- });
121
- }
122
- /** 从历史中恢复数据 */
123
- async resetThread(agent: string, threadId: string) {
124
- await this.initAssistant(agent);
125
- this.currentThread = await this.threads.get(threadId);
126
- this.graphState = this.currentThread.values;
127
- this.graphMessages = this.graphState.messages;
128
- this.emitStreamingUpdate({
129
- type: "value",
130
- data: {
131
- event: "messages/partial",
132
- data: {
133
- messages: this.graphMessages,
134
- },
135
- },
136
- });
137
- }
138
-
139
- streamingMessage: RenderMessage[] = [];
140
- /** 图发过来的更新信息 */
141
- graphMessages: RenderMessage[] = [];
142
- cloneMessage(message: Message): Message {
143
- return JSON.parse(JSON.stringify(message));
144
- }
145
- private replaceMessageWithValuesMessage(message: AIMessage | ToolMessage, isTool = false): Message {
146
- const key = (isTool ? "tool_call_id" : "id") as any as "id";
147
- const valuesMessage = this.graphMessages.find((i) => i[key] === message[key]);
148
- if (valuesMessage) {
149
- return {
150
- ...valuesMessage,
151
- /** @ts-ignore */
152
- tool_input: message.tool_input,
153
- };
154
- }
155
- return message;
156
- }
157
-
158
- /** 用于 UI 中的流式渲染中的消息 */
159
- get renderMessage() {
160
- const previousMessage = new Map<string, Message>();
161
- const result: Message[] = [];
162
- const inputMessages = [...this.graphMessages, ...this.streamingMessage];
163
-
164
- // 从后往前遍历,这样可以保证最新的消息在前面
165
- for (let i = inputMessages.length - 1; i >= 0; i--) {
166
- const message = this.cloneMessage(inputMessages[i]);
167
-
168
- if (!message.id) {
169
- result.unshift(message);
170
- continue;
171
- }
172
-
173
- // 如果已经处理过这个 id 的消息,跳过
174
- if (previousMessage.has(message.id)) {
175
- continue;
176
- }
177
-
178
- if (StreamingMessageType.isToolAssistant(message)) {
179
- const m = this.replaceMessageWithValuesMessage(message as AIMessage);
180
- // 记录这个 id 的消息,并添加到结果中
181
- previousMessage.set(message.id, m);
182
-
183
- /** @ts-ignore */
184
- const tool_calls: NonNullable<AIMessage["tool_calls"]> = (m as AIMessage).tool_calls?.length ? (m as AIMessage).tool_calls : (m as RenderMessage).tool_call_chunks;
185
- const new_tool_calls = tool_calls!.map((tool, index) => {
186
- return this.replaceMessageWithValuesMessage(
187
- {
188
- type: "tool",
189
- additional_kwargs: {},
190
- /** @ts-ignore */
191
- tool_input: m.additional_kwargs?.tool_calls[index].function.arguments,
192
- id: tool.id,
193
- name: tool.name,
194
- response_metadata: {},
195
- tool_call_id: tool.id!,
196
- },
197
- true
198
- );
199
- });
200
- for (const tool of new_tool_calls) {
201
- if (!previousMessage.has(tool.id!)) {
202
- result.unshift(tool);
203
- previousMessage.set(tool.id!, tool);
204
- }
205
- }
206
- result.unshift(m);
207
- } else {
208
- // 记录这个 id 的消息,并添加到结果中
209
- const m = this.replaceMessageWithValuesMessage(message as AIMessage);
210
- previousMessage.set(message.id, m);
211
- result.unshift(m);
212
- }
213
- }
214
-
215
- return this.attachInfoForMessage(this.composeToolMessages(result as RenderMessage[]));
216
- }
217
- attachInfoForMessage(result: RenderMessage[]) {
218
- let lastMessage: RenderMessage | null = null;
219
- for (const message of result) {
220
- const createTime = message.response_metadata?.create_time || "";
221
- // 用长度作为渲染 id,长度变了就要重新渲染
222
- message.unique_id = message.id! + JSON.stringify(message.content).length;
223
- message.spend_time = new Date(createTime).getTime() - new Date(lastMessage?.response_metadata?.create_time || createTime).getTime();
224
- if (!message.usage_metadata && (message as AIMessage).response_metadata?.usage) {
225
- const usage = (message as AIMessage).response_metadata!.usage as {
226
- prompt_tokens: number;
227
- completion_tokens: number;
228
- total_tokens: number;
229
- };
230
- message.usage_metadata = {
231
- input_tokens: usage.prompt_tokens,
232
- output_tokens: usage.completion_tokens,
233
- total_tokens: usage.total_tokens,
234
- };
235
- }
236
- lastMessage = message;
237
- }
238
- return result;
239
- }
240
- composeToolMessages(messages: RenderMessage[]): RenderMessage[] {
241
- const result: RenderMessage[] = [];
242
- const assistantToolMessages = new Map<string, { args: string }>();
243
- const toolParentMessage = new Map<string, RenderMessage>();
244
- for (const message of messages) {
245
- if (StreamingMessageType.isToolAssistant(message)) {
246
- /** @ts-ignore 只有 tool_call_chunks args 才是文本 */
247
- message.tool_call_chunks?.forEach((element) => {
248
- assistantToolMessages.set(element.id!, element);
249
- toolParentMessage.set(element.id!, message);
250
- });
251
- if (!message.content) continue;
252
- }
253
- if (StreamingMessageType.isTool(message) && !message.tool_input) {
254
- const assistantToolMessage = assistantToolMessages.get(message.tool_call_id!);
255
- const parentMessage = toolParentMessage.get(message.tool_call_id!);
256
- if (assistantToolMessage) {
257
- message.tool_input = assistantToolMessage.args;
258
- if (message.additional_kwargs) {
259
- message.additional_kwargs.done = true;
260
- } else {
261
- message.additional_kwargs = {
262
- done: true,
263
- };
264
- }
265
- }
266
- if (parentMessage) {
267
- message.usage_metadata = parentMessage.usage_metadata;
268
- }
269
- }
270
- result.push(message);
271
- }
272
- return result;
273
- }
274
- get tokenCounter() {
275
- return this.graphMessages.reduce(
276
- (acc, message) => {
277
- if (message.usage_metadata) {
278
- acc.total_tokens += message.usage_metadata?.total_tokens || 0;
279
- acc.input_tokens += message.usage_metadata?.input_tokens || 0;
280
- acc.output_tokens += message.usage_metadata?.output_tokens || 0;
281
- } else if ((message as AIMessage).response_metadata?.usage) {
282
- const usage = (message as AIMessage).response_metadata?.usage as {
283
- prompt_tokens: number;
284
- completion_tokens: number;
285
- total_tokens: number;
286
- };
287
- acc.total_tokens += usage.total_tokens || 0;
288
- acc.input_tokens += usage.prompt_tokens || 0;
289
- acc.output_tokens += usage.completion_tokens || 0;
290
- }
291
-
292
- return acc;
293
- },
294
- {
295
- total_tokens: 0,
296
- input_tokens: 0,
297
- output_tokens: 0,
298
- }
299
- );
300
- }
301
- onStreamingUpdate(callback: StreamingUpdateCallback) {
302
- this.streamingCallbacks.add(callback);
303
- return () => {
304
- this.streamingCallbacks.delete(callback);
305
- };
306
- }
307
-
308
- private emitStreamingUpdate(event: StreamingUpdateEvent) {
309
- this.streamingCallbacks.forEach((callback) => callback(event));
310
- }
311
- graphState: any = {};
312
- currentRun?: { run_id: string };
313
- cancelRun() {
314
- if (this.currentThread?.thread_id && this.currentRun?.run_id) {
315
- this.runs.cancel(this.currentThread!.thread_id, this.currentRun.run_id);
316
- }
317
- }
318
- async sendMessage(input: string | Message[], { extraParams, _debug, command }: SendMessageOptions = {}) {
319
- if (!this.currentAssistant) {
320
- throw new Error("Thread or Assistant not initialized");
321
- }
322
- if (!this.currentThread) {
323
- await this.createThread();
324
- this.emitStreamingUpdate({
325
- type: "thread",
326
- data: {
327
- event: "thread/create",
328
- data: {
329
- thread: this.currentThread,
330
- },
331
- },
332
- });
333
- }
334
-
335
- const messagesToSend = Array.isArray(input)
336
- ? input
337
- : [
338
- {
339
- type: "human",
340
- content: input,
341
- } as HumanMessage,
342
- ];
343
- const streamResponse =
344
- _debug?.streamResponse ||
345
- this.runs.stream(this.currentThread!.thread_id, this.currentAssistant.assistant_id, {
346
- input: { ...this.graphState, ...(extraParams || {}), messages: messagesToSend, fe_tools: this.tools.toJSON() },
347
- streamMode: ["messages", "values"],
348
- streamSubgraphs: true,
349
- command,
350
- });
351
- const streamRecord: any[] = [];
352
- for await (const chunk of streamResponse) {
353
- streamRecord.push(chunk);
354
- if (chunk.event === "metadata") {
355
- this.currentRun = chunk.data;
356
- } else if (chunk.event === "error") {
357
- this.emitStreamingUpdate({
358
- type: "error",
359
- data: chunk,
360
- });
361
- } else if (chunk.event === "messages/partial") {
362
- for (const message of chunk.data) {
363
- this.streamingMessage.push(message);
364
- }
365
- this.emitStreamingUpdate({
366
- type: "message",
367
- data: chunk,
368
- });
369
- continue;
370
- } else if (chunk.event.startsWith("values")) {
371
- const data = chunk.data as { messages: Message[] };
372
-
373
- if (data.messages) {
374
- const isResume = !!command?.resume;
375
- const isLongerThanLocal = data.messages.length >= this.graphMessages.length;
376
- // resume 情况下,长度低于前端 message 的统统不接受
377
- if (!isResume || (isResume && isLongerThanLocal)) {
378
- this.graphMessages = data.messages as RenderMessage[];
379
- this.emitStreamingUpdate({
380
- type: "value",
381
- data: chunk,
382
- });
383
- }
384
- }
385
- this.graphState = chunk.data;
386
- this.streamingMessage = [];
387
- continue;
388
- }
389
- }
390
- this.streamingMessage = [];
391
- const data = await this.runFETool();
392
- if (data) streamRecord.push(...data);
393
- this.emitStreamingUpdate({
394
- type: "done",
395
- data: {
396
- event: "done",
397
- },
398
- });
399
- return streamRecord;
400
- }
401
- private runFETool() {
402
- const data = this.graphMessages;
403
- const lastMessage = data[data.length - 1];
404
- // 如果最后一条消息是前端工具消息,则调用工具
405
- if (lastMessage.type === "ai" && lastMessage.tool_calls?.length) {
406
- const result = lastMessage.tool_calls.map((tool) => {
407
- if (this.tools.getTool(tool.name!)) {
408
- const toolMessage: ToolMessage = {
409
- ...tool,
410
- tool_call_id: tool.id!,
411
- /** @ts-ignore */
412
- tool_input: JSON.stringify(tool.args),
413
- additional_kwargs: {},
414
- };
415
- // json 校验
416
- return this.callFETool(toolMessage, tool.args);
417
- }
418
- });
419
- return Promise.all(result);
420
- }
421
- }
422
- private async callFETool(message: ToolMessage, args: any) {
423
- const that = this; // 防止 this 被错误解析
424
- const result = await this.tools.callTool(message.name!, args, { client: that, message });
425
- return this.resume(result);
426
- }
427
- /** 恢复消息,当中断流时使用 */
428
- resume(result: CallToolResult) {
429
- return this.sendMessage([], {
430
- command: {
431
- resume: result,
432
- },
433
- });
434
- }
435
- /** 完成工具等待 */
436
- doneFEToolWaiting(id: string, result: CallToolResult) {
437
- const done = this.tools.doneWaiting(id, result);
438
- if (!done && this.currentThread?.status === "interrupted") {
439
- this.resume(result);
440
- }
441
- }
442
-
443
- getCurrentThread() {
444
- return this.currentThread;
445
- }
446
-
447
- getCurrentAssistant() {
448
- return this.currentAssistant;
449
- }
450
-
451
- async reset() {
452
- await this.initAssistant(this.currentAssistant?.name!);
453
- this.currentThread = null;
454
- this.graphState = {};
455
- this.graphMessages = [];
456
- this.streamingMessage = [];
457
- this.currentRun = undefined;
458
- this.emitStreamingUpdate({
459
- type: "value",
460
- data: {
461
- event: "messages/partial",
462
- },
463
- });
464
- }
465
- }
1
+ import { Client, Thread, Message, Assistant, HumanMessage, AIMessage, ToolMessage, Command } from "@langchain/langgraph-sdk";
2
+ import { ToolManager } from "./ToolManager";
3
+ import { CallToolResult } from "./tool";
4
+ import { AsyncCallerParams } from "@langchain/langgraph-sdk/dist/utils/async_caller";
5
+
6
+ export type RenderMessage = Message & {
7
+ /** 工具入参 ,聚合而来*/
8
+ tool_input?: string;
9
+ additional_kwargs?: {
10
+ done?: boolean;
11
+ tool_calls?: {
12
+ function: {
13
+ arguments: string;
14
+ };
15
+ }[];
16
+ };
17
+ usage_metadata?: {
18
+ total_tokens: number;
19
+ input_tokens: number;
20
+ output_tokens: number;
21
+ };
22
+ response_metadata?: {
23
+ create_time: string;
24
+ };
25
+ /** 耗时 */
26
+ spend_time?: number;
27
+ /** 渲染时的唯一 id,聚合而来*/
28
+ unique_id?: string;
29
+ };
30
+ export type SendMessageOptions = {
31
+ extraParams?: Record<string, any>;
32
+ _debug?: { streamResponse?: any };
33
+ command?: Command;
34
+ };
35
+ export interface LangGraphClientConfig {
36
+ apiUrl?: string;
37
+ apiKey?: string;
38
+ callerOptions?: AsyncCallerParams;
39
+ timeoutMs?: number;
40
+ defaultHeaders?: Record<string, string | null | undefined>;
41
+ }
42
+
43
+ export class StreamingMessageType {
44
+ static isUser(m: Message) {
45
+ return m.type === "human";
46
+ }
47
+ static isTool(m: Message) {
48
+ return m.type === "tool";
49
+ }
50
+ static isAssistant(m: Message) {
51
+ return m.type === "ai" && !this.isToolAssistant(m);
52
+ }
53
+ static isToolAssistant(m: Message) {
54
+ /** @ts-ignore */
55
+ return m.type === "ai" && (m.tool_calls?.length || m.tool_call_chunks?.length);
56
+ }
57
+ }
58
+
59
+ type StreamingUpdateEvent = {
60
+ type: "message" | "value" | "update" | "error" | "thread" | "done";
61
+ data: any;
62
+ };
63
+
64
+ type StreamingUpdateCallback = (event: StreamingUpdateEvent) => void;
65
+
66
+ export class LangGraphClient extends Client {
67
+ private currentAssistant: Assistant | null = null;
68
+ private currentThread: Thread | null = null;
69
+ private streamingCallbacks: Set<StreamingUpdateCallback> = new Set();
70
+ tools: ToolManager = new ToolManager();
71
+ stopController: AbortController | null = null;
72
+
73
+ constructor(config: LangGraphClientConfig) {
74
+ super(config);
75
+ }
76
+ availableAssistants: Assistant[] = [];
77
+ private listAssistants() {
78
+ return this.assistants.search({
79
+ metadata: null,
80
+ offset: 0,
81
+ limit: 100,
82
+ });
83
+ }
84
+ async initAssistant(agentName: string) {
85
+ try {
86
+ const assistants = await this.listAssistants();
87
+ this.availableAssistants = assistants;
88
+ if (assistants.length > 0) {
89
+ this.currentAssistant = assistants.find((assistant) => assistant.name === agentName) || null;
90
+ if (!this.currentAssistant) {
91
+ throw new Error("Agent not found");
92
+ }
93
+ } else {
94
+ throw new Error("No assistants found");
95
+ }
96
+ } catch (error) {
97
+ console.error("Failed to initialize LangGraphClient:", error);
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ async createThread({
103
+ threadId,
104
+ }: {
105
+ threadId?: string;
106
+ } = {}) {
107
+ try {
108
+ this.currentThread = await this.threads.create({
109
+ threadId,
110
+ });
111
+ return this.currentThread;
112
+ } catch (error) {
113
+ console.error("Failed to create new thread:", error);
114
+ throw error;
115
+ }
116
+ }
117
+ async listThreads<T>() {
118
+ return this.threads.search<T>({
119
+ sortOrder: "desc",
120
+ });
121
+ }
122
+ /** 从历史中恢复数据 */
123
+ async resetThread(agent: string, threadId: string) {
124
+ await this.initAssistant(agent);
125
+ this.currentThread = await this.threads.get(threadId);
126
+ this.graphState = this.currentThread.values;
127
+ this.graphMessages = this.graphState.messages;
128
+ this.emitStreamingUpdate({
129
+ type: "value",
130
+ data: {
131
+ event: "messages/partial",
132
+ data: {
133
+ messages: this.graphMessages,
134
+ },
135
+ },
136
+ });
137
+ }
138
+
139
+ streamingMessage: RenderMessage[] = [];
140
+ /** 图发过来的更新信息 */
141
+ graphMessages: RenderMessage[] = [];
142
+ cloneMessage(message: Message): Message {
143
+ return JSON.parse(JSON.stringify(message));
144
+ }
145
+ private replaceMessageWithValuesMessage(message: AIMessage | ToolMessage, isTool = false): Message {
146
+ const key = (isTool ? "tool_call_id" : "id") as any as "id";
147
+ const valuesMessage = this.graphMessages.find((i) => i[key] === message[key]);
148
+ if (valuesMessage) {
149
+ return {
150
+ ...valuesMessage,
151
+ /** @ts-ignore */
152
+ tool_input: message.tool_input,
153
+ };
154
+ }
155
+ return message;
156
+ }
157
+
158
+ /** 用于 UI 中的流式渲染中的消息 */
159
+ get renderMessage() {
160
+ const previousMessage = new Map<string, Message>();
161
+ const result: Message[] = [];
162
+ const inputMessages = [...this.graphMessages, ...this.streamingMessage];
163
+
164
+ // 从后往前遍历,这样可以保证最新的消息在前面
165
+ for (let i = inputMessages.length - 1; i >= 0; i--) {
166
+ const message = this.cloneMessage(inputMessages[i]);
167
+
168
+ if (!message.id) {
169
+ result.unshift(message);
170
+ continue;
171
+ }
172
+
173
+ // 如果已经处理过这个 id 的消息,跳过
174
+ if (previousMessage.has(message.id)) {
175
+ continue;
176
+ }
177
+
178
+ if (StreamingMessageType.isToolAssistant(message)) {
179
+ const m = this.replaceMessageWithValuesMessage(message as AIMessage);
180
+ // 记录这个 id 的消息,并添加到结果中
181
+ previousMessage.set(message.id, m);
182
+
183
+ /** @ts-ignore */
184
+ const tool_calls: NonNullable<AIMessage["tool_calls"]> = (m as AIMessage).tool_calls?.length ? (m as AIMessage).tool_calls : (m as RenderMessage).tool_call_chunks;
185
+ const new_tool_calls = tool_calls!.map((tool, index) => {
186
+ return this.replaceMessageWithValuesMessage(
187
+ {
188
+ type: "tool",
189
+ additional_kwargs: {},
190
+ /** @ts-ignore */
191
+ tool_input: m.additional_kwargs?.tool_calls[index].function.arguments,
192
+ id: tool.id,
193
+ name: tool.name,
194
+ response_metadata: {},
195
+ tool_call_id: tool.id!,
196
+ },
197
+ true
198
+ );
199
+ });
200
+ for (const tool of new_tool_calls) {
201
+ if (!previousMessage.has(tool.id!)) {
202
+ result.unshift(tool);
203
+ previousMessage.set(tool.id!, tool);
204
+ }
205
+ }
206
+ result.unshift(m);
207
+ } else {
208
+ // 记录这个 id 的消息,并添加到结果中
209
+ const m = this.replaceMessageWithValuesMessage(message as AIMessage);
210
+ previousMessage.set(message.id, m);
211
+ result.unshift(m);
212
+ }
213
+ }
214
+
215
+ return this.attachInfoForMessage(this.composeToolMessages(result as RenderMessage[]));
216
+ }
217
+ attachInfoForMessage(result: RenderMessage[]) {
218
+ let lastMessage: RenderMessage | null = null;
219
+ for (const message of result) {
220
+ const createTime = message.response_metadata?.create_time || "";
221
+ try {
222
+ // 用长度作为渲染 id,长度变了就要重新渲染
223
+ message.unique_id = message.id! + JSON.stringify(message.content).length;
224
+ } catch (e) {
225
+ message.unique_id = message.id!;
226
+ }
227
+ message.spend_time = new Date(createTime).getTime() - new Date(lastMessage?.response_metadata?.create_time || createTime).getTime();
228
+ if (!message.usage_metadata && (message as AIMessage).response_metadata?.usage) {
229
+ const usage = (message as AIMessage).response_metadata!.usage as {
230
+ prompt_tokens: number;
231
+ completion_tokens: number;
232
+ total_tokens: number;
233
+ };
234
+ message.usage_metadata = {
235
+ input_tokens: usage.prompt_tokens,
236
+ output_tokens: usage.completion_tokens,
237
+ total_tokens: usage.total_tokens,
238
+ };
239
+ }
240
+ lastMessage = message;
241
+ }
242
+ return result;
243
+ }
244
+ composeToolMessages(messages: RenderMessage[]): RenderMessage[] {
245
+ const result: RenderMessage[] = [];
246
+ const assistantToolMessages = new Map<string, { args: string }>();
247
+ const toolParentMessage = new Map<string, RenderMessage>();
248
+ for (const message of messages) {
249
+ if (StreamingMessageType.isToolAssistant(message)) {
250
+ /** @ts-ignore 只有 tool_call_chunks 的 args 才是文本 */
251
+ (message.tool_calls || message.tool_call_chunks)?.forEach((element) => {
252
+ assistantToolMessages.set(element.id!, element);
253
+ toolParentMessage.set(element.id!, message);
254
+ });
255
+ if (!message.content) continue;
256
+ }
257
+ if (StreamingMessageType.isTool(message) && !message.tool_input) {
258
+ const assistantToolMessage = assistantToolMessages.get(message.tool_call_id!);
259
+ const parentMessage = toolParentMessage.get(message.tool_call_id!);
260
+ if (assistantToolMessage) {
261
+ message.tool_input = typeof assistantToolMessage.args !== "object" ? JSON.stringify(assistantToolMessage.args) : assistantToolMessage.args;
262
+ if (message.additional_kwargs) {
263
+ message.additional_kwargs.done = true;
264
+ } else {
265
+ message.additional_kwargs = {
266
+ done: true,
267
+ };
268
+ }
269
+ }
270
+ if (parentMessage) {
271
+ message.usage_metadata = parentMessage.usage_metadata;
272
+ }
273
+ }
274
+ result.push(message);
275
+ }
276
+ return result;
277
+ }
278
+ get tokenCounter() {
279
+ return this.graphMessages.reduce(
280
+ (acc, message) => {
281
+ if (message.usage_metadata) {
282
+ acc.total_tokens += message.usage_metadata?.total_tokens || 0;
283
+ acc.input_tokens += message.usage_metadata?.input_tokens || 0;
284
+ acc.output_tokens += message.usage_metadata?.output_tokens || 0;
285
+ } else if ((message as AIMessage).response_metadata?.usage) {
286
+ const usage = (message as AIMessage).response_metadata?.usage as {
287
+ prompt_tokens: number;
288
+ completion_tokens: number;
289
+ total_tokens: number;
290
+ };
291
+ acc.total_tokens += usage.total_tokens || 0;
292
+ acc.input_tokens += usage.prompt_tokens || 0;
293
+ acc.output_tokens += usage.completion_tokens || 0;
294
+ }
295
+
296
+ return acc;
297
+ },
298
+ {
299
+ total_tokens: 0,
300
+ input_tokens: 0,
301
+ output_tokens: 0,
302
+ }
303
+ );
304
+ }
305
+ onStreamingUpdate(callback: StreamingUpdateCallback) {
306
+ this.streamingCallbacks.add(callback);
307
+ return () => {
308
+ this.streamingCallbacks.delete(callback);
309
+ };
310
+ }
311
+
312
+ private emitStreamingUpdate(event: StreamingUpdateEvent) {
313
+ this.streamingCallbacks.forEach((callback) => callback(event));
314
+ }
315
+ graphState: any = {};
316
+ currentRun?: { run_id: string };
317
+ cancelRun() {
318
+ if (this.currentThread?.thread_id && this.currentRun?.run_id) {
319
+ this.runs.cancel(this.currentThread!.thread_id, this.currentRun.run_id);
320
+ }
321
+ }
322
+ async sendMessage(input: string | Message[], { extraParams, _debug, command }: SendMessageOptions = {}) {
323
+ if (!this.currentAssistant) {
324
+ throw new Error("Thread or Assistant not initialized");
325
+ }
326
+ if (!this.currentThread) {
327
+ await this.createThread();
328
+ this.emitStreamingUpdate({
329
+ type: "thread",
330
+ data: {
331
+ event: "thread/create",
332
+ data: {
333
+ thread: this.currentThread,
334
+ },
335
+ },
336
+ });
337
+ }
338
+
339
+ const messagesToSend = Array.isArray(input)
340
+ ? input
341
+ : [
342
+ {
343
+ type: "human",
344
+ content: input,
345
+ } as HumanMessage,
346
+ ];
347
+ const streamResponse =
348
+ _debug?.streamResponse ||
349
+ this.runs.stream(this.currentThread!.thread_id, this.currentAssistant.assistant_id, {
350
+ input: { ...this.graphState, ...(extraParams || {}), messages: messagesToSend, fe_tools: this.tools.toJSON() },
351
+ streamMode: ["messages", "values"],
352
+ streamSubgraphs: true,
353
+ command,
354
+ });
355
+ const streamRecord: any[] = [];
356
+ for await (const chunk of streamResponse) {
357
+ streamRecord.push(chunk);
358
+ if (chunk.event === "metadata") {
359
+ this.currentRun = chunk.data;
360
+ } else if (chunk.event === "error") {
361
+ this.emitStreamingUpdate({
362
+ type: "error",
363
+ data: chunk,
364
+ });
365
+ } else if (chunk.event === "messages/partial") {
366
+ for (const message of chunk.data) {
367
+ this.streamingMessage.push(message);
368
+ }
369
+ this.emitStreamingUpdate({
370
+ type: "message",
371
+ data: chunk,
372
+ });
373
+ continue;
374
+ } else if (chunk.event.startsWith("values")) {
375
+ const data = chunk.data as { messages: Message[] };
376
+
377
+ if (data.messages) {
378
+ const isResume = !!command?.resume;
379
+ const isLongerThanLocal = data.messages.length >= this.graphMessages.length;
380
+ // resume 情况下,长度低于前端 message 的统统不接受
381
+ if (!isResume || (isResume && isLongerThanLocal)) {
382
+ this.graphMessages = data.messages as RenderMessage[];
383
+ this.emitStreamingUpdate({
384
+ type: "value",
385
+ data: chunk,
386
+ });
387
+ }
388
+ }
389
+ this.graphState = chunk.data;
390
+ this.streamingMessage = [];
391
+ continue;
392
+ }
393
+ }
394
+ this.streamingMessage = [];
395
+ const data = await this.runFETool();
396
+ if (data) streamRecord.push(...data);
397
+ this.emitStreamingUpdate({
398
+ type: "done",
399
+ data: {
400
+ event: "done",
401
+ },
402
+ });
403
+ return streamRecord;
404
+ }
405
+ private runFETool() {
406
+ const data = this.graphMessages;
407
+ const lastMessage = data[data.length - 1];
408
+ // 如果最后一条消息是前端工具消息,则调用工具
409
+ if (lastMessage.type === "ai" && lastMessage.tool_calls?.length) {
410
+ const result = lastMessage.tool_calls.map((tool) => {
411
+ if (this.tools.getTool(tool.name!)) {
412
+ const toolMessage: ToolMessage = {
413
+ ...tool,
414
+ tool_call_id: tool.id!,
415
+ /** @ts-ignore */
416
+ tool_input: JSON.stringify(tool.args),
417
+ additional_kwargs: {},
418
+ };
419
+ // json 校验
420
+ return this.callFETool(toolMessage, tool.args);
421
+ }
422
+ });
423
+ return Promise.all(result);
424
+ }
425
+ }
426
+ private async callFETool(message: ToolMessage, args: any) {
427
+ const that = this; // 防止 this 被错误解析
428
+ const result = await this.tools.callTool(message.name!, args, { client: that, message });
429
+ return this.resume(result);
430
+ }
431
+ /** 恢复消息,当中断流时使用 */
432
+ resume(result: CallToolResult) {
433
+ return this.sendMessage([], {
434
+ command: {
435
+ resume: result,
436
+ },
437
+ });
438
+ }
439
+ /** 完成工具等待 */
440
+ doneFEToolWaiting(id: string, result: CallToolResult) {
441
+ const done = this.tools.doneWaiting(id, result);
442
+ if (!done && this.currentThread?.status === "interrupted") {
443
+ this.resume(result);
444
+ }
445
+ }
446
+
447
+ getCurrentThread() {
448
+ return this.currentThread;
449
+ }
450
+
451
+ getCurrentAssistant() {
452
+ return this.currentAssistant;
453
+ }
454
+
455
+ async reset() {
456
+ await this.initAssistant(this.currentAssistant?.name!);
457
+ this.currentThread = null;
458
+ this.graphState = {};
459
+ this.graphMessages = [];
460
+ this.streamingMessage = [];
461
+ this.currentRun = undefined;
462
+ this.emitStreamingUpdate({
463
+ type: "value",
464
+ data: {
465
+ event: "messages/partial",
466
+ },
467
+ });
468
+ }
469
+ }