diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 063af3df..f744fe0e 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -35,6 +35,7 @@ "setup/intro", "setup/development", "setup/authentication", + "setup/customization", "setup/monorepo", "setup/ci" ] diff --git a/apps/docs/setup/customization.mdx b/apps/docs/setup/customization.mdx new file mode 100644 index 00000000..53a6cb95 --- /dev/null +++ b/apps/docs/setup/customization.mdx @@ -0,0 +1,108 @@ +--- +title: "Custom Framework Configuration" +description: "How to configure and customize OpenSWE for custom libraries" +--- + +Open SWE includes support for LangGraph development through the **LangGraph Engineer** toggle. This feature adds framework-specific prompts and MCP tools for building LangGraph agents and workflows. + +You can use a similar approach to build your own customization. + +## LangGraph Engineer Configuration + +### Setting Configuration in the UI + +Enabling the **LangGraph Engineer** toggle in the main input area adds specific LangGraph prompts and allows LangGraph documentation access. + +This sets the following configuration: + +```typescript +const config = { + configurable: { + customFramework: true + } +} +``` + +## How Custom Framework Configuration Works + +The configuration is passed to all agent nodes and used to conditionally include specialized prompts: + +```typescript +// In prompt formatting functions +.replace( + "{CUSTOM_FRAMEWORK_PROMPT}", + shouldUseCustomFramework(config) ? CUSTOM_FRAMEWORK_PROMPT : "", +) +``` + +The `shouldUseCustomFramework(config)` function checks if `config.configurable?.customFramework === true`. + +## Custom Framework Files + +When `customFramework` is `true`, we inject a series of custom prompts and tools into the agent. The table below shows every place in the codebase that needs updating if you're customizing it for a framework other than LangGraph. + +### Prompt Files + +| Component | File | Prompt Constant | Description | +|-----------|------|-----------------|-------------| +| Planner | [prompt.ts](https://github.com/langchain-ai/open-swe/tree/main/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts) | `EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT` | Adds framework documentation access instructions | +| Planner | [prompt.ts](https://github.com/langchain-ai/open-swe/tree/main/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts) | `EXTERNAL_FRAMEWORK_PLAN_PROMPT` | Provides framework-specific planning requirements | +| Programmer | [prompt.ts](https://github.com/langchain-ai/open-swe/tree/main/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts) | `CUSTOM_FRAMEWORK_PROMPT` | Comprehensive framework implementation patterns and best practices | +| Reviewer | [prompt.ts](https://github.com/langchain-ai/open-swe/tree/main/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/prompt.ts) | `CUSTOM_FRAMEWORK_PROMPT` | Framework validation and testing requirements | + +### Agent & UI Configuration Files + +| Component | File | Description | +|-----------|------|-------------| +| Toggle Button | [default-view.tsx](https://github.com/langchain-ai/open-swe/tree/main/apps/web/src/components/v2/default-view.tsx) | Contains the LangGraph Engineer toggle button - modify this to add your own button or change the existing one | +| Framework Logic | [should-use-custom-framework.ts](https://github.com/langchain-ai/open-swe/tree/main/apps/open-swe/src/utils/should-use-custom-framework.ts) | Logic function that determines when to enable custom framework features. Currently a boolean based on the UI toggle | +| Documentation | [constants.ts](https://github.com/langchain-ai/open-swe/tree/main/packages/shared/src/constants.ts) | MCP server configuration for framework documentation access | + + +## Customizing for Your Own Framework + +Follow these steps to adapt Open SWE for your own framework: + +### Modify Prompt Constants + +1. **Update prompt files** from the table above to replace LangGraph-specific content: + - Replace `CUSTOM_FRAMEWORK_PROMPT` with your framework's patterns + - Update `EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT` with your docs access instructions + - Modify `EXTERNAL_FRAMEWORK_PLAN_PROMPT` with your planning requirements + +### Update UI Configuration + +1. **Modify the toggle button** in [`default-view.tsx`](https://github.com/langchain-ai/open-swe/tree/main/apps/web/src/components/v2/default-view.tsx): + - Change button text from "LangGraph Engineer" to your framework name + - Update tooltip text and descriptions + +2. **Optional**: Create a new toggle button for your framework while keeping the LangGraph one + +### Configure Framework Logic + +You can either: + +- **Reuse the existing `customFramework: true` configuration** and modify the prompts to match your framework instead of LangGraph (no additional code changes needed) +- **Create a separate config variable** by adding a new field in your graph configuration (e.g., `yourFramework: true`) and adding the detection logic similar to [`should-use-custom-framework.ts`](https://github.com/langchain-ai/open-swe/tree/main/apps/open-swe/src/utils/should-use-custom-framework.ts) + +### Add Documentation Access + +1. **Configure MCP servers** in [`constants.ts`](https://github.com/langchain-ai/open-swe/tree/main/packages/shared/src/constants.ts): + ```typescript + export const DEFAULT_MCP_SERVERS = { + "your-framework-docs-mcp": { + command: "uvx", + args: ["your-framework-docs-mcp"], + }, + }; + ``` + +### Build and Test + +1. **Rebuild the application**: + ```bash + cd apps/open-swe + yarn build + ``` + +2. **Test your customization** by enabling the toggle and verifying framework-specific prompts are used diff --git a/apps/docs/usage/ui.mdx b/apps/docs/usage/ui.mdx index 67356b7d..d3f1fdea 100644 --- a/apps/docs/usage/ui.mdx +++ b/apps/docs/usage/ui.mdx @@ -173,6 +173,30 @@ Once a plan is accepted, the Programmer graph executes the implementation. work is preserved even if the session is interrupted. +## LangGraph Engineer Toggle + +The UI includes a **LangGraph Engineer** toggle button that optimizes the agent's performance when working with LangGraph code: + + + The LangGraph Engineer toggle is located in the main input area, represented by the Open SWE icon. + + +### When to Enable LangGraph Engineer + +Enable this toggle when your request involves creating or modifying LangGraph agents or workflows. It is very useful for building LLM apps from scratch. + +### What LangGraph Engineer Does + +When enabled, the toggle: + +- **Provides specific prompts**: Adds LangGraph-specific guidance and best practices to all agent prompts +- **Includes documentation access**: Agents can automatically fetch up-to-date LangGraph documentation during planning and implementation through an MCP server. +- **Follows LangGraph patterns**: Ensures agents use proper LangGraph structure, primitives, and deployment methods. + + + Enable LangGraph Engineer for any project that imports from `@langchain/langgraph` or `langgraph` to get the best results. + + ## Getting Started To begin using the Open SWE UI: diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts index 8e2a0080..1f153e6a 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-message/index.ts @@ -20,7 +20,11 @@ import { formatFollowupMessagePrompt, isFollowupRequest, } from "../../utils/followup.js"; -import { SYSTEM_PROMPT } from "./prompt.js"; +import { + SYSTEM_PROMPT, + EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT, + EXTERNAL_FRAMEWORK_PLAN_PROMPT, +} from "./prompt.js"; import { getRepoAbsolutePath } from "@openswe/shared/git"; import { isLocalMode, @@ -41,6 +45,7 @@ import { } from "../../../../utils/caching.js"; import { createViewTool } from "../../../../tools/builtin-tools/view.js"; import { shouldCreateIssue } from "../../../../utils/should-create-issue.js"; +import { shouldUseCustomFramework } from "../../../../utils/should-use-custom-framework.js"; const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode"); @@ -80,7 +85,18 @@ function formatSystemPrompt( state.codebaseTree || "No codebase tree generated yet.", ) .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)) - .replace("{USER_REQUEST_PROMPT}", formatUserRequestPrompt(state.messages)); + .replace("{USER_REQUEST_PROMPT}", formatUserRequestPrompt(state.messages)) + .replace( + "{EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT}", + shouldUseCustomFramework(config) + ? EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT + : "", + ) + .replace( + "{EXTERNAL_FRAMEWORK_PLAN_PROMPT}", + shouldUseCustomFramework(config) ? EXTERNAL_FRAMEWORK_PLAN_PROMPT : "", + ) + .replace("{DEV_SERVER_PROMPT}", ""); // Always empty until we add dev server tool } export async function generateAction( diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts b/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts index 2d656ca1..b2a9c102 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-message/prompt.ts @@ -34,8 +34,11 @@ Your sole objective in this phase is to gather comprehensive context about the c - You will always be able to gather more context after the planning phase, so ensure that the actions you perform in this planning phase are only the most necessary and targeted actions to gather context. - Avoid rabbit holes for gathering context. You should always first consider whether or not the action you're about to take is necessary to generate a plan for the user's request. If it is not, do not take it. 9. Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd. You may use cd if the User explicitly requests it. + {EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT} +{EXTERNAL_FRAMEWORK_PLAN_PROMPT} + ### Grep search tool - Use the \`grep\` tool for all file searches. The \`grep\` tool allows for efficient simple and complex searches, and it respect .gitignore patterns. @@ -74,6 +77,8 @@ Your sole objective in this phase is to gather comprehensive context about the c Parameters: - \`url\`: The URL to fetch the contents of - \`query\`: The query to search for within the document. This should be a natural language query. The query will be passed to a separate LLM and prompted to extract context from the document which answers this query. + + {DEV_SERVER_PROMPT} @@ -96,3 +101,115 @@ Your sole objective in this phase is to gather comprehensive context about the c {USER_REQUEST_PROMPT} `; + +export const EXTERNAL_FRAMEWORK_DOCUMENTATION_PROMPT = ` +10. LangGraph Documentation Access: + - You have access to the langgraph-docs-mcp__list_doc_sources, langgraph-docs-mcp__fetch_docs tools. Use them when planning AI agents, workflows, or multi-step LLM applications that involve LangGraph APIs or when user specifies they want to use LangGraph. + - In the case of generating a plan, mention in the plan to use the langgraph-docs-mcp__list_doc_sources, langgraph-docs-mcp__fetch_docs tools to get up to date information on the LangGraph API while coding. + - The list_doc_sources tool will return a list of all the documentation sources available to you. By default, you should expect the url to LangGraph python and the javascript documentation to be available. + - The fetch_docs tool will fetch the documentation for the given source. You are expected to use this tool to get up to date information by passing in a particular url. It returns the documentation as a markdown string. + - [Important] In some cases, links to other pages in the LangGraph documentation will use relative paths, such as ../../langgraph-platform/local-server. When this happens: + - Determine the base URL from which the current documentation was fetched. It should be the url of the page you you read the relative path from. + - For ../, go one level up in the URL hierarchy. + - For ../../, go two levels up, then append the relative path. + - If the current page is: https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/setup/ And you encounter a relative link: ../../langgraph-platform/local-server, + - Go up two levels: https://langchain-ai.github.io/langgraph/tutorials/get-started/ + - Append the relative path to form the full URL: https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/local-server + - If you get a response like Encountered an HTTP error: Client error '404' for url, it probably means that the url you created with relative path is incorrect so you should try constructing it again. +`; + +export const EXTERNAL_FRAMEWORK_PLAN_PROMPT = ` + + + When planning LangGraph agents, ensure tasks include: + + **Structure Requirements:** + - If any LangGraph-related files exist in the codebase (graph.py, main.py, app.py, or any files with graph imports/exports), do not create a newagent.py. Always work with existing files and follow the established patterns. + - Create agent.py when building a new LangGraph project from an empty directory with zero existing graph-related files. + - For existing projects, always follow the existing structure and never impose new patterns. + - Proper state management with TypedDict or Pydantic BaseModel + - Never add a checkpointer unless explicitly requested by user + + **Deployment-First Planning:** + - Plan to use prebuilt components: create_react_agent, supervisor patterns, swarm patterns + - Only plan to use custom StateGraph when prebuilt components don't fit the use case + - Always include tasks for runtime testing with dev_server + - Plan for \`langgraph dev\` testing after implementation + + **Critical Error Prevention in Plans:** + - State updates must return dictionaries, not full state objects + - Message objects are not strings - plan for .content property extraction + - Always plan for exporting compiled graph as 'app' variable + - Plan for type safety verification before chaining operations + + **Required Testing Tasks:** + - Include dev_server task after any LangGraph implementation + - Plan for \`langgraph dev\` command testing + - Plan for sending test requests to verify agent responses + - Plan for reviewing server logs for initialization issues + + + + **Streamlit + LangGraph Integration:** + - Plan for nest_asyncio setup tasks + - Plan for session state management tasks + - Plan for form widget constraints handling + + **FastAPI + LangGraph Integration:** + - Plan for async endpoint patterns + - Plan for proper event loop management + + **Multi-Framework Integration:** + - Plan debugging verification tasks with test markers + - Plan for config propagation verification + - Plan for integration point testing + + + **LangGraph Core Concepts:** + - https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/ + - https://langchain-ai.github.io/langgraph/how-tos/pass-config-to-tools/ + + **LangGraph Patterns:** + - https://langchain-ai.github.io/langgraph/reference/supervisor/ + - https://langchain-ai.github.io/langgraph/reference/swarm/ + + **LangGraph Streaming & Interrupts (needed when user input required):** + - https://langchain-ai.github.io/langgraph/how-tos/stream-updates/ + - https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#stream + - https://langchain-ai.github.io/langgraph/concepts/streaming/#whats-possible-with-langgraph-streaming + - https://docs.langchain.com/langgraph-platform/interrupt-concurrent + + **Framework Integration:** + - https://docs.streamlit.io/library/api-reference/session-state + - https://docs.streamlit.io/knowledge-base/using-streamlit/how-to-use-async-await + - https://docs.python.org/3/library/asyncio-dev.html#common-mistakes + - https://github.com/erdewit/nest_asyncio + +`; + +export const DEV_SERVER_PROMPT = ` +### Dev server tool + The \`dev_server\` tool allows you to start development servers and monitor their behavior for debugging purposes. + You SHOULD use this tool when reviewing any changes to web applications, APIs, or services. + Static code review is insufficient - you must verify runtime behavior when creating langgraph agents. + + **You should always use this tool when:** + - Reviewing API modifications (verify endpoints respond properly) + - Investigating server startup issues or runtime errors + + Common development server commands by technology: + - **Python/LangGraph**: \`langgraph dev\` (for LangGraph applications) + - **Node.js/React**: \`npm start\`, \`npm run dev\`, \`yarn start\`, \`yarn dev\` + - **Python/Django**: \`python manage.py runserver\` + - **Python/Flask**: \`python app.py\`, \`flask run\` + - **Python/FastAPI**: \`uvicorn main:app --reload\` + - **Go**: \`go run .\`, \`go run main.go\` + - **Ruby/Rails**: \`rails server\`, \`bundle exec rails server\` + + Parameters: + - \`command\`: The development server command to execute (e.g., ["langgraph", "dev"] or ["npm", "start"]) + - \`request\`: HTTP request to send to the server for testing (JSON format with url, method, headers, body) + - \`workdir\`: Working directory for the command + - \`wait_time\`: Time to wait in seconds before sending request (default: 10) + + The tool will start the server, send a test request, capture logs, and return the results for your review.`; diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts b/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts index 3b926f98..3c5d057e 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-plan/index.ts @@ -20,14 +20,22 @@ import { stopSandbox } from "../../../../utils/sandbox.js"; import { z } from "zod"; import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js"; import { getScratchpad } from "../../utils/scratchpad-notes.js"; -import { SCRATCHPAD_PROMPT, SYSTEM_PROMPT } from "./prompt.js"; +import { + SCRATCHPAD_PROMPT, + SYSTEM_PROMPT, + CUSTOM_FRAMEWORK_PROMPT, +} from "./prompt.js"; +import { shouldUseCustomFramework } from "../../../../utils/should-use-custom-framework.js"; import { DO_NOT_RENDER_ID_PREFIX } from "@openswe/shared/constants"; import { filterMessagesWithoutContent } from "../../../../utils/message/content.js"; import { getModelManager } from "../../../../utils/llms/model-manager.js"; import { trackCachePerformance } from "../../../../utils/caching.js"; import { isLocalMode } from "@openswe/shared/open-swe/local-mode"; -function formatSystemPrompt(state: PlannerGraphState): string { +function formatSystemPrompt( + state: PlannerGraphState, + config: GraphConfig, +): string { // It's a followup if there's more than one human message. const isFollowup = isFollowupRequest(state.taskPlan, state.proposedPlan); const scratchpad = getScratchpad(state.messages) @@ -48,6 +56,10 @@ function formatSystemPrompt(state: PlannerGraphState): string { scratchpad.length ? SCRATCHPAD_PROMPT.replace("{SCRATCHPAD}", scratchpad) : "", + ) + .replace( + "{ADDITIONAL_INSTRUCTIONS}", + shouldUseCustomFramework(config) ? CUSTOM_FRAMEWORK_PROMPT : "", ); } @@ -97,7 +109,7 @@ export async function generatePlan( .invoke([ { role: "system", - content: formatSystemPrompt(state), + content: formatSystemPrompt(state, config), }, ...inputMessages, ]); diff --git a/apps/open-swe/src/graphs/planner/nodes/generate-plan/prompt.ts b/apps/open-swe/src/graphs/planner/nodes/generate-plan/prompt.ts index b652c7dc..4938f57e 100644 --- a/apps/open-swe/src/graphs/planner/nodes/generate-plan/prompt.ts +++ b/apps/open-swe/src/graphs/planner/nodes/generate-plan/prompt.ts @@ -54,6 +54,8 @@ Create your plan following these guidelines: - If you have multiple simple steps that are related, and should be executed one after the other, combine them into a single step. - For example, if you have multiple steps to run a linter, formatter, etc., combine them into a single step. The same goes for passing arguments, or editing files. +{ADDITIONAL_INSTRUCTIONS} + ${GITHUB_WORKFLOWS_PERMISSIONS_PROMPT} @@ -72,3 +74,25 @@ Always format your plan items with proper markdown. Avoid large headers, but you {SCRATCHPAD} Remember: Your goal is to create a focused, executable plan that efficiently accomplishes the user's request using the context you've already gathered.`; + +export const CUSTOM_FRAMEWORK_PROMPT = ` +7. **LangGraph-specific planning:** + - When the user's request involves LangGraph code generation, editing, or bug fixing, ensure the execution agent will have access to up-to-date LangGraph documentation + - If the codebase contains any existing LangGraph files (such as graph.py, main.py, app.py) or any files that import/export graphs, do NOT plan new agent files unless asked. Always work with the existing file structure. + - Create agent.py when building a completely new LangGraph project from an empty directory with zero existing graph-related files. + - When LangGraph is involved, include a plan item to reference the langgraph-docs-mcp tools for current API information during implementation + +8. **LangGraph Documentation Access:** + - You have access to the langgraph-docs-mcp__list_doc_sources, langgraph-docs-mcp__fetch_docs tools. Use them when planning AI agents, workflows, or multi-step LLM applications that involve LangGraph APIs or when user specifies they want to use LangGraph. + - In the case of generating a plan, mention in the plan to use the langgraph-docs-mcp__list_doc_sources, langgraph-docs-mcp__fetch_docs tools to get up to date information on the LangGraph API while coding. + - The list_doc_sources tool will return a list of all the documentation sources available to you. By default, you should expect the url to LangGraph python and the javascript documentation to be available. + - The fetch_docs tool will fetch the documentation for the given source. You are expected to use this tool to get up to date information by passing in a particular url. It returns the documentation as a markdown string. + - [Important] In some cases, links to other pages in the LangGraph documentation will use relative paths, such as ../../langgraph-platform/local-server. When this happens: + - Determine the base URL from which the current documentation was fetched. It should be the url of the page you you read the relative path from. + - For ../, go one level up in the URL hierarchy. + - For ../../, go two levels up, then append the relative path. + - If the current page is: https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/setup/ And you encounter a relative link: ../../langgraph-platform/local-server, + - Go up two levels: https://langchain-ai.github.io/langgraph/tutorials/get-started/ + - Append the relative path to form the full URL: https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/local-server + - If you get a response like Encountered an HTTP error: Client error '404' for url, it probably means that the url you created with relative path is incorrect so you should try constructing it again. +`; diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts index 610df86b..e3719237 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts @@ -34,6 +34,7 @@ import { DYNAMIC_SYSTEM_PROMPT, STATIC_ANTHROPIC_SYSTEM_INSTRUCTIONS, STATIC_SYSTEM_INSTRUCTIONS, + CUSTOM_FRAMEWORK_PROMPT, } from "./prompt.js"; import { getRepoAbsolutePath } from "@openswe/shared/git"; import { getMissingMessages } from "../../../../utils/github/issue-messages.js"; @@ -66,6 +67,7 @@ import { shouldIncludeReviewCommentTool, createReplyToReviewTool, } from "../../../../tools/reply-to-review-comment.js"; +import { shouldUseCustomFramework } from "../../../../utils/should-use-custom-framework.js"; const logger = createLogger(LogLevel.INFO, "GenerateMessageNode"); @@ -93,6 +95,7 @@ const formatDynamicContextPrompt = (state: GraphState) => { const formatStaticInstructionsPrompt = ( state: GraphState, + config: GraphConfig, isAnthropicModel: boolean, ) => { return ( @@ -101,11 +104,17 @@ const formatStaticInstructionsPrompt = ( : STATIC_SYSTEM_INSTRUCTIONS ) .replaceAll("{REPO_DIRECTORY}", getRepoAbsolutePath(state.targetRepository)) - .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)); + .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)) + .replace( + "{CUSTOM_FRAMEWORK_PROMPT}", + shouldUseCustomFramework(config) ? CUSTOM_FRAMEWORK_PROMPT : "", + ) + .replace("{DEV_SERVER_PROMPT}", ""); // Always empty until we add dev server tool }; const formatCacheablePrompt = ( state: GraphState, + config: GraphConfig, args?: { isAnthropicModel?: boolean; excludeCacheControl?: boolean; @@ -117,7 +126,11 @@ const formatCacheablePrompt = ( // Cache Breakpoint 2: Static Instructions { type: "text", - text: formatStaticInstructionsPrompt(state, !!args?.isAnthropicModel), + text: formatStaticInstructionsPrompt( + state, + config, + !!args?.isAnthropicModel, + ), ...(!args?.excludeCacheControl ? { cache_control: { type: "ephemeral" } } : {}), @@ -236,6 +249,7 @@ async function createToolsAndPrompt( ...state, taskPlan: options.latestTaskPlan ?? state.taskPlan, }, + config, { isAnthropicModel: true, excludeCacheControl: false, @@ -254,6 +268,7 @@ async function createToolsAndPrompt( ...state, taskPlan: options.latestTaskPlan ?? state.taskPlan, }, + config, { isAnthropicModel: false, excludeCacheControl: true, diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts index 715c5f16..8579c049 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts @@ -188,12 +188,16 @@ ${CORE_BEHAVIOR_PROMPT} The \`mark_task_completed\` tool allows Claude to mark a task as completed. Parameters: - \`completed_task_summary\`: A summary of the completed task. This summary should include high level context about the actions you took to complete the task, and any other context which would be useful to another developer reviewing the actions you took. Ensure this is properly formatted using markdown. + + {DEV_SERVER_PROMPT} ${TOOL_USE_BEST_PRACTICES_PROMPT} ${CODING_STANDARDS_PROMPT} + {CUSTOM_FRAMEWORK_PROMPT} + ${COMMUNICATION_GUIDELINES_PROMPT} ${SPECIAL_TOOLS_PROMPT} @@ -219,6 +223,8 @@ ${CORE_BEHAVIOR_PROMPT} ${CODING_STANDARDS_PROMPT} + {CUSTOM_FRAMEWORK_PROMPT} + ${COMMUNICATION_GUIDELINES_PROMPT} ${SPECIAL_TOOLS_PROMPT} @@ -273,3 +279,506 @@ These are notes you took while gathering context for the plan: `; + +export const DEV_SERVER_PROMPT = ` +### Dev server tool + The \`dev_server\` tool allows you to start development servers and monitor their behavior for debugging purposes. + You SHOULD use this tool when reviewing any changes to web applications, APIs, or services. + Static code review is insufficient - you must verify runtime behavior when creating langgraph agents. + + **You should always use this tool when:** + - Reviewing API modifications (verify endpoints respond properly) + - Investigating server startup issues or runtime errors + + Common development server commands by technology: + - **Python/LangGraph**: \`langgraph dev\` (for LangGraph applications) + - **Node.js/React**: \`npm start\`, \`npm run dev\`, \`yarn start\`, \`yarn dev\` + - **Python/Django**: \`python manage.py runserver\` + - **Python/Flask**: \`python app.py\`, \`flask run\` + - **Python/FastAPI**: \`uvicorn main:app --reload\` + - **Go**: \`go run .\`, \`go run main.go\` + - **Ruby/Rails**: \`rails server\`, \`bundle exec rails server\` + + Parameters: + - \`command\`: The development server command to execute (e.g., ["langgraph", "dev"] or ["npm", "start"]) + - \`request\`: HTTP request to send to the server for testing (JSON format with url, method, headers, body) + - \`workdir\`: Working directory for the command + - \`wait_time\`: Time to wait in seconds before sending request (default: 10) + + The tool will start the server, send a test request, capture logs, and return the results for your review.`; + +export const CUSTOM_FRAMEWORK_PROMPT = ` + + + **MANDATORY FIRST STEP**: Before creating any files, search the codebase for existing LangGraph-related files. Look for: + - Files with names like: graph.py, main.py, app.py, agent.py, workflow.py + - Files containing: ".compile()", "StateGraph", "create_react_agent", "app =", graph exports + - Any existing LangGraph imports or patterns + + **If any LangGraph files exist**: Follow the existing structure exactly. Do not create new agent.py files. + + **Only create agent.py when**: Building from completely empty directory with zero existing LangGraph files: + 1. agent.py at project root with compiled graph exported as 'app' + 2. langgraph.json configuration file in same directory as the graph + 3. Proper state management with TypedDict or Pydantic BaseModel + + Example structure: + \`\`\`python + from langgraph.graph import StateGraph, START, END + # ... your state and node definitions ... + + # Build your graph + graph_builder = StateGraph(YourState) + # ... add nodes and edges ... + + # Export as 'app' for new agents from scratch + graph = graph_builder.compile() + app = graph # Required for new LangGraph agents. For existing projects, follow established patterns. + \`\`\` + 4. Test small components before building complex graphs + + + + - Incorrect interrupt() usage: It pauses execution, doesn't return values. + - Refer to documentation to refer to best interrupt handling practcies, including waiting for user input and proper handling of it. + - Wrong state update patterns: Return updates, not full state. + - Missing state type annotations. + - Missing state fields (current_field, user_input). + - Invalid edge conditions: Ensure all paths have valid transitions. + - Not handling error states properly. + - Not exporting graph as 'app' when creating new LangGraph agents from scratch. For existing projects, follow the established structure. + - Forgetting langgraph.json configuration. + - **Type assumption errors**: Assuming message objects are strings, or that state fields are certain types + - **Chain operations without type checking**: Like \`state.get("field", "")[-1].method()\` without verifying types + + + + **CRITICAL**: LangGraph state and message handling patterns: + + \`\`\`python + # CORRECT: Extract message content properly + result = agent.invoke({"messages": state["messages"]}) + if result.get("messages"): + final_message = result["messages"][-1] # This is a message object + content = final_message.content # This is the string content + + # WRONG: Treating message objects as strings + content = result["messages"][-1] # This is an object, not a string! + if content.startswith("Error"): # Will fail - objects don't have startswith() + \`\`\` + + **State Updates Must Be Dictionaries**: + \`\`\`python + def my_node(state: State) -> Dict[str, Any]: + # Do work... + return { + "field_name": extracted_string, # Always return dict updates + "messages": updated_message_list # Not the raw messages + } + \`\`\` + + + + - Interrupts only work with stream_mode="updates", not stream_mode="values" + - In "updates" mode, events are structured as {node_name: node_data, ...} + - Check for "__interrupt__" key directly in the event object + - Iterate through event.items() to access individual node outputs + - Interrupts appear as event["__interrupt__"] containing tuple of Interrupt objects + - Access interrupt data via interrupt_obj.value where interrupt_obj = event["__interrupt__"][0] + + - LangGraph Streaming: https://langchain-ai.github.io/langgraph/how-tos/stream-updates/ + - SDK Streaming: https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#stream + - Concurrent Interrupts: https://docs.langchain.com/langgraph-platform/interrupt-concurrent + + + + + **Use interrupt() when you need:** + - User approval for generated plans or proposed changes + - Human confirmation before executing potentially risky operations + - Additional clarification when the task is ambiguous + - User input for decision points that require human judgment + - Feedback on partially completed work before proceeding + + + + + **When building integrations, always start with debugging**: + + **Log Everything Initially**: + Use temporary print statements to understand the data flowing through your integration. + \`\`\`python + # Temporary debugging for new integrations + def my_integration_function(input_data, config): + print(f"=== DEBUG START ===") + print(f"Input type: {type(input_data)}") + print(f"Input data: {input_data}") + print(f"Config type: {type(config)}") + print(f"Config data: {config}") + + # Process... + result = process(input_data, config) + + print(f"Result type: {type(result)}") + print(f"Result data: {result}") + print(f"=== DEBUG END ===") + + return result + \`\`\` + + + + - **Backend Verification Pattern**: Always verify the receiving end actually uses configuration: + \`\`\`python + # WRONG: Assuming config is used + def my_node(state: State) -> Dict[str, Any]: + response = llm.invoke(state["messages"]) + return {"messages": [response]} + + # CORRECT: Actually using config + def my_node(state: State, config: RunnableConfig) -> Dict[str, Any]: + # Extract configuration + configurable = config.get("configurable", {}) + system_prompt = configurable.get("system_prompt", "Default prompt") + + # Use configuration in messages + messages = [SystemMessage(content=system_prompt)] + state["messages"] + response = llm.invoke(messages) + return {"messages": [response]} + \`\`\` + + + + - LangGraph Config: https://langchain-ai.github.io/langgraph/how-tos/pass-config-to-tools/ + - Streamlit Session State: https://docs.streamlit.io/library/api-reference/session-state + - Asyncio with Web Frameworks: https://docs.python.org/3/library/asyncio-eventloop.html#running-and-stopping-the-loop + + + + + - Test small components before building complex graphs + - **Avoid unnecessary complexity**: Before adding complex solutions, consider if simpler approaches with prebuilt components would achieve the same goals: + - Don't create redundant graph nodes that could be combined or simplified + - Check for duplicate processing or validation that could be consolidated + - Question whether additional nodes actually improve the workflow or just add complexity + - Prefer fewer, well-designed nodes over many small, redundant ones + - **Structured LLM Calls and Validation**: When working with LangGraph nodes that involve LLM calls, always use structured output with Pydantic dataclasses for validation and parsing: + - Use \`with_structured_output()\` method for LLM calls that need specific response formats + - Define Pydantic BaseModel classes for all structured data (state schemas, LLM responses, tool inputs/outputs) + - Validate and parse LLM responses using Pydantic models to ensure type safety and data integrity + - For conditional nodes relying on LLM decisions, use structured output to ensure the LLM returns the correct type of data + - Example: \`llm.with_structured_output(MyPydanticModel).invoke(messages)\` instead of raw string parsing + + + + + **CRITICAL**: All LangGraph agents should be written for DEPLOYMENT unless otherwise specified by the user. + + **Core Requirements:** + - NEVER ADD A CHECKPOINTER unless explicitly requested by user. + - Always export compiled graph as 'app'. + - Use prebuilt components when possible. + - Follow model preference hierarchy: Anthropic > OpenAI > Google. + - Keep state minimal (MessagesState usually sufficient). + + **AVOID unless user specifically requests:** + \`\`\`python + # Don't do this unless asked! + from langgraph.checkpoint.memory import MemorySaver + graph = create_react_agent(model, tools, checkpointer=MemorySaver()) + \`\`\` + + **For existing codebases**: + - Always search for existing graph export patterns first + - Work within the established structure rather than imposing new patterns + - Do not create agent.py if graphs are already exported elsewhere + + + + **Always use prebuilt components when possible** They are deployment-ready and well-tested. + + **Basic agents** - use create_react_agent: + \`\`\`python + from langgraph.prebuilt import create_react_agent + + # Simple, deployment-ready agent + graph = create_react_agent( + model=model, + tools=tools, + prompt="Your agent instructions here" + ) + app = graph + \`\`\` + + **Multi-agent systems** - use prebuilt patterns: + + **Supervisor pattern** (central coordination): + \`\`\`python + from langgraph_supervisor import create_supervisor + + supervisor = create_supervisor( + agents=[agent1, agent2], + model=model, + prompt="You coordinate between agents..." + ) + app = supervisor.compile() + \`\`\` + https://langchain-ai.github.io/langgraph/reference/supervisor/ + + **Swarm pattern** (dynamic handoffs): + \`\`\`python + from langgraph_swarm import create_swarm, create_handoff_tool + + alice = create_react_agent( + model, + [tools, create_handoff_tool(agent_name="Bob")], + prompt="You are Alice.", + name="Alice", + ) + + workflow = create_swarm([alice, bob], default_active_agent="Alice") + app = workflow.compile() + \`\`\` + https://langchain-ai.github.io/langgraph/reference/swarm/ + + **Only build custom StateGraph when:** + - Prebuilt components don't fit the specific use case. + - User explicitly asks for custom workflow. + - Complex branching logic required. + - Advanced streaming patterns needed. + + https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/ + + + + **AVOID these patterns:** + + **Mixing responsibilities in single nodes:** + \`\`\`python + # AVOID: LLM call + tool execution in same node + def bad_node(state): + ai_response = model.invoke(state["messages"]) # LLM call + tool_result = tool_node.invoke({"messages": [ai_response]}) # Tool execution + return {"messages": [...]} # Mixed concerns! + \`\`\` + + **PREFER: Separate nodes for separate concerns:** + \`\`\`python + # GOOD: LLM node only calls model + def llm_node(state): + return {"messages": [model.invoke(state["messages"])]} + + # GOOD: Tool node only executes tools + def tool_node(state): + return ToolNode(tools).invoke(state) + + # Connect with edges + workflow.add_edge("llm", "tools") + \`\`\` + + **Overly complex agents when simple ones suffice:** + \`\`\`python + # AVOID: Unnecessary complexity + workflow = StateGraph(ComplexState) + workflow.add_node("agent", agent_node) + workflow.add_node("tools", tool_node) + # ... 20 lines of manual setup when create_react_agent would work + \`\`\` + + **Overly complex state:** + \`\`\`python + # AVOID: Too many state fields + class State(TypedDict): + messages: List[BaseMessage] + user_input: str + current_step: int + metadata: Dict[str, Any] + history: List[Dict] + # ... many more fields + \`\`\` + + **Wrong export patterns:** + \`\`\`python + # AVOID: Wrong variable names or missing export + compiled_graph = workflow.compile() # Wrong name + # Missing: app = compiled_graph + \`\`\` + + **Incorrect interrupt() usage:** + \`\`\`python + # AVOID: Treating interrupt() as synchronous + result = interrupt("Please confirm action") # Wrong - doesn't return values + if result == "yes": # This won't work + proceed() + \`\`\` + **CORRECT**: interrupt() pauses execution for human input + \`\`\`python + interrupt("Please confirm action") + # Execution resumes after human provides input through platform + \`\`\` + https://langchain-ai.github.io/langgraph/concepts/streaming/#whats-possible-with-langgraph-streaming + + + + + **Framework-Specific Async Patterns**: + + 1. **Streamlit** (has its own event loop): + \`\`\`python + # WRONG: Creating new event loops + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + # WRONG: Using ThreadPoolExecutor + with ThreadPoolExecutor() as executor: + future = executor.submit(async_func) + + # CORRECT: Use nest_asyncio + import nest_asyncio + nest_asyncio.apply() + + # Then simple asyncio.run() + result = asyncio.run(async_function()) + \`\`\` + + 2. **FastAPI** (manages its own event loop): + \`\`\`python + # CORRECT: Use async endpoints directly + @app.post("/run") + async def run_agent(request: Request): + result = await agent.ainvoke(...) + return result + \`\`\` + + 3. **Jupyter** (IPython event loop): + \`\`\`python + # CORRECT: Use await directly in cells + result = await agent.ainvoke(...) + \`\`\` + + + + Common errors and solutions: + - \`RuntimeError: Event loop is closed\` → Use nest_asyncio + - \`RuntimeError: This event loop is already running\` → Use nest_asyncio or await directly + - \`asyncio.locks.Event object is bound to a different event loop\` → Don't create new loops + + + + - nest_asyncio: https://github.com/erdewit/nest_asyncio + - Streamlit async: https://docs.streamlit.io/knowledge-base/using-streamlit/how-to-use-async-await + - Python asyncio: https://docs.python.org/3/library/asyncio-dev.html#common-mistakes + + + + + + **Centralized State Pattern**: + \`\`\`python + def init_session_state(): + """Initialize all session state variables at once""" + defaults = { + # Static values + "messages": [], + "client": None, + "thread_id": None, + + # Dynamic tracking - prefix with 'current_' + "current_system_prompt": "Default prompt", + "current_config": {}, + + # UI state + "show_feedback": False, + "last_user_input": None, + } + + for key, default_value in defaults.items(): + if key not in st.session_state: + st.session_state[key] = default_value + + # Call at app start + init_session_state() + \`\`\` + + + + **Form API Constraints**: + \`\`\`python + # WRONG: Regular widgets in forms + with st.form("my_form"): + st.text_input("Input") + if st.button("Action"): # Not allowed + process() + + # CORRECT: Only form widgets in forms + with st.form("my_form"): + user_input = st.text_input("Input") + submitted = st.form_submit_button("Submit") + + # Process outside form + if submitted: + process(user_input) + + # Other actions outside form + if st.button("Other Action"): + other_process() + \`\`\` + + + + **Avoiding Infinite Reruns**: + \`\`\`python + # WRONG: Modifying state in main flow + st.session_state.counter += 1 # Causes rerun loop + + # CORRECT: Modify state in callbacks or conditionally + if st.button("Increment"): + st.session_state.counter += 1 + \`\`\` + + + + - Session State API: https://docs.streamlit.io/library/api-reference/session-state + - Forms reference: https://docs.streamlit.io/library/api-reference/control-flow/st.form + - Widget behavior: https://docs.streamlit.io/library/advanced-features/widget-behavior + + + + + **LLM MODEL PRIORITY** (follow this order): + \`\`\`python + # 1. PREFER: Anthropic + from langchain_anthropic import ChatAnthropic + model = ChatAnthropic(model="claude-3-5-sonnet-20241022") + + # 2. SECOND CHOICE: OpenAI + from langchain_openai import ChatOpenAI + model = ChatOpenAI(model="gpt-4o") + + # 3. THIRD CHOICE: Google + from langchain_google_genai import ChatGoogleGenerativeAI + model = ChatGoogleGenerativeAI(model="gemini-1.5-pro") + \`\`\` + **NOTE**: Assume API keys are available in environment - ignore missing key errors during development. + + + + Always use the documentation tools before implementing LangGraph code rather than relying on internal knowledge, as the API evolves rapidly. Specifically: + - Before creating new graph nodes or modifying existing ones. + - When implementing state schemas or message passing patterns. + - Before using LangGraph-specific decorators, annotations, or utilities. + - When working with conditional edges, dynamic routing, or subgraphs. + - Before implementing tool calling patterns within graph nodes. + Whenever you are building applications that require multiple frameworks and their integrations for e.g., LangGraph + Streamlit, LangGraph + Next.js, LangGraph + React, etc., you should consult the documentation of the framework you are using to ensure you are using the correct syntax and patterns. + + + - Determine the base URL from the current documentation page. + - For ../, go one level up in the URL hierarchy. + - For ../../, go two levels up, then append the relative path. + - Example: From https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/setup/ with link ../../langgraph-platform/local-server + - Go up two levels: https://langchain-ai.github.io/langgraph/tutorials/get-started/ + - Append path: https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/local-server + - If you get a response like Encountered an HTTP error: Client error '404' for url, it probably means that the url you created with relative path is incorrect so you should try constructing it again. + + +`; diff --git a/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts b/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts index a7985eaf..0067f54a 100644 --- a/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts +++ b/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/index.ts @@ -12,7 +12,12 @@ import { import { GraphConfig } from "@openswe/shared/open-swe/types"; import { createLogger, LogLevel } from "../../../../utils/logger.js"; import { getMessageContentString } from "@openswe/shared/messages"; -import { PREVIOUS_REVIEW_PROMPT, SYSTEM_PROMPT } from "./prompt.js"; +import { + PREVIOUS_REVIEW_PROMPT, + SYSTEM_PROMPT, + CUSTOM_FRAMEWORK_PROMPT, +} from "./prompt.js"; +import { shouldUseCustomFramework } from "../../../../utils/should-use-custom-framework.js"; import { getRepoAbsolutePath } from "@openswe/shared/git"; import { createGrepTool, @@ -40,7 +45,10 @@ import { BindToolsInput } from "@langchain/core/language_models/chat_models"; const logger = createLogger(LogLevel.INFO, "GenerateReviewActionsNode"); -function formatSystemPrompt(state: ReviewerGraphState): string { +function formatSystemPrompt( + state: ReviewerGraphState, + config: GraphConfig, +): string { const activePlan = getActivePlanItems(state.taskPlan); const tasksString = formatPlanPromptWithSummaries(activePlan); @@ -55,6 +63,10 @@ function formatSystemPrompt(state: ReviewerGraphState): string { .replaceAll("{CUSTOM_RULES}", formatCustomRulesPrompt(state.customRules)) .replaceAll("{CHANGED_FILES}", state.changedFiles) .replaceAll("{BASE_BRANCH_NAME}", state.baseBranchName) + .replace( + "{CUSTOM_FRAMEWORK_PROMPT}", + shouldUseCustomFramework(config) ? CUSTOM_FRAMEWORK_PROMPT : "", + ) .replaceAll("{COMPLETED_TASKS_AND_SUMMARIES}", tasksString) .replaceAll( "{DEPENDENCIES_INSTALLED}", @@ -68,6 +80,7 @@ function formatSystemPrompt(state: ReviewerGraphState): string { const formatCacheablePrompt = ( state: ReviewerGraphState, + config: GraphConfig, args?: { excludeCacheControl?: boolean; }, @@ -77,7 +90,7 @@ const formatCacheablePrompt = ( const segments: CacheablePromptSegment[] = [ { type: "text", - text: formatSystemPrompt(state), + text: formatSystemPrompt(state, config), ...(!args?.excludeCacheControl ? { cache_control: { type: "ephemeral" } } : {}), @@ -146,7 +159,9 @@ function createToolsAndPrompt( const anthropicMessages = [ { role: "system", - content: formatCacheablePrompt(state, { excludeCacheControl: false }), + content: formatCacheablePrompt(state, config, { + excludeCacheControl: false, + }), }, { role: "user", @@ -159,7 +174,9 @@ function createToolsAndPrompt( const nonAnthropicMessages = [ { role: "system", - content: formatCacheablePrompt(state, { excludeCacheControl: true }), + content: formatCacheablePrompt(state, config, { + excludeCacheControl: true, + }), }, { role: "user", diff --git a/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/prompt.ts b/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/prompt.ts index 7721d846..adb7bf40 100644 --- a/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/prompt.ts +++ b/apps/open-swe/src/graphs/reviewer/nodes/generate-review-actions/prompt.ts @@ -106,6 +106,8 @@ By reviewing these actions, and comparing them to the plan and original user req **REMINDER**: You are ONLY gathering context. Any non-read actions you believe are necessary to take can be executed after you've provided your final review. Only gather context right now in order to inform your final review, and to provide any additional steps to take after the review. + + {CUSTOM_FRAMEWORK_PROMPT} @@ -170,3 +172,32 @@ By reviewing these actions, and comparing them to the plan and original user req {USER_REQUEST_PROMPT} `; + +export const CUSTOM_FRAMEWORK_PROMPT = ` + + When reviewing LangGraph implementations: + + **1. Structure Validation**: + - Search for existing graph exports first (app =, .compile(), graph exports) + - Validate existing structure rather than expecting new agent.py files + - Only validate agent.py if no existing exports found + + **2. Quality Checks**: + - Verify structured outputs with Pydantic models for LLM calls + - Check for unnecessary complexity or duplicate nodes + - Ensure proper use of with_structured_output() for type safety + - Validate state management patterns + + **3. Compilation Testing**: + - Test basic import: python3 -c "import [module]; print('Success')" + - Test graph compilation: python3 -c "from [module] import app; print('Compiled')" + - Check langgraph.json validity if present + - Run available linters (ruff, mypy) but don't block on warnings + + **4. Success Criteria**: + - Module imports without errors + - Graph compiles successfully + - No blocking syntax/import issues + - Follows established patterns in codebase + +`; diff --git a/apps/open-swe/src/utils/mcp-client.ts b/apps/open-swe/src/utils/mcp-client.ts index b4a426cd..d9a39f44 100644 --- a/apps/open-swe/src/utils/mcp-client.ts +++ b/apps/open-swe/src/utils/mcp-client.ts @@ -8,6 +8,7 @@ import { } from "@openswe/shared/open-swe/mcp"; import { createLogger, LogLevel } from "./logger.js"; import { DEFAULT_MCP_SERVERS } from "@openswe/shared/constants"; +import { shouldUseCustomFramework } from "./should-use-custom-framework.js"; const logger = createLogger(LogLevel.INFO, "MCP Client"); @@ -86,8 +87,10 @@ export async function getMcpTools( config: GraphConfig, ): Promise { try { - // TODO: Remove default MCP servers obj once UI is implemented - const mergedServers: McpServers = { ...DEFAULT_MCP_SERVERS }; + let mergedServers: McpServers = {}; + if (shouldUseCustomFramework(config)) { + mergedServers = { ...DEFAULT_MCP_SERVERS }; + } const mcpServersConfig = config?.configurable?.["mcpServers"]; if (mcpServersConfig) { diff --git a/apps/open-swe/src/utils/should-use-custom-framework.ts b/apps/open-swe/src/utils/should-use-custom-framework.ts new file mode 100644 index 00000000..46985c36 --- /dev/null +++ b/apps/open-swe/src/utils/should-use-custom-framework.ts @@ -0,0 +1,5 @@ +import { GraphConfig } from "@openswe/shared/open-swe/types"; + +export function shouldUseCustomFramework(config: GraphConfig): boolean { + return config.configurable?.customFramework === true; +} diff --git a/apps/web/src/app/(v2)/chat/threads/page.tsx b/apps/web/src/app/(v2)/chat/threads/page.tsx index cbdf3359..7efc1058 100644 --- a/apps/web/src/app/(v2)/chat/threads/page.tsx +++ b/apps/web/src/app/(v2)/chat/threads/page.tsx @@ -17,7 +17,7 @@ import { useThreadsStatus } from "@/hooks/useThreadsStatus"; import { cn } from "@/lib/utils"; import { threadsToMetadata } from "@/lib/thread-utils"; import { UserPopover } from "@/components/user-popover"; -import { OpenSWELogoSVG } from "@/components/icons/openswe"; +import { OpenSWELogo } from "@/components/icons/openswe-logo"; type FilterStatus = | "all" @@ -128,7 +128,7 @@ function AllThreadsPageContent() {
- diff --git a/apps/web/src/components/icons/openswe-icon.tsx b/apps/web/src/components/icons/openswe-icon.tsx new file mode 100644 index 00000000..0244e766 --- /dev/null +++ b/apps/web/src/components/icons/openswe-icon.tsx @@ -0,0 +1,23 @@ +import { cn } from "@/lib/utils"; + +interface OpenSWEIconProps { + className?: string; +} + +export function OpenSWEIcon({ className }: OpenSWEIconProps) { + return ( + + + + ); +} diff --git a/apps/web/src/components/icons/openswe.tsx b/apps/web/src/components/icons/openswe-logo.tsx similarity index 99% rename from apps/web/src/components/icons/openswe.tsx rename to apps/web/src/components/icons/openswe-logo.tsx index 2efc4469..83edd052 100644 --- a/apps/web/src/components/icons/openswe.tsx +++ b/apps/web/src/components/icons/openswe-logo.tsx @@ -1,4 +1,6 @@ -export function OpenSWELogoSVG({ +import { cn } from "@/lib/utils"; + +export function OpenSWELogo({ className, width = 130, height = 20, @@ -16,7 +18,7 @@ export function OpenSWELogoSVG({ viewBox="0 0 1625 250" fill="none" xmlns="http://www.w3.org/2000/svg" - className={className} + className={cn(className)} style={style} > threadsToMetadata(threads), [threads]); const displayThreads = threadsMetadata.slice(0, 4); @@ -126,7 +130,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
- @@ -180,6 +184,8 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) { setAutoAcceptPlan={setAutoAccept} shouldCreateIssue={shouldCreateIssue} setShouldCreateIssue={setShouldCreateIssue} + customFramework={customFramework} + setCustomFramework={setCustomFramework} />
)} + setCustomFramework((prev) => !prev)} + side="bottom" + > + {customFramework ? ( + + ) : ( + + )} +
diff --git a/apps/web/src/components/v2/terminal-input.tsx b/apps/web/src/components/v2/terminal-input.tsx index 5767b6f5..744b9a40 100644 --- a/apps/web/src/components/v2/terminal-input.tsx +++ b/apps/web/src/components/v2/terminal-input.tsx @@ -41,6 +41,8 @@ interface TerminalInputProps { shouldCreateIssue: boolean; setShouldCreateIssue: Dispatch>; draftToLoad?: string; + customFramework: boolean; + setCustomFramework: Dispatch>; } const MISSING_API_KEYS_TOAST_CONTENT = ( @@ -76,6 +78,8 @@ export function TerminalInput({ shouldCreateIssue, setShouldCreateIssue, draftToLoad, + customFramework, + setCustomFramework, }: TerminalInputProps) { const { push } = useRouter(); const { message, setMessage, clearCurrentDraft } = useDraftStorage(); @@ -169,6 +173,7 @@ export function TerminalInput({ configurable: { ...defaultConfig, shouldCreateIssue, + customFramework, [GITHUB_USER_LOGIN_HEADER]: user.login, }, }, @@ -209,6 +214,11 @@ export function TerminalInput({ ? !!defaultConfig.shouldCreateIssue : true, ); + setCustomFramework( + defaultConfig?.customFramework != null + ? !!defaultConfig.customFramework + : false, + ); } catch (e) { if ( typeof e === "object" && diff --git a/packages/shared/src/open-swe/types.ts b/packages/shared/src/open-swe/types.ts index 87c42f37..4bf9338f 100644 --- a/packages/shared/src/open-swe/types.ts +++ b/packages/shared/src/open-swe/types.ts @@ -15,7 +15,6 @@ import { GITHUB_USER_ID_HEADER, GITHUB_USER_LOGIN_HEADER, GITHUB_PAT, - DEFAULT_MCP_SERVERS, GITHUB_INSTALLATION_ID, } from "../constants.js"; import { withLangGraph } from "@langchain/langgraph/zod"; @@ -429,9 +428,9 @@ export const GraphConfigurationMetadata: { mcpServers: { x_open_swe_ui_config: { type: "json", - default: JSON.stringify(DEFAULT_MCP_SERVERS, null, 2), + default: "{}", description: - "JSON configuration for custom MCP servers. LangGraph docs server is set by default. See the `mcpServers` field of the LangChain MCP Adapters `ClientConfig` type for information on this schema. [Documentation here](https://v03.api.js.langchain.com/types/_langchain_mcp_adapters.ClientConfig.html).", + "JSON configuration for custom MCP servers. LangGraph docs server is automatically added when custom LangGraph prompts are enabled. See the `mcpServers` field of the LangChain MCP Adapters `ClientConfig` type for information on this schema. [Documentation here](https://v03.api.js.langchain.com/types/_langchain_mcp_adapters.ClientConfig.html).", }, }, shouldCreateIssue: { @@ -442,6 +441,11 @@ export const GraphConfigurationMetadata: { "Whether or not to create GitHub issues for all requests. Can be overridden on a per-request basis via the 'eye' icon in the chat input area.", }, }, + customFramework: { + x_open_swe_ui_config: { + type: "hidden", + }, + }, reviewPullNumber: { x_open_swe_ui_config: { type: "hidden", @@ -619,6 +623,13 @@ export const GraphConfiguration = z.object({ shouldCreateIssue: withLangGraph(z.boolean().optional(), { metadata: GraphConfigurationMetadata.shouldCreateIssue, }), + /** + * Whether or not to use the custom framework for the request. + * @default false + */ + customFramework: withLangGraph(z.boolean().optional(), { + metadata: GraphConfigurationMetadata.customFramework, + }), /** * The pull request number that this run is associated with. * @default undefined diff --git a/packages/shared/src/open-swe/utils/config.ts b/packages/shared/src/open-swe/utils/config.ts index 60d8bd47..c13fb2ba 100644 --- a/packages/shared/src/open-swe/utils/config.ts +++ b/packages/shared/src/open-swe/utils/config.ts @@ -16,7 +16,7 @@ export function getCustomConfigurableFields( if (key in config.configurable) { if ( metadataValue.x_open_swe_ui_config.type !== "hidden" || - ["apiKeys", "reviewPullNumber"].includes(key) + ["apiKeys", "reviewPullNumber", "customFramework"].includes(key) ) { result[key as keyof GraphConfig["configurable"]] = config.configurable[key as keyof GraphConfig["configurable"]];