import React, { createContext, useContext, ReactNode, useState, useEffect, } from "react"; import { useStream } from "@langchain/langgraph-sdk/react"; import { type Message } from "@langchain/langgraph-sdk"; import { uiMessageReducer, isUIMessage, isRemoveUIMessage, type UIMessage, type RemoveUIMessage, } from "@langchain/langgraph-sdk/react-ui"; import { useQueryState } from "nuqs"; import { LangGraphLogoSVG } from "@/components/icons/langgraph"; import { useThreads } from "./Thread"; import { TooltipIconButton } from "@/components/thread/tooltip-icon-button"; import { Copy, CopyCheck, ArrowRight } from "lucide-react"; import { motion } from "framer-motion"; import { Button } from "@/components/ui/button"; import { GitHubSVG } from "@/components/icons/github"; import { useGitHubToken } from "@/hooks/useGitHubToken"; type TargetRepository = { owner: string; repo: string }; export type StateType = { messages: Message[]; ui?: UIMessage[]; targetRepository?: TargetRepository; }; const useTypedStream = useStream< StateType, { UpdateType: { messages?: Message[] | Message | string; ui?: (UIMessage | RemoveUIMessage)[] | UIMessage | RemoveUIMessage; context?: Record; targetRepository?: TargetRepository; }; CustomEventType: UIMessage | RemoveUIMessage; } >; type StreamContextType = ReturnType; const StreamContext = createContext(undefined); async function sleep(ms = 4000) { return new Promise((resolve) => setTimeout(resolve, ms)); } const StreamSession = ({ children, apiUrl, assistantId, githubToken, }: { children: ReactNode; apiUrl: string; assistantId: string; githubToken: string; }) => { const [threadId, setThreadId] = useQueryState("threadId"); const { getThreads, setThreads } = useThreads(); const githubAccessToken = document.cookie .split("; ") .find((row) => row.startsWith("x-github_access_token=")) ?.split("=")[1] || ""; const streamValue = useTypedStream({ apiUrl, assistantId, threadId: threadId ?? null, defaultHeaders: { "x-github-installation-token": githubToken, "x-github-access-token": githubAccessToken, }, onCustomEvent: (event, options) => { if (isUIMessage(event) || isRemoveUIMessage(event)) { options.mutate((prev) => { const ui = uiMessageReducer(prev.ui ?? [], event); return { ...prev, ui }; }); } }, onThreadId: (id) => { setThreadId(id); // Refetch threads list when thread ID changes. // Wait for some seconds before fetching so we're able to get the new thread that was created. sleep().then(() => getThreads().then(setThreads).catch(console.error)); }, }); return ( {children} ); }; export const StreamProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { const baseCopyTooltipText = "Copy environment variables"; const [copyTooltipText, setCopyTooltipText] = useState(baseCopyTooltipText); const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? ""; const assistantId: string | undefined = process.env.NEXT_PUBLIC_ASSISTANT_ID ?? ""; const [isAuth, setIsAuth] = useState(null); const [isLoading, setIsLoading] = useState(false); const [hasGitHubAppInstalled, setHasGitHubAppInstalled] = useState< boolean | null >(null); const [isCheckingAppInstallation, setIsCheckingAppInstallation] = useState(false); const { token: githubToken, fetchToken: fetchGitHubToken } = useGitHubToken(); useEffect(() => { checkAuthStatus(); }, []); useEffect(() => { if (isAuth) { checkGitHubAppInstallation(); } }, [isAuth]); const checkAuthStatus = async () => { try { const response = await fetch("/api/auth/status"); const data = await response.json(); setIsAuth(data.authenticated); } catch (error) { console.error("Error checking auth status:", error); setIsAuth(false); } }; const checkGitHubAppInstallation = async () => { setIsCheckingAppInstallation(true); try { const response = await fetch("/api/github/repositories"); if (response.ok) { setHasGitHubAppInstalled(true); // If the app is installed, fetch a token await fetchGitHubToken(); } else { const errorData = await response.json(); if (errorData.error.includes("installation")) { setHasGitHubAppInstalled(false); } else { // If there's a different error, we'll assume the app is not installed setHasGitHubAppInstalled(false); } } } catch (error) { console.error("Error checking GitHub App installation:", error); setHasGitHubAppInstalled(false); } finally { setIsCheckingAppInstallation(false); } }; const handleLogin = () => { setIsLoading(true); window.location.href = "/api/auth/github/login"; }; const handleInstallGitHubApp = () => { setIsLoading(true); window.location.href = "/api/github/installation"; }; if (!apiUrl || !assistantId) { return (

Environment Variables Missing

Whoops, looks like you don't have an API URL or assistant ID set in your environment variables. Please make sure you have both of these set before continuing.

{ const textToCopy = `NEXT_PUBLIC_API_URL=${apiUrl}\nNEXT_PUBLIC_ASSISTANT_ID=${assistantId}`; navigator.clipboard.writeText(textToCopy).then(() => { setCopyTooltipText("Copied!"); setTimeout( () => setCopyTooltipText(baseCopyTooltipText), 2000, ); }); }} className="absolute top-2 right-2 cursor-pointer" tooltip={copyTooltipText} > {copyTooltipText === baseCopyTooltipText ? ( ) : ( )} NEXT_PUBLIC_API_URL={apiUrl} NEXT_PUBLIC_ASSISTANT_ID={assistantId}
); } if (!isAuth) { return (

Get started

Connect your GitHub account to get started with Open SWE.

); } // Step 2: GitHub App Installation (only show if authenticated but app not installed) if ((isAuth && hasGitHubAppInstalled === false) || !githubToken) { return (

One more step

1. GitHub Login ✓ 2. Repository Access

Great! Now we need access to your GitHub repositories. Install our GitHub App to grant access to specific repositories.

You'll be redirected to GitHub where you can select which repositories to grant access to.

); } return ( {children} ); }; // Create a custom hook to use the context export const useStreamContext = (): StreamContextType => { const context = useContext(StreamContext); if (context === undefined) { throw new Error("useStreamContext must be used within a StreamProvider"); } return context; }; export default StreamContext;