@langgraph-js/sdk 1.0.0 → 1.1.1
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.
- package/.env +0 -0
- package/README.md +163 -0
- package/dist/LangGraphClient.d.ts +101 -0
- package/dist/LangGraphClient.js +401 -0
- package/dist/SpendTime.d.ts +9 -0
- package/dist/SpendTime.js +32 -0
- package/dist/ToolManager.d.ts +63 -0
- package/dist/ToolManager.js +93 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/tool/copilotkit-actions.d.ts +66 -0
- package/dist/tool/copilotkit-actions.js +1 -0
- package/dist/tool/createTool.d.ts +47 -0
- package/dist/tool/createTool.js +61 -0
- package/dist/tool/index.d.ts +2 -0
- package/dist/tool/index.js +2 -0
- package/dist/tool/utils.d.ts +36 -0
- package/dist/tool/utils.js +120 -0
- package/dist/ui-store/UnionStore.d.ts +11 -0
- package/dist/ui-store/UnionStore.js +9 -0
- package/dist/ui-store/createChatStore.d.ts +43 -0
- package/dist/ui-store/createChatStore.js +145 -0
- package/dist/ui-store/index.d.ts +2 -0
- package/dist/ui-store/index.js +2 -0
- package/index.html +12 -0
- package/package.json +35 -7
- package/src/LangGraphClient.ts +461 -0
- package/src/SpendTime.ts +29 -0
- package/src/ToolManager.ts +100 -0
- package/src/index.ts +5 -0
- package/src/tool/copilotkit-actions.ts +72 -0
- package/src/tool/createTool.ts +78 -0
- package/src/tool/index.ts +2 -0
- package/src/tool/utils.ts +158 -0
- package/src/ui-store/UnionStore.ts +20 -0
- package/src/ui-store/createChatStore.ts +153 -0
- package/src/ui-store/index.ts +2 -0
- package/test/testResponse.json +5418 -0
- package/tsconfig.json +112 -0
- package/ui/index.ts +182 -0
- package/ui/tool.ts +55 -0
package/src/SpendTime.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class SpendTime {
|
|
2
|
+
private timeCounter = new Map<string, [Date, Date] | [Date]>();
|
|
3
|
+
|
|
4
|
+
start(key: string) {
|
|
5
|
+
this.timeCounter.set(key, [new Date()]);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
end(key: string) {
|
|
9
|
+
this.timeCounter.set(key, [this.timeCounter.get(key)?.[0] || new Date(), new Date()]);
|
|
10
|
+
}
|
|
11
|
+
setSpendTime(key: string) {
|
|
12
|
+
if (this.timeCounter.has(key)) {
|
|
13
|
+
this.end(key);
|
|
14
|
+
} else {
|
|
15
|
+
this.start(key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
getStartTime(key: string) {
|
|
19
|
+
return this.timeCounter.get(key)?.[0] || new Date();
|
|
20
|
+
}
|
|
21
|
+
getEndTime(key: string) {
|
|
22
|
+
return this.timeCounter.get(key)?.[1] || new Date();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getSpendTime(key: string) {
|
|
26
|
+
const [start, end = new Date()] = this.timeCounter.get(key) || [new Date(), new Date()];
|
|
27
|
+
return end.getTime() - start.getTime();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ToolMessage } from "@langchain/langgraph-sdk";
|
|
2
|
+
import { LangGraphClient } from "./LangGraphClient";
|
|
3
|
+
import { CallToolResult, createJSONDefineTool, UnionTool } from "./tool/createTool";
|
|
4
|
+
|
|
5
|
+
export class ToolManager {
|
|
6
|
+
private tools: Map<string, UnionTool<any>> = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 注册一个工具
|
|
10
|
+
* @param tool 要注册的工具
|
|
11
|
+
*/
|
|
12
|
+
bindTool(tool: UnionTool<any>) {
|
|
13
|
+
if (this.tools.has(tool.name)) {
|
|
14
|
+
throw new Error(`Tool with name ${tool.name} already exists`);
|
|
15
|
+
}
|
|
16
|
+
this.tools.set(tool.name, tool);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 注册多个工具
|
|
21
|
+
* @param tools 要注册的工具数组
|
|
22
|
+
*/
|
|
23
|
+
bindTools(tools: UnionTool<any>[]) {
|
|
24
|
+
tools.forEach((tool) => this.bindTool(tool));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 获取所有已注册的工具
|
|
29
|
+
* @returns 工具数组
|
|
30
|
+
*/
|
|
31
|
+
getAllTools(): UnionTool<any>[] {
|
|
32
|
+
return Array.from(this.tools.values());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取指定名称的工具
|
|
37
|
+
* @param name 工具名称
|
|
38
|
+
* @returns 工具实例或 undefined
|
|
39
|
+
*/
|
|
40
|
+
getTool(name: string): UnionTool<any> | undefined {
|
|
41
|
+
return this.tools.get(name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 移除指定名称的工具
|
|
46
|
+
* @param name 工具名称
|
|
47
|
+
* @returns 是否成功移除
|
|
48
|
+
*/
|
|
49
|
+
removeTool(name: string): boolean {
|
|
50
|
+
return this.tools.delete(name);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 清空所有工具
|
|
55
|
+
*/
|
|
56
|
+
clearTools() {
|
|
57
|
+
this.tools.clear();
|
|
58
|
+
}
|
|
59
|
+
async callTool(name: string, args: any, context: { client: LangGraphClient; message: ToolMessage }) {
|
|
60
|
+
const tool = this.getTool(name);
|
|
61
|
+
if (!tool) {
|
|
62
|
+
throw new Error(`Tool with name ${name} not found`);
|
|
63
|
+
}
|
|
64
|
+
return await tool.execute(args, context);
|
|
65
|
+
}
|
|
66
|
+
toJSON() {
|
|
67
|
+
return Array.from(this.tools.values()).map((i) => createJSONDefineTool(i));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// === 专门为前端设计的异步触发结构
|
|
71
|
+
private waitingMap: Map<string, (value: CallToolResult) => void> = new Map();
|
|
72
|
+
doneWaiting(id: string, value: CallToolResult) {
|
|
73
|
+
if (this.waitingMap.has(id)) {
|
|
74
|
+
this.waitingMap.get(id)!(value);
|
|
75
|
+
this.waitingMap.delete(id);
|
|
76
|
+
return true;
|
|
77
|
+
} else {
|
|
78
|
+
console.warn(`Waiting for tool ${id} not found`);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
waitForDone(id: string) {
|
|
83
|
+
if (this.waitingMap.has(id)) {
|
|
84
|
+
return this.waitingMap.get(id);
|
|
85
|
+
}
|
|
86
|
+
const promise = new Promise((resolve, reject) => {
|
|
87
|
+
this.waitingMap.set(id, resolve);
|
|
88
|
+
});
|
|
89
|
+
return promise;
|
|
90
|
+
}
|
|
91
|
+
/** 等待用户输入
|
|
92
|
+
* @example
|
|
93
|
+
* // 继续 chat 流
|
|
94
|
+
* client.tools.doneWaiting(message.id!, (e.target as any).value);
|
|
95
|
+
*/
|
|
96
|
+
static waitForUIDone<T>(_: T, context: { client: LangGraphClient; message: ToolMessage }) {
|
|
97
|
+
// console.log(context.message);
|
|
98
|
+
return context.client.tools.waitForDone(context.message.id!);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Message } from "@langchain/langgraph-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* copy and modify from copilotkit
|
|
4
|
+
* https://github.com/copilotkit/copilotkit
|
|
5
|
+
*
|
|
6
|
+
* MIT License
|
|
7
|
+
*/
|
|
8
|
+
type TypeMap = {
|
|
9
|
+
string: string;
|
|
10
|
+
number: number;
|
|
11
|
+
boolean: boolean;
|
|
12
|
+
object: object;
|
|
13
|
+
"string[]": string[];
|
|
14
|
+
"number[]": number[];
|
|
15
|
+
"boolean[]": boolean[];
|
|
16
|
+
"object[]": object[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type AbstractParameter = {
|
|
20
|
+
name: string;
|
|
21
|
+
type?: keyof TypeMap;
|
|
22
|
+
description?: string;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface StringParameter extends AbstractParameter {
|
|
27
|
+
type: "string";
|
|
28
|
+
enum?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ObjectParameter extends AbstractParameter {
|
|
32
|
+
type: "object";
|
|
33
|
+
attributes?: Parameter[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ObjectArrayParameter extends AbstractParameter {
|
|
37
|
+
type: "object[]";
|
|
38
|
+
attributes?: Parameter[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type SpecialParameters = StringParameter | ObjectParameter | ObjectArrayParameter;
|
|
42
|
+
interface BaseParameter extends AbstractParameter {
|
|
43
|
+
type?: Exclude<AbstractParameter["type"], SpecialParameters["type"]>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type Parameter = BaseParameter | SpecialParameters;
|
|
47
|
+
|
|
48
|
+
type OptionalParameterType<P extends AbstractParameter> = P["required"] extends false ? undefined : never;
|
|
49
|
+
|
|
50
|
+
type StringParameterType<P> = P extends StringParameter ? (P extends { enum?: Array<infer E> } ? E : string) : never;
|
|
51
|
+
|
|
52
|
+
type ObjectParameterType<P> = P extends ObjectParameter ? (P extends { attributes?: infer Attributes extends Parameter[] } ? MappedParameterTypes<Attributes> : object) : never;
|
|
53
|
+
|
|
54
|
+
type ObjectArrayParameterType<P> = P extends ObjectArrayParameter ? (P extends { attributes?: infer Attributes extends Parameter[] } ? MappedParameterTypes<Attributes>[] : any[]) : never;
|
|
55
|
+
|
|
56
|
+
type MappedTypeOrString<T> = T extends keyof TypeMap ? TypeMap[T] : string;
|
|
57
|
+
type BaseParameterType<P extends AbstractParameter> = P extends { type: infer T } ? (T extends BaseParameter["type"] ? MappedTypeOrString<T> : never) : string;
|
|
58
|
+
|
|
59
|
+
export type MappedParameterTypes<T extends Parameter[] | [] = []> = T extends []
|
|
60
|
+
? Record<string, any>
|
|
61
|
+
: {
|
|
62
|
+
[P in T[number] as P["name"]]: OptionalParameterType<P> | StringParameterType<P> | ObjectParameterType<P> | ObjectArrayParameterType<P> | BaseParameterType<P>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type Action<T extends Parameter[] | [] = []> = {
|
|
66
|
+
name: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
parameters?: T;
|
|
69
|
+
handler?: T extends [] ? () => any | Promise<any> : (args: MappedParameterTypes<T>, context?: any) => any | Promise<any>;
|
|
70
|
+
returnDirect?: boolean;
|
|
71
|
+
callbackMessage?: () => Message[];
|
|
72
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { actionParametersToJsonSchema, convertJsonSchemaToZodRawShape } from "./utils";
|
|
2
|
+
import { z, ZodRawShape, ZodTypeAny } from "zod";
|
|
3
|
+
import { Action, Parameter } from "./copilotkit-actions";
|
|
4
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
5
|
+
import { Message } from "@langchain/langgraph-sdk";
|
|
6
|
+
|
|
7
|
+
export interface UnionTool<Args extends ZodRawShape> {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: Args;
|
|
11
|
+
/** 是否直接返回工具结果,而不是通过消息返回 */
|
|
12
|
+
returnDirect?: boolean;
|
|
13
|
+
execute: ToolCallback<Args>;
|
|
14
|
+
/** 工具执行成功后触发的附加消息 */
|
|
15
|
+
callbackMessage?: (result: CallToolResult) => Message[];
|
|
16
|
+
}
|
|
17
|
+
export type ToolCallback<Args extends ZodRawShape> = (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => CallToolResult | Promise<CallToolResult>;
|
|
18
|
+
|
|
19
|
+
export type CallToolResult = string | { type: "text"; text: string }[];
|
|
20
|
+
|
|
21
|
+
/** 用于格式校验 */
|
|
22
|
+
export const createTool = <Args extends ZodRawShape>(tool: UnionTool<Args>) => {
|
|
23
|
+
return tool;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** 提供一种兼容 copilotkit 的定义方式,简化定义形式
|
|
27
|
+
* 来自 copilotkit 的 frontend action
|
|
28
|
+
*/
|
|
29
|
+
export const createFETool = <const T extends Parameter[], Args extends ZodRawShape>(tool: Action<T>): UnionTool<Args> => {
|
|
30
|
+
return {
|
|
31
|
+
name: tool.name,
|
|
32
|
+
description: tool.description || "",
|
|
33
|
+
parameters: convertJsonSchemaToZodRawShape(actionParametersToJsonSchema(tool.parameters || [])) as any,
|
|
34
|
+
returnDirect: tool.returnDirect,
|
|
35
|
+
callbackMessage: tool.callbackMessage,
|
|
36
|
+
async execute(args, context) {
|
|
37
|
+
try {
|
|
38
|
+
const result = await tool.handler?.(args, context);
|
|
39
|
+
if (typeof result === "string") {
|
|
40
|
+
return [{ type: "text", text: result }];
|
|
41
|
+
}
|
|
42
|
+
return [{ type: "text", text: JSON.stringify(result) }];
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return [{ type: "text", text: `Error: ${error}` }];
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
///======= UnionTool 到 各种工具的辅助函数
|
|
51
|
+
export const createJSONDefineTool = <Args extends ZodRawShape>(tool: UnionTool<Args>) => {
|
|
52
|
+
return {
|
|
53
|
+
name: tool.name,
|
|
54
|
+
description: tool.description,
|
|
55
|
+
parameters: zodToJsonSchema(z.object(tool.parameters)),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const createMCPTool = <Args extends ZodRawShape>(tool: UnionTool<Args>) => {
|
|
60
|
+
return [
|
|
61
|
+
tool.name,
|
|
62
|
+
tool.description,
|
|
63
|
+
tool.parameters,
|
|
64
|
+
async (args: z.objectOutputType<Args, ZodTypeAny>) => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await tool.execute(args);
|
|
67
|
+
if (typeof result === "string") {
|
|
68
|
+
return { content: [{ type: "text", text: result }] };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
content: result,
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { content: [{ type: "text", text: `Error: ${error}` }], isError: true };
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* copy and modify from copilotkit
|
|
3
|
+
* https://github.com/copilotkit/copilotkit
|
|
4
|
+
*
|
|
5
|
+
* MIT License
|
|
6
|
+
*/
|
|
7
|
+
import { z, ZodRawShape } from "zod";
|
|
8
|
+
import { Parameter } from "./copilotkit-actions";
|
|
9
|
+
|
|
10
|
+
export type JSONSchemaString = {
|
|
11
|
+
type: "string";
|
|
12
|
+
description?: string;
|
|
13
|
+
enum?: string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type JSONSchemaNumber = {
|
|
17
|
+
type: "number";
|
|
18
|
+
description?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type JSONSchemaBoolean = {
|
|
22
|
+
type: "boolean";
|
|
23
|
+
description?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type JSONSchemaObject = {
|
|
27
|
+
type: "object";
|
|
28
|
+
properties?: Record<string, JSONSchema>;
|
|
29
|
+
required?: string[];
|
|
30
|
+
description?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type JSONSchemaArray = {
|
|
34
|
+
type: "array";
|
|
35
|
+
items: JSONSchema;
|
|
36
|
+
description?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type JSONSchema = JSONSchemaString | JSONSchemaNumber | JSONSchemaBoolean | JSONSchemaObject | JSONSchemaArray;
|
|
40
|
+
|
|
41
|
+
export function actionParametersToJsonSchema(actionParameters: Parameter[]): JSONSchema {
|
|
42
|
+
// Create the parameters object based on the argumentAnnotations
|
|
43
|
+
let parameters: { [key: string]: any } = {};
|
|
44
|
+
for (let parameter of actionParameters || []) {
|
|
45
|
+
parameters[parameter.name] = convertAttribute(parameter);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let requiredParameterNames: string[] = [];
|
|
49
|
+
for (let arg of actionParameters || []) {
|
|
50
|
+
if (arg.required !== false) {
|
|
51
|
+
requiredParameterNames.push(arg.name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Create the ChatCompletionFunctions object
|
|
56
|
+
return {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: parameters,
|
|
59
|
+
required: requiredParameterNames,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function convertAttribute(attribute: Parameter): JSONSchema {
|
|
64
|
+
switch (attribute.type) {
|
|
65
|
+
case "string":
|
|
66
|
+
return {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: attribute.description,
|
|
69
|
+
...(attribute.enum && { enum: attribute.enum }),
|
|
70
|
+
};
|
|
71
|
+
case "number":
|
|
72
|
+
case "boolean":
|
|
73
|
+
return {
|
|
74
|
+
type: attribute.type,
|
|
75
|
+
description: attribute.description,
|
|
76
|
+
};
|
|
77
|
+
case "object":
|
|
78
|
+
case "object[]":
|
|
79
|
+
const properties = attribute.attributes?.reduce(
|
|
80
|
+
(acc, attr) => {
|
|
81
|
+
acc[attr.name] = convertAttribute(attr);
|
|
82
|
+
return acc;
|
|
83
|
+
},
|
|
84
|
+
{} as Record<string, any>
|
|
85
|
+
);
|
|
86
|
+
const required = attribute.attributes?.filter((attr) => attr.required !== false).map((attr) => attr.name);
|
|
87
|
+
if (attribute.type === "object[]") {
|
|
88
|
+
return {
|
|
89
|
+
type: "array",
|
|
90
|
+
items: {
|
|
91
|
+
type: "object",
|
|
92
|
+
...(properties && { properties }),
|
|
93
|
+
...(required && required.length > 0 && { required }),
|
|
94
|
+
},
|
|
95
|
+
description: attribute.description,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
type: "object",
|
|
100
|
+
description: attribute.description,
|
|
101
|
+
...(properties && { properties }),
|
|
102
|
+
...(required && required.length > 0 && { required }),
|
|
103
|
+
};
|
|
104
|
+
default:
|
|
105
|
+
// Handle arrays of primitive types and undefined attribute.type
|
|
106
|
+
if (attribute.type?.endsWith("[]")) {
|
|
107
|
+
const itemType = attribute.type.slice(0, -2);
|
|
108
|
+
return {
|
|
109
|
+
type: "array",
|
|
110
|
+
items: { type: itemType as any },
|
|
111
|
+
description: attribute.description,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Fallback for undefined type or any other unexpected type
|
|
115
|
+
return {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: attribute.description,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function convertJsonSchemaToZodRawShape(jsonSchema: any): ZodRawShape {
|
|
123
|
+
const spec: { [key: string]: z.ZodSchema } = {};
|
|
124
|
+
for (const [key, value] of Object.entries(jsonSchema.properties)) {
|
|
125
|
+
spec[key] = convertJsonSchemaToZodSchema(value, jsonSchema.required ? jsonSchema.required.includes(key) : false);
|
|
126
|
+
}
|
|
127
|
+
return spec;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function convertJsonSchemaToZodSchema(jsonSchema: any, required: boolean): z.ZodSchema {
|
|
131
|
+
if (jsonSchema.type === "object") {
|
|
132
|
+
const spec: { [key: string]: z.ZodSchema } = {};
|
|
133
|
+
|
|
134
|
+
if (!jsonSchema.properties || !Object.keys(jsonSchema.properties).length) {
|
|
135
|
+
return !required ? z.object(spec).optional() : z.object(spec);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const [key, value] of Object.entries(jsonSchema.properties)) {
|
|
139
|
+
spec[key] = convertJsonSchemaToZodSchema(value, jsonSchema.required ? jsonSchema.required.includes(key) : false);
|
|
140
|
+
}
|
|
141
|
+
let schema = z.object(spec).describe(jsonSchema.description);
|
|
142
|
+
return required ? schema : schema.optional();
|
|
143
|
+
} else if (jsonSchema.type === "string") {
|
|
144
|
+
let schema = z.string().describe(jsonSchema.description);
|
|
145
|
+
return required ? schema : schema.optional();
|
|
146
|
+
} else if (jsonSchema.type === "number") {
|
|
147
|
+
let schema = z.number().describe(jsonSchema.description);
|
|
148
|
+
return required ? schema : schema.optional();
|
|
149
|
+
} else if (jsonSchema.type === "boolean") {
|
|
150
|
+
let schema = z.boolean().describe(jsonSchema.description);
|
|
151
|
+
return required ? schema : schema.optional();
|
|
152
|
+
} else if (jsonSchema.type === "array") {
|
|
153
|
+
let itemSchema = convertJsonSchemaToZodSchema(jsonSchema.items, true);
|
|
154
|
+
let schema = z.array(itemSchema).describe(jsonSchema.description);
|
|
155
|
+
return required ? schema : schema.optional();
|
|
156
|
+
}
|
|
157
|
+
throw new Error("Invalid JSON schema");
|
|
158
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { atom, PreinitializedWritableAtom, StoreValue } from "nanostores";
|
|
2
|
+
export type UnionStore<T extends { data: Record<string, PreinitializedWritableAtom<any>>; mutations: Record<string, any> }> = {
|
|
3
|
+
[k in keyof T["data"]]: StoreValue<T["data"][k]>;
|
|
4
|
+
} & T["mutations"];
|
|
5
|
+
|
|
6
|
+
export const useUnionStore = <T extends { data: Record<string, any>; mutations: Record<string, any> }>(
|
|
7
|
+
store: T,
|
|
8
|
+
useStore: (store: PreinitializedWritableAtom<any>) => StoreValue<T["data"][keyof T["data"]]>
|
|
9
|
+
): UnionStore<T> => {
|
|
10
|
+
const data: any = Object.fromEntries(
|
|
11
|
+
Object.entries(store.data as any).map(([key, value]) => {
|
|
12
|
+
return [key, useStore(value as any)];
|
|
13
|
+
})
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
...data,
|
|
18
|
+
...store.mutations,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { atom } from "nanostores";
|
|
2
|
+
import { LangGraphClient, LangGraphClientConfig, RenderMessage } from "../LangGraphClient";
|
|
3
|
+
import { Message, Thread } from "@langchain/langgraph-sdk";
|
|
4
|
+
export const formatTime = (date: Date) => {
|
|
5
|
+
return date.toLocaleTimeString("en-US");
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const formatTokens = (tokens: number) => {
|
|
9
|
+
return tokens.toLocaleString("en");
|
|
10
|
+
};
|
|
11
|
+
export const getMessageContent = (content: any) => {
|
|
12
|
+
if (typeof content === "string") return content;
|
|
13
|
+
if (Array.isArray(content)) {
|
|
14
|
+
return content
|
|
15
|
+
.map((item) => {
|
|
16
|
+
if (typeof item === "string") return item;
|
|
17
|
+
if (item.type === "text") return item.text;
|
|
18
|
+
if (item.type === "image_url") return `[图片]`;
|
|
19
|
+
return JSON.stringify(item);
|
|
20
|
+
})
|
|
21
|
+
.join("");
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(content);
|
|
24
|
+
};
|
|
25
|
+
export const createChatStore = (
|
|
26
|
+
initClientName: string,
|
|
27
|
+
config: LangGraphClientConfig,
|
|
28
|
+
context: {
|
|
29
|
+
onInit?: (client: LangGraphClient) => void;
|
|
30
|
+
} = {}
|
|
31
|
+
) => {
|
|
32
|
+
const client = atom<LangGraphClient | null>(null);
|
|
33
|
+
const renderMessages = atom<RenderMessage[]>([]);
|
|
34
|
+
const userInput = atom<string>("");
|
|
35
|
+
const loading = atom<boolean>(false);
|
|
36
|
+
const collapsedTools = atom<string[]>([]);
|
|
37
|
+
const inChatError = atom<string | null>(null);
|
|
38
|
+
const showHistory = atom<boolean>(true);
|
|
39
|
+
const currentAgent = atom<string>(initClientName);
|
|
40
|
+
const currentChatId = atom<string | null>(null);
|
|
41
|
+
|
|
42
|
+
const initClient = async () => {
|
|
43
|
+
const newClient = new LangGraphClient(config);
|
|
44
|
+
await newClient.initAssistant(currentAgent.get());
|
|
45
|
+
// 不再需要创建,sendMessage 会自动创建
|
|
46
|
+
// await newClient.createThread();
|
|
47
|
+
|
|
48
|
+
newClient.onStreamingUpdate((event) => {
|
|
49
|
+
if (event.type === "thread" || event.type === "done") {
|
|
50
|
+
// console.log(event.data);
|
|
51
|
+
// 创建新会话时,需要自动刷新历史面板
|
|
52
|
+
return refreshHistoryList();
|
|
53
|
+
}
|
|
54
|
+
if (event.type === "error") {
|
|
55
|
+
loading.set(false);
|
|
56
|
+
inChatError.set(event.data?.message || "发生错误");
|
|
57
|
+
}
|
|
58
|
+
console.log(newClient.renderMessage);
|
|
59
|
+
renderMessages.set(newClient.renderMessage);
|
|
60
|
+
});
|
|
61
|
+
context.onInit?.(newClient);
|
|
62
|
+
// newClient.tools.bindTools([fileTool, askUserTool]);
|
|
63
|
+
newClient.graphState = {};
|
|
64
|
+
client.set(newClient);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const sendMessage = async () => {
|
|
68
|
+
if (!userInput.get().trim() || loading.get() || !client.get()) return;
|
|
69
|
+
|
|
70
|
+
loading.set(true);
|
|
71
|
+
inChatError.set(null);
|
|
72
|
+
|
|
73
|
+
await client.get()?.sendMessage(userInput.get());
|
|
74
|
+
|
|
75
|
+
userInput.set("");
|
|
76
|
+
loading.set(false);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const interruptMessage = () => {
|
|
80
|
+
client.get()?.cancelRun();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const toggleToolCollapse = (toolId: string) => {
|
|
84
|
+
const prev = collapsedTools.get();
|
|
85
|
+
collapsedTools.set(prev.includes(toolId) ? prev.filter((id) => id !== toolId) : [...prev, toolId]);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const toggleHistoryVisible = () => {
|
|
89
|
+
showHistory.set(!showHistory.get());
|
|
90
|
+
};
|
|
91
|
+
const historyList = atom<Thread<{ messages: Message[] }>[]>([]);
|
|
92
|
+
const refreshHistoryList = async () => {
|
|
93
|
+
if (!client.get()) return;
|
|
94
|
+
try {
|
|
95
|
+
const response = await client.get()?.listThreads<{ messages: Message[] }>();
|
|
96
|
+
historyList.set(response || []);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("Failed to fetch threads:", error);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const addToHistory = (thread: Thread<{ messages: Message[] }>) => {
|
|
103
|
+
const prev = historyList.get();
|
|
104
|
+
historyList.set([thread, ...prev]);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
data: {
|
|
109
|
+
client,
|
|
110
|
+
renderMessages,
|
|
111
|
+
userInput,
|
|
112
|
+
loading,
|
|
113
|
+
inChatError,
|
|
114
|
+
currentAgent,
|
|
115
|
+
collapsedTools,
|
|
116
|
+
showHistory,
|
|
117
|
+
historyList,
|
|
118
|
+
currentChatId,
|
|
119
|
+
},
|
|
120
|
+
mutations: {
|
|
121
|
+
initClient,
|
|
122
|
+
sendMessage,
|
|
123
|
+
interruptMessage,
|
|
124
|
+
toggleToolCollapse,
|
|
125
|
+
toggleHistoryVisible,
|
|
126
|
+
refreshHistoryList,
|
|
127
|
+
addToHistory,
|
|
128
|
+
setUserInput(input: string) {
|
|
129
|
+
userInput.set(input);
|
|
130
|
+
},
|
|
131
|
+
setCurrentAgent(agent: string) {
|
|
132
|
+
currentAgent.set(agent);
|
|
133
|
+
return initClient().then(() => {
|
|
134
|
+
refreshHistoryList();
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
createNewChat() {
|
|
138
|
+
client.get()?.reset();
|
|
139
|
+
},
|
|
140
|
+
toHistoryChat(
|
|
141
|
+
thread: Thread<{
|
|
142
|
+
messages: Message[];
|
|
143
|
+
}>
|
|
144
|
+
) {
|
|
145
|
+
client.get()?.resetThread(thread.metadata?.graph_id as string, thread.thread_id);
|
|
146
|
+
},
|
|
147
|
+
async deleteHistoryChat(thread: Thread<{ messages: Message[] }>) {
|
|
148
|
+
await client.get()?.threads.delete(thread.thread_id);
|
|
149
|
+
await refreshHistoryList();
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
};
|