@langgraph-js/sdk 3.6.0 → 3.7.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.
Files changed (40) hide show
  1. package/README.md +29 -0
  2. package/dist/LangGraphClient.d.ts +13 -1
  3. package/dist/LangGraphClient.js +101 -77
  4. package/dist/MessageProcessor.js +24 -33
  5. package/dist/SpendTime.js +4 -9
  6. package/dist/TestKit.js +16 -15
  7. package/dist/ToolManager.js +4 -7
  8. package/dist/artifacts/index.js +1 -1
  9. package/dist/client/LanggraphServer.js +1 -1
  10. package/dist/client/LowJSServer.d.ts +3 -0
  11. package/dist/client/LowJSServer.js +80 -0
  12. package/dist/client/index.d.ts +2 -0
  13. package/dist/client/index.js +2 -0
  14. package/dist/client/utils/sse.d.ts +8 -0
  15. package/dist/client/utils/sse.js +151 -0
  16. package/dist/client/utils/stream.d.ts +15 -0
  17. package/dist/client/utils/stream.js +104 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +1 -0
  20. package/dist/react/ChatContext.d.ts +3 -0
  21. package/dist/react/ChatContext.js +8 -3
  22. package/dist/tool/ToolUI.js +3 -2
  23. package/dist/tool/createTool.js +3 -6
  24. package/dist/tool/utils.js +3 -4
  25. package/dist/ui-store/createChatStore.js +23 -39
  26. package/dist/vue/ChatContext.d.ts +3 -0
  27. package/dist/vue/ChatContext.js +3 -2
  28. package/package.json +3 -1
  29. package/src/LangGraphClient.ts +73 -45
  30. package/src/MessageProcessor.ts +7 -9
  31. package/src/client/LanggraphServer.ts +1 -2
  32. package/src/client/LowJSServer.ts +80 -0
  33. package/src/client/index.ts +2 -0
  34. package/src/client/utils/sse.ts +176 -0
  35. package/src/client/utils/stream.ts +114 -0
  36. package/src/index.ts +1 -0
  37. package/src/react/ChatContext.ts +20 -15
  38. package/src/vue/ChatContext.ts +5 -0
  39. package/test/TestKit.test.ts +10 -2
  40. package/tsconfig.json +1 -1
@@ -0,0 +1,176 @@
1
+ /** copied from https://github.com/langchain-ai/langgraphjs/tree/main/libs/sdk/src/utils */
2
+ const CR = "\r".charCodeAt(0);
3
+ const LF = "\n".charCodeAt(0);
4
+ const NULL = "\0".charCodeAt(0);
5
+ const COLON = ":".charCodeAt(0);
6
+ const SPACE = " ".charCodeAt(0);
7
+
8
+ const TRAILING_NEWLINE = [CR, LF];
9
+
10
+ export function BytesLineDecoder() {
11
+ let buffer: Uint8Array[] = [];
12
+ let trailingCr = false;
13
+
14
+ return new TransformStream<Uint8Array, Uint8Array>({
15
+ start() {
16
+ buffer = [];
17
+ trailingCr = false;
18
+ },
19
+
20
+ transform(chunk, controller) {
21
+ // See https://docs.python.org/3/glossary.html#term-universal-newlines
22
+ let text = chunk;
23
+
24
+ // Handle trailing CR from previous chunk
25
+ if (trailingCr) {
26
+ text = joinArrays([[CR], text]);
27
+ trailingCr = false;
28
+ }
29
+
30
+ // Check for trailing CR in current chunk
31
+ if (text.length > 0 && text.at(-1) === CR) {
32
+ trailingCr = true;
33
+ text = text.subarray(0, -1);
34
+ }
35
+
36
+ if (!text.length) return;
37
+ const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1)!);
38
+
39
+ const lastIdx = text.length - 1;
40
+ const { lines } = text.reduce<{ lines: Uint8Array[]; from: number }>(
41
+ (acc, cur, idx) => {
42
+ if (acc.from > idx) return acc;
43
+
44
+ if (cur === CR || cur === LF) {
45
+ acc.lines.push(text.subarray(acc.from, idx));
46
+ if (cur === CR && text[idx + 1] === LF) {
47
+ acc.from = idx + 2;
48
+ } else {
49
+ acc.from = idx + 1;
50
+ }
51
+ }
52
+
53
+ if (idx === lastIdx && acc.from <= lastIdx) {
54
+ acc.lines.push(text.subarray(acc.from));
55
+ }
56
+
57
+ return acc;
58
+ },
59
+ { lines: [], from: 0 }
60
+ );
61
+
62
+ if (lines.length === 1 && !trailingNewline) {
63
+ buffer.push(lines[0]);
64
+ return;
65
+ }
66
+
67
+ if (buffer.length) {
68
+ // Include existing buffer in first line
69
+ buffer.push(lines[0]);
70
+ lines[0] = joinArrays(buffer);
71
+ buffer = [];
72
+ }
73
+
74
+ if (!trailingNewline) {
75
+ // If the last segment is not newline terminated,
76
+ // buffer it for the next chunk
77
+ if (lines.length) buffer = [lines.pop()!];
78
+ }
79
+
80
+ // Enqueue complete lines
81
+ for (const line of lines) {
82
+ controller.enqueue(line);
83
+ }
84
+ },
85
+
86
+ flush(controller) {
87
+ if (buffer.length) {
88
+ controller.enqueue(joinArrays(buffer));
89
+ }
90
+ },
91
+ });
92
+ }
93
+
94
+ interface StreamPart {
95
+ id: string | undefined;
96
+ event: string;
97
+ data: unknown;
98
+ }
99
+
100
+ export function SSEDecoder() {
101
+ let event = "";
102
+ let data: Uint8Array[] = [];
103
+ let lastEventId = "";
104
+ let retry: number | null = null;
105
+
106
+ const decoder = new TextDecoder();
107
+
108
+ return new TransformStream<Uint8Array, StreamPart>({
109
+ transform(chunk, controller) {
110
+ // Handle empty line case
111
+ if (!chunk.length) {
112
+ if (!event && !data.length && !lastEventId && retry == null) return;
113
+
114
+ const sse = {
115
+ id: lastEventId || undefined,
116
+ event,
117
+ data: data.length ? decodeArraysToJson(decoder, data) : null,
118
+ };
119
+
120
+ // NOTE: as per the SSE spec, do not reset lastEventId
121
+ event = "";
122
+ data = [];
123
+ retry = null;
124
+
125
+ controller.enqueue(sse);
126
+ return;
127
+ }
128
+
129
+ // Ignore comments
130
+ if (chunk[0] === COLON) return;
131
+
132
+ const sepIdx = chunk.indexOf(COLON);
133
+ if (sepIdx === -1) return;
134
+
135
+ const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
136
+ let value = chunk.subarray(sepIdx + 1);
137
+ if (value[0] === SPACE) value = value.subarray(1);
138
+
139
+ if (fieldName === "event") {
140
+ event = decoder.decode(value);
141
+ } else if (fieldName === "data") {
142
+ data.push(value);
143
+ } else if (fieldName === "id") {
144
+ if (value.indexOf(NULL) === -1) lastEventId = decoder.decode(value);
145
+ } else if (fieldName === "retry") {
146
+ const retryNum = Number.parseInt(decoder.decode(value), 10);
147
+ if (!Number.isNaN(retryNum)) retry = retryNum;
148
+ }
149
+ },
150
+
151
+ flush(controller) {
152
+ if (event) {
153
+ controller.enqueue({
154
+ id: lastEventId || undefined,
155
+ event,
156
+ data: data.length ? decodeArraysToJson(decoder, data) : null,
157
+ });
158
+ }
159
+ },
160
+ });
161
+ }
162
+
163
+ function joinArrays(data: ArrayLike<number>[]) {
164
+ const totalLength = data.reduce((acc, curr) => acc + curr.length, 0);
165
+ const merged = new Uint8Array(totalLength);
166
+ let offset = 0;
167
+ for (const c of data) {
168
+ merged.set(c, offset);
169
+ offset += c.length;
170
+ }
171
+ return merged;
172
+ }
173
+
174
+ function decodeArraysToJson(decoder: TextDecoder, data: ArrayLike<number>[]) {
175
+ return JSON.parse(decoder.decode(joinArrays(data)));
176
+ }
@@ -0,0 +1,114 @@
1
+ /** copied from https://github.com/langchain-ai/langgraphjs/tree/main/libs/sdk/src/utils */
2
+ // in this case don't quite match.
3
+ type IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;
4
+
5
+ /*
6
+ * Support async iterator syntax for ReadableStreams in all environments.
7
+ * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
8
+ */
9
+ export class IterableReadableStream<T> extends ReadableStream<T> implements IterableReadableStreamInterface<T> {
10
+ /** @ts-ignore */
11
+ public reader: ReadableStreamDefaultReader<T>;
12
+
13
+ ensureReader() {
14
+ if (!this.reader) {
15
+ this.reader = this.getReader();
16
+ }
17
+ }
18
+
19
+ async next(): Promise<IteratorResult<T>> {
20
+ this.ensureReader();
21
+ try {
22
+ const result = await this.reader.read();
23
+ if (result.done) {
24
+ this.reader.releaseLock(); // release lock when stream becomes closed
25
+ return {
26
+ done: true,
27
+ value: undefined,
28
+ };
29
+ } else {
30
+ return {
31
+ done: false,
32
+ value: result.value,
33
+ };
34
+ }
35
+ } catch (e) {
36
+ this.reader.releaseLock(); // release lock when stream becomes errored
37
+ throw e;
38
+ }
39
+ }
40
+
41
+ async return(): Promise<IteratorResult<T>> {
42
+ this.ensureReader();
43
+ // If wrapped in a Node stream, cancel is already called.
44
+ if (this.locked) {
45
+ const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
46
+ this.reader.releaseLock(); // release lock first
47
+ await cancelPromise; // now await it
48
+ }
49
+ return { done: true, value: undefined };
50
+ }
51
+
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ async throw(e: any): Promise<IteratorResult<T>> {
54
+ this.ensureReader();
55
+ if (this.locked) {
56
+ const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
57
+ this.reader.releaseLock(); // release lock first
58
+ await cancelPromise; // now await it
59
+ }
60
+ throw e;
61
+ }
62
+
63
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
64
+ // @ts-ignore Not present in Node 18 types, required in latest Node 22
65
+ async [Symbol.asyncDispose]() {
66
+ await this.return();
67
+ }
68
+
69
+ [Symbol.asyncIterator]() {
70
+ return this;
71
+ }
72
+
73
+ static fromReadableStream<T>(stream: ReadableStream<T>) {
74
+ // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream
75
+ const reader = stream.getReader();
76
+ return new IterableReadableStream<T>({
77
+ start(controller) {
78
+ return pump();
79
+ function pump(): Promise<T | undefined> {
80
+ return reader.read().then(({ done, value }) => {
81
+ // When no more data needs to be consumed, close the stream
82
+ if (done) {
83
+ controller.close();
84
+ return;
85
+ }
86
+ // Enqueue the next data chunk into our target stream
87
+ controller.enqueue(value);
88
+ return pump();
89
+ });
90
+ }
91
+ },
92
+ cancel() {
93
+ reader.releaseLock();
94
+ },
95
+ });
96
+ }
97
+
98
+ static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {
99
+ return new IterableReadableStream<T>({
100
+ async pull(controller) {
101
+ const { value, done } = await generator.next();
102
+ // When no more data needs to be consumed, close the stream
103
+ if (done) {
104
+ controller.close();
105
+ }
106
+ // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled
107
+ controller.enqueue(value);
108
+ },
109
+ async cancel(reason) {
110
+ await generator.return(reason);
111
+ },
112
+ });
113
+ }
114
+ }
package/src/index.ts CHANGED
@@ -6,3 +6,4 @@ export * from "./ui-store/index.js";
6
6
  export * from "./ToolManager.js";
7
7
  export * from "./TestKit.js";
8
8
  export * from "./artifacts/index.js";
9
+ export * from "./client/index.js";
@@ -2,6 +2,7 @@ import { createElement, createContext, useContext, useMemo, ReactNode, useEffect
2
2
 
3
3
  import { createChatStore, UnionStore, useUnionStore } from "../ui-store/index.js";
4
4
  import { useStore } from "@nanostores/react";
5
+ import { ILangGraphClient } from "@langgraph-js/pure-graph/dist/types.js";
5
6
 
6
7
  const ChatContext = createContext<UnionStore<ReturnType<typeof createChatStore>> | undefined>(undefined);
7
8
 
@@ -23,6 +24,8 @@ interface ChatProviderProps {
23
24
  showGraph?: boolean;
24
25
  fallbackToAvailableAssistants?: boolean;
25
26
  onInitError?: (error: any, currentAgent: string) => void;
27
+ client?: ILangGraphClient;
28
+ legacyMode?: boolean;
26
29
  }
27
30
 
28
31
  export const ChatProvider: React.FC<ChatProviderProps> = ({
@@ -35,6 +38,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
35
38
  showGraph = false,
36
39
  fallbackToAvailableAssistants = false,
37
40
  onInitError,
41
+ client,
42
+ legacyMode = false,
38
43
  }) => {
39
44
  // 使用 useMemo 稳定 defaultHeaders 的引用
40
45
  const stableHeaders = useMemo(() => defaultHeaders || {}, [defaultHeaders]);
@@ -53,22 +58,22 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
53
58
  }
54
59
  : fetch;
55
60
 
56
- return createChatStore(
57
- defaultAgent,
58
- {
59
- apiUrl,
60
- defaultHeaders: stableHeaders,
61
- callerOptions: {
62
- fetch: F,
63
- maxRetries: 1,
64
- },
61
+ const config = {
62
+ apiUrl,
63
+ defaultHeaders: stableHeaders,
64
+ callerOptions: {
65
+ fetch: F,
66
+ maxRetries: 1,
65
67
  },
66
- {
67
- showHistory,
68
- showGraph,
69
- fallbackToAvailableAssistants,
70
- }
71
- );
68
+ legacyMode,
69
+ };
70
+ /** @ts-ignore */
71
+ if (client) config.client = client;
72
+ return createChatStore(defaultAgent, config, {
73
+ showHistory,
74
+ showGraph,
75
+ fallbackToAvailableAssistants,
76
+ });
72
77
  }, [defaultAgent, apiUrl, stableHeaders, withCredentials, showHistory, showGraph, fallbackToAvailableAssistants]);
73
78
 
74
79
  const unionStore = useUnionStore(store, useStore);
@@ -2,6 +2,7 @@ import { defineComponent, inject, provide, onMounted, defineExpose, type Injecti
2
2
  import { createChatStore } from "../ui-store/index.js";
3
3
  import { useStore } from "@nanostores/vue";
4
4
  import { PreinitializedWritableAtom, StoreValue } from "nanostores";
5
+ import { ILangGraphClient } from "@langgraph-js/pure-graph/dist/types.js";
5
6
 
6
7
  /**
7
8
  * @zh UnionStore 类型用于合并 store 的 data 和 mutations,使其可以直接访问。
@@ -55,6 +56,8 @@ export interface ChatProviderProps {
55
56
  showGraph?: boolean;
56
57
  fallbackToAvailableAssistants?: boolean;
57
58
  onInitError?: (error: any, currentAgent: string) => void;
59
+ client?: ILangGraphClient;
60
+ legacyMode?: boolean;
58
61
  }
59
62
 
60
63
  /**
@@ -78,6 +81,8 @@ export const useChatProvider = (props: ChatProviderProps) => {
78
81
  fetch: F,
79
82
  maxRetries: 1,
80
83
  },
84
+ client: props.client,
85
+ legacyMode: props.legacyMode,
81
86
  },
82
87
  {
83
88
  showHistory: props.showHistory,
@@ -1,14 +1,22 @@
1
1
  import { expect, test } from "vitest";
2
- import { TestLangGraphChat, createChatStore } from "../src";
2
+ import { TestLangGraphChat, createChatStore, createLowerJSClient } from "../src";
3
3
 
4
4
  test("test", async () => {
5
+ const client = await createLowerJSClient({
6
+ apiUrl: "http://localhost:8123",
7
+ defaultHeaders: {
8
+ Authorization: "Bearer 123",
9
+ },
10
+ });
5
11
  const testChat = new TestLangGraphChat(
6
12
  createChatStore(
7
- "agent",
13
+ "graph",
8
14
  {
9
15
  defaultHeaders: {
10
16
  Authorization: "Bearer 123",
11
17
  },
18
+ client,
19
+ legacyMode: true,
12
20
  },
13
21
  {}
14
22
  ),
package/tsconfig.json CHANGED
@@ -11,7 +11,7 @@
11
11
  // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12
12
 
13
13
  /* Language and Environment */
14
- "target": "ES2019" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
14
+ "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15
15
  // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16
16
  // "jsx": "preserve", /* Specify what JSX code is generated. */
17
17
  // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */