@langchain/langgraph-sdk 1.5.2 → 1.5.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.
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/react/stream.lgp.cjs +2 -1
- package/dist/react/stream.lgp.cjs.map +1 -1
- package/dist/react/stream.lgp.js +3 -2
- package/dist/react/stream.lgp.js.map +1 -1
- package/dist/react/types.d.cts +6 -0
- package/dist/react/types.d.cts.map +1 -1
- package/dist/react/types.d.ts +6 -0
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react-ui/client.cjs +2 -2
- package/dist/react-ui/client.cjs.map +1 -1
- package/dist/singletons/fetch.cjs +2 -2
- package/dist/singletons/fetch.cjs.map +1 -1
- package/dist/singletons/fetch.js +2 -2
- package/dist/singletons/fetch.js.map +1 -1
- package/dist/types.stream.d.ts.map +1 -1
- package/dist/ui/manager.cjs +6 -5
- package/dist/ui/manager.cjs.map +1 -1
- package/dist/ui/manager.js +6 -5
- package/dist/ui/manager.js.map +1 -1
- package/dist/ui/utils.cjs +8 -0
- package/dist/ui/utils.cjs.map +1 -1
- package/dist/ui/utils.js +8 -1
- package/dist/ui/utils.js.map +1 -1
- package/dist/utils/error.cjs +8 -1
- package/dist/utils/error.cjs.map +1 -1
- package/dist/utils/error.js +8 -1
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/stream.cjs +4 -4
- package/dist/utils/stream.cjs.map +1 -1
- package/dist/utils/stream.js +4 -4
- package/dist/utils/stream.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/manager.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.cjs","names":["StreamError","values","toMessageDict"],"sources":["../../src/ui/manager.ts"],"sourcesContent":["import type {\n CheckpointsStreamEvent,\n CustomStreamEvent,\n DebugStreamEvent,\n ErrorStreamEvent,\n EventsStreamEvent,\n FeedbackStreamEvent,\n MessagesTupleStreamEvent,\n MetadataStreamEvent,\n TasksStreamEvent,\n UpdatesStreamEvent,\n ValuesStreamEvent,\n} from \"../types.stream.js\";\nimport { MessageTupleManager, toMessageDict } from \"./messages.js\";\nimport { StreamError } from \"./errors.js\";\nimport type { Message } from \"../types.messages.js\";\nimport type { BagTemplate } from \"../types.template.js\";\n\n/**\n * Special ID used by LangGraph's messagesStateReducer to signal\n * that all messages should be removed from the state.\n */\nexport const REMOVE_ALL_MESSAGES = \"__remove_all__\";\n\ntype GetUpdateType<\n Bag extends BagTemplate,\n StateType extends Record<string, unknown>\n> = Bag extends { UpdateType: unknown }\n ? Bag[\"UpdateType\"]\n : Partial<StateType>;\n\ntype GetCustomEventType<Bag extends BagTemplate> = Bag extends {\n CustomEventType: unknown;\n}\n ? Bag[\"CustomEventType\"]\n : unknown;\n\ntype EventStreamMap<StateType, UpdateType, CustomType> = {\n values: ValuesStreamEvent<StateType>;\n updates: UpdatesStreamEvent<UpdateType>;\n custom: CustomStreamEvent<CustomType>;\n debug: DebugStreamEvent;\n messages: MessagesTupleStreamEvent;\n events: EventsStreamEvent;\n metadata: MetadataStreamEvent;\n checkpoints: CheckpointsStreamEvent<StateType>;\n tasks: TasksStreamEvent<StateType, UpdateType>;\n error: ErrorStreamEvent;\n feedback: FeedbackStreamEvent;\n};\n\nexport type EventStreamEvent<StateType, UpdateType, CustomType> =\n EventStreamMap<StateType, UpdateType, CustomType>[keyof EventStreamMap<\n StateType,\n UpdateType,\n CustomType\n >];\n\ninterface StreamManagerEventCallbacks<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n onUpdateEvent?: (\n data: UpdatesStreamEvent<GetUpdateType<Bag, StateType>>[\"data\"],\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onCustomEvent?: (\n data: GetCustomEventType<Bag>,\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onMetadataEvent?: (data: MetadataStreamEvent[\"data\"]) => void;\n onLangChainEvent?: (data: EventsStreamEvent[\"data\"]) => void;\n onDebugEvent?: (\n data: DebugStreamEvent[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onCheckpointEvent?: (\n data: CheckpointsStreamEvent<StateType>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onTaskEvent?: (\n data: TasksStreamEvent<StateType, GetUpdateType<Bag, StateType>>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n}\n\nexport class StreamManager<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n private abortRef = new AbortController();\n\n private messages: MessageTupleManager;\n\n private listeners = new Set<() => void>();\n\n private throttle: number | boolean;\n\n private queue: Promise<unknown> = Promise.resolve();\n\n private queueSize: number = 0;\n\n private state: {\n isLoading: boolean;\n values: [values: StateType, kind: \"stream\" | \"stop\"] | null;\n error: unknown;\n };\n\n constructor(\n messages: MessageTupleManager,\n options: { throttle: number | boolean }\n ) {\n this.messages = messages;\n this.state = { isLoading: false, values: null, error: undefined };\n this.throttle = options.throttle;\n }\n\n private setState = (newState: Partial<typeof this.state>) => {\n this.state = { ...this.state, ...newState };\n this.notifyListeners();\n };\n\n private notifyListeners = () => {\n this.listeners.forEach((listener) => listener());\n };\n\n subscribe = (listener: () => void): (() => void) => {\n if (this.throttle === false) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n const timeoutMs = this.throttle === true ? 0 : this.throttle;\n let timeoutId: NodeJS.Timeout | number | undefined;\n\n const throttledListener = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n clearTimeout(timeoutId);\n listener();\n }, timeoutMs);\n };\n\n this.listeners.add(throttledListener);\n return () => {\n clearTimeout(timeoutId);\n this.listeners.delete(throttledListener);\n };\n };\n\n getSnapshot = () => this.state;\n\n get isLoading() {\n return this.state.isLoading;\n }\n\n get values() {\n return this.state.values?.[0] ?? null;\n }\n\n get error() {\n return this.state.error;\n }\n\n setStreamValues = (\n values:\n | (StateType | null)\n | ((prev: StateType | null, kind: \"stream\" | \"stop\") => StateType | null),\n kind: \"stream\" | \"stop\" = \"stream\"\n ) => {\n if (typeof values === \"function\") {\n const [prevValues, prevKind] = this.state.values ?? [null, \"stream\"];\n const nextValues = values(prevValues, prevKind);\n this.setState({ values: nextValues != null ? [nextValues, kind] : null });\n } else {\n const nextValues = values != null ? [values, kind] : null;\n this.setState({ values: nextValues as [StateType, \"stream\" | \"stop\"] });\n }\n };\n\n private getMutateFn = (kind: \"stream\" | \"stop\", historyValues: StateType) => {\n return (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => {\n const prev = {\n ...historyValues,\n ...(this.state.values ?? [null, \"stream\"])[0],\n };\n const next = typeof update === \"function\" ? update(prev) : update;\n this.setStreamValues({ ...prev, ...next }, kind);\n };\n };\n\n private matchEventType = <\n T extends keyof EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >(\n expected: T,\n actual: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"event\"],\n _data: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"data\"]\n ): _data is EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[T][\"data\"] => {\n return expected === actual || actual.startsWith(`${expected}|`);\n };\n\n protected enqueue = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ) => {\n try {\n this.queueSize = Math.max(0, this.queueSize - 1);\n this.setState({ isLoading: true, error: undefined });\n this.abortRef = new AbortController();\n\n const run = await action(this.abortRef.signal);\n\n let streamError: StreamError | undefined;\n for await (const { event, data } of run) {\n if (event === \"error\") {\n streamError = new StreamError(data);\n break;\n }\n\n const namespace = event.includes(\"|\")\n ? event.split(\"|\").slice(1)\n : undefined;\n\n const mutate = this.getMutateFn(\"stream\", options.initialValues);\n\n if (event === \"metadata\") options.callbacks.onMetadataEvent?.(data);\n if (event === \"events\") options.callbacks.onLangChainEvent?.(data);\n\n if (this.matchEventType(\"updates\", event, data)) {\n options.callbacks.onUpdateEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"custom\", event, data)) {\n options.callbacks.onCustomEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"checkpoints\", event, data)) {\n options.callbacks.onCheckpointEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"tasks\", event, data)) {\n options.callbacks.onTaskEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"debug\", event, data)) {\n options.callbacks.onDebugEvent?.(data, { namespace });\n }\n\n if (event === \"values\") {\n if (\"__interrupt__\" in data) {\n this.setStreamValues((prev) => ({ ...prev, ...data }));\n } else {\n this.setStreamValues(data);\n }\n }\n\n if (this.matchEventType(\"messages\", event, data)) {\n const [serialized, metadata] = data;\n\n const messageId = this.messages.add(serialized, metadata);\n if (!messageId) {\n console.warn(\n \"Failed to add message to manager, no message ID found\"\n );\n continue;\n }\n\n this.setStreamValues((streamValues) => {\n const values = { ...options.initialValues, ...streamValues };\n\n // Assumption: we're concatenating the message\n let messages = options.getMessages(values).slice();\n const { chunk, index } =\n this.messages.get(messageId, messages.length) ?? {};\n\n if (!chunk || index == null) return values;\n if (chunk.getType() === \"remove\") {\n // Check for special REMOVE_ALL_MESSAGES sentinel\n if (chunk.id === REMOVE_ALL_MESSAGES) {\n // Clear all messages when __remove_all__ is received\n messages = [];\n } else {\n messages.splice(index, 1);\n }\n } else {\n messages[index] = toMessageDict(chunk);\n }\n\n return options.setMessages(values, messages);\n });\n }\n }\n\n if (streamError != null) throw streamError;\n\n const values = await options.onSuccess?.();\n if (typeof values !== \"undefined\" && this.queueSize === 0) {\n this.setStreamValues(values);\n }\n } catch (error) {\n if (\n !(\n error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n )\n ) {\n console.error(error);\n this.setState({ error });\n await options.onError?.(error);\n }\n } finally {\n this.setState({ isLoading: false });\n this.abortRef = new AbortController();\n options.onFinish?.();\n }\n };\n\n start = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ): Promise<void> => {\n this.queueSize += 1;\n this.queue = this.queue.then(() => this.enqueue(action, options));\n };\n\n stop = async (\n historyValues: StateType,\n options: {\n onStop?: (options: {\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }) => void;\n }\n ): Promise<void> => {\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n options.onStop?.({ mutate: this.getMutateFn(\"stop\", historyValues) });\n };\n\n clear = () => {\n // Cancel any running streams\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n // Set the stream state to null\n this.setState({ error: undefined, values: null, isLoading: false });\n\n // Clear any pending messages\n this.messages.clear();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manager.cjs","names":["StreamError","values","toMessageDict"],"sources":["../../src/ui/manager.ts"],"sourcesContent":["import type {\n CheckpointsStreamEvent,\n CustomStreamEvent,\n DebugStreamEvent,\n ErrorStreamEvent,\n EventsStreamEvent,\n FeedbackStreamEvent,\n MessagesTupleStreamEvent,\n MetadataStreamEvent,\n TasksStreamEvent,\n UpdatesStreamEvent,\n ValuesStreamEvent,\n} from \"../types.stream.js\";\nimport { MessageTupleManager, toMessageDict } from \"./messages.js\";\nimport { StreamError } from \"./errors.js\";\nimport type { Message } from \"../types.messages.js\";\nimport type { BagTemplate } from \"../types.template.js\";\n\n/**\n * Special ID used by LangGraph's messagesStateReducer to signal\n * that all messages should be removed from the state.\n */\nexport const REMOVE_ALL_MESSAGES = \"__remove_all__\";\n\ntype GetUpdateType<\n Bag extends BagTemplate,\n StateType extends Record<string, unknown>\n> = Bag extends { UpdateType: unknown }\n ? Bag[\"UpdateType\"]\n : Partial<StateType>;\n\ntype GetCustomEventType<Bag extends BagTemplate> = Bag extends {\n CustomEventType: unknown;\n}\n ? Bag[\"CustomEventType\"]\n : unknown;\n\ntype EventStreamMap<StateType, UpdateType, CustomType> = {\n values: ValuesStreamEvent<StateType>;\n updates: UpdatesStreamEvent<UpdateType>;\n custom: CustomStreamEvent<CustomType>;\n debug: DebugStreamEvent;\n messages: MessagesTupleStreamEvent;\n events: EventsStreamEvent;\n metadata: MetadataStreamEvent;\n checkpoints: CheckpointsStreamEvent<StateType>;\n tasks: TasksStreamEvent<StateType, UpdateType>;\n error: ErrorStreamEvent;\n feedback: FeedbackStreamEvent;\n};\n\nexport type EventStreamEvent<StateType, UpdateType, CustomType> =\n EventStreamMap<StateType, UpdateType, CustomType>[keyof EventStreamMap<\n StateType,\n UpdateType,\n CustomType\n >];\n\ninterface StreamManagerEventCallbacks<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n onUpdateEvent?: (\n data: UpdatesStreamEvent<GetUpdateType<Bag, StateType>>[\"data\"],\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onCustomEvent?: (\n data: GetCustomEventType<Bag>,\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onMetadataEvent?: (data: MetadataStreamEvent[\"data\"]) => void;\n onLangChainEvent?: (data: EventsStreamEvent[\"data\"]) => void;\n onDebugEvent?: (\n data: DebugStreamEvent[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onCheckpointEvent?: (\n data: CheckpointsStreamEvent<StateType>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onTaskEvent?: (\n data: TasksStreamEvent<StateType, GetUpdateType<Bag, StateType>>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n}\n\nexport class StreamManager<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n private abortRef = new AbortController();\n\n private messages: MessageTupleManager;\n\n private listeners = new Set<() => void>();\n\n private throttle: number | boolean;\n\n private queue: Promise<unknown> = Promise.resolve();\n\n private queueSize: number = 0;\n\n private state: {\n isLoading: boolean;\n values: [values: StateType, kind: \"stream\" | \"stop\"] | null;\n error: unknown;\n };\n\n constructor(\n messages: MessageTupleManager,\n options: { throttle: number | boolean }\n ) {\n this.messages = messages;\n this.state = { isLoading: false, values: null, error: undefined };\n this.throttle = options.throttle;\n }\n\n private setState = (newState: Partial<typeof this.state>) => {\n this.state = { ...this.state, ...newState };\n this.notifyListeners();\n };\n\n private notifyListeners = () => {\n this.listeners.forEach((listener) => listener());\n };\n\n subscribe = (listener: () => void): (() => void) => {\n if (this.throttle === false) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n const timeoutMs = this.throttle === true ? 0 : this.throttle;\n let timeoutId: NodeJS.Timeout | number | undefined;\n\n const throttledListener = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n clearTimeout(timeoutId);\n listener();\n }, timeoutMs);\n };\n\n this.listeners.add(throttledListener);\n return () => {\n clearTimeout(timeoutId);\n this.listeners.delete(throttledListener);\n };\n };\n\n getSnapshot = () => this.state;\n\n get isLoading() {\n return this.state.isLoading;\n }\n\n get values() {\n return this.state.values?.[0] ?? null;\n }\n\n get error() {\n return this.state.error;\n }\n\n setStreamValues = (\n values:\n | (StateType | null)\n | ((prev: StateType | null, kind: \"stream\" | \"stop\") => StateType | null),\n kind: \"stream\" | \"stop\" = \"stream\"\n ) => {\n if (typeof values === \"function\") {\n const [prevValues, prevKind] = this.state.values ?? [null, \"stream\"];\n const nextValues = values(prevValues, prevKind);\n this.setState({ values: nextValues != null ? [nextValues, kind] : null });\n } else {\n const nextValues = values != null ? [values, kind] : null;\n this.setState({ values: nextValues as [StateType, \"stream\" | \"stop\"] });\n }\n };\n\n private getMutateFn = (kind: \"stream\" | \"stop\", historyValues: StateType) => {\n return (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => {\n const stateValues = (this.state.values ?? [null, \"stream\"])[0];\n const prev = {\n ...historyValues,\n ...(stateValues ?? {}),\n };\n const next = typeof update === \"function\" ? update(prev) : update;\n this.setStreamValues({ ...prev, ...(next ?? {}) }, kind);\n };\n };\n\n private matchEventType = <\n T extends keyof EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >(\n expected: T,\n actual: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"event\"],\n _data: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"data\"]\n ): _data is EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[T][\"data\"] => {\n return expected === actual || actual.startsWith(`${expected}|`);\n };\n\n protected enqueue = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ) => {\n try {\n this.queueSize = Math.max(0, this.queueSize - 1);\n this.setState({ isLoading: true, error: undefined });\n this.abortRef = new AbortController();\n\n const run = await action(this.abortRef.signal);\n\n let streamError: StreamError | undefined;\n for await (const { event, data } of run) {\n if (event === \"error\") {\n streamError = new StreamError(data);\n break;\n }\n\n const namespace = event.includes(\"|\")\n ? event.split(\"|\").slice(1)\n : undefined;\n\n const mutate = this.getMutateFn(\"stream\", options.initialValues);\n\n if (event === \"metadata\") options.callbacks.onMetadataEvent?.(data);\n if (event === \"events\") options.callbacks.onLangChainEvent?.(data);\n\n if (this.matchEventType(\"updates\", event, data)) {\n options.callbacks.onUpdateEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"custom\", event, data)) {\n options.callbacks.onCustomEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"checkpoints\", event, data)) {\n options.callbacks.onCheckpointEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"tasks\", event, data)) {\n options.callbacks.onTaskEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"debug\", event, data)) {\n options.callbacks.onDebugEvent?.(data, { namespace });\n }\n\n if (event === \"values\") {\n if (\n data != null &&\n typeof data === \"object\" &&\n \"__interrupt__\" in data\n ) {\n this.setStreamValues((prev) => ({ ...(prev ?? {}), ...data }));\n } else {\n this.setStreamValues(data);\n }\n }\n\n if (this.matchEventType(\"messages\", event, data)) {\n const [serialized, metadata] = data;\n\n const messageId = this.messages.add(serialized, metadata);\n if (!messageId) {\n console.warn(\n \"Failed to add message to manager, no message ID found\"\n );\n continue;\n }\n\n this.setStreamValues((streamValues) => {\n const values = {\n ...options.initialValues,\n ...(streamValues ?? {}),\n };\n\n // Assumption: we're concatenating the message\n let messages = options.getMessages(values).slice();\n const { chunk, index } =\n this.messages.get(messageId, messages.length) ?? {};\n\n if (!chunk || index == null) return values;\n if (chunk.getType() === \"remove\") {\n // Check for special REMOVE_ALL_MESSAGES sentinel\n if (chunk.id === REMOVE_ALL_MESSAGES) {\n // Clear all messages when __remove_all__ is received\n messages = [];\n } else {\n messages.splice(index, 1);\n }\n } else {\n messages[index] = toMessageDict(chunk);\n }\n\n return options.setMessages(values, messages);\n });\n }\n }\n\n if (streamError != null) throw streamError;\n\n const values = await options.onSuccess?.();\n if (typeof values !== \"undefined\" && this.queueSize === 0) {\n this.setStreamValues(values);\n }\n } catch (error) {\n if (\n !(\n error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n )\n ) {\n console.error(error);\n this.setState({ error });\n await options.onError?.(error);\n }\n } finally {\n this.setState({ isLoading: false });\n this.abortRef = new AbortController();\n options.onFinish?.();\n }\n };\n\n start = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ): Promise<void> => {\n this.queueSize += 1;\n this.queue = this.queue.then(() => this.enqueue(action, options));\n };\n\n stop = async (\n historyValues: StateType,\n options: {\n onStop?: (options: {\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }) => void;\n }\n ): Promise<void> => {\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n options.onStop?.({ mutate: this.getMutateFn(\"stop\", historyValues) });\n };\n\n clear = () => {\n // Cancel any running streams\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n // Set the stream state to null\n this.setState({ error: undefined, values: null, isLoading: false });\n\n // Clear any pending messages\n this.messages.clear();\n };\n}\n"],"mappings":";;;;;;;;AAsBA,MAAa,sBAAsB;AA0EnC,IAAa,gBAAb,MAGE;CACA,AAAQ,WAAW,IAAI,iBAAiB;CAExC,AAAQ;CAER,AAAQ,4BAAY,IAAI,KAAiB;CAEzC,AAAQ;CAER,AAAQ,QAA0B,QAAQ,SAAS;CAEnD,AAAQ,YAAoB;CAE5B,AAAQ;CAMR,YACE,UACA,SACA;AACA,OAAK,WAAW;AAChB,OAAK,QAAQ;GAAE,WAAW;GAAO,QAAQ;GAAM,OAAO;GAAW;AACjE,OAAK,WAAW,QAAQ;;CAG1B,AAAQ,YAAY,aAAyC;AAC3D,OAAK,QAAQ;GAAE,GAAG,KAAK;GAAO,GAAG;GAAU;AAC3C,OAAK,iBAAiB;;CAGxB,AAAQ,wBAAwB;AAC9B,OAAK,UAAU,SAAS,aAAa,UAAU,CAAC;;CAGlD,aAAa,aAAuC;AAClD,MAAI,KAAK,aAAa,OAAO;AAC3B,QAAK,UAAU,IAAI,SAAS;AAC5B,gBAAa,KAAK,UAAU,OAAO,SAAS;;EAG9C,MAAM,YAAY,KAAK,aAAa,OAAO,IAAI,KAAK;EACpD,IAAI;EAEJ,MAAM,0BAA0B;AAC9B,gBAAa,UAAU;AACvB,eAAY,iBAAiB;AAC3B,iBAAa,UAAU;AACvB,cAAU;MACT,UAAU;;AAGf,OAAK,UAAU,IAAI,kBAAkB;AACrC,eAAa;AACX,gBAAa,UAAU;AACvB,QAAK,UAAU,OAAO,kBAAkB;;;CAI5C,oBAAoB,KAAK;CAEzB,IAAI,YAAY;AACd,SAAO,KAAK,MAAM;;CAGpB,IAAI,SAAS;AACX,SAAO,KAAK,MAAM,SAAS,MAAM;;CAGnC,IAAI,QAAQ;AACV,SAAO,KAAK,MAAM;;CAGpB,mBACE,QAGA,OAA0B,aACvB;AACH,MAAI,OAAO,WAAW,YAAY;GAChC,MAAM,CAAC,YAAY,YAAY,KAAK,MAAM,UAAU,CAAC,MAAM,SAAS;GACpE,MAAM,aAAa,OAAO,YAAY,SAAS;AAC/C,QAAK,SAAS,EAAE,QAAQ,cAAc,OAAO,CAAC,YAAY,KAAK,GAAG,MAAM,CAAC;SACpE;GACL,MAAM,aAAa,UAAU,OAAO,CAAC,QAAQ,KAAK,GAAG;AACrD,QAAK,SAAS,EAAE,QAAQ,YAA8C,CAAC;;;CAI3E,AAAQ,eAAe,MAAyB,kBAA6B;AAC3E,UACE,WACG;GACH,MAAM,eAAe,KAAK,MAAM,UAAU,CAAC,MAAM,SAAS,EAAE;GAC5D,MAAM,OAAO;IACX,GAAG;IACH,GAAI,eAAe,EAAE;IACtB;GACD,MAAM,OAAO,OAAO,WAAW,aAAa,OAAO,KAAK,GAAG;AAC3D,QAAK,gBAAgB;IAAE,GAAG;IAAM,GAAI,QAAQ,EAAE;IAAG,EAAE,KAAK;;;CAI5D,AAAQ,kBAON,UACA,QAKA,UASc;AACd,SAAO,aAAa,UAAU,OAAO,WAAW,GAAG,SAAS,GAAG;;CAGjE,AAAU,UAAU,OAClB,QAWA,YAoBG;AACH,MAAI;AACF,QAAK,YAAY,KAAK,IAAI,GAAG,KAAK,YAAY,EAAE;AAChD,QAAK,SAAS;IAAE,WAAW;IAAM,OAAO;IAAW,CAAC;AACpD,QAAK,WAAW,IAAI,iBAAiB;GAErC,MAAM,MAAM,MAAM,OAAO,KAAK,SAAS,OAAO;GAE9C,IAAI;AACJ,cAAW,MAAM,EAAE,OAAO,UAAU,KAAK;AACvC,QAAI,UAAU,SAAS;AACrB,mBAAc,IAAIA,2BAAY,KAAK;AACnC;;IAGF,MAAM,YAAY,MAAM,SAAS,IAAI,GACjC,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,GACzB;IAEJ,MAAM,SAAS,KAAK,YAAY,UAAU,QAAQ,cAAc;AAEhE,QAAI,UAAU,WAAY,SAAQ,UAAU,kBAAkB,KAAK;AACnE,QAAI,UAAU,SAAU,SAAQ,UAAU,mBAAmB,KAAK;AAElE,QAAI,KAAK,eAAe,WAAW,OAAO,KAAK,CAC7C,SAAQ,UAAU,gBAAgB,MAAM;KAAE;KAAW;KAAQ,CAAC;AAGhE,QAAI,KAAK,eAAe,UAAU,OAAO,KAAK,CAC5C,SAAQ,UAAU,gBAAgB,MAAM;KAAE;KAAW;KAAQ,CAAC;AAGhE,QAAI,KAAK,eAAe,eAAe,OAAO,KAAK,CACjD,SAAQ,UAAU,oBAAoB,MAAM,EAAE,WAAW,CAAC;AAG5D,QAAI,KAAK,eAAe,SAAS,OAAO,KAAK,CAC3C,SAAQ,UAAU,cAAc,MAAM,EAAE,WAAW,CAAC;AAGtD,QAAI,KAAK,eAAe,SAAS,OAAO,KAAK,CAC3C,SAAQ,UAAU,eAAe,MAAM,EAAE,WAAW,CAAC;AAGvD,QAAI,UAAU,SACZ,KACE,QAAQ,QACR,OAAO,SAAS,YAChB,mBAAmB,KAEnB,MAAK,iBAAiB,UAAU;KAAE,GAAI,QAAQ,EAAE;KAAG,GAAG;KAAM,EAAE;QAE9D,MAAK,gBAAgB,KAAK;AAI9B,QAAI,KAAK,eAAe,YAAY,OAAO,KAAK,EAAE;KAChD,MAAM,CAAC,YAAY,YAAY;KAE/B,MAAM,YAAY,KAAK,SAAS,IAAI,YAAY,SAAS;AACzD,SAAI,CAAC,WAAW;AACd,cAAQ,KACN,wDACD;AACD;;AAGF,UAAK,iBAAiB,iBAAiB;MACrC,MAAMC,WAAS;OACb,GAAG,QAAQ;OACX,GAAI,gBAAgB,EAAE;OACvB;MAGD,IAAI,WAAW,QAAQ,YAAYA,SAAO,CAAC,OAAO;MAClD,MAAM,EAAE,OAAO,UACb,KAAK,SAAS,IAAI,WAAW,SAAS,OAAO,IAAI,EAAE;AAErD,UAAI,CAAC,SAAS,SAAS,KAAM,QAAOA;AACpC,UAAI,MAAM,SAAS,KAAK,SAEtB,KAAI,MAAM,OAAO,oBAEf,YAAW,EAAE;UAEb,UAAS,OAAO,OAAO,EAAE;UAG3B,UAAS,SAASC,+BAAc,MAAM;AAGxC,aAAO,QAAQ,YAAYD,UAAQ,SAAS;OAC5C;;;AAIN,OAAI,eAAe,KAAM,OAAM;GAE/B,MAAM,SAAS,MAAM,QAAQ,aAAa;AAC1C,OAAI,OAAO,WAAW,eAAe,KAAK,cAAc,EACtD,MAAK,gBAAgB,OAAO;WAEvB,OAAO;AACd,OACE,EACE,iBAAiB,UAChB,MAAM,SAAS,gBAAgB,MAAM,SAAS,kBAEjD;AACA,YAAQ,MAAM,MAAM;AACpB,SAAK,SAAS,EAAE,OAAO,CAAC;AACxB,UAAM,QAAQ,UAAU,MAAM;;YAExB;AACR,QAAK,SAAS,EAAE,WAAW,OAAO,CAAC;AACnC,QAAK,WAAW,IAAI,iBAAiB;AACrC,WAAQ,YAAY;;;CAIxB,QAAQ,OACN,QAWA,YAoBkB;AAClB,OAAK,aAAa;AAClB,OAAK,QAAQ,KAAK,MAAM,WAAW,KAAK,QAAQ,QAAQ,QAAQ,CAAC;;CAGnE,OAAO,OACL,eACA,YAOkB;AAClB,OAAK,SAAS,OAAO;AACrB,OAAK,WAAW,IAAI,iBAAiB;AAErC,UAAQ,SAAS,EAAE,QAAQ,KAAK,YAAY,QAAQ,cAAc,EAAE,CAAC;;CAGvE,cAAc;AAEZ,OAAK,SAAS,OAAO;AACrB,OAAK,WAAW,IAAI,iBAAiB;AAGrC,OAAK,SAAS;GAAE,OAAO;GAAW,QAAQ;GAAM,WAAW;GAAO,CAAC;AAGnE,OAAK,SAAS,OAAO"}
|
package/dist/ui/manager.js
CHANGED
|
@@ -76,14 +76,15 @@ var StreamManager = class {
|
|
|
76
76
|
};
|
|
77
77
|
getMutateFn = (kind, historyValues) => {
|
|
78
78
|
return (update) => {
|
|
79
|
+
const stateValues = (this.state.values ?? [null, "stream"])[0];
|
|
79
80
|
const prev = {
|
|
80
81
|
...historyValues,
|
|
81
|
-
...
|
|
82
|
+
...stateValues ?? {}
|
|
82
83
|
};
|
|
83
84
|
const next = typeof update === "function" ? update(prev) : update;
|
|
84
85
|
this.setStreamValues({
|
|
85
86
|
...prev,
|
|
86
|
-
...next
|
|
87
|
+
...next ?? {}
|
|
87
88
|
}, kind);
|
|
88
89
|
};
|
|
89
90
|
};
|
|
@@ -120,8 +121,8 @@ var StreamManager = class {
|
|
|
120
121
|
if (this.matchEventType("checkpoints", event, data)) options.callbacks.onCheckpointEvent?.(data, { namespace });
|
|
121
122
|
if (this.matchEventType("tasks", event, data)) options.callbacks.onTaskEvent?.(data, { namespace });
|
|
122
123
|
if (this.matchEventType("debug", event, data)) options.callbacks.onDebugEvent?.(data, { namespace });
|
|
123
|
-
if (event === "values") if ("__interrupt__" in data) this.setStreamValues((prev) => ({
|
|
124
|
-
...prev,
|
|
124
|
+
if (event === "values") if (data != null && typeof data === "object" && "__interrupt__" in data) this.setStreamValues((prev) => ({
|
|
125
|
+
...prev ?? {},
|
|
125
126
|
...data
|
|
126
127
|
}));
|
|
127
128
|
else this.setStreamValues(data);
|
|
@@ -135,7 +136,7 @@ var StreamManager = class {
|
|
|
135
136
|
this.setStreamValues((streamValues) => {
|
|
136
137
|
const values$1 = {
|
|
137
138
|
...options.initialValues,
|
|
138
|
-
...streamValues
|
|
139
|
+
...streamValues ?? {}
|
|
139
140
|
};
|
|
140
141
|
let messages = options.getMessages(values$1).slice();
|
|
141
142
|
const { chunk, index } = this.messages.get(messageId, messages.length) ?? {};
|
package/dist/ui/manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":["values"],"sources":["../../src/ui/manager.ts"],"sourcesContent":["import type {\n CheckpointsStreamEvent,\n CustomStreamEvent,\n DebugStreamEvent,\n ErrorStreamEvent,\n EventsStreamEvent,\n FeedbackStreamEvent,\n MessagesTupleStreamEvent,\n MetadataStreamEvent,\n TasksStreamEvent,\n UpdatesStreamEvent,\n ValuesStreamEvent,\n} from \"../types.stream.js\";\nimport { MessageTupleManager, toMessageDict } from \"./messages.js\";\nimport { StreamError } from \"./errors.js\";\nimport type { Message } from \"../types.messages.js\";\nimport type { BagTemplate } from \"../types.template.js\";\n\n/**\n * Special ID used by LangGraph's messagesStateReducer to signal\n * that all messages should be removed from the state.\n */\nexport const REMOVE_ALL_MESSAGES = \"__remove_all__\";\n\ntype GetUpdateType<\n Bag extends BagTemplate,\n StateType extends Record<string, unknown>\n> = Bag extends { UpdateType: unknown }\n ? Bag[\"UpdateType\"]\n : Partial<StateType>;\n\ntype GetCustomEventType<Bag extends BagTemplate> = Bag extends {\n CustomEventType: unknown;\n}\n ? Bag[\"CustomEventType\"]\n : unknown;\n\ntype EventStreamMap<StateType, UpdateType, CustomType> = {\n values: ValuesStreamEvent<StateType>;\n updates: UpdatesStreamEvent<UpdateType>;\n custom: CustomStreamEvent<CustomType>;\n debug: DebugStreamEvent;\n messages: MessagesTupleStreamEvent;\n events: EventsStreamEvent;\n metadata: MetadataStreamEvent;\n checkpoints: CheckpointsStreamEvent<StateType>;\n tasks: TasksStreamEvent<StateType, UpdateType>;\n error: ErrorStreamEvent;\n feedback: FeedbackStreamEvent;\n};\n\nexport type EventStreamEvent<StateType, UpdateType, CustomType> =\n EventStreamMap<StateType, UpdateType, CustomType>[keyof EventStreamMap<\n StateType,\n UpdateType,\n CustomType\n >];\n\ninterface StreamManagerEventCallbacks<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n onUpdateEvent?: (\n data: UpdatesStreamEvent<GetUpdateType<Bag, StateType>>[\"data\"],\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onCustomEvent?: (\n data: GetCustomEventType<Bag>,\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onMetadataEvent?: (data: MetadataStreamEvent[\"data\"]) => void;\n onLangChainEvent?: (data: EventsStreamEvent[\"data\"]) => void;\n onDebugEvent?: (\n data: DebugStreamEvent[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onCheckpointEvent?: (\n data: CheckpointsStreamEvent<StateType>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onTaskEvent?: (\n data: TasksStreamEvent<StateType, GetUpdateType<Bag, StateType>>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n}\n\nexport class StreamManager<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n private abortRef = new AbortController();\n\n private messages: MessageTupleManager;\n\n private listeners = new Set<() => void>();\n\n private throttle: number | boolean;\n\n private queue: Promise<unknown> = Promise.resolve();\n\n private queueSize: number = 0;\n\n private state: {\n isLoading: boolean;\n values: [values: StateType, kind: \"stream\" | \"stop\"] | null;\n error: unknown;\n };\n\n constructor(\n messages: MessageTupleManager,\n options: { throttle: number | boolean }\n ) {\n this.messages = messages;\n this.state = { isLoading: false, values: null, error: undefined };\n this.throttle = options.throttle;\n }\n\n private setState = (newState: Partial<typeof this.state>) => {\n this.state = { ...this.state, ...newState };\n this.notifyListeners();\n };\n\n private notifyListeners = () => {\n this.listeners.forEach((listener) => listener());\n };\n\n subscribe = (listener: () => void): (() => void) => {\n if (this.throttle === false) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n const timeoutMs = this.throttle === true ? 0 : this.throttle;\n let timeoutId: NodeJS.Timeout | number | undefined;\n\n const throttledListener = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n clearTimeout(timeoutId);\n listener();\n }, timeoutMs);\n };\n\n this.listeners.add(throttledListener);\n return () => {\n clearTimeout(timeoutId);\n this.listeners.delete(throttledListener);\n };\n };\n\n getSnapshot = () => this.state;\n\n get isLoading() {\n return this.state.isLoading;\n }\n\n get values() {\n return this.state.values?.[0] ?? null;\n }\n\n get error() {\n return this.state.error;\n }\n\n setStreamValues = (\n values:\n | (StateType | null)\n | ((prev: StateType | null, kind: \"stream\" | \"stop\") => StateType | null),\n kind: \"stream\" | \"stop\" = \"stream\"\n ) => {\n if (typeof values === \"function\") {\n const [prevValues, prevKind] = this.state.values ?? [null, \"stream\"];\n const nextValues = values(prevValues, prevKind);\n this.setState({ values: nextValues != null ? [nextValues, kind] : null });\n } else {\n const nextValues = values != null ? [values, kind] : null;\n this.setState({ values: nextValues as [StateType, \"stream\" | \"stop\"] });\n }\n };\n\n private getMutateFn = (kind: \"stream\" | \"stop\", historyValues: StateType) => {\n return (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => {\n const prev = {\n ...historyValues,\n ...(this.state.values ?? [null, \"stream\"])[0],\n };\n const next = typeof update === \"function\" ? update(prev) : update;\n this.setStreamValues({ ...prev, ...next }, kind);\n };\n };\n\n private matchEventType = <\n T extends keyof EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >(\n expected: T,\n actual: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"event\"],\n _data: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"data\"]\n ): _data is EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[T][\"data\"] => {\n return expected === actual || actual.startsWith(`${expected}|`);\n };\n\n protected enqueue = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ) => {\n try {\n this.queueSize = Math.max(0, this.queueSize - 1);\n this.setState({ isLoading: true, error: undefined });\n this.abortRef = new AbortController();\n\n const run = await action(this.abortRef.signal);\n\n let streamError: StreamError | undefined;\n for await (const { event, data } of run) {\n if (event === \"error\") {\n streamError = new StreamError(data);\n break;\n }\n\n const namespace = event.includes(\"|\")\n ? event.split(\"|\").slice(1)\n : undefined;\n\n const mutate = this.getMutateFn(\"stream\", options.initialValues);\n\n if (event === \"metadata\") options.callbacks.onMetadataEvent?.(data);\n if (event === \"events\") options.callbacks.onLangChainEvent?.(data);\n\n if (this.matchEventType(\"updates\", event, data)) {\n options.callbacks.onUpdateEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"custom\", event, data)) {\n options.callbacks.onCustomEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"checkpoints\", event, data)) {\n options.callbacks.onCheckpointEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"tasks\", event, data)) {\n options.callbacks.onTaskEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"debug\", event, data)) {\n options.callbacks.onDebugEvent?.(data, { namespace });\n }\n\n if (event === \"values\") {\n if (\"__interrupt__\" in data) {\n this.setStreamValues((prev) => ({ ...prev, ...data }));\n } else {\n this.setStreamValues(data);\n }\n }\n\n if (this.matchEventType(\"messages\", event, data)) {\n const [serialized, metadata] = data;\n\n const messageId = this.messages.add(serialized, metadata);\n if (!messageId) {\n console.warn(\n \"Failed to add message to manager, no message ID found\"\n );\n continue;\n }\n\n this.setStreamValues((streamValues) => {\n const values = { ...options.initialValues, ...streamValues };\n\n // Assumption: we're concatenating the message\n let messages = options.getMessages(values).slice();\n const { chunk, index } =\n this.messages.get(messageId, messages.length) ?? {};\n\n if (!chunk || index == null) return values;\n if (chunk.getType() === \"remove\") {\n // Check for special REMOVE_ALL_MESSAGES sentinel\n if (chunk.id === REMOVE_ALL_MESSAGES) {\n // Clear all messages when __remove_all__ is received\n messages = [];\n } else {\n messages.splice(index, 1);\n }\n } else {\n messages[index] = toMessageDict(chunk);\n }\n\n return options.setMessages(values, messages);\n });\n }\n }\n\n if (streamError != null) throw streamError;\n\n const values = await options.onSuccess?.();\n if (typeof values !== \"undefined\" && this.queueSize === 0) {\n this.setStreamValues(values);\n }\n } catch (error) {\n if (\n !(\n error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n )\n ) {\n console.error(error);\n this.setState({ error });\n await options.onError?.(error);\n }\n } finally {\n this.setState({ isLoading: false });\n this.abortRef = new AbortController();\n options.onFinish?.();\n }\n };\n\n start = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ): Promise<void> => {\n this.queueSize += 1;\n this.queue = this.queue.then(() => this.enqueue(action, options));\n };\n\n stop = async (\n historyValues: StateType,\n options: {\n onStop?: (options: {\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }) => void;\n }\n ): Promise<void> => {\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n options.onStop?.({ mutate: this.getMutateFn(\"stop\", historyValues) });\n };\n\n clear = () => {\n // Cancel any running streams\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n // Set the stream state to null\n this.setState({ error: undefined, values: null, isLoading: false });\n\n // Clear any pending messages\n this.messages.clear();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manager.js","names":["values"],"sources":["../../src/ui/manager.ts"],"sourcesContent":["import type {\n CheckpointsStreamEvent,\n CustomStreamEvent,\n DebugStreamEvent,\n ErrorStreamEvent,\n EventsStreamEvent,\n FeedbackStreamEvent,\n MessagesTupleStreamEvent,\n MetadataStreamEvent,\n TasksStreamEvent,\n UpdatesStreamEvent,\n ValuesStreamEvent,\n} from \"../types.stream.js\";\nimport { MessageTupleManager, toMessageDict } from \"./messages.js\";\nimport { StreamError } from \"./errors.js\";\nimport type { Message } from \"../types.messages.js\";\nimport type { BagTemplate } from \"../types.template.js\";\n\n/**\n * Special ID used by LangGraph's messagesStateReducer to signal\n * that all messages should be removed from the state.\n */\nexport const REMOVE_ALL_MESSAGES = \"__remove_all__\";\n\ntype GetUpdateType<\n Bag extends BagTemplate,\n StateType extends Record<string, unknown>\n> = Bag extends { UpdateType: unknown }\n ? Bag[\"UpdateType\"]\n : Partial<StateType>;\n\ntype GetCustomEventType<Bag extends BagTemplate> = Bag extends {\n CustomEventType: unknown;\n}\n ? Bag[\"CustomEventType\"]\n : unknown;\n\ntype EventStreamMap<StateType, UpdateType, CustomType> = {\n values: ValuesStreamEvent<StateType>;\n updates: UpdatesStreamEvent<UpdateType>;\n custom: CustomStreamEvent<CustomType>;\n debug: DebugStreamEvent;\n messages: MessagesTupleStreamEvent;\n events: EventsStreamEvent;\n metadata: MetadataStreamEvent;\n checkpoints: CheckpointsStreamEvent<StateType>;\n tasks: TasksStreamEvent<StateType, UpdateType>;\n error: ErrorStreamEvent;\n feedback: FeedbackStreamEvent;\n};\n\nexport type EventStreamEvent<StateType, UpdateType, CustomType> =\n EventStreamMap<StateType, UpdateType, CustomType>[keyof EventStreamMap<\n StateType,\n UpdateType,\n CustomType\n >];\n\ninterface StreamManagerEventCallbacks<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n onUpdateEvent?: (\n data: UpdatesStreamEvent<GetUpdateType<Bag, StateType>>[\"data\"],\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onCustomEvent?: (\n data: GetCustomEventType<Bag>,\n options: {\n namespace: string[] | undefined;\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }\n ) => void;\n onMetadataEvent?: (data: MetadataStreamEvent[\"data\"]) => void;\n onLangChainEvent?: (data: EventsStreamEvent[\"data\"]) => void;\n onDebugEvent?: (\n data: DebugStreamEvent[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onCheckpointEvent?: (\n data: CheckpointsStreamEvent<StateType>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n onTaskEvent?: (\n data: TasksStreamEvent<StateType, GetUpdateType<Bag, StateType>>[\"data\"],\n options: { namespace: string[] | undefined }\n ) => void;\n}\n\nexport class StreamManager<\n StateType extends Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate\n> {\n private abortRef = new AbortController();\n\n private messages: MessageTupleManager;\n\n private listeners = new Set<() => void>();\n\n private throttle: number | boolean;\n\n private queue: Promise<unknown> = Promise.resolve();\n\n private queueSize: number = 0;\n\n private state: {\n isLoading: boolean;\n values: [values: StateType, kind: \"stream\" | \"stop\"] | null;\n error: unknown;\n };\n\n constructor(\n messages: MessageTupleManager,\n options: { throttle: number | boolean }\n ) {\n this.messages = messages;\n this.state = { isLoading: false, values: null, error: undefined };\n this.throttle = options.throttle;\n }\n\n private setState = (newState: Partial<typeof this.state>) => {\n this.state = { ...this.state, ...newState };\n this.notifyListeners();\n };\n\n private notifyListeners = () => {\n this.listeners.forEach((listener) => listener());\n };\n\n subscribe = (listener: () => void): (() => void) => {\n if (this.throttle === false) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n const timeoutMs = this.throttle === true ? 0 : this.throttle;\n let timeoutId: NodeJS.Timeout | number | undefined;\n\n const throttledListener = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n clearTimeout(timeoutId);\n listener();\n }, timeoutMs);\n };\n\n this.listeners.add(throttledListener);\n return () => {\n clearTimeout(timeoutId);\n this.listeners.delete(throttledListener);\n };\n };\n\n getSnapshot = () => this.state;\n\n get isLoading() {\n return this.state.isLoading;\n }\n\n get values() {\n return this.state.values?.[0] ?? null;\n }\n\n get error() {\n return this.state.error;\n }\n\n setStreamValues = (\n values:\n | (StateType | null)\n | ((prev: StateType | null, kind: \"stream\" | \"stop\") => StateType | null),\n kind: \"stream\" | \"stop\" = \"stream\"\n ) => {\n if (typeof values === \"function\") {\n const [prevValues, prevKind] = this.state.values ?? [null, \"stream\"];\n const nextValues = values(prevValues, prevKind);\n this.setState({ values: nextValues != null ? [nextValues, kind] : null });\n } else {\n const nextValues = values != null ? [values, kind] : null;\n this.setState({ values: nextValues as [StateType, \"stream\" | \"stop\"] });\n }\n };\n\n private getMutateFn = (kind: \"stream\" | \"stop\", historyValues: StateType) => {\n return (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => {\n const stateValues = (this.state.values ?? [null, \"stream\"])[0];\n const prev = {\n ...historyValues,\n ...(stateValues ?? {}),\n };\n const next = typeof update === \"function\" ? update(prev) : update;\n this.setStreamValues({ ...prev, ...(next ?? {}) }, kind);\n };\n };\n\n private matchEventType = <\n T extends keyof EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >(\n expected: T,\n actual: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"event\"],\n _data: EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[\"data\"]\n ): _data is EventStreamMap<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >[T][\"data\"] => {\n return expected === actual || actual.startsWith(`${expected}|`);\n };\n\n protected enqueue = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ) => {\n try {\n this.queueSize = Math.max(0, this.queueSize - 1);\n this.setState({ isLoading: true, error: undefined });\n this.abortRef = new AbortController();\n\n const run = await action(this.abortRef.signal);\n\n let streamError: StreamError | undefined;\n for await (const { event, data } of run) {\n if (event === \"error\") {\n streamError = new StreamError(data);\n break;\n }\n\n const namespace = event.includes(\"|\")\n ? event.split(\"|\").slice(1)\n : undefined;\n\n const mutate = this.getMutateFn(\"stream\", options.initialValues);\n\n if (event === \"metadata\") options.callbacks.onMetadataEvent?.(data);\n if (event === \"events\") options.callbacks.onLangChainEvent?.(data);\n\n if (this.matchEventType(\"updates\", event, data)) {\n options.callbacks.onUpdateEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"custom\", event, data)) {\n options.callbacks.onCustomEvent?.(data, { namespace, mutate });\n }\n\n if (this.matchEventType(\"checkpoints\", event, data)) {\n options.callbacks.onCheckpointEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"tasks\", event, data)) {\n options.callbacks.onTaskEvent?.(data, { namespace });\n }\n\n if (this.matchEventType(\"debug\", event, data)) {\n options.callbacks.onDebugEvent?.(data, { namespace });\n }\n\n if (event === \"values\") {\n if (\n data != null &&\n typeof data === \"object\" &&\n \"__interrupt__\" in data\n ) {\n this.setStreamValues((prev) => ({ ...(prev ?? {}), ...data }));\n } else {\n this.setStreamValues(data);\n }\n }\n\n if (this.matchEventType(\"messages\", event, data)) {\n const [serialized, metadata] = data;\n\n const messageId = this.messages.add(serialized, metadata);\n if (!messageId) {\n console.warn(\n \"Failed to add message to manager, no message ID found\"\n );\n continue;\n }\n\n this.setStreamValues((streamValues) => {\n const values = {\n ...options.initialValues,\n ...(streamValues ?? {}),\n };\n\n // Assumption: we're concatenating the message\n let messages = options.getMessages(values).slice();\n const { chunk, index } =\n this.messages.get(messageId, messages.length) ?? {};\n\n if (!chunk || index == null) return values;\n if (chunk.getType() === \"remove\") {\n // Check for special REMOVE_ALL_MESSAGES sentinel\n if (chunk.id === REMOVE_ALL_MESSAGES) {\n // Clear all messages when __remove_all__ is received\n messages = [];\n } else {\n messages.splice(index, 1);\n }\n } else {\n messages[index] = toMessageDict(chunk);\n }\n\n return options.setMessages(values, messages);\n });\n }\n }\n\n if (streamError != null) throw streamError;\n\n const values = await options.onSuccess?.();\n if (typeof values !== \"undefined\" && this.queueSize === 0) {\n this.setStreamValues(values);\n }\n } catch (error) {\n if (\n !(\n error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof\n (error.name === \"AbortError\" || error.name === \"TimeoutError\")\n )\n ) {\n console.error(error);\n this.setState({ error });\n await options.onError?.(error);\n }\n } finally {\n this.setState({ isLoading: false });\n this.abortRef = new AbortController();\n options.onFinish?.();\n }\n };\n\n start = async (\n action: (\n signal: AbortSignal\n ) => Promise<\n AsyncGenerator<\n EventStreamEvent<\n StateType,\n GetUpdateType<Bag, StateType>,\n GetCustomEventType<Bag>\n >\n >\n >,\n options: {\n getMessages: (values: StateType) => Message[];\n\n setMessages: (current: StateType, messages: Message[]) => StateType;\n\n initialValues: StateType;\n\n callbacks: StreamManagerEventCallbacks<StateType, Bag>;\n\n onSuccess: () =>\n | StateType\n | null\n | undefined\n | void\n | Promise<StateType | null | undefined | void>;\n\n onError: (error: unknown) => void | Promise<void>;\n\n onFinish?: () => void;\n }\n ): Promise<void> => {\n this.queueSize += 1;\n this.queue = this.queue.then(() => this.enqueue(action, options));\n };\n\n stop = async (\n historyValues: StateType,\n options: {\n onStop?: (options: {\n mutate: (\n update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)\n ) => void;\n }) => void;\n }\n ): Promise<void> => {\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n options.onStop?.({ mutate: this.getMutateFn(\"stop\", historyValues) });\n };\n\n clear = () => {\n // Cancel any running streams\n this.abortRef.abort();\n this.abortRef = new AbortController();\n\n // Set the stream state to null\n this.setState({ error: undefined, values: null, isLoading: false });\n\n // Clear any pending messages\n this.messages.clear();\n };\n}\n"],"mappings":"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"}
|
package/dist/ui/utils.cjs
CHANGED
|
@@ -6,8 +6,16 @@ function unique(array) {
|
|
|
6
6
|
function findLast(array, predicate) {
|
|
7
7
|
for (let i = array.length - 1; i >= 0; i -= 1) if (predicate(array[i])) return array[i];
|
|
8
8
|
}
|
|
9
|
+
async function* filterStream(stream, filter) {
|
|
10
|
+
while (true) {
|
|
11
|
+
const { value, done } = await stream.next();
|
|
12
|
+
if (done) return value;
|
|
13
|
+
if (filter(value)) yield value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
//#endregion
|
|
18
|
+
exports.filterStream = filterStream;
|
|
11
19
|
exports.findLast = findLast;
|
|
12
20
|
exports.unique = unique;
|
|
13
21
|
//# sourceMappingURL=utils.cjs.map
|
package/dist/ui/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","names":[],"sources":["../../src/ui/utils.ts"],"sourcesContent":["export function unique<T>(array: T[]) {\n return [...new Set(array)] as T[];\n}\n\nexport function findLast<T>(array: T[], predicate: (item: T) => boolean) {\n for (let i = array.length - 1; i >= 0; i -= 1) {\n if (predicate(array[i])) return array[i];\n }\n return undefined;\n}\n"],"mappings":";;AAAA,SAAgB,OAAU,OAAY;AACpC,QAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;;AAG5B,SAAgB,SAAY,OAAY,WAAiC;AACvE,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,EAC1C,KAAI,UAAU,MAAM,GAAG,CAAE,QAAO,MAAM"}
|
|
1
|
+
{"version":3,"file":"utils.cjs","names":[],"sources":["../../src/ui/utils.ts"],"sourcesContent":["export function unique<T>(array: T[]) {\n return [...new Set(array)] as T[];\n}\n\nexport function findLast<T>(array: T[], predicate: (item: T) => boolean) {\n for (let i = array.length - 1; i >= 0; i -= 1) {\n if (predicate(array[i])) return array[i];\n }\n return undefined;\n}\n\nexport async function* filterStream<T, TReturn>(\n stream: AsyncGenerator<T, TReturn>,\n filter: (event: T) => boolean\n): AsyncGenerator<T, TReturn> {\n while (true) {\n const { value, done } = await stream.next();\n if (done) return value as TReturn;\n if (filter(value)) yield value as T;\n }\n}\n"],"mappings":";;AAAA,SAAgB,OAAU,OAAY;AACpC,QAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;;AAG5B,SAAgB,SAAY,OAAY,WAAiC;AACvE,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,EAC1C,KAAI,UAAU,MAAM,GAAG,CAAE,QAAO,MAAM;;AAK1C,gBAAuB,aACrB,QACA,QAC4B;AAC5B,QAAO,MAAM;EACX,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM,QAAO;AACjB,MAAI,OAAO,MAAM,CAAE,OAAM"}
|
package/dist/ui/utils.js
CHANGED
|
@@ -5,7 +5,14 @@ function unique(array) {
|
|
|
5
5
|
function findLast(array, predicate) {
|
|
6
6
|
for (let i = array.length - 1; i >= 0; i -= 1) if (predicate(array[i])) return array[i];
|
|
7
7
|
}
|
|
8
|
+
async function* filterStream(stream, filter) {
|
|
9
|
+
while (true) {
|
|
10
|
+
const { value, done } = await stream.next();
|
|
11
|
+
if (done) return value;
|
|
12
|
+
if (filter(value)) yield value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
//#endregion
|
|
10
|
-
export { findLast, unique };
|
|
17
|
+
export { filterStream, findLast, unique };
|
|
11
18
|
//# sourceMappingURL=utils.js.map
|
package/dist/ui/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../src/ui/utils.ts"],"sourcesContent":["export function unique<T>(array: T[]) {\n return [...new Set(array)] as T[];\n}\n\nexport function findLast<T>(array: T[], predicate: (item: T) => boolean) {\n for (let i = array.length - 1; i >= 0; i -= 1) {\n if (predicate(array[i])) return array[i];\n }\n return undefined;\n}\n"],"mappings":";AAAA,SAAgB,OAAU,OAAY;AACpC,QAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;;AAG5B,SAAgB,SAAY,OAAY,WAAiC;AACvE,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,EAC1C,KAAI,UAAU,MAAM,GAAG,CAAE,QAAO,MAAM"}
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../src/ui/utils.ts"],"sourcesContent":["export function unique<T>(array: T[]) {\n return [...new Set(array)] as T[];\n}\n\nexport function findLast<T>(array: T[], predicate: (item: T) => boolean) {\n for (let i = array.length - 1; i >= 0; i -= 1) {\n if (predicate(array[i])) return array[i];\n }\n return undefined;\n}\n\nexport async function* filterStream<T, TReturn>(\n stream: AsyncGenerator<T, TReturn>,\n filter: (event: T) => boolean\n): AsyncGenerator<T, TReturn> {\n while (true) {\n const { value, done } = await stream.next();\n if (done) return value as TReturn;\n if (filter(value)) yield value as T;\n }\n}\n"],"mappings":";AAAA,SAAgB,OAAU,OAAY;AACpC,QAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;;AAG5B,SAAgB,SAAY,OAAY,WAAiC;AACvE,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,EAC1C,KAAI,UAAU,MAAM,GAAG,CAAE,QAAO,MAAM;;AAK1C,gBAAuB,aACrB,QACA,QAC4B;AAC5B,QAAO,MAAM;EACX,MAAM,EAAE,OAAO,SAAS,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM,QAAO;AACjB,MAAI,OAAO,MAAM,CAAE,OAAM"}
|
package/dist/utils/error.cjs
CHANGED
|
@@ -5,11 +5,18 @@ const isError = (error) => {
|
|
|
5
5
|
const stringTag = Object.prototype.toString.call(error);
|
|
6
6
|
return stringTag === "[object Error]" || stringTag === "[object DOMException]" || stringTag === "[object DOMError]" || stringTag === "[object Exception]";
|
|
7
7
|
};
|
|
8
|
+
const getCauseError = (error) => {
|
|
9
|
+
const { cause } = error;
|
|
10
|
+
if (typeof cause !== "object" || cause == null) return null;
|
|
11
|
+
if (!isError(cause)) return null;
|
|
12
|
+
return cause;
|
|
13
|
+
};
|
|
8
14
|
const isNetworkError = (error) => {
|
|
9
15
|
if (!isError(error)) return false;
|
|
10
16
|
if (error.name !== "TypeError" || typeof error.message !== "string") return false;
|
|
11
17
|
const msg = error.message.toLowerCase();
|
|
12
|
-
|
|
18
|
+
const causeMsg = getCauseError(error)?.message?.toLowerCase() ?? "";
|
|
19
|
+
return msg.includes("fetch") || msg.includes("network") || msg.includes("connection") || msg.includes("error sending request") || msg.includes("load failed") || msg.includes("terminated") || causeMsg.includes("other side closed") || causeMsg.includes("socket");
|
|
13
20
|
};
|
|
14
21
|
|
|
15
22
|
//#endregion
|
package/dist/utils/error.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.cjs","names":[],"sources":["../../src/utils/error.ts"],"sourcesContent":["export const isError = (error: unknown): error is Error => {\n // check for presence of `Error.isError` for newer browsers\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n return Error.isError(error);\n }\n\n // Resort to checking string tag\n const stringTag = Object.prototype.toString.call(error);\n return (\n stringTag === \"[object Error]\" ||\n stringTag === \"[object DOMException]\" ||\n stringTag === \"[object DOMError]\" ||\n stringTag === \"[object Exception]\"\n );\n};\n\nexport const isNetworkError = (error: unknown): error is Error => {\n if (!isError(error)) return false;\n if (error.name !== \"TypeError\" || typeof error.message !== \"string\") {\n return false;\n }\n const msg = error.message.toLowerCase();\n return (\n msg.includes(\"fetch\") ||\n msg.includes(\"network\") ||\n msg.includes(\"connection\") ||\n msg.includes(\"error sending request\") ||\n msg.includes(\"load failed\")\n );\n};\n"],"mappings":";;AAAA,MAAa,WAAW,UAAmC;AAEzD,KAAI,aAAa,SAAS,OAAO,MAAM,YAAY,WACjD,QAAO,MAAM,QAAQ,MAAM;CAI7B,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK,MAAM;AACvD,QACE,cAAc,oBACd,cAAc,2BACd,cAAc,uBACd,cAAc;;AAIlB,MAAa,kBAAkB,UAAmC;AAChE,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,YAAY,SACzD,QAAO;CAET,MAAM,MAAM,MAAM,QAAQ,aAAa;
|
|
1
|
+
{"version":3,"file":"error.cjs","names":[],"sources":["../../src/utils/error.ts"],"sourcesContent":["export const isError = (error: unknown): error is Error => {\n // check for presence of `Error.isError` for newer browsers\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n return Error.isError(error);\n }\n\n // Resort to checking string tag\n const stringTag = Object.prototype.toString.call(error);\n return (\n stringTag === \"[object Error]\" ||\n stringTag === \"[object DOMException]\" ||\n stringTag === \"[object DOMError]\" ||\n stringTag === \"[object Exception]\"\n );\n};\n\nconst getCauseError = (error: Error): Error | null => {\n const { cause } = error;\n if (typeof cause !== \"object\" || cause == null) return null;\n\n if (!isError(cause)) return null;\n return cause;\n};\n\nexport const isNetworkError = (error: unknown): error is Error => {\n if (!isError(error)) return false;\n if (error.name !== \"TypeError\" || typeof error.message !== \"string\") {\n return false;\n }\n const msg = error.message.toLowerCase();\n const causeMsg = getCauseError(error)?.message?.toLowerCase() ?? \"\";\n return (\n msg.includes(\"fetch\") ||\n msg.includes(\"network\") ||\n msg.includes(\"connection\") ||\n msg.includes(\"error sending request\") ||\n msg.includes(\"load failed\") ||\n msg.includes(\"terminated\") ||\n causeMsg.includes(\"other side closed\") ||\n causeMsg.includes(\"socket\")\n );\n};\n"],"mappings":";;AAAA,MAAa,WAAW,UAAmC;AAEzD,KAAI,aAAa,SAAS,OAAO,MAAM,YAAY,WACjD,QAAO,MAAM,QAAQ,MAAM;CAI7B,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK,MAAM;AACvD,QACE,cAAc,oBACd,cAAc,2BACd,cAAc,uBACd,cAAc;;AAIlB,MAAM,iBAAiB,UAA+B;CACpD,MAAM,EAAE,UAAU;AAClB,KAAI,OAAO,UAAU,YAAY,SAAS,KAAM,QAAO;AAEvD,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,QAAO;;AAGT,MAAa,kBAAkB,UAAmC;AAChE,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,YAAY,SACzD,QAAO;CAET,MAAM,MAAM,MAAM,QAAQ,aAAa;CACvC,MAAM,WAAW,cAAc,MAAM,EAAE,SAAS,aAAa,IAAI;AACjE,QACE,IAAI,SAAS,QAAQ,IACrB,IAAI,SAAS,UAAU,IACvB,IAAI,SAAS,aAAa,IAC1B,IAAI,SAAS,wBAAwB,IACrC,IAAI,SAAS,cAAc,IAC3B,IAAI,SAAS,aAAa,IAC1B,SAAS,SAAS,oBAAoB,IACtC,SAAS,SAAS,SAAS"}
|
package/dist/utils/error.js
CHANGED
|
@@ -4,11 +4,18 @@ const isError = (error) => {
|
|
|
4
4
|
const stringTag = Object.prototype.toString.call(error);
|
|
5
5
|
return stringTag === "[object Error]" || stringTag === "[object DOMException]" || stringTag === "[object DOMError]" || stringTag === "[object Exception]";
|
|
6
6
|
};
|
|
7
|
+
const getCauseError = (error) => {
|
|
8
|
+
const { cause } = error;
|
|
9
|
+
if (typeof cause !== "object" || cause == null) return null;
|
|
10
|
+
if (!isError(cause)) return null;
|
|
11
|
+
return cause;
|
|
12
|
+
};
|
|
7
13
|
const isNetworkError = (error) => {
|
|
8
14
|
if (!isError(error)) return false;
|
|
9
15
|
if (error.name !== "TypeError" || typeof error.message !== "string") return false;
|
|
10
16
|
const msg = error.message.toLowerCase();
|
|
11
|
-
|
|
17
|
+
const causeMsg = getCauseError(error)?.message?.toLowerCase() ?? "";
|
|
18
|
+
return msg.includes("fetch") || msg.includes("network") || msg.includes("connection") || msg.includes("error sending request") || msg.includes("load failed") || msg.includes("terminated") || causeMsg.includes("other side closed") || causeMsg.includes("socket");
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
//#endregion
|
package/dist/utils/error.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.js","names":[],"sources":["../../src/utils/error.ts"],"sourcesContent":["export const isError = (error: unknown): error is Error => {\n // check for presence of `Error.isError` for newer browsers\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n return Error.isError(error);\n }\n\n // Resort to checking string tag\n const stringTag = Object.prototype.toString.call(error);\n return (\n stringTag === \"[object Error]\" ||\n stringTag === \"[object DOMException]\" ||\n stringTag === \"[object DOMError]\" ||\n stringTag === \"[object Exception]\"\n );\n};\n\nexport const isNetworkError = (error: unknown): error is Error => {\n if (!isError(error)) return false;\n if (error.name !== \"TypeError\" || typeof error.message !== \"string\") {\n return false;\n }\n const msg = error.message.toLowerCase();\n return (\n msg.includes(\"fetch\") ||\n msg.includes(\"network\") ||\n msg.includes(\"connection\") ||\n msg.includes(\"error sending request\") ||\n msg.includes(\"load failed\")\n );\n};\n"],"mappings":";AAAA,MAAa,WAAW,UAAmC;AAEzD,KAAI,aAAa,SAAS,OAAO,MAAM,YAAY,WACjD,QAAO,MAAM,QAAQ,MAAM;CAI7B,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK,MAAM;AACvD,QACE,cAAc,oBACd,cAAc,2BACd,cAAc,uBACd,cAAc;;AAIlB,MAAa,kBAAkB,UAAmC;AAChE,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,YAAY,SACzD,QAAO;CAET,MAAM,MAAM,MAAM,QAAQ,aAAa;
|
|
1
|
+
{"version":3,"file":"error.js","names":[],"sources":["../../src/utils/error.ts"],"sourcesContent":["export const isError = (error: unknown): error is Error => {\n // check for presence of `Error.isError` for newer browsers\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n return Error.isError(error);\n }\n\n // Resort to checking string tag\n const stringTag = Object.prototype.toString.call(error);\n return (\n stringTag === \"[object Error]\" ||\n stringTag === \"[object DOMException]\" ||\n stringTag === \"[object DOMError]\" ||\n stringTag === \"[object Exception]\"\n );\n};\n\nconst getCauseError = (error: Error): Error | null => {\n const { cause } = error;\n if (typeof cause !== \"object\" || cause == null) return null;\n\n if (!isError(cause)) return null;\n return cause;\n};\n\nexport const isNetworkError = (error: unknown): error is Error => {\n if (!isError(error)) return false;\n if (error.name !== \"TypeError\" || typeof error.message !== \"string\") {\n return false;\n }\n const msg = error.message.toLowerCase();\n const causeMsg = getCauseError(error)?.message?.toLowerCase() ?? \"\";\n return (\n msg.includes(\"fetch\") ||\n msg.includes(\"network\") ||\n msg.includes(\"connection\") ||\n msg.includes(\"error sending request\") ||\n msg.includes(\"load failed\") ||\n msg.includes(\"terminated\") ||\n causeMsg.includes(\"other side closed\") ||\n causeMsg.includes(\"socket\")\n );\n};\n"],"mappings":";AAAA,MAAa,WAAW,UAAmC;AAEzD,KAAI,aAAa,SAAS,OAAO,MAAM,YAAY,WACjD,QAAO,MAAM,QAAQ,MAAM;CAI7B,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK,MAAM;AACvD,QACE,cAAc,oBACd,cAAc,2BACd,cAAc,uBACd,cAAc;;AAIlB,MAAM,iBAAiB,UAA+B;CACpD,MAAM,EAAE,UAAU;AAClB,KAAI,OAAO,UAAU,YAAY,SAAS,KAAM,QAAO;AAEvD,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,QAAO;;AAGT,MAAa,kBAAkB,UAAmC;AAChE,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,YAAY,SACzD,QAAO;CAET,MAAM,MAAM,MAAM,QAAQ,aAAa;CACvC,MAAM,WAAW,cAAc,MAAM,EAAE,SAAS,aAAa,IAAI;AACjE,QACE,IAAI,SAAS,QAAQ,IACrB,IAAI,SAAS,UAAU,IACvB,IAAI,SAAS,aAAa,IAC1B,IAAI,SAAS,wBAAwB,IACrC,IAAI,SAAS,cAAc,IAC3B,IAAI,SAAS,aAAa,IAC1B,SAAS,SAAS,oBAAoB,IACtC,SAAS,SAAS,SAAS"}
|
package/dist/utils/stream.cjs
CHANGED
|
@@ -16,7 +16,7 @@ var MaxReconnectAttemptsError = class extends Error {
|
|
|
16
16
|
* Implements reconnection behavior similar to the Python SDK.
|
|
17
17
|
*
|
|
18
18
|
* @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.
|
|
19
|
-
* When `params.
|
|
19
|
+
* When `params.reconnectPath` is provided, it's a reconnection request.
|
|
20
20
|
* @param options Configuration options
|
|
21
21
|
* @returns AsyncGenerator yielding stream events
|
|
22
22
|
*/
|
|
@@ -31,7 +31,7 @@ async function* streamWithRetry(makeRequest, options = {}) {
|
|
|
31
31
|
let reader;
|
|
32
32
|
try {
|
|
33
33
|
if (options.signal?.aborted) return;
|
|
34
|
-
const { response, stream } = await makeRequest(
|
|
34
|
+
const { response, stream } = await makeRequest(reconnectPath ? {
|
|
35
35
|
lastEventId,
|
|
36
36
|
reconnectPath
|
|
37
37
|
} : void 0);
|
|
@@ -53,7 +53,7 @@ async function* streamWithRetry(makeRequest, options = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
break;
|
|
55
55
|
} catch (error) {
|
|
56
|
-
if (
|
|
56
|
+
if (reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
57
57
|
else throw error;
|
|
58
58
|
} finally {
|
|
59
59
|
if (reader) try {
|
|
@@ -62,7 +62,7 @@ async function* streamWithRetry(makeRequest, options = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
} catch (error) {
|
|
64
64
|
lastError = error;
|
|
65
|
-
if (require_error.isNetworkError(error) &&
|
|
65
|
+
if (require_error.isNetworkError(error) && reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
66
66
|
else throw error;
|
|
67
67
|
}
|
|
68
68
|
if (shouldRetry) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.cjs","names":["isNetworkError"],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * If provided, this is a reconnection request with the last event ID\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.lastEventId` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no lastEventId, reconnect otherwise\n const { response, stream } = await makeRequest(\n lastEventId ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have lastEventId and a location header\n if (lastEventId && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (\n isNetworkError(error) &&\n lastEventId &&\n reconnectPath &&\n !options.signal?.aborted\n ) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stream.cjs","names":["isNetworkError"],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * Last event ID to resume from, if available\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.reconnectPath` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no reconnect path, reconnect otherwise\n const { response, stream } = await makeRequest(\n reconnectPath ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have a location header\n if (reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (isNetworkError(error) && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/utils/stream.js
CHANGED
|
@@ -16,7 +16,7 @@ var MaxReconnectAttemptsError = class extends Error {
|
|
|
16
16
|
* Implements reconnection behavior similar to the Python SDK.
|
|
17
17
|
*
|
|
18
18
|
* @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.
|
|
19
|
-
* When `params.
|
|
19
|
+
* When `params.reconnectPath` is provided, it's a reconnection request.
|
|
20
20
|
* @param options Configuration options
|
|
21
21
|
* @returns AsyncGenerator yielding stream events
|
|
22
22
|
*/
|
|
@@ -31,7 +31,7 @@ async function* streamWithRetry(makeRequest, options = {}) {
|
|
|
31
31
|
let reader;
|
|
32
32
|
try {
|
|
33
33
|
if (options.signal?.aborted) return;
|
|
34
|
-
const { response, stream } = await makeRequest(
|
|
34
|
+
const { response, stream } = await makeRequest(reconnectPath ? {
|
|
35
35
|
lastEventId,
|
|
36
36
|
reconnectPath
|
|
37
37
|
} : void 0);
|
|
@@ -53,7 +53,7 @@ async function* streamWithRetry(makeRequest, options = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
break;
|
|
55
55
|
} catch (error) {
|
|
56
|
-
if (
|
|
56
|
+
if (reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
57
57
|
else throw error;
|
|
58
58
|
} finally {
|
|
59
59
|
if (reader) try {
|
|
@@ -62,7 +62,7 @@ async function* streamWithRetry(makeRequest, options = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
} catch (error) {
|
|
64
64
|
lastError = error;
|
|
65
|
-
if (isNetworkError(error) &&
|
|
65
|
+
if (isNetworkError(error) && reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
66
66
|
else throw error;
|
|
67
67
|
}
|
|
68
68
|
if (shouldRetry) {
|
package/dist/utils/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","names":[],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * If provided, this is a reconnection request with the last event ID\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.lastEventId` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no lastEventId, reconnect otherwise\n const { response, stream } = await makeRequest(\n lastEventId ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have lastEventId and a location header\n if (lastEventId && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (\n isNetworkError(error) &&\n lastEventId &&\n reconnectPath &&\n !options.signal?.aborted\n ) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stream.js","names":[],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * Last event ID to resume from, if available\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.reconnectPath` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no reconnect path, reconnect otherwise\n const { response, stream } = await makeRequest(\n reconnectPath ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have a location header\n if (reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (isNetworkError(error) && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|