diff --git a/package.json b/package.json index 10a9987c..6d1b5ea5 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,12 @@ "@codemirror/lang-xml": "^6.1.0", "@langchain/anthropic": "^0.3.12", "@langchain/community": "^0.3.26", - "@langchain/core": "^0.3.33", - "@langchain/google-genai": "^0.1.6", + "@langchain/core": "^0.3.36", + "@langchain/google-genai": "^0.1.7", "@langchain/langgraph": "^0.2.41", "@langchain/langgraph-sdk": "^0.0.36", "@langchain/ollama": "^0.1.4", - "@langchain/openai": "^0.3.17", + "@langchain/openai": "^0.4.2", "@nextjournal/lang-clojure": "^1.0.0", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.2", @@ -77,6 +77,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.441.0", "next": "14.2.10", + "pdf-parse": "^1.1.1", "react": "^18", "react-colorful": "^5.6.1", "react-dom": "^18", @@ -99,6 +100,7 @@ "@types/js-cookie": "^3.0.6", "@types/lodash": "^4.17.12", "@types/node": "^20", + "@types/pdf-parse": "^1.1.4", "@types/react": "^18", "@types/react-dom": "^18", "@types/uuid": "^10.0.0", diff --git a/src/agent/open-canvas/nodes/customAction.ts b/src/agent/open-canvas/nodes/customAction.ts index 762908f7..6e3397cf 100644 --- a/src/agent/open-canvas/nodes/customAction.ts +++ b/src/agent/open-canvas/nodes/customAction.ts @@ -49,7 +49,7 @@ export const customAction = async ( throw new Error("`assistant_id` not found in configurable"); } if (!userId) { - throw new Error("`supabase_user_id` not found in configurable"); + throw new Error("`user.id` not found in configurable"); } const customActionsNamespace = ["custom_actions", userId]; const actionsKey = "actions"; diff --git a/src/agent/open-canvas/nodes/generate-artifact/index.ts b/src/agent/open-canvas/nodes/generate-artifact/index.ts index 2fbd1e53..308afe13 100644 --- a/src/agent/open-canvas/nodes/generate-artifact/index.ts +++ b/src/agent/open-canvas/nodes/generate-artifact/index.ts @@ -1,7 +1,9 @@ import { + createContextDocumentMessages, getFormattedReflections, getModelConfig, getModelFromConfig, + isUsingO1MiniModel, optionallyGetSystemPromptFromConfig, } from "@/agent/utils"; import { ArtifactV3 } from "@/types"; @@ -20,9 +22,12 @@ export const generateArtifact = async ( state: typeof OpenCanvasGraphAnnotation.State, config: LangGraphRunnableConfig ): Promise => { - const { modelName, modelProvider } = getModelConfig(config); + const { modelName, modelProvider } = getModelConfig(config, { + isToolCalling: true, + }); const smallModel = await getModelFromConfig(config, { temperature: 0.5, + isToolCalling: true, }); const modelWithArtifactTool = smallModel.bindTools( @@ -47,8 +52,14 @@ export const generateArtifact = async ( ? `${userSystemPrompt}\n${formattedNewArtifactPrompt}` : formattedNewArtifactPrompt; + const contextDocumentMessages = await createContextDocumentMessages(config); + const isO1MiniModel = isUsingO1MiniModel(config); const response = await modelWithArtifactTool.invoke( - [{ role: "system", content: fullSystemPrompt }, ...state.messages], + [ + { role: isO1MiniModel ? "user" : "system", content: fullSystemPrompt }, + ...contextDocumentMessages, + ...state.messages, + ], { runName: "generate_artifact" } ); diff --git a/src/agent/open-canvas/nodes/generatePath.ts b/src/agent/open-canvas/nodes/generatePath.ts index 2242a69c..7cbf52ee 100644 --- a/src/agent/open-canvas/nodes/generatePath.ts +++ b/src/agent/open-canvas/nodes/generatePath.ts @@ -2,6 +2,7 @@ import { LangGraphRunnableConfig } from "@langchain/langgraph"; import { z } from "zod"; import { getArtifactContent } from "../../../contexts/utils"; import { + createContextDocumentMessages, formatArtifactContentWithTemplate, getModelFromConfig, } from "../../utils"; @@ -94,6 +95,7 @@ export const generatePath = async ( const model = await getModelFromConfig(config, { temperature: 0, + isToolCalling: true, }); const modelWithTool = model.withStructuredOutput( z.object({ @@ -106,7 +108,9 @@ export const generatePath = async ( } ); + const contextDocumentMessages = await createContextDocumentMessages(config); const result = await modelWithTool.invoke([ + ...contextDocumentMessages, { role: "user", content: formattedPrompt, diff --git a/src/agent/open-canvas/nodes/replyToGeneralInput.ts b/src/agent/open-canvas/nodes/replyToGeneralInput.ts index b75b0f2c..8eca1d1f 100644 --- a/src/agent/open-canvas/nodes/replyToGeneralInput.ts +++ b/src/agent/open-canvas/nodes/replyToGeneralInput.ts @@ -2,10 +2,12 @@ import { LangGraphRunnableConfig } from "@langchain/langgraph"; import { getArtifactContent } from "../../../contexts/utils"; import { Reflections } from "../../../types"; import { + createContextDocumentMessages, ensureStoreInConfig, formatArtifactContentWithTemplate, formatReflections, getModelFromConfig, + isUsingO1MiniModel, } from "../../utils"; import { CURRENT_ARTIFACT_PROMPT, NO_ARTIFACT_PROMPT } from "../prompts"; import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state"; @@ -58,8 +60,11 @@ You also have the following reflections on style guidelines and general memories : NO_ARTIFACT_PROMPT ); + const contextDocumentMessages = await createContextDocumentMessages(config); + const isO1MiniModel = isUsingO1MiniModel(config); const response = await smallModel.invoke([ - { role: "system", content: formattedPrompt }, + { role: isO1MiniModel ? "user" : "system", content: formattedPrompt }, + ...contextDocumentMessages, ...state.messages, ]); diff --git a/src/agent/open-canvas/nodes/rewrite-artifact/index.ts b/src/agent/open-canvas/nodes/rewrite-artifact/index.ts index 7615366d..ec8ad572 100644 --- a/src/agent/open-canvas/nodes/rewrite-artifact/index.ts +++ b/src/agent/open-canvas/nodes/rewrite-artifact/index.ts @@ -6,8 +6,10 @@ import { LangGraphRunnableConfig } from "@langchain/langgraph"; import { optionallyUpdateArtifactMeta } from "./update-meta"; import { buildPrompt, createNewArtifactContent, validateState } from "./utils"; import { + createContextDocumentMessages, getFormattedReflections, getModelFromConfig, + isUsingO1MiniModel, optionallyGetSystemPromptFromConfig, } from "@/agent/utils"; import { isArtifactMarkdownContent } from "@/lib/artifact_content_types"; @@ -45,8 +47,11 @@ export const rewriteArtifact = async ( ? `${userSystemPrompt}\n${formattedPrompt}` : formattedPrompt; + const contextDocumentMessages = await createContextDocumentMessages(config); + const isO1MiniModel = isUsingO1MiniModel(config); const newArtifactResponse = await smallModelWithConfig.invoke([ - { role: "system", content: fullSystemPrompt }, + { role: isO1MiniModel ? "user" : "system", content: fullSystemPrompt }, + ...contextDocumentMessages, recentHumanMessage, ]); diff --git a/src/agent/open-canvas/nodes/rewrite-artifact/update-meta.ts b/src/agent/open-canvas/nodes/rewrite-artifact/update-meta.ts index b2874c85..91c757c6 100644 --- a/src/agent/open-canvas/nodes/rewrite-artifact/update-meta.ts +++ b/src/agent/open-canvas/nodes/rewrite-artifact/update-meta.ts @@ -4,6 +4,7 @@ import { formatArtifactContent, getModelConfig, getModelFromConfig, + isUsingO1MiniModel, } from "@/agent/utils"; import { getArtifactContent } from "@/contexts/utils"; import { GET_TITLE_TYPE_REWRITE_ARTIFACT } from "../../prompts"; @@ -15,8 +16,14 @@ export async function optionallyUpdateArtifactMeta( state: typeof OpenCanvasGraphAnnotation.State, config: LangGraphRunnableConfig ): Promise { - const { modelProvider } = getModelConfig(config); - const toolCallingModel = (await getModelFromConfig(config)) + const { modelProvider } = getModelConfig(config, { + isToolCalling: true, + }); + const toolCallingModel = ( + await getModelFromConfig(config, { + isToolCalling: true, + }) + ) .bindTools( [ { @@ -56,8 +63,12 @@ export async function optionallyUpdateArtifactMeta( throw new Error("No recent human message found"); } + const isO1MiniModel = isUsingO1MiniModel(config); const optionallyUpdateArtifactResponse = await toolCallingModel.invoke([ - { role: "system", content: optionallyUpdateArtifactMetaPrompt }, + { + role: isO1MiniModel ? "user" : "system", + content: optionallyUpdateArtifactMetaPrompt, + }, recentHumanMessage, ]); diff --git a/src/agent/open-canvas/nodes/updateArtifact.ts b/src/agent/open-canvas/nodes/updateArtifact.ts index 928ccf41..0d95a3f8 100644 --- a/src/agent/open-canvas/nodes/updateArtifact.ts +++ b/src/agent/open-canvas/nodes/updateArtifact.ts @@ -3,10 +3,12 @@ import { getArtifactContent } from "../../../contexts/utils"; import { isArtifactCodeContent } from "../../../lib/artifact_content_types"; import { ArtifactCodeV3, ArtifactV3, Reflections } from "../../../types"; import { + createContextDocumentMessages, ensureStoreInConfig, formatReflections, getModelConfig, getModelFromConfig, + isUsingO1MiniModel, } from "../../utils"; import { UPDATE_HIGHLIGHTED_ARTIFACT_PROMPT } from "../prompts"; import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state"; @@ -18,15 +20,15 @@ export const updateArtifact = async ( state: typeof OpenCanvasGraphAnnotation.State, config: LangGraphRunnableConfig ): Promise => { - const { modelProvider } = getModelConfig(config); + const { modelProvider, modelName } = getModelConfig(config); let smallModel: Awaited>; - if (modelProvider.includes("openai")) { - // Custom model is OpenAI/Azure OpenAI + if (modelProvider.includes("openai") || modelName.includes("3-5-sonnet")) { + // Custom model is intelligent enough for updating artifacts smallModel = await getModelFromConfig(config, { temperature: 0, }); } else { - // Custom model is not set to OpenAI/Azure OpenAI. Use GPT-4o + // Custom model is not intelligent enough for updating artifacts smallModel = await getModelFromConfig( { ...config, @@ -102,8 +104,12 @@ export const updateArtifact = async ( if (!recentHumanMessage) { throw new Error("No recent human message found"); } + + const contextDocumentMessages = await createContextDocumentMessages(config); + const isO1MiniModel = isUsingO1MiniModel(config); const updatedArtifact = await smallModel.invoke([ - { role: "system", content: formattedPrompt }, + { role: isO1MiniModel ? "user" : "system", content: formattedPrompt }, + ...contextDocumentMessages, recentHumanMessage, ]); diff --git a/src/agent/open-canvas/nodes/updateHighlightedText.ts b/src/agent/open-canvas/nodes/updateHighlightedText.ts index 173ebaf1..73a66cb9 100644 --- a/src/agent/open-canvas/nodes/updateHighlightedText.ts +++ b/src/agent/open-canvas/nodes/updateHighlightedText.ts @@ -1,4 +1,9 @@ -import { getModelConfig, getModelFromConfig } from "@/agent/utils"; +import { + createContextDocumentMessages, + getModelConfig, + getModelFromConfig, + isUsingO1MiniModel, +} from "@/agent/utils"; import { BaseLanguageModelInput } from "@langchain/core/language_models/base"; import { AIMessageChunk } from "@langchain/core/messages"; import { RunnableBinding } from "@langchain/core/runnables"; @@ -35,21 +40,21 @@ export const updateHighlightedText = async ( state: typeof OpenCanvasGraphAnnotation.State, config: LangGraphRunnableConfig ): Promise => { - const { modelProvider } = getModelConfig(config); + const { modelProvider, modelName } = getModelConfig(config); let model: RunnableBinding< BaseLanguageModelInput, AIMessageChunk, ConfigurableChatModelCallOptions >; - if (modelProvider.includes("openai")) { - // Custom model is OpenAI/Azure OpenAI + if (modelProvider.includes("openai") || modelName.includes("3-5-sonnet")) { + // Custom model is intelligent enough for updating artifacts model = ( await getModelFromConfig(config, { temperature: 0, }) ).withConfig({ runName: "update_highlighted_markdown" }); } else { - // Custom model is not set to OpenAI/Azure OpenAI. Use GPT-4o + // Custom model is not intelligent enough for updating artifacts model = ( await getModelFromConfig( { @@ -92,11 +97,14 @@ export const updateHighlightedText = async ( throw new Error("Expected a human message"); } + const contextDocumentMessages = await createContextDocumentMessages(config); + const isO1MiniModel = isUsingO1MiniModel(config); const response = await model.invoke([ { - role: "system", + role: isO1MiniModel ? "user" : "system", content: formattedPrompt, }, + ...contextDocumentMessages, recentUserMessage, ]); const responseContent = response.content as string; diff --git a/src/agent/utils.ts b/src/agent/utils.ts index 7891260f..bb29c17a 100644 --- a/src/agent/utils.ts +++ b/src/agent/utils.ts @@ -3,6 +3,14 @@ import { CustomModelConfig } from "@/types"; import { BaseStore, LangGraphRunnableConfig } from "@langchain/langgraph"; import { initChatModel } from "langchain/chat_models/universal"; import { ArtifactCodeV3, ArtifactMarkdownV3, Reflections } from "../types"; +import { ContextDocument } from "@/hooks/useAssistants"; +import pdfParse from "pdf-parse"; +import { MessageContentComplex } from "@langchain/core/messages"; +import { + LANGCHAIN_USER_ONLY_MODELS, + TEMPERATURE_EXCLUDED_MODELS, +} from "@/constants"; +import { createClient, Session, User } from "@supabase/supabase-js"; export const formatReflections = ( reflections: Reflections, @@ -136,7 +144,10 @@ export const formatArtifactContentWithTemplate = ( }; export const getModelConfig = ( - config: LangGraphRunnableConfig + config: LangGraphRunnableConfig, + extra?: { + isToolCalling?: boolean; + } ): { modelName: string; modelProvider: string; @@ -157,7 +168,11 @@ export const getModelConfig = ( const modelConfig = config.configurable?.modelConfig as CustomModelConfig; if (customModelName.startsWith("azure/")) { - const actualModelName = customModelName.replace("azure/", ""); + let actualModelName = customModelName.replace("azure/", ""); + if (extra?.isToolCalling && actualModelName.includes("o1")) { + // Fallback to 4o model for tool calling since o1 does not support tools. + actualModelName = "gpt-4o"; + } return { modelName: actualModelName, modelProvider: "azure_openai", @@ -179,9 +194,15 @@ export const getModelConfig = ( modelConfig, }; - if (customModelName.includes("gpt-")) { + if (customModelName.includes("gpt-") || customModelName.includes("o1")) { + let actualModelName = providerConfig.modelName; + if (extra?.isToolCalling && actualModelName.includes("o1")) { + // Fallback to 4o model for tool calling since o1 does not support tools. + actualModelName = "gpt-4o"; + } return { ...providerConfig, + modelName: actualModelName, modelProvider: "openai", apiKey: process.env.OPENAI_API_KEY, }; @@ -225,11 +246,40 @@ export function optionallyGetSystemPromptFromConfig( return config.configurable?.systemPrompt as string | undefined; } +async function getUserFromConfig( + config: LangGraphRunnableConfig +): Promise { + if ( + !process.env.NEXT_PUBLIC_SUPABASE_URL || + !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + ) { + return undefined; + } + const accessToken = ( + config.configurable?.supabase_session as Session | undefined + )?.access_token; + if (!accessToken) { + return undefined; + } + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + ); + const authRes = await supabase.auth.getUser(accessToken); + return authRes.data.user || undefined; +} + +export function isUsingO1MiniModel(config: LangGraphRunnableConfig) { + const { modelName } = getModelConfig(config); + return modelName.includes("o1-mini"); +} + export async function getModelFromConfig( config: LangGraphRunnableConfig, extra?: { temperature?: number; maxTokens?: number; + isToolCalling?: boolean; } ) { const { @@ -239,17 +289,46 @@ export async function getModelFromConfig( apiKey, baseUrl, modelConfig, - } = getModelConfig(config); + } = getModelConfig(config, { + isToolCalling: extra?.isToolCalling, + }); const { temperature = 0.5, maxTokens } = { temperature: modelConfig?.temperatureRange.current, maxTokens: modelConfig?.maxTokens.current, ...extra, }; + const isLangChainUserModel = LANGCHAIN_USER_ONLY_MODELS.some( + (m) => m === modelName + ); + if (isLangChainUserModel) { + const user = await getUserFromConfig(config); + if (!user) { + throw new Error( + "Unauthorized. Can not use LangChain only models without a user." + ); + } + if (!user.email?.endsWith("@langchain.dev")) { + throw new Error( + "Unauthorized. Can not use LangChain only models without a user with a @langchain.dev email." + ); + } + } + + const includeStandardParams = !TEMPERATURE_EXCLUDED_MODELS.some( + (m) => m === modelName + ); + return await initChatModel(modelName, { modelProvider, - maxTokens, - temperature, + // Certain models (e.g., OpenAI o1) do not support passing the temperature param. + ...(includeStandardParams + ? { maxTokens, temperature } + : { + max_completion_tokens: maxTokens, + // streaming: false, + // disableStreaming: true, + }), ...(baseUrl ? { baseUrl } : {}), ...(apiKey ? { apiKey } : {}), ...(azureConfig != null @@ -264,3 +343,156 @@ export async function getModelFromConfig( : {}), }); } + +const cleanBase64 = (base64String: string): string => { + return base64String.replace(/^data:.*?;base64,/, ""); +}; + +async function convertPDFToText(base64PDF: string) { + try { + // Convert base64 to buffer + const pdfBuffer = Buffer.from(base64PDF, "base64"); + + // Parse PDF + const data = await pdfParse(pdfBuffer); + + // Get text content + const text = data.text; + + return cleanBase64(text); + } catch (error) { + console.error("Error converting PDF to text:", error); + throw error; + } +} + +export async function createContextDocumentMessagesAnthropic( + config: LangGraphRunnableConfig, + options?: { nativeSupport: boolean } +) { + if (!config.configurable?.documents) { + return []; + } + + const messagesPromises = ( + config.configurable?.documents as ContextDocument[] + ).map(async (doc) => { + if (doc.type.includes("pdf") && options?.nativeSupport) { + return { + type: "document", + source: { + type: "base64", + media_type: doc.type, + data: cleanBase64(doc.data), + }, + }; + } + + let text = ""; + if (doc.type.includes("pdf") && !options?.nativeSupport) { + text = await convertPDFToText(doc.data); + } else if (doc.type.startsWith("text/")) { + text = atob(doc.data); + } + + return { + type: "text", + text, + }; + }); + + return await Promise.all(messagesPromises); +} + +export function createContextDocumentMessagesGemini( + config: LangGraphRunnableConfig +) { + if (!config.configurable?.documents) { + return []; + } + + return (config.configurable?.documents as ContextDocument[]).map((doc) => { + if (doc.type.includes("pdf")) { + return { + type: doc.type, + data: cleanBase64(doc.data), + }; + } else if (doc.type.startsWith("text/")) { + return { + type: "text", + text: atob(doc.data), + }; + } + throw new Error("Unsupported document type: " + doc.type); + }); +} + +export async function createContextDocumentMessagesOpenAI( + config: LangGraphRunnableConfig +) { + if (!config.configurable?.documents) { + return []; + } + + const messagesPromises = ( + config.configurable?.documents as ContextDocument[] + ).map(async (doc) => { + let text = ""; + + if (doc.type.includes("pdf")) { + text = await convertPDFToText(doc.data); + } else if (doc.type.startsWith("text/")) { + text = atob(doc.data); + } + + return { + type: "text", + text, + }; + }); + + return await Promise.all(messagesPromises); +} + +export async function createContextDocumentMessages( + config: LangGraphRunnableConfig +) { + const { modelProvider, modelName } = getModelConfig(config); + let contextDocumentMessages: Record[] = []; + if (modelProvider === "openai") { + contextDocumentMessages = await createContextDocumentMessagesOpenAI(config); + } else if (modelProvider === "anthropic") { + const nativeSupport = modelName.includes("3-5-sonnet"); + contextDocumentMessages = await createContextDocumentMessagesAnthropic( + config, + { + nativeSupport, + } + ); + } else if (modelProvider === "google-genai") { + contextDocumentMessages = createContextDocumentMessagesGemini(config); + } + + if (!contextDocumentMessages.length) return []; + + let contextMessages: Array<{ + role: "user"; + content: MessageContentComplex[]; + }> = []; + if (contextDocumentMessages?.length) { + contextMessages = [ + { + role: "user", + content: [ + { + type: "text", + text: "Use the file(s) and/or text below as context when generating your response.", + }, + ...contextDocumentMessages, + ], + }, + ]; + } + + return contextMessages; +} diff --git a/src/app/api/[..._path]/route.ts b/src/app/api/[..._path]/route.ts index 52e16c6d..3e062a0e 100644 --- a/src/app/api/[..._path]/route.ts +++ b/src/app/api/[..._path]/route.ts @@ -1,6 +1,6 @@ import { LANGGRAPH_API_URL } from "../../../constants"; import { NextRequest, NextResponse } from "next/server"; -import { User } from "@supabase/supabase-js"; +import { Session, User } from "@supabase/supabase-js"; import { verifyUserAuthenticated } from "../../../lib/supabase/verify_user_server"; function getCorsHeaders() { @@ -12,10 +12,13 @@ function getCorsHeaders() { } async function handleRequest(req: NextRequest, method: string) { + let session: Session | undefined; let user: User | undefined; try { - user = await verifyUserAuthenticated(); - if (!user) { + const authRes = await verifyUserAuthenticated(); + session = authRes?.session; + user = authRes?.user; + if (!session || !user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } catch (e) { @@ -52,6 +55,7 @@ async function handleRequest(req: NextRequest, method: string) { parsedBody.config = parsedBody.config || {}; parsedBody.config.configurable = { ...parsedBody.config.configurable, + supabase_session: session, supabase_user_id: user.id, }; options.body = JSON.stringify(parsedBody); diff --git a/src/app/api/store/delete/id/route.ts b/src/app/api/store/delete/id/route.ts index 47cfcb93..c2a8cb43 100644 --- a/src/app/api/store/delete/id/route.ts +++ b/src/app/api/store/delete/id/route.ts @@ -1,14 +1,12 @@ import { NextRequest, NextResponse } from "next/server"; import { Client } from "@langchain/langgraph-sdk"; import { LANGGRAPH_API_URL } from "@/constants"; -import { User } from "@supabase/supabase-js"; import { verifyUserAuthenticated } from "../../../../../lib/supabase/verify_user_server"; export async function POST(req: NextRequest) { - let user: User | undefined; try { - user = await verifyUserAuthenticated(); - if (!user) { + const authRes = await verifyUserAuthenticated(); + if (!authRes?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } catch (e) { diff --git a/src/app/api/store/delete/route.ts b/src/app/api/store/delete/route.ts index 9478aaa4..9505381e 100644 --- a/src/app/api/store/delete/route.ts +++ b/src/app/api/store/delete/route.ts @@ -1,14 +1,12 @@ import { NextRequest, NextResponse } from "next/server"; import { Client } from "@langchain/langgraph-sdk"; import { LANGGRAPH_API_URL } from "@/constants"; -import { User } from "@supabase/supabase-js"; import { verifyUserAuthenticated } from "../../../../lib/supabase/verify_user_server"; export async function POST(req: NextRequest) { - let user: User | undefined; try { - user = await verifyUserAuthenticated(); - if (!user) { + const authRes = await verifyUserAuthenticated(); + if (!authRes?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } catch (e) { diff --git a/src/app/api/store/get/route.ts b/src/app/api/store/get/route.ts index b420e10c..19d3c4d8 100644 --- a/src/app/api/store/get/route.ts +++ b/src/app/api/store/get/route.ts @@ -1,14 +1,12 @@ import { NextRequest, NextResponse } from "next/server"; import { Client } from "@langchain/langgraph-sdk"; import { LANGGRAPH_API_URL } from "@/constants"; -import { User } from "@supabase/supabase-js"; import { verifyUserAuthenticated } from "../../../../lib/supabase/verify_user_server"; export async function POST(req: NextRequest) { - let user: User | undefined; try { - user = await verifyUserAuthenticated(); - if (!user) { + const authRes = await verifyUserAuthenticated(); + if (!authRes?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } catch (e) { diff --git a/src/app/api/store/put/route.ts b/src/app/api/store/put/route.ts index 8e46b1f4..9a394c31 100644 --- a/src/app/api/store/put/route.ts +++ b/src/app/api/store/put/route.ts @@ -1,14 +1,12 @@ import { NextRequest, NextResponse } from "next/server"; import { Client } from "@langchain/langgraph-sdk"; import { LANGGRAPH_API_URL } from "@/constants"; -import { User } from "@supabase/supabase-js"; import { verifyUserAuthenticated } from "../../../../lib/supabase/verify_user_server"; export async function POST(req: NextRequest) { - let user: User | undefined; try { - user = await verifyUserAuthenticated(); - if (!user) { + const authRes = await verifyUserAuthenticated(); + if (!authRes?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } catch (e) { diff --git a/src/components/assistant-select/create-edit-assistant-dialog.tsx b/src/components/assistant-select/create-edit-assistant-dialog.tsx index 51103caf..08aa1573 100644 --- a/src/components/assistant-select/create-edit-assistant-dialog.tsx +++ b/src/components/assistant-select/create-edit-assistant-dialog.tsx @@ -1,4 +1,9 @@ -import { CreateAssistantFields } from "@/hooks/useAssistants"; +import { + ContextDocument, + CreateCustomAssistantArgs, + EditCustomAssistantArgs, + fileToBase64, +} from "@/hooks/useAssistants"; import { Assistant } from "@langchain/langgraph-sdk"; import { Dispatch, @@ -25,6 +30,50 @@ import { useToast } from "@/hooks/use-toast"; import { ColorPicker } from "./color-picker"; import { Textarea } from "../ui/textarea"; import { InlineContextTooltip } from "../ui/inline-context-tooltip"; +import { UploadedFiles } from "./uploaded-file"; + +function arrayToFileList(files: File[] | undefined) { + if (!files || !files.length) return undefined; + const dt = new DataTransfer(); + files?.forEach((file) => dt.items.add(file)); + return dt.files; +} + +function contextDocumentToFile(document: ContextDocument): File { + // Remove any data URL prefix if it exists + let base64String = document.data; + if (base64String.includes(",")) { + base64String = base64String.split(",")[1]; + } + + // Fix padding if necessary + while (base64String.length % 4 !== 0) { + base64String += "="; + } + + // Clean the string (remove whitespace and invalid characters) + base64String = base64String.replace(/\s/g, ""); + + try { + // Convert base64 to binary + const binaryString = atob(base64String); + + // Convert binary string to Uint8Array + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // Create Blob from the bytes + const blob = new Blob([bytes], { type: document.type }); + + // Create File object + return new File([blob], document.name, { type: document.type }); + } catch (error) { + console.error("Error converting base64 to file:", error); + throw error; + } +} interface CreateEditAssistantDialogProps { open: boolean; @@ -32,16 +81,16 @@ interface CreateEditAssistantDialogProps { userId: string | undefined; isEditing: boolean; assistant?: Assistant; - createCustomAssistant: ( - newAssistant: CreateAssistantFields, - userId: string, - successCallback?: (id: string) => void - ) => Promise; - editCustomAssistant: ( - editedAssistant: CreateAssistantFields, - assistantId: string, - userId: string - ) => Promise; + createCustomAssistant: ({ + newAssistant, + userId, + successCallback, + }: CreateCustomAssistantArgs) => Promise; + editCustomAssistant: ({ + editedAssistant, + assistantId, + userId, + }: EditCustomAssistantArgs) => Promise; isLoading: boolean; allDisabled: boolean; setAllDisabled: Dispatch>; @@ -67,6 +116,16 @@ const SystemPromptWhatsThis = (): React.ReactNode => ( ); +const ContextDocumentsWhatsThis = (): React.ReactNode => ( + +

+ Context documents are text or PDF files which will be included in the + LLM's context for ALL interactions except quick actions, when + generating, re-writing and editing artifacts. +

+
+); + export function CreateEditAssistantDialog( props: CreateEditAssistantDialogProps ) { @@ -79,6 +138,7 @@ export function CreateEditAssistantDialog( const [iconColor, setIconColor] = useState("#000000"); const [showColorPicker, setShowColorPicker] = useState(false); const [hoverTimer, setHoverTimer] = useState(null); + const [documents, setDocuments] = useState(); const metadata = props.assistant?.metadata as Record | undefined; @@ -94,12 +154,20 @@ export function CreateEditAssistantDialog( setHasSelectedIcon(true); setIconName(metadata?.iconData?.iconName || "User"); setIconColor(metadata?.iconData?.iconColor || "#000000"); + const documents = props.assistant?.config?.configurable?.documents as + | ContextDocument[] + | undefined; + if (documents && documents.length > 0) { + const files = documents.map(contextDocumentToFile); + setDocuments(arrayToFileList(files)); + } } else if (!props.isEditing) { setName(""); setDescription(""); setSystemPrompt(""); setIconName("User"); setIconColor("#000000"); + setDocuments(undefined); } }, [props.assistant, props.isEditing]); @@ -124,10 +192,20 @@ export function CreateEditAssistantDialog( props.setAllDisabled(true); + const contentDocuments: ContextDocument[] = []; + if (documents?.length) { + const documentsPromise = Array.from(documents).map(async (doc) => ({ + name: doc.name, + type: doc.type, + data: await fileToBase64(doc), + })); + contentDocuments.push(...(await Promise.all(documentsPromise))); + } + let res: boolean; if (props.isEditing && props.assistant) { - res = !!(await props.editCustomAssistant( - { + res = !!(await props.editCustomAssistant({ + editedAssistant: { name, description, systemPrompt, @@ -135,13 +213,14 @@ export function CreateEditAssistantDialog( iconName, iconColor, }, + documents: contentDocuments, }, - props.assistant.assistant_id, - props.userId - )); + assistantId: props.assistant.assistant_id, + userId: props.userId, + })); } else { - res = await props.createCustomAssistant( - { + res = await props.createCustomAssistant({ + newAssistant: { name, description, systemPrompt, @@ -149,9 +228,10 @@ export function CreateEditAssistantDialog( iconName, iconColor, }, + documents: contentDocuments, }, - props.userId - ); + userId: props.userId, + }); } if (res) { @@ -178,6 +258,15 @@ export function CreateEditAssistantDialog( setIconColor("#000000"); }; + const handleRemoveFile = (index: number) => { + setDocuments((prev) => { + if (!prev) return prev; + const files = Array.from(prev); + const newFiles = files.filter((_, i) => i !== index); + return arrayToFileList(newFiles); + }); + }; + if (props.isEditing && !props.assistant) { return null; } @@ -302,6 +391,63 @@ export function CreateEditAssistantDialog( + + {!documents && ( + { + const files = e.target.files; + if (!files) return; + + if (files.length > 20) { + alert("You can only upload up to 20 files"); + e.target.value = ""; + return; + } + + // Check each file size (10MB = 10485760 bytes) + const tenMbBytes = 10485760; + for (let i = 0; i < files.length; i += 1) { + if (files[i].size > tenMbBytes) { + alert( + `File "${files[i].name}" exceeds the 10MB size limit` + ); + e.target.value = ""; + return; + } + } + + setDocuments(files || undefined); + }} + /> + )} + + {documents && ( + + )} +
+
+ ))} + + ); +} diff --git a/src/components/chat-interface/model-selector/index.tsx b/src/components/chat-interface/model-selector/index.tsx index bbfdb183..b55a58dc 100644 --- a/src/components/chat-interface/model-selector/index.tsx +++ b/src/components/chat-interface/model-selector/index.tsx @@ -7,8 +7,12 @@ import { CommandItem, CommandList, } from "@/components/ui/command"; -import { ALL_MODEL_NAMES, ALL_MODELS } from "@/constants"; -import { useCallback, useState } from "react"; +import { + ALL_MODEL_NAMES, + ALL_MODELS, + LANGCHAIN_USER_ONLY_MODELS, +} from "@/constants"; +import { useCallback, useEffect, useState } from "react"; import { AlertNewModelSelectorFeature } from "./alert-new-model-selector"; import { ModelConfigPanel } from "./model-config-pannel"; import { IsNewBadge } from "./new-badge"; @@ -22,6 +26,7 @@ import { } from "@radix-ui/react-popover"; import { Check } from "lucide-react"; import NextImage from "next/image"; +import { useUser } from "@/hooks/useUser"; interface ModelSelectorProps { modelName: ALL_MODEL_NAMES; @@ -42,11 +47,19 @@ export default function ModelSelector({ setModelName, modelConfigs, }: ModelSelectorProps) { + const { getUser } = useUser(); + const [isLangChainUser, setIsLangChainUser] = useState(false); const [showAlert, setShowAlert] = useState(false); const [open, setOpen] = useState(false); const [openConfigModelId, setOpenConfigModelId] = useState(null); + useEffect(() => { + getUser().then((user) => { + setIsLangChainUser(user?.email?.endsWith("@langchain.dev") || false); + }); + }, []); + const handleModelChange = useCallback( async (newModel: ALL_MODEL_NAMES) => { setModelName(newModel); @@ -56,6 +69,13 @@ export default function ModelSelector({ ); const allAllowedModels = ALL_MODELS.filter((model) => { + if ( + !isLangChainUser && + LANGCHAIN_USER_ONLY_MODELS.some((m) => m === model.name) + ) { + return false; + } + if ( model.name.includes("fireworks/") && process.env.NEXT_PUBLIC_FIREWORKS_ENABLED === "false" diff --git a/src/components/chat-interface/model-selector/model-config-pannel.tsx b/src/components/chat-interface/model-selector/model-config-pannel.tsx index fb9df003..c0b0e63f 100644 --- a/src/components/chat-interface/model-selector/model-config-pannel.tsx +++ b/src/components/chat-interface/model-selector/model-config-pannel.tsx @@ -7,7 +7,7 @@ import { CustomModelConfig, ModelConfigurationParams } from "@/types"; import { Button } from "@/components/ui/button"; import { Slider } from "@/components/ui/slider"; -import { ALL_MODEL_NAMES } from "@/constants"; +import { ALL_MODEL_NAMES, TEMPERATURE_EXCLUDED_MODELS } from "@/constants"; import { cn } from "@/lib/utils"; import { GearIcon, ResetIcon } from "@radix-ui/react-icons"; import { useCallback } from "react"; @@ -34,8 +34,6 @@ export function ModelConfigPanel({ onClick, setModelConfig, }: ModelConfigPanelProps) { - console.log("modelConfig ", modelConfig); - const handleTemperatureChange = useCallback( (value: number[]) => { setModelConfig(model.name, { @@ -100,6 +98,7 @@ export function ModelConfigPanel({ step: 0.1, }} onChange={handleTemperatureChange} + disabled={TEMPERATURE_EXCLUDED_MODELS.some((m) => m === model.name)} /> void; + disabled?: boolean; }) => (
-

{title}

-

{description}

+

+ {title} + {disabled && " (disabled)"} +

+

+ {description} +

-
{value}
+
+ {disabled ? range.min : value} +
); diff --git a/src/components/icons/svg/PDFIcon.svg b/src/components/icons/svg/PDFIcon.svg new file mode 100644 index 00000000..cb3aa649 --- /dev/null +++ b/src/components/icons/svg/PDFIcon.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/icons/svg/TXTIcon.svg b/src/components/icons/svg/TXTIcon.svg new file mode 100644 index 00000000..dcd0ab40 --- /dev/null +++ b/src/components/icons/svg/TXTIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/ui/icons.tsx b/src/components/ui/icons.tsx index 6be550f9..15d86472 100644 --- a/src/components/ui/icons.tsx +++ b/src/components/ui/icons.tsx @@ -1,4 +1,4 @@ -type IconProps = React.HTMLAttributes; +type IconProps = React.SVGAttributes; export const Icons = { logo: (props: IconProps) => ( @@ -92,7 +92,7 @@ export const Icons = { react: (props: IconProps) => ( @@ -100,7 +100,7 @@ export const Icons = { tailwind: (props: IconProps) => ( @@ -116,7 +116,7 @@ export const Icons = { apple: (props: IconProps) => ( @@ -145,4 +145,40 @@ export const Icons = { ), + pdf: (props?: IconProps) => ( + + + + + + + + ), }; diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx index 31ac5c1e..f4e30f37 100644 --- a/src/components/ui/slider.tsx +++ b/src/components/ui/slider.tsx @@ -23,12 +23,16 @@ const Slider = React.forwardRef< > (
))} - + )); Slider.displayName = SliderPrimitive.Root.displayName; diff --git a/src/constants.ts b/src/constants.ts index 0875c079..8a85052c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -51,6 +51,66 @@ export const AZURE_MODELS: ModelConfigurationParams[] = [ ]; export const OPENAI_MODELS: ModelConfigurationParams[] = [ + { + name: "o1-mini", + label: "o1 mini", + config: { + provider: "openai", + temperatureRange: { + min: 0, + max: 1, + default: 0.5, + current: 0.5, + }, + maxTokens: { + min: 1, + max: 65536, + default: 4096, + current: 4096, + }, + }, + isNew: true, + }, + { + name: "o1", + label: "o1", + config: { + provider: "openai", + temperatureRange: { + min: 0, + max: 1, + default: 0.5, + current: 0.5, + }, + maxTokens: { + min: 1, + max: 100000, + default: 4096, + current: 4096, + }, + }, + isNew: true, + }, + { + name: "gpt-4o", + label: "GPT-4o", + config: { + provider: "openai", + temperatureRange: { + min: 0, + max: 1, + default: 0.5, + current: 0.5, + }, + maxTokens: { + min: 1, + max: 16384, + default: 4096, + current: 4096, + }, + }, + isNew: false, + }, { name: "gpt-4o-mini", label: "GPT-4o mini", @@ -100,6 +160,26 @@ export const OLLAMA_MODELS = [ ]; export const ANTHROPIC_MODELS = [ + { + name: "claude-3-5-sonnet-latest", + label: "Claude 3.5 Sonnet", + config: { + provider: "anthropic", + temperatureRange: { + min: 0, + max: 1, + default: 0.5, + current: 0.5, + }, + maxTokens: { + min: 1, + max: 8192, + default: 4096, + current: 4096, + }, + }, + isNew: true, + }, { name: "claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku", @@ -141,6 +221,7 @@ export const ANTHROPIC_MODELS = [ isNew: false, }, ]; + export const FIREWORKS_MODELS: ModelConfigurationParams[] = [ { name: "accounts/fireworks/models/llama-v3p1-70b-instruct", @@ -207,6 +288,15 @@ export const GEMINI_MODELS: ModelConfigurationParams[] = [ }, ]; +export const LANGCHAIN_USER_ONLY_MODELS = [ + "o1-mini", + "o1", + "gpt-4o", + "claude-3-5-sonnet-latest", +]; + +export const TEMPERATURE_EXCLUDED_MODELS = ["o1-mini", "o1"]; + export const ALL_MODELS: ModelConfigurationParams[] = [ ...OPENAI_MODELS, ...ANTHROPIC_MODELS, diff --git a/src/contexts/GraphContext.tsx b/src/contexts/GraphContext.tsx index 834ea676..ccf8864f 100644 --- a/src/contexts/GraphContext.tsx +++ b/src/contexts/GraphContext.tsx @@ -118,6 +118,13 @@ function extractStreamDataChunk(chunk: any) { return chunk; } +function extractStreamDataOutput(output: any) { + if (Array.isArray(output)) { + return output[1]; + } + return output; +} + export function GraphProvider({ children }: { children: ReactNode }) { const userData = useUser(); const assistantsData = useAssistants(); @@ -362,6 +369,7 @@ export function GraphProvider({ children }: { children: ReactNode }) { runId = chunk.data.metadata.run_id; setRunId(runId); } + if (chunk.data.event === "on_chain_start") { if ( chunk.data.metadata.langgraph_node === "updateHighlightedText" @@ -743,6 +751,20 @@ export function GraphProvider({ children }: { children: ReactNode }) { setArtifact(() => result); } } + + if ( + ["generateFollowup", "replyToGeneralInput"].includes( + chunk.data.metadata.langgraph_node + ) + ) { + const message = extractStreamDataOutput(chunk.data.data.output); + if (!followupMessageId) { + followupMessageId = message.id; + } + setMessages((prevMessages) => + replaceOrInsertMessageChunk(prevMessages, message) + ); + } } } catch (e) { console.error( diff --git a/src/hooks/useAssistants.tsx b/src/hooks/useAssistants.tsx index 96c82903..300ca878 100644 --- a/src/hooks/useAssistants.tsx +++ b/src/hooks/useAssistants.tsx @@ -5,6 +5,23 @@ import { createClient } from "./utils"; import { ASSISTANT_ID_COOKIE } from "@/constants"; import { getCookie, removeCookie } from "@/lib/cookies"; +export function fileToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + if (typeof reader.result === "string") { + resolve(reader.result); + } else { + reject( + `Failed to convert file to base64. Received ${typeof reader.result} result.` + ); + } + }; + reader.onerror = (error) => reject(error); + }); +} + export type AssistantTool = { /** * The name of the tool @@ -20,6 +37,21 @@ export type AssistantTool = { parameters: Record; }; +export type ContextDocument = { + /** + * The name of the document. + */ + name: string; + /** + * The type of the document. + */ + type: string; + /** + * The base64 encoded content of the document. + */ + data: string; +}; + export interface CreateAssistantFields { iconData?: { /** @@ -49,8 +81,24 @@ export interface CreateAssistantFields { */ systemPrompt?: string; is_default?: boolean; + /** + * The documents to include in the LLMs context. + */ + documents?: ContextDocument[]; } +export type CreateCustomAssistantArgs = { + newAssistant: CreateAssistantFields; + userId: string; + successCallback?: (id: string) => void; +}; + +export type EditCustomAssistantArgs = { + editedAssistant: CreateAssistantFields; + assistantId: string; + userId: string; +}; + export function useAssistants() { const { toast } = useToast(); const [isLoadingAllAssistants, setIsLoadingAllAssistants] = useState(false); @@ -113,15 +161,16 @@ export function useAssistants() { } }; - const createCustomAssistant = async ( - newAssistant: CreateAssistantFields, - userId: string, - successCallback?: (id: string) => void - ): Promise => { + const createCustomAssistant = async ({ + newAssistant, + userId, + successCallback, + }: CreateCustomAssistantArgs): Promise => { setIsCreatingAssistant(true); try { const client = createClient(); - const { tools, systemPrompt, name, ...metadata } = newAssistant; + const { tools, systemPrompt, name, documents, ...metadata } = + newAssistant; const createdAssistant = await client.assistants.create({ graphId: "agent", name, @@ -133,6 +182,7 @@ export function useAssistants() { configurable: { tools, systemPrompt, + documents, }, }, ifExists: "do_nothing", @@ -154,15 +204,16 @@ export function useAssistants() { } }; - const editCustomAssistant = async ( - editedAssistant: CreateAssistantFields, - assistantId: string, - userId: string - ): Promise => { + const editCustomAssistant = async ({ + editedAssistant, + assistantId, + userId, + }: EditCustomAssistantArgs): Promise => { setIsEditingAssistant(true); try { const client = createClient(); - const { tools, systemPrompt, name, ...metadata } = editedAssistant; + const { tools, systemPrompt, name, documents, ...metadata } = + editedAssistant; const response = await client.assistants.update(assistantId, { name, graphId: "agent", @@ -174,6 +225,7 @@ export function useAssistants() { configurable: { tools, systemPrompt, + documents, }, }, }); @@ -203,8 +255,8 @@ export function useAssistants() { userId: string, assistantIdCookie: string ) => { - const updatedAssistant = await editCustomAssistant( - { + const updatedAssistant = await editCustomAssistant({ + editedAssistant: { is_default: true, iconData: { iconName: "User", @@ -215,9 +267,9 @@ export function useAssistants() { tools: undefined, systemPrompt: undefined, }, - assistantIdCookie, - userId - ); + assistantId: assistantIdCookie, + userId, + }); if (!updatedAssistant) { const ghIssueTitle = "Failed to set default assistant"; @@ -283,8 +335,8 @@ export function useAssistants() { if (!userAssistants.length) { // No assistants found, create a new assistant and set it as the default. - await createCustomAssistant( - { + await createCustomAssistant({ + newAssistant: { iconData: { iconName: "User", iconColor: "#000000", @@ -293,8 +345,8 @@ export function useAssistants() { description: "Your default assistant.", is_default: true, }, - userId - ); + userId, + }); // Return early because this function will set the selected assistant and assistants state. setIsLoadingAllAssistants(false); @@ -311,8 +363,8 @@ export function useAssistants() { const firstAssistant = userAssistants.sort((a, b) => { return a.created_at.localeCompare(b.created_at); })[0]; - const updatedAssistant = await editCustomAssistant( - { + const updatedAssistant = await editCustomAssistant({ + editedAssistant: { is_default: true, iconData: { iconName: @@ -338,9 +390,9 @@ export function useAssistants() { | string | undefined) || undefined, }, - firstAssistant.assistant_id, - userId - ); + assistantId: firstAssistant.assistant_id, + userId, + }); setSelectedAssistant(updatedAssistant); } else { diff --git a/src/hooks/useThread.tsx b/src/hooks/useThread.tsx index dc8035a9..e37635f1 100644 --- a/src/hooks/useThread.tsx +++ b/src/hooks/useThread.tsx @@ -128,7 +128,6 @@ export function useThread() { setThreadId(thread.thread_id); setCookie(THREAD_ID_COOKIE_NAME, thread.thread_id); setModelName(customModelName); - console.log("setting model config", customModelConfig); setModelConfig(customModelName, customModelConfig); await getUserThreads(userId); return thread; diff --git a/src/hooks/useUser.tsx b/src/hooks/useUser.tsx index 232c63bb..f917e2b5 100644 --- a/src/hooks/useUser.tsx +++ b/src/hooks/useUser.tsx @@ -9,7 +9,7 @@ export function useUser() { async function getUser() { if (user) { setLoading(false); - return; + return user; } const supabase = createSupabaseClient(); @@ -19,6 +19,7 @@ export function useUser() { } = await supabase.auth.getUser(); setUser(supabaseUser || undefined); setLoading(false); + return supabaseUser; } return { diff --git a/src/lib/supabase/verify_user_server.ts b/src/lib/supabase/verify_user_server.ts index 799520f4..f85cad59 100644 --- a/src/lib/supabase/verify_user_server.ts +++ b/src/lib/supabase/verify_user_server.ts @@ -1,10 +1,18 @@ -import { User } from "@supabase/supabase-js"; +import { Session, User } from "@supabase/supabase-js"; import { createClient } from "./server"; -export async function verifyUserAuthenticated(): Promise { +export async function verifyUserAuthenticated(): Promise< + { user: User; session: Session } | undefined +> { const supabase = createClient(); const { data: { user }, } = await supabase.auth.getUser(); - return user || undefined; + const { + data: { session }, + } = await supabase.auth.getSession(); + if (!user || !session) { + return undefined; + } + return { user, session }; } diff --git a/yarn.lock b/yarn.lock index 1eaa035a..6d237aca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -835,10 +835,10 @@ zod "^3.22.3" zod-to-json-schema "^3.22.5" -"@langchain/core@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.33.tgz#7efcbdc06b1e9f24b774e5aa1c594203d28c9e18" - integrity sha512-gIszaRKWmP1HEgOhJLJaMiTMH8U3W9hG6raWihwpCTb0Ns7owjrmaqmgMa9h3W4/0xriaKfrfFBd6tepKsfxZA== +"@langchain/core@^0.3.36": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.36.tgz#00824a3b32e8eaf8f2d8fc8f0d97d513d2771e45" + integrity sha512-lOS6f5o2MarjGPomHPhzde9xI3lZW2NIOEdCv0dvjb1ZChWhwXWHtAMHSZmuSB53ySzDWAMkimimHd+Yqz5MwQ== dependencies: "@cfworker/json-schema" "^4.0.2" ansi-styles "^5.0.0" @@ -853,10 +853,10 @@ zod "^3.22.4" zod-to-json-schema "^3.22.3" -"@langchain/google-genai@^0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@langchain/google-genai/-/google-genai-0.1.6.tgz#71c58fdd31ff813e7119af779631f07cdc139835" - integrity sha512-LF3fan9pvgFa1vw2/IYGhi5KjppE0OvPFX3QQBUshBLpXWERP+BSpSD7jcXyqm9Kf7DcFj7w5/2knKeEwih8Xg== +"@langchain/google-genai@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@langchain/google-genai/-/google-genai-0.1.7.tgz#02353d09575f72bdcee3fff8c110173b598b2912" + integrity sha512-m0cg2VKxxySFfiIFfoMEt22sM4DifmV7AYUN3/DBG8AABG1qLMpNTXUA+b9DGbRD21JFCQpny8Z8eFKRSZFHzA== dependencies: "@google/generative-ai" "^0.21.0" zod-to-json-schema "^3.22.4" @@ -906,10 +906,10 @@ zod "^3.22.4" zod-to-json-schema "^3.22.3" -"@langchain/openai@^0.3.17": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.3.17.tgz#5b8613ac8d849da90f3ecd8368ae7389fca4ee13" - integrity sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag== +"@langchain/openai@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.4.2.tgz#1259bf56c4948ed2301d366e2fe945c29dfb53bc" + integrity sha512-Cuj7qbVcycALTP0aqZuPpEc7As8cwiGaU21MhXRyZFs+dnWxKYxZ1Q1z4kcx6cYkq/I+CNwwmk+sP+YruU73Aw== dependencies: js-tiktoken "^1.0.12" openai "^4.77.0" @@ -2338,6 +2338,11 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== +"@types/pdf-parse@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@types/pdf-parse/-/pdf-parse-1.1.4.tgz#21a539efd2f16009d08aeed3350133948b5d7ed1" + integrity sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg== + "@types/phoenix@^1.5.4": version "1.6.5" resolved "https://registry.yarnpkg.com/@types/phoenix/-/phoenix-1.6.5.tgz#5654e14ec7ad25334a157a20015996b6d7d2075e" @@ -3405,7 +3410,7 @@ date-fns@^4.1.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== -debug@^3.2.7: +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -6561,6 +6566,11 @@ node-domexception@1.0.0: resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== +node-ensure@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" + integrity sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw== + node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -6872,6 +6882,14 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== +pdf-parse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pdf-parse/-/pdf-parse-1.1.1.tgz#745e07408679548b3995ff896fd38e96e19d14a7" + integrity sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A== + dependencies: + debug "^3.1.0" + node-ensure "^0.0.0" + picocolors@^1.0.0, picocolors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" @@ -7918,6 +7936,7 @@ streamsearch@^1.1.0: integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8006,6 +8025,7 @@ stringify-entities@^4.0.0: character-entities-legacy "^3.0.0" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==