import React, { createContext, useContext, ReactNode, useState, useEffect, } from "react"; import { useStream } from "@langchain/langgraph-sdk/react"; 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 { useThreadsContext } from "./Thread"; import { TooltipIconButton } from "@/components/ui/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"; import { GraphState, GraphUpdate } from "@open-swe/shared/open-swe/types"; import { CustomNodeEvent, isCustomNodeEvent, } from "@open-swe/shared/open-swe/custom-node-events"; const useTypedStream = useStream< GraphState, { UpdateType: GraphUpdate; CustomEventType: UIMessage | RemoveUIMessage; } >; type StreamContextType = ReturnType & { customEvents: CustomNodeEvent[]; }; 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 [customEvents, setCustomEvents] = useState([]); const { refreshThreads } = useThreadsContext(); const streamValue = useTypedStream({ apiUrl, assistantId, reconnectOnMount: true, threadId: threadId ?? null, onCustomEvent: (event, options) => { if (isCustomNodeEvent(event)) { setCustomEvents((prev) => [...prev, event]); } if (isUIMessage(event) || isRemoveUIMessage(event)) { options.mutate((prev) => { const ui = uiMessageReducer(prev.ui ?? [], event); return { ...prev, ui }; }); } }, onThreadId: (id) => { setThreadId(id); sleep().then(() => { refreshThreads().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 >(() => { if (typeof window === "undefined") return null; const cached = localStorage.getItem("github_app_installed"); return cached === "true" ? true : cached === "false" ? false : null; }); const [isCheckingAppInstallation, setIsCheckingAppInstallation] = useState(false); const { token: githubToken, fetchToken: fetchGitHubToken, isLoading: isTokenLoading, } = useGitHubToken(); useEffect(() => { checkAuthStatus(); }, []); useEffect(() => { if (isAuth) { const cachedInstallationStatus = localStorage.getItem( "github_app_installed", ); if (cachedInstallationStatus === "true") { setHasGitHubAppInstalled(true); // Fetch token if we don't have one yet if (!githubToken && !isTokenLoading) { fetchGitHubToken(); } } else if (cachedInstallationStatus === "false") { setHasGitHubAppInstalled(false); } else { checkGitHubAppInstallation(); } } }, [isAuth, githubToken]); 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); localStorage.setItem("github_app_installed", "true"); await fetchGitHubToken(); } else { const errorData = await response.json(); if (errorData.error.includes("installation")) { setHasGitHubAppInstalled(false); localStorage.setItem("github_app_installed", "false"); } else { setHasGitHubAppInstalled(false); localStorage.setItem("github_app_installed", "false"); } } } catch (error) { console.error("Error checking GitHub App installation:", error); setHasGitHubAppInstalled(false); localStorage.setItem("github_app_installed", "false"); } finally { setIsCheckingAppInstallation(false); } }; const handleLogin = () => { setIsLoading(true); window.location.href = "/api/auth/github/login"; }; const handleInstallGitHubApp = () => { setIsLoading(true); localStorage.removeItem("github_app_installed"); 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) // Only show modal if we've definitively determined the app is not installed // Don't show while we're still loading or have cached positive status if (isAuth && hasGitHubAppInstalled === false && !isTokenLoading) { 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.

); } // Only render StreamSession when we have a valid token if (!githubToken) { return (

Loading...

Setting up your GitHub integration...

); } return ( {children} ); }; 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;