diff --git a/.changeset/healthy-bears-drum.md b/.changeset/healthy-bears-drum.md new file mode 100644 index 000000000..7d2292718 --- /dev/null +++ b/.changeset/healthy-bears-drum.md @@ -0,0 +1,5 @@ +--- +"syncia": minor +--- + +Adds webpage context diff --git a/artifacts/chrome.zip b/artifacts/chrome.zip deleted file mode 100644 index a7330f34c..000000000 Binary files a/artifacts/chrome.zip and /dev/null differ diff --git a/package.json b/package.json index 425c55b2a..d7388f3c9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dnd-kit-sortable-tree": "^0.1.58", "endent": "^2.1.0", "jotai": "^2.4.3", - "langchain": "^0.0.151", + "langchain": "^0.0.197-rc.1", "object-hash": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -54,13 +54,13 @@ "@crxjs/vite-plugin": "^2.0.0-beta.15", "@types/chrome": "^0.0.213", "@types/node": "^18.13.0", - "dotenv-cli": "^7.3.0", "@types/object-hash": "^3.0.2", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/react-syntax-highlighter": "^15.5.6", "@vitejs/plugin-react": "^3.1.0", "autoprefixer": "^10.4.13", + "dotenv-cli": "^7.3.0", "postcss": "^8.4.21", "publish-browser-extension": "^1.4.1", "rome": "12.0.0", diff --git a/src/components/Settings/Sections/ChatSettings.tsx b/src/components/Settings/Sections/ChatSettings.tsx index 75faef905..52b0e0442 100644 --- a/src/components/Settings/Sections/ChatSettings.tsx +++ b/src/components/Settings/Sections/ChatSettings.tsx @@ -2,7 +2,7 @@ import React from 'react' import SectionHeading from '../Elements/SectionHeading' import FieldWrapper from '../Elements/FieldWrapper' import { useSettings } from '../../../hooks/useSettings' -import { validateApiKey } from '../../../utils/validApiKey' +import { validateApiKey } from '../../../lib/validApiKey' import { AvailableModels, Mode } from '../../../config/settings' const ChatSettings = () => { diff --git a/src/components/Settings/Sections/GeneralSettings.tsx b/src/components/Settings/Sections/GeneralSettings.tsx index c93998ee6..4e2769098 100644 --- a/src/components/Settings/Sections/GeneralSettings.tsx +++ b/src/components/Settings/Sections/GeneralSettings.tsx @@ -3,6 +3,7 @@ import SectionHeading from '../Elements/SectionHeading' import FieldWrapper from '../Elements/FieldWrapper' import { useSettings } from '../../../hooks/useSettings' import { ThemeOptions } from '../../../config/settings' +import * as Switch from '@radix-ui/react-switch' const GeneralSettings = () => { const [settings, setSettings] = useSettings() @@ -40,6 +41,27 @@ const GeneralSettings = () => { ))} + + + setSettings({ + ...settings, + general: { + ...generalSettings, + webpageContext: value, + }, + }) + } + className="cdx-w-[42px] cdx-h-[25px] cdx-bg-neutral-500 cdx-rounded-full cdx-relative data-[state=checked]:cdx-bg-blue-500 cdx-outline-none cdx-cursor-default" + > + + + ) } diff --git a/src/components/Sidebar/auth/index.tsx b/src/components/Sidebar/auth/index.tsx index a68c8a258..b03595cd6 100644 --- a/src/components/Sidebar/auth/index.tsx +++ b/src/components/Sidebar/auth/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react' import { useSettings } from '../../../hooks/useSettings' -import { validateApiKey } from '../../../utils/validApiKey' +import { validateApiKey } from '../../../lib/validApiKey' const Auth = () => { const [, setSettings] = useSettings() diff --git a/src/components/Sidebar/chat/ChatHistory.tsx b/src/components/Sidebar/chat/ChatHistory.tsx index a32ecc0c7..4ef0f6c6f 100644 --- a/src/components/Sidebar/chat/ChatHistory.tsx +++ b/src/components/Sidebar/chat/ChatHistory.tsx @@ -1,7 +1,7 @@ import { useChatHistory } from '../../../hooks/useChatHistory' import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import { RiAddLine, RiCloseCircleFill, RiTimeLine } from 'react-icons/ri' -import { generateReadableRelativeDate } from '../../../utils/generateReadableDate' +import { generateReadableRelativeDate } from '../../../lib/generateReadableDate' const ChatHistory = () => { const { diff --git a/src/components/Sidebar/chat/ChatInput.tsx b/src/components/Sidebar/chat/ChatInput.tsx index 6979bf61f..9243c4301 100644 --- a/src/components/Sidebar/chat/ChatInput.tsx +++ b/src/components/Sidebar/chat/ChatInput.tsx @@ -5,21 +5,26 @@ import { IoSend } from 'react-icons/io5' import { HiHand } from 'react-icons/hi' import ChatHistory from './ChatHistory' import { useChatHistory } from '../../../hooks/useChatHistory' +import WebPageContentToggle from './WebPageContentToggle' interface SidebarInputProps { loading: boolean - submitMessage: (prompt: string) => void + submitMessage: (message: string, context?: string) => void clearMessages: () => void chatIsEmpty: boolean cancelRequest: () => void + isWebpageContextOn: boolean } +const MAX_MESSAGE_LENGTH = 10000 + export function SidebarInput({ loading, submitMessage, clearMessages, chatIsEmpty, cancelRequest, + isWebpageContextOn, }: SidebarInputProps) { const [text, setText] = useState('') const [delayedLoading, setDelayedLoading] = useState(false) @@ -34,11 +39,45 @@ export function SidebarInput({ } }, [loading]) - const handleSubmit = () => { - submitMessage(text) + const handleSubmit = async () => { + let context + if (isWebpageContextOn) { + const pageContent = new Promise((resolve) => { + window.addEventListener('message', function (event) { + if (event.data.action === 'get-page-content') { + resolve(event.data.pageContent) + } + }) + + window.parent.postMessage({ action: 'get-page-content' }, '*') + }) + context = (await pageContent) as string + } + submitMessage(text, isWebpageContextOn ? context : undefined) setText('') } + const sendButton = ( + + ) + + const stopButton = ( + + ) + return (
@@ -59,7 +98,7 @@ export function SidebarInput({
-
- - {text.length} / 10,000 - + + {text.length.toLocaleString()} /{' '} + {MAX_MESSAGE_LENGTH.toLocaleString()} + +
+ + {!delayedLoading ? sendButton : stopButton}
- {!delayedLoading ? ( - - ) : ( - - )}
diff --git a/src/components/Sidebar/chat/WebPageContentToggle.tsx b/src/components/Sidebar/chat/WebPageContentToggle.tsx new file mode 100644 index 000000000..c8441bbe3 --- /dev/null +++ b/src/components/Sidebar/chat/WebPageContentToggle.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { useSettings } from '../../../hooks/useSettings' +import * as Switch from '@radix-ui/react-switch' + +const WebPageContentToggle = () => { + const [settings, setSettings] = useSettings() + return ( +
+ + setSettings({ + ...settings, + general: { + ...settings.general, + webpageContext: value, + }, + }) + } + className="cdx-w-[28px] cdx-h-[16px] cdx-bg-neutral-500 cdx-rounded-full cdx-relative data-[state=checked]:cdx-bg-blue-500 cdx-outline-none cdx-cursor-default" + > + + + +
+ ) +} + +export default WebPageContentToggle diff --git a/src/components/Sidebar/chat/index.tsx b/src/components/Sidebar/chat/index.tsx index 6997d7143..ee69ca2c8 100644 --- a/src/components/Sidebar/chat/index.tsx +++ b/src/components/Sidebar/chat/index.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import React, { useEffect } from 'react' import ChatList from './ChatList' import { SidebarInput } from './ChatInput' import { useChatCompletion } from '../../../hooks/useChatCompletion' @@ -33,7 +33,7 @@ const Chat = ({ settings }: ChatProps) => { return () => { window.removeEventListener('message', handleWindowMessage) } - }, []) + }, [submitQuery]) return ( <> @@ -44,6 +44,7 @@ const Chat = ({ settings }: ChatProps) => { chatIsEmpty={messages.length <= 1} clearMessages={clearMessages} cancelRequest={cancelRequest} + isWebpageContextOn={settings.general.webpageContext} /> ) diff --git a/src/config/settings/index.ts b/src/config/settings/index.ts index 4330967f4..c335af5ba 100644 --- a/src/config/settings/index.ts +++ b/src/config/settings/index.ts @@ -35,6 +35,7 @@ export type Settings = { } general: { theme: ThemeOptions + webpageContext: boolean } } @@ -51,5 +52,6 @@ export const defaultSettings: Settings = { }, general: { theme: ThemeOptions.SYSTEM, + webpageContext: true, }, } diff --git a/src/hooks/useChatCompletion.ts b/src/hooks/useChatCompletion.ts index 852dfd270..49e7b3697 100644 --- a/src/hooks/useChatCompletion.ts +++ b/src/hooks/useChatCompletion.ts @@ -1,9 +1,10 @@ -import { AvailableModels, Mode } from '../config/settings' +import endent from 'endent' import { ChatOpenAI } from 'langchain/chat_models/openai' -import { useCurrentChat, ChatRole } from './useCurrentChat' -import { useMemo } from 'react' import { AIMessage, HumanMessage, SystemMessage } from 'langchain/schema' -import { useState } from 'react' +import { useMemo, useState } from 'react' +import { AvailableModels, Mode } from '../config/settings' +import { ChatRole, useCurrentChat } from './useCurrentChat' +import { getMatchedContent } from '../lib/getMatchedContent' interface UseChatCompletionProps { model: AvailableModels @@ -37,7 +38,7 @@ export const useChatCompletion = ({ } = useCurrentChat() const [generating, setGenerating] = useState(false) - const chat = useMemo( + const llm = useMemo( () => new ChatOpenAI({ streaming: true, @@ -45,7 +46,7 @@ export const useChatCompletion = ({ modelName: model, temperature: Number(mode), }), - [], + [apiKey, model, mode], ) const previousMessages = messages.map((msg) => { @@ -61,22 +62,43 @@ export const useChatCompletion = ({ const controller = new AbortController() - const submitQuery = async (query: string) => { + const submitQuery = async (query: string, context?: string) => { await addNewMessage(ChatRole.USER, query) - const messages = [ - new SystemMessage(systemPrompt), - ...previousMessages, - new HumanMessage(query), - ] const options = { signal: controller.signal, callbacks: [{ handleLLMNewToken: updateAssistantMessage }], } + setGenerating(true) - chat.call(messages, options).then(() => { - commitToStoredMessages() - setGenerating(false) - }) + + /** + * If context is provided, we need to use the LLM to get the relevant documents + * and then run the LLM on those documents. We use in memory vector store to + * get the relevant documents + */ + let matchedContext + if (context) { + matchedContext = await getMatchedContent(query, context, apiKey) + } + + const expandedQuery = matchedContext + ? endent` + ### Context + ${matchedContext} + ### Question: + ${query} + ` + : query + + const messages = [ + new SystemMessage(systemPrompt), + ...previousMessages, + new HumanMessage(expandedQuery), + ] + + await llm.call(messages, options) + commitToStoredMessages() + setGenerating(false) } const cancelRequest = () => { diff --git a/src/hooks/useStorage.ts b/src/hooks/useStorage.ts index b3f3a5a02..80061d888 100644 --- a/src/hooks/useStorage.ts +++ b/src/hooks/useStorage.ts @@ -18,6 +18,7 @@ export function useStorage( ): [T, SetValue] { const [storedValue, setStoredValue] = useAtom(atom) + // biome-ignore lint/correctness/useExhaustiveDependencies: This works fine. i don't want to change it. useEffect(() => { readStorage(key, area).then((res) => { if (res) setStoredValue(res) diff --git a/src/lib/createSHA256Hash.ts b/src/lib/createSHA256Hash.ts new file mode 100644 index 000000000..982aa1135 --- /dev/null +++ b/src/lib/createSHA256Hash.ts @@ -0,0 +1,10 @@ +export const createSHA256Hash = async (content: string): Promise => { + const encoder = new TextEncoder() + const data = encoder.encode(content) + const hashBuffer = await crypto.subtle.digest('SHA-256', data) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hashHex = hashArray + .map((byte) => byte.toString(16).padStart(2, '0')) + .join('') + return hashHex +} diff --git a/src/utils/generateReadableDate.ts b/src/lib/generateReadableDate.ts similarity index 100% rename from src/utils/generateReadableDate.ts rename to src/lib/generateReadableDate.ts diff --git a/src/lib/getMatchedContent.ts b/src/lib/getMatchedContent.ts new file mode 100644 index 000000000..e0110c41d --- /dev/null +++ b/src/lib/getMatchedContent.ts @@ -0,0 +1,48 @@ +import { OpenAIEmbeddings } from 'langchain/embeddings/openai' +import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter' +import { MemoryVectorStore } from 'langchain/vectorstores/memory' +import { createSHA256Hash } from './createSHA256Hash' + +/** + * This function is responsible for getting the matched content + * from the context and query + */ +export const getMatchedContent = async ( + query: string, + context: string, + apiKey: string, +) => { + const vectorStore = await getContextVectorStore(context, apiKey) + const retriever = vectorStore.asRetriever() + const relevantDocs = await retriever.getRelevantDocuments(query) + return relevantDocs.map((doc) => doc.pageContent).join('\n') +} + +/** + * This function is responsible for getting the context vector store + * from the context. It caches the vector store in the local storage + * for faster retrieval + */ +const getContextVectorStore = async (context: string, apiKey: string) => { + const embeddings = new OpenAIEmbeddings({ openAIApiKey: apiKey }) + const hashKey = `SYNCIA_STORE_EMBEDDINGS_${await createSHA256Hash(context)}` + const memoryVectors: [] | null = JSON.parse( + localStorage.getItem(hashKey) || 'null', + ) + + if (!memoryVectors) { + const textSplitter = new RecursiveCharacterTextSplitter({ + chunkSize: 1000, + }) + const docs = await textSplitter.createDocuments([context]) + const store = await MemoryVectorStore.fromDocuments(docs, embeddings) + localStorage.setItem(hashKey, JSON.stringify(store.memoryVectors)) + return store + } + + console.log({ memoryVectors }) + + const store = new MemoryVectorStore(embeddings) + store.memoryVectors = memoryVectors + return store +} diff --git a/src/utils/validApiKey.ts b/src/lib/validApiKey.ts similarity index 100% rename from src/utils/validApiKey.ts rename to src/lib/validApiKey.ts diff --git a/src/pages/background/sidebar/sendSidebarShortcut.ts b/src/pages/background/sidebar/sendSidebarShortcut.ts index 78eb9e229..01f5e3e4e 100644 --- a/src/pages/background/sidebar/sendSidebarShortcut.ts +++ b/src/pages/background/sidebar/sendSidebarShortcut.ts @@ -12,11 +12,17 @@ export const sendSidebarShortcut = () => { // Send shortcut to client chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { - if (tabs[0].id) - chrome.tabs.sendMessage(tabs[0].id, { - action: 'sidebar-shortcut', - shortcut, + if (tabs[0].id) { + chrome.tabs.onUpdated.addListener(function listener(tabId, info) { + if (info.status === 'complete' && tabId === tabs[0].id) { + chrome.tabs.sendMessage(tabs[0].id, { + action: 'sidebar-shortcut', + shortcut, + }) + chrome.tabs.onUpdated.removeListener(listener) + } }) + } }) }) } diff --git a/src/pages/content/sidebar.tsx b/src/pages/content/sidebar.tsx index f717f853f..53525c3ad 100644 --- a/src/pages/content/sidebar.tsx +++ b/src/pages/content/sidebar.tsx @@ -17,6 +17,12 @@ iframe.id = 'syncia_sidebar' document.body.appendChild(iframe) +/** + * BG SCRIPT <-> CONTENT SCRIPT + * Event listener for messages from the background script. + * To open the sidebar, the background script sends a message with the action 'open-sidebar'. + * The sidebar is opened by setting the width of the iframe to 400px. + */ chrome.runtime.onMessage.addListener(function (msg) { if (msg.action === 'open-sidebar') { if (iframe.style.width === '0px') { @@ -26,3 +32,25 @@ chrome.runtime.onMessage.addListener(function (msg) { } } }) + +/** + * SIDEBAR <-> CONTENT SCRIPT + * Event listener for messages from the sidebar. + * To get the page content, the sidebar sends a message with the action 'get-page-content'. + * The page content is sent back to the sidebar by posting a message with the action 'get-page-content'. + */ +window.addEventListener('message', (event) => { + const { action, _payload } = event.data as { action: string; _payload: any } + + if (action === 'get-page-content') { + console.log('get-page-content Triggered') + const pageContent = document.body.innerText + iframe.contentWindow?.postMessage( + { + action: 'get-page-content', + pageContent, + }, + '*', + ) + } +}) diff --git a/yarn.lock b/yarn.lock index 5c7fc339e..4e1d6e5ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,10 +15,10 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@anthropic-ai/sdk@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.6.2.tgz#4be415e6b1d948df6f8e03af84aedf102ec74b70" - integrity sha512-fB9PUj9RFT+XjkL+E9Ol864ZIJi+1P8WnbHspN3N3/GK2uSzjd0cbVIKTGgf4v3N8MwaQu+UWnU7C4BG/fap/g== +"@anthropic-ai/sdk@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.10.0.tgz#df21d266520b6b439e39ab4853a2ee787239d85c" + integrity sha512-OqTbjjZzDjkCSe0RcNTNeVrHLrC3RUzUvTJB/qt6seVaETnLkKt6b4hkOnkhkXnBHH8+w20zmB4Ekcwir2B/sw== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" @@ -28,6 +28,7 @@ form-data-encoder "1.7.2" formdata-node "^4.3.2" node-fetch "^2.6.7" + web-streams-polyfill "^3.2.1" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": version "7.21.4" @@ -815,6 +816,39 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@langchain/anthropic@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-0.0.3.tgz#a0d365971895f838cf1ec528010eacd35fff6eca" + integrity sha512-iSp0S7h02cUS1ClyrKRBKqp7E8y1qjjZg5Tu5HLTOrRRBDgi22P7Jph1BNB8hazX/+YotDCTlChaBoolhsspXQ== + dependencies: + "@anthropic-ai/sdk" "^0.10.0" + "@langchain/core" "~0.0.1" + +"@langchain/core@^0.0.1", "@langchain/core@~0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.0.1.tgz#1a835016bfd9c6b196e317d5118775fe93b262a0" + integrity sha512-VgpKcfvtC+oN5ZfCX1k05fXmMs87/U0j4KzOiGbgIjwO9xfE8bIMesodinDQO1H5ohF1SEDE1tSeVXz/bTuang== + dependencies: + ansi-styles "^5.0.0" + camelcase "6" + decamelize "1.2.0" + js-tiktoken "^1.0.7" + langsmith "^0.0.48" + p-queue "^6.6.2" + p-retry "4" + uuid "^9.0.0" + zod "^3.22.3" + +"@langchain/openai@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.1.tgz#a6e36c757abdbad6cacf2022831d4d627f86cf6e" + integrity sha512-AYmsaiI4Vh1q1llGu1+P77oDqeSuSLavUzaaT25xJtCLl0o/2mqF6BQ1Wa0/HJ1+vOKxRDvCk9jhB4RjJSkuTg== + dependencies: + "@langchain/core" "~0.0.1" + js-tiktoken "^1.0.7" + openai "^4.19.0" + zod-to-json-schema "3.20.3" + "@manypkg/find-root@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@manypkg/find-root/-/find-root-1.1.0.tgz#a62d8ed1cd7e7d4c11d9d52a8397460b5d4ad29f" @@ -2387,7 +2421,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== @@ -3850,43 +3884,39 @@ kleur@^4.0.3, kleur@^4.1.5: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -langchain@^0.0.151: - version "0.0.151" - resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.151.tgz#10c1ebdda0d772e49dbca755ada6307c9280f601" - integrity sha512-RA7/ELK5dqUgv5glIP5Wm5JmbnrjH/eeROYdKGDGaDUNZrRJ2CLuEu+oJH7hcE5hpPoPlkLBCs/vz4hvr/YtYw== +langchain@^0.0.197-rc.1: + version "0.0.197-rc.1" + resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.197-rc.1.tgz#4447ae83d09f400e928bfb2c6e15fc8635c76f48" + integrity sha512-KyIolBPL7LxktSUPPm0qvTJP0z5OTLzbk4zEJu55iNm3r5W8ucWUEnvv+kqJPubF+z2+b+aZoqT8+MevsFwfEA== dependencies: - "@anthropic-ai/sdk" "^0.6.2" - ansi-styles "^5.0.0" + "@langchain/anthropic" "^0.0.3" + "@langchain/core" "^0.0.1" + "@langchain/openai" "^0.0.1" binary-extensions "^2.2.0" - camelcase "6" - decamelize "^1.2.0" expr-eval "^2.0.2" flat "^5.0.2" js-tiktoken "^1.0.7" js-yaml "^4.1.0" jsonpointer "^5.0.1" langchainhub "~0.0.6" - langsmith "~0.0.31" + langsmith "~0.0.48" ml-distance "^4.0.0" - object-hash "^3.0.0" - openai "~4.4.0" openapi-types "^12.1.3" - p-queue "^6.6.2" p-retry "4" uuid "^9.0.0" yaml "^2.2.1" - zod "^3.21.4" - zod-to-json-schema "^3.20.4" + zod "^3.22.3" + zod-to-json-schema "3.20.3" langchainhub@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.6.tgz#9d2d06e4ce0807b4e8a31e19611f57aef990b54d" integrity sha512-SW6105T+YP1cTe0yMf//7kyshCgvCTyFBMTgH2H3s9rTAR4e+78DA/BBrUL/Mt4Q5eMWui7iGuAYb3pgGsdQ9w== -langsmith@~0.0.31: - version "0.0.38" - resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.38.tgz#7946b4f6b91c66a450132d8853cf010e3c4a44ac" - integrity sha512-SNeje3mF+90aAX2Br919rraA21+EaNw1IfaXSrWxkqYJjJifWs0knLlAki8Vg1Sg5uv5ay/6MOO8v5mU5mL0yw== +langsmith@^0.0.48, langsmith@~0.0.48: + version "0.0.48" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.48.tgz#3a9a8ce257271ddb43d01ebf585c4370a3a3ba79" + integrity sha512-s0hW8iZ90Q9XLTnDK0Pgee245URV3b1cXQjPDj5OKm1+KN7iSK1pKx+4CO7RcFLz58Ixe7Mt+mVcomYqUuryxQ== dependencies: "@types/uuid" "^9.0.1" commander "^10.0.1" @@ -4761,10 +4791,10 @@ open@^9.1.0: is-inside-container "^1.0.0" is-wsl "^2.2.0" -openai@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.4.0.tgz#dbaab326eb044ddec479951b245850c482678031" - integrity sha512-JN0t628Kh95T0IrXl0HdBqnlJg+4Vq0Bnh55tio+dfCnyzHvMLiWyCM9m726MAJD2YkDU4/8RQB6rNbEq9ct2w== +openai@^4.19.0: + version "4.20.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.20.0.tgz#d171b5afb38e0c5ba07f9dd7b15b5c4bbc1f1bda" + integrity sha512-VbAYerNZFfIIeESS+OL9vgDkK8Mnri55n+jN0UN/HZeuM0ghGh6nDN6UGRZxslNgyJ7XmY/Ca9DO4YYyvrszGA== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" @@ -4774,6 +4804,7 @@ openai@~4.4.0: form-data-encoder "1.7.2" formdata-node "^4.3.2" node-fetch "^2.6.7" + web-streams-polyfill "^3.2.1" openapi-types@^12.1.3: version "12.1.3" @@ -6286,6 +6317,11 @@ web-streams-polyfill@4.0.0-beta.3: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== +web-streams-polyfill@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + webextension-polyfill@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz#ccb28101c910ba8cf955f7e6a263e662d744dbb8" @@ -6496,15 +6532,15 @@ 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.20.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz#de97c5b6d4a25e9d444618486cb55c0c7fb949fd" - integrity sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw== +zod-to-json-schema@3.20.3: + version "3.20.3" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.20.3.tgz#8c95d8c20f20455ffa0b4b526c29703f35f6d787" + integrity sha512-/Q3wnyxAfCt94ZcrGiXXoiAfRqasxl9CX64LZ9fj+4dKH68zulUtU0uk1WMxQPfAxQ0ZI70dKzcoW7hHj+DwSQ== -zod@^3.21.4: - version "3.22.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b" - integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg== +zod@^3.22.3: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== zwitch@^2.0.0: version "2.0.4"