feat(sdk-js): add experimental useStream hook (#3361)

Experimental `useStream` React hook to make streaming as simple as
possible. Supports branching / forking, message-tuple stream mode for
rendering messages.

Usage: https://github.com/langchain-ai/langchain-nextjs-template/pull/62
This commit is contained in:
David Duong
2025-02-12 09:39:02 -08:00
committed by GitHub
11 changed files with 928 additions and 27 deletions
+4
View File
@@ -6,6 +6,10 @@ client.cjs
client.js
client.d.ts
client.d.cts
react.cjs
react.js
react.d.ts
react.d.cts
node_modules
dist
.yarn
+2 -2
View File
@@ -10,8 +10,8 @@ function abs(relativePath) {
}
export const config = {
internals: [],
entrypoints: { index: "index", client: "client" },
internals: [/react/],
entrypoints: { index: "index", client: "client", react: "react/index" },
tsConfigPath: resolve("./tsconfig.json"),
cjsSource: "./dist-cjs",
cjsDestination: "./dist",
+22 -2
View File
@@ -22,18 +22,25 @@
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@langchain/core": "^0.3.31",
"@langchain/scripts": "^0.1.4",
"@tsconfig/recommended": "^1.0.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.12",
"@types/uuid": "^9.0.1",
"@types/react": "18.3.2",
"concat-md": "^0.5.1",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"typedoc": "^0.26.1",
"typedoc-plugin-markdown": "^4.1.0",
"typescript": "^5.4.5"
"typescript": "^5.4.5",
"react": "^18.3.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"@langchain/core": ">=0.2.31 <0.4.0"
},
"exports": {
".": {
@@ -54,6 +61,15 @@
"import": "./client.js",
"require": "./client.cjs"
},
"./react": {
"types": {
"import": "./react.d.ts",
"require": "./react.d.cts",
"default": "./react.d.ts"
},
"import": "./react.js",
"require": "./react.cjs"
},
"./package.json": "./package.json"
},
"files": [
@@ -65,6 +81,10 @@
"client.cjs",
"client.js",
"client.d.ts",
"client.d.cts"
"client.d.cts",
"react.cjs",
"react.js",
"react.d.ts",
"react.d.cts"
]
}
+1 -1
View File
@@ -68,7 +68,7 @@ export function getApiKey(apiKey?: string): string | undefined {
return undefined;
}
interface ClientConfig {
export interface ClientConfig {
apiUrl?: string;
apiKey?: string;
callerOptions?: AsyncCallerParams;
+102
View File
@@ -0,0 +1,102 @@
import { ThreadState } from "../schema.js";
interface Node<StateType = any> {
type: "node";
value: ThreadState<StateType>;
path: string[];
}
interface Fork<StateType = any> {
type: "fork";
items: Array<Sequence<StateType>>;
}
interface Sequence<StateType = any> {
type: "sequence";
items: Array<Node<StateType> | Fork<StateType>>;
}
interface ValidFork<StateType = any> {
type: "fork";
items: Array<ValidSequence<StateType>>;
}
interface ValidSequence<StateType = any> {
type: "sequence";
items: [Node<StateType>, ...(Node<StateType> | ValidFork<StateType>)[]];
}
// forks
export type CheckpointBranchPath = string[];
export type MessageBranch = {
current: CheckpointBranchPath;
options: CheckpointBranchPath[];
};
export function DebugSegmentsView(props: {
sequence: ValidSequence<ThreadState>;
}) {
const concatContent = (value: ThreadState<any>) => {
let content;
try {
content = value.values?.messages?.at(-1)?.content ?? "";
} catch {
content = JSON.stringify(value.values);
}
content = content.replace(/(\n|\r\n)/g, "");
if (content.length <= 23) return content;
return `${content.slice(0, 10)}...${content.slice(-10)}`;
};
return (
<div>
{props.sequence.items.map((item, index) => {
if (item.type === "fork") {
return (
<div key={index}>
{item.items.map((fork, idx) => {
const [first] = fork.items;
return (
<details key={idx}>
<summary>
Fork{" "}
<span className="font-mono">
...{first.path.at(-1)?.slice(-4)}
</span>
</summary>
<div className="ml-4">
<DebugSegmentsView sequence={fork} />
</div>
</details>
);
})}
</div>
);
}
if (item.type === "node") {
return (
<div key={index} className="flex items-center gap-2">
<pre>
({item.value.metadata?.step}) ...
{item.value.checkpoint.checkpoint_id?.slice(-4)} (
{item.value.metadata?.source}): {concatContent(item.value)}
</pre>
<button
type="button"
className="border rounded-sm text-sm py-0.5 px-1 text-muted-foreground"
onClick={() => console.log(item.path, item.value)}
>
console.log
</button>
</div>
);
}
return null;
})}
</div>
);
}
+1
View File
@@ -0,0 +1 @@
export { useStream, type MessageMetadata } from "./stream.js";
+665
View File
@@ -0,0 +1,665 @@
/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
"use client";
import { Client, type ClientConfig } from "../client.js";
import type {
Command,
DisconnectMode,
MultitaskStrategy,
OnCompletionBehavior,
} from "../types.js";
import type { Message } from "../types.messages.js";
import type { Checkpoint, Config, Metadata, ThreadState } from "../schema.js";
import type {
CustomStreamEvent,
DebugStreamEvent,
ErrorStreamEvent,
EventsStreamEvent,
FeedbackStreamEvent,
MessagesStreamEvent,
MessagesTupleStreamEvent,
MetadataStreamEvent,
StreamMode,
UpdatesStreamEvent,
ValuesStreamEvent,
} from "../types.stream.js";
import {
type MutableRefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
type BaseMessageChunk,
type BaseMessage,
coerceMessageLikeToMessage,
convertToChunk,
} from "@langchain/core/messages";
class StreamError extends Error {
constructor(data: { error?: string; name?: string; message: string }) {
super(data.message);
this.name = data.name ?? data.error ?? "StreamError";
}
static isStructuredError(error: unknown): error is {
error?: string;
name?: string;
message: string;
} {
return typeof error === "object" && error != null && "message" in error;
}
}
class MessageTupleManager {
chunks: Record<string, { chunk?: BaseMessageChunk; index?: number }> = {};
constructor() {
this.chunks = {};
}
add(serialized: Message): string | null {
const chunk = convertToChunk(coerceMessageLikeToMessage(serialized));
const id = chunk.id;
if (!id) return null;
this.chunks[id] ??= {};
this.chunks[id].chunk = this.chunks[id]?.chunk?.concat(chunk) ?? chunk;
return id;
}
clear() {
this.chunks = {};
}
get(id: string, defaultIndex: number) {
if (this.chunks[id] == null) return null;
this.chunks[id].index ??= defaultIndex;
return this.chunks[id];
}
}
const toMessageDict = (chunk: BaseMessage): Message => {
const { type, data } = chunk.toDict();
return { ...data, type } as Message;
};
function unique<T>(array: T[]) {
return [...new Set(array)] as T[];
}
function findLastIndex<T>(array: T[], predicate: (item: T) => boolean) {
for (let i = array.length - 1; i >= 0; i--) {
if (predicate(array[i])) return i;
}
return -1;
}
interface Node<StateType = any> {
type: "node";
value: ThreadState<StateType>;
path: string[];
}
interface Fork<StateType = any> {
type: "fork";
items: Array<Sequence<StateType>>;
}
interface Sequence<StateType = any> {
type: "sequence";
items: Array<Node<StateType> | Fork<StateType>>;
}
interface ValidFork<StateType = any> {
type: "fork";
items: Array<ValidSequence<StateType>>;
}
interface ValidSequence<StateType = any> {
type: "sequence";
items: [Node<StateType>, ...(Node<StateType> | ValidFork<StateType>)[]];
}
export type MessageMetadata<StateType extends Record<string, unknown>> = {
messageId: string;
firstSeenState: ThreadState<StateType> | undefined;
branch: string | undefined;
branchOptions: string[] | undefined;
};
function fetchHistory<StateType extends Record<string, unknown>>(
client: Client,
threadId: string,
) {
return client.threads.getHistory<StateType>(threadId, { limit: 1000 });
}
function useThreadHistory<StateType extends Record<string, unknown>>(
threadId: string | undefined | null,
client: Client,
clearCallbackRef: MutableRefObject<(() => void) | undefined>,
submittingRef: MutableRefObject<boolean>,
) {
const [history, setHistory] = useState<ThreadState<StateType>[]>([]);
const fetcher = useCallback(
(
threadId: string | undefined | null,
): Promise<ThreadState<StateType>[]> => {
if (threadId != null) {
return fetchHistory<StateType>(client, threadId).then((history) => {
setHistory(history);
return history;
});
}
setHistory([]);
clearCallbackRef.current?.();
return Promise.resolve([]);
},
[],
);
useEffect(() => {
if (submittingRef.current) return;
fetcher(threadId);
}, [fetcher, submittingRef, threadId]);
return {
data: history,
mutate: (mutateId?: string) => fetcher(mutateId ?? threadId),
};
}
export function useStream<
StateType extends Record<string, unknown> = Record<string, unknown>,
UpdateType extends Record<string, unknown> = Partial<StateType>,
CustomType = unknown,
>(options: {
assistantId: string;
apiUrl: ClientConfig["apiUrl"];
apiKey?: ClientConfig["apiKey"];
withMessages?: string;
onError?: (error: unknown) => void;
onFinish?: (state: ThreadState<StateType>) => void;
onUpdateEvent?: (data: UpdatesStreamEvent<UpdateType>["data"]) => void;
onCustomEvent?: (data: CustomStreamEvent<CustomType>["data"]) => void;
onMetadataEvent?: (data: MetadataStreamEvent["data"]) => void;
// TODO: can we make threadId uncontrollable / controllable?
threadId?: string | null;
onThreadId?: (threadId: string) => void;
}) {
type EventStreamEvent =
| ValuesStreamEvent<StateType>
| UpdatesStreamEvent<UpdateType>
| CustomStreamEvent<CustomType>
| DebugStreamEvent
| MessagesStreamEvent
| MessagesTupleStreamEvent
| EventsStreamEvent
| MetadataStreamEvent
| ErrorStreamEvent
| FeedbackStreamEvent;
const { assistantId, threadId, withMessages, onError, onFinish } = options;
const client = useMemo(
() => new Client({ apiUrl: options.apiUrl, apiKey: options.apiKey }),
[options.apiKey, options.apiUrl],
);
const [branchPath, setBranchPath] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [_, setEvents] = useState<EventStreamEvent[]>([]);
const [streamError, setStreamError] = useState<unknown>(undefined);
const [streamValues, setStreamValues] = useState<StateType | null>(null);
const messageManagerRef = useRef(new MessageTupleManager());
const submittingRef = useRef(false);
const abortRef = useRef<AbortController | null>(null);
const trackStreamModeRef = useRef<
Array<"values" | "updates" | "events" | "custom" | "messages-tuple">
>(["values", "messages-tuple"]);
const trackStreamMode = useCallback(
(mode: Exclude<StreamMode, "debug" | "messages">) => {
if (!trackStreamModeRef.current.includes(mode))
trackStreamModeRef.current.push(mode);
},
[],
);
const hasUpdateListener = options.onUpdateEvent != null;
const hasCustomListener = options.onCustomEvent != null;
const callbackStreamMode = useMemo(() => {
const modes: Exclude<StreamMode, "debug" | "messages">[] = [];
if (hasUpdateListener) modes.push("updates");
if (hasCustomListener) modes.push("custom");
return modes;
}, [hasUpdateListener, hasCustomListener]);
const clearCallbackRef = useRef<() => void>(null!);
clearCallbackRef.current = () => {
setStreamError(undefined);
setStreamValues(null);
};
// TODO: this should be done on the server to avoid pagination
// TODO: should we permit adapter? SWR / React Query?
const history = useThreadHistory<StateType>(
threadId,
client,
clearCallbackRef,
submittingRef,
);
const getMessages = useMemo(() => {
if (withMessages == null) return undefined;
return (value: StateType) =>
Array.isArray(value[withMessages])
? (value[withMessages] as Message[])
: [];
}, [withMessages]);
const [sequence, pathMap] = (() => {
const childrenMap: Record<string, ThreadState<StateType>[]> = {};
// First pass - collect nodes for each checkpoint
history.data.forEach((state) => {
const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
childrenMap[checkpointId] ??= [];
childrenMap[checkpointId].push(state);
});
// Second pass - create a tree of sequences
type Task = { id: string; sequence: Sequence; path: string[] };
const rootSequence: Sequence = { type: "sequence", items: [] };
const queue: Task[] = [{ id: "$", sequence: rootSequence, path: [] }];
const paths: string[][] = [];
const visited = new Set<string>();
while (queue.length > 0) {
const task = queue.shift()!;
if (visited.has(task.id)) continue;
visited.add(task.id);
const children = childrenMap[task.id];
if (children == null || children.length === 0) continue;
// If we've encountered a fork (2+ children), push the fork
// to the sequence and add a new sequence for each child
let fork: Fork | undefined;
if (children.length > 1) {
fork = { type: "fork", items: [] };
task.sequence.items.push(fork);
}
for (const value of children) {
const id = value.checkpoint.checkpoint_id!;
let sequence = task.sequence;
let path = task.path;
if (fork != null) {
sequence = { type: "sequence", items: [] };
fork.items.unshift(sequence);
path = path.slice();
path.push(id);
paths.push(path);
}
sequence.items.push({ type: "node", value, path });
queue.push({ id, sequence, path });
}
}
// Third pass, create a map for available forks
const pathMap: Record<string, string[][]> = {};
for (const path of paths) {
const parent = path.at(-2) ?? "$";
pathMap[parent] ??= [];
pathMap[parent].unshift(path);
}
return [rootSequence as ValidSequence, pathMap];
})();
const [flatValues, flatPaths] = (() => {
const result: ThreadState<StateType>[] = [];
const flatPaths: Record<
string,
{ current: string[] | undefined; branches: string[][] | undefined }
> = {};
const forkStack = branchPath.slice();
const queue: (Node<StateType> | Fork<StateType>)[] = [...sequence.items];
while (queue.length > 0) {
const item = queue.shift()!;
if (item.type === "node") {
result.push(item.value);
flatPaths[item.value.checkpoint.checkpoint_id!] = {
current: item.path,
branches:
item.path.length > 0 ? pathMap[item.path.at(-2) ?? "$"] ?? [] : [],
};
}
if (item.type === "fork") {
const forkId = forkStack.shift();
const index =
forkId != null
? item.items.findIndex((value) => {
const firstItem = value.items.at(0);
if (!firstItem || firstItem.type !== "node") return false;
return firstItem.value.checkpoint.checkpoint_id === forkId;
})
: -1;
const nextItems = item.items.at(index)?.items ?? [];
queue.push(...nextItems);
}
}
return [result, flatPaths];
})();
const threadHead: ThreadState<StateType> | undefined = flatValues.at(-1);
const historyValues = threadHead?.values ?? ({} as StateType);
const historyError = (() => {
const error = threadHead?.tasks?.at(-1)?.error;
if (error == null) return undefined;
try {
const parsed = JSON.parse(error) as unknown;
if (StreamError.isStructuredError(parsed)) {
return new StreamError(parsed);
}
return parsed;
} catch {
// do nothing
}
return error;
})();
const messageMetadata = (() => {
if (getMessages == null) return undefined;
const alreadyShown = new Set<string>();
return getMessages(historyValues).map(
(message, idx): MessageMetadata<StateType> => {
const messageId = message.id ?? idx;
const firstSeenIdx = findLastIndex(history.data, (state) =>
getMessages(state.values)
.map((m, idx) => m.id ?? idx)
.includes(messageId),
);
const firstSeen = history.data[firstSeenIdx] as
| ThreadState<StateType>
| undefined;
let branch = firstSeen
? flatPaths[firstSeen.checkpoint.checkpoint_id!]
: undefined;
if (!branch?.current?.length) branch = undefined;
// serialize branches
const optionsShown = branch?.branches?.flat(2).join(",");
if (optionsShown) {
if (alreadyShown.has(optionsShown)) branch = undefined;
alreadyShown.add(optionsShown);
}
return {
messageId: messageId.toString(),
firstSeenState: firstSeen,
branch: branch?.current?.join(">"),
branchOptions: branch?.branches?.map((b) => b.join(">")),
};
},
);
})();
const stop = useCallback(() => {
if (abortRef.current != null) abortRef.current.abort();
abortRef.current = null;
}, []);
const submit = async (
values: UpdateType | undefined,
submitOptions?: {
config?: Config;
checkpoint?: Omit<Checkpoint, "thread_id"> | null;
command?: Command;
interruptBefore?: "*" | string[];
interruptAfter?: "*" | string[];
metadata?: Metadata;
multitaskStrategy?: MultitaskStrategy;
onCompletion?: OnCompletionBehavior;
onDisconnect?: DisconnectMode;
feedbackKeys?: string[];
streamMode?: Array<StreamMode>;
optimisticValues?:
| Partial<StateType>
| ((prev: StateType) => Partial<StateType>);
},
) => {
try {
setIsLoading(true);
setStreamError(undefined);
submittingRef.current = true;
abortRef.current = new AbortController();
let usableThreadId = threadId;
if (!usableThreadId) {
const thread = await client.threads.create();
options?.onThreadId?.(thread.thread_id);
usableThreadId = thread.thread_id;
}
const streamMode = unique([
...(submitOptions?.streamMode ?? []),
...trackStreamModeRef.current,
...callbackStreamMode,
]);
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
const run = (await client.runs.stream(usableThreadId, assistantId, {
input: values as Record<string, unknown>,
config: submitOptions?.config,
command: submitOptions?.command,
interruptBefore: submitOptions?.interruptBefore,
interruptAfter: submitOptions?.interruptAfter,
metadata: submitOptions?.metadata,
multitaskStrategy: submitOptions?.multitaskStrategy,
onCompletion: submitOptions?.onCompletion,
onDisconnect: submitOptions?.onDisconnect ?? "cancel",
signal: abortRef.current.signal,
checkpoint,
streamMode,
})) as AsyncGenerator<EventStreamEvent>;
// Unbranch things
const newPath = submitOptions?.checkpoint?.checkpoint_id
? flatPaths[submitOptions?.checkpoint?.checkpoint_id]?.current
: undefined;
if (newPath != null) setBranchPath(newPath ?? []);
// Assumption: we're setting the initial value
// Used for instant feedback
setStreamValues(() => {
const values = { ...historyValues };
if (submitOptions?.optimisticValues != null) {
return {
...values,
...(typeof submitOptions.optimisticValues === "function"
? submitOptions.optimisticValues(values)
: submitOptions.optimisticValues),
};
}
return values;
});
let streamError: StreamError | undefined;
for await (const { event, data } of run) {
setEvents((events) => [...events, { event, data } as EventStreamEvent]);
if (event === "error") {
streamError = new StreamError(data);
break;
}
if (event === "updates") {
options.onUpdateEvent?.(data);
}
if (event === "custom") {
options.onCustomEvent?.(data);
}
if (event === "metadata") {
options.onMetadataEvent?.(data);
}
if (event === "values") {
setStreamValues(data);
}
if (event === "messages") {
if (!getMessages) continue;
const [serialized] = data;
const messageId = messageManagerRef.current.add(serialized);
if (!messageId) {
console.warn(
"Failed to add message to manager, no message ID found",
);
continue;
}
setStreamValues((streamValues) => {
const values = { ...historyValues, ...streamValues };
// Assumption: we're concatenating the message
const messages = getMessages(values).slice();
const { chunk, index } =
messageManagerRef.current.get(messageId, messages.length) ?? {};
if (!chunk || index == null) return values;
messages[index] = toMessageDict(chunk);
return { ...values, [withMessages!]: messages };
});
}
}
// TODO: stream created checkpoints to avoid an unnecessary network request
const result = await history.mutate(usableThreadId);
// TODO: write tests verifying that stream values are properly handled lifecycle-wise
setStreamValues(null);
if (streamError != null) throw streamError;
const lastHead = result.at(0);
if (lastHead) onFinish?.(lastHead);
} catch (error) {
if (
!(
error instanceof Error &&
(error.name === "AbortError" || error.name === "TimeoutError")
)
) {
setStreamError(error);
onError?.(error);
}
} finally {
setIsLoading(false);
// Assumption: messages are already handled, we can clear the manager
messageManagerRef.current.clear();
submittingRef.current = false;
abortRef.current = null;
}
};
const error = isLoading ? streamError : historyError;
const values = streamValues ?? historyValues;
const setBranch = useCallback(
(path: string) => setBranchPath(path.split(">")),
[setBranchPath],
);
return {
get values() {
trackStreamMode("values");
return values;
},
error,
isLoading,
stop,
submit,
setBranch,
get messages() {
trackStreamMode("messages-tuple");
if (getMessages == null) {
throw new Error(
"No messages key provided. Make sure that `useStream` contains the `messagesKey` property.",
);
}
return getMessages(values);
},
getMessagesMetadata(
message: Message,
index?: number,
): MessageMetadata<StateType> | undefined {
trackStreamMode("messages-tuple");
if (getMessages == null) {
throw new Error(
"No messages key provided. Make sure that `useStream` contains the `messagesKey` property.",
);
}
return messageMetadata?.find(
(m) => m.messageId === (message.id ?? index),
);
},
};
}
+4 -13
View File
@@ -8,19 +8,10 @@ type MessageContentText = { type: "text"; text: string };
type MessageContentComplex = MessageContentText | MessageContentImageUrl;
type MessageContent = string | MessageContentComplex[];
type MessageAdditionalKwargs = {
[x: string]: unknown;
function_call?: { arguments: string; name: string } | undefined;
tool_calls?:
| {
id: string;
function: { arguments: string; name: string };
type: "function";
index?: number | undefined;
}[]
| undefined;
};
/**
* Model-specific additional kwargs, which is passed back to the underlying LLM.
*/
type MessageAdditionalKwargs = Record<string, unknown>;
export type HumanMessage = {
type: "human";
+1 -1
View File
@@ -26,7 +26,7 @@ export interface Command {
/**
* An object to update the thread state with.
*/
update?: Record<string, unknown> | [string, unknown][];
update?: Record<string, unknown> | [string, unknown][] | null;
/**
* The value to return from an `interrupt` function call.
+1
View File
@@ -19,6 +19,7 @@
"strictPropertyInitialization": false,
"allowJs": true,
"strict": true,
"jsx": "react-jsx",
"outDir": "dist"
},
"include": [
+125 -8
View File
@@ -296,6 +296,11 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@cfworker/json-schema@^4.0.2":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz#4a2a3947ee9fa7b7c24be981422831b8674c3be6"
integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -548,6 +553,24 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@langchain/core@^0.3.31":
version "0.3.39"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.39.tgz#81002e0cacacb7c1011264d6612f923816a3965f"
integrity sha512-muXs4asy1A7qDtcdznxqyBfxf4N6qxofY/S0c95vbsWa0r9YAE2PttHIjcuxSy1q2jUiTkpCcgFEjNJRQRVhEw==
dependencies:
"@cfworker/json-schema" "^4.0.2"
ansi-styles "^5.0.0"
camelcase "6"
decamelize "1.2.0"
js-tiktoken "^1.0.12"
langsmith ">=0.2.8 <0.4.0"
mustache "^4.2.0"
p-queue "^6.6.2"
p-retry "4"
uuid "^10.0.0"
zod "^3.22.4"
zod-to-json-schema "^3.22.3"
"@langchain/scripts@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@langchain/scripts/-/scripts-0.1.4.tgz#8c5d03d627686f20b9522213c12e2b329e73e381"
@@ -950,6 +973,19 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==
"@types/prop-types@*":
version "15.7.14"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2"
integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
"@types/react@18.3.2":
version "18.3.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.2.tgz#462ae4904973bc212fa910424d901e3d137dbfcd"
integrity sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/retry@0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@@ -965,6 +1001,11 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc"
integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==
"@types/uuid@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d"
integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==
"@types/uuid@^9.0.1":
version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
@@ -1181,6 +1222,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
before-after-hook@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d"
@@ -1262,16 +1308,16 @@ camelcase-keys@^6.2.2:
map-obj "^4.0.0"
quick-lru "^4.0.1"
camelcase@6, camelcase@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001688:
version "1.0.30001692"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9"
@@ -1291,7 +1337,7 @@ chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.0.2:
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1406,6 +1452,13 @@ concat-md@^0.5.1:
meow "^9.0.0"
transform-markdown-links "^2.0.0"
console-table-printer@^2.12.1:
version "2.12.1"
resolved "https://registry.yarnpkg.com/console-table-printer/-/console-table-printer-2.12.1.tgz#4a9646537a246a6d8de57075d4fae1e08abae267"
integrity sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==
dependencies:
simple-wcswidth "^1.0.1"
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
@@ -1442,6 +1495,11 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
data-view-buffer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
@@ -1491,7 +1549,7 @@ decamelize-keys@^1.1.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
decamelize@^1.1.0, decamelize@^1.2.0:
decamelize@1.2.0, decamelize@^1.1.0, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
@@ -2794,7 +2852,14 @@ jest@^29.7.0:
import-local "^3.0.2"
jest-cli "^29.7.0"
js-tokens@^4.0.0:
js-tiktoken@^1.0.12:
version "1.0.18"
resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.18.tgz#aaf68dda155bc693e6f0a572b9a359d569cb53df"
integrity sha512-hFYx4xYf6URgcttcGvGuOBJhTxPYZ2R5eIesqCaNRJmYH8sNmsfTeWg4yu//7u1VD/qIUkgKJTpGom9oHXmB4g==
dependencies:
base64-js "^1.5.1"
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@@ -2832,6 +2897,19 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
"langsmith@>=0.2.8 <0.4.0":
version "0.3.7"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.3.7.tgz#c29362f78ea2872252a60a680d6adb8b67e18b74"
integrity sha512-wakN1hxGkm1JR2PpAV7fiT7oC99LKcgxiuUrYGZWPbuj7Y8EPF19F7VNr4B+hA219bfaeWTa4Lxy2YrtPSKnQA==
dependencies:
"@types/uuid" "^10.0.0"
chalk "^4.1.2"
console-table-printer "^2.12.1"
p-queue "^6.6.2"
p-retry "4"
semver "^7.6.3"
uuid "^10.0.0"
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -2876,6 +2954,13 @@ longest-streak@^2.0.0:
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^10.2.0:
version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
@@ -3234,6 +3319,11 @@ ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
mustache@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -3518,6 +3608,13 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
read-pkg-up@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507"
@@ -3787,6 +3884,11 @@ signal-exit@^4.0.1:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
simple-wcswidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz#8ab18ac0ae342f9d9b629604e54d2aa1ecb018b2"
integrity sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -4230,6 +4332,11 @@ update-section@^0.3.3:
resolved "https://registry.yarnpkg.com/update-section/-/update-section-0.3.3.tgz#458f17820d37820dc60e20b86d94391b00123158"
integrity sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==
uuid@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
uuid@^9.0.0:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
@@ -4394,6 +4501,16 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod-to-json-schema@^3.22.3:
version "3.24.1"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz#f08c6725091aadabffa820ba8d50c7ab527f227a"
integrity sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==
zod@^3.22.4:
version "3.24.1"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee"
integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==
zwitch@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"