From 25f88740c18855b6fff7ba65f8daffe2e248f1ec Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sat, 8 Feb 2025 20:36:42 -0800 Subject: [PATCH] feat(sdk-js): add experimental useStream hook --- libs/sdk-js/.gitignore | 4 + libs/sdk-js/langchain.config.js | 4 +- libs/sdk-js/package.json | 25 +- libs/sdk-js/src/react/index.ts | 1 + libs/sdk-js/src/react/stream.tsx | 559 +++++++++++++++++++++++++++++++ libs/sdk-js/tsconfig.json | 1 + libs/sdk-js/yarn.lock | 133 +++++++- 7 files changed, 715 insertions(+), 12 deletions(-) create mode 100644 libs/sdk-js/src/react/index.ts create mode 100644 libs/sdk-js/src/react/stream.tsx diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index fd0d01027..4d7124e5f 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -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 diff --git a/libs/sdk-js/langchain.config.js b/libs/sdk-js/langchain.config.js index eeb79ab47..b644f4b3c 100644 --- a/libs/sdk-js/langchain.config.js +++ b/libs/sdk-js/langchain.config.js @@ -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", diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index aae3d0219..1181feaf8 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -33,7 +33,15 @@ "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", + "@langchain/core": "^0.3.31", + "@types/react": "18.3.2" + }, + "peerDependencies": { + "react": "*", + "@types/react": "*", + "@langchain/core": "*" }, "exports": { ".": { @@ -54,6 +62,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 +82,10 @@ "client.cjs", "client.js", "client.d.ts", - "client.d.cts" + "client.d.cts", + "react.cjs", + "react.js", + "react.d.ts", + "react.d.cts" ] } diff --git a/libs/sdk-js/src/react/index.ts b/libs/sdk-js/src/react/index.ts new file mode 100644 index 000000000..7fc550ada --- /dev/null +++ b/libs/sdk-js/src/react/index.ts @@ -0,0 +1 @@ +export { useStream, LangGraphConfig } from "./stream.js"; diff --git a/libs/sdk-js/src/react/stream.tsx b/libs/sdk-js/src/react/stream.tsx new file mode 100644 index 000000000..8b5b84562 --- /dev/null +++ b/libs/sdk-js/src/react/stream.tsx @@ -0,0 +1,559 @@ +/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */ +"use client"; + +import { Client } from "../client.js"; +import type { Command } from "../types.js"; +import type { Message } from "../types.messages.js"; +import type { Config, ThreadState } from "../schema.js"; +import type { + CustomPayload, + DebugPayload, + EventsPayload, + MessagesPayload, + MessagesTuplePayload, + UpdatesPayload, + ValuesPayload, +} from "../types.stream.js"; + +import { + type MutableRefObject, + type ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + type BaseMessageChunk, + type BaseMessage, + coerceMessageLikeToMessage, + convertToChunk, +} from "@langchain/core/messages"; + +class MessageTupleManager { + chunks: Record = {}; + + 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(array: T[]) { + return [...new Set(array)] as T[]; +} + +function findLastIndex(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 { + type: "node"; + value: ThreadState; + path: string[]; +} + +interface Fork { + type: "fork"; + items: Array>; +} + +interface Sequence { + type: "sequence"; + items: Array | Fork>; +} + +interface ValidFork { + type: "fork"; + items: Array>; +} + +interface ValidSequence { + type: "sequence"; + items: [Node, ...(Node | ValidFork)[]]; +} + +// forks +export type CheckpointBranchPath = string[]; + +export type MessageBranch = { + current: CheckpointBranchPath; + options: CheckpointBranchPath[]; +}; + +const mergeConfig = (...configs: (Config | undefined)[]) => { + const result: Config = { configurable: {} }; + + for (const config of configs) { + if (config == null) continue; + // TODO: Just assigning the latest configurable is iffy. + result.configurable = config.configurable; + } + + return result; +}; + +function fetchHistory>( + client: Client, + threadId: string, +) { + return client.threads.getHistory(threadId, { limit: 1000 }); +} + +function useThreadHistory>( + threadId: string | undefined | null, + client: Client, + submittingRef: MutableRefObject, +) { + const [history, setHistory] = useState[]>([]); + + const fetcher = useCallback( + (threadId: string | undefined | null): Promise => { + if (threadId != null) { + return fetchHistory(client, threadId).then((history) => + setHistory(history), + ); + } + + setHistory([]); + return Promise.resolve(); + }, + [], + ); + + useEffect(() => { + if (submittingRef.current) return; + fetcher(threadId); + }, [fetcher, submittingRef, threadId]); + + return { + data: history, + mutate: (mutateId?: string) => fetcher(mutateId ?? threadId), + }; +} + +interface LangGraphConfig { + withMessages?: string; + onError?: (error: unknown) => void; + client?: Client; +} + +const ConfigProvider = createContext(null!); +export const LangGraphConfig = (props: { + config: LangGraphConfig; + children?: ReactNode; +}) => { + return ( + + {props.children} + + ); +}; + +export function useStream< + StateType extends Record = Record, + UpdateType extends Record = Partial, + CustomType = unknown, +>(options?: { + client?: Client; + + withMessages?: string; + onError?: (error: unknown) => void; + + // TODO: can we make threadId uncontrollable / controllable? + threadId?: string | null; + onThreadId?: (threadId: string) => void; +}) { + type EventPayload = + | ValuesPayload + | UpdatesPayload + | CustomPayload + | DebugPayload + | MessagesPayload + | MessagesTuplePayload + | EventsPayload; + + const contextConfig = useContext(ConfigProvider); + const { withMessages, onError, threadId, client } = Object.assign( + {}, + contextConfig, + options, + ); + + if (client == null) { + throw new Error( + "LangGraph SDK not provided. Either pass a client to `useStream` or wrap your app in a `LangGraphConfig` provider and pass the client there.", + ); + } + + const [branchPath, setBranchPath] = useState([]); + + const [error, setError] = useState(undefined); + const [events, setEvents] = useState([]); + + const [streamValues, setStreamValues] = useState(null); + + const [streamMode, setStreamMode] = useState< + Array<"values" | "updates" | "events" | "custom" | "messages-tuple"> + >(["values", "messages-tuple"]); + + const manager = useRef(new MessageTupleManager()); + const submittingRef = useRef(false); + + // TODO: is this a responsibility of SWR / React Query? + // Maybe we should use that instead to allow rehydration? + const history = useThreadHistory(threadId, client, 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[]> = {}; + + // 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(); + 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 = {}; + for (const path of paths) { + const parent = path.at(-2) ?? "$"; + pathMap[parent] ??= []; + pathMap[parent].unshift(path); + } + + return [rootSequence as ValidSequence, pathMap]; + })(); + + const [flatValues, checkpointPathMap] = (() => { + const result: ThreadState[] = []; + + // TODO: this is kinda ugly + const checkpointPathMap: Record = {}; + + const forkStack = branchPath.slice(); + const queue: (Node | Fork)[] = [...sequence.items]; + + while (queue.length > 0) { + const item = queue.shift()!; + + if (item.type === "node") { + result.push(item.value); + checkpointPathMap[item.value.checkpoint.checkpoint_id!] = { + current: item.path, + options: + 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, checkpointPathMap]; + })(); + + const lastSeenValue = flatValues.at(-1); + const historyValues = lastSeenValue?.values ?? ({} as StateType); + + const messageMeta = (() => { + if (getMessages == null) return undefined; + + const alreadyShown = new Set(); + return getMessages(historyValues).map((message, idx) => { + const messageId = message.id ?? idx; + const firstSeenIdx = findLastIndex(history.data, (state) => + getMessages(state.values) + .map((m, idx) => m.id ?? idx) + .includes(messageId), + ); + + const firstState = history.data[firstSeenIdx] as + | ThreadState + | undefined; + + let branch = firstState + ? checkpointPathMap[firstState.checkpoint.checkpoint_id!] + : undefined; + if (!branch?.current.length) branch = undefined; + + const optionsShown = branch?.options?.flat(2).join(","); + if (optionsShown) { + if (alreadyShown.has(optionsShown)) branch = undefined; + alreadyShown.add(optionsShown); + } + + return { messageId, firstState, branch }; + }); + })(); + + const handleSubmit = async ( + values: UpdateType | undefined, + submitOptions?: { + config?: Config; + command?: Command; + optimisticValues?: + | Partial + | ((prev: StateType) => Partial); + }, + ) => { + try { + // TODO: have loading state as well + submittingRef.current = true; + + // This is used to reset the path to make sure we always fetch the + // latest generation / edit. + // TODO: make sure it's actually aware of the config passed to handleSubmit + setBranchPath((path) => path.slice(0, -1)); + + let usableThreadId = threadId; + if (!usableThreadId) { + const thread = await client.threads.create(); + options?.onThreadId?.(thread.thread_id); + usableThreadId = thread.thread_id; + } + + // TODO: why non-existent assistant ID does not throw an error here? + const run = (await client.runs.stream(usableThreadId, "agent", { + input: values as Record, + config: mergeConfig( + { configurable: lastSeenValue?.checkpoint }, + submitOptions?.config, + ), + streamMode, + })) as AsyncGenerator; + + // Assumption: we're setting the initial value + // Used for instant feedback + if (submitOptions?.optimisticValues != null) { + setStreamValues((streamValues) => { + const values = { ...historyValues, ...streamValues }; + return { + ...values, + ...(typeof submitOptions.optimisticValues === "function" + ? submitOptions.optimisticValues(values) + : submitOptions.optimisticValues), + }; + }); + } + + for await (const { event, data } of run) { + setEvents((events) => [...events, { event, data } as EventPayload]); + + if (event === "values") { + setStreamValues(data); + } else if (event === "messages") { + if (!getMessages) continue; + + const [serialized] = data; + + const messageId = manager.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 concating the message + const messages = getMessages(values).slice(); + const { chunk, index } = + manager.current.get(messageId, messages.length) ?? {}; + + if (!chunk || index == null) return values; + messages[index] = toMessageDict(chunk); + + return { ...values, [withMessages!]: messages }; + }); + } + } + + // TODO: add a "checkpoint" stream mode to get the branches directly + await history.mutate(usableThreadId); + setStreamValues(null); + } catch (error) { + setError(error); + onError?.(error); + } finally { + // Assumption: messages are already handled, we can clear the manager + manager.current.clear(); + submittingRef.current = false; + } + }; + + const values = streamValues ?? historyValues; + const stream = { + get custom() { + if (!streamMode.includes("custom")) { + setStreamMode((mode) => unique([...mode, "custom"])); + } + + return events + .filter((item) => item.event === "custom") + .map(({ data }) => data as CustomType); + }, + + get events() { + if (!streamMode.includes("events")) { + setStreamMode((mode) => unique([...mode, "events"])); + } + + return events; + }, + get updates() { + if (!streamMode.includes("updates")) { + setStreamMode((mode) => unique([...mode, "updates"])); + } + + return events + .filter( + (item): item is UpdatesPayload => + item.event === "updates", + ) + .map(({ data }) => data); + }, + }; + + return { + error, + handleSubmit, + setBranchPath, + + sequence, + stream, + + get messages() { + if (!streamMode.includes("messages-tuple")) { + setStreamMode((mode) => unique([...mode, "messages-tuple"])); + } + + if (getMessages == null) { + throw new Error( + "No messages key provided. Make sure that `useStream` contains the `messagesKey` property.", + ); + } + + return getMessages(values); + }, + + getMessagesMeta(message: Message, index?: number) { + if (!streamMode.includes("messages-tuple")) { + setStreamMode((mode) => unique([...mode, "messages-tuple"])); + } + + if (getMessages == null) { + throw new Error( + "No messages key provided. Make sure that `useStream` contains the `messagesKey` property.", + ); + } + + return messageMeta?.find((m) => m.messageId === (message.id ?? index)); + }, + + get values() { + if (!streamMode.includes("values")) { + setStreamMode((mode) => unique([...mode, "values"])); + } + + return values; + }, + }; +} diff --git a/libs/sdk-js/tsconfig.json b/libs/sdk-js/tsconfig.json index f72c108ed..9c6561a09 100644 --- a/libs/sdk-js/tsconfig.json +++ b/libs/sdk-js/tsconfig.json @@ -19,6 +19,7 @@ "strictPropertyInitialization": false, "allowJs": true, "strict": true, + "jsx": "react-jsx", "outDir": "dist" }, "include": [ diff --git a/libs/sdk-js/yarn.lock b/libs/sdk-js/yarn.lock index fbde16b49..4d4e4ccf8 100644 --- a/libs/sdk-js/yarn.lock +++ b/libs/sdk-js/yarn.lock @@ -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"